diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index c844db9778..cbc492488b 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -77,3 +77,4 @@ $ ->
TwoFactorAuth.attachTo('.two-factor-auth-container')
+
diff --git a/app/assets/javascripts/component_ui/auto_window.js.coffee b/app/assets/javascripts/component_ui/auto_window.js.coffee
index 7d08481cf7..2c075b432c 100644
--- a/app/assets/javascripts/component_ui/auto_window.js.coffee
+++ b/app/assets/javascripts/component_ui/auto_window.js.coffee
@@ -44,6 +44,7 @@ BORDER_WIDTH = 1
window_w = window.innerWidth
markets_w = $('#market_list').width()
order_book_w = $('#order_book').width()
+ $('#market_list .panel panel-default').width(markets_w)
$('#candlestick').width(window_w - order_book_w - markets_w - gutter_4x - 20)
@$node.resize()
diff --git a/app/assets/javascripts/component_ui/candlestick.js.coffee b/app/assets/javascripts/component_ui/candlestick.js.coffee
index f57ffb8050..6e94392f07 100644
--- a/app/assets/javascripts/component_ui/candlestick.js.coffee
+++ b/app/assets/javascripts/component_ui/candlestick.js.coffee
@@ -58,7 +58,10 @@ COLOR = {
candlestick: _.extend({}, COLOR_OFF.candlestick),
close: _.extend({}, COLOR_OFF.close)
}
-INDICATOR = {MA: false, EMA: false}
+INDICATOR = {MA: false, EMA: false, OFF: false}
+MACD_INDICATOR = {MACD: false, OFF: false}
+SIG_INDICATOR = {SIG: false, OFF: false}
+HIST_INDICATOR = {HIST: false, OFF: false}
@CandlestickUI = flight.component ->
@mask = ->
@@ -93,9 +96,8 @@ INDICATOR = {MA: false, EMA: false}
INDICATOR[data.x] = true
if chart = @$node.find('#candlestick_chart').highcharts()
- # reset all series depend on close
for s in chart.series
- if s.userOptions.linkedTo == 'close'
+ if s.userOptions.innerGroup == 'main'
s.setVisible(true, false)
for indicator, visible of INDICATOR
@@ -104,6 +106,21 @@ INDICATOR = {MA: false, EMA: false}
s.setVisible(visible, false)
chart.redraw()
+ @switchMACDIndicator = (event, data) ->
+ MACD_INDICATOR[key] = false for key, val of MACD_INDICATOR
+ MACD_INDICATOR[data.x] = true
+
+ if chart = @$node.find('#candlestick_chart').highcharts()
+ for s in chart.series
+ if s.userOptions.innerGroup == 'macd'
+ s.setVisible(MACD_INDICATOR['MACD'], false)
+
+# for indicator, visible of MACD_INDICATOR
+# for s in chart.series
+# if s.userOptions.algorithm? && (s.userOptions.algorithm == indicator)
+# s.setVisible(visible, false)
+ chart.redraw()
+
@default_range = (unit) ->
1000 * 60 * unit * 100
@@ -140,27 +157,33 @@ INDICATOR = {MA: false, EMA: false}
marginTop: 95
backgroundColor: 'rgba(0,0,0, 0.0)'
+ mapNavigation:
+ enableMouseWheelZoom: true
+
credits:
enabled: false
tooltip:
- crosshairs: [{
- width: 0.5,
- dashStyle: 'solid',
- color: '#777'
- }, false],
+# crosshairs: [{
+# width: 0.5,
+# dashStyle: 'solid',
+# color: '#777'
+# }, false],
valueDecimals: gon.market.bid.fixed
borderWidth: 0
backgroundColor: 'rgba(0,0,0,0)'
borderRadius: 2
shadow: false
shared: true
+ split: false
positioner: (w, h, point) ->
chart_w = $(@chart.renderTo).width()
chart_h = $(@chart.renderTo).height()
grid_h = Math.min(20, Math.ceil(chart_h/10))
- x = Math.max(10, point.plotX-w-20)
- y = Math.max(0, Math.floor(point.plotY/grid_h)*grid_h-20)
+ # x = Math.max(10, point.plotX-w-20)
+ # y = Math.max(0, Math.floor(point.plotY/grid_h)*grid_h-20)
+ x = 8
+ y = 8
x: x, y: y
useHTML: true
formatter: ->
@@ -181,7 +204,13 @@ INDICATOR = {MA: false, EMA: false}
tooltipTemplate
title: title
indicator: INDICATOR
- format: (v, fixed=3) -> Highcharts.numberFormat v, fixed
+ macd_indicator: MACD_INDICATOR
+ format: (v, fixed=3) -> formatter.fixPriceGroup parseFloat(v)
+ format4: (v, fixed) ->
+ if fixed > 4
+ formatter.round(v, 4)
+ else
+ formatter.round(v, fixed)
points: _.reduce chart.series, fun, {}
plotOptions:
@@ -222,6 +251,9 @@ INDICATOR = {MA: false, EMA: false}
maskFill: 'rgba(32, 32, 32, 0.6)'
outlineColor: '#333'
outlineWidth: 1
+ baseSeries: 'candlestick'
+ series:
+ color: '#306196'
xAxis:
dateTimeLabelFormats: DATETIME_LABEL_FORMAT
@@ -232,6 +264,20 @@ INDICATOR = {MA: false, EMA: false}
tickColor: '#333'
tickWidth: 2
range: range
+ maxPadding: 1
+ crosshair:
+ snap: false
+ width: 0.5
+ dashStyle: 'solid'
+ color: '#777'
+ interpolate: true
+ label:
+ enabled: true
+ formatter: (val) ->
+ Highcharts.dateFormat('%a %d %b %H:%M:%S', val)
+ backgroundColor: '#2b3434'
+ style:
+ color: '#aaa'
events:
afterSetExtremes: (e) ->
if e.trigger == 'navigator' && e.triggerOp == 'navigator-drag'
@@ -242,17 +288,31 @@ INDICATOR = {MA: false, EMA: false}
{
labels:
enabled: true
- align: 'right'
- formatter: -> Highcharts.numberFormat @.value, gon.market.bid.fixed
+ align: 'left'
+ reserveSpace: true
+ formatter: -> formatter.fixPriceGroup @.value
x: 2
y: -5
zIndex: -7
+# format: '{value:.9f}'
gridLineColor: '#222'
gridLineDashStyle: 'ShortDot'
top: "0%"
height: "70%"
lineColor: '#fff'
minRange: if gon.ticker.last then parseFloat(gon.ticker.last)/25 else null
+ crosshair:
+ snap: false
+ interpolate: true
+ width: 0.5
+ dashStyle: 'solid'
+ color: '#777'
+ label:
+ enabled: true
+ formatter: (v) -> formatter.fixPriceGroup parseFloat(v)
+ backgroundColor: '#2b3434'
+ style:
+ color: '#aaa'
}
{
labels:
@@ -260,6 +320,17 @@ INDICATOR = {MA: false, EMA: false}
top: "70%"
gridLineColor: '#000'
height: "15%"
+ crosshair:
+ snap: false
+ width: 0.5
+ dashStyle: 'solid'
+ color: '#777'
+ label:
+ enabled: true
+ format: '{value:.4f}'
+ backgroundColor: '#2b3434'
+ style:
+ color: '#aaa'
}
{
labels:
@@ -267,25 +338,15 @@ INDICATOR = {MA: false, EMA: false}
top: "85%"
gridLineColor: '#000'
height: "15%"
+ # crosshair:
+ # snap: false
+ # width: 0.5
+ # dashStyle: 'solid'
+ # color: '#777'
}
]
series: [
- _.extend({
- id: 'candlestick'
- name: gon.i18n.chart.candlestick
- type: "candlestick"
- data: data['candlestick']
- showInLegend: false
- }, COLOR['candlestick']),
- _.extend({
- id: 'close'
- type: 'spline'
- data: data['close']
- showInLegend: false
- marker:
- radius: 0
- }, COLOR['close']),
{
id: 'volume'
name: gon.i18n.chart.volume
@@ -299,12 +360,14 @@ INDICATOR = {MA: false, EMA: false}
id: 'ma5'
name: 'MA5',
linkedTo: 'close',
+ innerGroup: 'main',
showInLegend: true,
type: 'trendline',
algorithm: 'MA',
periods: 5
color: '#7c9aaa'
visible: INDICATOR['MA']
+ zIndex: 1
marker:
radius: 0
}
@@ -312,12 +375,14 @@ INDICATOR = {MA: false, EMA: false}
id: 'ma10'
name: 'MA10'
linkedTo: 'close',
+ innerGroup: 'main',
showInLegend: true,
type: 'trendline',
algorithm: 'MA',
periods: 10
color: '#be8f53'
visible: INDICATOR['MA']
+ zIndex: 2
marker:
radius: 0
}
@@ -325,12 +390,14 @@ INDICATOR = {MA: false, EMA: false}
id: 'ema7'
name: 'EMA7',
linkedTo: 'close',
+ innerGroup: 'main',
showInLegend: true,
type: 'trendline',
algorithm: 'EMA',
periods: 7
color: '#7c9aaa'
visible: INDICATOR['EMA']
+ zIndex: 3
marker:
radius: 0
}
@@ -338,12 +405,14 @@ INDICATOR = {MA: false, EMA: false}
id: 'ema30'
name: 'EMA30',
linkedTo: 'close',
+ innerGroup: 'main',
showInLegend: true,
type: 'trendline',
algorithm: 'EMA',
periods: 30
color: '#be8f53'
visible: INDICATOR['EMA']
+ zIndex: 4
marker:
radius: 0
}
@@ -351,11 +420,14 @@ INDICATOR = {MA: false, EMA: false}
id: 'macd'
name : 'MACD',
linkedTo: 'close',
+ innerGroup: 'macd',
yAxis: 2,
showInLegend: true,
type: 'trendline',
algorithm: 'MACD'
color: '#7c9aaa'
+ visible: MACD_INDICATOR['MACD']
+ zIndex: 5
marker:
radius: 0
}
@@ -363,11 +435,14 @@ INDICATOR = {MA: false, EMA: false}
id: 'sig'
name : 'SIG',
linkedTo: 'close',
+ innerGroup: 'macd',
yAxis: 2,
showInLegend: true,
type: 'trendline',
algorithm: 'signalLine'
color: '#be8f53'
+ visible: MACD_INDICATOR['MACD']
+ zIndex: 6
marker:
radius: 0
}
@@ -375,11 +450,30 @@ INDICATOR = {MA: false, EMA: false}
id: 'hist'
name: 'HIST',
linkedTo: 'close',
+ innerGroup: 'macd',
yAxis: 2,
showInLegend: true,
type: 'histogram'
+ visible: MACD_INDICATOR['MACD']
+ zIndex: 7
color: '#990f0f'
}
+ _.extend({
+ id: 'candlestick'
+ name: gon.i18n.chart.candlestick
+ type: "candlestick"
+ data: data['candlestick']
+ showInLegend: false
+ zIndex: 0
+ }, COLOR['candlestick']),
+ _.extend({
+ id: 'close'
+ type: 'spline'
+ data: data['close']
+ showInLegend: false
+ marker:
+ radius: 0
+ }, COLOR['close']),
]
@formatPointArray = (point) ->
@@ -438,5 +532,6 @@ INDICATOR = {MA: false, EMA: false}
@on document, 'market::candlestick::response', @init
@on document, 'market::candlestick::trades', @updateByTrades
@on document, 'switch::main_indicator_switch', @switchMainIndicator
+ @on document, 'switch::indicator_switch', @switchMACDIndicator
@on document, 'switch::type_switch', @switchType
diff --git a/app/assets/javascripts/component_ui/market_switch.js.coffee b/app/assets/javascripts/component_ui/market_switch.js.coffee
index 84bec42b9d..f013944ce3 100644
--- a/app/assets/javascripts/component_ui/market_switch.js.coffee
+++ b/app/assets/javascripts/component_ui/market_switch.js.coffee
@@ -14,6 +14,7 @@ window.MarketSwitchUI = flight.component ->
@select('marketGroupName').text item.find('span').text()
@select('marketsTable').attr("class", "table table-hover markets #{name}")
+ @select('marketsTable').attr("style", "font-size: 12px")
@updateMarket = (select, ticker) ->
trend = formatter.trend ticker.last_trend
@@ -43,11 +44,20 @@ window.MarketSwitchUI = flight.component ->
window.location.href = window.formatter.market_url($(@).data('market'))
@.hide_accounts = $('tr.hide')
+
$('.view_all_accounts').on 'click', (e) =>
$el = $(e.currentTarget)
- if @.hide_accounts.hasClass('hide')
- $el.text($el.data('hide-text'))
- @.hide_accounts.removeClass('hide')
- else
+ if @.hide_accounts.hasClass('show1')
$el.text($el.data('show-text'))
- @.hide_accounts.addClass('hide')
+ for acc in @.hide_accounts
+ if acc.lastChild.firstChild.textContent != '0.0000'
+ if acc.attributes['class'].value.indexOf('show1') > 0
+ acc.attributes['class'].value = acc.attributes['class'].value.substr(0, acc.attributes['class'].value.indexOf('show1') - 1)
+ acc.attributes['class'].value += " hide"
+ else if @.hide_accounts.hasClass('hide')
+ $el.text($el.data('hide-text'))
+ for acc in @.hide_accounts
+ if acc.lastChild.firstChild.textContent != '0.0000'
+ if acc.attributes['class'].value.indexOf('hide') > 0
+ acc.attributes['class'].value = acc.attributes['class'].value.substr(0, acc.attributes['class'].value.indexOf('hide') - 1)
+ acc.attributes['class'].value += " show1"
diff --git a/app/assets/javascripts/component_ui/order_total.js.coffee b/app/assets/javascripts/component_ui/order_total.js.coffee
index c3ab7b66ef..06b2f06cba 100644
--- a/app/assets/javascripts/component_ui/order_total.js.coffee
+++ b/app/assets/javascripts/component_ui/order_total.js.coffee
@@ -14,5 +14,13 @@
@changeOrder @value unless @validateRange(total)
@setInputValue @value
+ @fee = 0.0
+ if event.target.id == 'ask_entry'
+ @fee = gon.market.ask.fee
+ else
+ @fee = gon.market.bid.fee
+
order.total = @value
+ order.fee = @value * @fee
@trigger 'place_order::order::updated', order
+ console.log(event.target.id, order)
diff --git a/app/assets/javascripts/component_ui/place_order.js.coffee b/app/assets/javascripts/component_ui/place_order.js.coffee
index 6b4ffef5fe..4527d4e48e 100644
--- a/app/assets/javascripts/component_ui/place_order.js.coffee
+++ b/app/assets/javascripts/component_ui/place_order.js.coffee
@@ -3,9 +3,11 @@
formSel: 'form'
successSel: '.status-success'
infoSel: '.status-info'
+ feeSel: '.status-fee'
dangerSel: '.status-danger'
priceAlertSel: '.hint-price-disadvantage'
positionsLabelSel: '.hint-positions'
+ feeLabelSel: '.hint-fee'
priceSel: 'input[id$=price]'
volumeSel: 'input[id$=volume]'
@@ -23,6 +25,7 @@
@select('successSel').text('')
@select('infoSel').text('')
@select('dangerSel').text('')
+ @select('feeSel').text('')
@resetForm = (event) ->
@trigger 'place_order::reset::price'
@@ -104,11 +107,17 @@
order[@usedInput] = 0 unless order[@usedInput]
available = formatter.fix type, @getBalance().minus(order[@usedInput])
+ if @select('priceSel').val() != 0.0
+ @select('feeLabelSel').hide().text(formatter.fixPriceGroup(order.fee)).fadeIn()
+ else
+ @select('feeLabelSel').fadeOut().text('')
+
if BigNumber(available).equals(0)
@select('positionsLabelSel').hide().text(gon.i18n.place_order["full_#{type}"]).fadeIn()
else
@select('positionsLabelSel').fadeOut().text('')
node.text(available)
+ #console.log("priceorder/order_fee = " + order.fee)
@priceAlertHide = (event) ->
@select('priceAlertSel').fadeOut ->
diff --git a/app/assets/javascripts/funds.js.coffee b/app/assets/javascripts/funds.js.coffee
index 9ad8067ee5..74054f4d86 100644
--- a/app/assets/javascripts/funds.js.coffee
+++ b/app/assets/javascripts/funds.js.coffee
@@ -33,3 +33,6 @@
#= require_tree ./component_mixin
#= require_tree ./component_data
#= require_tree ./component_ui
+
+
+
diff --git a/app/assets/javascripts/locales/en.js.erb b/app/assets/javascripts/locales/en.js.erb
index 610d4146a0..c012dbc6c4 100644
--- a/app/assets/javascripts/locales/en.js.erb
+++ b/app/assets/javascripts/locales/en.js.erb
@@ -4,5 +4,3 @@
-
-
diff --git a/app/assets/javascripts/market.js.coffee b/app/assets/javascripts/market.js.coffee
index 8733a4e8e5..72d94b6e91 100644
--- a/app/assets/javascripts/market.js.coffee
+++ b/app/assets/javascripts/market.js.coffee
@@ -64,3 +64,4 @@ $ ->
$('.panel-body-content').niceScroll
autohidemode: true
cursorborder: "none"
+
diff --git a/app/assets/javascripts/templates/market_trade.jst.eco b/app/assets/javascripts/templates/market_trade.jst.eco
index d52e7b4117..0a882c5dd1 100644
--- a/app/assets/javascripts/templates/market_trade.jst.eco
+++ b/app/assets/javascripts/templates/market_trade.jst.eco
@@ -4,11 +4,11 @@
<%- formatter.trade_time @date %>
-
+
-
- <%- formatter.mask_fixed_price @price %>
+
+ <%- formatter.ticker_price @price %>
<%- formatter.mask_fixed_volume @amount %>
diff --git a/app/assets/javascripts/templates/order_active.jst.eco b/app/assets/javascripts/templates/order_active.jst.eco
index c4c4d3631c..25485a28ed 100644
--- a/app/assets/javascripts/templates/order_active.jst.eco
+++ b/app/assets/javascripts/templates/order_active.jst.eco
@@ -4,16 +4,16 @@
<%- formatter.fulltime @at %>
-
+
<%= formatter.short_trade @kind %> <%= gon.i18n.trade_state[trade_state] %>
-
+
<%- formatter.mask_fixed_price @price %>
-
+
<%- formatter.mask_fixed_volume @volume %>
-
+
<%- formatter.amount @volume, @price %>
diff --git a/app/assets/javascripts/templates/order_book_ask.jst.eco b/app/assets/javascripts/templates/order_book_ask.jst.eco
index 6577a0099c..7f17a3c2c3 100644
--- a/app/assets/javascripts/templates/order_book_ask.jst.eco
+++ b/app/assets/javascripts/templates/order_book_ask.jst.eco
@@ -1,8 +1,8 @@
- <%- formatter.mask_fixed_price @price %>
+ <%- formatter.ticker_price @price %>
-
+
<%- formatter.mask_fixed_volume @volume %>
diff --git a/app/assets/javascripts/templates/order_book_bid.jst.eco b/app/assets/javascripts/templates/order_book_bid.jst.eco
index c524443577..e91291f00c 100644
--- a/app/assets/javascripts/templates/order_book_bid.jst.eco
+++ b/app/assets/javascripts/templates/order_book_bid.jst.eco
@@ -1,11 +1,11 @@
-
+
<%- formatter.amount @volume, @price %>
-
+
<%- formatter.mask_fixed_volume @volume %>
- <%- formatter.mask_fixed_price @price %>
+ <%- formatter.ticker_price @price %>
diff --git a/app/assets/javascripts/templates/order_done.jst.eco b/app/assets/javascripts/templates/order_done.jst.eco
index 58917c27ae..b85134ffee 100644
--- a/app/assets/javascripts/templates/order_done.jst.eco
+++ b/app/assets/javascripts/templates/order_done.jst.eco
@@ -2,16 +2,16 @@
<%- formatter.fulltime @at %>
-
+
<%- formatter.short_trade @kind %>
-
+
<%= formatter.fix_bid @price %>
-
+
<%= formatter.fix_ask @volume %>
-
+
<%- formatter.amount @volume, @price %>
diff --git a/app/assets/javascripts/templates/tooltip.jst.eco b/app/assets/javascripts/templates/tooltip.jst.eco
index b9f249cc68..2187d74e18 100644
--- a/app/assets/javascripts/templates/tooltip.jst.eco
+++ b/app/assets/javascripts/templates/tooltip.jst.eco
@@ -21,35 +21,37 @@
<%= gon.i18n.chart.volume %>
- <%= @format @points.volume.y, gon.market.ask.fixed %>
+ <%= @format4 @points.volume.y, 3 %>
<% if @indicator['MA']: %>
- <%= @points.ma5.series.name %>: <%= @format @points.ma5.y, gon.market.bid.fixed %>
+ <%= @points.ma5.series.name %>: <%= @format4 @points.ma5.y, gon.market.bid.fixed %>
- <%= @points.ma10.series.name %>: <%= @format @points.ma10.y, gon.market.bid.fixed %>
+ <%= @points.ma10.series.name %>: <%= @format4 @points.ma10.y, gon.market.bid.fixed %>
<% end %>
<% if @indicator['EMA']: %>
- <%= @points.ema7.series.name %>: <%= @format @points.ema7.y, gon.market.bid.fixed %>
+ <%= @points.ema7.series.name %>: <%= @format4 @points.ema7.y, gon.market.bid.fixed %>
- <%= @points.ema30.series.name %>: <%= @format @points.ema30.y, gon.market.bid.fixed %>
+ <%= @points.ema30.series.name %>: <%= @format4 @points.ema30.y, gon.market.bid.fixed %>
+
+ <% end %>
+ <% if @macd_indicator['MACD']: %>
+
+ <%= @points.macd.series.name %>: <%= @format4 @points.macd.y %>
+
+
+ <%= @points.sig.series.name %>: <%= @format4 @points.sig.y %>
+
+
+ <%= @points.hist.series.name %>: <%= @format4 @points.hist.y %>
<% end %>
-
- <%= @points.macd.series.name %>: <%= @format @points.macd.y %>
-
-
- <%= @points.sig.series.name %>: <%= @format @points.sig.y %>
-
-
- <%= @points.hist.series.name %>: <%= @format @points.hist.y %>
-
diff --git a/app/assets/stylesheets/market.css.scss b/app/assets/stylesheets/market.css.scss
index 74ccebe865..00cad8915d 100644
--- a/app/assets/stylesheets/market.css.scss
+++ b/app/assets/stylesheets/market.css.scss
@@ -479,8 +479,8 @@ chat_tabs_wrapper {
.last {
padding-top: 5px;
- font-size: 34px;
- line-height: 34px;
+ font-size: 24px;
+ line-height: 42px;
}
}
@@ -495,6 +495,11 @@ chat_tabs_wrapper {
table.asks, table.bids {
font-size: 12px;
+
+ td.price {
+ .fill { color: #333; }
+ }
+
.new {
background-color: #333;
div { display: none; }
@@ -570,6 +575,10 @@ chat_tabs_wrapper {
}
.panel-body-content {
min-height: $market-trades-height - $panel_table_header_high;
+
+ td.price {
+ .fill { color: #333; }
+ }
}
table {
@@ -730,29 +739,38 @@ chat_tabs_wrapper {
input {
text-align: right;
- padding-top: 8px !important;
+ padding-top: 6px !important;
padding-bottom: 4px !important;
padding-right: 6px !important;
- padding-left: 24px !important;
+ padding-left: 6px !important;
+ font-size: 13px
}
margin-bottom: 8px;
}
.hint-price-disadvantage, .hint-positions {
- top: 3px;
- left: 60px;
+ top: 2px;
+ left: 72px;
+ margin-left: 0px;
+ position: absolute;
+ z-index: 10;
+ }
+
+ .hint-fee {
+ top: 14px;
+ left: 72px;
margin-left: 0px;
position: absolute;
z-index: 10;
}
.label { font-size: inherit; }
- .label.label-info { color: $brand-info }
+ .label.label-info { color: $brand-info; font-size: 9px; padding: 0 0 0 0 }
.label.label-success { color: $brand-success }
- .label.label-danger { color: $brand-danger }
+ .label.label-danger { color: $brand-danger; font-size: 9px; padding: 0 0 0 0 }
- span.status-info, .status-success, .status-danger {
+ span.status-info, .status-success, .status-danger, .status-fee {
top: -3px;
left: -6px;
margin-left: 0px;
diff --git a/app/assets/stylesheets/vars/_market.css.scss b/app/assets/stylesheets/vars/_market.css.scss
index 7a307a3e45..4f2c382521 100644
--- a/app/assets/stylesheets/vars/_market.css.scss
+++ b/app/assets/stylesheets/vars/_market.css.scss
@@ -13,8 +13,8 @@ $gutter-4x: $gutter * 4;
$gutter-5x: $gutter * 5;
$gutter-6x: $gutter * 6;
-$entry-width: 242px;
-$entry-height: 576px;
+$entry-width: 332px;
+$entry-height: 496px;
$right-entry-height: 196px;
$ticker-width: $entry-width * 2 + $gutter;
@@ -24,7 +24,7 @@ $depths-width: $entry-width * 2 + $gutter;
$depths-height: 102px;
$my-orders-width: $depths-width;
-$my-orders-height: 92px;
+$my-orders-height: 184px;
$order-book-width: $depths-width;
$order-book-height: $min-height - $navbar-height - $right-entry-height - $ticker-height - $depths-height - $my-orders-height - $gutter-6x;
diff --git a/app/models/currency.rb b/app/models/currency.rb
index 1f10289173..5d4e251a60 100644
--- a/app/models/currency.rb
+++ b/app/models/currency.rb
@@ -98,6 +98,7 @@ def refresh_status
end
if @local_status
+ Rails.logger.info @local_status
Rails.cache.write(blocks_count_cache_key, @local_status[:blocks]) if coin?
Rails.cache.write(headers_count_cache_key, @local_status[:headers]) if coin?
Rails.cache.write(blocktime_cache_key, Time.at(@local_status[:mediantime]).to_datetime.strftime("%Y-%m-%d %H:%M:%S")) if coin?
diff --git a/app/models/global.rb b/app/models/global.rb
index 128bcfc9c6..435d0c0fe7 100644
--- a/app/models/global.rb
+++ b/app/models/global.rb
@@ -10,7 +10,7 @@ def channel
def trigger(event, data)
Pusher.trigger_async(channel, event, data)
- Rails.logger.info "Pusher_3: #{channel}, #{data}"
+ # Rails.logger.info "Pusher_3: #{channel}, #{data}"
end
def daemon_statuses
diff --git a/app/models/worker/order_processor.rb b/app/models/worker/order_processor.rb
index ccc32831ec..2a865d488c 100644
--- a/app/models/worker/order_processor.rb
+++ b/app/models/worker/order_processor.rb
@@ -4,6 +4,7 @@ class OrderProcessor
def initialize
@cancel_queue = []
create_cancel_thread
+ #create_test_thread
end
def process(payload, metadata, delivery_info)
@@ -63,6 +64,42 @@ def process_cancel_jobs
Rails.logger.debug $!.backtrace.join("\n")
end
+ def create_test_order
+ member_rand = rand(10)
+ member1 = 2
+ member2 = 3
+ if member_rand >= 5
+ member1 = 3
+ member2 = 2
+ end
+ order_bid = OrderBid.new(
+ source: 'APIv2',
+ state: ::Order::WAIT,
+ member_id: member1,
+ ask: 'gio',
+ bid: 'btc',
+ currency: 4,
+ ord_type: 'limit',
+ price: 1+rand(100)/10.0,
+ volume: 0.01,
+ origin_volume: 0.01
+ )
+ Ordering.new(order_bid).submit
+ order_ask = OrderAsk.new(
+ source: 'APIv2',
+ state: ::Order::WAIT,
+ member_id: member2,
+ ask: 'gio',
+ bid: 'btc',
+ currency: 4,
+ ord_type: 'limit',
+ price: 1+rand(100)/10.0,
+ volume: 0.01,
+ origin_volume: 0.01
+ )
+ Ordering.new(order_ask).submit
+ end
+
def create_cancel_thread
Thread.new do
loop do
@@ -72,5 +109,14 @@ def create_cancel_thread
end
end
+ def create_test_thread
+ Thread.new do
+ loop do
+ sleep 5
+ create_test_order
+ end
+ end
+ end
+
end
end
diff --git a/app/services/coin_rpc.rb b/app/services/coin_rpc.rb
index 73fad12910..beded8731e 100644
--- a/app/services/coin_rpc.rb
+++ b/app/services/coin_rpc.rb
@@ -108,12 +108,13 @@ def safe_getbalance
def getblockchaininfo
@lastBlock = eth_getBlockByNumber("latest", true)
- Rails.logger.info @lastBlock
+ #Rails.logger.info @lastBlock
+ #Rails.logger.info "number = " + Integer(@lastBlock[:number]).to_s + ", timestamp = " + Integer(@lastBlock[:timestamp]).to_s
{
- blocks: @lastBlock[:number].to_i(10),
+ blocks: Integer(@lastBlock[:number]),
headers: 0,
- mediantime: @lastBlock[:timestamp].to_i(10)
+ mediantime: Integer(@lastBlock[:timestamp])
}
end
diff --git a/app/views/private/markets/_ask_entry.html.slim b/app/views/private/markets/_ask_entry.html.slim
index 4ba1ecbefa..50b1c44bb9 100644
--- a/app/views/private/markets/_ask_entry.html.slim
+++ b/app/views/private/markets/_ask_entry.html.slim
@@ -14,6 +14,7 @@
span.input-group-addon = t(market.ask['currency'], scope: 'market.currency')
.input-group.total
label.input-group-addon = t('.total')
+ span.label.label-info.hint-fee
= f.text_field :total, class: 'form-control'
span.input-group-addon = t(market.bid['currency'], scope: 'market.currency')
= render partial: 'balance', locals: {currency: t(market.ask['currency'], scope: 'market.currency')}
diff --git a/app/views/private/markets/_balance.html.slim b/app/views/private/markets/_balance.html.slim
index d6afe1191e..7f01de0c54 100644
--- a/app/views/private/markets/_balance.html.slim
+++ b/app/views/private/markets/_balance.html.slim
@@ -3,5 +3,6 @@
span.label.label-info.status-info
span.label.label-success.status-success
span.label.label-danger.status-danger
+ span.label.label-info.status-fee
span.current-balance.number
span.unit = currency
diff --git a/app/views/private/markets/_bid_entry.html.slim b/app/views/private/markets/_bid_entry.html.slim
index ed2c355835..915564592f 100644
--- a/app/views/private/markets/_bid_entry.html.slim
+++ b/app/views/private/markets/_bid_entry.html.slim
@@ -14,6 +14,7 @@
.input-group.total
label.input-group-addon = t('.total')
span.label.label-info.hint-positions
+ span.label.label-info.hint-fee
= f.text_field :total, class: 'form-control'
span.input-group-addon = t(market.bid['currency'], scope: 'market.currency')
= render partial: 'balance', locals: {currency: t(market.bid['currency'], scope: 'market.currency')}
diff --git a/app/views/private/markets/_indicator_switch.html.slim b/app/views/private/markets/_indicator_switch.html.slim
index d7e0c6fe0f..910e5b56e2 100644
--- a/app/views/private/markets/_indicator_switch.html.slim
+++ b/app/views/private/markets/_indicator_switch.html.slim
@@ -7,6 +7,9 @@
li: ul#main_indicator_switch.indicator_switch.list-inline data-x='MA'
li.text-center: a.hand-point data-x='MA' href='#' MA
li.text-center: a.hand-point data-x='EMA' href='#' EMA
+ li.text-center: a.hand-point data-x='OFF' href='#' x
li.text-center: span |
li: ul#indicator_switch.indicator_switch.list-inline data-x='MACD'
li.text-center: a.hand-point data-x='MACD' href='#' MACD
+ li.text-center: a.hand-point data-x='OFF' href='#' x
+
diff --git a/app/views/private/markets/_market_list.html.slim b/app/views/private/markets/_market_list.html.slim
index 0fb82b9fb9..9d2c486611 100644
--- a/app/views/private/markets/_market_list.html.slim
+++ b/app/views/private/markets/_market_list.html.slim
@@ -18,7 +18,7 @@
table.table.table-hover.markets.all
tbody
- @markets.each do |market|
- tr.market id="market-list-#{market.id}" class="quote-#{market.quote_unit}" data-market=market.id
+ tr.market id="market-list-#{market.id}" class="quote-#{market.quote_unit}" style="font-size: 12px" data-market=market.id
td.col-xs-4.name
= link_to market.name, market_path(market)
td.col-xs-15.price
diff --git a/app/views/private/markets/_market_trades.html.slim b/app/views/private/markets/_market_trades.html.slim
index d31dc8e443..81459c2e7c 100644
--- a/app/views/private/markets/_market_trades.html.slim
+++ b/app/views/private/markets/_market_trades.html.slim
@@ -10,7 +10,7 @@
thead: tr
th.at.col-xs-5.text-left #{t('.time')}
th.my.col-xs-1.text-left
- th.price.col-xs-6.text-right #{t('.price')}
+ th[class='price col-xs-6 text-right' style='padding: 0 10px 0 0'] #{t('.price')}
th.volume.col-xs-5.text-right #{t('.volume')}
.panel-body.panel-body-content
table.table.table-hover.all-trades
diff --git a/app/views/private/markets/_order_book.html.slim b/app/views/private/markets/_order_book.html.slim
index 99d93cac5f..0baedd22e9 100644
--- a/app/views/private/markets/_order_book.html.slim
+++ b/app/views/private/markets/_order_book.html.slim
@@ -3,13 +3,13 @@
#order_book_header.row
.col-xs-12.col-left: table.table
thead: tr
- th.amount.text-left.col-xs-8 = t('.amount')
- th.volume.text-left.col-xs-10 = t('.volume')
- th.price.text-right.col-xs-12 = t('.bid')
+ th[class='amount text-right col-xs-6' style='padding: 0 15px 0 0'] = t('.amount')
+ th[class='volume text-right col-xs-6' style='padding: 0 3px 0 0'] = t('.volume')
+ th[class='price text-right col-xs-6' style='padding: 0 0 0 0'] = t('.bid')
.col-xs-12: table.table.table-head
thead: tr
- th[class='price text-left col-xs-12' style='padding: 0 0 0 0'] = t('.ask')
- th[class='volume text-left col-xs-10' style='padding: 0 0 0 0'] = t('.volume')
+ th[class='price text-right col-xs-7' style='padding: 0 0 0 0'] = t('.ask')
+ th[class='volume text-right col-xs-8' style='padding: 0 0 0 0'] = t('.volume')
th.amount.text-right.col-xs-10 = t('.amount')
.panel-body.panel-body-content
#order_book_body.row
diff --git a/config/initializers/pusher.rb b/config/initializers/pusher.rb
index 067bbd6a15..01b1ef9115 100644
--- a/config/initializers/pusher.rb
+++ b/config/initializers/pusher.rb
@@ -1,5 +1,5 @@
Pusher.app_id = ENV['PUSHER_APP']
Pusher.key = ENV['PUSHER_KEY']
Pusher.secret = ENV['PUSHER_SECRET']
-Pusher.host = ENV['PUSHER_HOST'] || 'api.pusherapp.com'
+Pusher.host = ENV['PUSHER_INNER_HOST'] || 'api.pusherapp.com'
Pusher.port = ENV['PUSHER_PORT'].present? ? ENV['PUSHER_PORT'].to_i : 80
diff --git a/config/nginx.conf b/config/nginx.conf
index e2dbd06952..6ebff5390e 100644
--- a/config/nginx.conf
+++ b/config/nginx.conf
@@ -35,6 +35,8 @@
# return 301 https://graviex.net$request_uri;
#}
+limit_conn_zone $binary_remote_addr zone=perip:10m;
+
server {
# listen 443 ssl spdy default;
# server_name graviex.net www.graviex.net;
@@ -61,24 +63,27 @@ server {
resolver_timeout 5s;
location = /favicon.png {
+ limit_conn perip 3;
expires max;
add_header Cache-Control public;
}
location = /ZeroClipboard.swf {
+ limit_conn perip 3;
expires max;
add_header Cache-Control public;
}
location ~ ^/(assets)/ {
+ limit_conn perip 3;
gzip_static on;
expires max;
add_header Cache-Control public;
-
}
# disable gzip on all omniauth paths to prevent BREACH
location ~ ^/auth/ {
+ limit_conn perip 3;
gzip off;
passenger_enabled on;
}
diff --git a/vendor/assets/javascripts/highstock.js b/vendor/assets/javascripts/highstock.js
index 8b69546e11..3fd43cc03d 100644
--- a/vendor/assets/javascripts/highstock.js
+++ b/vendor/assets/javascripts/highstock.js
@@ -1,388 +1,43493 @@
+/**
+ * @license Highcharts JS v5aaca1890988fc7073f112a2ff1556645b8dda5e (2018-01-26)
+ *
+ * (c) 2009-2016 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */'use strict';
+(function (root, factory) {
+if (typeof module === 'object' && module.exports) {
+module.exports = root.document ?
+factory(root) :
+factory;
+} else {
+root.Highcharts = factory(root);
+}
+}(typeof window !== 'undefined' ? window : this, function (win) {
+var Highcharts = (function () {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+/* global win, window */
+
+// glob is a temporary fix to allow our es-modules to work.
+var glob = typeof win === 'undefined' ? window : win,
+ doc = glob.document,
+ SVG_NS = 'http://www.w3.org/2000/svg',
+ userAgent = (glob.navigator && glob.navigator.userAgent) || '',
+ svg = doc && doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
+ isMS = /(edge|msie|trident)/i.test(userAgent) && !glob.opera,
+ isFirefox = /Firefox/.test(userAgent),
+ hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4; // issue #38;
+
+var Highcharts = glob.Highcharts ? glob.Highcharts.error(16, true) : {
+ product: 'Highcharts',
+ version: '5aaca1890988fc7073f112a2ff1556645b8dda5e',
+ deg2rad: Math.PI * 2 / 360,
+ doc: doc,
+ hasBidiBug: hasBidiBug,
+ hasTouch: doc && doc.documentElement.ontouchstart !== undefined,
+ isMS: isMS,
+ isWebKit: /AppleWebKit/.test(userAgent),
+ isFirefox: isFirefox,
+ isTouchDevice: /(Mobile|Android|Windows Phone)/.test(userAgent),
+ SVG_NS: SVG_NS,
+ chartCount: 0,
+ seriesTypes: {},
+ symbolSizes: {},
+ svg: svg,
+ win: glob,
+ marginNames: ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'],
+ noop: function () {
+ return undefined;
+ },
+ /**
+ * An array containing the current chart objects in the page. A chart's
+ * position in the array is preserved throughout the page's lifetime. When
+ * a chart is destroyed, the array item becomes `undefined`.
+ * @type {Array.}
+ * @memberOf Highcharts
+ */
+ charts: []
+};
+return Highcharts;
+}());
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+/* eslint max-len: ["warn", 80, 4] */
+
+/**
+ * The Highcharts object is the placeholder for all other members, and various
+ * utility functions. The most important member of the namespace would be the
+ * chart constructor.
+ *
+ * @example
+ * var chart = Highcharts.chart('container', { ... });
+ *
+ * @namespace Highcharts
+ */
+
+H.timers = [];
+
+var charts = H.charts,
+ doc = H.doc,
+ win = H.win;
+
+/**
+ * Provide error messages for debugging, with links to online explanation. This
+ * function can be overridden to provide custom error handling.
+ *
+ * @function #error
+ * @memberOf Highcharts
+ * @param {Number|String} code - The error code. See [errors.xml]{@link
+ * https://github.com/highcharts/highcharts/blob/master/errors/errors.xml}
+ * for available codes. If it is a string, the error message is printed
+ * directly in the console.
+ * @param {Boolean} [stop=false] - Whether to throw an error or just log a
+ * warning in the console.
+ *
+ * @sample highcharts/chart/highcharts-error/ Custom error handler
+ */
+H.error = function (code, stop) {
+ var msg = H.isNumber(code) ?
+ 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code :
+ code;
+ if (stop) {
+ throw new Error(msg);
+ }
+ // else ...
+ if (win.console) {
+ console.log(msg); // eslint-disable-line no-console
+ }
+};
+
+/**
+ * An animator object used internally. One instance applies to one property
+ * (attribute or style prop) on one element. Animation is always initiated
+ * through {@link SVGElement#animate}.
+ *
+ * @constructor Fx
+ * @memberOf Highcharts
+ * @param {HTMLDOMElement|SVGElement} elem - The element to animate.
+ * @param {AnimationOptions} options - Animation options.
+ * @param {string} prop - The single attribute or CSS property to animate.
+ * @private
+ *
+ * @example
+ * var rect = renderer.rect(0, 0, 10, 10).add();
+ * rect.animate({ width: 100 });
+ */
+H.Fx = function (elem, options, prop) {
+ this.options = options;
+ this.elem = elem;
+ this.prop = prop;
+};
+H.Fx.prototype = {
+
+ /**
+ * Set the current step of a path definition on SVGElement.
+ *
+ * @function #dSetter
+ * @memberOf Highcharts.Fx
+ */
+ dSetter: function () {
+ var start = this.paths[0],
+ end = this.paths[1],
+ ret = [],
+ now = this.now,
+ i = start.length,
+ startVal;
+
+ // Land on the final path without adjustment points appended in the ends
+ if (now === 1) {
+ ret = this.toD;
+
+ } else if (i === end.length && now < 1) {
+ while (i--) {
+ startVal = parseFloat(start[i]);
+ ret[i] =
+ isNaN(startVal) ? // a letter instruction like M or L
+ end[i] :
+ now * (parseFloat(end[i] - startVal)) + startVal;
+
+ }
+ // If animation is finished or length not matching, land on right value
+ } else {
+ ret = end;
+ }
+ this.elem.attr('d', ret, null, true);
+ },
+
+ /**
+ * Update the element with the current animation step.
+ *
+ * @function #update
+ * @memberOf Highcharts.Fx
+ */
+ update: function () {
+ var elem = this.elem,
+ prop = this.prop, // if destroyed, it is null
+ now = this.now,
+ step = this.options.step;
+
+ // Animation setter defined from outside
+ if (this[prop + 'Setter']) {
+ this[prop + 'Setter']();
+
+ // Other animations on SVGElement
+ } else if (elem.attr) {
+ if (elem.element) {
+ elem.attr(prop, now, null, true);
+ }
+
+ // HTML styles, raw HTML content like container size
+ } else {
+ elem.style[prop] = now + this.unit;
+ }
+
+ if (step) {
+ step.call(elem, now, this);
+ }
+
+ },
+
+ /**
+ * Run an animation.
+ *
+ * @function #run
+ * @memberOf Highcharts.Fx
+ * @param {Number} from - The current value, value to start from.
+ * @param {Number} to - The end value, value to land on.
+ * @param {String} [unit] - The property unit, for example `px`.
+ *
+ */
+ run: function (from, to, unit) {
+ var self = this,
+ options = self.options,
+ timer = function (gotoEnd) {
+ return timer.stopped ? false : self.step(gotoEnd);
+ },
+ requestAnimationFrame =
+ win.requestAnimationFrame ||
+ function (step) {
+ setTimeout(step, 13);
+ },
+ step = function () {
+ for (var i = 0; i < H.timers.length; i++) {
+ if (!H.timers[i]()) {
+ H.timers.splice(i--, 1);
+ }
+ }
+
+ if (H.timers.length) {
+ requestAnimationFrame(step);
+ }
+ };
+
+ if (from === to) {
+ delete options.curAnim[this.prop];
+ if (options.complete && H.keys(options.curAnim).length === 0) {
+ options.complete.call(this.elem);
+ }
+ } else { // #7166
+ this.startTime = +new Date();
+ this.start = from;
+ this.end = to;
+ this.unit = unit;
+ this.now = this.start;
+ this.pos = 0;
+
+ timer.elem = this.elem;
+ timer.prop = this.prop;
+
+ if (timer() && H.timers.push(timer) === 1) {
+ requestAnimationFrame(step);
+ }
+ }
+ },
+
+ /**
+ * Run a single step in the animation.
+ *
+ * @function #step
+ * @memberOf Highcharts.Fx
+ * @param {Boolean} [gotoEnd] - Whether to go to the endpoint of the
+ * animation after abort.
+ * @returns {Boolean} Returns `true` if animation continues.
+ */
+ step: function (gotoEnd) {
+ var t = +new Date(),
+ ret,
+ done,
+ options = this.options,
+ elem = this.elem,
+ complete = options.complete,
+ duration = options.duration,
+ curAnim = options.curAnim;
+
+ if (elem.attr && !elem.element) { // #2616, element is destroyed
+ ret = false;
+
+ } else if (gotoEnd || t >= duration + this.startTime) {
+ this.now = this.end;
+ this.pos = 1;
+ this.update();
+
+ curAnim[this.prop] = true;
+
+ done = true;
+
+ H.objectEach(curAnim, function (val) {
+ if (val !== true) {
+ done = false;
+ }
+ });
+
+ if (done && complete) {
+ complete.call(elem);
+ }
+ ret = false;
+
+ } else {
+ this.pos = options.easing((t - this.startTime) / duration);
+ this.now = this.start + ((this.end - this.start) * this.pos);
+ this.update();
+ ret = true;
+ }
+ return ret;
+ },
+
+ /**
+ * Prepare start and end values so that the path can be animated one to one.
+ *
+ * @function #initPath
+ * @memberOf Highcharts.Fx
+ * @param {SVGElement} elem - The SVGElement item.
+ * @param {String} fromD - Starting path definition.
+ * @param {Array} toD - Ending path definition.
+ * @returns {Array} An array containing start and end paths in array form
+ * so that they can be animated in parallel.
+ */
+ initPath: function (elem, fromD, toD) {
+ fromD = fromD || '';
+ var shift,
+ startX = elem.startX,
+ endX = elem.endX,
+ bezier = fromD.indexOf('C') > -1,
+ numParams = bezier ? 7 : 3,
+ fullLength,
+ slice,
+ i,
+ start = fromD.split(' '),
+ end = toD.slice(), // copy
+ isArea = elem.isArea,
+ positionFactor = isArea ? 2 : 1,
+ reverse;
+
+ /**
+ * In splines make moveTo and lineTo points have six parameters like
+ * bezier curves, to allow animation one-to-one.
+ */
+ function sixify(arr) {
+ var isOperator,
+ nextIsOperator;
+ i = arr.length;
+ while (i--) {
+
+ // Fill in dummy coordinates only if the next operator comes
+ // three places behind (#5788)
+ isOperator = arr[i] === 'M' || arr[i] === 'L';
+ nextIsOperator = /[a-zA-Z]/.test(arr[i + 3]);
+ if (isOperator && nextIsOperator) {
+ arr.splice(
+ i + 1, 0,
+ arr[i + 1], arr[i + 2],
+ arr[i + 1], arr[i + 2]
+ );
+ }
+ }
+ }
+
+ /**
+ * Insert an array at the given position of another array
+ */
+ function insertSlice(arr, subArr, index) {
+ [].splice.apply(
+ arr,
+ [index, 0].concat(subArr)
+ );
+ }
+
+ /**
+ * If shifting points, prepend a dummy point to the end path.
+ */
+ function prepend(arr, other) {
+ while (arr.length < fullLength) {
+
+ // Move to, line to or curve to?
+ arr[0] = other[fullLength - arr.length];
+
+ // Prepend a copy of the first point
+ insertSlice(arr, arr.slice(0, numParams), 0);
+
+ // For areas, the bottom path goes back again to the left, so we
+ // need to append a copy of the last point.
+ if (isArea) {
+ insertSlice(
+ arr,
+ arr.slice(arr.length - numParams), arr.length
+ );
+ i--;
+ }
+ }
+ arr[0] = 'M';
+ }
+
+ /**
+ * Copy and append last point until the length matches the end length
+ */
+ function append(arr, other) {
+ var i = (fullLength - arr.length) / numParams;
+ while (i > 0 && i--) {
+
+ // Pull out the slice that is going to be appended or inserted.
+ // In a line graph, the positionFactor is 1, and the last point
+ // is sliced out. In an area graph, the positionFactor is 2,
+ // causing the middle two points to be sliced out, since an area
+ // path starts at left, follows the upper path then turns and
+ // follows the bottom back.
+ slice = arr.slice().splice(
+ (arr.length / positionFactor) - numParams,
+ numParams * positionFactor
+ );
+
+ // Move to, line to or curve to?
+ slice[0] = other[fullLength - numParams - (i * numParams)];
+
+ // Disable first control point
+ if (bezier) {
+ slice[numParams - 6] = slice[numParams - 2];
+ slice[numParams - 5] = slice[numParams - 1];
+ }
+
+ // Now insert the slice, either in the middle (for areas) or at
+ // the end (for lines)
+ insertSlice(arr, slice, arr.length / positionFactor);
+
+ if (isArea) {
+ i--;
+ }
+ }
+ }
+
+ if (bezier) {
+ sixify(start);
+ sixify(end);
+ }
+
+ // For sideways animation, find out how much we need to shift to get the
+ // start path Xs to match the end path Xs.
+ if (startX && endX) {
+ for (i = 0; i < startX.length; i++) {
+ // Moving left, new points coming in on right
+ if (startX[i] === endX[0]) {
+ shift = i;
+ break;
+ // Moving right
+ } else if (startX[0] ===
+ endX[endX.length - startX.length + i]) {
+ shift = i;
+ reverse = true;
+ break;
+ }
+ }
+ if (shift === undefined) {
+ start = [];
+ }
+ }
+
+ if (start.length && H.isNumber(shift)) {
+
+ // The common target length for the start and end array, where both
+ // arrays are padded in opposite ends
+ fullLength = end.length + shift * positionFactor * numParams;
+
+ if (!reverse) {
+ prepend(end, start);
+ append(start, end);
+ } else {
+ prepend(start, end);
+ append(end, start);
+ }
+ }
+
+ return [start, end];
+ }
+}; // End of Fx prototype
+
+/**
+ * Handle animation of the color attributes directly.
+ */
+H.Fx.prototype.fillSetter =
+H.Fx.prototype.strokeSetter = function () {
+ this.elem.attr(
+ this.prop,
+ H.color(this.start).tweenTo(H.color(this.end), this.pos),
+ null,
+ true
+ );
+};
+
+
+/**
+ * Utility function to deep merge two or more objects and return a third object.
+ * If the first argument is true, the contents of the second object is copied
+ * into the first object. The merge function can also be used with a single
+ * object argument to create a deep copy of an object.
+ *
+ * @function #merge
+ * @memberOf Highcharts
+ * @param {Boolean} [extend] - Whether to extend the left-side object (a) or
+ return a whole new object.
+ * @param {Object} a - The first object to extend. When only this is given, the
+ function returns a deep copy.
+ * @param {...Object} [n] - An object to merge into the previous one.
+ * @returns {Object} - The merged object. If the first argument is true, the
+ * return is the same as the second argument.
+ */
+H.merge = function () {
+ var i,
+ args = arguments,
+ len,
+ ret = {},
+ doCopy = function (copy, original) {
+ // An object is replacing a primitive
+ if (typeof copy !== 'object') {
+ copy = {};
+ }
+
+ H.objectEach(original, function (value, key) {
+
+ // Copy the contents of objects, but not arrays or DOM nodes
+ if (
+ H.isObject(value, true) &&
+ !H.isClass(value) &&
+ !H.isDOMElement(value)
+ ) {
+ copy[key] = doCopy(copy[key] || {}, value);
+
+ // Primitives and arrays are copied over directly
+ } else {
+ copy[key] = original[key];
+ }
+ });
+ return copy;
+ };
+
+ // If first argument is true, copy into the existing object. Used in
+ // setOptions.
+ if (args[0] === true) {
+ ret = args[1];
+ args = Array.prototype.slice.call(args, 2);
+ }
+
+ // For each argument, extend the return
+ len = args.length;
+ for (i = 0; i < len; i++) {
+ ret = doCopy(ret, args[i]);
+ }
+
+ return ret;
+};
+
+/**
+ * Shortcut for parseInt
+ * @ignore
+ * @param {Object} s
+ * @param {Number} mag Magnitude
+ */
+H.pInt = function (s, mag) {
+ return parseInt(s, mag || 10);
+};
+
+/**
+ * Utility function to check for string type.
+ *
+ * @function #isString
+ * @memberOf Highcharts
+ * @param {Object} s - The item to check.
+ * @returns {Boolean} - True if the argument is a string.
+ */
+H.isString = function (s) {
+ return typeof s === 'string';
+};
+
+/**
+ * Utility function to check if an item is an array.
+ *
+ * @function #isArray
+ * @memberOf Highcharts
+ * @param {Object} obj - The item to check.
+ * @returns {Boolean} - True if the argument is an array.
+ */
+H.isArray = function (obj) {
+ var str = Object.prototype.toString.call(obj);
+ return str === '[object Array]' || str === '[object Array Iterator]';
+};
+
+/**
+ * Utility function to check if an item is of type object.
+ *
+ * @function #isObject
+ * @memberOf Highcharts
+ * @param {Object} obj - The item to check.
+ * @param {Boolean} [strict=false] - Also checks that the object is not an
+ * array.
+ * @returns {Boolean} - True if the argument is an object.
+ */
+H.isObject = function (obj, strict) {
+ return !!obj && typeof obj === 'object' && (!strict || !H.isArray(obj));
+};
+
+/**
+ * Utility function to check if an Object is a HTML Element.
+ *
+ * @function #isDOMElement
+ * @memberOf Highcharts
+ * @param {Object} obj - The item to check.
+ * @returns {Boolean} - True if the argument is a HTML Element.
+ */
+H.isDOMElement = function (obj) {
+ return H.isObject(obj) && typeof obj.nodeType === 'number';
+};
+
+/**
+ * Utility function to check if an Object is an class.
+ *
+ * @function #isClass
+ * @memberOf Highcharts
+ * @param {Object} obj - The item to check.
+ * @returns {Boolean} - True if the argument is an class.
+ */
+H.isClass = function (obj) {
+ var c = obj && obj.constructor;
+ return !!(
+ H.isObject(obj, true) &&
+ !H.isDOMElement(obj) &&
+ (c && c.name && c.name !== 'Object')
+ );
+};
+
+/**
+ * Utility function to check if an item is a number and it is finite (not NaN,
+ * Infinity or -Infinity).
+ *
+ * @function #isNumber
+ * @memberOf Highcharts
+ * @param {Object} n
+ * The item to check.
+ * @return {Boolean}
+ * True if the item is a finite number
+ */
+H.isNumber = function (n) {
+ return typeof n === 'number' && !isNaN(n) && n < Infinity && n > -Infinity;
+};
+
+/**
+ * Remove the last occurence of an item from an array.
+ *
+ * @function #erase
+ * @memberOf Highcharts
+ * @param {Array} arr - The array.
+ * @param {*} item - The item to remove.
+ */
+H.erase = function (arr, item) {
+ var i = arr.length;
+ while (i--) {
+ if (arr[i] === item) {
+ arr.splice(i, 1);
+ break;
+ }
+ }
+};
+
+/**
+ * Check if an object is null or undefined.
+ *
+ * @function #defined
+ * @memberOf Highcharts
+ * @param {Object} obj - The object to check.
+ * @returns {Boolean} - False if the object is null or undefined, otherwise
+ * true.
+ */
+H.defined = function (obj) {
+ return obj !== undefined && obj !== null;
+};
+
+/**
+ * Set or get an attribute or an object of attributes. To use as a setter, pass
+ * a key and a value, or let the second argument be a collection of keys and
+ * values. To use as a getter, pass only a string as the second argument.
+ *
+ * @function #attr
+ * @memberOf Highcharts
+ * @param {Object} elem - The DOM element to receive the attribute(s).
+ * @param {String|Object} [prop] - The property or an object of key-value pairs.
+ * @param {String} [value] - The value if a single property is set.
+ * @returns {*} When used as a getter, return the value.
+ */
+H.attr = function (elem, prop, value) {
+ var ret;
+
+ // if the prop is a string
+ if (H.isString(prop)) {
+ // set the value
+ if (H.defined(value)) {
+ elem.setAttribute(prop, value);
+
+ // get the value
+ } else if (elem && elem.getAttribute) {
+ ret = elem.getAttribute(prop);
+ }
+
+ // else if prop is defined, it is a hash of key/value pairs
+ } else if (H.defined(prop) && H.isObject(prop)) {
+ H.objectEach(prop, function (val, key) {
+ elem.setAttribute(key, val);
+ });
+ }
+ return ret;
+};
+
+/**
+ * Check if an element is an array, and if not, make it into an array.
+ *
+ * @function #splat
+ * @memberOf Highcharts
+ * @param obj {*} - The object to splat.
+ * @returns {Array} The produced or original array.
+ */
+H.splat = function (obj) {
+ return H.isArray(obj) ? obj : [obj];
+};
+
+/**
+ * Set a timeout if the delay is given, otherwise perform the function
+ * synchronously.
+ *
+ * @function #syncTimeout
+ * @memberOf Highcharts
+ * @param {Function} fn - The function callback.
+ * @param {Number} delay - Delay in milliseconds.
+ * @param {Object} [context] - The context.
+ * @returns {Number} An identifier for the timeout that can later be cleared
+ * with clearTimeout.
+ */
+H.syncTimeout = function (fn, delay, context) {
+ if (delay) {
+ return setTimeout(fn, delay, context);
+ }
+ fn.call(0, context);
+};
+
+
+/**
+ * Utility function to extend an object with the members of another.
+ *
+ * @function #extend
+ * @memberOf Highcharts
+ * @param {Object} a - The object to be extended.
+ * @param {Object} b - The object to add to the first one.
+ * @returns {Object} Object a, the original object.
+ */
+H.extend = function (a, b) {
+ var n;
+ if (!a) {
+ a = {};
+ }
+ for (n in b) {
+ a[n] = b[n];
+ }
+ return a;
+};
+
+
+/**
+ * Return the first value that is not null or undefined.
+ *
+ * @function #pick
+ * @memberOf Highcharts
+ * @param {...*} items - Variable number of arguments to inspect.
+ * @returns {*} The value of the first argument that is not null or undefined.
+ */
+H.pick = function () {
+ var args = arguments,
+ i,
+ arg,
+ length = args.length;
+ for (i = 0; i < length; i++) {
+ arg = args[i];
+ if (arg !== undefined && arg !== null) {
+ return arg;
+ }
+ }
+};
+
+/**
+ * @typedef {Object} CSSObject - A style object with camel case property names.
+ * The properties can be whatever styles are supported on the given SVG or HTML
+ * element.
+ * @example
+ * {
+ * fontFamily: 'monospace',
+ * fontSize: '1.2em'
+ * }
+ */
+/**
+ * Set CSS on a given element.
+ *
+ * @function #css
+ * @memberOf Highcharts
+ * @param {HTMLDOMElement} el - A HTML DOM element.
+ * @param {CSSObject} styles - Style object with camel case property names.
+ *
+ */
+H.css = function (el, styles) {
+ if (H.isMS && !H.svg) { // #2686
+ if (styles && styles.opacity !== undefined) {
+ styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
+ }
+ }
+ H.extend(el.style, styles);
+};
+
+/**
+ * A HTML DOM element.
+ * @typedef {Object} HTMLDOMElement
+ */
+
+/**
+ * Utility function to create an HTML element with attributes and styles.
+ *
+ * @function #createElement
+ * @memberOf Highcharts
+ * @param {String} tag - The HTML tag.
+ * @param {Object} [attribs] - Attributes as an object of key-value pairs.
+ * @param {CSSObject} [styles] - Styles as an object of key-value pairs.
+ * @param {Object} [parent] - The parent HTML object.
+ * @param {Boolean} [nopad=false] - If true, remove all padding, border and
+ * margin.
+ * @returns {HTMLDOMElement} The created DOM element.
+ */
+H.createElement = function (tag, attribs, styles, parent, nopad) {
+ var el = doc.createElement(tag),
+ css = H.css;
+ if (attribs) {
+ H.extend(el, attribs);
+ }
+ if (nopad) {
+ css(el, { padding: 0, border: 'none', margin: 0 });
+ }
+ if (styles) {
+ css(el, styles);
+ }
+ if (parent) {
+ parent.appendChild(el);
+ }
+ return el;
+};
+
+/**
+ * Extend a prototyped class by new members.
+ *
+ * @function #extendClass
+ * @memberOf Highcharts
+ * @param {Object} parent - The parent prototype to inherit.
+ * @param {Object} members - A collection of prototype members to add or
+ * override compared to the parent prototype.
+ * @returns {Object} A new prototype.
+ */
+H.extendClass = function (parent, members) {
+ var object = function () {};
+ object.prototype = new parent(); // eslint-disable-line new-cap
+ H.extend(object.prototype, members);
+ return object;
+};
+
+/**
+ * Left-pad a string to a given length by adding a character repetetively.
+ *
+ * @function #pad
+ * @memberOf Highcharts
+ * @param {Number} number - The input string or number.
+ * @param {Number} length - The desired string length.
+ * @param {String} [padder=0] - The character to pad with.
+ * @returns {String} The padded string.
+ */
+H.pad = function (number, length, padder) {
+ return new Array((length || 2) + 1 -
+ String(number).length).join(padder || 0) + number;
+};
+
+/**
+ * @typedef {Number|String} RelativeSize - If a number is given, it defines the
+ * pixel length. If a percentage string is given, like for example `'50%'`,
+ * the setting defines a length relative to a base size, for example the size
+ * of a container.
+ */
+/**
+ * Return a length based on either the integer value, or a percentage of a base.
+ *
+ * @function #relativeLength
+ * @memberOf Highcharts
+ * @param {RelativeSize} value
+ * A percentage string or a number.
+ * @param {number} base
+ * The full length that represents 100%.
+ * @param {number} [offset=0]
+ * A pixel offset to apply for percentage values. Used internally in
+ * axis positioning.
+ * @return {number}
+ * The computed length.
+ */
+H.relativeLength = function (value, base, offset) {
+ return (/%$/).test(value) ?
+ (base * parseFloat(value) / 100) + (offset || 0) :
+ parseFloat(value);
+};
+
+/**
+ * Wrap a method with extended functionality, preserving the original function.
+ *
+ * @function #wrap
+ * @memberOf Highcharts
+ * @param {Object} obj - The context object that the method belongs to. In real
+ * cases, this is often a prototype.
+ * @param {String} method - The name of the method to extend.
+ * @param {Function} func - A wrapper function callback. This function is called
+ * with the same arguments as the original function, except that the
+ * original function is unshifted and passed as the first argument.
+ *
+ */
+H.wrap = function (obj, method, func) {
+ var proceed = obj[method];
+ obj[method] = function () {
+ var args = Array.prototype.slice.call(arguments),
+ outerArgs = arguments,
+ ctx = this,
+ ret;
+ ctx.proceed = function () {
+ proceed.apply(ctx, arguments.length ? arguments : outerArgs);
+ };
+ args.unshift(proceed);
+ ret = func.apply(this, args);
+ ctx.proceed = null;
+ return ret;
+ };
+};
+
+
+
+/**
+ * Format a single variable. Similar to sprintf, without the % prefix.
+ *
+ * @example
+ * formatSingle('.2f', 5); // => '5.00'.
+ *
+ * @function #formatSingle
+ * @memberOf Highcharts
+ * @param {String} format The format string.
+ * @param {*} val The value.
+ * @param {Time} [time]
+ * A `Time` instance that determines the date formatting, for example for
+ * applying time zone corrections to the formatted date.
+
+ * @returns {String} The formatted representation of the value.
+ */
+H.formatSingle = function (format, val, time) {
+ var floatRegex = /f$/,
+ decRegex = /\.([0-9])/,
+ lang = H.defaultOptions.lang,
+ decimals;
+
+ if (floatRegex.test(format)) { // float
+ decimals = format.match(decRegex);
+ decimals = decimals ? decimals[1] : -1;
+ if (val !== null) {
+ val = H.numberFormat(
+ val,
+ decimals,
+ lang.decimalPoint,
+ format.indexOf(',') > -1 ? lang.thousandsSep : ''
+ );
+ }
+ } else {
+ val = (time || H.time).dateFormat(format, val);
+ }
+ return val;
+};
+
+/**
+ * Format a string according to a subset of the rules of Python's String.format
+ * method.
+ *
+ * @function #format
+ * @memberOf Highcharts
+ * @param {String} str
+ * The string to format.
+ * @param {Object} ctx
+ * The context, a collection of key-value pairs where each key is
+ * replaced by its value.
+ * @param {Time} [time]
+ * A `Time` instance that determines the date formatting, for example for
+ * applying time zone corrections to the formatted date.
+ * @returns {String} The formatted string.
+ *
+ * @example
+ * var s = Highcharts.format(
+ * 'The {color} fox was {len:.2f} feet long',
+ * { color: 'red', len: Math.PI }
+ * );
+ * // => The red fox was 3.14 feet long
+ */
+H.format = function (str, ctx, time) {
+ var splitter = '{',
+ isInside = false,
+ segment,
+ valueAndFormat,
+ path,
+ i,
+ len,
+ ret = [],
+ val,
+ index;
+
+ while (str) {
+ index = str.indexOf(splitter);
+ if (index === -1) {
+ break;
+ }
+
+ segment = str.slice(0, index);
+ if (isInside) { // we're on the closing bracket looking back
+
+ valueAndFormat = segment.split(':');
+ path = valueAndFormat.shift().split('.'); // get first and leave
+ len = path.length;
+ val = ctx;
+
+ // Assign deeper paths
+ for (i = 0; i < len; i++) {
+ if (val) {
+ val = val[path[i]];
+ }
+ }
+
+ // Format the replacement
+ if (valueAndFormat.length) {
+ val = H.formatSingle(valueAndFormat.join(':'), val, time);
+ }
+
+ // Push the result and advance the cursor
+ ret.push(val);
+
+ } else {
+ ret.push(segment);
+
+ }
+ str = str.slice(index + 1); // the rest
+ isInside = !isInside; // toggle
+ splitter = isInside ? '}' : '{'; // now look for next matching bracket
+ }
+ ret.push(str);
+ return ret.join('');
+};
+
+/**
+ * Get the magnitude of a number.
+ *
+ * @function #getMagnitude
+ * @memberOf Highcharts
+ * @param {Number} number The number.
+ * @returns {Number} The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2
+ * etc.
+ */
+H.getMagnitude = function (num) {
+ return Math.pow(10, Math.floor(Math.log(num) / Math.LN10));
+};
+
+/**
+ * Take an interval and normalize it to multiples of round numbers.
+ *
+ * @todo Move this function to the Axis prototype. It is here only for
+ * historical reasons.
+ * @function #normalizeTickInterval
+ * @memberOf Highcharts
+ * @param {Number} interval - The raw, un-rounded interval.
+ * @param {Array} [multiples] - Allowed multiples.
+ * @param {Number} [magnitude] - The magnitude of the number.
+ * @param {Boolean} [allowDecimals] - Whether to allow decimals.
+ * @param {Boolean} [hasTickAmount] - If it has tickAmount, avoid landing
+ * on tick intervals lower than original.
+ * @returns {Number} The normalized interval.
+ */
+H.normalizeTickInterval = function (interval, multiples, magnitude,
+ allowDecimals, hasTickAmount) {
+ var normalized,
+ i,
+ retInterval = interval;
+
+ // round to a tenfold of 1, 2, 2.5 or 5
+ magnitude = H.pick(magnitude, 1);
+ normalized = interval / magnitude;
+
+ // multiples for a linear scale
+ if (!multiples) {
+ multiples = hasTickAmount ?
+ // Finer grained ticks when the tick amount is hard set, including
+ // when alignTicks is true on multiple axes (#4580).
+ [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] :
+
+ // Else, let ticks fall on rounder numbers
+ [1, 2, 2.5, 5, 10];
+
+
+ // the allowDecimals option
+ if (allowDecimals === false) {
+ if (magnitude === 1) {
+ multiples = H.grep(multiples, function (num) {
+ return num % 1 === 0;
+ });
+ } else if (magnitude <= 0.1) {
+ multiples = [1 / magnitude];
+ }
+ }
+ }
+
+ // normalize the interval to the nearest multiple
+ for (i = 0; i < multiples.length; i++) {
+ retInterval = multiples[i];
+ // only allow tick amounts smaller than natural
+ if ((hasTickAmount && retInterval * magnitude >= interval) ||
+ (!hasTickAmount && (normalized <= (multiples[i] +
+ (multiples[i + 1] || multiples[i])) / 2))) {
+ break;
+ }
+ }
+
+ // Multiply back to the correct magnitude. Correct floats to appropriate
+ // precision (#6085).
+ retInterval = H.correctFloat(
+ retInterval * magnitude,
+ -Math.round(Math.log(0.001) / Math.LN10)
+ );
+
+ return retInterval;
+};
+
+
+/**
+ * Sort an object array and keep the order of equal items. The ECMAScript
+ * standard does not specify the behaviour when items are equal.
+ *
+ * @function #stableSort
+ * @memberOf Highcharts
+ * @param {Array} arr - The array to sort.
+ * @param {Function} sortFunction - The function to sort it with, like with
+ * regular Array.prototype.sort.
+ *
+ */
+H.stableSort = function (arr, sortFunction) {
+ var length = arr.length,
+ sortValue,
+ i;
+
+ // Add index to each item
+ for (i = 0; i < length; i++) {
+ arr[i].safeI = i; // stable sort index
+ }
+
+ arr.sort(function (a, b) {
+ sortValue = sortFunction(a, b);
+ return sortValue === 0 ? a.safeI - b.safeI : sortValue;
+ });
+
+ // Remove index from items
+ for (i = 0; i < length; i++) {
+ delete arr[i].safeI; // stable sort index
+ }
+};
+
+/**
+ * Non-recursive method to find the lowest member of an array. `Math.min` raises
+ * a maximum call stack size exceeded error in Chrome when trying to apply more
+ * than 150.000 points. This method is slightly slower, but safe.
+ *
+ * @function #arrayMin
+ * @memberOf Highcharts
+ * @param {Array} data An array of numbers.
+ * @returns {Number} The lowest number.
+ */
+H.arrayMin = function (data) {
+ var i = data.length,
+ min = data[0];
+
+ while (i--) {
+ if (data[i] < min) {
+ min = data[i];
+ }
+ }
+ return min;
+};
+
+/**
+ * Non-recursive method to find the lowest member of an array. `Math.max` raises
+ * a maximum call stack size exceeded error in Chrome when trying to apply more
+ * than 150.000 points. This method is slightly slower, but safe.
+ *
+ * @function #arrayMax
+ * @memberOf Highcharts
+ * @param {Array} data - An array of numbers.
+ * @returns {Number} The highest number.
+ */
+H.arrayMax = function (data) {
+ var i = data.length,
+ max = data[0];
+
+ while (i--) {
+ if (data[i] > max) {
+ max = data[i];
+ }
+ }
+ return max;
+};
+
+/**
+ * Utility method that destroys any SVGElement instances that are properties on
+ * the given object. It loops all properties and invokes destroy if there is a
+ * destroy method. The property is then delete.
+ *
+ * @function #destroyObjectProperties
+ * @memberOf Highcharts
+ * @param {Object} obj - The object to destroy properties on.
+ * @param {Object} [except] - Exception, do not destroy this property, only
+ * delete it.
+ *
+ */
+H.destroyObjectProperties = function (obj, except) {
+ H.objectEach(obj, function (val, n) {
+ // If the object is non-null and destroy is defined
+ if (val && val !== except && val.destroy) {
+ // Invoke the destroy
+ val.destroy();
+ }
+
+ // Delete the property from the object.
+ delete obj[n];
+ });
+};
+
+
+/**
+ * Discard a HTML element by moving it to the bin and delete.
+ *
+ * @function #discardElement
+ * @memberOf Highcharts
+ * @param {HTMLDOMElement} element - The HTML node to discard.
+ *
+ */
+H.discardElement = function (element) {
+ var garbageBin = H.garbageBin;
+ // create a garbage bin element, not part of the DOM
+ if (!garbageBin) {
+ garbageBin = H.createElement('div');
+ }
+
+ // move the node and empty bin
+ if (element) {
+ garbageBin.appendChild(element);
+ }
+ garbageBin.innerHTML = '';
+};
+
+/**
+ * Fix JS round off float errors.
+ *
+ * @function #correctFloat
+ * @memberOf Highcharts
+ * @param {Number} num - A float number to fix.
+ * @param {Number} [prec=14] - The precision.
+ * @returns {Number} The corrected float number.
+ */
+H.correctFloat = function (num, prec) {
+ return parseFloat(
+ num.toPrecision(prec || 14)
+ );
+};
+
+/**
+ * Set the global animation to either a given value, or fall back to the given
+ * chart's animation option.
+ *
+ * @function #setAnimation
+ * @memberOf Highcharts
+ * @param {Boolean|Animation} animation - The animation object.
+ * @param {Object} chart - The chart instance.
+ *
+ * @todo This function always relates to a chart, and sets a property on the
+ * renderer, so it should be moved to the SVGRenderer.
+ */
+H.setAnimation = function (animation, chart) {
+ chart.renderer.globalAnimation = H.pick(
+ animation,
+ chart.options.chart.animation,
+ true
+ );
+};
+
+/**
+ * Get the animation in object form, where a disabled animation is always
+ * returned as `{ duration: 0 }`.
+ *
+ * @function #animObject
+ * @memberOf Highcharts
+ * @param {Boolean|AnimationOptions} animation - An animation setting. Can be an
+ * object with duration, complete and easing properties, or a boolean to
+ * enable or disable.
+ * @returns {AnimationOptions} An object with at least a duration property.
+ */
+H.animObject = function (animation) {
+ return H.isObject(animation) ?
+ H.merge(animation) :
+ { duration: animation ? 500 : 0 };
+};
+
+/**
+ * The time unit lookup
+ */
+H.timeUnits = {
+ millisecond: 1,
+ second: 1000,
+ minute: 60000,
+ hour: 3600000,
+ day: 24 * 3600000,
+ week: 7 * 24 * 3600000,
+ month: 28 * 24 * 3600000,
+ year: 364 * 24 * 3600000
+};
+
+/**
+ * Format a number and return a string based on input settings.
+ *
+ * @function #numberFormat
+ * @memberOf Highcharts
+ * @param {Number} number - The input number to format.
+ * @param {Number} decimals - The amount of decimals. A value of -1 preserves
+ * the amount in the input number.
+ * @param {String} [decimalPoint] - The decimal point, defaults to the one given
+ * in the lang options, or a dot.
+ * @param {String} [thousandsSep] - The thousands separator, defaults to the one
+ * given in the lang options, or a space character.
+ * @returns {String} The formatted number.
+ *
+ * @sample highcharts/members/highcharts-numberformat/ Custom number format
+ */
+H.numberFormat = function (number, decimals, decimalPoint, thousandsSep) {
+ number = +number || 0;
+ decimals = +decimals;
+
+ var lang = H.defaultOptions.lang,
+ origDec = (number.toString().split('.')[1] || '').split('e')[0].length,
+ strinteger,
+ thousands,
+ ret,
+ roundedNumber,
+ exponent = number.toString().split('e'),
+ fractionDigits;
+
+ if (decimals === -1) {
+ // Preserve decimals. Not huge numbers (#3793).
+ decimals = Math.min(origDec, 20);
+ } else if (!H.isNumber(decimals)) {
+ decimals = 2;
+ } else if (decimals && exponent[1] && exponent[1] < 0) {
+ // Expose decimals from exponential notation (#7042)
+ fractionDigits = decimals + +exponent[1];
+ if (fractionDigits >= 0) {
+ // remove too small part of the number while keeping the notation
+ exponent[0] = (+exponent[0]).toExponential(fractionDigits)
+ .split('e')[0];
+ decimals = fractionDigits;
+ } else {
+ // fractionDigits < 0
+ exponent[0] = exponent[0].split('.')[0] || 0;
+
+ if (decimals < 20) {
+ // use number instead of exponential notation (#7405)
+ number = (exponent[0] * Math.pow(10, exponent[1]))
+ .toFixed(decimals);
+ } else {
+ // or zero
+ number = 0;
+ }
+ exponent[1] = 0;
+ }
+ }
+
+ // Add another decimal to avoid rounding errors of float numbers. (#4573)
+ // Then use toFixed to handle rounding.
+ roundedNumber = (
+ Math.abs(exponent[1] ? exponent[0] : number) +
+ Math.pow(10, -Math.max(decimals, origDec) - 1)
+ ).toFixed(decimals);
+
+ // A string containing the positive integer component of the number
+ strinteger = String(H.pInt(roundedNumber));
+
+ // Leftover after grouping into thousands. Can be 0, 1 or 3.
+ thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;
+
+ // Language
+ decimalPoint = H.pick(decimalPoint, lang.decimalPoint);
+ thousandsSep = H.pick(thousandsSep, lang.thousandsSep);
+
+ // Start building the return
+ ret = number < 0 ? '-' : '';
+
+ // Add the leftover after grouping into thousands. For example, in the
+ // number 42 000 000, this line adds 42.
+ ret += thousands ? strinteger.substr(0, thousands) + thousandsSep : '';
+
+ // Add the remaining thousands groups, joined by the thousands separator
+ ret += strinteger
+ .substr(thousands)
+ .replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep);
+
+ // Add the decimal point and the decimal component
+ if (decimals) {
+ // Get the decimal component
+ ret += decimalPoint + roundedNumber.slice(-decimals);
+ }
+
+ if (exponent[1] && +ret !== 0) {
+ ret += 'e' + exponent[1];
+ }
+
+ return ret;
+};
+
+/**
+ * Easing definition
+ * @ignore
+ * @param {Number} pos Current position, ranging from 0 to 1.
+ */
+Math.easeInOutSine = function (pos) {
+ return -0.5 * (Math.cos(Math.PI * pos) - 1);
+};
+
+/**
+ * Get the computed CSS value for given element and property, only for numerical
+ * properties. For width and height, the dimension of the inner box (excluding
+ * padding) is returned. Used for fitting the chart within the container.
+ *
+ * @function #getStyle
+ * @memberOf Highcharts
+ * @param {HTMLDOMElement} el - A HTML element.
+ * @param {String} prop - The property name.
+ * @param {Boolean} [toInt=true] - Parse to integer.
+ * @returns {Number} - The numeric value.
+ */
+H.getStyle = function (el, prop, toInt) {
+
+ var style;
+
+ // For width and height, return the actual inner pixel size (#4913)
+ if (prop === 'width') {
+ return Math.min(el.offsetWidth, el.scrollWidth) -
+ H.getStyle(el, 'padding-left') -
+ H.getStyle(el, 'padding-right');
+ } else if (prop === 'height') {
+ return Math.min(el.offsetHeight, el.scrollHeight) -
+ H.getStyle(el, 'padding-top') -
+ H.getStyle(el, 'padding-bottom');
+ }
+
+ if (!win.getComputedStyle) {
+ // SVG not supported, forgot to load oldie.js?
+ H.error(27, true);
+ }
+
+ // Otherwise, get the computed style
+ style = win.getComputedStyle(el, undefined);
+ if (style) {
+ style = style.getPropertyValue(prop);
+ if (H.pick(toInt, prop !== 'opacity')) {
+ style = H.pInt(style);
+ }
+ }
+ return style;
+};
+
+/**
+ * Search for an item in an array.
+ *
+ * @function #inArray
+ * @memberOf Highcharts
+ * @param {*} item - The item to search for.
+ * @param {arr} arr - The array or node collection to search in.
+ * @returns {Number} - The index within the array, or -1 if not found.
+ */
+H.inArray = function (item, arr) {
+ return (H.indexOfPolyfill || Array.prototype.indexOf).call(arr, item);
+};
+
+/**
+ * Filter an array by a callback.
+ *
+ * @function #grep
+ * @memberOf Highcharts
+ * @param {Array} arr - The array to filter.
+ * @param {Function} callback - The callback function. The function receives the
+ * item as the first argument. Return `true` if the item is to be
+ * preserved.
+ * @returns {Array} - A new, filtered array.
+ */
+H.grep = function (arr, callback) {
+ return (H.filterPolyfill || Array.prototype.filter).call(arr, callback);
+};
+
+/**
+ * Return the value of the first element in the array that satisfies the
+ * provided testing function.
+ *
+ * @function #find
+ * @memberOf Highcharts
+ * @param {Array} arr - The array to test.
+ * @param {Function} callback - The callback function. The function receives the
+ * item as the first argument. Return `true` if this item satisfies the
+ * condition.
+ * @returns {Mixed} - The value of the element.
+ */
+H.find = Array.prototype.find ?
+ function (arr, callback) {
+ return arr.find(callback);
+ } :
+ // Legacy implementation. PhantomJS, IE <= 11 etc. #7223.
+ function (arr, fn) {
+ var i,
+ length = arr.length;
+
+ for (i = 0; i < length; i++) {
+ if (fn(arr[i], i)) {
+ return arr[i];
+ }
+ }
+ };
+
+/**
+ * Map an array by a callback.
+ *
+ * @function #map
+ * @memberOf Highcharts
+ * @param {Array} arr - The array to map.
+ * @param {Function} fn - The callback function. Return the new value for the
+ * new array.
+ * @returns {Array} - A new array item with modified items.
+ */
+H.map = function (arr, fn) {
+ var results = [],
+ i = 0,
+ len = arr.length;
+
+ for (; i < len; i++) {
+ results[i] = fn.call(arr[i], arr[i], i, arr);
+ }
+
+ return results;
+};
+
+/**
+ * Returns an array of a given object's own properties.
+ *
+ * @function #keys
+ * @memberOf highcharts
+ * @param {Object} obj - The object of which the properties are to be returned.
+ * @returns {Array} - An array of strings that represents all the properties.
+ */
+H.keys = function (obj) {
+ return (H.keysPolyfill || Object.keys).call(undefined, obj);
+};
+
+/**
+ * Reduce an array to a single value.
+ *
+ * @function #reduce
+ * @memberOf Highcharts
+ * @param {Array} arr - The array to reduce.
+ * @param {Function} fn - The callback function. Return the reduced value.
+ * Receives 4 arguments: Accumulated/reduced value, current value, current
+ * array index, and the array.
+ * @param {Mixed} initialValue - The initial value of the accumulator.
+ * @returns {Mixed} - The reduced value.
+ */
+H.reduce = function (arr, func, initialValue) {
+ return (H.reducePolyfill || Array.prototype.reduce).call(
+ arr,
+ func,
+ initialValue
+ );
+};
+
+/**
+ * Get the element's offset position, corrected for `overflow: auto`.
+ *
+ * @function #offset
+ * @memberOf Highcharts
+ * @param {HTMLDOMElement} el - The HTML element.
+ * @returns {Object} An object containing `left` and `top` properties for the
+ * position in the page.
+ */
+H.offset = function (el) {
+ var docElem = doc.documentElement,
+ box = el.parentElement ? // IE11 throws Unspecified error in test suite
+ el.getBoundingClientRect() :
+ { top: 0, left: 0 };
+
+ return {
+ top: box.top + (win.pageYOffset || docElem.scrollTop) -
+ (docElem.clientTop || 0),
+ left: box.left + (win.pageXOffset || docElem.scrollLeft) -
+ (docElem.clientLeft || 0)
+ };
+};
+
+/**
+ * Stop running animation.
+ *
+ * @todo A possible extension to this would be to stop a single property, when
+ * we want to continue animating others. Then assign the prop to the timer
+ * in the Fx.run method, and check for the prop here. This would be an
+ * improvement in all cases where we stop the animation from .attr. Instead of
+ * stopping everything, we can just stop the actual attributes we're setting.
+ *
+ * @function #stop
+ * @memberOf Highcharts
+ * @param {SVGElement} el - The SVGElement to stop animation on.
+ * @param {string} [prop] - The property to stop animating. If given, the stop
+ * method will stop a single property from animating, while others continue.
+ *
+ */
+H.stop = function (el, prop) {
+
+ var i = H.timers.length;
+
+ // Remove timers related to this element (#4519)
+ while (i--) {
+ if (H.timers[i].elem === el && (!prop || prop === H.timers[i].prop)) {
+ H.timers[i].stopped = true; // #4667
+ }
+ }
+};
+
+/**
+ * Iterate over an array.
+ *
+ * @function #each
+ * @memberOf Highcharts
+ * @param {Array} arr - The array to iterate over.
+ * @param {Function} fn - The iterator callback. It passes three arguments:
+ * * item - The array item.
+ * * index - The item's index in the array.
+ * * arr - The array that each is being applied to.
+ * @param {Object} [ctx] The context.
+ */
+H.each = function (arr, fn, ctx) { // modern browsers
+ return (H.forEachPolyfill || Array.prototype.forEach).call(arr, fn, ctx);
+};
+
+/**
+ * Iterate over object key pairs in an object.
+ *
+ * @function #objectEach
+ * @memberOf Highcharts
+ * @param {Object} obj - The object to iterate over.
+ * @param {Function} fn - The iterator callback. It passes three arguments:
+ * * value - The property value.
+ * * key - The property key.
+ * * obj - The object that objectEach is being applied to.
+ * @param {Object} ctx The context
+ */
+H.objectEach = function (obj, fn, ctx) {
+ for (var key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ fn.call(ctx, obj[key], key, obj);
+ }
+ }
+};
+
+/**
+ * Add an event listener.
+ *
+ * @function #addEvent
+ * @memberOf Highcharts
+ * @param {Object} el - The element or object to add a listener to. It can be a
+ * {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
+ * @param {String} type - The event type.
+ * @param {Function} fn - The function callback to execute when the event is
+ * fired.
+ * @returns {Function} A callback function to remove the added event.
+ */
+H.addEvent = function (el, type, fn) {
+
+ var events,
+ itemEvents,
+ addEventListener = el.addEventListener || H.addEventListenerPolyfill;
+
+ // If events are previously set directly on the prototype, pick them up
+ // and copy them over to the instance. Otherwise instance handlers would
+ // be set on the prototype and apply to multiple charts in the page.
+ if (
+ el.hcEvents &&
+ // IE8, window and document don't have hasOwnProperty
+ !Object.prototype.hasOwnProperty.call(el, 'hcEvents')
+ ) {
+ itemEvents = {};
+ H.objectEach(el.hcEvents, function (handlers, eventType) {
+ itemEvents[eventType] = handlers.slice(0);
+ });
+ el.hcEvents = itemEvents;
+ }
+
+ events = el.hcEvents = el.hcEvents || {};
+
+ // Handle DOM events
+ if (addEventListener) {
+ addEventListener.call(el, type, fn, false);
+ }
+
+ if (!events[type]) {
+ events[type] = [];
+ }
+
+ events[type].push(fn);
+
+ // Return a function that can be called to remove this event.
+ return function () {
+ H.removeEvent(el, type, fn);
+ };
+};
+
+/**
+ * Remove an event that was added with {@link Highcharts#addEvent}.
+ *
+ * @function #removeEvent
+ * @memberOf Highcharts
+ * @param {Object} el - The element to remove events on.
+ * @param {String} [type] - The type of events to remove. If undefined, all
+ * events are removed from the element.
+ * @param {Function} [fn] - The specific callback to remove. If undefined, all
+ * events that match the element and optionally the type are removed.
+ *
+ */
+H.removeEvent = function (el, type, fn) {
+
+ var events,
+ hcEvents = el.hcEvents,
+ index;
+
+ function removeOneEvent(type, fn) {
+ var removeEventListener =
+ el.removeEventListener || H.removeEventListenerPolyfill;
+
+ if (removeEventListener) {
+ removeEventListener.call(el, type, fn, false);
+ }
+ }
+
+ function removeAllEvents() {
+ var types,
+ len;
+
+ if (!el.nodeName) {
+ return; // break on non-DOM events
+ }
+
+ if (type) {
+ types = {};
+ types[type] = true;
+ } else {
+ types = hcEvents;
+ }
+
+ H.objectEach(types, function (val, n) {
+ if (hcEvents[n]) {
+ len = hcEvents[n].length;
+ while (len--) {
+ removeOneEvent(n, hcEvents[n][len]);
+ }
+ }
+ });
+ }
+
+ if (hcEvents) {
+ if (type) {
+ events = hcEvents[type] || [];
+ if (fn) {
+ index = H.inArray(fn, events);
+ if (index > -1) {
+ events.splice(index, 1);
+ hcEvents[type] = events;
+ }
+ removeOneEvent(type, fn);
+
+ } else {
+ removeAllEvents();
+ hcEvents[type] = [];
+ }
+ } else {
+ removeAllEvents();
+ el.hcEvents = {};
+ }
+ }
+};
+
+/**
+ * Fire an event that was registered with {@link Highcharts#addEvent}.
+ *
+ * @function #fireEvent
+ * @memberOf Highcharts
+ * @param {Object} el - The object to fire the event on. It can be a
+ * {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
+ * @param {String} type - The type of event.
+ * @param {Object} [eventArguments] - Custom event arguments that are passed on
+ * as an argument to the event handler.
+ * @param {Function} [defaultFunction] - The default function to execute if the
+ * other listeners haven't returned false.
+ *
+ */
+H.fireEvent = function (el, type, eventArguments, defaultFunction) {
+ var e,
+ hcEvents = el.hcEvents,
+ events,
+ len,
+ i,
+ fn;
+
+ eventArguments = eventArguments || {};
+
+ if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {
+ e = doc.createEvent('Events');
+ e.initEvent(type, true, true);
+
+ H.extend(e, eventArguments);
+
+ if (el.dispatchEvent) {
+ el.dispatchEvent(e);
+ } else {
+ el.fireEvent(type, e);
+ }
+
+ } else if (hcEvents) {
+
+ events = hcEvents[type] || [];
+ len = events.length;
+
+ if (!eventArguments.target) { // We're running a custom event
+
+ H.extend(eventArguments, {
+ // Attach a simple preventDefault function to skip default
+ // handler if called. The built-in defaultPrevented property is
+ // not overwritable (#5112)
+ preventDefault: function () {
+ eventArguments.defaultPrevented = true;
+ },
+ // Setting target to native events fails with clicking the
+ // zoom-out button in Chrome.
+ target: el,
+ // If the type is not set, we're running a custom event (#2297).
+ // If it is set, we're running a browser event, and setting it
+ // will cause en error in IE8 (#2465).
+ type: type
+ });
+ }
+
+
+ for (i = 0; i < len; i++) {
+ fn = events[i];
+
+ // If the event handler return false, prevent the default handler
+ // from executing
+ if (fn && fn.call(el, eventArguments) === false) {
+ eventArguments.preventDefault();
+ }
+ }
+ }
+
+ // Run the default if not prevented
+ if (defaultFunction && !eventArguments.defaultPrevented) {
+ defaultFunction(eventArguments);
+ }
+};
+
+/**
+ * An animation configuration. Animation configurations can also be defined as
+ * booleans, where `false` turns off animation and `true` defaults to a duration
+ * of 500ms.
+ * @typedef {Object} AnimationOptions
+ * @property {Number} duration - The animation duration in milliseconds.
+ * @property {String} [easing] - The name of an easing function as defined on
+ * the `Math` object.
+ * @property {Function} [complete] - A callback function to exectute when the
+ * animation finishes.
+ * @property {Function} [step] - A callback function to execute on each step of
+ * each attribute or CSS property that's being animated. The first argument
+ * contains information about the animation and progress.
+ */
+
+
+/**
+ * The global animate method, which uses Fx to create individual animators.
+ *
+ * @function #animate
+ * @memberOf Highcharts
+ * @param {HTMLDOMElement|SVGElement} el - The element to animate.
+ * @param {Object} params - An object containing key-value pairs of the
+ * properties to animate. Supports numeric as pixel-based CSS properties
+ * for HTML objects and attributes for SVGElements.
+ * @param {AnimationOptions} [opt] - Animation options.
+ */
+H.animate = function (el, params, opt) {
+ var start,
+ unit = '',
+ end,
+ fx,
+ args;
+
+ if (!H.isObject(opt)) { // Number or undefined/null
+ args = arguments;
+ opt = {
+ duration: args[2],
+ easing: args[3],
+ complete: args[4]
+ };
+ }
+ if (!H.isNumber(opt.duration)) {
+ opt.duration = 400;
+ }
+ opt.easing = typeof opt.easing === 'function' ?
+ opt.easing :
+ (Math[opt.easing] || Math.easeInOutSine);
+ opt.curAnim = H.merge(params);
+
+ H.objectEach(params, function (val, prop) {
+ // Stop current running animation of this property
+ H.stop(el, prop);
+
+ fx = new H.Fx(el, opt, prop);
+ end = null;
+
+ if (prop === 'd') {
+ fx.paths = fx.initPath(
+ el,
+ el.d,
+ params.d
+ );
+ fx.toD = params.d;
+ start = 0;
+ end = 1;
+ } else if (el.attr) {
+ start = el.attr(prop);
+ } else {
+ start = parseFloat(H.getStyle(el, prop)) || 0;
+ if (prop !== 'opacity') {
+ unit = 'px';
+ }
+ }
+
+ if (!end) {
+ end = val;
+ }
+ if (end && end.match && end.match('px')) {
+ end = end.replace(/px/g, ''); // #4351
+ }
+ fx.run(start, end, unit);
+ });
+};
+
+/**
+ * Factory to create new series prototypes.
+ *
+ * @function #seriesType
+ * @memberOf Highcharts
+ *
+ * @param {String} type - The series type name.
+ * @param {String} parent - The parent series type name. Use `line` to inherit
+ * from the basic {@link Series} object.
+ * @param {Object} options - The additional default options that is merged with
+ * the parent's options.
+ * @param {Object} props - The properties (functions and primitives) to set on
+ * the new prototype.
+ * @param {Object} [pointProps] - Members for a series-specific extension of the
+ * {@link Point} prototype if needed.
+ * @returns {*} - The newly created prototype as extended from {@link Series}
+ * or its derivatives.
+ */
+// docs: add to API + extending Highcharts
+H.seriesType = function (type, parent, options, props, pointProps) {
+ var defaultOptions = H.getOptions(),
+ seriesTypes = H.seriesTypes;
+
+ // Merge the options
+ defaultOptions.plotOptions[type] = H.merge(
+ defaultOptions.plotOptions[parent],
+ options
+ );
+
+ // Create the class
+ seriesTypes[type] = H.extendClass(seriesTypes[parent] ||
+ function () {}, props);
+ seriesTypes[type].prototype.type = type;
+
+ // Create the point class if needed
+ if (pointProps) {
+ seriesTypes[type].prototype.pointClass =
+ H.extendClass(H.Point, pointProps);
+ }
+
+ return seriesTypes[type];
+};
+
+/**
+ * Get a unique key for using in internal element id's and pointers. The key
+ * is composed of a random hash specific to this Highcharts instance, and a
+ * counter.
+ * @function #uniqueKey
+ * @memberOf Highcharts
+ * @return {string} The key.
+ * @example
+ * var id = H.uniqueKey(); // => 'highcharts-x45f6hp-0'
+ */
+H.uniqueKey = (function () {
+
+ var uniqueKeyHash = Math.random().toString(36).substring(2, 9),
+ idCounter = 0;
+
+ return function () {
+ return 'highcharts-' + uniqueKeyHash + '-' + idCounter++;
+ };
+}());
+
+/**
+ * Register Highcharts as a plugin in jQuery
+ */
+if (win.jQuery) {
+ win.jQuery.fn.highcharts = function () {
+ var args = [].slice.call(arguments);
+
+ if (this[0]) { // this[0] is the renderTo div
+
+ // Create the chart
+ if (args[0]) {
+ new H[ // eslint-disable-line no-new
+ // Constructor defaults to Chart
+ H.isString(args[0]) ? args.shift() : 'Chart'
+ ](this[0], args[0], args[1]);
+ return this;
+ }
+
+ // When called without parameters or with the return argument,
+ // return an existing chart
+ return charts[H.attr(this[0], 'data-highcharts-chart')];
+ }
+ };
+}
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var each = H.each,
+ isNumber = H.isNumber,
+ map = H.map,
+ merge = H.merge,
+ pInt = H.pInt;
+
+/**
+ * @typedef {string} ColorString
+ * A valid color to be parsed and handled by Highcharts. Highcharts internally
+ * supports hex colors like `#ffffff`, rgb colors like `rgb(255,255,255)` and
+ * rgba colors like `rgba(255,255,255,1)`. Other colors may be supported by the
+ * browsers and displayed correctly, but Highcharts is not able to process them
+ * and apply concepts like opacity and brightening.
+ */
+/**
+ * Handle color operations. The object methods are chainable.
+ * @param {String} input The input color in either rbga or hex format
+ */
+H.Color = function (input) {
+ // Backwards compatibility, allow instanciation without new
+ if (!(this instanceof H.Color)) {
+ return new H.Color(input);
+ }
+ // Initialize
+ this.init(input);
+};
+H.Color.prototype = {
+
+ // Collection of parsers. This can be extended from the outside by pushing parsers
+ // to Highcharts.Color.prototype.parsers.
+ parsers: [{
+ // RGBA color
+ regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,
+ parse: function (result) {
+ return [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
+ }
+ }, {
+ // RGB color
+ regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,
+ parse: function (result) {
+ return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
+ }
+ }],
+
+ // Collection of named colors. Can be extended from the outside by adding
+ // colors to Highcharts.Color.prototype.names.
+ names: {
+ none: 'rgba(255,255,255,0)',
+ white: '#ffffff',
+ black: '#000000'
+ },
+
+ /**
+ * Parse the input color to rgba array
+ * @param {String} input
+ */
+ init: function (input) {
+ var result,
+ rgba,
+ i,
+ parser,
+ len;
+
+ this.input = input = this.names[
+ input && input.toLowerCase ?
+ input.toLowerCase() :
+ ''
+ ] || input;
+
+ // Gradients
+ if (input && input.stops) {
+ this.stops = map(input.stops, function (stop) {
+ return new H.Color(stop[1]);
+ });
+
+ // Solid colors
+ } else {
+
+ // Bitmasking as input[0] is not working for legacy IE.
+ if (input && input.charAt && input.charAt() === '#') {
+
+ len = input.length;
+ input = parseInt(input.substr(1), 16);
+
+ // Handle long-form, e.g. #AABBCC
+ if (len === 7) {
+
+ rgba = [
+ (input & 0xFF0000) >> 16,
+ (input & 0xFF00) >> 8,
+ (input & 0xFF),
+ 1
+ ];
+
+ // Handle short-form, e.g. #ABC
+ // In short form, the value is assumed to be the same
+ // for both nibbles for each component. e.g. #ABC = #AABBCC
+ } else if (len === 4) {
+
+ rgba = [
+ ((input & 0xF00) >> 4) | (input & 0xF00) >> 8,
+ ((input & 0xF0) >> 4) | (input & 0xF0),
+ ((input & 0xF) << 4) | (input & 0xF),
+ 1
+ ];
+ }
+ }
+
+ // Otherwise, check regex parsers
+ if (!rgba) {
+ i = this.parsers.length;
+ while (i-- && !rgba) {
+ parser = this.parsers[i];
+ result = parser.regex.exec(input);
+ if (result) {
+ rgba = parser.parse(result);
+ }
+ }
+ }
+ }
+ this.rgba = rgba || [];
+ },
+
+ /**
+ * Return the color a specified format
+ * @param {String} format
+ */
+ get: function (format) {
+ var input = this.input,
+ rgba = this.rgba,
+ ret;
+
+ if (this.stops) {
+ ret = merge(input);
+ ret.stops = [].concat(ret.stops);
+ each(this.stops, function (stop, i) {
+ ret.stops[i] = [ret.stops[i][0], stop.get(format)];
+ });
+
+ // it's NaN if gradient colors on a column chart
+ } else if (rgba && isNumber(rgba[0])) {
+ if (format === 'rgb' || (!format && rgba[3] === 1)) {
+ ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
+ } else if (format === 'a') {
+ ret = rgba[3];
+ } else {
+ ret = 'rgba(' + rgba.join(',') + ')';
+ }
+ } else {
+ ret = input;
+ }
+ return ret;
+ },
+
+ /**
+ * Brighten the color
+ * @param {Number} alpha
+ */
+ brighten: function (alpha) {
+ var i,
+ rgba = this.rgba;
+
+ if (this.stops) {
+ each(this.stops, function (stop) {
+ stop.brighten(alpha);
+ });
+
+ } else if (isNumber(alpha) && alpha !== 0) {
+ for (i = 0; i < 3; i++) {
+ rgba[i] += pInt(alpha * 255);
+
+ if (rgba[i] < 0) {
+ rgba[i] = 0;
+ }
+ if (rgba[i] > 255) {
+ rgba[i] = 255;
+ }
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Set the color's opacity to a given alpha value
+ * @param {Number} alpha
+ */
+ setOpacity: function (alpha) {
+ this.rgba[3] = alpha;
+ return this;
+ },
+
+ /*
+ * Return an intermediate color between two colors.
+ *
+ * @param {Highcharts.Color} to
+ * The color object to tween to.
+ * @param {Number} pos
+ * The intermediate position, where 0 is the from color (current
+ * color item), and 1 is the `to` color.
+ *
+ * @return {String}
+ * The intermediate color in rgba notation.
+ */
+ tweenTo: function (to, pos) {
+ // Check for has alpha, because rgba colors perform worse due to lack of
+ // support in WebKit.
+ var fromRgba = this.rgba,
+ toRgba = to.rgba,
+ hasAlpha,
+ ret;
+
+ // Unsupported color, return to-color (#3920, #7034)
+ if (!toRgba.length || !fromRgba || !fromRgba.length) {
+ ret = to.input || 'none';
+
+ // Interpolate
+ } else {
+ hasAlpha = (toRgba[3] !== 1 || fromRgba[3] !== 1);
+ ret = (hasAlpha ? 'rgba(' : 'rgb(') +
+ Math.round(toRgba[0] + (fromRgba[0] - toRgba[0]) * (1 - pos)) +
+ ',' +
+ Math.round(toRgba[1] + (fromRgba[1] - toRgba[1]) * (1 - pos)) +
+ ',' +
+ Math.round(toRgba[2] + (fromRgba[2] - toRgba[2]) * (1 - pos)) +
+ (
+ hasAlpha ?
+ (
+ ',' +
+ (toRgba[3] + (fromRgba[3] - toRgba[3]) * (1 - pos))
+ ) :
+ ''
+ ) +
+ ')';
+ }
+ return ret;
+ }
+};
+H.color = function (input) {
+ return new H.Color(input);
+};
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var SVGElement,
+ SVGRenderer,
+
+ addEvent = H.addEvent,
+ animate = H.animate,
+ attr = H.attr,
+ charts = H.charts,
+ color = H.color,
+ css = H.css,
+ createElement = H.createElement,
+ defined = H.defined,
+ deg2rad = H.deg2rad,
+ destroyObjectProperties = H.destroyObjectProperties,
+ doc = H.doc,
+ each = H.each,
+ extend = H.extend,
+ erase = H.erase,
+ grep = H.grep,
+ hasTouch = H.hasTouch,
+ inArray = H.inArray,
+ isArray = H.isArray,
+ isFirefox = H.isFirefox,
+ isMS = H.isMS,
+ isObject = H.isObject,
+ isString = H.isString,
+ isWebKit = H.isWebKit,
+ merge = H.merge,
+ noop = H.noop,
+ objectEach = H.objectEach,
+ pick = H.pick,
+ pInt = H.pInt,
+ removeEvent = H.removeEvent,
+ splat = H.splat,
+ stop = H.stop,
+ svg = H.svg,
+ SVG_NS = H.SVG_NS,
+ symbolSizes = H.symbolSizes,
+ win = H.win;
+
+/**
+ * @typedef {Object} SVGDOMElement - An SVG DOM element.
+ */
+/**
+ * The SVGElement prototype is a JavaScript wrapper for SVG elements used in the
+ * rendering layer of Highcharts. Combined with the {@link
+ * Highcharts.SVGRenderer} object, these prototypes allow freeform annotation
+ * in the charts or even in HTML pages without instanciating a chart. The
+ * SVGElement can also wrap HTML labels, when `text` or `label` elements are
+ * created with the `useHTML` parameter.
+ *
+ * The SVGElement instances are created through factory functions on the
+ * {@link Highcharts.SVGRenderer} object, like
+ * [rect]{@link Highcharts.SVGRenderer#rect}, [path]{@link
+ * Highcharts.SVGRenderer#path}, [text]{@link Highcharts.SVGRenderer#text},
+ * [label]{@link Highcharts.SVGRenderer#label}, [g]{@link
+ * Highcharts.SVGRenderer#g} and more.
+ *
+ * @class Highcharts.SVGElement
+ */
+SVGElement = H.SVGElement = function () {
+ return this;
+};
+extend(SVGElement.prototype, /** @lends Highcharts.SVGElement.prototype */ {
+
+ // Default base for animation
+ opacity: 1,
+ SVG_NS: SVG_NS,
+
+ /**
+ * For labels, these CSS properties are applied to the `text` node directly.
+ *
+ * @private
+ * @type {Array.}
+ */
+ textProps: ['direction', 'fontSize', 'fontWeight', 'fontFamily',
+ 'fontStyle', 'color', 'lineHeight', 'width', 'textAlign',
+ 'textDecoration', 'textOverflow', 'textOutline'],
+
+ /**
+ * Initialize the SVG element. This function only exists to make the
+ * initiation process overridable. It should not be called directly.
+ *
+ * @param {SVGRenderer} renderer
+ * The SVGRenderer instance to initialize to.
+ * @param {String} nodeName
+ * The SVG node name.
+ *
+ */
+ init: function (renderer, nodeName) {
+
+ /**
+ * The primary DOM node. Each `SVGElement` instance wraps a main DOM
+ * node, but may also represent more nodes.
+ *
+ * @name element
+ * @memberOf SVGElement
+ * @type {SVGDOMNode|HTMLDOMNode}
+ */
+ this.element = nodeName === 'span' ?
+ createElement(nodeName) :
+ doc.createElementNS(this.SVG_NS, nodeName);
+
+ /**
+ * The renderer that the SVGElement belongs to.
+ *
+ * @name renderer
+ * @memberOf SVGElement
+ * @type {SVGRenderer}
+ */
+ this.renderer = renderer;
+ },
+
+ /**
+ * Animate to given attributes or CSS properties.
+ *
+ * @param {SVGAttributes} params SVG attributes or CSS to animate.
+ * @param {AnimationOptions} [options] Animation options.
+ * @param {Function} [complete] Function to perform at the end of animation.
+ *
+ * @sample highcharts/members/element-on/
+ * Setting some attributes by animation
+ *
+ * @returns {SVGElement} Returns the SVGElement for chaining.
+ */
+ animate: function (params, options, complete) {
+ var animOptions = H.animObject(
+ pick(options, this.renderer.globalAnimation, true)
+ );
+ if (animOptions.duration !== 0) {
+ // allows using a callback with the global animation without
+ // overwriting it
+ if (complete) {
+ animOptions.complete = complete;
+ }
+ animate(this, params, animOptions);
+ } else {
+ this.attr(params, null, complete);
+ if (animOptions.step) {
+ animOptions.step.call(this);
+ }
+ }
+ return this;
+ },
+
+ /**
+ * @typedef {Object} GradientOptions
+ * @property {Object} linearGradient Holds an object that defines the start
+ * position and the end position relative to the shape.
+ * @property {Number} linearGradient.x1 Start horizontal position of the
+ * gradient. Ranges 0-1.
+ * @property {Number} linearGradient.x2 End horizontal position of the
+ * gradient. Ranges 0-1.
+ * @property {Number} linearGradient.y1 Start vertical position of the
+ * gradient. Ranges 0-1.
+ * @property {Number} linearGradient.y2 End vertical position of the
+ * gradient. Ranges 0-1.
+ * @property {Object} radialGradient Holds an object that defines the center
+ * position and the radius.
+ * @property {Number} radialGradient.cx Center horizontal position relative
+ * to the shape. Ranges 0-1.
+ * @property {Number} radialGradient.cy Center vertical position relative
+ * to the shape. Ranges 0-1.
+ * @property {Number} radialGradient.r Radius relative to the shape. Ranges
+ * 0-1.
+ * @property {Array.} stops The first item in each tuple is the
+ * position in the gradient, where 0 is the start of the gradient and 1
+ * is the end of the gradient. Multiple stops can be applied. The second
+ * item is the color for each stop. This color can also be given in the
+ * rgba format.
+ *
+ * @example
+ * // Linear gradient used as a color option
+ * color: {
+ * linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
+ * stops: [
+ * [0, '#003399'], // start
+ * [0.5, '#ffffff'], // middle
+ * [1, '#3366AA'] // end
+ * ]
+ * }
+ * }
+ */
+ /**
+ * Build and apply an SVG gradient out of a common JavaScript configuration
+ * object. This function is called from the attribute setters.
+ *
+ * @private
+ * @param {GradientOptions} color The gradient options structure.
+ * @param {string} prop The property to apply, can either be `fill` or
+ * `stroke`.
+ * @param {SVGDOMElement} elem SVG DOM element to apply the gradient on.
+ */
+ colorGradient: function (color, prop, elem) {
+ var renderer = this.renderer,
+ colorObject,
+ gradName,
+ gradAttr,
+ radAttr,
+ gradients,
+ gradientObject,
+ stops,
+ stopColor,
+ stopOpacity,
+ radialReference,
+ id,
+ key = [],
+ value;
+
+ // Apply linear or radial gradients
+ if (color.radialGradient) {
+ gradName = 'radialGradient';
+ } else if (color.linearGradient) {
+ gradName = 'linearGradient';
+ }
+
+ if (gradName) {
+ gradAttr = color[gradName];
+ gradients = renderer.gradients;
+ stops = color.stops;
+ radialReference = elem.radialReference;
+
+ // Keep < 2.2 kompatibility
+ if (isArray(gradAttr)) {
+ color[gradName] = gradAttr = {
+ x1: gradAttr[0],
+ y1: gradAttr[1],
+ x2: gradAttr[2],
+ y2: gradAttr[3],
+ gradientUnits: 'userSpaceOnUse'
+ };
+ }
+
+ // Correct the radial gradient for the radial reference system
+ if (
+ gradName === 'radialGradient' &&
+ radialReference &&
+ !defined(gradAttr.gradientUnits)
+ ) {
+ radAttr = gradAttr; // Save the radial attributes for updating
+ gradAttr = merge(
+ gradAttr,
+ renderer.getRadialAttr(radialReference, radAttr),
+ { gradientUnits: 'userSpaceOnUse' }
+ );
+ }
+
+ // Build the unique key to detect whether we need to create a new
+ // element (#1282)
+ objectEach(gradAttr, function (val, n) {
+ if (n !== 'id') {
+ key.push(n, val);
+ }
+ });
+ objectEach(stops, function (val) {
+ key.push(val);
+ });
+ key = key.join(',');
+
+ // Check if a gradient object with the same config object is created
+ // within this renderer
+ if (gradients[key]) {
+ id = gradients[key].attr('id');
+
+ } else {
+
+ // Set the id and create the element
+ gradAttr.id = id = H.uniqueKey();
+ gradients[key] = gradientObject =
+ renderer.createElement(gradName)
+ .attr(gradAttr)
+ .add(renderer.defs);
+
+ gradientObject.radAttr = radAttr;
+
+ // The gradient needs to keep a list of stops to be able to
+ // destroy them
+ gradientObject.stops = [];
+ each(stops, function (stop) {
+ var stopObject;
+ if (stop[1].indexOf('rgba') === 0) {
+ colorObject = H.color(stop[1]);
+ stopColor = colorObject.get('rgb');
+ stopOpacity = colorObject.get('a');
+ } else {
+ stopColor = stop[1];
+ stopOpacity = 1;
+ }
+ stopObject = renderer.createElement('stop').attr({
+ offset: stop[0],
+ 'stop-color': stopColor,
+ 'stop-opacity': stopOpacity
+ }).add(gradientObject);
+
+ // Add the stop element to the gradient
+ gradientObject.stops.push(stopObject);
+ });
+ }
+
+ // Set the reference to the gradient object
+ value = 'url(' + renderer.url + '#' + id + ')';
+ elem.setAttribute(prop, value);
+ elem.gradient = key;
+
+ // Allow the color to be concatenated into tooltips formatters etc.
+ // (#2995)
+ color.toString = function () {
+ return value;
+ };
+ }
+ },
+
+ /**
+ * Apply a text outline through a custom CSS property, by copying the text
+ * element and apply stroke to the copy. Used internally. Contrast checks
+ * at http://jsfiddle.net/highcharts/43soe9m1/2/ .
+ *
+ * @private
+ * @param {String} textOutline A custom CSS `text-outline` setting, defined
+ * by `width color`.
+ * @example
+ * // Specific color
+ * text.css({
+ * textOutline: '1px black'
+ * });
+ * // Automatic contrast
+ * text.css({
+ * color: '#000000', // black text
+ * textOutline: '1px contrast' // => white outline
+ * });
+ */
+ applyTextOutline: function (textOutline) {
+ var elem = this.element,
+ tspans,
+ tspan,
+ hasContrast = textOutline.indexOf('contrast') !== -1,
+ styles = {},
+ color,
+ strokeWidth,
+ firstRealChild,
+ i;
+
+ // When the text shadow is set to contrast, use dark stroke for light
+ // text and vice versa.
+ if (hasContrast) {
+ styles.textOutline = textOutline = textOutline.replace(
+ /contrast/g,
+ this.renderer.getContrast(elem.style.fill)
+ );
+ }
+
+ // Extract the stroke width and color
+ textOutline = textOutline.split(' ');
+ color = textOutline[textOutline.length - 1];
+ strokeWidth = textOutline[0];
+
+ if (strokeWidth && strokeWidth !== 'none' && H.svg) {
+
+ this.fakeTS = true; // Fake text shadow
+
+ tspans = [].slice.call(elem.getElementsByTagName('tspan'));
+
+ // In order to get the right y position of the clone,
+ // copy over the y setter
+ this.ySetter = this.xSetter;
+
+ // Since the stroke is applied on center of the actual outline, we
+ // need to double it to get the correct stroke-width outside the
+ // glyphs.
+ strokeWidth = strokeWidth.replace(
+ /(^[\d\.]+)(.*?)$/g,
+ function (match, digit, unit) {
+ return (2 * digit) + unit;
+ }
+ );
+
+ // Remove shadows from previous runs. Iterate from the end to
+ // support removing items inside the cycle (#6472).
+ i = tspans.length;
+ while (i--) {
+ tspan = tspans[i];
+ if (tspan.getAttribute('class') === 'highcharts-text-outline') {
+ // Remove then erase
+ erase(tspans, elem.removeChild(tspan));
+ }
+ }
+
+ // For each of the tspans, create a stroked copy behind it.
+ firstRealChild = elem.firstChild;
+ each(tspans, function (tspan, y) {
+ var clone;
+
+ // Let the first line start at the correct X position
+ if (y === 0) {
+ tspan.setAttribute('x', elem.getAttribute('x'));
+ y = elem.getAttribute('y');
+ tspan.setAttribute('y', y || 0);
+ if (y === null) {
+ elem.setAttribute('y', 0);
+ }
+ }
+
+ // Create the clone and apply outline properties
+ clone = tspan.cloneNode(1);
+ attr(clone, {
+ 'class': 'highcharts-text-outline',
+ 'fill': color,
+ 'stroke': color,
+ 'stroke-width': strokeWidth,
+ 'stroke-linejoin': 'round'
+ });
+ elem.insertBefore(clone, firstRealChild);
+ });
+ }
+ },
+
+ /**
+ *
+ * @typedef {Object} SVGAttributes An object of key-value pairs for SVG
+ * attributes. Attributes in Highcharts elements for the most parts
+ * correspond to SVG, but some are specific to Highcharts, like `zIndex`,
+ * `rotation`, `rotationOriginX`, `rotationOriginY`, `translateX`,
+ * `translateY`, `scaleX` and `scaleY`. SVG attributes containing a hyphen
+ * are _not_ camel-cased, they should be quoted to preserve the hyphen.
+ *
+ * @example
+ * {
+ * 'stroke': '#ff0000', // basic
+ * 'stroke-width': 2, // hyphenated
+ * 'rotation': 45 // custom
+ * 'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format
+ * }
+ */
+ /**
+ * Apply native and custom attributes to the SVG elements.
+ *
+ * In order to set the rotation center for rotation, set x and y to 0 and
+ * use `translateX` and `translateY` attributes to position the element
+ * instead.
+ *
+ * Attributes frequently used in Highcharts are `fill`, `stroke`,
+ * `stroke-width`.
+ *
+ * @param {SVGAttributes|String} hash - The native and custom SVG
+ * attributes.
+ * @param {string} [val] - If the type of the first argument is `string`,
+ * the second can be a value, which will serve as a single attribute
+ * setter. If the first argument is a string and the second is undefined,
+ * the function serves as a getter and the current value of the property
+ * is returned.
+ * @param {Function} [complete] - A callback function to execute after
+ * setting the attributes. This makes the function compliant and
+ * interchangeable with the {@link SVGElement#animate} function.
+ * @param {boolean} [continueAnimation=true] Used internally when `.attr` is
+ * called as part of an animation step. Otherwise, calling `.attr` for an
+ * attribute will stop animation for that attribute.
+ *
+ * @returns {SVGElement|string|number} If used as a setter, it returns the
+ * current {@link SVGElement} so the calls can be chained. If used as a
+ * getter, the current value of the attribute is returned.
+ *
+ * @sample highcharts/members/renderer-rect/
+ * Setting some attributes
+ *
+ * @example
+ * // Set multiple attributes
+ * element.attr({
+ * stroke: 'red',
+ * fill: 'blue',
+ * x: 10,
+ * y: 10
+ * });
+ *
+ * // Set a single attribute
+ * element.attr('stroke', 'red');
+ *
+ * // Get an attribute
+ * element.attr('stroke'); // => 'red'
+ *
+ */
+ attr: function (hash, val, complete, continueAnimation) {
+ var key,
+ element = this.element,
+ hasSetSymbolSize,
+ ret = this,
+ skipAttr,
+ setter;
+
+ // single key-value pair
+ if (typeof hash === 'string' && val !== undefined) {
+ key = hash;
+ hash = {};
+ hash[key] = val;
+ }
+
+ // used as a getter: first argument is a string, second is undefined
+ if (typeof hash === 'string') {
+ ret = (this[hash + 'Getter'] || this._defaultGetter).call(
+ this,
+ hash,
+ element
+ );
+
+ // setter
+ } else {
+
+ objectEach(hash, function eachAttribute(val, key) {
+ skipAttr = false;
+
+ // Unless .attr is from the animator update, stop current
+ // running animation of this property
+ if (!continueAnimation) {
+ stop(this, key);
+ }
+
+ // Special handling of symbol attributes
+ if (
+ this.symbolName &&
+ /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)$/
+ .test(key)
+ ) {
+ if (!hasSetSymbolSize) {
+ this.symbolAttr(hash);
+ hasSetSymbolSize = true;
+ }
+ skipAttr = true;
+ }
+
+ if (this.rotation && (key === 'x' || key === 'y')) {
+ this.doTransform = true;
+ }
+
+ if (!skipAttr) {
+ setter = this[key + 'Setter'] || this._defaultSetter;
+ setter.call(this, val, key, element);
+
+
+ // Let the shadow follow the main element
+ if (
+ this.shadows &&
+ /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/
+ .test(key)
+ ) {
+ this.updateShadows(key, val, setter);
+ }
+
+ }
+ }, this);
+
+ this.afterSetters();
+ }
+
+ // In accordance with animate, run a complete callback
+ if (complete) {
+ complete();
+ }
+
+ return ret;
+ },
+
+ /**
+ * This method is executed in the end of `attr()`, after setting all
+ * attributes in the hash. In can be used to efficiently consolidate
+ * multiple attributes in one SVG property -- e.g., translate, rotate and
+ * scale are merged in one "transform" attribute in the SVG node.
+ *
+ * @private
+ */
+ afterSetters: function () {
+ // Update transform. Do this outside the loop to prevent redundant
+ // updating for batch setting of attributes.
+ if (this.doTransform) {
+ this.updateTransform();
+ this.doTransform = false;
+ }
+ },
+
+
+ /**
+ * Update the shadow elements with new attributes.
+ *
+ * @private
+ * @param {String} key - The attribute name.
+ * @param {String|Number} value - The value of the attribute.
+ * @param {Function} setter - The setter function, inherited from the
+ * parent wrapper
+ *
+ */
+ updateShadows: function (key, value, setter) {
+ var shadows = this.shadows,
+ i = shadows.length;
+
+ while (i--) {
+ setter.call(
+ shadows[i],
+ key === 'height' ?
+ Math.max(value - (shadows[i].cutHeight || 0), 0) :
+ key === 'd' ? this.d : value,
+ key,
+ shadows[i]
+ );
+ }
+ },
+
+
+ /**
+ * Add a class name to an element.
+ *
+ * @param {string} className - The new class name to add.
+ * @param {boolean} [replace=false] - When true, the existing class name(s)
+ * will be overwritten with the new one. When false, the new one is
+ * added.
+ * @returns {SVGElement} Return the SVG element for chainability.
+ */
+ addClass: function (className, replace) {
+ var currentClassName = this.attr('class') || '';
+ if (currentClassName.indexOf(className) === -1) {
+ if (!replace) {
+ className =
+ (currentClassName + (currentClassName ? ' ' : '') +
+ className).replace(' ', ' ');
+ }
+ this.attr('class', className);
+ }
+
+ return this;
+ },
+
+ /**
+ * Check if an element has the given class name.
+ * @param {string} className
+ * The class name to check for.
+ * @return {Boolean}
+ * Whether the class name is found.
+ */
+ hasClass: function (className) {
+ return inArray(
+ className,
+ (this.attr('class') || '').split(' ')
+ ) !== -1;
+ },
+
+ /**
+ * Remove a class name from the element.
+ * @param {String|RegExp} className The class name to remove.
+ * @return {SVGElement} Returns the SVG element for chainability.
+ */
+ removeClass: function (className) {
+ return this.attr(
+ 'class',
+ (this.attr('class') || '').replace(className, '')
+ );
+ },
+
+ /**
+ * If one of the symbol size affecting parameters are changed,
+ * check all the others only once for each call to an element's
+ * .attr() method
+ * @param {Object} hash - The attributes to set.
+ * @private
+ */
+ symbolAttr: function (hash) {
+ var wrapper = this;
+
+ each([
+ 'x',
+ 'y',
+ 'r',
+ 'start',
+ 'end',
+ 'width',
+ 'height',
+ 'innerR',
+ 'anchorX',
+ 'anchorY'
+ ], function (key) {
+ wrapper[key] = pick(hash[key], wrapper[key]);
+ });
+
+ wrapper.attr({
+ d: wrapper.renderer.symbols[wrapper.symbolName](
+ wrapper.x,
+ wrapper.y,
+ wrapper.width,
+ wrapper.height,
+ wrapper
+ )
+ });
+ },
+
+ /**
+ * Apply a clipping rectangle to this element.
+ *
+ * @param {ClipRect} [clipRect] - The clipping rectangle. If skipped, the
+ * current clip is removed.
+ * @returns {SVGElement} Returns the SVG element to allow chaining.
+ */
+ clip: function (clipRect) {
+ return this.attr(
+ 'clip-path',
+ clipRect ?
+ 'url(' + this.renderer.url + '#' + clipRect.id + ')' :
+ 'none'
+ );
+ },
+
+ /**
+ * Calculate the coordinates needed for drawing a rectangle crisply and
+ * return the calculated attributes.
+ *
+ * @param {Object} rect - A rectangle.
+ * @param {number} rect.x - The x position.
+ * @param {number} rect.y - The y position.
+ * @param {number} rect.width - The width.
+ * @param {number} rect.height - The height.
+ * @param {number} [strokeWidth] - The stroke width to consider when
+ * computing crisp positioning. It can also be set directly on the rect
+ * parameter.
+ *
+ * @returns {{x: Number, y: Number, width: Number, height: Number}} The
+ * modified rectangle arguments.
+ */
+ crisp: function (rect, strokeWidth) {
+
+ var wrapper = this,
+ normalizer;
+
+ strokeWidth = strokeWidth || rect.strokeWidth || 0;
+ // Math.round because strokeWidth can sometimes have roundoff errors
+ normalizer = Math.round(strokeWidth) % 2 / 2;
+
+ // normalize for crisp edges
+ rect.x = Math.floor(rect.x || wrapper.x || 0) + normalizer;
+ rect.y = Math.floor(rect.y || wrapper.y || 0) + normalizer;
+ rect.width = Math.floor(
+ (rect.width || wrapper.width || 0) - 2 * normalizer
+ );
+ rect.height = Math.floor(
+ (rect.height || wrapper.height || 0) - 2 * normalizer
+ );
+ if (defined(rect.strokeWidth)) {
+ rect.strokeWidth = strokeWidth;
+ }
+ return rect;
+ },
+
+ /**
+ * Set styles for the element. In addition to CSS styles supported by
+ * native SVG and HTML elements, there are also some custom made for
+ * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text
+ * elements.
+ * @param {CSSObject} styles The new CSS styles.
+ * @returns {SVGElement} Return the SVG element for chaining.
+ *
+ * @sample highcharts/members/renderer-text-on-chart/
+ * Styled text
+ */
+ css: function (styles) {
+ var oldStyles = this.styles,
+ newStyles = {},
+ elem = this.element,
+ textWidth,
+ serializedCss = '',
+ hyphenate,
+ hasNew = !oldStyles,
+ // These CSS properties are interpreted internally by the SVG
+ // renderer, but are not supported by SVG and should not be added to
+ // the DOM. In styled mode, no CSS should find its way to the DOM
+ // whatsoever (#6173, #6474).
+ svgPseudoProps = ['textOutline', 'textOverflow', 'width'];
+
+ // convert legacy
+ if (styles && styles.color) {
+ styles.fill = styles.color;
+ }
+
+ // Filter out existing styles to increase performance (#2640)
+ if (oldStyles) {
+ objectEach(styles, function (style, n) {
+ if (style !== oldStyles[n]) {
+ newStyles[n] = style;
+ hasNew = true;
+ }
+ });
+ }
+ if (hasNew) {
+
+ // Merge the new styles with the old ones
+ if (oldStyles) {
+ styles = extend(
+ oldStyles,
+ newStyles
+ );
+ }
+
+ // Get the text width from style
+ textWidth = this.textWidth = (
+ styles &&
+ styles.width &&
+ styles.width !== 'auto' &&
+ elem.nodeName.toLowerCase() === 'text' &&
+ pInt(styles.width)
+ );
+
+ // store object
+ this.styles = styles;
+
+ if (textWidth && (!svg && this.renderer.forExport)) {
+ delete styles.width;
+ }
+
+ // serialize and set style attribute
+ if (isMS && !svg) {
+ css(this.element, styles);
+ } else {
+ hyphenate = function (a, b) {
+ return '-' + b.toLowerCase();
+ };
+ objectEach(styles, function (style, n) {
+ if (inArray(n, svgPseudoProps) === -1) {
+ serializedCss +=
+ n.replace(/([A-Z])/g, hyphenate) + ':' +
+ style + ';';
+ }
+ });
+ if (serializedCss) {
+ attr(elem, 'style', serializedCss); // #1881
+ }
+ }
+
+
+ if (this.added) {
+
+ // Rebuild text after added. Cache mechanisms in the buildText
+ // will prevent building if there are no significant changes.
+ if (this.element.nodeName === 'text') {
+ this.renderer.buildText(this);
+ }
+
+ // Apply text outline after added
+ if (styles && styles.textOutline) {
+ this.applyTextOutline(styles.textOutline);
+ }
+ }
+ }
+
+ return this;
+ },
+
+
+ /**
+ * Get the current stroke width. In classic mode, the setter registers it
+ * directly on the element.
+ * @returns {number} The stroke width in pixels.
+ * @ignore
+ */
+ strokeWidth: function () {
+ return this['stroke-width'] || 0;
+ },
+
+
+ /**
+ * Add an event listener. This is a simple setter that replaces all other
+ * events of the same type, opposed to the {@link Highcharts#addEvent}
+ * function.
+ * @param {string} eventType - The event type. If the type is `click`,
+ * Highcharts will internally translate it to a `touchstart` event on
+ * touch devices, to prevent the browser from waiting for a click event
+ * from firing.
+ * @param {Function} handler - The handler callback.
+ * @returns {SVGElement} The SVGElement for chaining.
+ *
+ * @sample highcharts/members/element-on/
+ * A clickable rectangle
+ */
+ on: function (eventType, handler) {
+ var svgElement = this,
+ element = svgElement.element;
+
+ // touch
+ if (hasTouch && eventType === 'click') {
+ element.ontouchstart = function (e) {
+ svgElement.touchEventFired = Date.now(); // #2269
+ e.preventDefault();
+ handler.call(element, e);
+ };
+ element.onclick = function (e) {
+ if (win.navigator.userAgent.indexOf('Android') === -1 ||
+ Date.now() - (svgElement.touchEventFired || 0) > 1100) {
+ handler.call(element, e);
+ }
+ };
+ } else {
+ // simplest possible event model for internal use
+ element['on' + eventType] = handler;
+ }
+ return this;
+ },
+
+ /**
+ * Set the coordinates needed to draw a consistent radial gradient across
+ * a shape regardless of positioning inside the chart. Used on pie slices
+ * to make all the slices have the same radial reference point.
+ *
+ * @param {Array} coordinates The center reference. The format is
+ * `[centerX, centerY, diameter]` in pixels.
+ * @returns {SVGElement} Returns the SVGElement for chaining.
+ */
+ setRadialReference: function (coordinates) {
+ var existingGradient = this.renderer.gradients[this.element.gradient];
+
+ this.element.radialReference = coordinates;
+
+ // On redrawing objects with an existing gradient, the gradient needs
+ // to be repositioned (#3801)
+ if (existingGradient && existingGradient.radAttr) {
+ existingGradient.animate(
+ this.renderer.getRadialAttr(
+ coordinates,
+ existingGradient.radAttr
+ )
+ );
+ }
+
+ return this;
+ },
+
+ /**
+ * Move an object and its children by x and y values.
+ *
+ * @param {number} x - The x value.
+ * @param {number} y - The y value.
+ */
+ translate: function (x, y) {
+ return this.attr({
+ translateX: x,
+ translateY: y
+ });
+ },
+
+ /**
+ * Invert a group, rotate and flip. This is used internally on inverted
+ * charts, where the points and graphs are drawn as if not inverted, then
+ * the series group elements are inverted.
+ *
+ * @param {boolean} inverted
+ * Whether to invert or not. An inverted shape can be un-inverted by
+ * setting it to false.
+ * @return {SVGElement}
+ * Return the SVGElement for chaining.
+ */
+ invert: function (inverted) {
+ var wrapper = this;
+ wrapper.inverted = inverted;
+ wrapper.updateTransform();
+ return wrapper;
+ },
+
+ /**
+ * Update the transform attribute based on internal properties. Deals with
+ * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY`
+ * attributes and updates the SVG `transform` attribute.
+ * @private
+ *
+ */
+ updateTransform: function () {
+ var wrapper = this,
+ translateX = wrapper.translateX || 0,
+ translateY = wrapper.translateY || 0,
+ scaleX = wrapper.scaleX,
+ scaleY = wrapper.scaleY,
+ inverted = wrapper.inverted,
+ rotation = wrapper.rotation,
+ matrix = wrapper.matrix,
+ element = wrapper.element,
+ transform;
+
+ // Flipping affects translate as adjustment for flipping around the
+ // group's axis
+ if (inverted) {
+ translateX += wrapper.width;
+ translateY += wrapper.height;
+ }
+
+ // Apply translate. Nearly all transformed elements have translation,
+ // so instead of checking for translate = 0, do it always (#1767,
+ // #1846).
+ transform = ['translate(' + translateX + ',' + translateY + ')'];
+
+ // apply matrix
+ if (defined(matrix)) {
+ transform.push(
+ 'matrix(' + matrix.join(',') + ')'
+ );
+ }
+
+ // apply rotation
+ if (inverted) {
+ transform.push('rotate(90) scale(-1,1)');
+ } else if (rotation) { // text rotation
+ transform.push(
+ 'rotate(' + rotation + ' ' +
+ pick(this.rotationOriginX, element.getAttribute('x'), 0) +
+ ' ' +
+ pick(this.rotationOriginY, element.getAttribute('y') || 0) + ')'
+ );
+ }
+
+ // apply scale
+ if (defined(scaleX) || defined(scaleY)) {
+ transform.push(
+ 'scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'
+ );
+ }
+
+ if (transform.length) {
+ element.setAttribute('transform', transform.join(' '));
+ }
+ },
+
+ /**
+ * Bring the element to the front. Alternatively, a new zIndex can be set.
+ *
+ * @returns {SVGElement} Returns the SVGElement for chaining.
+ *
+ * @sample highcharts/members/element-tofront/
+ * Click an element to bring it to front
+ */
+ toFront: function () {
+ var element = this.element;
+ element.parentNode.appendChild(element);
+ return this;
+ },
+
+
+ /**
+ * Align the element relative to the chart or another box.
+ *
+ * @param {Object} [alignOptions] The alignment options. The function can be
+ * called without this parameter in order to re-align an element after the
+ * box has been updated.
+ * @param {string} [alignOptions.align=left] Horizontal alignment. Can be
+ * one of `left`, `center` and `right`.
+ * @param {string} [alignOptions.verticalAlign=top] Vertical alignment. Can
+ * be one of `top`, `middle` and `bottom`.
+ * @param {number} [alignOptions.x=0] Horizontal pixel offset from
+ * alignment.
+ * @param {number} [alignOptions.y=0] Vertical pixel offset from alignment.
+ * @param {Boolean} [alignByTranslate=false] Use the `transform` attribute
+ * with translateX and translateY custom attributes to align this elements
+ * rather than `x` and `y` attributes.
+ * @param {String|Object} box The box to align to, needs a width and height.
+ * When the box is a string, it refers to an object in the Renderer. For
+ * example, when box is `spacingBox`, it refers to `Renderer.spacingBox`
+ * which holds `width`, `height`, `x` and `y` properties.
+ * @returns {SVGElement} Returns the SVGElement for chaining.
+ */
+ align: function (alignOptions, alignByTranslate, box) {
+ var align,
+ vAlign,
+ x,
+ y,
+ attribs = {},
+ alignTo,
+ renderer = this.renderer,
+ alignedObjects = renderer.alignedObjects,
+ alignFactor,
+ vAlignFactor;
+
+ // First call on instanciate
+ if (alignOptions) {
+ this.alignOptions = alignOptions;
+ this.alignByTranslate = alignByTranslate;
+ if (!box || isString(box)) {
+ this.alignTo = alignTo = box || 'renderer';
+ // prevent duplicates, like legendGroup after resize
+ erase(alignedObjects, this);
+ alignedObjects.push(this);
+ box = null; // reassign it below
+ }
+
+ // When called on resize, no arguments are supplied
+ } else {
+ alignOptions = this.alignOptions;
+ alignByTranslate = this.alignByTranslate;
+ alignTo = this.alignTo;
+ }
+
+ box = pick(box, renderer[alignTo], renderer);
+
+ // Assign variables
+ align = alignOptions.align;
+ vAlign = alignOptions.verticalAlign;
+ x = (box.x || 0) + (alignOptions.x || 0); // default: left align
+ y = (box.y || 0) + (alignOptions.y || 0); // default: top align
+
+ // Align
+ if (align === 'right') {
+ alignFactor = 1;
+ } else if (align === 'center') {
+ alignFactor = 2;
+ }
+ if (alignFactor) {
+ x += (box.width - (alignOptions.width || 0)) / alignFactor;
+ }
+ attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x);
+
+
+ // Vertical align
+ if (vAlign === 'bottom') {
+ vAlignFactor = 1;
+ } else if (vAlign === 'middle') {
+ vAlignFactor = 2;
+ }
+ if (vAlignFactor) {
+ y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
+ }
+ attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y);
+
+ // Animate only if already placed
+ this[this.placed ? 'animate' : 'attr'](attribs);
+ this.placed = true;
+ this.alignAttr = attribs;
+
+ return this;
+ },
+
+ /**
+ * Get the bounding box (width, height, x and y) for the element. Generally
+ * used to get rendered text size. Since this is called a lot in charts,
+ * the results are cached based on text properties, in order to save DOM
+ * traffic. The returned bounding box includes the rotation, so for example
+ * a single text line of rotation 90 will report a greater height, and a
+ * width corresponding to the line-height.
+ *
+ * @param {boolean} [reload] Skip the cache and get the updated DOM bouding
+ * box.
+ * @param {number} [rot] Override the element's rotation. This is internally
+ * used on axis labels with a value of 0 to find out what the bounding box
+ * would be have been if it were not rotated.
+ * @returns {Object} The bounding box with `x`, `y`, `width` and `height`
+ * properties.
+ *
+ * @sample highcharts/members/renderer-on-chart/
+ * Draw a rectangle based on a text's bounding box
+ */
+ getBBox: function (reload, rot) {
+ var wrapper = this,
+ bBox, // = wrapper.bBox,
+ renderer = wrapper.renderer,
+ width,
+ height,
+ rotation,
+ rad,
+ element = wrapper.element,
+ styles = wrapper.styles,
+ fontSize,
+ textStr = wrapper.textStr,
+ toggleTextShadowShim,
+ cache = renderer.cache,
+ cacheKeys = renderer.cacheKeys,
+ cacheKey;
+
+ rotation = pick(rot, wrapper.rotation);
+ rad = rotation * deg2rad;
+
+
+ fontSize = styles && styles.fontSize;
+
+
+ // Avoid undefined and null (#7316)
+ if (defined(textStr)) {
+
+ cacheKey = textStr.toString();
+
+ // Since numbers are monospaced, and numerical labels appear a lot
+ // in a chart, we assume that a label of n characters has the same
+ // bounding box as others of the same length. Unless there is inner
+ // HTML in the label. In that case, leave the numbers as is (#5899).
+ if (cacheKey.indexOf('<') === -1) {
+ cacheKey = cacheKey.replace(/[0-9]/g, '0');
+ }
+
+ // Properties that affect bounding box
+ cacheKey += [
+ '',
+ rotation || 0,
+ fontSize,
+ styles && styles.width,
+ styles && styles.textOverflow // #5968
+ ]
+ .join(',');
+
+ }
+
+ if (cacheKey && !reload) {
+ bBox = cache[cacheKey];
+ }
+
+ // No cache found
+ if (!bBox) {
+
+ // SVG elements
+ if (element.namespaceURI === wrapper.SVG_NS || renderer.forExport) {
+ try { // Fails in Firefox if the container has display: none.
+
+ // When the text shadow shim is used, we need to hide the
+ // fake shadows to get the correct bounding box (#3872)
+ toggleTextShadowShim = this.fakeTS && function (display) {
+ each(
+ element.querySelectorAll(
+ '.highcharts-text-outline'
+ ),
+ function (tspan) {
+ tspan.style.display = display;
+ }
+ );
+ };
+
+ // Workaround for #3842, Firefox reporting wrong bounding
+ // box for shadows
+ if (toggleTextShadowShim) {
+ toggleTextShadowShim('none');
+ }
+
+ bBox = element.getBBox ?
+ // SVG: use extend because IE9 is not allowed to change
+ // width and height in case of rotation (below)
+ extend({}, element.getBBox()) : {
+
+ // Legacy IE in export mode
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ };
+
+ // #3842
+ if (toggleTextShadowShim) {
+ toggleTextShadowShim('');
+ }
+ } catch (e) {}
+
+ // If the bBox is not set, the try-catch block above failed. The
+ // other condition is for Opera that returns a width of
+ // -Infinity on hidden elements.
+ if (!bBox || bBox.width < 0) {
+ bBox = { width: 0, height: 0 };
+ }
+
+
+ // VML Renderer or useHTML within SVG
+ } else {
+
+ bBox = wrapper.htmlGetBBox();
+
+ }
+
+ // True SVG elements as well as HTML elements in modern browsers
+ // using the .useHTML option need to compensated for rotation
+ if (renderer.isSVG) {
+ width = bBox.width;
+ height = bBox.height;
+
+ // Workaround for wrong bounding box in IE, Edge and Chrome on
+ // Windows. With Highcharts' default font, IE and Edge report
+ // a box height of 16.899 and Chrome rounds it to 17. If this
+ // stands uncorrected, it results in more padding added below
+ // the text than above when adding a label border or background.
+ // Also vertical positioning is affected.
+ // http://jsfiddle.net/highcharts/em37nvuj/
+ // (#1101, #1505, #1669, #2568, #6213).
+ if (
+ styles &&
+ styles.fontSize === '11px' &&
+ Math.round(height) === 17
+ ) {
+ bBox.height = height = 14;
+ }
+
+ // Adjust for rotated text
+ if (rotation) {
+ bBox.width = Math.abs(height * Math.sin(rad)) +
+ Math.abs(width * Math.cos(rad));
+ bBox.height = Math.abs(height * Math.cos(rad)) +
+ Math.abs(width * Math.sin(rad));
+ }
+ }
+
+ // Cache it. When loading a chart in a hidden iframe in Firefox and
+ // IE/Edge, the bounding box height is 0, so don't cache it (#5620).
+ if (cacheKey && bBox.height > 0) {
+
+ // Rotate (#4681)
+ while (cacheKeys.length > 250) {
+ delete cache[cacheKeys.shift()];
+ }
+
+ if (!cache[cacheKey]) {
+ cacheKeys.push(cacheKey);
+ }
+ cache[cacheKey] = bBox;
+ }
+ }
+ return bBox;
+ },
+
+ /**
+ * Show the element after it has been hidden.
+ *
+ * @param {boolean} [inherit=false] Set the visibility attribute to
+ * `inherit` rather than `visible`. The difference is that an element with
+ * `visibility="visible"` will be visible even if the parent is hidden.
+ *
+ * @returns {SVGElement} Returns the SVGElement for chaining.
+ */
+ show: function (inherit) {
+ return this.attr({ visibility: inherit ? 'inherit' : 'visible' });
+ },
+
+ /**
+ * Hide the element, equivalent to setting the `visibility` attribute to
+ * `hidden`.
+ *
+ * @returns {SVGElement} Returns the SVGElement for chaining.
+ */
+ hide: function () {
+ return this.attr({ visibility: 'hidden' });
+ },
+
+ /**
+ * Fade out an element by animating its opacity down to 0, and hide it on
+ * complete. Used internally for the tooltip.
+ *
+ * @param {number} [duration=150] The fade duration in milliseconds.
+ */
+ fadeOut: function (duration) {
+ var elemWrapper = this;
+ elemWrapper.animate({
+ opacity: 0
+ }, {
+ duration: duration || 150,
+ complete: function () {
+ // #3088, assuming we're only using this for tooltips
+ elemWrapper.attr({ y: -9999 });
+ }
+ });
+ },
+
+ /**
+ * Add the element to the DOM. All elements must be added this way.
+ *
+ * @param {SVGElement|SVGDOMElement} [parent] The parent item to add it to.
+ * If undefined, the element is added to the {@link
+ * Highcharts.SVGRenderer.box}.
+ *
+ * @returns {SVGElement} Returns the SVGElement for chaining.
+ *
+ * @sample highcharts/members/renderer-g - Elements added to a group
+ */
+ add: function (parent) {
+
+ var renderer = this.renderer,
+ element = this.element,
+ inserted;
+
+ if (parent) {
+ this.parentGroup = parent;
+ }
+
+ // mark as inverted
+ this.parentInverted = parent && parent.inverted;
+
+ // build formatted text
+ if (this.textStr !== undefined) {
+ renderer.buildText(this);
+ }
+
+ // Mark as added
+ this.added = true;
+
+ // If we're adding to renderer root, or other elements in the group
+ // have a z index, we need to handle it
+ if (!parent || parent.handleZ || this.zIndex) {
+ inserted = this.zIndexSetter();
+ }
+
+ // If zIndex is not handled, append at the end
+ if (!inserted) {
+ (parent ? parent.element : renderer.box).appendChild(element);
+ }
+
+ // fire an event for internal hooks
+ if (this.onAdd) {
+ this.onAdd();
+ }
+
+ return this;
+ },
+
+ /**
+ * Removes an element from the DOM.
+ *
+ * @private
+ * @param {SVGDOMElement|HTMLDOMElement} element The DOM node to remove.
+ */
+ safeRemoveChild: function (element) {
+ var parentNode = element.parentNode;
+ if (parentNode) {
+ parentNode.removeChild(element);
+ }
+ },
+
+ /**
+ * Destroy the element and element wrapper and clear up the DOM and event
+ * hooks.
+ *
+ *
+ */
+ destroy: function () {
+ var wrapper = this,
+ element = wrapper.element || {},
+ parentToClean =
+ wrapper.renderer.isSVG &&
+ element.nodeName === 'SPAN' &&
+ wrapper.parentGroup,
+ grandParent,
+ ownerSVGElement = element.ownerSVGElement,
+ i;
+
+ // remove events
+ element.onclick = element.onmouseout = element.onmouseover =
+ element.onmousemove = element.point = null;
+ stop(wrapper); // stop running animations
+
+ if (wrapper.clipPath && ownerSVGElement) {
+ // Look for existing references to this clipPath and remove them
+ // before destroying the element (#6196).
+ each(
+ // The upper case version is for Edge
+ ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'),
+ function (el) {
+ // Include the closing paranthesis in the test to rule out
+ // id's from 10 and above (#6550)
+ if (el
+ .getAttribute('clip-path')
+ .match(RegExp(
+ // Edge puts quotes inside the url, others not
+ '[\("]#' + wrapper.clipPath.element.id + '[\)"]'
+ ))
+ ) {
+ el.removeAttribute('clip-path');
+ }
+ }
+ );
+ wrapper.clipPath = wrapper.clipPath.destroy();
+ }
+
+ // Destroy stops in case this is a gradient object
+ if (wrapper.stops) {
+ for (i = 0; i < wrapper.stops.length; i++) {
+ wrapper.stops[i] = wrapper.stops[i].destroy();
+ }
+ wrapper.stops = null;
+ }
+
+ // remove element
+ wrapper.safeRemoveChild(element);
+
+
+ wrapper.destroyShadows();
+
+
+ // In case of useHTML, clean up empty containers emulating SVG groups
+ // (#1960, #2393, #2697).
+ while (
+ parentToClean &&
+ parentToClean.div &&
+ parentToClean.div.childNodes.length === 0
+ ) {
+ grandParent = parentToClean.parentGroup;
+ wrapper.safeRemoveChild(parentToClean.div);
+ delete parentToClean.div;
+ parentToClean = grandParent;
+ }
+
+ // remove from alignObjects
+ if (wrapper.alignTo) {
+ erase(wrapper.renderer.alignedObjects, wrapper);
+ }
+
+ objectEach(wrapper, function (val, key) {
+ delete wrapper[key];
+ });
+
+ return null;
+ },
+
+
+ /**
+ * @typedef {Object} ShadowOptions
+ * @property {string} [color=#000000] The shadow color.
+ * @property {number} [offsetX=1] The horizontal offset from the element.
+ * @property {number} [offsetY=1] The vertical offset from the element.
+ * @property {number} [opacity=0.15] The shadow opacity.
+ * @property {number} [width=3] The shadow width or distance from the
+ * element.
+ */
+ /**
+ * Add a shadow to the element. Must be called after the element is added to
+ * the DOM. In styled mode, this method is not used, instead use `defs` and
+ * filters.
+ *
+ * @param {boolean|ShadowOptions} shadowOptions The shadow options. If
+ * `true`, the default options are applied. If `false`, the current
+ * shadow will be removed.
+ * @param {SVGElement} [group] The SVG group element where the shadows will
+ * be applied. The default is to add it to the same parent as the current
+ * element. Internally, this is ised for pie slices, where all the
+ * shadows are added to an element behind all the slices.
+ * @param {boolean} [cutOff] Used internally for column shadows.
+ *
+ * @returns {SVGElement} Returns the SVGElement for chaining.
+ *
+ * @example
+ * renderer.rect(10, 100, 100, 100)
+ * .attr({ fill: 'red' })
+ * .shadow(true);
+ */
+ shadow: function (shadowOptions, group, cutOff) {
+ var shadows = [],
+ i,
+ shadow,
+ element = this.element,
+ strokeWidth,
+ shadowWidth,
+ shadowElementOpacity,
+
+ // compensate for inverted plot area
+ transform;
+
+ if (!shadowOptions) {
+ this.destroyShadows();
+
+ } else if (!this.shadows) {
+ shadowWidth = pick(shadowOptions.width, 3);
+ shadowElementOpacity = (shadowOptions.opacity || 0.15) /
+ shadowWidth;
+ transform = this.parentInverted ?
+ '(-1,-1)' :
+ '(' + pick(shadowOptions.offsetX, 1) + ', ' +
+ pick(shadowOptions.offsetY, 1) + ')';
+ for (i = 1; i <= shadowWidth; i++) {
+ shadow = element.cloneNode(0);
+ strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
+ attr(shadow, {
+ 'isShadow': 'true',
+ 'stroke':
+ shadowOptions.color || '#000000',
+ 'stroke-opacity': shadowElementOpacity * i,
+ 'stroke-width': strokeWidth,
+ 'transform': 'translate' + transform,
+ 'fill': 'none'
+ });
+ if (cutOff) {
+ attr(
+ shadow,
+ 'height',
+ Math.max(attr(shadow, 'height') - strokeWidth, 0)
+ );
+ shadow.cutHeight = strokeWidth;
+ }
+
+ if (group) {
+ group.element.appendChild(shadow);
+ } else if (element.parentNode) {
+ element.parentNode.insertBefore(shadow, element);
+ }
+
+ shadows.push(shadow);
+ }
+
+ this.shadows = shadows;
+ }
+ return this;
+
+ },
+
+ /**
+ * Destroy shadows on the element.
+ * @private
+ */
+ destroyShadows: function () {
+ each(this.shadows || [], function (shadow) {
+ this.safeRemoveChild(shadow);
+ }, this);
+ this.shadows = undefined;
+ },
+
+
+
+ xGetter: function (key) {
+ if (this.element.nodeName === 'circle') {
+ if (key === 'x') {
+ key = 'cx';
+ } else if (key === 'y') {
+ key = 'cy';
+ }
+ }
+ return this._defaultGetter(key);
+ },
+
+ /**
+ * Get the current value of an attribute or pseudo attribute, used mainly
+ * for animation. Called internally from the {@link
+ * Highcharts.SVGRenderer#attr}
+ * function.
+ *
+ * @private
+ */
+ _defaultGetter: function (key) {
+ var ret = pick(
+ this[key + 'Value'], // align getter
+ this[key],
+ this.element ? this.element.getAttribute(key) : null,
+ 0
+ );
+
+ if (/^[\-0-9\.]+$/.test(ret)) { // is numerical
+ ret = parseFloat(ret);
+ }
+ return ret;
+ },
+
+
+ dSetter: function (value, key, element) {
+ if (value && value.join) { // join path
+ value = value.join(' ');
+ }
+ if (/(NaN| {2}|^$)/.test(value)) {
+ value = 'M 0 0';
+ }
+
+ // Check for cache before resetting. Resetting causes disturbance in the
+ // DOM, causing flickering in some cases in Edge/IE (#6747). Also
+ // possible performance gain.
+ if (this[key] !== value) {
+ element.setAttribute(key, value);
+ this[key] = value;
+ }
+
+ },
+
+ dashstyleSetter: function (value) {
+ var i,
+ strokeWidth = this['stroke-width'];
+
+ // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new
+ // strokeWidth function, we should be able to use that instead.
+ if (strokeWidth === 'inherit') {
+ strokeWidth = 1;
+ }
+ value = value && value.toLowerCase();
+ if (value) {
+ value = value
+ .replace('shortdashdotdot', '3,1,1,1,1,1,')
+ .replace('shortdashdot', '3,1,1,1')
+ .replace('shortdot', '1,1,')
+ .replace('shortdash', '3,1,')
+ .replace('longdash', '8,3,')
+ .replace(/dot/g, '1,3,')
+ .replace('dash', '4,3,')
+ .replace(/,$/, '')
+ .split(','); // ending comma
+
+ i = value.length;
+ while (i--) {
+ value[i] = pInt(value[i]) * strokeWidth;
+ }
+ value = value.join(',')
+ .replace(/NaN/g, 'none'); // #3226
+ this.element.setAttribute('stroke-dasharray', value);
+ }
+ },
+
+ alignSetter: function (value) {
+ var convert = { left: 'start', center: 'middle', right: 'end' };
+ this.alignValue = value;
+ this.element.setAttribute('text-anchor', convert[value]);
+ },
+ opacitySetter: function (value, key, element) {
+ this[key] = value;
+ element.setAttribute(key, value);
+ },
+ titleSetter: function (value) {
+ var titleNode = this.element.getElementsByTagName('title')[0];
+ if (!titleNode) {
+ titleNode = doc.createElementNS(this.SVG_NS, 'title');
+ this.element.appendChild(titleNode);
+ }
+
+ // Remove text content if it exists
+ if (titleNode.firstChild) {
+ titleNode.removeChild(titleNode.firstChild);
+ }
+
+ titleNode.appendChild(
+ doc.createTextNode(
+ // #3276, #3895
+ (String(pick(value), '')).replace(/<[^>]*>/g, '')
+ )
+ );
+ },
+ textSetter: function (value) {
+ if (value !== this.textStr) {
+ // Delete bBox memo when the text changes
+ delete this.bBox;
+
+ this.textStr = value;
+ if (this.added) {
+ this.renderer.buildText(this);
+ }
+ }
+ },
+ fillSetter: function (value, key, element) {
+ if (typeof value === 'string') {
+ element.setAttribute(key, value);
+ } else if (value) {
+ this.colorGradient(value, key, element);
+ }
+ },
+ visibilitySetter: function (value, key, element) {
+ // IE9-11 doesn't handle visibilty:inherit well, so we remove the
+ // attribute instead (#2881, #3909)
+ if (value === 'inherit') {
+ element.removeAttribute(key);
+ } else if (this[key] !== value) { // #6747
+ element.setAttribute(key, value);
+ }
+ this[key] = value;
+ },
+ zIndexSetter: function (value, key) {
+ var renderer = this.renderer,
+ parentGroup = this.parentGroup,
+ parentWrapper = parentGroup || renderer,
+ parentNode = parentWrapper.element || renderer.box,
+ childNodes,
+ otherElement,
+ otherZIndex,
+ element = this.element,
+ inserted,
+ undefinedOtherZIndex,
+ svgParent = parentNode === renderer.box,
+ run = this.added,
+ i;
+
+ if (defined(value)) {
+ // So we can read it for other elements in the group
+ element.zIndex = value;
+
+ value = +value;
+ if (this[key] === value) { // Only update when needed (#3865)
+ run = false;
+ }
+ this[key] = value;
+ }
+
+ // Insert according to this and other elements' zIndex. Before .add() is
+ // called, nothing is done. Then on add, or by later calls to
+ // zIndexSetter, the node is placed on the right place in the DOM.
+ if (run) {
+ value = this.zIndex;
+
+ if (value && parentGroup) {
+ parentGroup.handleZ = true;
+ }
+
+ childNodes = parentNode.childNodes;
+ for (i = childNodes.length - 1; i >= 0 && !inserted; i--) {
+ otherElement = childNodes[i];
+ otherZIndex = otherElement.zIndex;
+ undefinedOtherZIndex = !defined(otherZIndex);
+
+ if (otherElement !== element) {
+ if (
+ // Negative zIndex versus no zIndex:
+ // On all levels except the highest. If the parent is ,
+ // then we don't want to put items before or
+ (value < 0 && undefinedOtherZIndex && !svgParent && !i)
+ ) {
+ parentNode.insertBefore(element, childNodes[i]);
+ inserted = true;
+ } else if (
+ // Insert after the first element with a lower zIndex
+ pInt(otherZIndex) <= value ||
+ // If negative zIndex, add this before first undefined zIndex element
+ (undefinedOtherZIndex && (!defined(value) || value >= 0))
+ ) {
+ parentNode.insertBefore(
+ element,
+ childNodes[i + 1] || null // null for oldIE export
+ );
+ inserted = true;
+ }
+ }
+ }
+
+ if (!inserted) {
+ parentNode.insertBefore(
+ element,
+ childNodes[svgParent ? 3 : 0] || null // null for oldIE
+ );
+ inserted = true;
+ }
+ }
+ return inserted;
+ },
+ _defaultSetter: function (value, key, element) {
+ element.setAttribute(key, value);
+ }
+});
+
+// Some shared setters and getters
+SVGElement.prototype.yGetter =
+SVGElement.prototype.xGetter;
+SVGElement.prototype.translateXSetter =
+SVGElement.prototype.translateYSetter =
+SVGElement.prototype.rotationSetter =
+SVGElement.prototype.verticalAlignSetter =
+SVGElement.prototype.rotationOriginXSetter =
+SVGElement.prototype.rotationOriginYSetter =
+SVGElement.prototype.scaleXSetter =
+SVGElement.prototype.scaleYSetter =
+SVGElement.prototype.matrixSetter = function (value, key) {
+ this[key] = value;
+ this.doTransform = true;
+};
+
+
+// WebKit and Batik have problems with a stroke-width of zero, so in this case
+// we remove the stroke attribute altogether. #1270, #1369, #3065, #3072.
+SVGElement.prototype['stroke-widthSetter'] =
+SVGElement.prototype.strokeSetter = function (value, key, element) {
+ this[key] = value;
+ // Only apply the stroke attribute if the stroke width is defined and larger
+ // than 0
+ if (this.stroke && this['stroke-width']) {
+ // Use prototype as instance may be overridden
+ SVGElement.prototype.fillSetter.call(
+ this,
+ this.stroke,
+ 'stroke',
+ element
+ );
+
+ element.setAttribute('stroke-width', this['stroke-width']);
+ this.hasStroke = true;
+ } else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
+ element.removeAttribute('stroke');
+ this.hasStroke = false;
+ }
+};
+
+
+/**
+ * Allows direct access to the Highcharts rendering layer in order to draw
+ * primitive shapes like circles, rectangles, paths or text directly on a chart,
+ * or independent from any chart. The SVGRenderer represents a wrapper object
+ * for SVG in modern browsers. Through the VMLRenderer, part of the `oldie.js`
+ * module, it also brings vector graphics to IE <= 8.
+ *
+ * An existing chart's renderer can be accessed through {@link Chart.renderer}.
+ * The renderer can also be used completely decoupled from a chart.
+ *
+ * @param {HTMLDOMElement} container - Where to put the SVG in the web page.
+ * @param {number} width - The width of the SVG.
+ * @param {number} height - The height of the SVG.
+ * @param {boolean} [forExport=false] - Whether the rendered content is intended
+ * for export.
+ * @param {boolean} [allowHTML=true] - Whether the renderer is allowed to
+ * include HTML text, which will be projected on top of the SVG.
+ *
+ * @example
+ * // Use directly without a chart object.
+ * var renderer = new Highcharts.Renderer(parentNode, 600, 400);
+ *
+ * @sample highcharts/members/renderer-on-chart
+ * Annotating a chart programmatically.
+ * @sample highcharts/members/renderer-basic
+ * Independent SVG drawing.
+ *
+ * @class Highcharts.SVGRenderer
+ */
+SVGRenderer = H.SVGRenderer = function () {
+ this.init.apply(this, arguments);
+};
+extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
+ /**
+ * A pointer to the renderer's associated Element class. The VMLRenderer
+ * will have a pointer to VMLElement here.
+ * @type {SVGElement}
+ */
+ Element: SVGElement,
+ SVG_NS: SVG_NS,
+ /**
+ * Initialize the SVGRenderer. Overridable initiator function that takes
+ * the same parameters as the constructor.
+ */
+ init: function (container, width, height, style, forExport, allowHTML) {
+ var renderer = this,
+ boxWrapper,
+ element,
+ desc;
+
+ boxWrapper = renderer.createElement('svg')
+ .attr({
+ 'version': '1.1',
+ 'class': 'highcharts-root'
+ })
+
+ .css(this.getStyle(style))
+ ;
+ element = boxWrapper.element;
+ container.appendChild(element);
+
+ // Always use ltr on the container, otherwise text-anchor will be
+ // flipped and text appear outside labels, buttons, tooltip etc (#3482)
+ attr(container, 'dir', 'ltr');
+
+ // For browsers other than IE, add the namespace attribute (#1978)
+ if (container.innerHTML.indexOf('xmlns') === -1) {
+ attr(element, 'xmlns', this.SVG_NS);
+ }
+
+ // object properties
+ renderer.isSVG = true;
+
+ /**
+ * The root `svg` node of the renderer.
+ * @name box
+ * @memberOf SVGRenderer
+ * @type {SVGDOMElement}
+ */
+ this.box = element;
+ /**
+ * The wrapper for the root `svg` node of the renderer.
+ *
+ * @name boxWrapper
+ * @memberOf SVGRenderer
+ * @type {SVGElement}
+ */
+ this.boxWrapper = boxWrapper;
+ renderer.alignedObjects = [];
+
+ /**
+ * Page url used for internal references.
+ * @type {string}
+ */
+ // #24, #672, #1070
+ this.url = (
+ (isFirefox || isWebKit) &&
+ doc.getElementsByTagName('base').length
+ ) ?
+ win.location.href
+ .replace(/#.*?$/, '') // remove the hash
+ .replace(/<[^>]*>/g, '') // wing cut HTML
+ // escape parantheses and quotes
+ .replace(/([\('\)])/g, '\\$1')
+ // replace spaces (needed for Safari only)
+ .replace(/ /g, '%20') :
+ '';
+
+ // Add description
+ desc = this.createElement('desc').add();
+ desc.element.appendChild(
+ doc.createTextNode('Created with Highcharts 5aaca1890988fc7073f112a2ff1556645b8dda5e')
+ );
+
+ /**
+ * A pointer to the `defs` node of the root SVG.
+ * @type {SVGElement}
+ * @name defs
+ * @memberOf SVGRenderer
+ */
+ renderer.defs = this.createElement('defs').add();
+ renderer.allowHTML = allowHTML;
+ renderer.forExport = forExport;
+ renderer.gradients = {}; // Object where gradient SvgElements are stored
+ renderer.cache = {}; // Cache for numerical bounding boxes
+ renderer.cacheKeys = [];
+ renderer.imgCount = 0;
+
+ renderer.setSize(width, height, false);
+
+
+
+ // Issue 110 workaround:
+ // In Firefox, if a div is positioned by percentage, its pixel position
+ // may land between pixels. The container itself doesn't display this,
+ // but an SVG element inside this container will be drawn at subpixel
+ // precision. In order to draw sharp lines, this must be compensated
+ // for. This doesn't seem to work inside iframes though (like in
+ // jsFiddle).
+ var subPixelFix, rect;
+ if (isFirefox && container.getBoundingClientRect) {
+ subPixelFix = function () {
+ css(container, { left: 0, top: 0 });
+ rect = container.getBoundingClientRect();
+ css(container, {
+ left: (Math.ceil(rect.left) - rect.left) + 'px',
+ top: (Math.ceil(rect.top) - rect.top) + 'px'
+ });
+ };
+
+ // run the fix now
+ subPixelFix();
+
+ // run it on resize
+ renderer.unSubPixelFix = addEvent(win, 'resize', subPixelFix);
+ }
+ },
+
+
+
+ /**
+ * Get the global style setting for the renderer.
+ * @private
+ * @param {CSSObject} style - Style settings.
+ * @return {CSSObject} The style settings mixed with defaults.
+ */
+ getStyle: function (style) {
+ this.style = extend({
+
+ fontFamily: '"Lucida Grande", "Lucida Sans Unicode", ' +
+ 'Arial, Helvetica, sans-serif',
+ fontSize: '12px'
+
+ }, style);
+ return this.style;
+ },
+ /**
+ * Apply the global style on the renderer, mixed with the default styles.
+ *
+ * @param {CSSObject} style - CSS to apply.
+ */
+ setStyle: function (style) {
+ this.boxWrapper.css(this.getStyle(style));
+ },
+
+
+ /**
+ * Detect whether the renderer is hidden. This happens when one of the
+ * parent elements has `display: none`. Used internally to detect when we
+ * needto render preliminarily in another div to get the text bounding boxes
+ * right.
+ *
+ * @returns {boolean} True if it is hidden.
+ */
+ isHidden: function () { // #608
+ return !this.boxWrapper.getBBox().width;
+ },
+
+ /**
+ * Destroys the renderer and its allocated members.
+ */
+ destroy: function () {
+ var renderer = this,
+ rendererDefs = renderer.defs;
+ renderer.box = null;
+ renderer.boxWrapper = renderer.boxWrapper.destroy();
+
+ // Call destroy on all gradient elements
+ destroyObjectProperties(renderer.gradients || {});
+ renderer.gradients = null;
+
+ // Defs are null in VMLRenderer
+ // Otherwise, destroy them here.
+ if (rendererDefs) {
+ renderer.defs = rendererDefs.destroy();
+ }
+
+ // Remove sub pixel fix handler (#982)
+ if (renderer.unSubPixelFix) {
+ renderer.unSubPixelFix();
+ }
+
+ renderer.alignedObjects = null;
+
+ return null;
+ },
+
+ /**
+ * Create a wrapper for an SVG element. Serves as a factory for
+ * {@link SVGElement}, but this function is itself mostly called from
+ * primitive factories like {@link SVGRenderer#path}, {@link
+ * SVGRenderer#rect} or {@link SVGRenderer#text}.
+ *
+ * @param {string} nodeName - The node name, for example `rect`, `g` etc.
+ * @returns {SVGElement} The generated SVGElement.
+ */
+ createElement: function (nodeName) {
+ var wrapper = new this.Element();
+ wrapper.init(this, nodeName);
+ return wrapper;
+ },
+
+ /**
+ * Dummy function for plugins, called every time the renderer is updated.
+ * Prior to Highcharts 5, this was used for the canvg renderer.
+ * @function
+ */
+ draw: noop,
+
+ /**
+ * Get converted radial gradient attributes according to the radial
+ * reference. Used internally from the {@link SVGElement#colorGradient}
+ * function.
+ *
+ * @private
+ */
+ getRadialAttr: function (radialReference, gradAttr) {
+ return {
+ cx: (radialReference[0] - radialReference[2] / 2) +
+ gradAttr.cx * radialReference[2],
+ cy: (radialReference[1] - radialReference[2] / 2) +
+ gradAttr.cy * radialReference[2],
+ r: gradAttr.r * radialReference[2]
+ };
+ },
+
+ /**
+ * Extendable function to measure the tspan width.
+ *
+ * @private
+ */
+ getSpanWidth: function (wrapper) {
+ return wrapper.getBBox(true).width;
+ },
+
+ applyEllipsis: function (wrapper, tspan, text, width) {
+ var renderer = this,
+ rotation = wrapper.rotation,
+ str = text,
+ currentIndex,
+ minIndex = 0,
+ maxIndex = text.length,
+ updateTSpan = function (s) {
+ tspan.removeChild(tspan.firstChild);
+ if (s) {
+ tspan.appendChild(doc.createTextNode(s));
+ }
+ },
+ actualWidth,
+ wasTooLong;
+ wrapper.rotation = 0; // discard rotation when computing box
+ actualWidth = renderer.getSpanWidth(wrapper, tspan);
+ wasTooLong = actualWidth > width;
+ if (wasTooLong) {
+ while (minIndex <= maxIndex) {
+ currentIndex = Math.ceil((minIndex + maxIndex) / 2);
+ str = text.substring(0, currentIndex) + '\u2026';
+ updateTSpan(str);
+ actualWidth = renderer.getSpanWidth(wrapper, tspan);
+ if (minIndex === maxIndex) {
+ // Complete
+ minIndex = maxIndex + 1;
+ } else if (actualWidth > width) {
+ // Too large. Set max index to current.
+ maxIndex = currentIndex - 1;
+ } else {
+ // Within width. Set min index to current.
+ minIndex = currentIndex;
+ }
+ }
+ // If max index was 0 it means just ellipsis was also to large.
+ if (maxIndex === 0) {
+ // Remove ellipses.
+ updateTSpan('');
+ }
+ }
+ wrapper.rotation = rotation; // Apply rotation again.
+ return wasTooLong;
+ },
+
+ /**
+ * A collection of characters mapped to HTML entities. When `useHTML` on an
+ * element is true, these entities will be rendered correctly by HTML. In
+ * the SVG pseudo-HTML, they need to be unescaped back to simple characters,
+ * so for example `<` will render as `<`.
+ *
+ * @example
+ * // Add support for unescaping quotes
+ * Highcharts.SVGRenderer.prototype.escapes['"'] = '"';
+ *
+ * @type {Object}
+ */
+ escapes: {
+ '&': '&',
+ '<': '<',
+ '>': '>',
+ "'": ''', // eslint-disable-line quotes
+ '"': '"'
+ },
+
+ /**
+ * Parse a simple HTML string into SVG tspans. Called internally when text
+ * is set on an SVGElement. The function supports a subset of HTML tags,
+ * CSS text features like `width`, `text-overflow`, `white-space`, and
+ * also attributes like `href` and `style`.
+ * @private
+ * @param {SVGElement} wrapper The parent SVGElement.
+ */
+ buildText: function (wrapper) {
+ var textNode = wrapper.element,
+ renderer = this,
+ forExport = renderer.forExport,
+ textStr = pick(wrapper.textStr, '').toString(),
+ hasMarkup = textStr.indexOf('<') !== -1,
+ lines,
+ childNodes = textNode.childNodes,
+ clsRegex,
+ styleRegex,
+ hrefRegex,
+ wasTooLong,
+ parentX = attr(textNode, 'x'),
+ textStyles = wrapper.styles,
+ width = wrapper.textWidth,
+ textLineHeight = textStyles && textStyles.lineHeight,
+ textOutline = textStyles && textStyles.textOutline,
+ ellipsis = textStyles && textStyles.textOverflow === 'ellipsis',
+ noWrap = textStyles && textStyles.whiteSpace === 'nowrap',
+ fontSize = textStyles && textStyles.fontSize,
+ textCache,
+ isSubsequentLine,
+ i = childNodes.length,
+ tempParent = width && !wrapper.added && this.box,
+ getLineHeight = function (tspan) {
+ var fontSizeStyle;
+
+ fontSizeStyle = /(px|em)$/.test(tspan && tspan.style.fontSize) ?
+ tspan.style.fontSize :
+ (fontSize || renderer.style.fontSize || 12);
+
+
+ return textLineHeight ?
+ pInt(textLineHeight) :
+ renderer.fontMetrics(
+ fontSizeStyle,
+ // Get the computed size from parent if not explicit
+ tspan.getAttribute('style') ? tspan : textNode
+ ).h;
+ },
+ unescapeEntities = function (inputStr) {
+ objectEach(renderer.escapes, function (value, key) {
+ inputStr = inputStr.replace(
+ new RegExp(value, 'g'),
+ key
+ );
+ });
+ return inputStr;
+ };
+
+ // The buildText code is quite heavy, so if we're not changing something
+ // that affects the text, skip it (#6113).
+ textCache = [
+ textStr,
+ ellipsis,
+ noWrap,
+ textLineHeight,
+ textOutline,
+ fontSize,
+ width
+ ].join(',');
+ if (textCache === wrapper.textCache) {
+ return;
+ }
+ wrapper.textCache = textCache;
+
+ // Remove old text
+ while (i--) {
+ textNode.removeChild(childNodes[i]);
+ }
+
+ // Skip tspans, add text directly to text node. The forceTSpan is a hook
+ // used in text outline hack.
+ if (
+ !hasMarkup &&
+ !textOutline &&
+ !ellipsis &&
+ !width &&
+ textStr.indexOf(' ') === -1
+ ) {
+ textNode.appendChild(doc.createTextNode(unescapeEntities(textStr)));
+
+ // Complex strings, add more logic
+ } else {
+
+ clsRegex = /<.*class="([^"]+)".*>/;
+ styleRegex = /<.*style="([^"]+)".*>/;
+ hrefRegex = /<.*href="([^"]+)".*>/;
+
+ if (tempParent) {
+ // attach it to the DOM to read offset width
+ tempParent.appendChild(textNode);
+ }
+
+ if (hasMarkup) {
+ lines = textStr
+
+ .replace(/<(b|strong)>/g, '')
+ .replace(/<(i|em)>/g, '')
+
+ .replace(//g, ' ')
+ .split(//g);
+
+ } else {
+ lines = [textStr];
+ }
+
+
+ // Trim empty lines (#5261)
+ lines = grep(lines, function (line) {
+ return line !== '';
+ });
+
+
+ // build the lines
+ each(lines, function buildTextLines(line, lineNo) {
+ var spans,
+ spanNo = 0;
+ line = line
+ // Trim to prevent useless/costly process on the spaces
+ // (#5258)
+ .replace(/^\s+|\s+$/g, '')
+ .replace(//g, ' |||');
+ spans = line.split('|||');
+
+ each(spans, function buildTextSpans(span) {
+ if (span !== '' || spans.length === 1) {
+ var attributes = {},
+ tspan = doc.createElementNS(
+ renderer.SVG_NS,
+ 'tspan'
+ ),
+ spanCls,
+ spanStyle; // #390
+ if (clsRegex.test(span)) {
+ spanCls = span.match(clsRegex)[1];
+ attr(tspan, 'class', spanCls);
+ }
+ if (styleRegex.test(span)) {
+ spanStyle = span.match(styleRegex)[1].replace(
+ /(;| |^)color([ :])/,
+ '$1fill$2'
+ );
+ attr(tspan, 'style', spanStyle);
+ }
+
+ // Not for export - #1529
+ if (hrefRegex.test(span) && !forExport) {
+ attr(
+ tspan,
+ 'onclick',
+ 'location.href=\"' +
+ span.match(hrefRegex)[1] + '\"'
+ );
+ attr(tspan, 'class', 'highcharts-anchor');
+
+ css(tspan, { cursor: 'pointer' });
+
+ }
+
+ // Strip away unsupported HTML tags (#7126)
+ span = unescapeEntities(
+ span.replace(/<[a-zA-Z\/](.|\n)*?>/g, '') || ' '
+ );
+
+ // Nested tags aren't supported, and cause crash in
+ // Safari (#1596)
+ if (span !== ' ') {
+
+ // add the text node
+ tspan.appendChild(doc.createTextNode(span));
+
+ // First span in a line, align it to the left
+ if (!spanNo) {
+ if (lineNo && parentX !== null) {
+ attributes.x = parentX;
+ }
+ } else {
+ attributes.dx = 0; // #16
+ }
+
+ // add attributes
+ attr(tspan, attributes);
+
+ // Append it
+ textNode.appendChild(tspan);
+
+ // first span on subsequent line, add the line
+ // height
+ if (!spanNo && isSubsequentLine) {
+
+ // allow getting the right offset height in
+ // exporting in IE
+ if (!svg && forExport) {
+ css(tspan, { display: 'block' });
+ }
+
+ // Set the line height based on the font size of
+ // either the text element or the tspan element
+ attr(
+ tspan,
+ 'dy',
+ getLineHeight(tspan)
+ );
+ }
+
+ /*
+ // Experimental text wrapping based on
+ // getSubstringLength
+ if (width) {
+ var spans = renderer.breakText(wrapper, width);
+
+ each(spans, function (span) {
+
+ var dy = getLineHeight(tspan);
+ tspan = doc.createElementNS(
+ SVG_NS,
+ 'tspan'
+ );
+ tspan.appendChild(
+ doc.createTextNode(span)
+ );
+ attr(tspan, {
+ dy: dy,
+ x: parentX
+ });
+ if (spanStyle) { // #390
+ attr(tspan, 'style', spanStyle);
+ }
+ textNode.appendChild(tspan);
+ });
+
+ }
+ // */
+
+ // Check width and apply soft breaks or ellipsis
+ if (width) {
+ var words = span.replace(
+ /([^\^])-/g,
+ '$1- '
+ ).split(' '), // #1273
+ hasWhiteSpace = (
+ spans.length > 1 ||
+ lineNo ||
+ (words.length > 1 && !noWrap)
+ ),
+ tooLong,
+ rest = [],
+ actualWidth,
+ dy = getLineHeight(tspan),
+ rotation = wrapper.rotation;
+
+ if (ellipsis) {
+ wasTooLong = renderer.applyEllipsis(
+ wrapper,
+ tspan,
+ span,
+ width
+ );
+ }
+
+ while (
+ !ellipsis &&
+ hasWhiteSpace &&
+ (words.length || rest.length)
+ ) {
+ // discard rotation when computing box
+ wrapper.rotation = 0;
+ actualWidth = renderer.getSpanWidth(
+ wrapper,
+ tspan
+ );
+ tooLong = actualWidth > width;
+
+ // For ellipsis, do a binary search for the
+ // correct string length
+ if (wasTooLong === undefined) {
+ wasTooLong = tooLong; // First time
+ }
+
+ // Looping down, this is the first word
+ // sequence that is not too long, so we can
+ // move on to build the next line.
+ if (!tooLong || words.length === 1) {
+ words = rest;
+ rest = [];
+
+ if (words.length && !noWrap) {
+ tspan = doc.createElementNS(
+ SVG_NS,
+ 'tspan'
+ );
+ attr(tspan, {
+ dy: dy,
+ x: parentX
+ });
+ if (spanStyle) { // #390
+ attr(tspan, 'style', spanStyle);
+ }
+ textNode.appendChild(tspan);
+ }
+
+ // a single word is pressing it out
+ if (actualWidth > width) {
+ width = actualWidth;
+ }
+ } else { // append to existing line tspan
+ tspan.removeChild(tspan.firstChild);
+ rest.unshift(words.pop());
+ }
+ if (words.length) {
+ tspan.appendChild(
+ doc.createTextNode(
+ words.join(' ')
+ .replace(/- /g, '-')
+ )
+ );
+ }
+ }
+ wrapper.rotation = rotation;
+ }
+
+ spanNo++;
+ // */
+ }
+ }
+ });
+ // To avoid beginning lines that doesn't add to the textNode
+ // (#6144)
+ isSubsequentLine = (
+ isSubsequentLine ||
+ textNode.childNodes.length
+ );
+ });
+
+ if (wasTooLong) {
+ wrapper.attr(
+ 'title',
+ unescapeEntities(wrapper.textStr) // #7179
+ );
+ }
+ if (tempParent) {
+ tempParent.removeChild(textNode);
+ }
+
+ // Apply the text outline
+ if (textOutline && wrapper.applyTextOutline) {
+ wrapper.applyTextOutline(textOutline);
+ }
+ }
+ },
+
+
+
+ /*
+ breakText: function (wrapper, width) {
+ var bBox = wrapper.getBBox(),
+ node = wrapper.element,
+ charnum = node.textContent.length,
+ stringWidth,
+ // try this position first, based on average character width
+ guessedLineCharLength = Math.round(width * charnum / bBox.width),
+ pos = guessedLineCharLength,
+ spans = [],
+ increment = 0,
+ startPos = 0,
+ endPos,
+ safe = 0;
+
+ if (bBox.width > width) {
+ while (startPos < charnum && safe < 100) {
+
+ while (endPos === undefined && safe < 100) {
+ stringWidth = node.getSubStringLength(
+ startPos,
+ pos - startPos
+ );
+
+ if (stringWidth <= width) {
+ if (increment === -1) {
+ endPos = pos;
+ } else {
+ increment = 1;
+ }
+ } else {
+ if (increment === 1) {
+ endPos = pos - 1;
+ } else {
+ increment = -1;
+ }
+ }
+ pos += increment;
+ safe++;
+ }
+
+ spans.push(node.textContent.substr(startPos, endPos - startPos));
+
+ startPos = endPos;
+ pos = startPos + guessedLineCharLength;
+ endPos = undefined;
+ }
+ }
+
+ return spans;
+ },
+ // */
+
+ /**
+ * Returns white for dark colors and black for bright colors.
+ *
+ * @param {ColorString} rgba - The color to get the contrast for.
+ * @returns {string} The contrast color, either `#000000` or `#FFFFFF`.
+ */
+ getContrast: function (rgba) {
+ rgba = color(rgba).rgba;
+
+ // The threshold may be discussed. Here's a proposal for adding
+ // different weight to the color channels (#6216)
+ /*
+ rgba[0] *= 1; // red
+ rgba[1] *= 1.2; // green
+ rgba[2] *= 0.7; // blue
+ */
+
+ return rgba[0] + rgba[1] + rgba[2] > 2 * 255 ? '#000000' : '#FFFFFF';
+ },
+
+ /**
+ * Create a button with preset states.
+ * @param {string} text - The text or HTML to draw.
+ * @param {number} x - The x position of the button's left side.
+ * @param {number} y - The y position of the button's top side.
+ * @param {Function} callback - The function to execute on button click or
+ * touch.
+ * @param {SVGAttributes} [normalState] - SVG attributes for the normal
+ * state.
+ * @param {SVGAttributes} [hoverState] - SVG attributes for the hover state.
+ * @param {SVGAttributes} [pressedState] - SVG attributes for the pressed
+ * state.
+ * @param {SVGAttributes} [disabledState] - SVG attributes for the disabled
+ * state.
+ * @param {Symbol} [shape=rect] - The shape type.
+ * @returns {SVGRenderer} The button element.
+ */
+ button: function (
+ text,
+ x,
+ y,
+ callback,
+ normalState,
+ hoverState,
+ pressedState,
+ disabledState,
+ shape
+ ) {
+ var label = this.label(
+ text,
+ x,
+ y,
+ shape,
+ null,
+ null,
+ null,
+ null,
+ 'button'
+ ),
+ curState = 0;
+
+ // Default, non-stylable attributes
+ label.attr(merge({
+ 'padding': 8,
+ 'r': 2
+ }, normalState));
+
+
+ // Presentational
+ var normalStyle,
+ hoverStyle,
+ pressedStyle,
+ disabledStyle;
+
+ // Normal state - prepare the attributes
+ normalState = merge({
+ fill: '#f7f7f7',
+ stroke: '#cccccc',
+ 'stroke-width': 1,
+ style: {
+ color: '#333333',
+ cursor: 'pointer',
+ fontWeight: 'normal'
+ }
+ }, normalState);
+ normalStyle = normalState.style;
+ delete normalState.style;
+
+ // Hover state
+ hoverState = merge(normalState, {
+ fill: '#e6e6e6'
+ }, hoverState);
+ hoverStyle = hoverState.style;
+ delete hoverState.style;
+
+ // Pressed state
+ pressedState = merge(normalState, {
+ fill: '#e6ebf5',
+ style: {
+ color: '#000000',
+ fontWeight: 'bold'
+ }
+ }, pressedState);
+ pressedStyle = pressedState.style;
+ delete pressedState.style;
+
+ // Disabled state
+ disabledState = merge(normalState, {
+ style: {
+ color: '#cccccc'
+ }
+ }, disabledState);
+ disabledStyle = disabledState.style;
+ delete disabledState.style;
+
+
+ // Add the events. IE9 and IE10 need mouseover and mouseout to funciton
+ // (#667).
+ addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function () {
+ if (curState !== 3) {
+ label.setState(1);
+ }
+ });
+ addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function () {
+ if (curState !== 3) {
+ label.setState(curState);
+ }
+ });
+
+ label.setState = function (state) {
+ // Hover state is temporary, don't record it
+ if (state !== 1) {
+ label.state = curState = state;
+ }
+ // Update visuals
+ label.removeClass(
+ /highcharts-button-(normal|hover|pressed|disabled)/
+ )
+ .addClass(
+ 'highcharts-button-' +
+ ['normal', 'hover', 'pressed', 'disabled'][state || 0]
+ );
+
+
+ label.attr([
+ normalState,
+ hoverState,
+ pressedState,
+ disabledState
+ ][state || 0])
+ .css([
+ normalStyle,
+ hoverStyle,
+ pressedStyle,
+ disabledStyle
+ ][state || 0]);
+
+ };
+
+
+
+ // Presentational attributes
+ label
+ .attr(normalState)
+ .css(extend({ cursor: 'default' }, normalStyle));
+
+
+ return label
+ .on('click', function (e) {
+ if (curState !== 3) {
+ callback.call(label, e);
+ }
+ });
+ },
+
+ /**
+ * Make a straight line crisper by not spilling out to neighbour pixels.
+ *
+ * @param {Array} points - The original points on the format `['M', 0, 0,
+ * 'L', 100, 0]`.
+ * @param {number} width - The width of the line.
+ * @returns {Array} The original points array, but modified to render
+ * crisply.
+ */
+ crispLine: function (points, width) {
+ // normalize to a crisp line
+ if (points[1] === points[4]) {
+ // Substract due to #1129. Now bottom and left axis gridlines behave
+ // the same.
+ points[1] = points[4] = Math.round(points[1]) - (width % 2 / 2);
+ }
+ if (points[2] === points[5]) {
+ points[2] = points[5] = Math.round(points[2]) + (width % 2 / 2);
+ }
+ return points;
+ },
+
+
+ /**
+ * Draw a path, wraps the SVG `path` element.
+ *
+ * @param {Array} [path] An SVG path definition in array form.
+ *
+ * @example
+ * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
+ * .attr({ stroke: '#ff00ff' })
+ * .add();
+ * @returns {SVGElement} The generated wrapper element.
+ *
+ * @sample highcharts/members/renderer-path-on-chart/
+ * Draw a path in a chart
+ * @sample highcharts/members/renderer-path/
+ * Draw a path independent from a chart
+ *
+ *//**
+ * Draw a path, wraps the SVG `path` element.
+ *
+ * @param {SVGAttributes} [attribs] The initial attributes.
+ * @returns {SVGElement} The generated wrapper element.
+ */
+ path: function (path) {
+ var attribs = {
+
+ fill: 'none'
+
+ };
+ if (isArray(path)) {
+ attribs.d = path;
+ } else if (isObject(path)) { // attributes
+ extend(attribs, path);
+ }
+ return this.createElement('path').attr(attribs);
+ },
+
+ /**
+ * Draw a circle, wraps the SVG `circle` element.
+ *
+ * @param {number} [x] The center x position.
+ * @param {number} [y] The center y position.
+ * @param {number} [r] The radius.
+ * @returns {SVGElement} The generated wrapper element.
+ *
+ * @sample highcharts/members/renderer-circle/ Drawing a circle
+ *//**
+ * Draw a circle, wraps the SVG `circle` element.
+ *
+ * @param {SVGAttributes} [attribs] The initial attributes.
+ * @returns {SVGElement} The generated wrapper element.
+ */
+ circle: function (x, y, r) {
+ var attribs = isObject(x) ? x : { x: x, y: y, r: r },
+ wrapper = this.createElement('circle');
+
+ // Setting x or y translates to cx and cy
+ wrapper.xSetter = wrapper.ySetter = function (value, key, element) {
+ element.setAttribute('c' + key, value);
+ };
+
+ return wrapper.attr(attribs);
+ },
+
+ /**
+ * Draw and return an arc.
+ * @param {number} [x=0] Center X position.
+ * @param {number} [y=0] Center Y position.
+ * @param {number} [r=0] The outer radius of the arc.
+ * @param {number} [innerR=0] Inner radius like used in donut charts.
+ * @param {number} [start=0] The starting angle of the arc in radians, where
+ * 0 is to the right and `-Math.PI/2` is up.
+ * @param {number} [end=0] The ending angle of the arc in radians, where 0
+ * is to the right and `-Math.PI/2` is up.
+ * @returns {SVGElement} The generated wrapper element.
+ *
+ * @sample highcharts/members/renderer-arc/
+ * Drawing an arc
+ *//**
+ * Draw and return an arc. Overloaded function that takes arguments object.
+ * @param {SVGAttributes} attribs Initial SVG attributes.
+ * @returns {SVGElement} The generated wrapper element.
+ */
+ arc: function (x, y, r, innerR, start, end) {
+ var arc,
+ options;
+
+ if (isObject(x)) {
+ options = x;
+ y = options.y;
+ r = options.r;
+ innerR = options.innerR;
+ start = options.start;
+ end = options.end;
+ x = options.x;
+ } else {
+ options = {
+ innerR: innerR,
+ start: start,
+ end: end
+ };
+ }
+
+ // Arcs are defined as symbols for the ability to set
+ // attributes in attr and animate
+ arc = this.symbol('arc', x, y, r, r, options);
+ arc.r = r; // #959
+ return arc;
+ },
+
+ /**
+ * Draw and return a rectangle.
+ * @param {number} [x] Left position.
+ * @param {number} [y] Top position.
+ * @param {number} [width] Width of the rectangle.
+ * @param {number} [height] Height of the rectangle.
+ * @param {number} [r] Border corner radius.
+ * @param {number} [strokeWidth] A stroke width can be supplied to allow
+ * crisp drawing.
+ * @returns {SVGElement} The generated wrapper element.
+ *//**
+ * Draw and return a rectangle.
+ * @param {SVGAttributes} [attributes]
+ * General SVG attributes for the rectangle.
+ * @return {SVGElement}
+ * The generated wrapper element.
+ *
+ * @sample highcharts/members/renderer-rect-on-chart/
+ * Draw a rectangle in a chart
+ * @sample highcharts/members/renderer-rect/
+ * Draw a rectangle independent from a chart
+ */
+ rect: function (x, y, width, height, r, strokeWidth) {
+
+ r = isObject(x) ? x.r : r;
+
+ var wrapper = this.createElement('rect'),
+ attribs = isObject(x) ? x : x === undefined ? {} : {
+ x: x,
+ y: y,
+ width: Math.max(width, 0),
+ height: Math.max(height, 0)
+ };
+
+
+ if (strokeWidth !== undefined) {
+ attribs.strokeWidth = strokeWidth;
+ attribs = wrapper.crisp(attribs);
+ }
+ attribs.fill = 'none';
+
+
+ if (r) {
+ attribs.r = r;
+ }
+
+ wrapper.rSetter = function (value, key, element) {
+ attr(element, {
+ rx: value,
+ ry: value
+ });
+ };
+
+ return wrapper.attr(attribs);
+ },
+
+ /**
+ * Resize the {@link SVGRenderer#box} and re-align all aligned child
+ * elements.
+ * @param {number} width
+ * The new pixel width.
+ * @param {number} height
+ * The new pixel height.
+ * @param {Boolean|AnimationOptions} [animate=true]
+ * Whether and how to animate.
+ */
+ setSize: function (width, height, animate) {
+ var renderer = this,
+ alignedObjects = renderer.alignedObjects,
+ i = alignedObjects.length;
+
+ renderer.width = width;
+ renderer.height = height;
+
+ renderer.boxWrapper.animate({
+ width: width,
+ height: height
+ }, {
+ step: function () {
+ this.attr({
+ viewBox: '0 0 ' + this.attr('width') + ' ' +
+ this.attr('height')
+ });
+ },
+ duration: pick(animate, true) ? undefined : 0
+ });
+
+ while (i--) {
+ alignedObjects[i].align();
+ }
+ },
+
+ /**
+ * Create and return an svg group element. Child
+ * {@link Highcharts.SVGElement} objects are added to the group by using the
+ * group as the first parameter
+ * in {@link Highcharts.SVGElement#add|add()}.
+ *
+ * @param {string} [name] The group will be given a class name of
+ * `highcharts-{name}`. This can be used for styling and scripting.
+ * @returns {SVGElement} The generated wrapper element.
+ *
+ * @sample highcharts/members/renderer-g/
+ * Show and hide grouped objects
+ */
+ g: function (name) {
+ var elem = this.createElement('g');
+ return name ? elem.attr({ 'class': 'highcharts-' + name }) : elem;
+ },
+
+ /**
+ * Display an image.
+ * @param {string} src The image source.
+ * @param {number} [x] The X position.
+ * @param {number} [y] The Y position.
+ * @param {number} [width] The image width. If omitted, it defaults to the
+ * image file width.
+ * @param {number} [height] The image height. If omitted it defaults to the
+ * image file height.
+ * @returns {SVGElement} The generated wrapper element.
+ *
+ * @sample highcharts/members/renderer-image-on-chart/
+ * Add an image in a chart
+ * @sample highcharts/members/renderer-image/
+ * Add an image independent of a chart
+ */
+ image: function (src, x, y, width, height) {
+ var attribs = {
+ preserveAspectRatio: 'none'
+ },
+ elemWrapper;
+
+ // optional properties
+ if (arguments.length > 1) {
+ extend(attribs, {
+ x: x,
+ y: y,
+ width: width,
+ height: height
+ });
+ }
+
+ elemWrapper = this.createElement('image').attr(attribs);
+
+ // set the href in the xlink namespace
+ if (elemWrapper.element.setAttributeNS) {
+ elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
+ 'href', src);
+ } else {
+ // could be exporting in IE
+ // using href throws "not supported" in ie7 and under, requries
+ // regex shim to fix later
+ elemWrapper.element.setAttribute('hc-svg-href', src);
+ }
+ return elemWrapper;
+ },
+
+ /**
+ * Draw a symbol out of pre-defined shape paths from
+ * {@link SVGRenderer#symbols}.
+ * It is used in Highcharts for point makers, which cake a `symbol` option,
+ * and label and button backgrounds like in the tooltip and stock flags.
+ *
+ * @param {Symbol} symbol - The symbol name.
+ * @param {number} x - The X coordinate for the top left position.
+ * @param {number} y - The Y coordinate for the top left position.
+ * @param {number} width - The pixel width.
+ * @param {number} height - The pixel height.
+ * @param {Object} [options] - Additional options, depending on the actual
+ * symbol drawn.
+ * @param {number} [options.anchorX] - The anchor X position for the
+ * `callout` symbol. This is where the chevron points to.
+ * @param {number} [options.anchorY] - The anchor Y position for the
+ * `callout` symbol. This is where the chevron points to.
+ * @param {number} [options.end] - The end angle of an `arc` symbol.
+ * @param {boolean} [options.open] - Whether to draw `arc` symbol open or
+ * closed.
+ * @param {number} [options.r] - The radius of an `arc` symbol, or the
+ * border radius for the `callout` symbol.
+ * @param {number} [options.start] - The start angle of an `arc` symbol.
+ */
+ symbol: function (symbol, x, y, width, height, options) {
+
+ var ren = this,
+ obj,
+ imageRegex = /^url\((.*?)\)$/,
+ isImage = imageRegex.test(symbol),
+ sym = !isImage && (this.symbols[symbol] ? symbol : 'circle'),
+
+
+ // get the symbol definition function
+ symbolFn = sym && this.symbols[sym],
+
+ // check if there's a path defined for this symbol
+ path = defined(x) && symbolFn && symbolFn.call(
+ this.symbols,
+ Math.round(x),
+ Math.round(y),
+ width,
+ height,
+ options
+ ),
+ imageSrc,
+ centerImage;
+
+ if (symbolFn) {
+ obj = this.path(path);
+
+
+ obj.attr('fill', 'none');
+
+
+ // expando properties for use in animate and attr
+ extend(obj, {
+ symbolName: sym,
+ x: x,
+ y: y,
+ width: width,
+ height: height
+ });
+ if (options) {
+ extend(obj, options);
+ }
+
+
+ // Image symbols
+ } else if (isImage) {
+
+
+ imageSrc = symbol.match(imageRegex)[1];
+
+ // Create the image synchronously, add attribs async
+ obj = this.image(imageSrc);
+
+ // The image width is not always the same as the symbol width. The
+ // image may be centered within the symbol, as is the case when
+ // image shapes are used as label backgrounds, for example in flags.
+ obj.imgwidth = pick(
+ symbolSizes[imageSrc] && symbolSizes[imageSrc].width,
+ options && options.width
+ );
+ obj.imgheight = pick(
+ symbolSizes[imageSrc] && symbolSizes[imageSrc].height,
+ options && options.height
+ );
+ /**
+ * Set the size and position
+ */
+ centerImage = function () {
+ obj.attr({
+ width: obj.width,
+ height: obj.height
+ });
+ };
+
+ /**
+ * Width and height setters that take both the image's physical size
+ * and the label size into consideration, and translates the image
+ * to center within the label.
+ */
+ each(['width', 'height'], function (key) {
+ obj[key + 'Setter'] = function (value, key) {
+ var attribs = {},
+ imgSize = this['img' + key],
+ trans = key === 'width' ? 'translateX' : 'translateY';
+ this[key] = value;
+ if (defined(imgSize)) {
+ if (this.element) {
+ this.element.setAttribute(key, imgSize);
+ }
+ if (!this.alignByTranslate) {
+ attribs[trans] = ((this[key] || 0) - imgSize) / 2;
+ this.attr(attribs);
+ }
+ }
+ };
+ });
+
+
+ if (defined(x)) {
+ obj.attr({
+ x: x,
+ y: y
+ });
+ }
+ obj.isImg = true;
+
+ if (defined(obj.imgwidth) && defined(obj.imgheight)) {
+ centerImage();
+ } else {
+ // Initialize image to be 0 size so export will still function
+ // if there's no cached sizes.
+ obj.attr({ width: 0, height: 0 });
+
+ // Create a dummy JavaScript image to get the width and height.
+ createElement('img', {
+ onload: function () {
+
+ var chart = charts[ren.chartIndex];
+
+ // Special case for SVGs on IE11, the width is not
+ // accessible until the image is part of the DOM
+ // (#2854).
+ if (this.width === 0) {
+ css(this, {
+ position: 'absolute',
+ top: '-999em'
+ });
+ doc.body.appendChild(this);
+ }
+
+ // Center the image
+ symbolSizes[imageSrc] = { // Cache for next
+ width: this.width,
+ height: this.height
+ };
+ obj.imgwidth = this.width;
+ obj.imgheight = this.height;
+
+ if (obj.element) {
+ centerImage();
+ }
+
+ // Clean up after #2854 workaround.
+ if (this.parentNode) {
+ this.parentNode.removeChild(this);
+ }
+
+ // Fire the load event when all external images are
+ // loaded
+ ren.imgCount--;
+ if (!ren.imgCount && chart && chart.onload) {
+ chart.onload();
+ }
+ },
+ src: imageSrc
+ });
+ this.imgCount++;
+ }
+ }
+
+ return obj;
+ },
+
+ /**
+ * @typedef {string} Symbol
+ *
+ * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`,
+ * `triangle`, `triangle-down`. Symbols are used internally for point
+ * markers, button and label borders and backgrounds, or custom shapes.
+ * Extendable by adding to {@link SVGRenderer#symbols}.
+ */
+ /**
+ * An extendable collection of functions for defining symbol paths.
+ */
+ symbols: {
+ 'circle': function (x, y, w, h) {
+ // Return a full arc
+ return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
+ start: 0,
+ end: Math.PI * 2,
+ open: false
+ });
+ },
+
+ 'square': function (x, y, w, h) {
+ return [
+ 'M', x, y,
+ 'L', x + w, y,
+ x + w, y + h,
+ x, y + h,
+ 'Z'
+ ];
+ },
+
+ 'triangle': function (x, y, w, h) {
+ return [
+ 'M', x + w / 2, y,
+ 'L', x + w, y + h,
+ x, y + h,
+ 'Z'
+ ];
+ },
+
+ 'triangle-down': function (x, y, w, h) {
+ return [
+ 'M', x, y,
+ 'L', x + w, y,
+ x + w / 2, y + h,
+ 'Z'
+ ];
+ },
+ 'diamond': function (x, y, w, h) {
+ return [
+ 'M', x + w / 2, y,
+ 'L', x + w, y + h / 2,
+ x + w / 2, y + h,
+ x, y + h / 2,
+ 'Z'
+ ];
+ },
+ 'arc': function (x, y, w, h, options) {
+ var start = options.start,
+ rx = options.r || w,
+ ry = options.r || h || w,
+ proximity = 0.001,
+ fullCircle =
+ Math.abs(options.end - options.start - 2 * Math.PI) <
+ proximity,
+ // Substract a small number to prevent cos and sin of start and
+ // end from becoming equal on 360 arcs (related: #1561)
+ end = options.end - proximity,
+ innerRadius = options.innerR,
+ open = pick(options.open, fullCircle),
+ cosStart = Math.cos(start),
+ sinStart = Math.sin(start),
+ cosEnd = Math.cos(end),
+ sinEnd = Math.sin(end),
+ // Proximity takes care of rounding errors around PI (#6971)
+ longArc = options.end - start - Math.PI < proximity ? 0 : 1,
+ arc;
+
+ arc = [
+ 'M',
+ x + rx * cosStart,
+ y + ry * sinStart,
+ 'A', // arcTo
+ rx, // x radius
+ ry, // y radius
+ 0, // slanting
+ longArc, // long or short arc
+ 1, // clockwise
+ x + rx * cosEnd,
+ y + ry * sinEnd
+ ];
+
+ if (defined(innerRadius)) {
+ arc.push(
+ open ? 'M' : 'L',
+ x + innerRadius * cosEnd,
+ y + innerRadius * sinEnd,
+ 'A', // arcTo
+ innerRadius, // x radius
+ innerRadius, // y radius
+ 0, // slanting
+ longArc, // long or short arc
+ 0, // clockwise
+ x + innerRadius * cosStart,
+ y + innerRadius * sinStart
+ );
+ }
+
+ arc.push(open ? '' : 'Z'); // close
+ return arc;
+ },
+
+ /**
+ * Callout shape used for default tooltips, also used for rounded
+ * rectangles in VML
+ */
+ callout: function (x, y, w, h, options) {
+ var arrowLength = 6,
+ halfDistance = 6,
+ r = Math.min((options && options.r) || 0, w, h),
+ safeDistance = r + halfDistance,
+ anchorX = options && options.anchorX,
+ anchorY = options && options.anchorY,
+ path;
+
+ path = [
+ 'M', x + r, y,
+ 'L', x + w - r, y, // top side
+ 'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
+ 'L', x + w, y + h - r, // right side
+ 'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-rgt
+ 'L', x + r, y + h, // bottom side
+ 'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
+ 'L', x, y + r, // left side
+ 'C', x, y, x, y, x + r, y // top-left corner
+ ];
+
+ // Anchor on right side
+ if (anchorX && anchorX > w) {
+
+ // Chevron
+ if (
+ anchorY > y + safeDistance &&
+ anchorY < y + h - safeDistance
+ ) {
+ path.splice(13, 3,
+ 'L', x + w, anchorY - halfDistance,
+ x + w + arrowLength, anchorY,
+ x + w, anchorY + halfDistance,
+ x + w, y + h - r
+ );
+
+ // Simple connector
+ } else {
+ path.splice(13, 3,
+ 'L', x + w, h / 2,
+ anchorX, anchorY,
+ x + w, h / 2,
+ x + w, y + h - r
+ );
+ }
+
+ // Anchor on left side
+ } else if (anchorX && anchorX < 0) {
+
+ // Chevron
+ if (
+ anchorY > y + safeDistance &&
+ anchorY < y + h - safeDistance
+ ) {
+ path.splice(33, 3,
+ 'L', x, anchorY + halfDistance,
+ x - arrowLength, anchorY,
+ x, anchorY - halfDistance,
+ x, y + r
+ );
+
+ // Simple connector
+ } else {
+ path.splice(33, 3,
+ 'L', x, h / 2,
+ anchorX, anchorY,
+ x, h / 2,
+ x, y + r
+ );
+ }
+
+ } else if ( // replace bottom
+ anchorY &&
+ anchorY > h &&
+ anchorX > x + safeDistance &&
+ anchorX < x + w - safeDistance
+ ) {
+ path.splice(23, 3,
+ 'L', anchorX + halfDistance, y + h,
+ anchorX, y + h + arrowLength,
+ anchorX - halfDistance, y + h,
+ x + r, y + h
+ );
+
+ } else if ( // replace top
+ anchorY &&
+ anchorY < 0 &&
+ anchorX > x + safeDistance &&
+ anchorX < x + w - safeDistance
+ ) {
+ path.splice(3, 3,
+ 'L', anchorX - halfDistance, y,
+ anchorX, y - arrowLength,
+ anchorX + halfDistance, y,
+ w - r, y
+ );
+ }
+
+ return path;
+ }
+ },
+
+ /**
+ * @typedef {SVGElement} ClipRect - A clipping rectangle that can be applied
+ * to one or more {@link SVGElement} instances. It is instanciated with the
+ * {@link SVGRenderer#clipRect} function and applied with the {@link
+ * SVGElement#clip} function.
+ *
+ * @example
+ * var circle = renderer.circle(100, 100, 100)
+ * .attr({ fill: 'red' })
+ * .add();
+ * var clipRect = renderer.clipRect(100, 100, 100, 100);
+ *
+ * // Leave only the lower right quarter visible
+ * circle.clip(clipRect);
+ */
+ /**
+ * Define a clipping rectangle. The clipping rectangle is later applied
+ * to {@link SVGElement} objects through the {@link SVGElement#clip}
+ * function.
+ *
+ * @param {String} id
+ * @param {number} x
+ * @param {number} y
+ * @param {number} width
+ * @param {number} height
+ * @returns {ClipRect} A clipping rectangle.
+ *
+ * @example
+ * var circle = renderer.circle(100, 100, 100)
+ * .attr({ fill: 'red' })
+ * .add();
+ * var clipRect = renderer.clipRect(100, 100, 100, 100);
+ *
+ * // Leave only the lower right quarter visible
+ * circle.clip(clipRect);
+ */
+ clipRect: function (x, y, width, height) {
+ var wrapper,
+ id = H.uniqueKey(),
+
+ clipPath = this.createElement('clipPath').attr({
+ id: id
+ }).add(this.defs);
+
+ wrapper = this.rect(x, y, width, height, 0).add(clipPath);
+ wrapper.id = id;
+ wrapper.clipPath = clipPath;
+ wrapper.count = 0;
+
+ return wrapper;
+ },
+
+
+
+
+
+ /**
+ * Draw text. The text can contain a subset of HTML, like spans and anchors
+ * and some basic text styling of these. For more advanced features like
+ * border and background, use {@link Highcharts.SVGRenderer#label} instead.
+ * To update the text after render, run `text.attr({ text: 'New text' })`.
+ * @param {String} str
+ * The text of (subset) HTML to draw.
+ * @param {number} x
+ * The x position of the text's lower left corner.
+ * @param {number} y
+ * The y position of the text's lower left corner.
+ * @param {Boolean} [useHTML=false]
+ * Use HTML to render the text.
+ *
+ * @return {SVGElement} The text object.
+ *
+ * @sample highcharts/members/renderer-text-on-chart/
+ * Annotate the chart freely
+ * @sample highcharts/members/renderer-on-chart/
+ * Annotate with a border and in response to the data
+ * @sample highcharts/members/renderer-text/
+ * Formatted text
+ */
+ text: function (str, x, y, useHTML) {
+
+ // declare variables
+ var renderer = this,
+ wrapper,
+ attribs = {};
+
+ if (useHTML && (renderer.allowHTML || !renderer.forExport)) {
+ return renderer.html(str, x, y);
+ }
+
+ attribs.x = Math.round(x || 0); // X always needed for line-wrap logic
+ if (y) {
+ attribs.y = Math.round(y);
+ }
+ if (str || str === 0) {
+ attribs.text = str;
+ }
+
+ wrapper = renderer.createElement('text')
+ .attr(attribs);
+
+ if (!useHTML) {
+ wrapper.xSetter = function (value, key, element) {
+ var tspans = element.getElementsByTagName('tspan'),
+ tspan,
+ parentVal = element.getAttribute(key),
+ i;
+ for (i = 0; i < tspans.length; i++) {
+ tspan = tspans[i];
+ // If the x values are equal, the tspan represents a
+ // linebreak
+ if (tspan.getAttribute(key) === parentVal) {
+ tspan.setAttribute(key, value);
+ }
+ }
+ element.setAttribute(key, value);
+ };
+ }
+
+ return wrapper;
+ },
+
+ /**
+ * Utility to return the baseline offset and total line height from the font
+ * size.
+ *
+ * @param {?string} fontSize The current font size to inspect. If not given,
+ * the font size will be found from the DOM element.
+ * @param {SVGElement|SVGDOMElement} [elem] The element to inspect for a
+ * current font size.
+ * @returns {Object} An object containing `h`: the line height, `b`: the
+ * baseline relative to the top of the box, and `f`: the font size.
+ */
+ fontMetrics: function (fontSize, elem) {
+ var lineHeight,
+ baseline;
+
+
+ fontSize = fontSize ||
+ // When the elem is a DOM element (#5932)
+ (elem && elem.style && elem.style.fontSize) ||
+ // Fall back on the renderer style default
+ (this.style && this.style.fontSize);
+
+
+
+ // Handle different units
+ if (/px/.test(fontSize)) {
+ fontSize = pInt(fontSize);
+ } else if (/em/.test(fontSize)) {
+ // The em unit depends on parent items
+ fontSize = parseFloat(fontSize) *
+ (elem ? this.fontMetrics(null, elem.parentNode).f : 16);
+ } else {
+ fontSize = 12;
+ }
+
+ // Empirical values found by comparing font size and bounding box
+ // height. Applies to the default font family.
+ // http://jsfiddle.net/highcharts/7xvn7/
+ lineHeight = fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2);
+ baseline = Math.round(lineHeight * 0.8);
+
+ return {
+ h: lineHeight,
+ b: baseline,
+ f: fontSize
+ };
+ },
+
+ /**
+ * Correct X and Y positioning of a label for rotation (#1764).
+ *
+ * @private
+ */
+ rotCorr: function (baseline, rotation, alterY) {
+ var y = baseline;
+ if (rotation && alterY) {
+ y = Math.max(y * Math.cos(rotation * deg2rad), 4);
+ }
+ return {
+ x: (-baseline / 3) * Math.sin(rotation * deg2rad),
+ y: y
+ };
+ },
+
+ /**
+ * Draw a label, which is an extended text element with support for border
+ * and background. Highcharts creates a `g` element with a text and a `path`
+ * or `rect` inside, to make it behave somewhat like a HTML div. Border and
+ * background are set through `stroke`, `stroke-width` and `fill` attributes
+ * using the {@link Highcharts.SVGElement#attr|attr} method. To update the
+ * text after render, run `label.attr({ text: 'New text' })`.
+ *
+ * @param {string} str
+ * The initial text string or (subset) HTML to render.
+ * @param {number} x
+ * The x position of the label's left side.
+ * @param {number} y
+ * The y position of the label's top side or baseline, depending on
+ * the `baseline` parameter.
+ * @param {String} shape
+ * The shape of the label's border/background, if any. Defaults to
+ * `rect`. Other possible values are `callout` or other shapes
+ * defined in {@link Highcharts.SVGRenderer#symbols}.
+ * @param {number} anchorX
+ * In case the `shape` has a pointer, like a flag, this is the
+ * coordinates it should be pinned to.
+ * @param {number} anchorY
+ * In case the `shape` has a pointer, like a flag, this is the
+ * coordinates it should be pinned to.
+ * @param {Boolean} baseline
+ * Whether to position the label relative to the text baseline,
+ * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
+ * upper border of the rectangle.
+ * @param {String} className
+ * Class name for the group.
+ *
+ * @return {SVGElement}
+ * The generated label.
+ *
+ * @sample highcharts/members/renderer-label-on-chart/
+ * A label on the chart
+ */
+ label: function (
+ str,
+ x,
+ y,
+ shape,
+ anchorX,
+ anchorY,
+ useHTML,
+ baseline,
+ className
+ ) {
+
+ var renderer = this,
+ wrapper = renderer.g(className !== 'button' && 'label'),
+ text = wrapper.text = renderer.text('', 0, 0, useHTML)
+ .attr({
+ zIndex: 1
+ }),
+ box,
+ bBox,
+ alignFactor = 0,
+ padding = 3,
+ paddingLeft = 0,
+ width,
+ height,
+ wrapperX,
+ wrapperY,
+ textAlign,
+ deferredAttr = {},
+ strokeWidth,
+ baselineOffset,
+ hasBGImage = /^url\((.*?)\)$/.test(shape),
+ needsBox = hasBGImage,
+ getCrispAdjust,
+ updateBoxSize,
+ updateTextPadding,
+ boxAttr;
+
+ if (className) {
+ wrapper.addClass('highcharts-' + className);
+ }
+
+
+ needsBox = hasBGImage;
+ getCrispAdjust = function () {
+ return (strokeWidth || 0) % 2 / 2;
+ };
+
+
+
+ /**
+ * This function runs after the label is added to the DOM (when the
+ * bounding box is available), and after the text of the label is
+ * updated to detect the new bounding box and reflect it in the border
+ * box.
+ */
+ updateBoxSize = function () {
+ var style = text.element.style,
+ crispAdjust,
+ attribs = {};
+
+ bBox = (
+ (width === undefined || height === undefined || textAlign) &&
+ defined(text.textStr) &&
+ text.getBBox()
+ ); // #3295 && 3514 box failure when string equals 0
+ wrapper.width = (
+ (width || bBox.width || 0) +
+ 2 * padding +
+ paddingLeft
+ );
+ wrapper.height = (height || bBox.height || 0) + 2 * padding;
+
+ // Update the label-scoped y offset
+ baselineOffset = padding +
+ renderer.fontMetrics(style && style.fontSize, text).b;
+
+
+ if (needsBox) {
+
+ // Create the border box if it is not already present
+ if (!box) {
+ // Symbol definition exists (#5324)
+ wrapper.box = box = renderer.symbols[shape] || hasBGImage ?
+ renderer.symbol(shape) :
+ renderer.rect();
+
+ box.addClass( // Don't use label className for buttons
+ (className === 'button' ? '' : 'highcharts-label-box') +
+ (className ? ' highcharts-' + className + '-box' : '')
+ );
+
+ box.add(wrapper);
+
+ crispAdjust = getCrispAdjust();
+ attribs.x = crispAdjust;
+ attribs.y = (baseline ? -baselineOffset : 0) + crispAdjust;
+ }
+
+ // Apply the box attributes
+ attribs.width = Math.round(wrapper.width);
+ attribs.height = Math.round(wrapper.height);
+
+ box.attr(extend(attribs, deferredAttr));
+ deferredAttr = {};
+ }
+ };
+
+ /**
+ * This function runs after setting text or padding, but only if padding
+ * is changed
+ */
+ updateTextPadding = function () {
+ var textX = paddingLeft + padding,
+ textY;
+
+ // determin y based on the baseline
+ textY = baseline ? 0 : baselineOffset;
+
+ // compensate for alignment
+ if (
+ defined(width) &&
+ bBox &&
+ (textAlign === 'center' || textAlign === 'right')
+ ) {
+ textX += { center: 0.5, right: 1 }[textAlign] *
+ (width - bBox.width);
+ }
+
+ // update if anything changed
+ if (textX !== text.x || textY !== text.y) {
+ text.attr('x', textX);
+ if (textY !== undefined) {
+ text.attr('y', textY);
+ }
+ }
+
+ // record current values
+ text.x = textX;
+ text.y = textY;
+ };
+
+ /**
+ * Set a box attribute, or defer it if the box is not yet created
+ * @param {Object} key
+ * @param {Object} value
+ */
+ boxAttr = function (key, value) {
+ if (box) {
+ box.attr(key, value);
+ } else {
+ deferredAttr[key] = value;
+ }
+ };
+
+ /**
+ * After the text element is added, get the desired size of the border
+ * box and add it before the text in the DOM.
+ */
+ wrapper.onAdd = function () {
+ text.add(wrapper);
+ wrapper.attr({
+ // Alignment is available now (#3295, 0 not rendered if given
+ // as a value)
+ text: (str || str === 0) ? str : '',
+ x: x,
+ y: y
+ });
+
+ if (box && defined(anchorX)) {
+ wrapper.attr({
+ anchorX: anchorX,
+ anchorY: anchorY
+ });
+ }
+ };
+
+ /*
+ * Add specific attribute setters.
+ */
+
+ // only change local variables
+ wrapper.widthSetter = function (value) {
+ width = H.isNumber(value) ? value : null; // width:auto => null
+ };
+ wrapper.heightSetter = function (value) {
+ height = value;
+ };
+ wrapper['text-alignSetter'] = function (value) {
+ textAlign = value;
+ };
+ wrapper.paddingSetter = function (value) {
+ if (defined(value) && value !== padding) {
+ padding = wrapper.padding = value;
+ updateTextPadding();
+ }
+ };
+ wrapper.paddingLeftSetter = function (value) {
+ if (defined(value) && value !== paddingLeft) {
+ paddingLeft = value;
+ updateTextPadding();
+ }
+ };
+
+
+ // change local variable and prevent setting attribute on the group
+ wrapper.alignSetter = function (value) {
+ value = { left: 0, center: 0.5, right: 1 }[value];
+ if (value !== alignFactor) {
+ alignFactor = value;
+ // Bounding box exists, means we're dynamically changing
+ if (bBox) {
+ wrapper.attr({ x: wrapperX }); // #5134
+ }
+ }
+ };
+
+ // apply these to the box and the text alike
+ wrapper.textSetter = function (value) {
+ if (value !== undefined) {
+ text.textSetter(value);
+ }
+ updateBoxSize();
+ updateTextPadding();
+ };
+
+ // apply these to the box but not to the text
+ wrapper['stroke-widthSetter'] = function (value, key) {
+ if (value) {
+ needsBox = true;
+ }
+ strokeWidth = this['stroke-width'] = value;
+ boxAttr(key, value);
+ };
+
+ wrapper.strokeSetter =
+ wrapper.fillSetter =
+ wrapper.rSetter = function (value, key) {
+ if (key !== 'r') {
+ if (key === 'fill' && value) {
+ needsBox = true;
+ }
+ // for animation getter (#6776)
+ wrapper[key] = value;
+ }
+ boxAttr(key, value);
+ };
+
+ wrapper.anchorXSetter = function (value, key) {
+ anchorX = wrapper.anchorX = value;
+ boxAttr(key, Math.round(value) - getCrispAdjust() - wrapperX);
+ };
+ wrapper.anchorYSetter = function (value, key) {
+ anchorY = wrapper.anchorY = value;
+ boxAttr(key, value - wrapperY);
+ };
+
+ // rename attributes
+ wrapper.xSetter = function (value) {
+ wrapper.x = value; // for animation getter
+ if (alignFactor) {
+ value -= alignFactor * ((width || bBox.width) + 2 * padding);
+ }
+ wrapperX = Math.round(value);
+ wrapper.attr('translateX', wrapperX);
+ };
+ wrapper.ySetter = function (value) {
+ wrapperY = wrapper.y = Math.round(value);
+ wrapper.attr('translateY', wrapperY);
+ };
+
+ // Redirect certain methods to either the box or the text
+ var baseCss = wrapper.css;
+ return extend(wrapper, {
+ /**
+ * Pick up some properties and apply them to the text instead of the
+ * wrapper.
+ * @ignore
+ */
+ css: function (styles) {
+ if (styles) {
+ var textStyles = {};
+ // Create a copy to avoid altering the original object
+ // (#537)
+ styles = merge(styles);
+ each(wrapper.textProps, function (prop) {
+ if (styles[prop] !== undefined) {
+ textStyles[prop] = styles[prop];
+ delete styles[prop];
+ }
+ });
+ text.css(textStyles);
+ }
+ return baseCss.call(wrapper, styles);
+ },
+ /**
+ * Return the bounding box of the box, not the group.
+ * @ignore
+ */
+ getBBox: function () {
+ return {
+ width: bBox.width + 2 * padding,
+ height: bBox.height + 2 * padding,
+ x: bBox.x - padding,
+ y: bBox.y - padding
+ };
+ },
+
+ /**
+ * Apply the shadow to the box.
+ * @ignore
+ */
+ shadow: function (b) {
+ if (b) {
+ updateBoxSize();
+ if (box) {
+ box.shadow(b);
+ }
+ }
+ return wrapper;
+ },
+
+ /**
+ * Destroy and release memory.
+ * @ignore
+ */
+ destroy: function () {
+
+ // Added by button implementation
+ removeEvent(wrapper.element, 'mouseenter');
+ removeEvent(wrapper.element, 'mouseleave');
+
+ if (text) {
+ text = text.destroy();
+ }
+ if (box) {
+ box = box.destroy();
+ }
+ // Call base implementation to destroy the rest
+ SVGElement.prototype.destroy.call(wrapper);
+
+ // Release local pointers (#1298)
+ wrapper =
+ renderer =
+ updateBoxSize =
+ updateTextPadding =
+ boxAttr = null;
+ }
+ });
+ }
+}); // end SVGRenderer
+
+
+// general renderer
+H.Renderer = SVGRenderer;
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+/* eslint max-len: ["warn", 80, 4] */
+var attr = H.attr,
+ createElement = H.createElement,
+ css = H.css,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ isFirefox = H.isFirefox,
+ isMS = H.isMS,
+ isWebKit = H.isWebKit,
+ pick = H.pick,
+ pInt = H.pInt,
+ SVGElement = H.SVGElement,
+ SVGRenderer = H.SVGRenderer,
+ win = H.win,
+ wrap = H.wrap;
+
+// Extend SvgElement for useHTML option
+extend(SVGElement.prototype, /** @lends SVGElement.prototype */ {
+ /**
+ * Apply CSS to HTML elements. This is used in text within SVG rendering and
+ * by the VML renderer
+ */
+ htmlCss: function (styles) {
+ var wrapper = this,
+ element = wrapper.element,
+ textWidth = styles && element.tagName === 'SPAN' && styles.width;
+
+ if (textWidth) {
+ delete styles.width;
+ wrapper.textWidth = textWidth;
+ wrapper.updateTransform();
+ }
+ if (styles && styles.textOverflow === 'ellipsis') {
+ styles.whiteSpace = 'nowrap';
+ styles.overflow = 'hidden';
+ }
+ wrapper.styles = extend(wrapper.styles, styles);
+ css(wrapper.element, styles);
+
+ return wrapper;
+ },
+
+ /**
+ * VML and useHTML method for calculating the bounding box based on offsets
+ * @param {Boolean} refresh Whether to force a fresh value from the DOM or
+ * to use the cached value.
+ *
+ * @return {Object} A hash containing values for x, y, width and height
+ */
+
+ htmlGetBBox: function () {
+ var wrapper = this,
+ element = wrapper.element;
+
+ return {
+ x: element.offsetLeft,
+ y: element.offsetTop,
+ width: element.offsetWidth,
+ height: element.offsetHeight
+ };
+ },
+
+ /**
+ * VML override private method to update elements based on internal
+ * properties based on SVG transform
+ */
+ htmlUpdateTransform: function () {
+ // aligning non added elements is expensive
+ if (!this.added) {
+ this.alignOnAdd = true;
+ return;
+ }
+
+ var wrapper = this,
+ renderer = wrapper.renderer,
+ elem = wrapper.element,
+ translateX = wrapper.translateX || 0,
+ translateY = wrapper.translateY || 0,
+ x = wrapper.x || 0,
+ y = wrapper.y || 0,
+ align = wrapper.textAlign || 'left',
+ alignCorrection = { left: 0, center: 0.5, right: 1 }[align],
+ styles = wrapper.styles;
+
+ // apply translate
+ css(elem, {
+ marginLeft: translateX,
+ marginTop: translateY
+ });
+
+
+ if (wrapper.shadows) { // used in labels/tooltip
+ each(wrapper.shadows, function (shadow) {
+ css(shadow, {
+ marginLeft: translateX + 1,
+ marginTop: translateY + 1
+ });
+ });
+ }
+
+
+ // apply inversion
+ if (wrapper.inverted) { // wrapper is a group
+ each(elem.childNodes, function (child) {
+ renderer.invertChild(child, elem);
+ });
+ }
+
+ if (elem.tagName === 'SPAN') {
+
+ var rotation = wrapper.rotation,
+ baseline,
+ textWidth = pInt(wrapper.textWidth),
+ whiteSpace = styles && styles.whiteSpace,
+ currentTextTransform = [
+ rotation,
+ align,
+ elem.innerHTML,
+ wrapper.textWidth,
+ wrapper.textAlign
+ ].join(',');
+
+ // Do the calculations and DOM access only if properties changed
+ if (currentTextTransform !== wrapper.cTT) {
+
+
+ baseline = renderer.fontMetrics(elem.style.fontSize).b;
+
+ // Renderer specific handling of span rotation
+ if (defined(rotation)) {
+ wrapper.setSpanRotation(
+ rotation,
+ alignCorrection,
+ baseline
+ );
+ }
+
+ // Reset multiline/ellipsis in order to read width (#4928,
+ // #5417)
+ css(elem, {
+ width: '',
+ whiteSpace: whiteSpace || 'nowrap'
+ });
+
+ // Update textWidth
+ if (
+ elem.offsetWidth > textWidth &&
+ /[ \-]/.test(elem.textContent || elem.innerText)
+ ) { // #983, #1254
+ css(elem, {
+ width: textWidth + 'px',
+ display: 'block',
+ whiteSpace: whiteSpace || 'normal' // #3331
+ });
+ }
+
+
+ wrapper.getSpanCorrection(
+ elem.offsetWidth,
+ baseline,
+ alignCorrection,
+ rotation,
+ align
+ );
+ }
+
+ // apply position with correction
+ css(elem, {
+ left: (x + (wrapper.xCorr || 0)) + 'px',
+ top: (y + (wrapper.yCorr || 0)) + 'px'
+ });
+
+ // Force reflow in webkit to apply the left and top on useHTML
+ // element (#1249)
+ if (isWebKit) {
+ // Assigned to baseline for lint purpose
+ baseline = elem.offsetHeight;
+ }
+
+ // record current text transform
+ wrapper.cTT = currentTextTransform;
+ }
+ },
+
+ /**
+ * Set the rotation of an individual HTML span
+ */
+ setSpanRotation: function (rotation, alignCorrection, baseline) {
+ var rotationStyle = {},
+ cssTransformKey = this.renderer.getTransformKey();
+
+ rotationStyle[cssTransformKey] = rotationStyle.transform =
+ 'rotate(' + rotation + 'deg)';
+ rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] =
+ rotationStyle.transformOrigin =
+ (alignCorrection * 100) + '% ' + baseline + 'px';
+ css(this.element, rotationStyle);
+ },
+
+ /**
+ * Get the correction in X and Y positioning as the element is rotated.
+ */
+ getSpanCorrection: function (width, baseline, alignCorrection) {
+ this.xCorr = -width * alignCorrection;
+ this.yCorr = -baseline;
+ }
+});
+
+// Extend SvgRenderer for useHTML option.
+extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */ {
+
+ getTransformKey: function () {
+ return isMS && !/Edge/.test(win.navigator.userAgent) ?
+ '-ms-transform' :
+ isWebKit ?
+ '-webkit-transform' :
+ isFirefox ?
+ 'MozTransform' :
+ win.opera ?
+ '-o-transform' :
+ '';
+ },
+
+ /**
+ * Create HTML text node. This is used by the VML renderer as well as the
+ * SVG renderer through the useHTML option.
+ *
+ * @param {String} str
+ * @param {Number} x
+ * @param {Number} y
+ */
+ html: function (str, x, y) {
+ var wrapper = this.createElement('span'),
+ element = wrapper.element,
+ renderer = wrapper.renderer,
+ isSVG = renderer.isSVG,
+ addSetters = function (element, style) {
+ // These properties are set as attributes on the SVG group, and
+ // as identical CSS properties on the div. (#3542)
+ each(['opacity', 'visibility'], function (prop) {
+ wrap(element, prop + 'Setter', function (
+ proceed,
+ value,
+ key,
+ elem
+ ) {
+ proceed.call(this, value, key, elem);
+ style[key] = value;
+ });
+ });
+ };
+
+ // Text setter
+ wrapper.textSetter = function (value) {
+ if (value !== element.innerHTML) {
+ delete this.bBox;
+ }
+ this.textStr = value;
+ element.innerHTML = pick(value, '');
+ wrapper.htmlUpdateTransform();
+ };
+
+ // Add setters for the element itself (#4938)
+ if (isSVG) { // #4938, only for HTML within SVG
+ addSetters(wrapper, wrapper.element.style);
+ }
+
+ // Various setters which rely on update transform
+ wrapper.xSetter =
+ wrapper.ySetter =
+ wrapper.alignSetter =
+ wrapper.rotationSetter =
+ function (value, key) {
+ if (key === 'align') {
+ // Do not overwrite the SVGElement.align method. Same as VML.
+ key = 'textAlign';
+ }
+ wrapper[key] = value;
+ wrapper.htmlUpdateTransform();
+ };
+
+ // Set the default attributes
+ wrapper
+ .attr({
+ text: str,
+ x: Math.round(x),
+ y: Math.round(y)
+ })
+ .css({
+
+ fontFamily: this.style.fontFamily,
+ fontSize: this.style.fontSize,
+
+ position: 'absolute'
+ });
+
+ // Keep the whiteSpace style outside the wrapper.styles collection
+ element.style.whiteSpace = 'nowrap';
+
+ // Use the HTML specific .css method
+ wrapper.css = wrapper.htmlCss;
+
+ // This is specific for HTML within SVG
+ if (isSVG) {
+ wrapper.add = function (svgGroupWrapper) {
+
+ var htmlGroup,
+ container = renderer.box.parentNode,
+ parentGroup,
+ parents = [];
+
+ this.parentGroup = svgGroupWrapper;
+
+ // Create a mock group to hold the HTML elements
+ if (svgGroupWrapper) {
+ htmlGroup = svgGroupWrapper.div;
+ if (!htmlGroup) {
+
+ // Read the parent chain into an array and read from top
+ // down
+ parentGroup = svgGroupWrapper;
+ while (parentGroup) {
+
+ parents.push(parentGroup);
+
+ // Move up to the next parent group
+ parentGroup = parentGroup.parentGroup;
+ }
+
+ // Ensure dynamically updating position when any parent
+ // is translated
+ each(parents.reverse(), function (parentGroup) {
+ var htmlGroupStyle,
+ cls = attr(parentGroup.element, 'class');
+
+ // Common translate setter for X and Y on the HTML
+ // group. Reverted the fix for #6957 du to
+ // positioning problems and offline export (#7254,
+ // #7280, #7529)
+ function translateSetter(value, key) {
+ parentGroup[key] = value;
+
+ if (key === 'translateX') {
+ htmlGroupStyle.left = value + 'px';
+ } else {
+ htmlGroupStyle.top = value + 'px';
+ }
+
+ parentGroup.doTransform = true;
+ }
+
+ if (cls) {
+ cls = { className: cls };
+ } // else null
+
+ // Create a HTML div and append it to the parent div
+ // to emulate the SVG group structure
+ htmlGroup =
+ parentGroup.div =
+ parentGroup.div || createElement('div', cls, {
+ position: 'absolute',
+ left: (parentGroup.translateX || 0) + 'px',
+ top: (parentGroup.translateY || 0) + 'px',
+ display: parentGroup.display,
+ opacity: parentGroup.opacity, // #5075
+ pointerEvents: (
+ parentGroup.styles &&
+ parentGroup.styles.pointerEvents
+ ) // #5595
+
+ // the top group is appended to container
+ }, htmlGroup || container);
+
+ // Shortcut
+ htmlGroupStyle = htmlGroup.style;
+
+ // Set listeners to update the HTML div's position
+ // whenever the SVG group position is changed.
+ extend(parentGroup, {
+ // (#7287) Pass htmlGroup to use
+ // the related group
+ classSetter: (function (htmlGroup) {
+ return function (value) {
+ this.element.setAttribute(
+ 'class',
+ value
+ );
+ htmlGroup.className = value;
+ };
+ }(htmlGroup)),
+ on: function () {
+ if (parents[0].div) { // #6418
+ wrapper.on.apply(
+ { element: parents[0].div },
+ arguments
+ );
+ }
+ return parentGroup;
+ },
+ translateXSetter: translateSetter,
+ translateYSetter: translateSetter
+ });
+ addSetters(parentGroup, htmlGroupStyle);
+ });
+
+ }
+ } else {
+ htmlGroup = container;
+ }
+
+ htmlGroup.appendChild(element);
+
+ // Shared with VML:
+ wrapper.added = true;
+ if (wrapper.alignOnAdd) {
+ wrapper.htmlUpdateTransform();
+ }
+
+ return wrapper;
+ };
+ }
+ return wrapper;
+ }
+});
+
+}(Highcharts));
+(function (Highcharts) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+/* eslint max-len: ["warn", { "ignoreUrls": true}] */
+
+var H = Highcharts,
+
+ merge = H.merge,
+ pick = H.pick,
+ win = H.win;
+
+/**
+ * The Time class. Time settings are applied in general for each page using
+ * `Highcharts.setOptions`, or individually for each Chart item through the
+ * [time](https://api.highcharts.com/highcharts/time) options set.
+ *
+ * The Time object is available from
+ * [Chart.time](http://api.highcharts.com/class-reference/Highcharts.Chart#.time),
+ * which refers to `Highcharts.time` if no individual time settings are
+ * applied.
+ *
+ * @example
+ * // Apply time settings globally
+ * Highcharts.setOptions({
+ * time: {
+ * timezone: 'Europe/London'
+ * }
+ * });
+ *
+ * // Apply time settings by instance
+ * var chart = Highcharts.chart('container', {
+ * time: {
+ * timezone: 'America/New_York'
+ * },
+ * series: [{
+ * data: [1, 4, 3, 5]
+ * }]
+ * });
+ *
+ * // Use the Time object
+ * console.log(
+ * 'Current time in New York',
+ * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
+ * );
+ *
+ * @param chart {Chart}
+ * The chart instance. The `Time` instance reads its config options from
+ * [chart.options.time](/highcharts/time). If omitted, it reads from
+ * `Highcharts.defaultOptions.time`.
+ * @since 6.0.5
+ * @class
+ */
+Highcharts.Time = function (chart) {
+ this.init(chart);
+};
+
+Highcharts.Time.prototype = {
+
+ init: function (chart) {
+ this.chart = chart;
+ this.update(
+ chart ?
+ chart.options.time :
+ merge(H.defaultOptions.global, H.defaultOptions.time),
+ false
+ );
+ },
+
+ /**
+ * Time options that can apply globally or to individual charts. These
+ * settings affect how `datetime` axes are laid out, how tooltips are
+ * formatted, how series
+ * [pointIntervalUnit](#plotOptions.series.pointIntervalUnit) works and how
+ * the Highstock range selector handles time.
+ *
+ * The common use case is that all charts in the same Highcharts object
+ * share the same time settings, in which case the global settings are set
+ * using `setOptions`.
+ *
+ * ```js
+ * // Apply time settings globally
+ * Highcharts.setOptions({
+ * time: {
+ * timezone: 'Europe/London'
+ * }
+ * });
+ * // Apply time settings by instance
+ * var chart = Highcharts.chart('container', {
+ * time: {
+ * timezone: 'America/New_York'
+ * },
+ * series: [{
+ * data: [1, 4, 3, 5]
+ * }]
+ * });
+ *
+ * // Use the Time object
+ * console.log(
+ * 'Current time in New York',
+ * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
+ * );
+ * ```
+ *
+ * Since v6.0.5, the time options were moved from the `global` obect to the
+ * `time` object, and time options can be set on each individual chart.
+ *
+ * @sample {highcharts|highstock}
+ * highcharts/time/timezone/
+ * Set the timezone globally
+ * @sample {highcharts}
+ * highcharts/time/individual/
+ * Set the timezone per chart instance
+ * @sample {highstock}
+ * stock/time/individual/
+ * Set the timezone per chart instance
+ * @since 6.0.5
+ * @apioption time
+ */
+ defaultOptions: {
+ /**
+ * Whether to use UTC time for axis scaling, tickmark placement and
+ * time display in `Highcharts.dateFormat`. Advantages of using UTC
+ * is that the time displays equally regardless of the user agent's
+ * time zone settings. Local time can be used when the data is loaded
+ * in real time or when correct Daylight Saving Time transitions are
+ * required.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/time/useutc-true/ True by default
+ * @sample {highcharts} highcharts/time/useutc-false/ False
+ * @apioption time.useUTC
+ * @default true
+ */
+
+ /**
+ * A custom `Date` class for advanced date handling. For example,
+ * [JDate](https://githubcom/tahajahangir/jdate) can be hooked in to
+ * handle Jalali dates.
+ *
+ * @type {Object}
+ * @since 4.0.4
+ * @product highcharts highstock
+ * @apioption time.Date
+ */
+
+ /**
+ * A callback to return the time zone offset for a given datetime. It
+ * takes the timestamp in terms of milliseconds since January 1 1970,
+ * and returns the timezone offset in minutes. This provides a hook
+ * for drawing time based charts in specific time zones using their
+ * local DST crossover dates, with the help of external libraries.
+ *
+ * @type {Function}
+ * @see [global.timezoneOffset](#global.timezoneOffset)
+ * @sample {highcharts|highstock}
+ * highcharts/time/gettimezoneoffset/
+ * Use moment.js to draw Oslo time regardless of browser locale
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption time.getTimezoneOffset
+ */
+
+ /**
+ * Requires [moment.js](http://momentjs.com/). If the timezone option
+ * is specified, it creates a default
+ * [getTimezoneOffset](#time.getTimezoneOffset) function that looks
+ * up the specified timezone in moment.js. If moment.js is not included,
+ * this throws a Highcharts error in the console, but does not crash the
+ * chart.
+ *
+ * @type {String}
+ * @see [getTimezoneOffset](#time.getTimezoneOffset)
+ * @sample {highcharts|highstock}
+ * highcharts/time/timezone/
+ * Europe/Oslo
+ * @default undefined
+ * @since 5.0.7
+ * @product highcharts highstock
+ * @apioption time.timezone
+ */
+
+ /**
+ * The timezone offset in minutes. Positive values are west, negative
+ * values are east of UTC, as in the ECMAScript
+ * [getTimezoneOffset](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset)
+ * method. Use this to display UTC based data in a predefined time zone.
+ *
+ * @type {Number}
+ * @see [time.getTimezoneOffset](#time.getTimezoneOffset)
+ * @sample {highcharts|highstock}
+ * highcharts/time/timezoneoffset/
+ * Timezone offset
+ * @default 0
+ * @since 3.0.8
+ * @product highcharts highstock
+ * @apioption time.timezoneOffset
+ */
+
+ },
+
+ /**
+ * Update the Time object with current options. It is called internally on
+ * initiating Highcharts, after running `Highcharts.setOptions` and on
+ * `Chart.update`.
+ *
+ * @private
+ */
+ update: function (options) {
+ var useUTC = pick(options.useUTC, true),
+ getters = ['Minutes', 'Hours', 'Day', 'Date', 'Month', 'FullYear'],
+ setters = getters.concat(['Milliseconds', 'Seconds']),
+ n;
+
+ this.options = merge(true, this.options || {}, options);
+
+ // Allow using a different Date class
+ this.Date = options.Date || win.Date;
+
+ this.useUTC = useUTC;
+ this.timezoneOffset = useUTC && options.timezoneOffset;
+
+ /**
+ * Get the time zone offset based on the current timezone information as
+ * set in the global options.
+ *
+ * @function #getTimezoneOffset
+ * @memberOf Highcharts.Time
+ * @param {Number} timestamp
+ * The JavaScript timestamp to inspect.
+ * @return {Number}
+ * The timezone offset in minutes compared to UTC.
+ */
+ this.getTimezoneOffset = this.timezoneOffsetFunction();
+
+ /*
+ * The time object has options allowing for variable time zones, meaning
+ * the axis ticks or series data needs to consider this.
+ */
+ this.variableTimezone = !!(
+ options.getTimezoneOffset ||
+ options.timezone
+ );
+
+ // Dynamically set setters and getters. Sets strings pointing to the
+ // appropriate Date function to use depending on useUTC. Use `for` loop,
+ // H.each is not yet overridden in oldIE.
+ for (n = 0; n < getters.length; n++) {
+ this['get' + getters[n]] = (useUTC ? 'getUTC' : 'get') + getters[n];
+ }
+ for (n = 0; n < setters.length; n++) {
+ this['set' + setters[n]] = (useUTC ? 'setUTC' : 'set') + setters[n];
+ }
+
+ },
+
+ /**
+ * Make a time and returns milliseconds. Interprets the inputs as UTC time,
+ * local time or a specific timezone time depending on the current time
+ * settings.
+ *
+ * @param {Number} year
+ * The year
+ * @param {Number} month
+ * The month. Zero-based, so January is 0.
+ * @param {Number} date
+ * The day of the month
+ * @param {Number} hours
+ * The hour of the day, 0-23.
+ * @param {Number} minutes
+ * The minutes
+ * @param {Number} seconds
+ * The seconds
+ *
+ * @return {Number}
+ * The time in milliseconds since January 1st 1970.
+ */
+ makeTime: function (year, month, date, hours, minutes, seconds) {
+ var d;
+ if (this.useUTC) {
+ d = this.Date.UTC.apply(0, arguments);
+ d += this.getTimezoneOffset(d);
+ } else {
+ d = new this.Date(
+ year,
+ month,
+ pick(date, 1),
+ pick(hours, 0),
+ pick(minutes, 0),
+ pick(seconds, 0)
+ ).getTime();
+ }
+ return d;
+ },
+
+ /**
+ * Sets the getTimezoneOffset function. If the `timezone` option is set, a
+ * default getTimezoneOffset function with that timezone is returned. If
+ * a `getTimezoneOffset` option is defined, it is returned. If neither are
+ * specified, the function using the `timezoneOffset` option or 0 offset is
+ * returned.
+ *
+ * @private
+ * @return {Function} A getTimezoneOffset function
+ */
+ timezoneOffsetFunction: function () {
+ var time = this,
+ options = this.options,
+ moment = win.moment;
+
+ if (options.timezone) {
+ if (!moment) {
+ // getTimezoneOffset-function stays undefined because it depends
+ // on Moment.js
+ H.error(25);
+
+ } else {
+ return function (timestamp) {
+ return -moment.tz(
+ timestamp,
+ options.timezone
+ ).utcOffset() * 60000;
+ };
+ }
+ }
+
+ // If not timezone is set, look for the getTimezoneOffset callback
+ if (this.useUTC && options.getTimezoneOffset) {
+ return function (timestamp) {
+ return options.getTimezoneOffset(timestamp) * 60000;
+ };
+ }
+
+ // Last, use the `timezoneOffset` option if set
+ return function () {
+ return (time.timezoneOffset || 0) * 60000;
+ };
+ },
+
+ /**
+ * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970)
+ * into a human readable date string. The format is a subset of the formats
+ * for PHP's [strftime](http://www.php.net/manual/en/function.strftime.php)
+ * function. Additional formats can be given in the
+ * {@link Highcharts.dateFormats} hook.
+ *
+ * @param {String} format
+ * The desired format where various time
+ * representations are prefixed with %.
+ * @param {Number} timestamp
+ * The JavaScript timestamp.
+ * @param {Boolean} [capitalize=false]
+ * Upper case first letter in the return.
+ * @returns {String} The formatted date.
+ */
+ dateFormat: function (format, timestamp, capitalize) {
+ if (!H.defined(timestamp) || isNaN(timestamp)) {
+ return H.defaultOptions.lang.invalidDate || '';
+ }
+ format = H.pick(format, '%Y-%m-%d %H:%M:%S');
+
+ var time = this,
+ D = this.Date,
+ date = new D(timestamp - this.getTimezoneOffset(timestamp)),
+ // get the basic time values
+ hours = date[this.getHours](),
+ day = date[this.getDay](),
+ dayOfMonth = date[this.getDate](),
+ month = date[this.getMonth](),
+ fullYear = date[this.getFullYear](),
+ lang = H.defaultOptions.lang,
+ langWeekdays = lang.weekdays,
+ shortWeekdays = lang.shortWeekdays,
+ pad = H.pad,
+
+ // List all format keys. Custom formats can be added from the
+ // outside.
+ replacements = H.extend(
+ {
+
+ // Day
+ // Short weekday, like 'Mon'
+ 'a': shortWeekdays ?
+ shortWeekdays[day] :
+ langWeekdays[day].substr(0, 3),
+ // Long weekday, like 'Monday'
+ 'A': langWeekdays[day],
+ // Two digit day of the month, 01 to 31
+ 'd': pad(dayOfMonth),
+ // Day of the month, 1 through 31
+ 'e': pad(dayOfMonth, 2, ' '),
+ 'w': day,
+
+ // Week (none implemented)
+ // 'W': weekNumber(),
+
+ // Month
+ // Short month, like 'Jan'
+ 'b': lang.shortMonths[month],
+ // Long month, like 'January'
+ 'B': lang.months[month],
+ // Two digit month number, 01 through 12
+ 'm': pad(month + 1),
+
+ // Year
+ // Two digits year, like 09 for 2009
+ 'y': fullYear.toString().substr(2, 2),
+ // Four digits year, like 2009
+ 'Y': fullYear,
+
+ // Time
+ // Two digits hours in 24h format, 00 through 23
+ 'H': pad(hours),
+ // Hours in 24h format, 0 through 23
+ 'k': hours,
+ // Two digits hours in 12h format, 00 through 11
+ 'I': pad((hours % 12) || 12),
+ // Hours in 12h format, 1 through 12
+ 'l': (hours % 12) || 12,
+ // Two digits minutes, 00 through 59
+ 'M': pad(date[this.getMinutes]()),
+ // Upper case AM or PM
+ 'p': hours < 12 ? 'AM' : 'PM',
+ // Lower case AM or PM
+ 'P': hours < 12 ? 'am' : 'pm',
+ // Two digits seconds, 00 through 59
+ 'S': pad(date.getSeconds()),
+ // Milliseconds (naming from Ruby)
+ 'L': pad(Math.round(timestamp % 1000), 3)
+ },
+
+ /**
+ * A hook for defining additional date format specifiers. New
+ * specifiers are defined as key-value pairs by using the
+ * specifier as key, and a function which takes the timestamp as
+ * value. This function returns the formatted portion of the
+ * date.
+ *
+ * @type {Object}
+ * @name dateFormats
+ * @memberOf Highcharts
+ * @sample highcharts/global/dateformats/
+ * Adding support for week
+ * number
+ */
+ H.dateFormats
+ );
+
+
+ // Do the replaces
+ H.objectEach(replacements, function (val, key) {
+ // Regex would do it in one line, but this is faster
+ while (format.indexOf('%' + key) !== -1) {
+ format = format.replace(
+ '%' + key,
+ typeof val === 'function' ? val.call(time, timestamp) : val
+ );
+ }
+
+ });
+
+ // Optionally capitalize the string and return
+ return capitalize ?
+ format.substr(0, 1).toUpperCase() + format.substr(1) :
+ format;
+ }
+
+}; // end of Time
+
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+var color = H.color,
+ isTouchDevice = H.isTouchDevice,
+ merge = H.merge,
+ svg = H.svg;
+
+/* ****************************************************************************
+ * Handle the options *
+ *****************************************************************************/
+/**
+ * @optionparent
+ */
+H.defaultOptions = {
+
+
+ /**
+ * An array containing the default colors for the chart's series. When
+ * all colors are used, new colors are pulled from the start again.
+ *
+ * Default colors can also be set on a series or series.type basis,
+ * see [column.colors](#plotOptions.column.colors), [pie.colors](#plotOptions.
+ * pie.colors).
+ *
+ * In styled mode, the colors option doesn't exist. Instead, colors
+ * are defined in CSS and applied either through series or point class
+ * names, or through the [chart.colorCount](#chart.colorCount) option.
+ *
+ *
+ * ### Legacy
+ *
+ * In Highcharts 3.x, the default colors were:
+ *
+ * colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce',
+ * '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']
+ *
+ * In Highcharts 2.x, the default colors were:
+ *
+ * colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE',
+ * '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92']
+ *
+ * @type {Array}
+ * @sample {highcharts} highcharts/chart/colors/ Assign a global color theme
+ * @default ["#7cb5ec", "#434348", "#90ed7d", "#f7a35c", "#8085e9",
+ * "#f15c80", "#e4d354", "#2b908f", "#f45b5b", "#91e8e1"]
+ */
+ colors: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' '),
+
+
+
+ /**
+ * Styled mode only. Configuration object for adding SVG definitions for
+ * reusable elements. See [gradients, shadows and patterns](http://www.
+ * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
+ * patterns) for more information and code examples.
+ *
+ * @type {Object}
+ * @since 5.0.0
+ * @apioption defs
+ */
+
+ /**
+ * @ignore
+ */
+ symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
+ lang: {
+
+ /**
+ * The loading text that appears when the chart is set into the loading
+ * state following a call to `chart.showLoading`.
+ *
+ * @type {String}
+ * @default Loading...
+ */
+ loading: 'Loading...',
+
+ /**
+ * An array containing the months names. Corresponds to the `%B` format
+ * in `Highcharts.dateFormat()`.
+ *
+ * @type {Array}
+ * @default [ "January" , "February" , "March" , "April" , "May" ,
+ * "June" , "July" , "August" , "September" , "October" ,
+ * "November" , "December"]
+ */
+ months: [
+ 'January', 'February', 'March', 'April', 'May', 'June', 'July',
+ 'August', 'September', 'October', 'November', 'December'
+ ],
+
+ /**
+ * An array containing the months names in abbreviated form. Corresponds
+ * to the `%b` format in `Highcharts.dateFormat()`.
+ *
+ * @type {Array}
+ * @default [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" ,
+ * "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec"]
+ */
+ shortMonths: [
+ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
+ 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
+ ],
+
+ /**
+ * An array containing the weekday names.
+ *
+ * @type {Array}
+ * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
+ * "Friday", "Saturday"]
+ */
+ weekdays: [
+ 'Sunday', 'Monday', 'Tuesday', 'Wednesday',
+ 'Thursday', 'Friday', 'Saturday'
+ ],
+
+ /**
+ * Short week days, starting Sunday. If not specified, Highcharts uses
+ * the first three letters of the `lang.weekdays` option.
+ *
+ * @type {Array}
+ * @sample highcharts/lang/shortweekdays/
+ * Finnish two-letter abbreviations
+ * @since 4.2.4
+ * @apioption lang.shortWeekdays
+ */
+
+ /**
+ * What to show in a date field for invalid dates. Defaults to an empty
+ * string.
+ *
+ * @type {String}
+ * @since 4.1.8
+ * @product highcharts highstock
+ * @apioption lang.invalidDate
+ */
+
+ /**
+ * The default decimal point used in the `Highcharts.numberFormat`
+ * method unless otherwise specified in the function arguments.
+ *
+ * @type {String}
+ * @default .
+ * @since 1.2.2
+ */
+ decimalPoint: '.',
+
+ /**
+ * [Metric prefixes](http://en.wikipedia.org/wiki/Metric_prefix) used
+ * to shorten high numbers in axis labels. Replacing any of the positions
+ * with `null` causes the full number to be written. Setting `numericSymbols`
+ * to `null` disables shortening altogether.
+ *
+ * @type {Array}
+ * @sample {highcharts} highcharts/lang/numericsymbols/
+ * Replacing the symbols with text
+ * @sample {highstock} highcharts/lang/numericsymbols/
+ * Replacing the symbols with text
+ * @default [ "k" , "M" , "G" , "T" , "P" , "E"]
+ * @since 2.3.0
+ */
+ numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'],
+
+ /**
+ * The magnitude of [numericSymbols](#lang.numericSymbol) replacements.
+ * Use 10000 for Japanese, Korean and various Chinese locales, which
+ * use symbols for 10^4, 10^8 and 10^12.
+ *
+ * @type {Number}
+ * @sample highcharts/lang/numericsymbolmagnitude/
+ * 10000 magnitude for Japanese
+ * @default 1000
+ * @since 5.0.3
+ * @apioption lang.numericSymbolMagnitude
+ */
+
+ /**
+ * The text for the label appearing when a chart is zoomed.
+ *
+ * @type {String}
+ * @default Reset zoom
+ * @since 1.2.4
+ */
+ resetZoom: 'Reset zoom',
+
+ /**
+ * The tooltip title for the label appearing when a chart is zoomed.
+ *
+ * @type {String}
+ * @default Reset zoom level 1:1
+ * @since 1.2.4
+ */
+ resetZoomTitle: 'Reset zoom level 1:1',
+
+ /**
+ * The default thousands separator used in the `Highcharts.numberFormat`
+ * method unless otherwise specified in the function arguments. Since
+ * Highcharts 4.1 it defaults to a single space character, which is
+ * compatible with ISO and works across Anglo-American and continental
+ * European languages.
+ *
+ * The default is a single space.
+ *
+ * @type {String}
+ * @default
+ * @since 1.2.2
+ */
+ thousandsSep: ' '
+ },
+
+ /**
+ * Global options that don't apply to each chart. These options, like
+ * the `lang` options, must be set using the `Highcharts.setOptions`
+ * method.
+ *
+ * Highcharts.setOptions({
+ * global: {
+ * useUTC: false
+ * }
+ * });
+ *
+ */
+ global: {
+
+ /**
+ * _Canvg rendering for Android 2.x is removed as of Highcharts 5.0\.
+ * Use the [libURL](#exporting.libURL) option to configure exporting._
+ *
+ * The URL to the additional file to lazy load for Android 2.x devices.
+ * These devices don't support SVG, so we download a helper file that
+ * contains [canvg](http://code.google.com/p/canvg/), its dependency
+ * rbcolor, and our own CanVG Renderer class. To avoid hotlinking to
+ * our site, you can install canvas-tools.js on your own server and
+ * change this option accordingly.
+ *
+ * @type {String}
+ * @deprecated
+ * @default http://code.highcharts.com/{version}/modules/canvas-tools.js
+ * @product highcharts highmaps
+ * @apioption global.canvasToolsURL
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.useUTC](#time.useUTC) that supports individual time settings
+ * per chart.
+ *
+ * @deprecated
+ * @apioption global.useUTC
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.Date](#time.Date) that supports individual time settings
+ * per chart.
+ *
+ * @deprecated
+ * @product highcharts highstock
+ * @apioption global.Date
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.getTimezoneOffset](#time.getTimezoneOffset) that supports
+ * individual time settings per chart.
+ *
+ * @deprecated
+ * @product highcharts highstock
+ * @apioption global.getTimezoneOffset
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.timezone](#time.timezone) that supports individual time
+ * settings per chart.
+ *
+ * @deprecated
+ * @product highcharts highstock
+ * @apioption global.timezone
+ */
+
+ /**
+ * This option is deprecated since v6.0.5. Instead, use
+ * [time.timezoneOffset](#time.timezoneOffset) that supports individual
+ * time settings per chart.
+ *
+ * @deprecated
+ * @product highcharts highstock
+ * @apioption global.timezoneOffset
+ */
+ },
+
+
+ time: H.Time.prototype.defaultOptions,
+ chart: {
+
+ /**
+ * When using multiple axis, the ticks of two or more opposite axes
+ * will automatically be aligned by adding ticks to the axis or axes
+ * with the least ticks, as if `tickAmount` were specified.
+ *
+ * This can be prevented by setting `alignTicks` to false. If the grid
+ * lines look messy, it's a good idea to hide them for the secondary
+ * axis by setting `gridLineWidth` to 0.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/chart/alignticks-true/ True by default
+ * @sample {highcharts} highcharts/chart/alignticks-false/ False
+ * @sample {highstock} stock/chart/alignticks-true/
+ * True by default
+ * @sample {highstock} stock/chart/alignticks-false/
+ * False
+ * @default true
+ * @product highcharts highstock
+ * @apioption chart.alignTicks
+ */
+
+
+ /**
+ * Set the overall animation for all chart updating. Animation can be
+ * disabled throughout the chart by setting it to false here. It can
+ * be overridden for each individual API method as a function parameter.
+ * The only animation not affected by this option is the initial series
+ * animation, see [plotOptions.series.animation](#plotOptions.series.
+ * animation).
+ *
+ * The animation can either be set as a boolean or a configuration
+ * object. If `true`, it will use the 'swing' jQuery easing and a
+ * duration of 500 ms. If used as a configuration object, the following
+ * properties are supported:
+ *
+ *
+ *
+ * duration
+ *
+ * The duration of the animation in milliseconds.
+ *
+ * easing
+ *
+ * A string reference to an easing function set on the `Math` object.
+ * See [the easing demo](http://jsfiddle.net/gh/get/library/pure/
+ * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
+ * series-animation-easing/).
+ *
+ *
+ *
+ * @type {Boolean|Object}
+ * @sample {highcharts} highcharts/chart/animation-none/
+ * Updating with no animation
+ * @sample {highcharts} highcharts/chart/animation-duration/
+ * With a longer duration
+ * @sample {highcharts} highcharts/chart/animation-easing/
+ * With a jQuery UI easing
+ * @sample {highmaps} maps/chart/animation-none/
+ * Updating with no animation
+ * @sample {highmaps} maps/chart/animation-duration/
+ * With a longer duration
+ * @default true
+ * @apioption chart.animation
+ */
+
+ /**
+ * A CSS class name to apply to the charts container `div`, allowing
+ * unique CSS styling for each chart.
+ *
+ * @type {String}
+ * @apioption chart.className
+ */
+
+ /**
+ * Event listeners for the chart.
+ *
+ * @apioption chart.events
+ */
+
+ /**
+ * Fires when a series is added to the chart after load time, using
+ * the `addSeries` method. One parameter, `event`, is passed to the
+ * function, containing common event information.
+ * Through `event.options` you can access the series options that was
+ * passed to the `addSeries` method. Returning false prevents the series
+ * from being added.
+ *
+ * @type {Function}
+ * @context Chart
+ * @sample {highcharts} highcharts/chart/events-addseries/ Alert on add series
+ * @sample {highstock} stock/chart/events-addseries/ Alert on add series
+ * @since 1.2.0
+ * @apioption chart.events.addSeries
+ */
+
+ /**
+ * Fires when clicking on the plot background. One parameter, `event`,
+ * is passed to the function, containing common event information.
+ *
+ * Information on the clicked spot can be found through `event.xAxis`
+ * and `event.yAxis`, which are arrays containing the axes of each dimension
+ * and each axis' value at the clicked spot. The primary axes are `event.
+ * xAxis[0]` and `event.yAxis[0]`. Remember the unit of a datetime axis
+ * is milliseconds since 1970-01-01 00:00:00.
+ *
+ * click: function(e) {
+ * console.log(
+ * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value),
+ * e.yAxis[0].value
+ * )
+ * }
+ *
+ * @type {Function}
+ * @context Chart
+ * @sample {highcharts} highcharts/chart/events-click/
+ * Alert coordinates on click
+ * @sample {highcharts} highcharts/chart/events-container/
+ * Alternatively, attach event to container
+ * @sample {highstock} stock/chart/events-click/
+ * Alert coordinates on click
+ * @sample {highstock} highcharts/chart/events-container/
+ * Alternatively, attach event to container
+ * @sample {highmaps} maps/chart/events-click/
+ * Record coordinates on click
+ * @sample {highmaps} highcharts/chart/events-container/
+ * Alternatively, attach event to container
+ * @since 1.2.0
+ * @apioption chart.events.click
+ */
+
+
+ /**
+ * Fires when the chart is finished loading. Since v4.2.2, it also waits
+ * for images to be loaded, for example from point markers. One parameter,
+ * `event`, is passed to the function, containing common event information.
+ *
+ * There is also a second parameter to the chart constructor where a
+ * callback function can be passed to be executed on chart.load.
+ *
+ * @type {Function}
+ * @context Chart
+ * @sample {highcharts} highcharts/chart/events-load/
+ * Alert on chart load
+ * @sample {highstock} stock/chart/events-load/
+ * Alert on chart load
+ * @sample {highmaps} maps/chart/events-load/
+ * Add series on chart load
+ * @apioption chart.events.load
+ */
+
+ /**
+ * Fires when the chart is redrawn, either after a call to chart.redraw()
+ * or after an axis, series or point is modified with the `redraw` option
+ * set to true. One parameter, `event`, is passed to the function, containing common event information.
+ *
+ * @type {Function}
+ * @context Chart
+ * @sample {highcharts} highcharts/chart/events-redraw/
+ * Alert on chart redraw
+ * @sample {highstock} stock/chart/events-redraw/
+ * Alert on chart redraw when adding a series or moving the
+ * zoomed range
+ * @sample {highmaps} maps/chart/events-redraw/
+ * Set subtitle on chart redraw
+ * @since 1.2.0
+ * @apioption chart.events.redraw
+ */
+
+ /**
+ * Fires after initial load of the chart (directly after the `load`
+ * event), and after each redraw (directly after the `redraw` event).
+ *
+ * @type {Function}
+ * @context Chart
+ * @since 5.0.7
+ * @apioption chart.events.render
+ */
+
+ /**
+ * Fires when an area of the chart has been selected. Selection is enabled
+ * by setting the chart's zoomType. One parameter, `event`, is passed
+ * to the function, containing common event information. The default action for the selection event is to
+ * zoom the chart to the selected area. It can be prevented by calling
+ * `event.preventDefault()`.
+ *
+ * Information on the selected area can be found through `event.xAxis`
+ * and `event.yAxis`, which are arrays containing the axes of each dimension
+ * and each axis' min and max values. The primary axes are `event.xAxis[0]`
+ * and `event.yAxis[0]`. Remember the unit of a datetime axis is milliseconds
+ * since 1970-01-01 00:00:00.
+ *
+ * selection: function(event) {
+ * // log the min and max of the primary, datetime x-axis
+ * console.log(
+ * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].min),
+ * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].max)
+ * );
+ * // log the min and max of the y axis
+ * console.log(event.yAxis[0].min, event.yAxis[0].max);
+ * }
+ *
+ * @type {Function}
+ * @sample {highcharts} highcharts/chart/events-selection/
+ * Report on selection and reset
+ * @sample {highcharts} highcharts/chart/events-selection-points/
+ * Select a range of points through a drag selection
+ * @sample {highstock} stock/chart/events-selection/
+ * Report on selection and reset
+ * @sample {highstock} highcharts/chart/events-selection-points/
+ * Select a range of points through a drag selection (Highcharts)
+ * @apioption chart.events.selection
+ */
+
+ /**
+ * The margin between the outer edge of the chart and the plot area.
+ * The numbers in the array designate top, right, bottom and left
+ * respectively. Use the options `marginTop`, `marginRight`,
+ * `marginBottom` and `marginLeft` for shorthand setting of one option.
+ *
+ * By default there is no margin. The actual space is dynamically calculated
+ * from the offset of axis labels, axis title, title, subtitle and legend
+ * in addition to the `spacingTop`, `spacingRight`, `spacingBottom`
+ * and `spacingLeft` options.
+ *
+ * @type {Array}
+ * @sample {highcharts} highcharts/chart/margins-zero/
+ * Zero margins
+ * @sample {highstock} stock/chart/margin-zero/
+ * Zero margins
+ *
+ * @defaults {all} null
+ * @apioption chart.margin
+ */
+
+ /**
+ * The margin between the bottom outer edge of the chart and the plot
+ * area. Use this to set a fixed pixel value for the margin as opposed
+ * to the default dynamic margin. See also `spacingBottom`.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/marginbottom/
+ * 100px bottom margin
+ * @sample {highstock} stock/chart/marginbottom/
+ * 100px bottom margin
+ * @sample {highmaps} maps/chart/margin/
+ * 100px margins
+ * @since 2.0
+ * @apioption chart.marginBottom
+ */
+
+ /**
+ * The margin between the left outer edge of the chart and the plot
+ * area. Use this to set a fixed pixel value for the margin as opposed
+ * to the default dynamic margin. See also `spacingLeft`.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/marginleft/
+ * 150px left margin
+ * @sample {highstock} stock/chart/marginleft/
+ * 150px left margin
+ * @sample {highmaps} maps/chart/margin/
+ * 100px margins
+ * @default null
+ * @since 2.0
+ * @apioption chart.marginLeft
+ */
+
+ /**
+ * The margin between the right outer edge of the chart and the plot
+ * area. Use this to set a fixed pixel value for the margin as opposed
+ * to the default dynamic margin. See also `spacingRight`.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/marginright/
+ * 100px right margin
+ * @sample {highstock} stock/chart/marginright/
+ * 100px right margin
+ * @sample {highmaps} maps/chart/margin/
+ * 100px margins
+ * @default null
+ * @since 2.0
+ * @apioption chart.marginRight
+ */
+
+ /**
+ * The margin between the top outer edge of the chart and the plot area.
+ * Use this to set a fixed pixel value for the margin as opposed to
+ * the default dynamic margin. See also `spacingTop`.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/margintop/ 100px top margin
+ * @sample {highstock} stock/chart/margintop/
+ * 100px top margin
+ * @sample {highmaps} maps/chart/margin/
+ * 100px margins
+ * @default null
+ * @since 2.0
+ * @apioption chart.marginTop
+ */
+
+ /**
+ * Allows setting a key to switch between zooming and panning. Can be
+ * one of `alt`, `ctrl`, `meta` (the command key on Mac and Windows
+ * key on Windows) or `shift`. The keys are mapped directly to the key
+ * properties of the click event argument (`event.altKey`, `event.ctrlKey`,
+ * `event.metaKey` and `event.shiftKey`).
+ *
+ * @validvalue [null, "alt", "ctrl", "meta", "shift"]
+ * @type {String}
+ * @since 4.0.3
+ * @product highcharts
+ * @apioption chart.panKey
+ */
+
+ /**
+ * Allow panning in a chart. Best used with [panKey](#chart.panKey)
+ * to combine zooming and panning.
+ *
+ * On touch devices, when the [tooltip.followTouchMove](#tooltip.followTouchMove)
+ * option is `true` (default), panning requires two fingers. To allow
+ * panning with one finger, set `followTouchMove` to `false`.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/chart/pankey/ Zooming and panning
+ * @default {highcharts} false
+ * @default {highstock} true
+ * @since 4.0.3
+ * @product highcharts highstock
+ * @apioption chart.panning
+ */
+
+
+ /**
+ * Equivalent to [zoomType](#chart.zoomType), but for multitouch gestures
+ * only. By default, the `pinchType` is the same as the `zoomType` setting.
+ * However, pinching can be enabled separately in some cases, for example
+ * in stock charts where a mouse drag pans the chart, while pinching
+ * is enabled. When [tooltip.followTouchMove](#tooltip.followTouchMove)
+ * is true, pinchType only applies to two-finger touches.
+ *
+ * @validvalue ["x", "y", "xy"]
+ * @type {String}
+ * @default {highcharts} null
+ * @default {highstock} x
+ * @since 3.0
+ * @product highcharts highstock
+ * @apioption chart.pinchType
+ */
+
+ /**
+ * The corner radius of the outer chart border.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/borderradius/ 20px radius
+ * @sample {highstock} stock/chart/border/ 10px radius
+ * @sample {highmaps} maps/chart/border/ Border options
+ * @default 0
+ */
+ borderRadius: 0,
+
+
+ /**
+ * Alias of `type`.
+ *
+ * @validvalue ["line", "spline", "column", "area", "areaspline", "pie"]
+ * @type {String}
+ * @deprecated
+ * @sample {highcharts} highcharts/chart/defaultseriestype/ Bar
+ * @default line
+ * @product highcharts
+ */
+ defaultSeriesType: 'line',
+
+ /**
+ * If true, the axes will scale to the remaining visible series once
+ * one series is hidden. If false, hiding and showing a series will
+ * not affect the axes or the other series. For stacks, once one series
+ * within the stack is hidden, the rest of the stack will close in
+ * around it even if the axis is not affected.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/chart/ignorehiddenseries-true/
+ * True by default
+ * @sample {highcharts} highcharts/chart/ignorehiddenseries-false/
+ * False
+ * @sample {highcharts} highcharts/chart/ignorehiddenseries-true-stacked/
+ * True with stack
+ * @sample {highstock} stock/chart/ignorehiddenseries-true/
+ * True by default
+ * @sample {highstock} stock/chart/ignorehiddenseries-false/
+ * False
+ * @default true
+ * @since 1.2.0
+ * @product highcharts highstock
+ */
+ ignoreHiddenSeries: true,
+
+
+ /**
+ * Whether to invert the axes so that the x axis is vertical and y axis
+ * is horizontal. When `true`, the x axis is [reversed](#xAxis.reversed)
+ * by default.
+ *
+ * @productdesc {highcharts}
+ * If a bar series is present in the chart, it will be inverted
+ * automatically. Inverting the chart doesn't have an effect if there
+ * are no cartesian series in the chart, or if the chart is
+ * [polar](#chart.polar).
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/chart/inverted/
+ * Inverted line
+ * @sample {highstock} stock/navigator/inverted/
+ * Inverted stock chart
+ * @default false
+ * @product highcharts highstock
+ * @apioption chart.inverted
+ */
+
+ /**
+ * The distance between the outer edge of the chart and the content,
+ * like title or legend, or axis title and labels if present. The
+ * numbers in the array designate top, right, bottom and left respectively.
+ * Use the options spacingTop, spacingRight, spacingBottom and spacingLeft
+ * options for shorthand setting of one option.
+ *
+ * @type {Array}
+ * @see [chart.margin](#chart.margin)
+ * @default [10, 10, 15, 10]
+ * @since 3.0.6
+ */
+ spacing: [10, 10, 15, 10],
+
+ /**
+ * The button that appears after a selection zoom, allowing the user
+ * to reset zoom.
+ *
+ */
+ resetZoomButton: {
+
+ /**
+ * A collection of attributes for the button. The object takes SVG
+ * attributes like `fill`, `stroke`, `stroke-width` or `r`, the border
+ * radius. The theme also supports `style`, a collection of CSS properties
+ * for the text. Equivalent attributes for the hover state are given
+ * in `theme.states.hover`.
+ *
+ * @type {Object}
+ * @sample {highcharts} highcharts/chart/resetzoombutton-theme/
+ * Theming the button
+ * @sample {highstock} highcharts/chart/resetzoombutton-theme/
+ * Theming the button
+ * @since 2.2
+ */
+ theme: {
+
+ /**
+ * The Z index for the reset zoom button. The default value
+ * places it below the tooltip that has Z index 7.
+ */
+ zIndex: 6
+ },
+
+ /**
+ * The position of the button.
+ *
+ * @type {Object}
+ * @sample {highcharts} highcharts/chart/resetzoombutton-position/
+ * Above the plot area
+ * @sample {highstock} highcharts/chart/resetzoombutton-position/
+ * Above the plot area
+ * @sample {highmaps} highcharts/chart/resetzoombutton-position/
+ * Above the plot area
+ * @since 2.2
+ */
+ position: {
+
+ /**
+ * The horizontal alignment of the button.
+ *
+ * @type {String}
+ */
+ align: 'right',
+
+ /**
+ * The horizontal offset of the button.
+ *
+ * @type {Number}
+ */
+ x: -10,
+
+ /**
+ * The vertical alignment of the button.
+ *
+ * @validvalue ["top", "middle", "bottom"]
+ * @type {String}
+ * @default top
+ * @apioption chart.resetZoomButton.position.verticalAlign
+ */
+
+ /**
+ * The vertical offset of the button.
+ *
+ * @type {Number}
+ */
+ y: 10
+ }
+
+ /**
+ * What frame the button should be placed related to. Can be either
+ * `plot` or `chart`
+ *
+ * @validvalue ["plot", "chart"]
+ * @type {String}
+ * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/
+ * Relative to the chart
+ * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/
+ * Relative to the chart
+ * @default plot
+ * @since 2.2
+ * @apioption chart.resetZoomButton.relativeTo
+ */
+ },
+
+ /**
+ * An explicit width for the chart. By default (when `null`) the width
+ * is calculated from the offset width of the containing element.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/width/ 800px wide
+ * @sample {highstock} stock/chart/width/ 800px wide
+ * @sample {highmaps} maps/chart/size/ Chart with explicit size
+ * @default null
+ */
+ width: null,
+
+ /**
+ * An explicit height for the chart. If a _number_, the height is
+ * given in pixels. If given a _percentage string_ (for example `'56%'`),
+ * the height is given as the percentage of the actual chart width.
+ * This allows for preserving the aspect ratio across responsive
+ * sizes.
+ *
+ * By default (when `null`) the height is calculated from the offset
+ * height of the containing element, or 400 pixels if the containing
+ * element's height is 0.
+ *
+ * @type {Number|String}
+ * @sample {highcharts} highcharts/chart/height/
+ * 500px height
+ * @sample {highstock} stock/chart/height/
+ * 300px height
+ * @sample {highmaps} maps/chart/size/
+ * Chart with explicit size
+ * @sample highcharts/chart/height-percent/
+ * Highcharts with percentage height
+ * @default null
+ */
+ height: null,
+
+
+
+ /**
+ * The color of the outer chart border.
+ *
+ * @type {Color}
+ * @see In styled mode, the stroke is set with the `.highcharts-background`
+ * class.
+ * @sample {highcharts} highcharts/chart/bordercolor/ Brown border
+ * @sample {highstock} stock/chart/border/ Brown border
+ * @sample {highmaps} maps/chart/border/ Border options
+ * @default #335cad
+ */
+ borderColor: '#335cad',
+
+ /**
+ * The pixel width of the outer chart border.
+ *
+ * @type {Number}
+ * @see In styled mode, the stroke is set with the `.highcharts-background`
+ * class.
+ * @sample {highcharts} highcharts/chart/borderwidth/ 5px border
+ * @sample {highstock} stock/chart/border/
+ * 2px border
+ * @sample {highmaps} maps/chart/border/
+ * Border options
+ * @default 0
+ * @apioption chart.borderWidth
+ */
+
+ /**
+ * The background color or gradient for the outer chart area.
+ *
+ * @type {Color}
+ * @see In styled mode, the background is set with the `.highcharts-background` class.
+ * @sample {highcharts} highcharts/chart/backgroundcolor-color/ Color
+ * @sample {highcharts} highcharts/chart/backgroundcolor-gradient/ Gradient
+ * @sample {highstock} stock/chart/backgroundcolor-color/
+ * Color
+ * @sample {highstock} stock/chart/backgroundcolor-gradient/
+ * Gradient
+ * @sample {highmaps} maps/chart/backgroundcolor-color/
+ * Color
+ * @sample {highmaps} maps/chart/backgroundcolor-gradient/
+ * Gradient
+ * @default #FFFFFF
+ */
+ backgroundColor: '#ffffff',
+
+ /**
+ * The background color or gradient for the plot area.
+ *
+ * @type {Color}
+ * @see In styled mode, the plot background is set with the `.highcharts-plot-background` class.
+ * @sample {highcharts} highcharts/chart/plotbackgroundcolor-color/
+ * Color
+ * @sample {highcharts} highcharts/chart/plotbackgroundcolor-gradient/
+ * Gradient
+ * @sample {highstock} stock/chart/plotbackgroundcolor-color/
+ * Color
+ * @sample {highstock} stock/chart/plotbackgroundcolor-gradient/
+ * Gradient
+ * @sample {highmaps} maps/chart/plotbackgroundcolor-color/
+ * Color
+ * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
+ * Gradient
+ * @default null
+ * @apioption chart.plotBackgroundColor
+ */
+
+
+ /**
+ * The URL for an image to use as the plot background. To set an image
+ * as the background for the entire chart, set a CSS background image
+ * to the container element. Note that for the image to be applied to
+ * exported charts, its URL needs to be accessible by the export server.
+ *
+ * @type {String}
+ * @see In styled mode, a plot background image can be set with the
+ * `.highcharts-plot-background` class and a [custom pattern](http://www.
+ * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
+ * patterns).
+ * @sample {highcharts} highcharts/chart/plotbackgroundimage/ Skies
+ * @sample {highstock} stock/chart/plotbackgroundimage/ Skies
+ * @default null
+ * @apioption chart.plotBackgroundImage
+ */
+
+ /**
+ * The color of the inner chart or plot area border.
+ *
+ * @type {Color}
+ * @see In styled mode, a plot border stroke can be set with the `.
+ * highcharts-plot-border` class.
+ * @sample {highcharts} highcharts/chart/plotbordercolor/ Blue border
+ * @sample {highstock} stock/chart/plotborder/ Blue border
+ * @sample {highmaps} maps/chart/plotborder/ Plot border options
+ * @default #cccccc
+ */
+ plotBorderColor: '#cccccc'
+
+
+ /**
+ * The pixel width of the plot area border.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/plotborderwidth/ 1px border
+ * @sample {highstock} stock/chart/plotborder/
+ * 2px border
+ * @sample {highmaps} maps/chart/plotborder/
+ * Plot border options
+ * @default 0
+ * @apioption chart.plotBorderWidth
+ */
+
+ /**
+ * Whether to apply a drop shadow to the plot area. Requires that
+ * plotBackgroundColor be set. The shadow can be an object configuration
+ * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
+ *
+ * @type {Boolean|Object}
+ * @sample {highcharts} highcharts/chart/plotshadow/ Plot shadow
+ * @sample {highstock} stock/chart/plotshadow/
+ * Plot shadow
+ * @sample {highmaps} maps/chart/plotborder/
+ * Plot border options
+ * @default false
+ * @apioption chart.plotShadow
+ */
+
+ /**
+ * When true, cartesian charts like line, spline, area and column are
+ * transformed into the polar coordinate system. Requires `highcharts-
+ * more.js`.
+ *
+ * @type {Boolean}
+ * @default false
+ * @since 2.3.0
+ * @product highcharts
+ * @apioption chart.polar
+ */
+
+ /**
+ * Whether to reflow the chart to fit the width of the container div
+ * on resizing the window.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/chart/reflow-true/ True by default
+ * @sample {highcharts} highcharts/chart/reflow-false/ False
+ * @sample {highstock} stock/chart/reflow-true/
+ * True by default
+ * @sample {highstock} stock/chart/reflow-false/
+ * False
+ * @sample {highmaps} maps/chart/reflow-true/
+ * True by default
+ * @sample {highmaps} maps/chart/reflow-false/
+ * False
+ * @default true
+ * @since 2.1
+ * @apioption chart.reflow
+ */
+
+
+
+
+ /**
+ * The HTML element where the chart will be rendered. If it is a string,
+ * the element by that id is used. The HTML element can also be passed
+ * by direct reference, or as the first argument of the chart constructor,
+ * in which case the option is not needed.
+ *
+ * @type {String|Object}
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * String
+ * @sample {highcharts} highcharts/chart/renderto-object/
+ * Object reference
+ * @sample {highcharts} highcharts/chart/renderto-jquery/
+ * Object reference through jQuery
+ * @sample {highstock} stock/chart/renderto-string/
+ * String
+ * @sample {highstock} stock/chart/renderto-object/
+ * Object reference
+ * @sample {highstock} stock/chart/renderto-jquery/
+ * Object reference through jQuery
+ * @apioption chart.renderTo
+ */
+
+ /**
+ * The background color of the marker square when selecting (zooming
+ * in on) an area of the chart.
+ *
+ * @type {Color}
+ * @see In styled mode, the selection marker fill is set with the
+ * `.highcharts-selection-marker` class.
+ * @default rgba(51,92,173,0.25)
+ * @since 2.1.7
+ * @apioption chart.selectionMarkerFill
+ */
+
+ /**
+ * Whether to apply a drop shadow to the outer chart area. Requires
+ * that backgroundColor be set. The shadow can be an object configuration
+ * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
+ *
+ * @type {Boolean|Object}
+ * @sample {highcharts} highcharts/chart/shadow/ Shadow
+ * @sample {highstock} stock/chart/shadow/
+ * Shadow
+ * @sample {highmaps} maps/chart/border/
+ * Chart border and shadow
+ * @default false
+ * @apioption chart.shadow
+ */
+
+ /**
+ * Whether to show the axes initially. This only applies to empty charts
+ * where series are added dynamically, as axes are automatically added
+ * to cartesian series.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/chart/showaxes-false/ False by default
+ * @sample {highcharts} highcharts/chart/showaxes-true/ True
+ * @since 1.2.5
+ * @product highcharts
+ * @apioption chart.showAxes
+ */
+
+ /**
+ * The space between the bottom edge of the chart and the content (plot
+ * area, axis title and labels, title, subtitle or legend in top position).
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/spacingbottom/
+ * Spacing bottom set to 100
+ * @sample {highstock} stock/chart/spacingbottom/
+ * Spacing bottom set to 100
+ * @sample {highmaps} maps/chart/spacing/
+ * Spacing 100 all around
+ * @default 15
+ * @since 2.1
+ * @apioption chart.spacingBottom
+ */
+
+ /**
+ * The space between the left edge of the chart and the content (plot
+ * area, axis title and labels, title, subtitle or legend in top position).
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/spacingleft/
+ * Spacing left set to 100
+ * @sample {highstock} stock/chart/spacingleft/
+ * Spacing left set to 100
+ * @sample {highmaps} maps/chart/spacing/
+ * Spacing 100 all around
+ * @default 10
+ * @since 2.1
+ * @apioption chart.spacingLeft
+ */
+
+ /**
+ * The space between the right edge of the chart and the content (plot
+ * area, axis title and labels, title, subtitle or legend in top
+ * position).
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/spacingright-100/
+ * Spacing set to 100
+ * @sample {highcharts} highcharts/chart/spacingright-legend/
+ * Legend in right position with default spacing
+ * @sample {highstock} stock/chart/spacingright/
+ * Spacing set to 100
+ * @sample {highmaps} maps/chart/spacing/
+ * Spacing 100 all around
+ * @default 10
+ * @since 2.1
+ * @apioption chart.spacingRight
+ */
+
+ /**
+ * The space between the top edge of the chart and the content (plot
+ * area, axis title and labels, title, subtitle or legend in top
+ * position).
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/chart/spacingtop-100/
+ * A top spacing of 100
+ * @sample {highcharts} highcharts/chart/spacingtop-10/
+ * Floating chart title makes the plot area align to the default
+ * spacingTop of 10.
+ * @sample {highstock} stock/chart/spacingtop/
+ * A top spacing of 100
+ * @sample {highmaps} maps/chart/spacing/
+ * Spacing 100 all around
+ * @default 10
+ * @since 2.1
+ * @apioption chart.spacingTop
+ */
+
+ /**
+ * Additional CSS styles to apply inline to the container `div`. Note
+ * that since the default font styles are applied in the renderer, it
+ * is ignorant of the individual chart options and must be set globally.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, general chart styles can be set with the `.highcharts-root` class.
+ * @sample {highcharts} highcharts/chart/style-serif-font/
+ * Using a serif type font
+ * @sample {highcharts} highcharts/css/em/
+ * Styled mode with relative font sizes
+ * @sample {highstock} stock/chart/style/
+ * Using a serif type font
+ * @sample {highmaps} maps/chart/style-serif-font/
+ * Using a serif type font
+ * @default {"fontFamily":"\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif","fontSize":"12px"}
+ * @apioption chart.style
+ */
+
+ /**
+ * The default series type for the chart. Can be any of the chart types
+ * listed under [plotOptions](#plotOptions).
+ *
+ * @validvalue ["line", "spline", "column", "bar", "area", "areaspline", "pie", "arearange", "areasplinerange", "boxplot", "bubble", "columnrange", "errorbar", "funnel", "gauge", "heatmap", "polygon", "pyramid", "scatter", "solidgauge", "treemap", "waterfall"]
+ * @type {String}
+ * @sample {highcharts} highcharts/chart/type-bar/ Bar
+ * @sample {highstock} stock/chart/type/
+ * Areaspline
+ * @sample {highmaps} maps/chart/type-mapline/
+ * Mapline
+ * @default {highcharts} line
+ * @default {highstock} line
+ * @default {highmaps} map
+ * @since 2.1.0
+ * @apioption chart.type
+ */
+
+ /**
+ * Decides in what dimensions the user can zoom by dragging the mouse.
+ * Can be one of `x`, `y` or `xy`.
+ *
+ * @validvalue [null, "x", "y", "xy"]
+ * @type {String}
+ * @see [panKey](#chart.panKey)
+ * @sample {highcharts} highcharts/chart/zoomtype-none/ None by default
+ * @sample {highcharts} highcharts/chart/zoomtype-x/ X
+ * @sample {highcharts} highcharts/chart/zoomtype-y/ Y
+ * @sample {highcharts} highcharts/chart/zoomtype-xy/ Xy
+ * @sample {highstock} stock/demo/basic-line/ None by default
+ * @sample {highstock} stock/chart/zoomtype-x/ X
+ * @sample {highstock} stock/chart/zoomtype-y/ Y
+ * @sample {highstock} stock/chart/zoomtype-xy/ Xy
+ * @product highcharts highstock
+ * @apioption chart.zoomType
+ */
+ },
+
+ /**
+ * The chart's main title.
+ *
+ * @sample {highmaps} maps/title/title/ Title options demonstrated
+ */
+ title: {
+
+ /**
+ * The title of the chart. To disable the title, set the `text` to
+ * `null`.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/title/text/ Custom title
+ * @sample {highstock} stock/chart/title-text/ Custom title
+ * @default {highcharts|highmaps} Chart title
+ * @default {highstock} null
+ */
+ text: 'Chart title',
+
+ /**
+ * The horizontal alignment of the title. Can be one of "left", "center"
+ * and "right".
+ *
+ * @validvalue ["left", "center", "right"]
+ * @type {String}
+ * @sample {highcharts} highcharts/title/align/ Aligned to the plot area (x = 70px = margin left - spacing left)
+ * @sample {highstock} stock/chart/title-align/ Aligned to the plot area (x = 50px = margin left - spacing left)
+ * @default center
+ * @since 2.0
+ */
+ align: 'center',
+
+ /**
+ * The margin between the title and the plot area, or if a subtitle
+ * is present, the margin between the subtitle and the plot area.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/title/margin-50/ A chart title margin of 50
+ * @sample {highcharts} highcharts/title/margin-subtitle/ The same margin applied with a subtitle
+ * @sample {highstock} stock/chart/title-margin/ A chart title margin of 50
+ * @default 15
+ * @since 2.1
+ */
+ margin: 15,
+
+ /**
+ * Adjustment made to the title width, normally to reserve space for
+ * the exporting burger menu.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
+ * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
+ * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
+ * @default -44
+ * @since 4.2.5
+ */
+ widthAdjust: -44
+
+ /**
+ * When the title is floating, the plot area will not move to make space
+ * for it.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/chart/zoomtype-none/ False by default
+ * @sample {highcharts} highcharts/title/floating/
+ * True - title on top of the plot area
+ * @sample {highstock} stock/chart/title-floating/
+ * True - title on top of the plot area
+ * @default false
+ * @since 2.1
+ * @apioption title.floating
+ */
+
+ /**
+ * CSS styles for the title. Use this for font styling, but use `align`,
+ * `x` and `y` for text alignment.
+ *
+ * In styled mode, the title style is given in the `.highcharts-title` class.
+ *
+ * @type {CSSObject}
+ * @sample {highcharts} highcharts/title/style/ Custom color and weight
+ * @sample {highstock} stock/chart/title-style/ Custom color and weight
+ * @sample highcharts/css/titles/ Styled mode
+ * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" }
+ * @default {highstock} { "color": "#333333", "fontSize": "16px" }
+ * @apioption title.style
+ */
+
+ /**
+ * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
+ * and-string-formatting#html) to render the text.
+ *
+ * @type {Boolean}
+ * @default false
+ * @apioption title.useHTML
+ */
+
+ /**
+ * The vertical alignment of the title. Can be one of `"top"`, `"middle"`
+ * and `"bottom"`. When a value is given, the title behaves as if [floating](#title.
+ * floating) were `true`.
+ *
+ * @validvalue ["top", "middle", "bottom"]
+ * @type {String}
+ * @sample {highcharts} highcharts/title/verticalalign/
+ * Chart title in bottom right corner
+ * @sample {highstock} stock/chart/title-verticalalign/
+ * Chart title in bottom right corner
+ * @since 2.1
+ * @apioption title.verticalAlign
+ */
+
+ /**
+ * The x position of the title relative to the alignment within chart.
+ * spacingLeft and chart.spacingRight.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/title/align/
+ * Aligned to the plot area (x = 70px = margin left - spacing left)
+ * @sample {highstock} stock/chart/title-align/
+ * Aligned to the plot area (x = 50px = margin left - spacing left)
+ * @default 0
+ * @since 2.0
+ * @apioption title.x
+ */
+
+ /**
+ * The y position of the title relative to the alignment within [chart.
+ * spacingTop](#chart.spacingTop) and [chart.spacingBottom](#chart.spacingBottom).
+ * By default it depends on the font size.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/title/y/
+ * Title inside the plot area
+ * @sample {highstock} stock/chart/title-verticalalign/
+ * Chart title in bottom right corner
+ * @since 2.0
+ * @apioption title.y
+ */
+
+ },
+
+ /**
+ * The chart's subtitle. This can be used both to display a subtitle below
+ * the main title, and to display random text anywhere in the chart. The
+ * subtitle can be updated after chart initialization through the
+ * `Chart.setTitle` method.
+ *
+ * @sample {highmaps} maps/title/subtitle/ Subtitle options demonstrated
+ */
+ subtitle: {
+
+ /**
+ * The subtitle of the chart.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/subtitle/text/ Custom subtitle
+ * @sample {highcharts} highcharts/subtitle/text-formatted/ Formatted and linked text.
+ * @sample {highstock} stock/chart/subtitle-text Custom subtitle
+ * @sample {highstock} stock/chart/subtitle-text-formatted Formatted and linked text.
+ */
+ text: '',
+
+ /**
+ * The horizontal alignment of the subtitle. Can be one of "left",
+ * "center" and "right".
+ *
+ * @validvalue ["left", "center", "right"]
+ * @type {String}
+ * @sample {highcharts} highcharts/subtitle/align/ Footnote at right of plot area
+ * @sample {highstock} stock/chart/subtitle-footnote Footnote at bottom right of plot area
+ * @default center
+ * @since 2.0
+ */
+ align: 'center',
+
+ /**
+ * Adjustment made to the subtitle width, normally to reserve space
+ * for the exporting burger menu.
+ *
+ * @type {Number}
+ * @see [title.widthAdjust](#title.widthAdjust)
+ * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
+ * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
+ * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
+ * @default -44
+ * @since 4.2.5
+ */
+ widthAdjust: -44
+
+ /**
+ * When the subtitle is floating, the plot area will not move to make
+ * space for it.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/subtitle/floating/
+ * Floating title and subtitle
+ * @sample {highstock} stock/chart/subtitle-footnote
+ * Footnote floating at bottom right of plot area
+ * @default false
+ * @since 2.1
+ * @apioption subtitle.floating
+ */
+
+ /**
+ * CSS styles for the title.
+ *
+ * In styled mode, the subtitle style is given in the `.highcharts-subtitle` class.
+ *
+ * @type {CSSObject}
+ * @sample {highcharts} highcharts/subtitle/style/
+ * Custom color and weight
+ * @sample {highcharts} highcharts/css/titles/
+ * Styled mode
+ * @sample {highstock} stock/chart/subtitle-style
+ * Custom color and weight
+ * @sample {highstock} highcharts/css/titles/
+ * Styled mode
+ * @sample {highmaps} highcharts/css/titles/
+ * Styled mode
+ * @default { "color": "#666666" }
+ * @apioption subtitle.style
+ */
+
+ /**
+ * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
+ * and-string-formatting#html) to render the text.
+ *
+ * @type {Boolean}
+ * @default false
+ * @apioption subtitle.useHTML
+ */
+
+ /**
+ * The vertical alignment of the title. Can be one of "top", "middle"
+ * and "bottom". When a value is given, the title behaves as floating.
+ *
+ * @validvalue ["top", "middle", "bottom"]
+ * @type {String}
+ * @sample {highcharts} highcharts/subtitle/verticalalign/
+ * Footnote at the bottom right of plot area
+ * @sample {highstock} stock/chart/subtitle-footnote
+ * Footnote at the bottom right of plot area
+ * @default
+ * @since 2.1
+ * @apioption subtitle.verticalAlign
+ */
+
+ /**
+ * The x position of the subtitle relative to the alignment within chart.
+ * spacingLeft and chart.spacingRight.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/subtitle/align/
+ * Footnote at right of plot area
+ * @sample {highstock} stock/chart/subtitle-footnote
+ * Footnote at the bottom right of plot area
+ * @default 0
+ * @since 2.0
+ * @apioption subtitle.x
+ */
+
+ /**
+ * The y position of the subtitle relative to the alignment within chart.
+ * spacingTop and chart.spacingBottom. By default the subtitle is laid
+ * out below the title unless the title is floating.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/subtitle/verticalalign/
+ * Footnote at the bottom right of plot area
+ * @sample {highstock} stock/chart/subtitle-footnote
+ * Footnote at the bottom right of plot area
+ * @default {highcharts} null
+ * @default {highstock} null
+ * @default {highmaps}
+ * @since 2.0
+ * @apioption subtitle.y
+ */
+ },
+
+ /**
+ * The plotOptions is a wrapper object for config objects for each series
+ * type. The config objects for each series can also be overridden for
+ * each series item as given in the series array.
+ *
+ * Configuration options for the series are given in three levels. Options
+ * for all series in a chart are given in the [plotOptions.series](#plotOptions.
+ * series) object. Then options for all series of a specific type are
+ * given in the plotOptions of that type, for example plotOptions.line.
+ * Next, options for one single series are given in [the series array](#series).
+ *
+ */
+ plotOptions: {},
+
+ /**
+ * HTML labels that can be positioned anywhere in the chart area.
+ *
+ */
+ labels: {
+
+ /**
+ * A HTML label that can be positioned anywhere in the chart area.
+ *
+ * @type {Array}
+ * @apioption labels.items
+ */
+
+ /**
+ * Inner HTML or text for the label.
+ *
+ * @type {String}
+ * @apioption labels.items.html
+ */
+
+ /**
+ * CSS styles for each label. To position the label, use left and top
+ * like this:
+ *
+ * style: {
+ * left: '100px',
+ * top: '100px'
+ * }
+ *
+ * @type {CSSObject}
+ * @apioption labels.items.style
+ */
+
+ /**
+ * Shared CSS styles for all labels.
+ *
+ * @type {CSSObject}
+ * @default { "color": "#333333" }
+ */
+ style: {
+ position: 'absolute',
+ color: '#333333'
+ }
+ },
+
+ /**
+ * The legend is a box containing a symbol and name for each series
+ * item or point item in the chart. Each series (or points in case
+ * of pie charts) is represented by a symbol and its name in the legend.
+ *
+ * It is possible to override the symbol creator function and
+ * create [custom legend symbols](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/legend-
+ * custom-symbol/).
+ *
+ * @productdesc {highmaps}
+ * A Highmaps legend by default contains one legend item per series, but if
+ * a `colorAxis` is defined, the axis will be displayed in the legend.
+ * Either as a gradient, or as multiple legend items for `dataClasses`.
+ */
+ legend: {
+
+ /**
+ * The background color of the legend.
+ *
+ * @type {Color}
+ * @see In styled mode, the legend background fill can be applied with
+ * the `.highcharts-legend-box` class.
+ * @sample {highcharts} highcharts/legend/backgroundcolor/ Yellowish background
+ * @sample {highstock} stock/legend/align/ Various legend options
+ * @sample {highmaps} maps/legend/border-background/ Border and background options
+ * @apioption legend.backgroundColor
+ */
+
+ /**
+ * The width of the drawn border around the legend.
+ *
+ * @type {Number}
+ * @see In styled mode, the legend border stroke width can be applied
+ * with the `.highcharts-legend-box` class.
+ * @sample {highcharts} highcharts/legend/borderwidth/ 2px border width
+ * @sample {highstock} stock/legend/align/ Various legend options
+ * @sample {highmaps} maps/legend/border-background/ Border and background options
+ * @default 0
+ * @apioption legend.borderWidth
+ */
+
+ /**
+ * Enable or disable the legend.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/legend/enabled-false/ Legend disabled
+ * @sample {highstock} stock/legend/align/ Various legend options
+ * @sample {highmaps} maps/legend/enabled-false/ Legend disabled
+ * @default {highstock} false
+ * @default {highmaps} true
+ */
+ enabled: true,
+
+ /**
+ * The horizontal alignment of the legend box within the chart area.
+ * Valid values are `left`, `center` and `right`.
+ *
+ * In the case that the legend is aligned in a corner position, the
+ * `layout` option will determine whether to place it above/below
+ * or on the side of the plot area.
+ *
+ * @validvalue ["left", "center", "right"]
+ * @type {String}
+ * @sample {highcharts} highcharts/legend/align/
+ * Legend at the right of the chart
+ * @sample {highstock} stock/legend/align/
+ * Various legend options
+ * @sample {highmaps} maps/legend/alignment/
+ * Legend alignment
+ * @since 2.0
+ */
+ align: 'center',
+
+ /**
+ * When the legend is floating, the plot area ignores it and is allowed
+ * to be placed below it.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/legend/floating-false/ False by default
+ * @sample {highcharts} highcharts/legend/floating-true/ True
+ * @sample {highmaps} maps/legend/alignment/ Floating legend
+ * @default false
+ * @since 2.1
+ * @apioption legend.floating
+ */
+
+ /**
+ * The layout of the legend items. Can be one of "horizontal" or "vertical".
+ *
+ * @validvalue ["horizontal", "vertical"]
+ * @type {String}
+ * @sample {highcharts} highcharts/legend/layout-horizontal/ Horizontal by default
+ * @sample {highcharts} highcharts/legend/layout-vertical/ Vertical
+ * @sample {highstock} stock/legend/layout-horizontal/ Horizontal by default
+ * @sample {highmaps} maps/legend/padding-itemmargin/ Vertical with data classes
+ * @sample {highmaps} maps/legend/layout-vertical/ Vertical with color axis gradient
+ * @default horizontal
+ */
+ layout: 'horizontal',
+
+ /**
+ * In a legend with horizontal layout, the itemDistance defines the
+ * pixel distance between each item.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/layout-horizontal/ 50px item distance
+ * @sample {highstock} highcharts/legend/layout-horizontal/ 50px item distance
+ * @default {highcharts} 20
+ * @default {highstock} 20
+ * @default {highmaps} 8
+ * @since 3.0.3
+ * @apioption legend.itemDistance
+ */
+
+ /**
+ * The pixel bottom margin for each legend item.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
+ * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
+ * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
+ * @default 0
+ * @since 2.2.0
+ * @apioption legend.itemMarginBottom
+ */
+
+ /**
+ * The pixel top margin for each legend item.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
+ * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
+ * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
+ * @default 0
+ * @since 2.2.0
+ * @apioption legend.itemMarginTop
+ */
+
+ /**
+ * The width for each legend item. This is useful in a horizontal layout
+ * with many items when you want the items to align vertically. .
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/itemwidth-default/ Null by default
+ * @sample {highcharts} highcharts/legend/itemwidth-80/ 80 for aligned legend items
+ * @default null
+ * @since 2.0
+ * @apioption legend.itemWidth
+ */
+
+ /**
+ * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
+ * and-string-formatting) for each legend label. Available variables
+ * relates to properties on the series, or the point in case of pies.
+ *
+ * @type {String}
+ * @default {name}
+ * @since 1.3
+ * @apioption legend.labelFormat
+ */
+
+ /**
+ * Callback function to format each of the series' labels. The `this`
+ * keyword refers to the series object, or the point object in case
+ * of pie charts. By default the series or point name is printed.
+ *
+ * @productdesc {highmaps}
+ * In Highmaps the context can also be a data class in case
+ * of a `colorAxis`.
+ *
+ * @type {Function}
+ * @sample {highcharts} highcharts/legend/labelformatter/ Add text
+ * @sample {highmaps} maps/legend/labelformatter/ Data classes with label formatter
+ * @context {Series|Point}
+ */
+ labelFormatter: function () {
+ return this.name;
+ },
+
+ /**
+ * Line height for the legend items. Deprecated as of 2.1\. Instead,
+ * the line height for each item can be set using itemStyle.lineHeight,
+ * and the padding between items using itemMarginTop and itemMarginBottom.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/lineheight/ Setting padding
+ * @default 16
+ * @since 2.0
+ * @product highcharts
+ * @apioption legend.lineHeight
+ */
+
+ /**
+ * If the plot area sized is calculated automatically and the legend
+ * is not floating, the legend margin is the space between the legend
+ * and the axis labels or plot area.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/margin-default/ 12 pixels by default
+ * @sample {highcharts} highcharts/legend/margin-30/ 30 pixels
+ * @default 12
+ * @since 2.1
+ * @apioption legend.margin
+ */
+
+ /**
+ * Maximum pixel height for the legend. When the maximum height is extended,
+ * navigation will show.
+ *
+ * @type {Number}
+ * @default undefined
+ * @since 2.3.0
+ * @apioption legend.maxHeight
+ */
+
+ /**
+ * The color of the drawn border around the legend.
+ *
+ * @type {Color}
+ * @see In styled mode, the legend border stroke can be applied with
+ * the `.highcharts-legend-box` class.
+ * @sample {highcharts} highcharts/legend/bordercolor/ Brown border
+ * @sample {highstock} stock/legend/align/ Various legend options
+ * @sample {highmaps} maps/legend/border-background/ Border and background options
+ * @default #999999
+ */
+ borderColor: '#999999',
+
+ /**
+ * The border corner radius of the legend.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/borderradius-default/ Square by default
+ * @sample {highcharts} highcharts/legend/borderradius-round/ 5px rounded
+ * @sample {highmaps} maps/legend/border-background/ Border and background options
+ * @default 0
+ */
+ borderRadius: 0,
+
+ /**
+ * Options for the paging or navigation appearing when the legend
+ * is overflown. Navigation works well on screen, but not in static
+ * exported images. One way of working around that is to [increase
+ * the chart height in export](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-
+ * enabled-false/).
+ *
+ */
+ navigation: {
+
+
+ /**
+ * The color for the active up or down arrow in the legend page navigation.
+ *
+ * @type {Color}
+ * @see In styled mode, the active arrow be styled with the `.highcharts-legend-nav-active` class.
+ * @sample {highcharts} highcharts/legend/navigation/ Legend page navigation demonstrated
+ * @sample {highstock} highcharts/legend/navigation/ Legend page navigation demonstrated
+ * @default #003399
+ * @since 2.2.4
+ */
+ activeColor: '#003399',
+
+ /**
+ * The color of the inactive up or down arrow in the legend page
+ * navigation. .
+ *
+ * @type {Color}
+ * @see In styled mode, the inactive arrow be styled with the
+ * `.highcharts-legend-nav-inactive` class.
+ * @sample {highcharts} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @sample {highstock} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @default {highcharts} #cccccc
+ * @default {highstock} #cccccc
+ * @default {highmaps} ##cccccc
+ * @since 2.2.4
+ */
+ inactiveColor: '#cccccc'
+
+
+ /**
+ * How to animate the pages when navigating up or down. A value of `true`
+ * applies the default navigation given in the chart.animation option.
+ * Additional options can be given as an object containing values for
+ * easing and duration.
+ *
+ * @type {Boolean|Object}
+ * @sample {highcharts} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @sample {highstock} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @default true
+ * @since 2.2.4
+ * @apioption legend.navigation.animation
+ */
+
+ /**
+ * The pixel size of the up and down arrows in the legend paging
+ * navigation.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @sample {highstock} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @default 12
+ * @since 2.2.4
+ * @apioption legend.navigation.arrowSize
+ */
+
+ /**
+ * Whether to enable the legend navigation. In most cases, disabling
+ * the navigation results in an unwanted overflow.
+ *
+ * See also the [adapt chart to legend](http://www.highcharts.com/plugin-
+ * registry/single/8/Adapt-Chart-To-Legend) plugin for a solution to
+ * extend the chart height to make room for the legend, optionally in
+ * exported charts only.
+ *
+ * @type {Boolean}
+ * @default true
+ * @since 4.2.4
+ * @apioption legend.navigation.enabled
+ */
+
+ /**
+ * Text styles for the legend page navigation.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, the navigation items are styled with the
+ * `.highcharts-legend-navigation` class.
+ * @sample {highcharts} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @sample {highstock} highcharts/legend/navigation/
+ * Legend page navigation demonstrated
+ * @since 2.2.4
+ * @apioption legend.navigation.style
+ */
+ },
+
+ /**
+ * The inner padding of the legend box.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/padding-itemmargin/
+ * Padding and item margins demonstrated
+ * @sample {highstock} highcharts/legend/padding-itemmargin/
+ * Padding and item margins demonstrated
+ * @sample {highmaps} maps/legend/padding-itemmargin/
+ * Padding and item margins demonstrated
+ * @default 8
+ * @since 2.2.0
+ * @apioption legend.padding
+ */
+
+ /**
+ * Whether to reverse the order of the legend items compared to the
+ * order of the series or points as defined in the configuration object.
+ *
+ * @type {Boolean}
+ * @see [yAxis.reversedStacks](#yAxis.reversedStacks),
+ * [series.legendIndex](#series.legendIndex)
+ * @sample {highcharts} highcharts/legend/reversed/
+ * Stacked bar with reversed legend
+ * @default false
+ * @since 1.2.5
+ * @apioption legend.reversed
+ */
+
+ /**
+ * Whether to show the symbol on the right side of the text rather than
+ * the left side. This is common in Arabic and Hebraic.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/legend/rtl/ Symbol to the right
+ * @default false
+ * @since 2.2
+ * @apioption legend.rtl
+ */
+
+ /**
+ * CSS styles for the legend area. In the 1.x versions the position
+ * of the legend area was determined by CSS. In 2.x, the position is
+ * determined by properties like `align`, `verticalAlign`, `x` and `y`,
+ * but the styles are still parsed for backwards compatibility.
+ *
+ * @type {CSSObject}
+ * @deprecated
+ * @product highcharts highstock
+ * @apioption legend.style
+ */
+
+
+
+ /**
+ * CSS styles for each legend item. Only a subset of CSS is supported,
+ * notably those options related to text. The default `textOverflow`
+ * property makes long texts truncate. Set it to `null` to wrap text
+ * instead. A `width` property can be added to control the text width.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, the legend items can be styled with the `.
+ * highcharts-legend-item` class.
+ * @sample {highcharts} highcharts/legend/itemstyle/ Bold black text
+ * @sample {highmaps} maps/legend/itemstyle/ Item text styles
+ * @default { "color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis" }
+ */
+ itemStyle: {
+ color: '#333333',
+ fontSize: '12px',
+ fontWeight: 'bold',
+ textOverflow: 'ellipsis'
+ },
+
+ /**
+ * CSS styles for each legend item in hover mode. Only a subset of
+ * CSS is supported, notably those options related to text. Properties
+ * are inherited from `style` unless overridden here.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, the hovered legend items can be styled with
+ * the `.highcharts-legend-item:hover` pesudo-class.
+ * @sample {highcharts} highcharts/legend/itemhoverstyle/ Red on hover
+ * @sample {highmaps} maps/legend/itemstyle/ Item text styles
+ * @default { "color": "#000000" }
+ */
+ itemHoverStyle: {
+ color: '#000000'
+ },
+
+ /**
+ * CSS styles for each legend item when the corresponding series or
+ * point is hidden. Only a subset of CSS is supported, notably those
+ * options related to text. Properties are inherited from `style`
+ * unless overridden here.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, the hidden legend items can be styled with
+ * the `.highcharts-legend-item-hidden` class.
+ * @sample {highcharts} highcharts/legend/itemhiddenstyle/ Darker gray color
+ * @default { "color": "#cccccc" }
+ */
+ itemHiddenStyle: {
+ color: '#cccccc'
+ },
+
+ /**
+ * Whether to apply a drop shadow to the legend. A `backgroundColor`
+ * also needs to be applied for this to take effect. The shadow can be
+ * an object configuration containing `color`, `offsetX`, `offsetY`,
+ * `opacity` and `width`.
+ *
+ * @type {Boolean|Object}
+ * @sample {highcharts} highcharts/legend/shadow/
+ * White background and drop shadow
+ * @sample {highstock} stock/legend/align/
+ * Various legend options
+ * @sample {highmaps} maps/legend/border-background/
+ * Border and background options
+ * @default false
+ */
+ shadow: false,
+
+
+ /**
+ * Default styling for the checkbox next to a legend item when
+ * `showCheckbox` is true.
+ */
+ itemCheckboxStyle: {
+ position: 'absolute',
+ width: '13px', // for IE precision
+ height: '13px'
+ },
+ // itemWidth: undefined,
+
+ /**
+ * When this is true, the legend symbol width will be the same as
+ * the symbol height, which in turn defaults to the font size of the
+ * legend items.
+ *
+ * @type {Boolean}
+ * @default true
+ * @since 5.0.0
+ */
+ squareSymbol: true,
+
+ /**
+ * The pixel height of the symbol for series types that use a rectangle
+ * in the legend. Defaults to the font size of legend items.
+ *
+ * @productdesc {highmaps}
+ * In Highmaps, when the symbol is the gradient of a vertical color
+ * axis, the height defaults to 200.
+ *
+ * @type {Number}
+ * @sample {highmaps} maps/legend/layout-vertical-sized/
+ * Sized vertical gradient
+ * @sample {highmaps} maps/legend/padding-itemmargin/
+ * No distance between data classes
+ * @since 3.0.8
+ * @apioption legend.symbolHeight
+ */
+
+ /**
+ * The border radius of the symbol for series types that use a rectangle
+ * in the legend. Defaults to half the `symbolHeight`.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/symbolradius/ Round symbols
+ * @sample {highstock} highcharts/legend/symbolradius/ Round symbols
+ * @sample {highmaps} highcharts/legend/symbolradius/ Round symbols
+ * @since 3.0.8
+ * @apioption legend.symbolRadius
+ */
+
+ /**
+ * The pixel width of the legend item symbol. When the `squareSymbol`
+ * option is set, this defaults to the `symbolHeight`, otherwise 16.
+ *
+ * @productdesc {highmaps}
+ * In Highmaps, when the symbol is the gradient of a horizontal color
+ * axis, the width defaults to 200.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/symbolwidth/
+ * Greater symbol width and padding
+ * @sample {highmaps} maps/legend/padding-itemmargin/
+ * Padding and item margins demonstrated
+ * @sample {highmaps} maps/legend/layout-vertical-sized/
+ * Sized vertical gradient
+ * @apioption legend.symbolWidth
+ */
+
+ /**
+ * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
+ * and-string-formatting#html) to render the legend item texts. Prior
+ * to 4.1.7, when using HTML, [legend.navigation](#legend.navigation)
+ * was disabled.
+ *
+ * @type {Boolean}
+ * @default false
+ * @apioption legend.useHTML
+ */
+
+ /**
+ * The width of the legend box.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
+ * @default null
+ * @since 2.0
+ * @apioption legend.width
+ */
+
+ /**
+ * The pixel padding between the legend item symbol and the legend
+ * item text.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/symbolpadding/ Greater symbol width and padding
+ * @default 5
+ */
+ symbolPadding: 5,
+
+ /**
+ * The vertical alignment of the legend box. Can be one of `top`,
+ * `middle` or `bottom`. Vertical position can be further determined
+ * by the `y` option.
+ *
+ * In the case that the legend is aligned in a corner position, the
+ * `layout` option will determine whether to place it above/below
+ * or on the side of the plot area.
+ *
+ * @validvalue ["top", "middle", "bottom"]
+ * @type {String}
+ * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
+ * @sample {highstock} stock/legend/align/ Various legend options
+ * @sample {highmaps} maps/legend/alignment/ Legend alignment
+ * @default bottom
+ * @since 2.0
+ */
+ verticalAlign: 'bottom',
+ // width: undefined,
+
+ /**
+ * The x offset of the legend relative to its horizontal alignment
+ * `align` within chart.spacingLeft and chart.spacingRight. Negative
+ * x moves it to the left, positive x moves it to the right.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
+ * @default 0
+ * @since 2.0
+ */
+ x: 0,
+
+ /**
+ * The vertical offset of the legend relative to it's vertical alignment
+ * `verticalAlign` within chart.spacingTop and chart.spacingBottom.
+ * Negative y moves it up, positive y moves it down.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
+ * @sample {highstock} stock/legend/align/ Various legend options
+ * @sample {highmaps} maps/legend/alignment/ Legend alignment
+ * @default 0
+ * @since 2.0
+ */
+ y: 0,
+
+ /**
+ * A title to be added on top of the legend.
+ *
+ * @sample {highcharts} highcharts/legend/title/ Legend title
+ * @sample {highmaps} maps/legend/alignment/ Legend with title
+ * @since 3.0
+ */
+ title: {
+ /**
+ * A text or HTML string for the title.
+ *
+ * @type {String}
+ * @default null
+ * @since 3.0
+ * @apioption legend.title.text
+ */
+
+
+
+ /**
+ * Generic CSS styles for the legend title.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, the legend title is styled with the
+ * `.highcharts-legend-title` class.
+ * @default {"fontWeight":"bold"}
+ * @since 3.0
+ */
+ style: {
+ fontWeight: 'bold'
+ }
+
+ }
+ },
+
+
+ /**
+ * The loading options control the appearance of the loading screen
+ * that covers the plot area on chart operations. This screen only
+ * appears after an explicit call to `chart.showLoading()`. It is a
+ * utility for developers to communicate to the end user that something
+ * is going on, for example while retrieving new data via an XHR connection.
+ * The "Loading..." text itself is not part of this configuration
+ * object, but part of the `lang` object.
+ *
+ */
+ loading: {
+
+ /**
+ * The duration in milliseconds of the fade out effect.
+ *
+ * @type {Number}
+ * @sample highcharts/loading/hideduration/ Fade in and out over a second
+ * @default 100
+ * @since 1.2.0
+ * @apioption loading.hideDuration
+ */
+
+ /**
+ * The duration in milliseconds of the fade in effect.
+ *
+ * @type {Number}
+ * @sample highcharts/loading/hideduration/ Fade in and out over a second
+ * @default 100
+ * @since 1.2.0
+ * @apioption loading.showDuration
+ */
+
+
+ /**
+ * CSS styles for the loading label `span`.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, the loading label is styled with the
+ * `.highcharts-legend-loading-inner` class.
+ * @sample {highcharts|highmaps} highcharts/loading/labelstyle/ Vertically centered
+ * @sample {highstock} stock/loading/general/ Label styles
+ * @default { "fontWeight": "bold", "position": "relative", "top": "45%" }
+ * @since 1.2.0
+ */
+ labelStyle: {
+ fontWeight: 'bold',
+ position: 'relative',
+ top: '45%'
+ },
+
+ /**
+ * CSS styles for the loading screen that covers the plot area.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, the loading label is styled with the `.highcharts-legend-loading` class.
+ * @sample {highcharts|highmaps} highcharts/loading/style/ Gray plot area, white text
+ * @sample {highstock} stock/loading/general/ Gray plot area, white text
+ * @default { "position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center" }
+ * @since 1.2.0
+ */
+ style: {
+ position: 'absolute',
+ backgroundColor: '#ffffff',
+ opacity: 0.5,
+ textAlign: 'center'
+ }
+
+ },
+
+
+ /**
+ * Options for the tooltip that appears when the user hovers over a
+ * series or point.
+ *
+ */
+ tooltip: {
+
+ /**
+ * Enable or disable the tooltip.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/tooltip/enabled/ Disabled
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Disable tooltip and show values on chart instead
+ * @default true
+ */
+ enabled: true,
+
+ /**
+ * Enable or disable animation of the tooltip. In slow legacy IE browsers
+ * the animation is disabled by default.
+ *
+ * @type {Boolean}
+ * @default true
+ * @since 2.3.0
+ */
+ animation: svg,
+
+ /**
+ * The radius of the rounded border corners.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 5px by default
+ * @sample {highcharts} highcharts/tooltip/borderradius-0/ Square borders
+ * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
+ * @default 3
+ */
+ borderRadius: 3,
+
+ /**
+ * For series on a datetime axes, the date format in the tooltip's
+ * header will by default be guessed based on the closest data points.
+ * This member gives the default string representations used for
+ * each unit. For an overview of the replacement codes, see [dateFormat](#Highcharts.
+ * dateFormat).
+ *
+ * Defaults to:
+ *
+ * {
+ * millisecond:"%A, %b %e, %H:%M:%S.%L",
+ * second:"%A, %b %e, %H:%M:%S",
+ * minute:"%A, %b %e, %H:%M",
+ * hour:"%A, %b %e, %H:%M",
+ * day:"%A, %b %e, %Y",
+ * week:"Week from %A, %b %e, %Y",
+ * month:"%B %Y",
+ * year:"%Y"
+ * }
+ *
+ * @type {Object}
+ * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats)
+ * @product highcharts highstock
+ */
+ dateTimeLabelFormats: {
+ millisecond: '%A, %b %e, %H:%M:%S.%L',
+ second: '%A, %b %e, %H:%M:%S',
+ minute: '%A, %b %e, %H:%M',
+ hour: '%A, %b %e, %H:%M',
+ day: '%A, %b %e, %Y',
+ week: 'Week from %A, %b %e, %Y',
+ month: '%B %Y',
+ year: '%Y'
+ },
+
+ /**
+ * A string to append to the tooltip format.
+ *
+ * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
+ * @sample {highmaps} maps/tooltip/format/ Format demo
+ * @since 2.2
+ */
+ footerFormat: '',
+
+ /**
+ * Padding inside the tooltip, in pixels.
+ *
+ * @type {Number}
+ * @default 8
+ * @since 5.0.0
+ */
+ padding: 8,
+
+ /**
+ * Proximity snap for graphs or single points. It defaults to 10 for
+ * mouse-powered devices and 25 for touch devices.
+ *
+ * Note that in most cases the whole plot area captures the mouse
+ * movement, and in these cases `tooltip.snap` doesn't make sense.
+ * This applies when [stickyTracking](#plotOptions.series.stickyTracking)
+ * is `true` (default) and when the tooltip is [shared](#tooltip.shared)
+ * or [split](#tooltip.split).
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 10 px by default
+ * @sample {highcharts} highcharts/tooltip/snap-50/ 50 px on graph
+ * @default 10/25
+ * @since 1.2.0
+ * @product highcharts highstock
+ */
+ snap: isTouchDevice ? 25 : 10,
+
+
+ /**
+ * The background color or gradient for the tooltip.
+ *
+ * In styled mode, the stroke width is set in the `.highcharts-tooltip-box` class.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/tooltip/backgroundcolor-solid/ Yellowish background
+ * @sample {highcharts} highcharts/tooltip/backgroundcolor-gradient/ Gradient
+ * @sample {highcharts} highcharts/css/tooltip-border-background/ Tooltip in styled mode
+ * @sample {highstock} stock/tooltip/general/ Custom tooltip
+ * @sample {highstock} highcharts/css/tooltip-border-background/ Tooltip in styled mode
+ * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
+ * @sample {highmaps} highcharts/css/tooltip-border-background/ Tooltip in styled mode
+ * @default rgba(247,247,247,0.85)
+ */
+ backgroundColor: color('#f7f7f7').setOpacity(0.85).get(),
+
+ /**
+ * The pixel width of the tooltip border.
+ *
+ * In styled mode, the stroke width is set in the `.highcharts-tooltip-box` class.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 2px by default
+ * @sample {highcharts} highcharts/tooltip/borderwidth/ No border (shadow only)
+ * @sample {highcharts} highcharts/css/tooltip-border-background/ Tooltip in styled mode
+ * @sample {highstock} stock/tooltip/general/ Custom tooltip
+ * @sample {highstock} highcharts/css/tooltip-border-background/ Tooltip in styled mode
+ * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
+ * @sample {highmaps} highcharts/css/tooltip-border-background/ Tooltip in styled mode
+ * @default 1
+ */
+ borderWidth: 1,
+
+ /**
+ * The HTML of the tooltip header line. Variables are enclosed by
+ * curly brackets. Available variables are `point.key`, `series.name`,
+ * `series.color` and other members from the `point` and `series`
+ * objects. The `point.key` variable contains the category name, x
+ * value or datetime string depending on the type of axis. For datetime
+ * axes, the `point.key` date format can be set using tooltip.xDateFormat.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/tooltip/footerformat/
+ * A HTML table in the tooltip
+ * @sample {highstock} highcharts/tooltip/footerformat/
+ * A HTML table in the tooltip
+ * @sample {highmaps} maps/tooltip/format/ Format demo
+ */
+ headerFormat: '{point.key} ',
+
+ /**
+ * The HTML of the point's line in the tooltip. Variables are enclosed
+ * by curly brackets. Available variables are point.x, point.y, series.
+ * name and series.color and other properties on the same form. Furthermore,
+ * point.y can be extended by the `tooltip.valuePrefix` and `tooltip.
+ * valueSuffix` variables. This can also be overridden for each series,
+ * which makes it a good hook for displaying units.
+ *
+ * In styled mode, the dot is colored by a class name rather
+ * than the point color.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/tooltip/pointformat/ A different point format with value suffix
+ * @sample {highmaps} maps/tooltip/format/ Format demo
+ * @default \u25CF {series.name}: {point.y}
+ * @since 2.2
+ */
+ pointFormat: '\u25CF {series.name}: {point.y} ',
+
+ /**
+ * Whether to apply a drop shadow to the tooltip.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/ True by default
+ * @sample {highcharts} highcharts/tooltip/shadow/ False
+ * @sample {highmaps} maps/tooltip/positioner/ Fixed tooltip position, border and shadow disabled
+ * @default true
+ */
+ shadow: true,
+
+ /**
+ * CSS styles for the tooltip. The tooltip can also be styled through
+ * the CSS class `.highcharts-tooltip`.
+ *
+ * @type {CSSObject}
+ * @sample {highcharts} highcharts/tooltip/style/ Greater padding, bold text
+ * @default { "color": "#333333", "cursor": "default", "fontSize": "12px", "pointerEvents": "none", "whiteSpace": "nowrap" }
+ */
+ style: {
+ color: '#333333',
+ cursor: 'default',
+ fontSize: '12px',
+ pointerEvents: 'none', // #1686 http://caniuse.com/#feat=pointer-events
+ whiteSpace: 'nowrap'
+ }
+
+
+
+ /**
+ * The color of the tooltip border. When `null`, the border takes the
+ * color of the corresponding series or point.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/tooltip/bordercolor-default/
+ * Follow series by default
+ * @sample {highcharts} highcharts/tooltip/bordercolor-black/
+ * Black border
+ * @sample {highstock} stock/tooltip/general/
+ * Styled tooltip
+ * @sample {highmaps} maps/tooltip/background-border/
+ * Background and border demo
+ * @default null
+ * @apioption tooltip.borderColor
+ */
+
+ /**
+ * Since 4.1, the crosshair definitions are moved to the Axis object
+ * in order for a better separation from the tooltip. See [xAxis.crosshair](#xAxis.
+ * crosshair).
+ *
+ * @type {Mixed}
+ * @deprecated
+ * @sample {highcharts} highcharts/tooltip/crosshairs-x/
+ * Enable a crosshair for the x value
+ * @default true
+ * @apioption tooltip.crosshairs
+ */
+
+ /**
+ * Whether the tooltip should follow the mouse as it moves across columns,
+ * pie slices and other point types with an extent. By default it behaves
+ * this way for scatter, bubble and pie series by override in the `plotOptions`
+ * for those series types.
+ *
+ * For touch moves to behave the same way, [followTouchMove](#tooltip.
+ * followTouchMove) must be `true` also.
+ *
+ * @type {Boolean}
+ * @default {highcharts} false
+ * @default {highstock} false
+ * @default {highmaps} true
+ * @since 3.0
+ * @apioption tooltip.followPointer
+ */
+
+ /**
+ * Whether the tooltip should follow the finger as it moves on a touch
+ * device. If this is `true` and [chart.panning](#chart.panning) is
+ * set,`followTouchMove` will take over one-finger touches, so the user
+ * needs to use two fingers for zooming and panning.
+ *
+ * @type {Boolean}
+ * @default {highcharts} true
+ * @default {highstock} true
+ * @default {highmaps} false
+ * @since 3.0.1
+ * @apioption tooltip.followTouchMove
+ */
+
+ /**
+ * Callback function to format the text of the tooltip from scratch. Return
+ * `false` to disable tooltip for a specific point on series.
+ *
+ * A subset of HTML is supported. Unless `useHTML` is true, the HTML of the
+ * tooltip is parsed and converted to SVG, therefore this isn't a complete HTML
+ * renderer. The following tags are supported: ``, ``, ``, ``,
+ * ` `, ``. Spans can be styled with a `style` attribute,
+ * but only text-related CSS that is shared with SVG is handled.
+ *
+ * Since version 2.1 the tooltip can be shared between multiple series
+ * through the `shared` option. The available data in the formatter
+ * differ a bit depending on whether the tooltip is shared or not. In
+ * a shared tooltip, all properties except `x`, which is common for
+ * all points, are kept in an array, `this.points`.
+ *
+ * Available data are:
+ *
+ *
+ *
+ * this.percentage (not shared) / this.points[i].percentage (shared)
+ *
+ * Stacked series and pies only. The point's percentage of the total.
+ *
+ *
+ * this.point (not shared) / this.points[i].point (shared)
+ *
+ * The point object. The point name, if defined, is available through
+ * `this.point.name`.
+ *
+ * this.points
+ *
+ * In a shared tooltip, this is an array containing all other properties
+ * for each point.
+ *
+ * this.series (not shared) / this.points[i].series (shared)
+ *
+ * The series object. The series name is available through
+ * `this.series.name`.
+ *
+ * this.total (not shared) / this.points[i].total (shared)
+ *
+ * Stacked series only. The total value at this point's x value.
+ *
+ *
+ * this.x
+ *
+ * The x value. This property is the same regardless of the tooltip
+ * being shared or not.
+ *
+ * this.y (not shared) / this.points[i].y (shared)
+ *
+ * The y value.
+ *
+ *
+ *
+ * @type {Function}
+ * @sample {highcharts} highcharts/tooltip/formatter-simple/
+ * Simple string formatting
+ * @sample {highcharts} highcharts/tooltip/formatter-shared/
+ * Formatting with shared tooltip
+ * @sample {highstock} stock/tooltip/formatter/
+ * Formatting with shared tooltip
+ * @sample {highmaps} maps/tooltip/formatter/
+ * String formatting
+ * @apioption tooltip.formatter
+ */
+
+ /**
+ * The number of milliseconds to wait until the tooltip is hidden when
+ * mouse out from a point or chart.
+ *
+ * @type {Number}
+ * @default 500
+ * @since 3.0
+ * @apioption tooltip.hideDelay
+ */
+
+ /**
+ * A callback function for formatting the HTML output for a single point
+ * in the tooltip. Like the `pointFormat` string, but with more flexibility.
+ *
+ * @type {Function}
+ * @context Point
+ * @since 4.1.0
+ * @apioption tooltip.pointFormatter
+ */
+
+ /**
+ * A callback function to place the tooltip in a default position. The
+ * callback receives three parameters: `labelWidth`, `labelHeight` and
+ * `point`, where point contains values for `plotX` and `plotY` telling
+ * where the reference point is in the plot area. Add `chart.plotLeft`
+ * and `chart.plotTop` to get the full coordinates.
+ *
+ * The return should be an object containing x and y values, for example
+ * `{ x: 100, y: 100 }`.
+ *
+ * @type {Function}
+ * @sample {highcharts} highcharts/tooltip/positioner/ A fixed tooltip position
+ * @sample {highstock} stock/tooltip/positioner/ A fixed tooltip position on top of the chart
+ * @sample {highmaps} maps/tooltip/positioner/ A fixed tooltip position
+ * @since 2.2.4
+ * @apioption tooltip.positioner
+ */
+
+ /**
+ * The name of a symbol to use for the border around the tooltip.
+ *
+ * @type {String}
+ * @default callout
+ * @validvalue ["callout", "square"]
+ * @since 4.0
+ * @apioption tooltip.shape
+ */
+
+ /**
+ * When the tooltip is shared, the entire plot area will capture mouse
+ * movement or touch events. Tooltip texts for series types with ordered
+ * data (not pie, scatter, flags etc) will be shown in a single bubble.
+ * This is recommended for single series charts and for tablet/mobile
+ * optimized charts.
+ *
+ * See also [tooltip.split](#tooltip.split), that is better suited for
+ * charts with many series, especially line-type series.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/tooltip/shared-false/ False by default
+ * @sample {highcharts} highcharts/tooltip/shared-true/ True
+ * @sample {highcharts} highcharts/tooltip/shared-x-crosshair/ True with x axis crosshair
+ * @sample {highcharts} highcharts/tooltip/shared-true-mixed-types/ True with mixed series types
+ * @default false
+ * @since 2.1
+ * @product highcharts highstock
+ * @apioption tooltip.shared
+ */
+
+ /**
+ * Split the tooltip into one label per series, with the header close
+ * to the axis. This is recommended over [shared](#tooltip.shared) tooltips
+ * for charts with multiple line series, generally making them easier
+ * to read.
+ *
+ * @productdesc {highstock} In Highstock, tooltips are split by default
+ * since v6.0.0. Stock charts typically contain multi-dimension points
+ * and multiple panes, making split tooltips the preferred layout over
+ * the previous `shared` tooltip.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/tooltip/split/ Split tooltip
+ * @sample {highstock} highcharts/tooltip/split/ Split tooltip
+ * @sample {highmaps} highcharts/tooltip/split/ Split tooltip
+ * @default {highcharts} false
+ * @default {highstock} true
+ * @product highcharts highstock
+ * @since 5.0.0
+ * @apioption tooltip.split
+ */
+
+ /**
+ * Use HTML to render the contents of the tooltip instead of SVG. Using
+ * HTML allows advanced formatting like tables and images in the tooltip.
+ * It is also recommended for rtl languages as it works around rtl
+ * bugs in early Firefox.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
+ * @sample {highcharts} highcharts/tooltip/fullhtml/ Full HTML tooltip
+ * @sample {highstock} highcharts/tooltip/footerformat/ A table for value alignment
+ * @sample {highstock} highcharts/tooltip/fullhtml/ Full HTML tooltip
+ * @sample {highmaps} maps/tooltip/usehtml/ Pure HTML tooltip
+ * @default false
+ * @since 2.2
+ * @apioption tooltip.useHTML
+ */
+
+ /**
+ * How many decimals to show in each series' y value. This is overridable
+ * in each series' tooltip options object. The default is to preserve
+ * all decimals.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @since 2.2
+ * @apioption tooltip.valueDecimals
+ */
+
+ /**
+ * A string to prepend to each series' y value. Overridable in each
+ * series' tooltip options object.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @since 2.2
+ * @apioption tooltip.valuePrefix
+ */
+
+ /**
+ * A string to append to each series' y value. Overridable in each series'
+ * tooltip options object.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
+ * @since 2.2
+ * @apioption tooltip.valueSuffix
+ */
+
+ /**
+ * The format for the date in the tooltip header if the X axis is a
+ * datetime axis. The default is a best guess based on the smallest
+ * distance between points in the chart.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/tooltip/xdateformat/ A different format
+ * @product highcharts highstock
+ * @apioption tooltip.xDateFormat
+ */
+ },
+
+
+ /**
+ * Highchart by default puts a credits label in the lower right corner
+ * of the chart. This can be changed using these options.
+ */
+ credits: {
+
+ /**
+ * Whether to show the credits text.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/credits/enabled-false/ Credits disabled
+ * @sample {highstock} stock/credits/enabled/ Credits disabled
+ * @sample {highmaps} maps/credits/enabled-false/ Credits disabled
+ * @default true
+ */
+ enabled: true,
+
+ /**
+ * The URL for the credits label.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/credits/href/ Custom URL and text
+ * @sample {highmaps} maps/credits/customized/ Custom URL and text
+ * @default {highcharts} http://www.highcharts.com
+ * @default {highstock} "http://www.highcharts.com"
+ * @default {highmaps} http://www.highcharts.com
+ */
+ href: 'http://www.highcharts.com',
+
+ /**
+ * Position configuration for the credits label.
+ *
+ * @type {Object}
+ * @sample {highcharts} highcharts/credits/position-left/ Left aligned
+ * @sample {highcharts} highcharts/credits/position-left/ Left aligned
+ * @sample {highmaps} maps/credits/customized/ Left aligned
+ * @sample {highmaps} maps/credits/customized/ Left aligned
+ * @since 2.1
+ */
+ position: {
+
+ /**
+ * Horizontal alignment of the credits.
+ *
+ * @validvalue ["left", "center", "right"]
+ * @type {String}
+ * @default right
+ */
+ align: 'right',
+
+ /**
+ * Horizontal pixel offset of the credits.
+ *
+ * @type {Number}
+ * @default -10
+ */
+ x: -10,
+
+ /**
+ * Vertical alignment of the credits.
+ *
+ * @validvalue ["top", "middle", "bottom"]
+ * @type {String}
+ * @default bottom
+ */
+ verticalAlign: 'bottom',
+
+ /**
+ * Vertical pixel offset of the credits.
+ *
+ * @type {Number}
+ * @default -5
+ */
+ y: -5
+ },
+
+
+ /**
+ * CSS styles for the credits label.
+ *
+ * @type {CSSObject}
+ * @see In styled mode, credits styles can be set with the
+ * `.highcharts-credits` class.
+ * @default { "cursor": "pointer", "color": "#999999", "fontSize": "10px" }
+ */
+ style: {
+
+ cursor: 'pointer',
+ color: '#999999',
+ fontSize: '9px'
+ },
+
+
+ /**
+ * The text for the credits label.
+ *
+ * @productdesc {highmaps}
+ * If a map is loaded as GeoJSON, the text defaults to `Highcharts @
+ * {map-credits}`. Otherwise, it defaults to `Highcharts.com`.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/credits/href/ Custom URL and text
+ * @sample {highmaps} maps/credits/customized/ Custom URL and text
+ * @default {highcharts|highstock} Highcharts.com
+ */
+ text: 'Highcharts.com'
+ }
+};
+
+/**
+ * Merge the default options with custom options and return the new options
+ * structure. Commonly used for defining reusable templates.
+ *
+ * @function #setOptions
+ * @memberOf Highcharts
+ * @sample highcharts/global/useutc-false Setting a global option
+ * @sample highcharts/members/setoptions Applying a global theme
+ * @param {Object} options The new custom chart options.
+ * @returns {Object} Updated options.
+ */
+H.setOptions = function (options) {
+
+ // Copy in the default options
+ H.defaultOptions = merge(true, H.defaultOptions, options);
+
+ // Re-initiate time
+ H.time.init();
+
+ return H.defaultOptions;
+};
+
+/**
+ * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules
+ * wasn't enough because the setOptions method created a new object.
+ */
+H.getOptions = function () {
+ return H.defaultOptions;
+};
+
+
+// Series defaults
+H.defaultPlotOptions = H.defaultOptions.plotOptions;
+
+
+// Time utilities
+H.time = new H.Time();
+
+/**
+ * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) into a
+ * human readable date string. The format is a subset of the formats for PHP's
+ * [strftime]{@link
+ * http://www.php.net/manual/en/function.strftime.php} function. Additional
+ * formats can be given in the {@link Highcharts.dateFormats} hook.
+ *
+ * Since v6.0.5, all internal dates are formatted through the
+ * [Chart.time](Chart#time) instance to respect chart-level time settings. The
+ * `Highcharts.dateFormat` function only reflects global time settings set with
+ * `setOptions`.
+ *
+ * @function #dateFormat
+ * @memberOf Highcharts
+ * @param {String} format - The desired format where various time
+ * representations are prefixed with %.
+ * @param {Number} timestamp - The JavaScript timestamp.
+ * @param {Boolean} [capitalize=false] - Upper case first letter in the return.
+ * @returns {String} The formatted date.
+ */
+H.dateFormat = function (format, timestamp, capitalize) {
+ return H.time.dateFormat(format, timestamp, capitalize);
+};
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var correctFloat = H.correctFloat,
+ defined = H.defined,
+ destroyObjectProperties = H.destroyObjectProperties,
+ isNumber = H.isNumber,
+ merge = H.merge,
+ pick = H.pick,
+ deg2rad = H.deg2rad;
+
+/**
+ * The Tick class
+ */
+H.Tick = function (axis, pos, type, noLabel) {
+ this.axis = axis;
+ this.pos = pos;
+ this.type = type || '';
+ this.isNew = true;
+ this.isNewLabel = true;
+
+ if (!type && !noLabel) {
+ this.addLabel();
+ }
+};
+
+H.Tick.prototype = {
+ /**
+ * Write the tick label
+ */
+ addLabel: function () {
+ var tick = this,
+ axis = tick.axis,
+ options = axis.options,
+ chart = axis.chart,
+ categories = axis.categories,
+ names = axis.names,
+ pos = tick.pos,
+ labelOptions = options.labels,
+ str,
+ tickPositions = axis.tickPositions,
+ isFirst = pos === tickPositions[0],
+ isLast = pos === tickPositions[tickPositions.length - 1],
+ value = categories ?
+ pick(categories[pos], names[pos], pos) :
+ pos,
+ label = tick.label,
+ tickPositionInfo = tickPositions.info,
+ dateTimeLabelFormat;
+
+ // Set the datetime label format. If a higher rank is set for this
+ // position, use that. If not, use the general format.
+ if (axis.isDatetimeAxis && tickPositionInfo) {
+ dateTimeLabelFormat =
+ options.dateTimeLabelFormats[
+ tickPositionInfo.higherRanks[pos] ||
+ tickPositionInfo.unitName
+ ];
+ }
+ // set properties for access in render method
+ tick.isFirst = isFirst;
+ tick.isLast = isLast;
+
+ // get the string
+ str = axis.labelFormatter.call({
+ axis: axis,
+ chart: chart,
+ isFirst: isFirst,
+ isLast: isLast,
+ dateTimeLabelFormat: dateTimeLabelFormat,
+ value: axis.isLog ? correctFloat(axis.lin2log(value)) : value,
+ pos: pos
+ });
+
+ // first call
+ if (!defined(label)) {
+
+ tick.label = label =
+ defined(str) && labelOptions.enabled ?
+ chart.renderer.text(
+ str,
+ 0,
+ 0,
+ labelOptions.useHTML
+ )
+
+ // without position absolute, IE export sometimes is
+ // wrong.
+ .css(merge(labelOptions.style))
+
+ .add(axis.labelGroup) :
+ null;
+
+ // Un-rotated length
+ tick.labelLength = label && label.getBBox().width;
+ // Base value to detect change for new calls to getBBox
+ tick.rotation = 0;
+
+ // update
+ } else if (label) {
+ label.attr({ text: str });
+ }
+ },
+
+ /**
+ * Get the offset height or width of the label
+ */
+ getLabelSize: function () {
+ return this.label ?
+ this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] :
+ 0;
+ },
+
+ /**
+ * Handle the label overflow by adjusting the labels to the left and right
+ * edge, or hide them if they collide into the neighbour label.
+ */
+ handleOverflow: function (xy) {
+ var axis = this.axis,
+ labelOptions = axis.options.labels,
+ pxPos = xy.x,
+ chartWidth = axis.chart.chartWidth,
+ spacing = axis.chart.spacing,
+ leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])),
+ rightBound = pick(
+ axis.labelRight,
+ Math.max(
+ !axis.isRadial ? axis.pos + axis.len : 0,
+ chartWidth - spacing[1]
+ )
+ ),
+ label = this.label,
+ rotation = this.rotation,
+ factor = { left: 0, center: 0.5, right: 1 }[
+ axis.labelAlign || label.attr('align')
+ ],
+ labelWidth = label.getBBox().width,
+ slotWidth = axis.getSlotWidth(),
+ modifiedSlotWidth = slotWidth,
+ xCorrection = factor,
+ goRight = 1,
+ leftPos,
+ rightPos,
+ textWidth,
+ css = {};
+
+ // Check if the label overshoots the chart spacing box. If it does, move
+ // it. If it now overshoots the slotWidth, add ellipsis.
+ if (!rotation && labelOptions.overflow !== false) {
+ leftPos = pxPos - factor * labelWidth;
+ rightPos = pxPos + (1 - factor) * labelWidth;
+
+ if (leftPos < leftBound) {
+ modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
+ } else if (rightPos > rightBound) {
+ modifiedSlotWidth =
+ rightBound - xy.x + modifiedSlotWidth * factor;
+ goRight = -1;
+ }
+
+ modifiedSlotWidth = Math.min(slotWidth, modifiedSlotWidth); // #4177
+ if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') {
+ xy.x += (
+ goRight *
+ (
+ slotWidth -
+ modifiedSlotWidth -
+ xCorrection * (
+ slotWidth - Math.min(labelWidth, modifiedSlotWidth)
+ )
+ )
+ );
+ }
+ // If the label width exceeds the available space, set a text width
+ // to be picked up below. Also, if a width has been set before, we
+ // need to set a new one because the reported labelWidth will be
+ // limited by the box (#3938).
+ if (
+ labelWidth > modifiedSlotWidth ||
+ (axis.autoRotation && (label.styles || {}).width)
+ ) {
+ textWidth = modifiedSlotWidth;
+ }
+
+ // Add ellipsis to prevent rotated labels to be clipped against the edge
+ // of the chart
+ } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) {
+ textWidth = Math.round(
+ pxPos / Math.cos(rotation * deg2rad) - leftBound
+ );
+ } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) {
+ textWidth = Math.round(
+ (chartWidth - pxPos) / Math.cos(rotation * deg2rad)
+ );
+ }
+
+ if (textWidth) {
+ css.width = textWidth;
+ if (!(labelOptions.style || {}).textOverflow) {
+ css.textOverflow = 'ellipsis';
+ }
+ label.css(css);
+ }
+ },
+
+ /**
+ * Get the x and y position for ticks and labels
+ */
+ getPosition: function (horiz, pos, tickmarkOffset, old) {
+ var axis = this.axis,
+ chart = axis.chart,
+ cHeight = (old && chart.oldChartHeight) || chart.chartHeight;
+
+ return {
+ x: horiz ?
+ (
+ axis.translate(pos + tickmarkOffset, null, null, old) +
+ axis.transB
+ ) :
+ (
+ axis.left +
+ axis.offset +
+ (
+ axis.opposite ?
+ (
+ (
+ (old && chart.oldChartWidth) ||
+ chart.chartWidth
+ ) -
+ axis.right -
+ axis.left
+ ) :
+ 0
+ )
+ ),
+
+ y: horiz ?
+ (
+ cHeight -
+ axis.bottom +
+ axis.offset -
+ (axis.opposite ? axis.height : 0)
+ ) :
+ (
+ cHeight -
+ axis.translate(pos + tickmarkOffset, null, null, old) -
+ axis.transB
+ )
+ };
+
+ },
+
+ /**
+ * Get the x, y position of the tick label
+ */
+ getLabelPosition: function (
+ x,
+ y,
+ label,
+ horiz,
+ labelOptions,
+ tickmarkOffset,
+ index,
+ step
+ ) {
+ var axis = this.axis,
+ transA = axis.transA,
+ reversed = axis.reversed,
+ staggerLines = axis.staggerLines,
+ rotCorr = axis.tickRotCorr || { x: 0, y: 0 },
+ yOffset = labelOptions.y,
+
+ // Adjust for label alignment if we use reserveSpace: true (#5286)
+ labelOffsetCorrection = (
+ !horiz && !axis.reserveSpaceDefault ?
+ -axis.labelOffset * (
+ axis.labelAlign === 'center' ? 0.5 : 1
+ ) :
+ 0
+ ),
+ line;
+
+ if (!defined(yOffset)) {
+ if (axis.side === 0) {
+ yOffset = label.rotation ? -8 : -label.getBBox().height;
+ } else if (axis.side === 2) {
+ yOffset = rotCorr.y + 8;
+ } else {
+ // #3140, #3140
+ yOffset = Math.cos(label.rotation * deg2rad) *
+ (rotCorr.y - label.getBBox(false, 0).height / 2);
+ }
+ }
+
+ x = x +
+ labelOptions.x +
+ labelOffsetCorrection +
+ rotCorr.x -
+ (
+ tickmarkOffset && horiz ?
+ tickmarkOffset * transA * (reversed ? -1 : 1) :
+ 0
+ );
+ y = y + yOffset - (tickmarkOffset && !horiz ?
+ tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
+
+ // Correct for staggered labels
+ if (staggerLines) {
+ line = (index / (step || 1) % staggerLines);
+ if (axis.opposite) {
+ line = staggerLines - line - 1;
+ }
+ y += line * (axis.labelOffset / staggerLines);
+ }
+
+ return {
+ x: x,
+ y: Math.round(y)
+ };
+ },
+
+ /**
+ * Extendible method to return the path of the marker
+ */
+ getMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {
+ return renderer.crispLine([
+ 'M',
+ x,
+ y,
+ 'L',
+ x + (horiz ? 0 : -tickLength),
+ y + (horiz ? tickLength : 0)
+ ], tickWidth);
+ },
+
+ /**
+ * Renders the gridLine.
+ * @param {Boolean} old Whether or not the tick is old
+ * @param {number} opacity The opacity of the grid line
+ * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
+ * @return {undefined}
+ */
+ renderGridLine: function (old, opacity, reverseCrisp) {
+ var tick = this,
+ axis = tick.axis,
+ options = axis.options,
+ gridLine = tick.gridLine,
+ gridLinePath,
+ attribs = {},
+ pos = tick.pos,
+ type = tick.type,
+ tickmarkOffset = axis.tickmarkOffset,
+ renderer = axis.chart.renderer;
+
+
+ var gridPrefix = type ? type + 'Grid' : 'grid',
+ gridLineWidth = options[gridPrefix + 'LineWidth'],
+ gridLineColor = options[gridPrefix + 'LineColor'],
+ dashStyle = options[gridPrefix + 'LineDashStyle'];
+
+
+ if (!gridLine) {
+
+ attribs.stroke = gridLineColor;
+ attribs['stroke-width'] = gridLineWidth;
+ if (dashStyle) {
+ attribs.dashstyle = dashStyle;
+ }
+
+ if (!type) {
+ attribs.zIndex = 1;
+ }
+ if (old) {
+ attribs.opacity = 0;
+ }
+ tick.gridLine = gridLine = renderer.path()
+ .attr(attribs)
+ .addClass(
+ 'highcharts-' + (type ? type + '-' : '') + 'grid-line'
+ )
+ .add(axis.gridGroup);
+ }
+
+ // If the parameter 'old' is set, the current call will be followed
+ // by another call, therefore do not do any animations this time
+ if (!old && gridLine) {
+ gridLinePath = axis.getPlotLinePath(
+ pos + tickmarkOffset,
+ gridLine.strokeWidth() * reverseCrisp,
+ old, true
+ );
+ if (gridLinePath) {
+ gridLine[tick.isNew ? 'attr' : 'animate']({
+ d: gridLinePath,
+ opacity: opacity
+ });
+ }
+ }
+ },
+
+ /**
+ * Renders the tick mark.
+ * @param {Object} xy The position vector of the mark
+ * @param {number} xy.x The x position of the mark
+ * @param {number} xy.y The y position of the mark
+ * @param {number} opacity The opacity of the mark
+ * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
+ * @return {undefined}
+ */
+ renderMark: function (xy, opacity, reverseCrisp) {
+ var tick = this,
+ axis = tick.axis,
+ options = axis.options,
+ renderer = axis.chart.renderer,
+ type = tick.type,
+ tickPrefix = type ? type + 'Tick' : 'tick',
+ tickSize = axis.tickSize(tickPrefix),
+ mark = tick.mark,
+ isNewMark = !mark,
+ x = xy.x,
+ y = xy.y;
+
+
+ var tickWidth = pick(
+ options[tickPrefix + 'Width'],
+ !type && axis.isXAxis ? 1 : 0
+ ), // X axis defaults to 1
+ tickColor = options[tickPrefix + 'Color'];
+
+
+ if (tickSize) {
+
+ // negate the length
+ if (axis.opposite) {
+ tickSize[0] = -tickSize[0];
+ }
+
+ // First time, create it
+ if (isNewMark) {
+ tick.mark = mark = renderer.path()
+ .addClass('highcharts-' + (type ? type + '-' : '') + 'tick')
+ .add(axis.axisGroup);
+
+
+ mark.attr({
+ stroke: tickColor,
+ 'stroke-width': tickWidth
+ });
+
+ }
+ mark[isNewMark ? 'attr' : 'animate']({
+ d: tick.getMarkPath(
+ x,
+ y,
+ tickSize[0],
+ mark.strokeWidth() * reverseCrisp,
+ axis.horiz,
+ renderer),
+ opacity: opacity
+ });
+
+ }
+ },
+
+ /**
+ * Renders the tick label.
+ * Note: The label should already be created in init(), so it should only
+ * have to be moved into place.
+ * @param {Object} xy The position vector of the label
+ * @param {number} xy.x The x position of the label
+ * @param {number} xy.y The y position of the label
+ * @param {Boolean} old Whether or not the tick is old
+ * @param {number} opacity The opacity of the label
+ * @param {number} index The index of the tick
+ * @return {undefined}
+ */
+ renderLabel: function (xy, old, opacity, index) {
+ var tick = this,
+ axis = tick.axis,
+ horiz = axis.horiz,
+ options = axis.options,
+ label = tick.label,
+ labelOptions = options.labels,
+ step = labelOptions.step,
+ tickmarkOffset = axis.tickmarkOffset,
+ show = true,
+ x = xy.x,
+ y = xy.y;
+ if (label && isNumber(x)) {
+ label.xy = xy = tick.getLabelPosition(
+ x,
+ y,
+ label,
+ horiz,
+ labelOptions,
+ tickmarkOffset,
+ index,
+ step
+ );
+
+ // Apply show first and show last. If the tick is both first and
+ // last, it is a single centered tick, in which case we show the
+ // label anyway (#2100).
+ if (
+ (
+ tick.isFirst &&
+ !tick.isLast &&
+ !pick(options.showFirstLabel, 1)
+ ) ||
+ (
+ tick.isLast &&
+ !tick.isFirst &&
+ !pick(options.showLastLabel, 1)
+ )
+ ) {
+ show = false;
+
+ // Handle label overflow and show or hide accordingly
+ } else if (
+ horiz &&
+ !labelOptions.step &&
+ !labelOptions.rotation &&
+ !old &&
+ opacity !== 0
+ ) {
+ tick.handleOverflow(xy);
+ }
+
+ // apply step
+ if (step && index % step) {
+ // show those indices dividable by step
+ show = false;
+ }
+
+ // Set the new position, and show or hide
+ if (show && isNumber(xy.y)) {
+ xy.opacity = opacity;
+ label[tick.isNewLabel ? 'attr' : 'animate'](xy);
+ tick.isNewLabel = false;
+ } else {
+ label.attr('y', -9999); // #1338
+ tick.isNewLabel = true;
+ }
+ }
+ },
+
+ /**
+ * Put everything in place
+ *
+ * @param index {Number}
+ * @param old {Boolean} Use old coordinates to prepare an animation into new
+ * position
+ */
+ render: function (index, old, opacity) {
+ var tick = this,
+ axis = tick.axis,
+ horiz = axis.horiz,
+ pos = tick.pos,
+ tickmarkOffset = axis.tickmarkOffset,
+ xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
+ x = xy.x,
+ y = xy.y,
+ reverseCrisp = ((horiz && x === axis.pos + axis.len) ||
+ (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687
+
+ opacity = pick(opacity, 1);
+ this.isActive = true;
+
+ // Create the grid line
+ this.renderGridLine(old, opacity, reverseCrisp);
+
+ // create the tick mark
+ this.renderMark(xy, opacity, reverseCrisp);
+
+ // the label is created on init - now move it into place
+ this.renderLabel(xy, old, opacity, index);
+
+ tick.isNew = false;
+ },
+
+ /**
+ * Destructor for the tick prototype
+ */
+ destroy: function () {
+ destroyObjectProperties(this, this.axis);
+ }
+};
+
+}(Highcharts));
+var Axis = (function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+var addEvent = H.addEvent,
+ animObject = H.animObject,
+ arrayMax = H.arrayMax,
+ arrayMin = H.arrayMin,
+ color = H.color,
+ correctFloat = H.correctFloat,
+ defaultOptions = H.defaultOptions,
+ defined = H.defined,
+ deg2rad = H.deg2rad,
+ destroyObjectProperties = H.destroyObjectProperties,
+ each = H.each,
+ extend = H.extend,
+ fireEvent = H.fireEvent,
+ format = H.format,
+ getMagnitude = H.getMagnitude,
+ grep = H.grep,
+ inArray = H.inArray,
+ isArray = H.isArray,
+ isNumber = H.isNumber,
+ isString = H.isString,
+ merge = H.merge,
+ normalizeTickInterval = H.normalizeTickInterval,
+ objectEach = H.objectEach,
+ pick = H.pick,
+ removeEvent = H.removeEvent,
+ splat = H.splat,
+ syncTimeout = H.syncTimeout,
+ Tick = H.Tick;
+
+/**
+ * Create a new axis object. Called internally when instanciating a new chart or
+ * adding axes by {@link Highcharts.Chart#addAxis}.
+ *
+ * A chart can have from 0 axes (pie chart) to multiples. In a normal, single
+ * series cartesian chart, there is one X axis and one Y axis.
+ *
+ * The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is
+ * an array of Axis objects. If there is only one axis, it can be referenced
+ * through `chart.xAxis[0]`, and multiple axes have increasing indices. The same
+ * pattern goes for Y axes.
+ *
+ * If you need to get the axes from a series object, use the `series.xAxis` and
+ * `series.yAxis` properties. These are not arrays, as one series can only be
+ * associated to one X and one Y axis.
+ *
+ * A third way to reference the axis programmatically is by `id`. Add an `id` in
+ * the axis configuration options, and get the axis by
+ * {@link Highcharts.Chart#get}.
+ *
+ * Configuration options for the axes are given in options.xAxis and
+ * options.yAxis.
+ *
+ * @class Highcharts.Axis
+ * @memberOf Highcharts
+ * @param {Highcharts.Chart} chart - The Chart instance to apply the axis on.
+ * @param {Object} options - Axis options
+ */
+var Axis = function () {
+ this.init.apply(this, arguments);
+};
+
+H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */{
+
+ /**
+ * The X axis or category axis. Normally this is the horizontal axis,
+ * though if the chart is inverted this is the vertical axis. In case of
+ * multiple axes, the xAxis node is an array of configuration objects.
+ *
+ * See [the Axis object](#Axis) for programmatic access to the axis.
+ *
+ * @productdesc {highmaps}
+ * In Highmaps, the axis is hidden, but it is used behind the scenes to
+ * control features like zooming and panning. Zooming is in effect the same
+ * as setting the extremes of one of the exes.
+ *
+ * @optionparent xAxis
+ */
+ defaultOptions: {
+ /**
+ * Whether to allow decimals in this axis' ticks. When counting
+ * integers, like persons or hits on a web page, decimals should
+ * be avoided in the labels.
+ *
+ * @type {Boolean}
+ * @see [minTickInterval](#xAxis.minTickInterval)
+ * @sample {highcharts|highstock}
+ * highcharts/yaxis/allowdecimals-true/
+ * True by default
+ * @sample {highcharts|highstock}
+ * highcharts/yaxis/allowdecimals-false/
+ * False
+ * @default true
+ * @since 2.0
+ * @apioption xAxis.allowDecimals
+ */
+ // allowDecimals: null,
+
+
+ /**
+ * When using an alternate grid color, a band is painted across the
+ * plot area between every other grid line.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/yaxis/alternategridcolor/
+ * Alternate grid color on the Y axis
+ * @sample {highstock} stock/xaxis/alternategridcolor/
+ * Alternate grid color on the Y axis
+ * @default null
+ * @apioption xAxis.alternateGridColor
+ */
+ // alternateGridColor: null,
+
+ /**
+ * An array defining breaks in the axis, the sections defined will be
+ * left out and all the points shifted closer to each other.
+ *
+ * @productdesc {highcharts}
+ * Requires that the broken-axis.js module is loaded.
+ *
+ * @type {Array}
+ * @sample {highcharts}
+ * highcharts/axisbreak/break-simple/
+ * Simple break
+ * @sample {highcharts|highstock}
+ * highcharts/axisbreak/break-visualized/
+ * Advanced with callback
+ * @sample {highstock}
+ * stock/demo/intraday-breaks/
+ * Break on nights and weekends
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption xAxis.breaks
+ */
+
+ /**
+ * A number indicating how much space should be left between the start
+ * and the end of the break. The break size is given in axis units,
+ * so for instance on a `datetime` axis, a break size of 3600000 would
+ * indicate the equivalent of an hour.
+ *
+ * @type {Number}
+ * @default 0
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption xAxis.breaks.breakSize
+ */
+
+ /**
+ * The point where the break starts.
+ *
+ * @type {Number}
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption xAxis.breaks.from
+ */
+
+ /**
+ * Defines an interval after which the break appears again. By default
+ * the breaks do not repeat.
+ *
+ * @type {Number}
+ * @default 0
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption xAxis.breaks.repeat
+ */
+
+ /**
+ * The point where the break ends.
+ *
+ * @type {Number}
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption xAxis.breaks.to
+ */
+
+ /**
+ * If categories are present for the xAxis, names are used instead of
+ * numbers for that axis. Since Highcharts 3.0, categories can also
+ * be extracted by giving each point a [name](#series.data) and setting
+ * axis [type](#xAxis.type) to `category`. However, if you have multiple
+ * series, best practice remains defining the `categories` array.
+ *
+ * Example:
+ *
+ * categories: ['Apples', 'Bananas', 'Oranges']
+ *
+ * @type {Array}
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * With
+ * @sample {highcharts} highcharts/xaxis/categories/
+ * Without
+ * @product highcharts
+ * @default null
+ * @apioption xAxis.categories
+ */
+ // categories: [],
+
+ /**
+ * The highest allowed value for automatically computed axis extremes.
+ *
+ * @type {Number}
+ * @see [floor](#xAxis.floor)
+ * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/
+ * Floor and ceiling
+ * @since 4.0
+ * @product highcharts highstock
+ * @apioption xAxis.ceiling
+ */
+
+ /**
+ * A class name that opens for styling the axis by CSS, especially in
+ * Highcharts styled mode. The class name is applied to group elements
+ * for the grid, axis elements and labels.
+ *
+ * @type {String}
+ * @sample {highcharts|highstock|highmaps}
+ * highcharts/css/axis/
+ * Multiple axes with separate styling
+ * @since 5.0.0
+ * @apioption xAxis.className
+ */
+
+ /**
+ * Configure a crosshair that follows either the mouse pointer or the
+ * hovered point.
+ *
+ * In styled mode, the crosshairs are styled in the
+ * `.highcharts-crosshair`, `.highcharts-crosshair-thin` or
+ * `.highcharts-xaxis-category` classes.
+ *
+ * @productdesc {highstock}
+ * In Highstock, bu default, the crosshair is enabled on the X axis and
+ * disabled on the Y axis.
+ *
+ * @type {Boolean|Object}
+ * @sample {highcharts} highcharts/xaxis/crosshair-both/
+ * Crosshair on both axes
+ * @sample {highstock} stock/xaxis/crosshairs-xy/
+ * Crosshair on both axes
+ * @sample {highmaps} highcharts/xaxis/crosshair-both/
+ * Crosshair on both axes
+ * @default false
+ * @since 4.1
+ * @apioption xAxis.crosshair
+ */
+
+ /**
+ * A class name for the crosshair, especially as a hook for styling.
+ *
+ * @type {String}
+ * @since 5.0.0
+ * @apioption xAxis.crosshair.className
+ */
+
+ /**
+ * The color of the crosshair. Defaults to `#cccccc` for numeric and
+ * datetime axes, and `rgba(204,214,235,0.25)` for category axes, where
+ * the crosshair by default highlights the whole category.
+ *
+ * @type {Color}
+ * @sample {highcharts|highstock|highmaps}
+ * highcharts/xaxis/crosshair-customized/
+ * Customized crosshairs
+ * @default #cccccc
+ * @since 4.1
+ * @apioption xAxis.crosshair.color
+ */
+
+ /**
+ * The dash style for the crosshair. See
+ * [series.dashStyle](#plotOptions.series.dashStyle)
+ * for possible values.
+ *
+ * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
+ * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
+ * "DashDot", "LongDashDot", "LongDashDotDot"]
+ * @type {String}
+ * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/
+ * Dotted crosshair
+ * @sample {highstock} stock/xaxis/crosshair-dashed/
+ * Dashed X axis crosshair
+ * @default Solid
+ * @since 4.1
+ * @apioption xAxis.crosshair.dashStyle
+ */
+
+ /**
+ * Whether the crosshair should snap to the point or follow the pointer
+ * independent of points.
+ *
+ * @type {Boolean}
+ * @sample {highcharts|highstock}
+ * highcharts/xaxis/crosshair-snap-false/
+ * True by default
+ * @sample {highmaps}
+ * maps/demo/latlon-advanced/
+ * Snap is false
+ * @default true
+ * @since 4.1
+ * @apioption xAxis.crosshair.snap
+ */
+
+ /**
+ * The pixel width of the crosshair. Defaults to 1 for numeric or
+ * datetime axes, and for one category width for category axes.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/xaxis/crosshair-customized/
+ * Customized crosshairs
+ * @sample {highstock} highcharts/xaxis/crosshair-customized/
+ * Customized crosshairs
+ * @sample {highmaps} highcharts/xaxis/crosshair-customized/
+ * Customized crosshairs
+ * @default 1
+ * @since 4.1
+ * @apioption xAxis.crosshair.width
+ */
+
+ /**
+ * The Z index of the crosshair. Higher Z indices allow drawing the
+ * crosshair on top of the series or behind the grid lines.
+ *
+ * @type {Number}
+ * @default 2
+ * @since 4.1
+ * @apioption xAxis.crosshair.zIndex
+ */
+
+ /**
+ * For a datetime axis, the scale will automatically adjust to the
+ * appropriate unit. This member gives the default string
+ * representations used for each unit. For intermediate values,
+ * different units may be used, for example the `day` unit can be used
+ * on midnight and `hour` unit be used for intermediate values on the
+ * same axis. For an overview of the replacement codes, see
+ * [dateFormat](#Highcharts.dateFormat). Defaults to:
+ *
+ * {
+ * millisecond: '%H:%M:%S.%L',
+ * second: '%H:%M:%S',
+ * minute: '%H:%M',
+ * hour: '%H:%M',
+ * day: '%e. %b',
+ * week: '%e. %b',
+ * month: '%b \'%y',
+ * year: '%Y'
+ * }
+ *
+ * @type {Object}
+ * @sample {highcharts} highcharts/xaxis/datetimelabelformats/
+ * Different day format on X axis
+ * @sample {highstock} stock/xaxis/datetimelabelformats/
+ * More information in x axis labels
+ * @product highcharts highstock
+ */
+ dateTimeLabelFormats: {
+ millisecond: '%H:%M:%S.%L',
+ second: '%H:%M:%S',
+ minute: '%H:%M',
+ hour: '%H:%M',
+ day: '%e. %b',
+ week: '%e. %b',
+ month: '%b \'%y',
+ year: '%Y'
+ },
+
+ /**
+ * _Requires Accessibility module_
+ *
+ * Description of the axis to screen reader users.
+ *
+ * @type {String}
+ * @default undefined
+ * @since 5.0.0
+ * @apioption xAxis.description
+ */
+
+ /**
+ * Whether to force the axis to end on a tick. Use this option with
+ * the `maxPadding` option to control the axis end.
+ *
+ * @productdesc {highstock}
+ * In Highstock, `endOnTick` is always false when the navigator is
+ * enabled, to prevent jumpy scrolling.
+ *
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * True by default
+ * @sample {highcharts} highcharts/yaxis/endontick/
+ * False
+ * @sample {highstock} stock/demo/basic-line/
+ * True by default
+ * @sample {highstock} stock/xaxis/endontick/
+ * False
+ * @since 1.2.0
+ */
+ endOnTick: false,
+
+ /**
+ * Event handlers for the axis.
+ *
+ * @apioption xAxis.events
+ */
+
+ /**
+ * An event fired after the breaks have rendered.
+ *
+ * @type {Function}
+ * @see [breaks](#xAxis.breaks)
+ * @sample {highcharts} highcharts/axisbreak/break-event/
+ * AfterBreak Event
+ * @since 4.1.0
+ * @product highcharts
+ * @apioption xAxis.events.afterBreaks
+ */
+
+ /**
+ * As opposed to the `setExtremes` event, this event fires after the
+ * final min and max values are computed and corrected for `minRange`.
+ *
+ *
+ * Fires when the minimum and maximum is set for the axis, either by
+ * calling the `.setExtremes()` method or by selecting an area in the
+ * chart. One parameter, `event`, is passed to the function, containing
+ * common event information.
+ *
+ * The new user set minimum and maximum values can be found by `event.
+ * min` and `event.max`. These reflect the axis minimum and maximum
+ * in axis values. The actual data extremes are found in `event.dataMin`
+ * and `event.dataMax`.
+ *
+ * @type {Function}
+ * @context Axis
+ * @since 2.3
+ * @apioption xAxis.events.afterSetExtremes
+ */
+
+ /**
+ * An event fired when a break from this axis occurs on a point.
+ *
+ * @type {Function}
+ * @see [breaks](#xAxis.breaks)
+ * @context Axis
+ * @sample {highcharts} highcharts/axisbreak/break-visualized/
+ * Visualization of a Break
+ * @since 4.1.0
+ * @product highcharts
+ * @apioption xAxis.events.pointBreak
+ */
+
+ /**
+ * An event fired when a point falls inside a break from this axis.
+ *
+ * @type {Function}
+ * @context Axis
+ * @product highcharts highstock
+ * @apioption xAxis.events.pointInBreak
+ */
+
+ /**
+ * Fires when the minimum and maximum is set for the axis, either by
+ * calling the `.setExtremes()` method or by selecting an area in the
+ * chart. One parameter, `event`, is passed to the function,
+ * containing common event information.
+ *
+ * The new user set minimum and maximum values can be found by `event.
+ * min` and `event.max`. These reflect the axis minimum and maximum
+ * in data values. When an axis is zoomed all the way out from the
+ * "Reset zoom" button, `event.min` and `event.max` are null, and
+ * the new extremes are set based on `this.dataMin` and `this.dataMax`.
+ *
+ * @type {Function}
+ * @context Axis
+ * @sample {highstock} stock/xaxis/events-setextremes/
+ * Log new extremes on x axis
+ * @since 1.2.0
+ * @apioption xAxis.events.setExtremes
+ */
+
+ /**
+ * The lowest allowed value for automatically computed axis extremes.
+ *
+ * @type {Number}
+ * @see [ceiling](#yAxis.ceiling)
+ * @sample {highcharts} highcharts/yaxis/floor-ceiling/
+ * Floor and ceiling
+ * @sample {highstock} stock/demo/lazy-loading/
+ * Prevent negative stock price on Y axis
+ * @default null
+ * @since 4.0
+ * @product highcharts highstock
+ * @apioption xAxis.floor
+ */
+
+ /**
+ * The dash or dot style of the grid lines. For possible values, see
+ * [this demonstration](http://jsfiddle.net/gh/get/library/pure/
+ *highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
+ *series-dashstyle-all/).
+ *
+ * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
+ * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
+ * "DashDot", "LongDashDot", "LongDashDotDot"]
+ * @type {String}
+ * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/
+ * Long dashes
+ * @sample {highstock} stock/xaxis/gridlinedashstyle/
+ * Long dashes
+ * @default Solid
+ * @since 1.2
+ * @apioption xAxis.gridLineDashStyle
+ */
+
+ /**
+ * The Z index of the grid lines.
+ *
+ * @type {Number}
+ * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/
+ * A Z index of 4 renders the grid above the graph
+ * @default 1
+ * @product highcharts highstock
+ * @apioption xAxis.gridZIndex
+ */
+
+ /**
+ * An id for the axis. This can be used after render time to get
+ * a pointer to the axis object through `chart.get()`.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/xaxis/id/
+ * Get the object
+ * @sample {highstock} stock/xaxis/id/
+ * Get the object
+ * @default null
+ * @since 1.2.0
+ * @apioption xAxis.id
+ */
+
+ /**
+ * The axis labels show the number or category for each tick.
+ *
+ * @productdesc {highmaps}
+ * X and Y axis labels are by default disabled in Highmaps, but the
+ * functionality is inherited from Highcharts and used on `colorAxis`,
+ * and can be enabled on X and Y axes too.
+ */
+ labels: {
+ /**
+ * What part of the string the given position is anchored to.
+ * If `left`, the left side of the string is at the axis position.
+ * Can be one of `"left"`, `"center"` or `"right"`. Defaults to
+ * an intelligent guess based on which side of the chart the axis
+ * is on and the rotation of the label.
+ *
+ * @validvalue ["left", "center", "right"]
+ * @type {String}
+ * @sample {highcharts} highcharts/xaxis/labels-align-left/
+ * Left
+ * @sample {highcharts} highcharts/xaxis/labels-align-right/
+ * Right
+ * @sample {highcharts}
+ * highcharts/xaxis/labels-reservespace-true/
+ * Left-aligned labels on a vertical category axis
+ * @see [reserveSpace](#xAxis.labels.reserveSpace)
+ * @apioption xAxis.labels.align
+ */
+ // align: 'center',
+
+ /**
+ * For horizontal axes, the allowed degrees of label rotation
+ * to prevent overlapping labels. If there is enough space,
+ * labels are not rotated. As the chart gets narrower, it
+ * will start rotating the labels -45 degrees, then remove
+ * every second label and try again with rotations 0 and -45 etc.
+ * Set it to `false` to disable rotation, which will
+ * cause the labels to word-wrap if possible.
+ *
+ * @type {Array}
+ * @sample {highcharts|highstock}
+ * highcharts/xaxis/labels-autorotation-default/
+ * Default auto rotation of 0 or -45
+ * @sample {highcharts|highstock}
+ * highcharts/xaxis/labels-autorotation-0-90/
+ * Custom graded auto rotation
+ * @default [-45]
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption xAxis.labels.autoRotation
+ */
+
+ /**
+ * When each category width is more than this many pixels, we don't
+ * apply auto rotation. Instead, we lay out the axis label with word
+ * wrap. A lower limit makes sense when the label contains multiple
+ * short words that don't extend the available horizontal space for
+ * each label.
+ *
+ * @type {Number}
+ * @sample {highcharts}
+ * highcharts/xaxis/labels-autorotationlimit/
+ * Lower limit
+ * @default 80
+ * @since 4.1.5
+ * @product highcharts
+ * @apioption xAxis.labels.autoRotationLimit
+ */
+
+ /**
+ * Polar charts only. The label's pixel distance from the perimeter
+ * of the plot area.
+ *
+ * @type {Number}
+ * @default 15
+ * @product highcharts
+ * @apioption xAxis.labels.distance
+ */
+
+ /**
+ * Enable or disable the axis labels.
+ *
+ * @sample {highcharts} highcharts/xaxis/labels-enabled/
+ * X axis labels disabled
+ * @sample {highstock} stock/xaxis/labels-enabled/
+ * X axis labels disabled
+ * @default {highcharts|highstock} true
+ * @default {highmaps} false
+ */
+ enabled: true,
+
+ /**
+ * A [format string](http://www.highcharts.com/docs/chart-
+ * concepts/labels-and-string-formatting) for the axis label.
+ *
+ * @type {String}
+ * @sample {highcharts|highstock} highcharts/yaxis/labels-format/
+ * Add units to Y axis label
+ * @default {value}
+ * @since 3.0
+ * @apioption xAxis.labels.format
+ */
+
+ /**
+ * Callback JavaScript function to format the label. The value
+ * is given by `this.value`. Additional properties for `this` are
+ * `axis`, `chart`, `isFirst` and `isLast`. The value of the default
+ * label formatter can be retrieved by calling
+ * `this.axis.defaultLabelFormatter.call(this)` within the function.
+ *
+ * Defaults to:
+ *
+ * function() {
+ * return this.value;
+ * }
+ *
+ * @type {Function}
+ * @sample {highcharts}
+ * highcharts/xaxis/labels-formatter-linked/
+ * Linked category names
+ * @sample {highcharts}
+ * highcharts/xaxis/labels-formatter-extended/
+ * Modified numeric labels
+ * @sample {highstock}
+ * stock/xaxis/labels-formatter/
+ * Added units on Y axis
+ * @apioption xAxis.labels.formatter
+ */
+
+ /**
+ * How to handle overflowing labels on horizontal axis. Can be
+ * undefined, `false` or `"justify"`. By default it aligns inside
+ * the chart area. If "justify", labels will not render outside
+ * the plot area. If `false`, it will not be aligned at all.
+ * If there is room to move it, it will be aligned to the edge,
+ * else it will be removed.
+ *
+ * @deprecated
+ * @validvalue [null, "justify"]
+ * @type {String}
+ * @since 2.2.5
+ * @apioption xAxis.labels.overflow
+ */
+
+ /**
+ * The pixel padding for axis labels, to ensure white space between
+ * them.
+ *
+ * @type {Number}
+ * @default 5
+ * @product highcharts
+ * @apioption xAxis.labels.padding
+ */
+
+ /**
+ * Whether to reserve space for the labels. By default, space is
+ * reserved for the labels in these cases:
+ *
+ * * On all horizontal axes.
+ * * On vertical axes if `label.align` is `right` on a left-side
+ * axis or `left` on a right-side axis.
+ * * On vertical axes if `label.align` is `center`.
+ *
+ * This can be turned off when for example the labels are rendered
+ * inside the plot area instead of outside.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/xaxis/labels-reservespace/
+ * No reserved space, labels inside plot
+ * @sample {highcharts}
+ * highcharts/xaxis/labels-reservespace-true/
+ * Left-aligned labels on a vertical category axis
+ * @see [labels.align](#xAxis.labels.align)
+ * @default null
+ * @since 4.1.10
+ * @product highcharts
+ * @apioption xAxis.labels.reserveSpace
+ */
+
+ /**
+ * Rotation of the labels in degrees.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/xaxis/labels-rotation/
+ * X axis labels rotated 90°
+ * @default 0
+ * @apioption xAxis.labels.rotation
+ */
+ // rotation: 0,
+
+ /**
+ * Horizontal axes only. The number of lines to spread the labels
+ * over to make room or tighter labels.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/xaxis/labels-staggerlines/
+ * Show labels over two lines
+ * @sample {highstock} stock/xaxis/labels-staggerlines/
+ * Show labels over two lines
+ * @default null
+ * @since 2.1
+ * @apioption xAxis.labels.staggerLines
+ */
+
+ /**
+ * To show only every _n_'th label on the axis, set the step to _n_.
+ * Setting the step to 2 shows every other label.
+ *
+ * By default, the step is calculated automatically to avoid
+ * overlap. To prevent this, set it to 1\. This usually only
+ * happens on a category axis, and is often a sign that you have
+ * chosen the wrong axis type.
+ *
+ * Read more at
+ * [Axis docs](http://www.highcharts.com/docs/chart-concepts/axes)
+ * => What axis should I use?
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/xaxis/labels-step/
+ * Showing only every other axis label on a categorized
+ * x axis
+ * @sample {highcharts} highcharts/xaxis/labels-step-auto/
+ * Auto steps on a category axis
+ * @default null
+ * @since 2.1
+ * @apioption xAxis.labels.step
+ */
+ // step: null,
+
+
+
+ /**
+ * CSS styles for the label. Use `whiteSpace: 'nowrap'` to prevent
+ * wrapping of category labels. Use `textOverflow: 'none'` to
+ * prevent ellipsis (dots).
+ *
+ * In styled mode, the labels are styled with the
+ * `.highcharts-axis-labels` class.
+ *
+ * @type {CSSObject}
+ * @sample {highcharts} highcharts/xaxis/labels-style/
+ * Red X axis labels
+ */
+ style: {
+ color: '#666666',
+ cursor: 'default',
+ fontSize: '11px'
+ },
+
+
+ /**
+ * Whether to [use HTML](http://www.highcharts.com/docs/chart-
+ * concepts/labels-and-string-formatting#html) to render the labels.
+ *
+ * @type {Boolean}
+ * @default false
+ * @apioption xAxis.labels.useHTML
+ */
+
+ /**
+ * The x position offset of the label relative to the tick position
+ * on the axis.
+ *
+ * @sample {highcharts} highcharts/xaxis/labels-x/
+ * Y axis labels placed on grid lines
+ */
+ x: 0
+
+ /**
+ * The y position offset of the label relative to the tick position
+ * on the axis. The default makes it adapt to the font size on
+ * bottom axis.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/xaxis/labels-x/
+ * Y axis labels placed on grid lines
+ * @default null
+ * @apioption xAxis.labels.y
+ */
+
+ /**
+ * The Z index for the axis labels.
+ *
+ * @type {Number}
+ * @default 7
+ * @apioption xAxis.labels.zIndex
+ */
+ },
+
+ /**
+ * Index of another axis that this axis is linked to. When an axis is
+ * linked to a master axis, it will take the same extremes as
+ * the master, but as assigned by min or max or by setExtremes.
+ * It can be used to show additional info, or to ease reading the
+ * chart by duplicating the scales.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/xaxis/linkedto/
+ * Different string formats of the same date
+ * @sample {highcharts} highcharts/yaxis/linkedto/
+ * Y values on both sides
+ * @default null
+ * @since 2.0.2
+ * @product highcharts highstock
+ * @apioption xAxis.linkedTo
+ */
+
+ /**
+ * The maximum value of the axis. If `null`, the max value is
+ * automatically calculated.
+ *
+ * If the `endOnTick` option is true, the `max` value might
+ * be rounded up.
+ *
+ * If a [tickAmount](#yAxis.tickAmount) is set, the axis may be extended
+ * beyond the set max in order to reach the given number of ticks. The
+ * same may happen in a chart with multiple axes, determined by [chart.
+ * alignTicks](#chart), where a `tickAmount` is applied internally.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/yaxis/max-200/
+ * Y axis max of 200
+ * @sample {highcharts} highcharts/yaxis/max-logarithmic/
+ * Y axis max on logarithmic axis
+ * @sample {highstock} stock/xaxis/min-max/
+ * Fixed min and max on X axis
+ * @sample {highmaps} maps/axis/min-max/
+ * Pre-zoomed to a specific area
+ * @apioption xAxis.max
+ */
+
+ /**
+ * Padding of the max value relative to the length of the axis. A
+ * padding of 0.05 will make a 100px axis 5px longer. This is useful
+ * when you don't want the highest data value to appear on the edge
+ * of the plot area. When the axis' `max` option is set or a max extreme
+ * is set using `axis.setExtremes()`, the maxPadding will be ignored.
+ *
+ * @sample {highcharts} highcharts/yaxis/maxpadding/
+ * Max padding of 0.25 on y axis
+ * @sample {highstock} stock/xaxis/minpadding-maxpadding/
+ * Greater min- and maxPadding
+ * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
+ * Add some padding
+ * @default {highcharts} 0.01
+ * @default {highstock|highmaps} 0
+ * @since 1.2.0
+ */
+ maxPadding: 0.01,
+
+ /**
+ * Deprecated. Use `minRange` instead.
+ *
+ * @deprecated
+ * @type {Number}
+ * @product highcharts highstock
+ * @apioption xAxis.maxZoom
+ */
+
+ /**
+ * The minimum value of the axis. If `null` the min value is
+ * automatically calculated.
+ *
+ * If the `startOnTick` option is true (default), the `min` value might
+ * be rounded down.
+ *
+ * The automatically calculated minimum value is also affected by
+ * [floor](#yAxis.floor), [softMin](#yAxis.softMin),
+ * [minPadding](#yAxis.minPadding), [minRange](#yAxis.minRange)
+ * as well as [series.threshold](#plotOptions.series.threshold)
+ * and [series.softThreshold](#plotOptions.series.softThreshold).
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/yaxis/min-startontick-false/
+ * -50 with startOnTick to false
+ * @sample {highcharts} highcharts/yaxis/min-startontick-true/
+ * -50 with startOnTick true by default
+ * @sample {highstock} stock/xaxis/min-max/
+ * Set min and max on X axis
+ * @sample {highmaps} maps/axis/min-max/
+ * Pre-zoomed to a specific area
+ * @apioption xAxis.min
+ */
+
+ /**
+ * The dash or dot style of the minor grid lines. For possible values,
+ * see [this demonstration](http://jsfiddle.net/gh/get/library/pure/
+ * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
+ * series-dashstyle-all/).
+ *
+ * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
+ * "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
+ * "DashDot", "LongDashDot", "LongDashDotDot"]
+ * @type {String}
+ * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/
+ * Long dashes on minor grid lines
+ * @sample {highstock} stock/xaxis/minorgridlinedashstyle/
+ * Long dashes on minor grid lines
+ * @default Solid
+ * @since 1.2
+ * @apioption xAxis.minorGridLineDashStyle
+ */
+
+ /**
+ * Specific tick interval in axis units for the minor ticks.
+ * On a linear axis, if `"auto"`, the minor tick interval is
+ * calculated as a fifth of the tickInterval. If `null`, minor
+ * ticks are not shown.
+ *
+ * On logarithmic axes, the unit is the power of the value. For example,
+ * setting the minorTickInterval to 1 puts one tick on each of 0.1,
+ * 1, 10, 100 etc. Setting the minorTickInterval to 0.1 produces 9
+ * ticks between 1 and 10, 10 and 100 etc.
+ *
+ * If user settings dictate minor ticks to become too dense, they don't
+ * make sense, and will be ignored to prevent performance problems.
+ *
+ * @type {Number|String}
+ * @sample {highcharts} highcharts/yaxis/minortickinterval-null/
+ * Null by default
+ * @sample {highcharts} highcharts/yaxis/minortickinterval-5/
+ * 5 units
+ * @sample {highcharts} highcharts/yaxis/minortickinterval-log-auto/
+ * "auto"
+ * @sample {highcharts} highcharts/yaxis/minortickinterval-log/
+ * 0.1
+ * @sample {highstock} stock/demo/basic-line/
+ * Null by default
+ * @sample {highstock} stock/xaxis/minortickinterval-auto/
+ * "auto"
+ * @apioption xAxis.minorTickInterval
+ */
+
+ /**
+ * The pixel length of the minor tick marks.
+ *
+ * @sample {highcharts} highcharts/yaxis/minorticklength/
+ * 10px on Y axis
+ * @sample {highstock} stock/xaxis/minorticks/
+ * 10px on Y axis
+ */
+ minorTickLength: 2,
+
+ /**
+ * The position of the minor tick marks relative to the axis line.
+ * Can be one of `inside` and `outside`.
+ *
+ * @validvalue ["inside", "outside"]
+ * @sample {highcharts} highcharts/yaxis/minortickposition-outside/
+ * Outside by default
+ * @sample {highcharts} highcharts/yaxis/minortickposition-inside/
+ * Inside
+ * @sample {highstock} stock/xaxis/minorticks/
+ * Inside
+ */
+ minorTickPosition: 'outside',
+
+ /**
+ * Enable or disable minor ticks. Unless
+ * [minorTickInterval](#xAxis.minorTickInterval) is set, the tick
+ * interval is calculated as a fifth of the `tickInterval`.
+ *
+ * On a logarithmic axis, minor ticks are laid out based on a best
+ * guess, attempting to enter approximately 5 minor ticks between
+ * each major tick.
+ *
+ * Prior to v6.0.0, ticks were unabled in auto layout by setting
+ * `minorTickInterval` to `"auto"`.
+ *
+ * @productdesc {highcharts}
+ * On axes using [categories](#xAxis.categories), minor ticks are not
+ * supported.
+ *
+ * @type {Boolean}
+ * @default false
+ * @since 6.0.0
+ * @sample {highcharts} highcharts/yaxis/minorticks-true/
+ * Enabled on linear Y axis
+ * @apioption xAxis.minorTicks
+ */
+
+ /**
+ * The pixel width of the minor tick mark.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/yaxis/minortickwidth/
+ * 3px width
+ * @sample {highstock} stock/xaxis/minorticks/
+ * 1px width
+ * @default 0
+ * @apioption xAxis.minorTickWidth
+ */
+
+ /**
+ * Padding of the min value relative to the length of the axis. A
+ * padding of 0.05 will make a 100px axis 5px longer. This is useful
+ * when you don't want the lowest data value to appear on the edge
+ * of the plot area. When the axis' `min` option is set or a min extreme
+ * is set using `axis.setExtremes()`, the minPadding will be ignored.
+ *
+ * @sample {highcharts} highcharts/yaxis/minpadding/
+ * Min padding of 0.2
+ * @sample {highstock} stock/xaxis/minpadding-maxpadding/
+ * Greater min- and maxPadding
+ * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
+ * Add some padding
+ * @default {highcharts} 0.01
+ * @default {highstock|highmaps} 0
+ * @since 1.2.0
+ */
+ minPadding: 0.01,
+
+ /**
+ * The minimum range to display on this axis. The entire axis will not
+ * be allowed to span over a smaller interval than this. For example,
+ * for a datetime axis the main unit is milliseconds. If minRange is
+ * set to 3600000, you can't zoom in more than to one hour.
+ *
+ * The default minRange for the x axis is five times the smallest
+ * interval between any of the data points.
+ *
+ * On a logarithmic axis, the unit for the minimum range is the power.
+ * So a minRange of 1 means that the axis can be zoomed to 10-100,
+ * 100-1000, 1000-10000 etc.
+ *
+ * Note that the `minPadding`, `maxPadding`, `startOnTick` and
+ * `endOnTick` settings also affect how the extremes of the axis
+ * are computed.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/xaxis/minrange/
+ * Minimum range of 5
+ * @sample {highstock} stock/xaxis/minrange/
+ * Max zoom of 6 months overrides user selections
+ * @sample {highmaps} maps/axis/minrange/
+ * Minimum range of 1000
+ * @apioption xAxis.minRange
+ */
+
+ /**
+ * The minimum tick interval allowed in axis values. For example on
+ * zooming in on an axis with daily data, this can be used to prevent
+ * the axis from showing hours. Defaults to the closest distance between
+ * two points on the axis.
+ *
+ * @type {Number}
+ * @since 2.3.0
+ * @apioption xAxis.minTickInterval
+ */
+
+ /**
+ * The distance in pixels from the plot area to the axis line.
+ * A positive offset moves the axis with it's line, labels and ticks
+ * away from the plot area. This is typically used when two or more
+ * axes are displayed on the same side of the plot. With multiple
+ * axes the offset is dynamically adjusted to avoid collision, this
+ * can be overridden by setting offset explicitly.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/yaxis/offset/
+ * Y axis offset of 70
+ * @sample {highcharts} highcharts/yaxis/offset-centered/
+ * Axes positioned in the center of the plot
+ * @sample {highstock} stock/xaxis/offset/
+ * Y axis offset by 70 px
+ * @default 0
+ * @apioption xAxis.offset
+ */
+
+ /**
+ * Whether to display the axis on the opposite side of the normal. The
+ * normal is on the left side for vertical axes and bottom for
+ * horizontal, so the opposite sides will be right and top respectively.
+ * This is typically used with dual or multiple axes.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/yaxis/opposite/
+ * Secondary Y axis opposite
+ * @sample {highstock} stock/xaxis/opposite/
+ * Y axis on left side
+ * @default false
+ * @apioption xAxis.opposite
+ */
+
+ /**
+ * Refers to the index in the [panes](#panes) array. Used for circular
+ * gauges and polar charts. When the option is not set then first pane
+ * will be used.
+ *
+ * @type {Number}
+ * @sample highcharts/demo/gauge-vu-meter
+ * Two gauges with different center
+ * @product highcharts
+ * @apioption xAxis.pane
+ */
+
+ /**
+ * Whether to reverse the axis so that the highest number is closest
+ * to the origin. If the chart is inverted, the x axis is reversed by
+ * default.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/yaxis/reversed/
+ * Reversed Y axis
+ * @sample {highstock} stock/xaxis/reversed/
+ * Reversed Y axis
+ * @default false
+ * @apioption xAxis.reversed
+ */
+ // reversed: false,
+
+ /**
+ * Whether to show the last tick label. Defaults to `true` on cartesian
+ * charts, and `false` on polar charts.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/xaxis/showlastlabel-true/
+ * Set to true on X axis
+ * @sample {highstock} stock/xaxis/showfirstlabel/
+ * Labels below plot lines on Y axis
+ * @default true
+ * @product highcharts highstock
+ * @apioption xAxis.showLastLabel
+ */
+
+ /**
+ * For datetime axes, this decides where to put the tick between weeks.
+ * 0 = Sunday, 1 = Monday.
+ *
+ * @sample {highcharts} highcharts/xaxis/startofweek-monday/
+ * Monday by default
+ * @sample {highcharts} highcharts/xaxis/startofweek-sunday/
+ * Sunday
+ * @sample {highstock} stock/xaxis/startofweek-1
+ * Monday by default
+ * @sample {highstock} stock/xaxis/startofweek-0
+ * Sunday
+ * @product highcharts highstock
+ */
+ startOfWeek: 1,
+
+ /**
+ * Whether to force the axis to start on a tick. Use this option with
+ * the `minPadding` option to control the axis start.
+ *
+ * @productdesc {highstock}
+ * In Highstock, `startOnTick` is always false when the navigator is
+ * enabled, to prevent jumpy scrolling.
+ *
+ * @sample {highcharts} highcharts/xaxis/startontick-false/
+ * False by default
+ * @sample {highcharts} highcharts/xaxis/startontick-true/
+ * True
+ * @sample {highstock} stock/xaxis/endontick/
+ * False for Y axis
+ * @since 1.2.0
+ */
+ startOnTick: false,
+
+ /**
+ * The pixel length of the main tick marks.
+ *
+ * @sample {highcharts} highcharts/xaxis/ticklength/
+ * 20 px tick length on the X axis
+ * @sample {highstock} stock/xaxis/ticks/
+ * Formatted ticks on X axis
+ */
+ tickLength: 10,
+
+ /**
+ * For categorized axes only. If `on` the tick mark is placed in the
+ * center of the category, if `between` the tick mark is placed between
+ * categories. The default is `between` if the `tickInterval` is 1,
+ * else `on`.
+ *
+ * @validvalue [null, "on", "between"]
+ * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/
+ * "between" by default
+ * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/
+ * "on"
+ * @product highcharts
+ */
+ tickmarkPlacement: 'between',
+
+ /**
+ * If tickInterval is `null` this option sets the approximate pixel
+ * interval of the tick marks. Not applicable to categorized axis.
+ *
+ * The tick interval is also influenced by the [minTickInterval](#xAxis.
+ * minTickInterval) option, that, by default prevents ticks from being
+ * denser than the data points.
+ *
+ * @see [tickInterval](#xAxis.tickInterval),
+ * [tickPositioner](#xAxis.tickPositioner),
+ * [tickPositions](#xAxis.tickPositions).
+ * @sample {highcharts} highcharts/xaxis/tickpixelinterval-50/
+ * 50 px on X axis
+ * @sample {highstock} stock/xaxis/tickpixelinterval/
+ * 200 px on X axis
+ */
+ tickPixelInterval: 100,
+
+ /**
+ * The position of the major tick marks relative to the axis line.
+ * Can be one of `inside` and `outside`.
+ *
+ * @validvalue ["inside", "outside"]
+ * @sample {highcharts} highcharts/xaxis/tickposition-outside/
+ * "outside" by default
+ * @sample {highcharts} highcharts/xaxis/tickposition-inside/
+ * "inside"
+ * @sample {highstock} stock/xaxis/ticks/
+ * Formatted ticks on X axis
+ */
+ tickPosition: 'outside',
+
+ /**
+ * The axis title, showing next to the axis line.
+ *
+ * @productdesc {highmaps}
+ * In Highmaps, the axis is hidden by default, but adding an axis title
+ * is still possible. X axis and Y axis titles will appear at the bottom
+ * and left by default.
+ */
+ title: {
+
+ /**
+ * Alignment of the title relative to the axis values. Possible
+ * values are "low", "middle" or "high".
+ *
+ * @validvalue ["low", "middle", "high"]
+ * @sample {highcharts} highcharts/xaxis/title-align-low/
+ * "low"
+ * @sample {highcharts} highcharts/xaxis/title-align-center/
+ * "middle" by default
+ * @sample {highcharts} highcharts/xaxis/title-align-high/
+ * "high"
+ * @sample {highcharts} highcharts/yaxis/title-offset/
+ * Place the Y axis title on top of the axis
+ * @sample {highstock} stock/xaxis/title-align/
+ * Aligned to "high" value
+ */
+ align: 'middle',
+
+
+
+ /**
+ * CSS styles for the title. If the title text is longer than the
+ * axis length, it will wrap to multiple lines by default. This can
+ * be customized by setting `textOverflow: 'ellipsis'`, by
+ * setting a specific `width` or by setting `wordSpace: 'nowrap'`.
+ *
+ * In styled mode, the stroke width is given in the
+ * `.highcharts-axis-title` class.
+ *
+ * @type {CSSObject}
+ * @sample {highcharts} highcharts/xaxis/title-style/
+ * Red
+ * @sample {highcharts} highcharts/css/axis/
+ * Styled mode
+ * @default { "color": "#666666" }
+ */
+ style: {
+ color: '#666666'
+ }
+
+ },
+
+ /**
+ * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`
+ * or `category`. In a datetime axis, the numbers are given in
+ * milliseconds, and tick marks are placed on appropriate values like
+ * full hours or days. In a category axis, the
+ * [point names](#series.line.data.name) of the chart's series are used
+ * for categories, if not a [categories](#xAxis.categories) array is
+ * defined.
+ *
+ * @validvalue ["linear", "logarithmic", "datetime", "category"]
+ * @sample {highcharts} highcharts/xaxis/type-linear/
+ * Linear
+ * @sample {highcharts} highcharts/yaxis/type-log/
+ * Logarithmic
+ * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/
+ * Logarithmic with minor grid lines
+ * @sample {highcharts} highcharts/xaxis/type-log-both/
+ * Logarithmic on two axes
+ * @sample {highcharts} highcharts/yaxis/type-log-negative/
+ * Logarithmic with extension to emulate negative values
+ * @product highcharts
+ */
+ type: 'linear',
+
+
+
+ /**
+ * Color of the minor, secondary grid lines.
+ *
+ * In styled mode, the stroke width is given in the
+ * `.highcharts-minor-grid-line` class.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/yaxis/minorgridlinecolor/
+ * Bright grey lines from Y axis
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/minorgridlinecolor/
+ * Bright grey lines from Y axis
+ * @default #f2f2f2
+ */
+ minorGridLineColor: '#f2f2f2',
+ // minorGridLineDashStyle: null,
+
+ /**
+ * Width of the minor, secondary grid lines.
+ *
+ * In styled mode, the stroke width is given in the
+ * `.highcharts-grid-line` class.
+ *
+ * @sample {highcharts} highcharts/yaxis/minorgridlinewidth/
+ * 2px lines from Y axis
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/minorgridlinewidth/
+ * 2px lines from Y axis
+ */
+ minorGridLineWidth: 1,
+
+ /**
+ * Color for the minor tick marks.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/yaxis/minortickcolor/
+ * Black tick marks on Y axis
+ * @sample {highstock} stock/xaxis/minorticks/
+ * Black tick marks on Y axis
+ * @default #999999
+ */
+ minorTickColor: '#999999',
+
+ /**
+ * The color of the line marking the axis itself.
+ *
+ * In styled mode, the line stroke is given in the
+ * `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
+ *
+ * @productdesc {highmaps}
+ * In Highmaps, the axis line is hidden by default, because the axis is
+ * not visible by default.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/yaxis/linecolor/
+ * A red line on Y axis
+ * @sample {highcharts|highstock} highcharts/css/axis/
+ * Axes in styled mode
+ * @sample {highstock} stock/xaxis/linecolor/
+ * A red line on X axis
+ * @default #ccd6eb
+ */
+ lineColor: '#ccd6eb',
+
+ /**
+ * The width of the line marking the axis itself.
+ *
+ * In styled mode, the stroke width is given in the
+ * `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
+ *
+ * @sample {highcharts} highcharts/yaxis/linecolor/
+ * A 1px line on Y axis
+ * @sample {highcharts|highstock} highcharts/css/axis/
+ * Axes in styled mode
+ * @sample {highstock} stock/xaxis/linewidth/
+ * A 2px line on X axis
+ * @default {highcharts|highstock} 1
+ * @default {highmaps} 0
+ */
+ lineWidth: 1,
+
+ /**
+ * Color of the grid lines extending the ticks across the plot area.
+ *
+ * In styled mode, the stroke is given in the `.highcharts-grid-line`
+ * class.
+ *
+ * @productdesc {highmaps}
+ * In Highmaps, the grid lines are hidden by default.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/yaxis/gridlinecolor/
+ * Green lines
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/gridlinecolor/
+ * Green lines
+ * @default #e6e6e6
+ */
+ gridLineColor: '#e6e6e6',
+ // gridLineDashStyle: 'solid',
+
+
+ /**
+ * The width of the grid lines extending the ticks across the plot area.
+ *
+ * In styled mode, the stroke width is given in the
+ * `.highcharts-grid-line` class.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/yaxis/gridlinewidth/
+ * 2px lines
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/gridlinewidth/
+ * 2px lines
+ * @default 0
+ * @apioption xAxis.gridLineWidth
+ */
+ // gridLineWidth: 0,
+
+ /**
+ * Color for the main tick marks.
+ *
+ * In styled mode, the stroke is given in the `.highcharts-tick`
+ * class.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/xaxis/tickcolor/
+ * Red ticks on X axis
+ * @sample {highcharts|highstock} highcharts/css/axis-grid/
+ * Styled mode
+ * @sample {highstock} stock/xaxis/ticks/
+ * Formatted ticks on X axis
+ * @default #ccd6eb
+ */
+ tickColor: '#ccd6eb'
+ // tickWidth: 1
+
+ },
+
+ /**
+ * The Y axis or value axis. Normally this is the vertical axis,
+ * though if the chart is inverted this is the horizontal axis.
+ * In case of multiple axes, the yAxis node is an array of
+ * configuration objects.
+ *
+ * See [the Axis object](#Axis) for programmatic access to the axis.
+ *
+ * @extends xAxis
+ * @excluding ordinal,overscroll
+ * @optionparent yAxis
+ */
+ defaultYAxisOptions: {
+ /**
+ * @productdesc {highstock}
+ * In Highstock, `endOnTick` is always false when the navigator is
+ * enabled, to prevent jumpy scrolling.
+ */
+ endOnTick: true,
+
+ /**
+ * @productdesc {highstock}
+ * In Highstock 1.x, the Y axis was placed on the left side by default.
+ *
+ * @sample {highcharts} highcharts/yaxis/opposite/
+ * Secondary Y axis opposite
+ * @sample {highstock} stock/xaxis/opposite/
+ * Y axis on left side
+ * @default {highstock} true
+ * @default {highcharts} false
+ * @product highstock highcharts
+ * @apioption yAxis.opposite
+ */
+
+ /**
+ * @see [tickInterval](#xAxis.tickInterval),
+ * [tickPositioner](#xAxis.tickPositioner),
+ * [tickPositions](#xAxis.tickPositions).
+ */
+ tickPixelInterval: 72,
+
+ showLastLabel: true,
+
+ /**
+ * @extends xAxis.labels
+ */
+ labels: {
+ /**
+ * What part of the string the given position is anchored to. Can
+ * be one of `"left"`, `"center"` or `"right"`. The exact position
+ * also depends on the `labels.x` setting.
+ *
+ * Angular gauges and solid gauges defaults to `center`.
+ *
+ * @validvalue ["left", "center", "right"]
+ * @type {String}
+ * @sample {highcharts} highcharts/yaxis/labels-align-left/
+ * Left
+ * @default {highcharts|highmaps} right
+ * @default {highstock} left
+ * @apioption yAxis.labels.align
+ */
+
+ /**
+ * The x position offset of the label relative to the tick position
+ * on the axis. Defaults to -15 for left axis, 15 for right axis.
+ *
+ * @sample {highcharts} highcharts/xaxis/labels-x/
+ * Y axis labels placed on grid lines
+ */
+ x: -8
+ },
+
+ /**
+ * @productdesc {highmaps}
+ * In Highmaps, the axis line is hidden by default, because the axis is
+ * not visible by default.
+ *
+ * @apioption yAxis.lineColor
+ */
+
+ /**
+ * @sample {highcharts} highcharts/yaxis/min-startontick-false/
+ * -50 with startOnTick to false
+ * @sample {highcharts} highcharts/yaxis/min-startontick-true/
+ * -50 with startOnTick true by default
+ * @sample {highstock} stock/yaxis/min-max/
+ * Fixed min and max on Y axis
+ * @sample {highmaps} maps/axis/min-max/
+ * Pre-zoomed to a specific area
+ * @apioption yAxis.min
+ */
+
+ /**
+ * @sample {highcharts} highcharts/yaxis/max-200/
+ * Y axis max of 200
+ * @sample {highcharts} highcharts/yaxis/max-logarithmic/
+ * Y axis max on logarithmic axis
+ * @sample {highstock} stock/yaxis/min-max/
+ * Fixed min and max on Y axis
+ * @sample {highmaps} maps/axis/min-max/
+ * Pre-zoomed to a specific area
+ * @apioption yAxis.max
+ */
+
+ /**
+ * Padding of the max value relative to the length of the axis. A
+ * padding of 0.05 will make a 100px axis 5px longer. This is useful
+ * when you don't want the highest data value to appear on the edge
+ * of the plot area. When the axis' `max` option is set or a max extreme
+ * is set using `axis.setExtremes()`, the maxPadding will be ignored.
+ *
+ * @sample {highcharts} highcharts/yaxis/maxpadding-02/
+ * Max padding of 0.2
+ * @sample {highstock} stock/xaxis/minpadding-maxpadding/
+ * Greater min- and maxPadding
+ * @since 1.2.0
+ * @product highcharts highstock
+ */
+ maxPadding: 0.05,
+
+ /**
+ * Padding of the min value relative to the length of the axis. A
+ * padding of 0.05 will make a 100px axis 5px longer. This is useful
+ * when you don't want the lowest data value to appear on the edge
+ * of the plot area. When the axis' `min` option is set or a max extreme
+ * is set using `axis.setExtremes()`, the maxPadding will be ignored.
+ *
+ * @sample {highcharts} highcharts/yaxis/minpadding/
+ * Min padding of 0.2
+ * @sample {highstock} stock/xaxis/minpadding-maxpadding/
+ * Greater min- and maxPadding
+ * @since 1.2.0
+ * @product highcharts highstock
+ */
+ minPadding: 0.05,
+
+ /**
+ * Whether to force the axis to start on a tick. Use this option with
+ * the `maxPadding` option to control the axis start.
+ *
+ * @sample {highcharts} highcharts/xaxis/startontick-false/
+ * False by default
+ * @sample {highcharts} highcharts/xaxis/startontick-true/
+ * True
+ * @sample {highstock} stock/xaxis/endontick/
+ * False for Y axis
+ * @since 1.2.0
+ * @product highcharts highstock
+ */
+ startOnTick: true,
+
+ /**
+ * @extends xAxis.title
+ */
+ title: {
+
+ /**
+ * The rotation of the text in degrees. 0 is horizontal, 270 is
+ * vertical reading from bottom to top.
+ *
+ * @sample {highcharts} highcharts/yaxis/title-offset/
+ * Horizontal
+ */
+ rotation: 270,
+
+ /**
+ * The actual text of the axis title. Horizontal texts can contain
+ * HTML, but rotated texts are painted using vector techniques and
+ * must be clean text. The Y axis title is disabled by setting the
+ * `text` option to `null`.
+ *
+ * @sample {highcharts} highcharts/xaxis/title-text/
+ * Custom HTML
+ * @default {highcharts} Values
+ * @default {highstock} null
+ * @product highcharts highstock
+ */
+ text: 'Values'
+ },
+
+ /**
+ * The stack labels show the total value for each bar in a stacked
+ * column or bar chart. The label will be placed on top of positive
+ * columns and below negative columns. In case of an inverted column
+ * chart or a bar chart the label is placed to the right of positive
+ * bars and to the left of negative bars.
+ *
+ * @product highcharts
+ */
+ stackLabels: {
+
+ /**
+ * Allow the stack labels to overlap.
+ *
+ * @sample {highcharts}
+ * highcharts/yaxis/stacklabels-allowoverlap-false/
+ * Default false
+ * @since 5.0.13
+ * @product highcharts
+ */
+ allowOverlap: false,
+
+ /**
+ * Enable or disable the stack total labels.
+ *
+ * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/
+ * Enabled stack total labels
+ * @since 2.1.5
+ * @product highcharts
+ */
+ enabled: false,
+
+ /**
+ * Callback JavaScript function to format the label. The value is
+ * given by `this.total`.
+ *
+ * @default function() { return this.total; }
+ *
+ * @type {Function}
+ * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/
+ * Added units to stack total value
+ * @since 2.1.5
+ * @product highcharts
+ */
+ formatter: function () {
+ return H.numberFormat(this.total, -1);
+ },
+
+
+ /**
+ * CSS styles for the label.
+ *
+ * In styled mode, the styles are set in the
+ * `.highcharts-stack-label` class.
+ *
+ * @type {CSSObject}
+ * @sample {highcharts} highcharts/yaxis/stacklabels-style/
+ * Red stack total labels
+ * @since 2.1.5
+ * @product highcharts
+ */
+ style: {
+ fontSize: '11px',
+ fontWeight: 'bold',
+ color: '#000000',
+ textOutline: '1px contrast'
+ }
+
+ },
+
+ gridLineWidth: 1,
+ lineWidth: 0
+ // tickWidth: 0
+
+ },
+
+ /**
+ * These options extend the defaultOptions for left axes.
+ *
+ * @private
+ * @type {Object}
+ */
+ defaultLeftAxisOptions: {
+ labels: {
+ x: -15
+ },
+ title: {
+ rotation: 270
+ }
+ },
+
+ /**
+ * These options extend the defaultOptions for right axes.
+ *
+ * @private
+ * @type {Object}
+ */
+ defaultRightAxisOptions: {
+ labels: {
+ x: 15
+ },
+ title: {
+ rotation: 90
+ }
+ },
+
+ /**
+ * These options extend the defaultOptions for bottom axes.
+ *
+ * @private
+ * @type {Object}
+ */
+ defaultBottomAxisOptions: {
+ labels: {
+ autoRotation: [-45],
+ x: 0
+ // overflow: undefined,
+ // staggerLines: null
+ },
+ title: {
+ rotation: 0
+ }
+ },
+ /**
+ * These options extend the defaultOptions for top axes.
+ *
+ * @private
+ * @type {Object}
+ */
+ defaultTopAxisOptions: {
+ labels: {
+ autoRotation: [-45],
+ x: 0
+ // overflow: undefined
+ // staggerLines: null
+ },
+ title: {
+ rotation: 0
+ }
+ },
+
+ /**
+ * Overrideable function to initialize the axis.
+ *
+ * @see {@link Axis}
+ */
+ init: function (chart, userOptions) {
+
+
+ var isXAxis = userOptions.isX,
+ axis = this;
+
+
+ /**
+ * The Chart that the axis belongs to.
+ *
+ * @name chart
+ * @memberOf Axis
+ * @type {Chart}
+ */
+ axis.chart = chart;
+
+ /**
+ * Whether the axis is horizontal.
+ *
+ * @name horiz
+ * @memberOf Axis
+ * @type {Boolean}
+ */
+ axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis;
+
+ // Flag, isXAxis
+ axis.isXAxis = isXAxis;
+
+ /**
+ * The collection where the axis belongs, for example `xAxis`, `yAxis`
+ * or `colorAxis`. Corresponds to properties on Chart, for example
+ * {@link Chart.xAxis}.
+ *
+ * @name coll
+ * @memberOf Axis
+ * @type {String}
+ */
+ axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis');
+
+
+ axis.opposite = userOptions.opposite; // needed in setOptions
+
+ /**
+ * The side on which the axis is rendered. 0 is top, 1 is right, 2 is
+ * bottom and 3 is left.
+ *
+ * @name side
+ * @memberOf Axis
+ * @type {Number}
+ */
+ axis.side = userOptions.side || (axis.horiz ?
+ (axis.opposite ? 0 : 2) : // top : bottom
+ (axis.opposite ? 1 : 3)); // right : left
+
+ axis.setOptions(userOptions);
+
+
+ var options = this.options,
+ type = options.type,
+ isDatetimeAxis = type === 'datetime';
+
+ axis.labelFormatter = options.labels.formatter ||
+ axis.defaultLabelFormatter; // can be overwritten by dynamic format
+
+
+ // Flag, stagger lines or not
+ axis.userOptions = userOptions;
+
+ axis.minPixelPadding = 0;
+
+
+ /**
+ * Whether the axis is reversed. Based on the `axis.reversed`,
+ * option, but inverted charts have reversed xAxis by default.
+ *
+ * @name reversed
+ * @memberOf Axis
+ * @type {Boolean}
+ */
+ axis.reversed = options.reversed;
+ axis.visible = options.visible !== false;
+ axis.zoomEnabled = options.zoomEnabled !== false;
+
+ // Initial categories
+ axis.hasNames = type === 'category' || options.categories === true;
+ axis.categories = options.categories || axis.hasNames;
+ axis.names = axis.names || []; // Preserve on update (#3830)
+
+ // Placeholder for plotlines and plotbands groups
+ axis.plotLinesAndBandsGroups = {};
+
+ // Shorthand types
+ axis.isLog = type === 'logarithmic';
+ axis.isDatetimeAxis = isDatetimeAxis;
+ axis.positiveValuesOnly = axis.isLog && !axis.allowNegativeLog;
+
+ // Flag, if axis is linked to another axis
+ axis.isLinked = defined(options.linkedTo);
+
+ // Major ticks
+ axis.ticks = {};
+ axis.labelEdge = [];
+ // Minor ticks
+ axis.minorTicks = {};
+
+ // List of plotLines/Bands
+ axis.plotLinesAndBands = [];
+
+ // Alternate bands
+ axis.alternateBands = {};
+
+ // Axis metrics
+ axis.len = 0;
+ axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
+ axis.range = options.range;
+ axis.offset = options.offset || 0;
+
+
+ // Dictionary for stacks
+ axis.stacks = {};
+ axis.oldStacks = {};
+ axis.stacksTouched = 0;
+
+
+ /**
+ * The maximum value of the axis. In a logarithmic axis, this is the
+ * logarithm of the real value, and the real value can be obtained from
+ * {@link Axis#getExtremes}.
+ *
+ * @name max
+ * @memberOf Axis
+ * @type {Number}
+ */
+ axis.max = null;
+ /**
+ * The minimum value of the axis. In a logarithmic axis, this is the
+ * logarithm of the real value, and the real value can be obtained from
+ * {@link Axis#getExtremes}.
+ *
+ * @name min
+ * @memberOf Axis
+ * @type {Number}
+ */
+ axis.min = null;
+
+
+ /**
+ * The processed crosshair options.
+ *
+ * @name crosshair
+ * @memberOf Axis
+ * @type {AxisCrosshairOptions}
+ */
+ axis.crosshair = pick(
+ options.crosshair,
+ splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1],
+ false
+ );
+
+ var events = axis.options.events;
+
+ // Register. Don't add it again on Axis.update().
+ if (inArray(axis, chart.axes) === -1) { //
+ if (isXAxis) { // #2713
+ chart.axes.splice(chart.xAxis.length, 0, axis);
+ } else {
+ chart.axes.push(axis);
+ }
+
+ chart[axis.coll].push(axis);
+ }
+
+ /**
+ * All series associated to the axis.
+ *
+ * @name series
+ * @memberOf Axis
+ * @type {Array.}
+ */
+ axis.series = axis.series || []; // populated by Series
+
+ // Reversed axis
+ if (
+ chart.inverted &&
+ !axis.isZAxis &&
+ isXAxis &&
+ axis.reversed === undefined
+ ) {
+ axis.reversed = true;
+ }
+
+ // register event listeners
+ objectEach(events, function (event, eventType) {
+ addEvent(axis, eventType, event);
+ });
+
+ // extend logarithmic axis
+ axis.lin2log = options.linearToLogConverter || axis.lin2log;
+ if (axis.isLog) {
+ axis.val2lin = axis.log2lin;
+ axis.lin2val = axis.lin2log;
+ }
+ },
+
+ /**
+ * Merge and set options.
+ *
+ * @private
+ */
+ setOptions: function (userOptions) {
+ this.options = merge(
+ this.defaultOptions,
+ this.coll === 'yAxis' && this.defaultYAxisOptions,
+ [
+ this.defaultTopAxisOptions,
+ this.defaultRightAxisOptions,
+ this.defaultBottomAxisOptions,
+ this.defaultLeftAxisOptions
+ ][this.side],
+ merge(
+ defaultOptions[this.coll], // if set in setOptions (#1053)
+ userOptions
+ )
+ );
+ },
+
+ /**
+ * The default label formatter. The context is a special config object for
+ * the label. In apps, use the {@link
+ * https://api.highcharts.com/highcharts/xAxis.labels.formatter|
+ * labels.formatter} instead except when a modification is needed.
+ *
+ * @private
+ */
+ defaultLabelFormatter: function () {
+ var axis = this.axis,
+ value = this.value,
+ time = axis.chart.time,
+ categories = axis.categories,
+ dateTimeLabelFormat = this.dateTimeLabelFormat,
+ lang = defaultOptions.lang,
+ numericSymbols = lang.numericSymbols,
+ numSymMagnitude = lang.numericSymbolMagnitude || 1000,
+ i = numericSymbols && numericSymbols.length,
+ multi,
+ ret,
+ formatOption = axis.options.labels.format,
+
+ // make sure the same symbol is added for all labels on a linear
+ // axis
+ numericSymbolDetector = axis.isLog ?
+ Math.abs(value) :
+ axis.tickInterval;
+
+ if (formatOption) {
+ ret = format(formatOption, this, time);
+
+ } else if (categories) {
+ ret = value;
+
+ } else if (dateTimeLabelFormat) { // datetime axis
+ ret = time.dateFormat(dateTimeLabelFormat, value);
+
+ } else if (i && numericSymbolDetector >= 1000) {
+ // Decide whether we should add a numeric symbol like k (thousands)
+ // or M (millions). If we are to enable this in tooltip or other
+ // places as well, we can move this logic to the numberFormatter and
+ // enable it by a parameter.
+ while (i-- && ret === undefined) {
+ multi = Math.pow(numSymMagnitude, i + 1);
+ if (
+ // Only accept a numeric symbol when the distance is more
+ // than a full unit. So for example if the symbol is k, we
+ // don't accept numbers like 0.5k.
+ numericSymbolDetector >= multi &&
+ // Accept one decimal before the symbol. Accepts 0.5k but
+ // not 0.25k. How does this work with the previous?
+ (value * 10) % multi === 0 &&
+ numericSymbols[i] !== null &&
+ value !== 0
+ ) { // #5480
+ ret = H.numberFormat(value / multi, -1) + numericSymbols[i];
+ }
+ }
+ }
+
+ if (ret === undefined) {
+ if (Math.abs(value) >= 10000) { // add thousands separators
+ ret = H.numberFormat(value, -1);
+ } else { // small numbers
+ ret = H.numberFormat(value, -1, undefined, ''); // #2466
+ }
+ }
+
+ return ret;
+ },
+
+ /**
+ * Get the minimum and maximum for the series of each axis. The function
+ * analyzes the axis series and updates `this.dataMin` and `this.dataMax`.
+ *
+ * @private
+ */
+ getSeriesExtremes: function () {
+ var axis = this,
+ chart = axis.chart;
+ axis.hasVisibleSeries = false;
+
+ // Reset properties in case we're redrawing (#3353)
+ axis.dataMin = axis.dataMax = axis.threshold = null;
+ axis.softThreshold = !axis.isXAxis;
+
+ if (axis.buildStacks) {
+ axis.buildStacks();
+ }
+
+ // loop through this axis' series
+ each(axis.series, function (series) {
+
+ if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
+
+ var seriesOptions = series.options,
+ xData,
+ threshold = seriesOptions.threshold,
+ seriesDataMin,
+ seriesDataMax;
+
+ axis.hasVisibleSeries = true;
+
+ // Validate threshold in logarithmic axes
+ if (axis.positiveValuesOnly && threshold <= 0) {
+ threshold = null;
+ }
+
+ // Get dataMin and dataMax for X axes
+ if (axis.isXAxis) {
+ xData = series.xData;
+ if (xData.length) {
+ // If xData contains values which is not numbers, then
+ // filter them out. To prevent performance hit, we only
+ // do this after we have already found seriesDataMin
+ // because in most cases all data is valid. #5234.
+ seriesDataMin = arrayMin(xData);
+ seriesDataMax = arrayMax(xData);
+
+ if (
+ !isNumber(seriesDataMin) &&
+ !(seriesDataMin instanceof Date) // #5010
+ ) {
+ xData = grep(xData, isNumber);
+ // Do it again with valid data
+ seriesDataMin = arrayMin(xData);
+ seriesDataMax = arrayMax(xData);
+ }
+
+ if (xData.length) {
+ axis.dataMin = Math.min(
+ pick(axis.dataMin, xData[0], seriesDataMin),
+ seriesDataMin
+ );
+ axis.dataMax = Math.max(
+ pick(axis.dataMax, xData[0], seriesDataMax),
+ seriesDataMax
+ );
+ }
+ }
+
+ // Get dataMin and dataMax for Y axes, as well as handle
+ // stacking and processed data
+ } else {
+
+ // Get this particular series extremes
+ series.getExtremes();
+ seriesDataMax = series.dataMax;
+ seriesDataMin = series.dataMin;
+
+ // Get the dataMin and dataMax so far. If percentage is
+ // used, the min and max are always 0 and 100. If
+ // seriesDataMin and seriesDataMax is null, then series
+ // doesn't have active y data, we continue with nulls
+ if (defined(seriesDataMin) && defined(seriesDataMax)) {
+ axis.dataMin = Math.min(
+ pick(axis.dataMin, seriesDataMin),
+ seriesDataMin
+ );
+ axis.dataMax = Math.max(
+ pick(axis.dataMax, seriesDataMax),
+ seriesDataMax
+ );
+ }
+
+ // Adjust to threshold
+ if (defined(threshold)) {
+ axis.threshold = threshold;
+ }
+ // If any series has a hard threshold, it takes precedence
+ if (
+ !seriesOptions.softThreshold ||
+ axis.positiveValuesOnly
+ ) {
+ axis.softThreshold = false;
+ }
+ }
+ }
+ });
+ },
+
+ /**
+ * Translate from axis value to pixel position on the chart, or back. Use
+ * the `toPixels` and `toValue` functions in applications.
+ *
+ * @private
+ */
+ translate: function (
+ val,
+ backwards,
+ cvsCoord,
+ old,
+ handleLog,
+ pointPlacement
+ ) {
+ var axis = this.linkedParent || this, // #1417
+ sign = 1,
+ cvsOffset = 0,
+ localA = old ? axis.oldTransA : axis.transA,
+ localMin = old ? axis.oldMin : axis.min,
+ returnValue,
+ minPixelPadding = axis.minPixelPadding,
+ doPostTranslate = (
+ axis.isOrdinal ||
+ axis.isBroken ||
+ (axis.isLog && handleLog)
+ ) && axis.lin2val;
+
+ if (!localA) {
+ localA = axis.transA;
+ }
+
+ // In vertical axes, the canvas coordinates start from 0 at the top like
+ // in SVG.
+ if (cvsCoord) {
+ sign *= -1; // canvas coordinates inverts the value
+ cvsOffset = axis.len;
+ }
+
+ // Handle reversed axis
+ if (axis.reversed) {
+ sign *= -1;
+ cvsOffset -= sign * (axis.sector || axis.len);
+ }
+
+ // From pixels to value
+ if (backwards) { // reverse translation
+
+ val = val * sign + cvsOffset;
+ val -= minPixelPadding;
+ returnValue = val / localA + localMin; // from chart pixel to value
+ if (doPostTranslate) { // log and ordinal axes
+ returnValue = axis.lin2val(returnValue);
+ }
+
+ // From value to pixels
+ } else {
+ if (doPostTranslate) { // log and ordinal axes
+ val = axis.val2lin(val);
+ }
+ returnValue = isNumber(localMin) ?
+ (
+ sign * (val - localMin) * localA +
+ cvsOffset +
+ (sign * minPixelPadding) +
+ (isNumber(pointPlacement) ? localA * pointPlacement : 0)
+ ) :
+ undefined;
+ }
+
+ return returnValue;
+ },
+
+ /**
+ * Translate a value in terms of axis units into pixels within the chart.
+ *
+ * @param {Number} value
+ * A value in terms of axis units.
+ * @param {Boolean} paneCoordinates
+ * Whether to return the pixel coordinate relative to the chart or
+ * just the axis/pane itself.
+ * @return {Number} Pixel position of the value on the chart or axis.
+ */
+ toPixels: function (value, paneCoordinates) {
+ return this.translate(value, false, !this.horiz, null, true) +
+ (paneCoordinates ? 0 : this.pos);
+ },
+
+ /**
+ * Translate a pixel position along the axis to a value in terms of axis
+ * units.
+ * @param {Number} pixel
+ * The pixel value coordinate.
+ * @param {Boolean} paneCoordiantes
+ * Whether the input pixel is relative to the chart or just the
+ * axis/pane itself.
+ * @return {Number} The axis value.
+ */
+ toValue: function (pixel, paneCoordinates) {
+ return this.translate(
+ pixel - (paneCoordinates ? 0 : this.pos),
+ true,
+ !this.horiz,
+ null,
+ true
+ );
+ },
+
+ /**
+ * Create the path for a plot line that goes from the given value on
+ * this axis, across the plot to the opposite side. Also used internally for
+ * grid lines and crosshairs.
+ *
+ * @param {Number} value
+ * Axis value.
+ * @param {Number} [lineWidth=1]
+ * Used for calculation crisp line coordinates.
+ * @param {Boolean} [old=false]
+ * Use old coordinates (for resizing and rescaling).
+ * @param {Boolean} [force=false]
+ * If `false`, the function will return null when it falls outside
+ * the axis bounds.
+ * @param {Number} [translatedValue]
+ * If given, return the plot line path of a pixel position on the
+ * axis.
+ *
+ * @return {Array.}
+ * The SVG path definition for the plot line.
+ */
+ getPlotLinePath: function (value, lineWidth, old, force, translatedValue) {
+ var axis = this,
+ chart = axis.chart,
+ axisLeft = axis.left,
+ axisTop = axis.top,
+ x1,
+ y1,
+ x2,
+ y2,
+ cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
+ cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
+ skip,
+ transB = axis.transB,
+ /**
+ * Check if x is between a and b. If not, either move to a/b
+ * or skip, depending on the force parameter.
+ */
+ between = function (x, a, b) {
+ if (x < a || x > b) {
+ if (force) {
+ x = Math.min(Math.max(a, x), b);
+ } else {
+ skip = true;
+ }
+ }
+ return x;
+ };
+
+ translatedValue = pick(
+ translatedValue,
+ axis.translate(value, null, null, old)
+ );
+ x1 = x2 = Math.round(translatedValue + transB);
+ y1 = y2 = Math.round(cHeight - translatedValue - transB);
+ if (!isNumber(translatedValue)) { // no min or max
+ skip = true;
+ force = false; // #7175, don't force it when path is invalid
+ } else if (axis.horiz) {
+ y1 = axisTop;
+ y2 = cHeight - axis.bottom;
+ x1 = x2 = between(x1, axisLeft, axisLeft + axis.width);
+ } else {
+ x1 = axisLeft;
+ x2 = cWidth - axis.right;
+ y1 = y2 = between(y1, axisTop, axisTop + axis.height);
+ }
+ return skip && !force ?
+ null :
+ chart.renderer.crispLine(
+ ['M', x1, y1, 'L', x2, y2],
+ lineWidth || 1
+ );
+ },
+
+ /**
+ * Internal function to et the tick positions of a linear axis to round
+ * values like whole tens or every five.
+ *
+ * @param {Number} tickInterval
+ * The normalized tick interval
+ * @param {Number} min
+ * Axis minimum.
+ * @param {Number} max
+ * Axis maximum.
+ *
+ * @return {Array.}
+ * An array of axis values where ticks should be placed.
+ */
+ getLinearTickPositions: function (tickInterval, min, max) {
+ var pos,
+ lastPos,
+ roundedMin =
+ correctFloat(Math.floor(min / tickInterval) * tickInterval),
+ roundedMax =
+ correctFloat(Math.ceil(max / tickInterval) * tickInterval),
+ tickPositions = [],
+ precision;
+
+ // When the precision is higher than what we filter out in
+ // correctFloat, skip it (#6183).
+ if (correctFloat(roundedMin + tickInterval) === roundedMin) {
+ precision = 20;
+ }
+
+ // For single points, add a tick regardless of the relative position
+ // (#2662, #6274)
+ if (this.single) {
+ return [min];
+ }
+
+ // Populate the intermediate values
+ pos = roundedMin;
+ while (pos <= roundedMax) {
+
+ // Place the tick on the rounded value
+ tickPositions.push(pos);
+
+ // Always add the raw tickInterval, not the corrected one.
+ pos = correctFloat(
+ pos + tickInterval,
+ precision
+ );
+
+ // If the interval is not big enough in the current min - max range
+ // to actually increase the loop variable, we need to break out to
+ // prevent endless loop. Issue #619
+ if (pos === lastPos) {
+ break;
+ }
+
+ // Record the last value
+ lastPos = pos;
+ }
+ return tickPositions;
+ },
+
+ /**
+ * Resolve the new minorTicks/minorTickInterval options into the legacy
+ * loosely typed minorTickInterval option.
+ */
+ getMinorTickInterval: function () {
+ var options = this.options;
+
+ if (options.minorTicks === true) {
+ return pick(options.minorTickInterval, 'auto');
+ }
+ if (options.minorTicks === false) {
+ return null;
+ }
+ return options.minorTickInterval;
+ },
+
+ /**
+ * Internal function to return the minor tick positions. For logarithmic
+ * axes, the same logic as for major ticks is reused.
+ *
+ * @return {Array.}
+ * An array of axis values where ticks should be placed.
+ */
+ getMinorTickPositions: function () {
+ var axis = this,
+ options = axis.options,
+ tickPositions = axis.tickPositions,
+ minorTickInterval = axis.minorTickInterval,
+ minorTickPositions = [],
+ pos,
+ pointRangePadding = axis.pointRangePadding || 0,
+ min = axis.min - pointRangePadding, // #1498
+ max = axis.max + pointRangePadding, // #1498
+ range = max - min;
+
+ // If minor ticks get too dense, they are hard to read, and may cause
+ // long running script. So we don't draw them.
+ if (range && range / minorTickInterval < axis.len / 3) { // #3875
+
+ if (axis.isLog) {
+ // For each interval in the major ticks, compute the minor ticks
+ // separately.
+ each(this.paddedTicks, function (pos, i, paddedTicks) {
+ if (i) {
+ minorTickPositions.push.apply(
+ minorTickPositions,
+ axis.getLogTickPositions(
+ minorTickInterval,
+ paddedTicks[i - 1],
+ paddedTicks[i],
+ true
+ )
+ );
+ }
+ });
+
+ } else if (
+ axis.isDatetimeAxis &&
+ this.getMinorTickInterval() === 'auto'
+ ) { // #1314
+ minorTickPositions = minorTickPositions.concat(
+ axis.getTimeTicks(
+ axis.normalizeTimeTickInterval(minorTickInterval),
+ min,
+ max,
+ options.startOfWeek
+ )
+ );
+ } else {
+ for (
+ pos = min + (tickPositions[0] - min) % minorTickInterval;
+ pos <= max;
+ pos += minorTickInterval
+ ) {
+ // Very, very, tight grid lines (#5771)
+ if (pos === minorTickPositions[0]) {
+ break;
+ }
+ minorTickPositions.push(pos);
+ }
+ }
+ }
+
+ if (minorTickPositions.length !== 0) {
+ axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330
+ }
+ return minorTickPositions;
+ },
+
+ /**
+ * Adjust the min and max for the minimum range. Keep in mind that the
+ * series data is not yet processed, so we don't have information on data
+ * cropping and grouping, or updated axis.pointRange or series.pointRange.
+ * The data can't be processed until we have finally established min and
+ * max.
+ *
+ * @private
+ */
+ adjustForMinRange: function () {
+ var axis = this,
+ options = axis.options,
+ min = axis.min,
+ max = axis.max,
+ zoomOffset,
+ spaceAvailable,
+ closestDataRange,
+ i,
+ distance,
+ xData,
+ loopLength,
+ minArgs,
+ maxArgs,
+ minRange;
+
+ // Set the automatic minimum range based on the closest point distance
+ if (axis.isXAxis && axis.minRange === undefined && !axis.isLog) {
+
+ if (defined(options.min) || defined(options.max)) {
+ axis.minRange = null; // don't do this again
+
+ } else {
+
+ // Find the closest distance between raw data points, as opposed
+ // to closestPointRange that applies to processed points
+ // (cropped and grouped)
+ each(axis.series, function (series) {
+ xData = series.xData;
+ loopLength = series.xIncrement ? 1 : xData.length - 1;
+ for (i = loopLength; i > 0; i--) {
+ distance = xData[i] - xData[i - 1];
+ if (
+ closestDataRange === undefined ||
+ distance < closestDataRange
+ ) {
+ closestDataRange = distance;
+ }
+ }
+ });
+ axis.minRange = Math.min(
+ closestDataRange * 5,
+ axis.dataMax - axis.dataMin
+ );
+ }
+ }
+
+ // if minRange is exceeded, adjust
+ if (max - min < axis.minRange) {
+
+ spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange;
+ minRange = axis.minRange;
+ zoomOffset = (minRange - max + min) / 2;
+
+ // if min and max options have been set, don't go beyond it
+ minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
+ // If space is available, stay within the data range
+ if (spaceAvailable) {
+ minArgs[2] = axis.isLog ?
+ axis.log2lin(axis.dataMin) :
+ axis.dataMin;
+ }
+ min = arrayMax(minArgs);
+
+ maxArgs = [min + minRange, pick(options.max, min + minRange)];
+ // If space is availabe, stay within the data range
+ if (spaceAvailable) {
+ maxArgs[2] = axis.isLog ?
+ axis.log2lin(axis.dataMax) :
+ axis.dataMax;
+ }
+
+ max = arrayMin(maxArgs);
+
+ // now if the max is adjusted, adjust the min back
+ if (max - min < minRange) {
+ minArgs[0] = max - minRange;
+ minArgs[1] = pick(options.min, max - minRange);
+ min = arrayMax(minArgs);
+ }
+ }
+
+ // Record modified extremes
+ axis.min = min;
+ axis.max = max;
+ },
+
+ /**
+ * Find the closestPointRange across all series.
+ *
+ * @private
+ */
+ getClosest: function () {
+ var ret;
+
+ if (this.categories) {
+ ret = 1;
+ } else {
+ each(this.series, function (series) {
+ var seriesClosest = series.closestPointRange,
+ visible = series.visible ||
+ !series.chart.options.chart.ignoreHiddenSeries;
+
+ if (
+ !series.noSharedTooltip &&
+ defined(seriesClosest) &&
+ visible
+ ) {
+ ret = defined(ret) ?
+ Math.min(ret, seriesClosest) :
+ seriesClosest;
+ }
+ });
+ }
+ return ret;
+ },
+
+ /**
+ * When a point name is given and no x, search for the name in the existing
+ * categories, or if categories aren't provided, search names or create a
+ * new category (#2522).
+ *
+ * @private
+ *
+ * @param {Point}
+ * The point to inspect.
+ *
+ * @return {Number}
+ * The X value that the point is given.
+ */
+ nameToX: function (point) {
+ var explicitCategories = isArray(this.categories),
+ names = explicitCategories ? this.categories : this.names,
+ nameX = point.options.x,
+ x;
+
+ point.series.requireSorting = false;
+
+ if (!defined(nameX)) {
+ nameX = this.options.uniqueNames === false ?
+ point.series.autoIncrement() :
+ inArray(point.name, names);
+ }
+ if (nameX === -1) { // The name is not found in currenct categories
+ if (!explicitCategories) {
+ x = names.length;
+ }
+ } else {
+ x = nameX;
+ }
+
+ // Write the last point's name to the names array
+ if (x !== undefined) {
+ this.names[x] = point.name;
+ }
+
+ return x;
+ },
+
+ /**
+ * When changes have been done to series data, update the axis.names.
+ *
+ * @private
+ */
+ updateNames: function () {
+ var axis = this;
+
+ if (this.names.length > 0) {
+ this.names.length = 0;
+ this.minRange = this.userMinRange; // Reset
+ each(this.series || [], function (series) {
+
+ // Reset incrementer (#5928)
+ series.xIncrement = null;
+
+ // When adding a series, points are not yet generated
+ if (!series.points || series.isDirtyData) {
+ series.processData();
+ series.generatePoints();
+ }
+
+ each(series.points, function (point, i) {
+ var x;
+ if (point.options) {
+ x = axis.nameToX(point);
+ if (x !== undefined && x !== point.x) {
+ point.x = x;
+ series.xData[i] = x;
+ }
+ }
+ });
+ });
+ }
+ },
+
+ /**
+ * Update translation information.
+ *
+ * @private
+ */
+ setAxisTranslation: function (saveOld) {
+ var axis = this,
+ range = axis.max - axis.min,
+ pointRange = axis.axisPointRange || 0,
+ closestPointRange,
+ minPointOffset = 0,
+ pointRangePadding = 0,
+ linkedParent = axis.linkedParent,
+ ordinalCorrection,
+ hasCategories = !!axis.categories,
+ transA = axis.transA,
+ isXAxis = axis.isXAxis;
+
+ // Adjust translation for padding. Y axis with categories need to go
+ // through the same (#1784).
+ if (isXAxis || hasCategories || pointRange) {
+
+ // Get the closest points
+ closestPointRange = axis.getClosest();
+
+ if (linkedParent) {
+ minPointOffset = linkedParent.minPointOffset;
+ pointRangePadding = linkedParent.pointRangePadding;
+ } else {
+ each(axis.series, function (series) {
+ var seriesPointRange = hasCategories ?
+ 1 :
+ (
+ isXAxis ?
+ pick(
+ series.options.pointRange,
+ closestPointRange,
+ 0
+ ) :
+ (axis.axisPointRange || 0)
+ ), // #2806
+ pointPlacement = series.options.pointPlacement;
+
+ pointRange = Math.max(pointRange, seriesPointRange);
+
+ if (!axis.single) {
+ // minPointOffset is the value padding to the left of
+ // the axis in order to make room for points with a
+ // pointRange, typically columns. When the
+ // pointPlacement option is 'between' or 'on', this
+ // padding does not apply.
+ minPointOffset = Math.max(
+ minPointOffset,
+ isString(pointPlacement) ? 0 : seriesPointRange / 2
+ );
+
+ // Determine the total padding needed to the length of
+ // the axis to make room for the pointRange. If the
+ // series' pointPlacement is 'on', no padding is added.
+ pointRangePadding = Math.max(
+ pointRangePadding,
+ pointPlacement === 'on' ? 0 : seriesPointRange
+ );
+ }
+ });
+ }
+
+ // Record minPointOffset and pointRangePadding
+ ordinalCorrection = axis.ordinalSlope && closestPointRange ?
+ axis.ordinalSlope / closestPointRange :
+ 1; // #988, #1853
+ axis.minPointOffset = minPointOffset =
+ minPointOffset * ordinalCorrection;
+ axis.pointRangePadding =
+ pointRangePadding = pointRangePadding * ordinalCorrection;
+
+ // pointRange means the width reserved for each point, like in a
+ // column chart
+ axis.pointRange = Math.min(pointRange, range);
+
+ // closestPointRange means the closest distance between points. In
+ // columns it is mostly equal to pointRange, but in lines pointRange
+ // is 0 while closestPointRange is some other value
+ if (isXAxis) {
+ axis.closestPointRange = closestPointRange;
+ }
+ }
+
+ // Secondary values
+ if (saveOld) {
+ axis.oldTransA = transA;
+ }
+ axis.translationSlope = axis.transA = transA =
+ axis.options.staticScale ||
+ axis.len / ((range + pointRangePadding) || 1);
+
+ // Translation addend
+ axis.transB = axis.horiz ? axis.left : axis.bottom;
+ axis.minPixelPadding = transA * minPointOffset;
+ },
+
+ minFromRange: function () {
+ return this.max - this.range;
+ },
+
+ /**
+ * Set the tick positions to round values and optionally extend the extremes
+ * to the nearest tick.
+ *
+ * @private
+ */
+ setTickInterval: function (secondPass) {
+ var axis = this,
+ chart = axis.chart,
+ options = axis.options,
+ isLog = axis.isLog,
+ log2lin = axis.log2lin,
+ isDatetimeAxis = axis.isDatetimeAxis,
+ isXAxis = axis.isXAxis,
+ isLinked = axis.isLinked,
+ maxPadding = options.maxPadding,
+ minPadding = options.minPadding,
+ length,
+ linkedParentExtremes,
+ tickIntervalOption = options.tickInterval,
+ minTickInterval,
+ tickPixelIntervalOption = options.tickPixelInterval,
+ categories = axis.categories,
+ threshold = axis.threshold,
+ softThreshold = axis.softThreshold,
+ thresholdMin,
+ thresholdMax,
+ hardMin,
+ hardMax;
+
+ if (!isDatetimeAxis && !categories && !isLinked) {
+ this.getTickAmount();
+ }
+
+ // Min or max set either by zooming/setExtremes or initial options
+ hardMin = pick(axis.userMin, options.min);
+ hardMax = pick(axis.userMax, options.max);
+
+ // Linked axis gets the extremes from the parent axis
+ if (isLinked) {
+ axis.linkedParent = chart[axis.coll][options.linkedTo];
+ linkedParentExtremes = axis.linkedParent.getExtremes();
+ axis.min = pick(
+ linkedParentExtremes.min,
+ linkedParentExtremes.dataMin
+ );
+ axis.max = pick(
+ linkedParentExtremes.max,
+ linkedParentExtremes.dataMax
+ );
+ if (options.type !== axis.linkedParent.options.type) {
+ H.error(11, 1); // Can't link axes of different type
+ }
+
+ // Initial min and max from the extreme data values
+ } else {
+
+ // Adjust to hard threshold
+ if (!softThreshold && defined(threshold)) {
+ if (axis.dataMin >= threshold) {
+ thresholdMin = threshold;
+ minPadding = 0;
+ } else if (axis.dataMax <= threshold) {
+ thresholdMax = threshold;
+ maxPadding = 0;
+ }
+ }
+
+ axis.min = pick(hardMin, thresholdMin, axis.dataMin);
+ axis.max = pick(hardMax, thresholdMax, axis.dataMax);
+
+ }
+
+ if (isLog) {
+ if (
+ axis.positiveValuesOnly &&
+ !secondPass &&
+ Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0
+ ) { // #978
+ H.error(10, 1); // Can't plot negative values on log axis
+ }
+ // The correctFloat cures #934, float errors on full tens. But it
+ // was too aggressive for #4360 because of conversion back to lin,
+ // therefore use precision 15.
+ axis.min = correctFloat(log2lin(axis.min), 15);
+ axis.max = correctFloat(log2lin(axis.max), 15);
+ }
+
+ // handle zoomed range
+ if (axis.range && defined(axis.max)) {
+ axis.userMin = axis.min = hardMin =
+ Math.max(axis.dataMin, axis.minFromRange()); // #618, #6773
+ axis.userMax = hardMax = axis.max;
+
+ axis.range = null; // don't use it when running setExtremes
+ }
+
+ // Hook for Highstock Scroller. Consider combining with beforePadding.
+ fireEvent(axis, 'foundExtremes');
+
+ // Hook for adjusting this.min and this.max. Used by bubble series.
+ if (axis.beforePadding) {
+ axis.beforePadding();
+ }
+
+ // adjust min and max for the minimum range
+ axis.adjustForMinRange();
+
+ // Pad the values to get clear of the chart's edges. To avoid
+ // tickInterval taking the padding into account, we do this after
+ // computing tick interval (#1337).
+ if (
+ !categories &&
+ !axis.axisPointRange &&
+ !axis.usePercentage &&
+ !isLinked &&
+ defined(axis.min) &&
+ defined(axis.max)
+ ) {
+ length = axis.max - axis.min;
+ if (length) {
+ if (!defined(hardMin) && minPadding) {
+ axis.min -= length * minPadding;
+ }
+ if (!defined(hardMax) && maxPadding) {
+ axis.max += length * maxPadding;
+ }
+ }
+ }
+
+ // Handle options for floor, ceiling, softMin and softMax (#6359)
+ if (isNumber(options.softMin) && !isNumber(axis.userMin)) {
+ axis.min = Math.min(axis.min, options.softMin);
+ }
+ if (isNumber(options.softMax) && !isNumber(axis.userMax)) {
+ axis.max = Math.max(axis.max, options.softMax);
+ }
+ if (isNumber(options.floor)) {
+ axis.min = Math.max(axis.min, options.floor);
+ }
+ if (isNumber(options.ceiling)) {
+ axis.max = Math.min(axis.max, options.ceiling);
+ }
+
+
+ // When the threshold is soft, adjust the extreme value only if the data
+ // extreme and the padded extreme land on either side of the threshold.
+ // For example, a series of [0, 1, 2, 3] would make the yAxis add a tick
+ // for -1 because of the default minPadding and startOnTick options.
+ // This is prevented by the softThreshold option.
+ if (softThreshold && defined(axis.dataMin)) {
+ threshold = threshold || 0;
+ if (
+ !defined(hardMin) &&
+ axis.min < threshold &&
+ axis.dataMin >= threshold
+ ) {
+ axis.min = threshold;
+
+ } else if (
+ !defined(hardMax) &&
+ axis.max > threshold &&
+ axis.dataMax <= threshold
+ ) {
+ axis.max = threshold;
+ }
+ }
+
+
+ // get tickInterval
+ if (
+ axis.min === axis.max ||
+ axis.min === undefined ||
+ axis.max === undefined
+ ) {
+ axis.tickInterval = 1;
+
+ } else if (
+ isLinked &&
+ !tickIntervalOption &&
+ tickPixelIntervalOption ===
+ axis.linkedParent.options.tickPixelInterval
+ ) {
+ axis.tickInterval = tickIntervalOption =
+ axis.linkedParent.tickInterval;
+
+ } else {
+ axis.tickInterval = pick(
+ tickIntervalOption,
+ this.tickAmount ?
+ ((axis.max - axis.min) / Math.max(this.tickAmount - 1, 1)) :
+ undefined,
+ // For categoried axis, 1 is default, for linear axis use
+ // tickPix
+ categories ?
+ 1 :
+ // don't let it be more than the data range
+ (axis.max - axis.min) * tickPixelIntervalOption /
+ Math.max(axis.len, tickPixelIntervalOption)
+ );
+ }
+
+ /**
+ * Now we're finished detecting min and max, crop and group series data.
+ * This is in turn needed in order to find tick positions in
+ * ordinal axes.
+ */
+ if (isXAxis && !secondPass) {
+ each(axis.series, function (series) {
+ series.processData(
+ axis.min !== axis.oldMin || axis.max !== axis.oldMax
+ );
+ });
+ }
+
+ // set the translation factor used in translate function
+ axis.setAxisTranslation(true);
+
+ // hook for ordinal axes and radial axes
+ if (axis.beforeSetTickPositions) {
+ axis.beforeSetTickPositions();
+ }
+
+ // hook for extensions, used in Highstock ordinal axes
+ if (axis.postProcessTickInterval) {
+ axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
+ }
+
+ // In column-like charts, don't cramp in more ticks than there are
+ // points (#1943, #4184)
+ if (axis.pointRange && !tickIntervalOption) {
+ axis.tickInterval = Math.max(axis.pointRange, axis.tickInterval);
+ }
+
+ // Before normalizing the tick interval, handle minimum tick interval.
+ // This applies only if tickInterval is not defined.
+ minTickInterval = pick(
+ options.minTickInterval,
+ axis.isDatetimeAxis && axis.closestPointRange
+ );
+ if (!tickIntervalOption && axis.tickInterval < minTickInterval) {
+ axis.tickInterval = minTickInterval;
+ }
+
+ // for linear axes, get magnitude and normalize the interval
+ if (!isDatetimeAxis && !isLog && !tickIntervalOption) {
+ axis.tickInterval = normalizeTickInterval(
+ axis.tickInterval,
+ null,
+ getMagnitude(axis.tickInterval),
+ // If the tick interval is between 0.5 and 5 and the axis max is
+ // in the order of thousands, chances are we are dealing with
+ // years. Don't allow decimals. #3363.
+ pick(
+ options.allowDecimals,
+ !(
+ axis.tickInterval > 0.5 &&
+ axis.tickInterval < 5 &&
+ axis.max > 1000 &&
+ axis.max < 9999
+ )
+ ),
+ !!this.tickAmount
+ );
+ }
+
+ // Prevent ticks from getting so close that we can't draw the labels
+ if (!this.tickAmount) {
+ axis.tickInterval = axis.unsquish();
+ }
+
+ this.setTickPositions();
+ },
+
+ /**
+ * Now we have computed the normalized tickInterval, get the tick positions
+ */
+ setTickPositions: function () {
+
+ var options = this.options,
+ tickPositions,
+ tickPositionsOption = options.tickPositions,
+ minorTickIntervalOption = this.getMinorTickInterval(),
+ tickPositioner = options.tickPositioner,
+ startOnTick = options.startOnTick,
+ endOnTick = options.endOnTick;
+
+ // Set the tickmarkOffset
+ this.tickmarkOffset = (
+ this.categories &&
+ options.tickmarkPlacement === 'between' &&
+ this.tickInterval === 1
+ ) ? 0.5 : 0; // #3202
+
+
+ // get minorTickInterval
+ this.minorTickInterval =
+ minorTickIntervalOption === 'auto' &&
+ this.tickInterval ?
+ this.tickInterval / 5 :
+ minorTickIntervalOption;
+
+ // When there is only one point, or all points have the same value on
+ // this axis, then min and max are equal and tickPositions.length is 0
+ // or 1. In this case, add some padding in order to center the point,
+ // but leave it with one tick. #1337.
+ this.single =
+ this.min === this.max &&
+ defined(this.min) &&
+ !this.tickAmount &&
+ (
+ // Data is on integer (#6563)
+ parseInt(this.min, 10) === this.min ||
+
+ // Between integers and decimals are not allowed (#6274)
+ options.allowDecimals !== false
+ );
+
+ // Find the tick positions. Work on a copy (#1565)
+ this.tickPositions = tickPositions =
+ tickPositionsOption && tickPositionsOption.slice();
+ if (!tickPositions) {
+
+ if (this.isDatetimeAxis) {
+ tickPositions = this.getTimeTicks(
+ this.normalizeTimeTickInterval(
+ this.tickInterval,
+ options.units
+ ),
+ this.min,
+ this.max,
+ options.startOfWeek,
+ this.ordinalPositions,
+ this.closestPointRange,
+ true
+ );
+ } else if (this.isLog) {
+ tickPositions = this.getLogTickPositions(
+ this.tickInterval,
+ this.min,
+ this.max
+ );
+ } else {
+ tickPositions = this.getLinearTickPositions(
+ this.tickInterval,
+ this.min,
+ this.max
+ );
+ }
+
+ // Too dense ticks, keep only the first and last (#4477)
+ if (tickPositions.length > this.len) {
+ tickPositions = [tickPositions[0], tickPositions.pop()];
+ // Reduce doubled value (#7339)
+ if (tickPositions[0] === tickPositions[1]) {
+ tickPositions.length = 1;
+ }
+ }
+
+ this.tickPositions = tickPositions;
+
+ // Run the tick positioner callback, that allows modifying auto tick
+ // positions.
+ if (tickPositioner) {
+ tickPositioner = tickPositioner.apply(
+ this,
+ [this.min, this.max]
+ );
+ if (tickPositioner) {
+ this.tickPositions = tickPositions = tickPositioner;
+ }
+ }
+
+ }
+
+ // Reset min/max or remove extremes based on start/end on tick
+ this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor
+ this.trimTicks(tickPositions, startOnTick, endOnTick);
+ if (!this.isLinked) {
+
+ // Substract half a unit (#2619, #2846, #2515, #3390),
+ // but not in case of multiple ticks (#6897)
+ if (this.single && tickPositions.length < 2) {
+ this.min -= 0.5;
+ this.max += 0.5;
+ }
+ if (!tickPositionsOption && !tickPositioner) {
+ this.adjustTickAmount();
+ }
+ }
+ },
+
+ /**
+ * Handle startOnTick and endOnTick by either adapting to padding min/max or
+ * rounded min/max. Also handle single data points.
+ *
+ * @private
+ */
+ trimTicks: function (tickPositions, startOnTick, endOnTick) {
+ var roundedMin = tickPositions[0],
+ roundedMax = tickPositions[tickPositions.length - 1],
+ minPointOffset = this.minPointOffset || 0;
+
+ if (!this.isLinked) {
+ if (startOnTick && roundedMin !== -Infinity) { // #6502
+ this.min = roundedMin;
+ } else {
+ while (this.min - minPointOffset > tickPositions[0]) {
+ tickPositions.shift();
+ }
+ }
+
+ if (endOnTick) {
+ this.max = roundedMax;
+ } else {
+ while (this.max + minPointOffset <
+ tickPositions[tickPositions.length - 1]) {
+ tickPositions.pop();
+ }
+ }
+
+ // If no tick are left, set one tick in the middle (#3195)
+ if (
+ tickPositions.length === 0 &&
+ defined(roundedMin) &&
+ !this.options.tickPositions
+ ) {
+ tickPositions.push((roundedMax + roundedMin) / 2);
+ }
+ }
+ },
+
+ /**
+ * Check if there are multiple axes in the same pane.
+ *
+ * @private
+ * @return {Boolean}
+ * True if there are other axes.
+ */
+ alignToOthers: function () {
+ var others = {}, // Whether there is another axis to pair with this one
+ hasOther,
+ options = this.options;
+
+ if (
+ // Only if alignTicks is true
+ this.chart.options.chart.alignTicks !== false &&
+ options.alignTicks !== false &&
+
+ // Don't try to align ticks on a log axis, they are not evenly
+ // spaced (#6021)
+ !this.isLog
+ ) {
+ each(this.chart[this.coll], function (axis) {
+ var otherOptions = axis.options,
+ horiz = axis.horiz,
+ key = [
+ horiz ? otherOptions.left : otherOptions.top,
+ otherOptions.width,
+ otherOptions.height,
+ otherOptions.pane
+ ].join(',');
+
+
+ if (axis.series.length) { // #4442
+ if (others[key]) {
+ hasOther = true; // #4201
+ } else {
+ others[key] = 1;
+ }
+ }
+ });
+ }
+ return hasOther;
+ },
+
+ /**
+ * Find the max ticks of either the x and y axis collection, and record it
+ * in `this.tickAmount`.
+ *
+ * @private
+ */
+ getTickAmount: function () {
+ var options = this.options,
+ tickAmount = options.tickAmount,
+ tickPixelInterval = options.tickPixelInterval;
+
+ if (
+ !defined(options.tickInterval) &&
+ this.len < tickPixelInterval &&
+ !this.isRadial &&
+ !this.isLog &&
+ options.startOnTick &&
+ options.endOnTick
+ ) {
+ tickAmount = 2;
+ }
+
+ if (!tickAmount && this.alignToOthers()) {
+ // Add 1 because 4 tick intervals require 5 ticks (including first
+ // and last)
+ tickAmount = Math.ceil(this.len / tickPixelInterval) + 1;
+ }
+
+ // For tick amounts of 2 and 3, compute five ticks and remove the
+ // intermediate ones. This prevents the axis from adding ticks that are
+ // too far away from the data extremes.
+ if (tickAmount < 4) {
+ this.finalTickAmt = tickAmount;
+ tickAmount = 5;
+ }
+
+ this.tickAmount = tickAmount;
+ },
+
+ /**
+ * When using multiple axes, adjust the number of ticks to match the highest
+ * number of ticks in that group.
+ *
+ * @private
+ */
+ adjustTickAmount: function () {
+ var tickInterval = this.tickInterval,
+ tickPositions = this.tickPositions,
+ tickAmount = this.tickAmount,
+ finalTickAmt = this.finalTickAmt,
+ currentTickAmount = tickPositions && tickPositions.length,
+ threshold = pick(this.threshold, this.softThreshold ? 0 : null),
+ i,
+ len;
+
+ if (this.hasData()) {
+ if (currentTickAmount < tickAmount) {
+ while (tickPositions.length < tickAmount) {
+
+ // Extend evenly for both sides unless we're on the
+ // threshold (#3965)
+ if (
+ tickPositions.length % 2 ||
+ this.min === threshold
+ ) {
+ // to the end
+ tickPositions.push(correctFloat(
+ tickPositions[tickPositions.length - 1] +
+ tickInterval
+ ));
+ } else {
+ // to the start
+ tickPositions.unshift(correctFloat(
+ tickPositions[0] - tickInterval
+ ));
+ }
+ }
+ this.transA *= (currentTickAmount - 1) / (tickAmount - 1);
+ this.min = tickPositions[0];
+ this.max = tickPositions[tickPositions.length - 1];
+
+ // We have too many ticks, run second pass to try to reduce ticks
+ } else if (currentTickAmount > tickAmount) {
+ this.tickInterval *= 2;
+ this.setTickPositions();
+ }
+
+ // The finalTickAmt property is set in getTickAmount
+ if (defined(finalTickAmt)) {
+ i = len = tickPositions.length;
+ while (i--) {
+ if (
+ // Remove every other tick
+ (finalTickAmt === 3 && i % 2 === 1) ||
+ // Remove all but first and last
+ (finalTickAmt <= 2 && i > 0 && i < len - 1)
+ ) {
+ tickPositions.splice(i, 1);
+ }
+ }
+ this.finalTickAmt = undefined;
+ }
+ }
+ },
+
+ /**
+ * Set the scale based on data min and max, user set min and max or options.
+ *
+ * @private
+ */
+ setScale: function () {
+ var axis = this,
+ isDirtyData,
+ isDirtyAxisLength;
+
+ axis.oldMin = axis.min;
+ axis.oldMax = axis.max;
+ axis.oldAxisLength = axis.len;
+
+ // set the new axisLength
+ axis.setAxisSize();
+ isDirtyAxisLength = axis.len !== axis.oldAxisLength;
+
+ // is there new data?
+ each(axis.series, function (series) {
+ if (
+ series.isDirtyData ||
+ series.isDirty ||
+ // When x axis is dirty, we need new data extremes for y as well
+ series.xAxis.isDirty
+ ) {
+ isDirtyData = true;
+ }
+ });
+
+ // do we really need to go through all this?
+ if (
+ isDirtyAxisLength ||
+ isDirtyData ||
+ axis.isLinked ||
+ axis.forceRedraw ||
+ axis.userMin !== axis.oldUserMin ||
+ axis.userMax !== axis.oldUserMax ||
+ axis.alignToOthers()
+ ) {
+
+ if (axis.resetStacks) {
+ axis.resetStacks();
+ }
+
+ axis.forceRedraw = false;
+
+ // get data extremes if needed
+ axis.getSeriesExtremes();
+
+ // get fixed positions based on tickInterval
+ axis.setTickInterval();
+
+ // record old values to decide whether a rescale is necessary later
+ // on (#540)
+ axis.oldUserMin = axis.userMin;
+ axis.oldUserMax = axis.userMax;
+
+ // Mark as dirty if it is not already set to dirty and extremes have
+ // changed. #595.
+ if (!axis.isDirty) {
+ axis.isDirty =
+ isDirtyAxisLength ||
+ axis.min !== axis.oldMin ||
+ axis.max !== axis.oldMax;
+ }
+ } else if (axis.cleanStacks) {
+ axis.cleanStacks();
+ }
+ },
+
+ /**
+ * Set the minimum and maximum of the axes after render time. If the
+ * `startOnTick` and `endOnTick` options are true, the minimum and maximum
+ * values are rounded off to the nearest tick. To prevent this, these
+ * options can be set to false before calling setExtremes. Also, setExtremes
+ * will not allow a range lower than the `minRange` option, which by default
+ * is the range of five points.
+ *
+ * @param {Number} [newMin]
+ * The new minimum value.
+ * @param {Number} [newMax]
+ * The new maximum value.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart or wait for an explicit call to
+ * {@link Highcharts.Chart#redraw}
+ * @param {AnimationOptions} [animation=true]
+ * Enable or modify animations.
+ * @param {Object} [eventArguments]
+ * Arguments to be accessed in event handler.
+ *
+ * @sample highcharts/members/axis-setextremes/
+ * Set extremes from a button
+ * @sample highcharts/members/axis-setextremes-datetime/
+ * Set extremes on a datetime axis
+ * @sample highcharts/members/axis-setextremes-off-ticks/
+ * Set extremes off ticks
+ * @sample stock/members/axis-setextremes/
+ * Set extremes in Highstock
+ * @sample maps/members/axis-setextremes/
+ * Set extremes in Highmaps
+ */
+ setExtremes: function (newMin, newMax, redraw, animation, eventArguments) {
+ var axis = this,
+ chart = axis.chart;
+
+ redraw = pick(redraw, true); // defaults to true
+
+ each(axis.series, function (serie) {
+ delete serie.kdTree;
+ });
+
+ // Extend the arguments with min and max
+ eventArguments = extend(eventArguments, {
+ min: newMin,
+ max: newMax
+ });
+
+ // Fire the event
+ fireEvent(axis, 'setExtremes', eventArguments, function () {
+
+ axis.userMin = newMin;
+ axis.userMax = newMax;
+ axis.eventArgs = eventArguments;
+
+ if (redraw) {
+ chart.redraw(animation);
+ }
+ });
+ },
+
+ /**
+ * Overridable method for zooming chart. Pulled out in a separate method to
+ * allow overriding in stock charts.
+ *
+ * @private
+ */
+ zoom: function (newMin, newMax) {
+ var dataMin = this.dataMin,
+ dataMax = this.dataMax,
+ options = this.options,
+ min = Math.min(dataMin, pick(options.min, dataMin)),
+ max = Math.max(dataMax, pick(options.max, dataMax));
+
+ if (newMin !== this.min || newMax !== this.max) { // #5790
+
+ // Prevent pinch zooming out of range. Check for defined is for
+ // #1946. #1734.
+ if (!this.allowZoomOutside) {
+ // #6014, sometimes newMax will be smaller than min (or newMin
+ // will be larger than max).
+ if (defined(dataMin)) {
+ if (newMin < min) {
+ newMin = min;
+ }
+ if (newMin > max) {
+ newMin = max;
+ }
+ }
+ if (defined(dataMax)) {
+ if (newMax < min) {
+ newMax = min;
+ }
+ if (newMax > max) {
+ newMax = max;
+ }
+ }
+ }
+
+ // In full view, displaying the reset zoom button is not required
+ this.displayBtn = newMin !== undefined || newMax !== undefined;
+
+ // Do it
+ this.setExtremes(
+ newMin,
+ newMax,
+ false,
+ undefined,
+ { trigger: 'zoom' }
+ );
+ }
+
+ return true;
+ },
+
+ /**
+ * Update the axis metrics.
+ *
+ * @private
+ */
+ setAxisSize: function () {
+ var chart = this.chart,
+ options = this.options,
+ // [top, right, bottom, left]
+ offsets = options.offsets || [0, 0, 0, 0],
+ horiz = this.horiz,
+
+ // Check for percentage based input values. Rounding fixes problems
+ // with column overflow and plot line filtering (#4898, #4899)
+ width = this.width = Math.round(H.relativeLength(
+ pick(
+ options.width,
+ chart.plotWidth - offsets[3] + offsets[1]
+ ),
+ chart.plotWidth
+ )),
+ height = this.height = Math.round(H.relativeLength(
+ pick(
+ options.height,
+ chart.plotHeight - offsets[0] + offsets[2]
+ ),
+ chart.plotHeight
+ )),
+ top = this.top = Math.round(H.relativeLength(
+ pick(options.top, chart.plotTop + offsets[0]),
+ chart.plotHeight,
+ chart.plotTop
+ )),
+ left = this.left = Math.round(H.relativeLength(
+ pick(options.left, chart.plotLeft + offsets[3]),
+ chart.plotWidth,
+ chart.plotLeft
+ ));
+
+ // Expose basic values to use in Series object and navigator
+ this.bottom = chart.chartHeight - height - top;
+ this.right = chart.chartWidth - width - left;
+
+ // Direction agnostic properties
+ this.len = Math.max(horiz ? width : height, 0); // Math.max fixes #905
+ this.pos = horiz ? left : top; // distance from SVG origin
+ },
+
+ /**
+ * The returned object literal from the {@link Highcharts.Axis#getExtremes}
+ * function.
+ *
+ * @typedef {Object} Extremes
+ * @property {Number} dataMax
+ * The maximum value of the axis' associated series.
+ * @property {Number} dataMin
+ * The minimum value of the axis' associated series.
+ * @property {Number} max
+ * The maximum axis value, either automatic or set manually. If
+ * the `max` option is not set, `maxPadding` is 0 and `endOnTick`
+ * is false, this value will be the same as `dataMax`.
+ * @property {Number} min
+ * The minimum axis value, either automatic or set manually. If
+ * the `min` option is not set, `minPadding` is 0 and
+ * `startOnTick` is false, this value will be the same
+ * as `dataMin`.
+ */
+ /**
+ * Get the current extremes for the axis.
+ *
+ * @returns {Extremes}
+ * An object containing extremes information.
+ *
+ * @sample highcharts/members/axis-getextremes/
+ * Report extremes by click on a button
+ * @sample maps/members/axis-getextremes/
+ * Get extremes in Highmaps
+ */
+ getExtremes: function () {
+ var axis = this,
+ isLog = axis.isLog,
+ lin2log = axis.lin2log;
+
+ return {
+ min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
+ max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
+ dataMin: axis.dataMin,
+ dataMax: axis.dataMax,
+ userMin: axis.userMin,
+ userMax: axis.userMax
+ };
+ },
+
+ /**
+ * Get the zero plane either based on zero or on the min or max value.
+ * Used in bar and area plots.
+ *
+ * @param {Number} threshold
+ * The threshold in axis values.
+ *
+ * @return {Number}
+ * The translated threshold position in terms of pixels, and
+ * corrected to stay within the axis bounds.
+ */
+ getThreshold: function (threshold) {
+ var axis = this,
+ isLog = axis.isLog,
+ lin2log = axis.lin2log,
+ realMin = isLog ? lin2log(axis.min) : axis.min,
+ realMax = isLog ? lin2log(axis.max) : axis.max;
+
+ if (threshold === null) {
+ threshold = realMin;
+ } else if (realMin > threshold) {
+ threshold = realMin;
+ } else if (realMax < threshold) {
+ threshold = realMax;
+ }
+
+ return axis.translate(threshold, 0, 1, 0, 1);
+ },
+
+ /**
+ * Compute auto alignment for the axis label based on which side the axis is
+ * on and the given rotation for the label.
+ *
+ * @param {Number} rotation
+ * The rotation in degrees as set by either the `rotation` or
+ * `autoRotation` options.
+ * @private
+ */
+ autoLabelAlign: function (rotation) {
+ var ret,
+ angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;
+
+ if (angle > 15 && angle < 165) {
+ ret = 'right';
+ } else if (angle > 195 && angle < 345) {
+ ret = 'left';
+ } else {
+ ret = 'center';
+ }
+ return ret;
+ },
+
+ /**
+ * Get the tick length and width for the axis based on axis options.
+ *
+ * @private
+ *
+ * @param {String} prefix
+ * 'tick' or 'minorTick'
+ * @return {Array.}
+ * An array of tickLength and tickWidth
+ */
+ tickSize: function (prefix) {
+ var options = this.options,
+ tickLength = options[prefix + 'Length'],
+ tickWidth = pick(
+ options[prefix + 'Width'],
+ prefix === 'tick' && this.isXAxis ? 1 : 0 // X axis default 1
+ );
+
+ if (tickWidth && tickLength) {
+ // Negate the length
+ if (options[prefix + 'Position'] === 'inside') {
+ tickLength = -tickLength;
+ }
+ return [tickLength, tickWidth];
+ }
+
+ },
+
+ /**
+ * Return the size of the labels.
+ *
+ * @private
+ */
+ labelMetrics: function () {
+ var index = this.tickPositions && this.tickPositions[0] || 0;
+ return this.chart.renderer.fontMetrics(
+ this.options.labels.style && this.options.labels.style.fontSize,
+ this.ticks[index] && this.ticks[index].label
+ );
+ },
+
+ /**
+ * Prevent the ticks from getting so close we can't draw the labels. On a
+ * horizontal axis, this is handled by rotating the labels, removing ticks
+ * and adding ellipsis. On a vertical axis remove ticks and add ellipsis.
+ *
+ * @private
+ */
+ unsquish: function () {
+ var labelOptions = this.options.labels,
+ horiz = this.horiz,
+ tickInterval = this.tickInterval,
+ newTickInterval = tickInterval,
+ slotSize = this.len / (
+ ((this.categories ? 1 : 0) + this.max - this.min) / tickInterval
+ ),
+ rotation,
+ rotationOption = labelOptions.rotation,
+ labelMetrics = this.labelMetrics(),
+ step,
+ bestScore = Number.MAX_VALUE,
+ autoRotation,
+ // Return the multiple of tickInterval that is needed to avoid
+ // collision
+ getStep = function (spaceNeeded) {
+ var step = spaceNeeded / (slotSize || 1);
+ step = step > 1 ? Math.ceil(step) : 1;
+ return step * tickInterval;
+ };
+
+ if (horiz) {
+ autoRotation = !labelOptions.staggerLines &&
+ !labelOptions.step &&
+ ( // #3971
+ defined(rotationOption) ?
+ [rotationOption] :
+ slotSize < pick(labelOptions.autoRotationLimit, 80) &&
+ labelOptions.autoRotation
+ );
+
+ if (autoRotation) {
+
+ // Loop over the given autoRotation options, and determine
+ // which gives the best score. The best score is that with
+ // the lowest number of steps and a rotation closest
+ // to horizontal.
+ each(autoRotation, function (rot) {
+ var score;
+
+ if (
+ rot === rotationOption ||
+ (rot && rot >= -90 && rot <= 90)
+ ) { // #3891
+
+ step = getStep(
+ Math.abs(labelMetrics.h / Math.sin(deg2rad * rot))
+ );
+
+ score = step + Math.abs(rot / 360);
+
+ if (score < bestScore) {
+ bestScore = score;
+ rotation = rot;
+ newTickInterval = step;
+ }
+ }
+ });
+ }
+
+ } else if (!labelOptions.step) { // #4411
+ newTickInterval = getStep(labelMetrics.h);
+ }
+
+ this.autoRotation = autoRotation;
+ this.labelRotation = pick(rotation, rotationOption);
+
+ return newTickInterval;
+ },
+
+ /**
+ * Get the general slot width for labels/categories on this axis. This may
+ * change between the pre-render (from Axis.getOffset) and the final tick
+ * rendering and placement.
+ *
+ * @private
+ * @return {Number}
+ * The pixel width allocated to each axis label.
+ */
+ getSlotWidth: function () {
+ // #5086, #1580, #1931
+ var chart = this.chart,
+ horiz = this.horiz,
+ labelOptions = this.options.labels,
+ slotCount = Math.max(
+ this.tickPositions.length - (this.categories ? 0 : 1),
+ 1
+ ),
+ marginLeft = chart.margin[3];
+
+ return (
+ horiz &&
+ (labelOptions.step || 0) < 2 &&
+ !labelOptions.rotation && // #4415
+ ((this.staggerLines || 1) * this.len) / slotCount
+ ) || (
+ !horiz && (
+ // #7028
+ (
+ labelOptions.style &&
+ parseInt(labelOptions.style.width, 10)
+ ) ||
+ (
+ marginLeft &&
+ (marginLeft - chart.spacing[3])
+ ) ||
+ chart.chartWidth * 0.33
+ )
+ );
+
+ },
+
+ /**
+ * Render the axis labels and determine whether ellipsis or rotation need
+ * to be applied.
+ *
+ * @private
+ */
+ renderUnsquish: function () {
+ var chart = this.chart,
+ renderer = chart.renderer,
+ tickPositions = this.tickPositions,
+ ticks = this.ticks,
+ labelOptions = this.options.labels,
+ horiz = this.horiz,
+ slotWidth = this.getSlotWidth(),
+ innerWidth = Math.max(
+ 1,
+ Math.round(slotWidth - 2 * (labelOptions.padding || 5))
+ ),
+ attr = {},
+ labelMetrics = this.labelMetrics(),
+ textOverflowOption = labelOptions.style &&
+ labelOptions.style.textOverflow,
+ css,
+ maxLabelLength = 0,
+ label,
+ i,
+ pos;
+
+ // Set rotation option unless it is "auto", like in gauges
+ if (!isString(labelOptions.rotation)) {
+ attr.rotation = labelOptions.rotation || 0; // #4443
+ }
+
+ // Get the longest label length
+ each(tickPositions, function (tick) {
+ tick = ticks[tick];
+ if (tick && tick.labelLength > maxLabelLength) {
+ maxLabelLength = tick.labelLength;
+ }
+ });
+ this.maxLabelLength = maxLabelLength;
+
+
+ // Handle auto rotation on horizontal axis
+ if (this.autoRotation) {
+
+ // Apply rotation only if the label is too wide for the slot, and
+ // the label is wider than its height.
+ if (
+ maxLabelLength > innerWidth &&
+ maxLabelLength > labelMetrics.h
+ ) {
+ attr.rotation = this.labelRotation;
+ } else {
+ this.labelRotation = 0;
+ }
+
+ // Handle word-wrap or ellipsis on vertical axis
+ } else if (slotWidth) {
+ // For word-wrap or ellipsis
+ css = { width: innerWidth + 'px' };
+
+ if (!textOverflowOption) {
+ css.textOverflow = 'clip';
+
+ // On vertical axis, only allow word wrap if there is room
+ // for more lines.
+ i = tickPositions.length;
+ while (!horiz && i--) {
+ pos = tickPositions[i];
+ label = ticks[pos].label;
+ if (label) {
+ // Reset ellipsis in order to get the correct
+ // bounding box (#4070)
+ if (
+ label.styles &&
+ label.styles.textOverflow === 'ellipsis'
+ ) {
+ label.css({ textOverflow: 'clip' });
+
+ // Set the correct width in order to read
+ // the bounding box height (#4678, #5034)
+ } else if (ticks[pos].labelLength > slotWidth) {
+ label.css({ width: slotWidth + 'px' });
+ }
+
+ if (
+ label.getBBox().height > (
+ this.len / tickPositions.length -
+ (labelMetrics.h - labelMetrics.f)
+ )
+ ) {
+ label.specCss = { textOverflow: 'ellipsis' };
+ }
+ }
+ }
+ }
+ }
+
+
+ // Add ellipsis if the label length is significantly longer than ideal
+ if (attr.rotation) {
+ css = {
+ width: (
+ maxLabelLength > chart.chartHeight * 0.5 ?
+ chart.chartHeight * 0.33 :
+ chart.chartHeight
+ ) + 'px'
+ };
+ if (!textOverflowOption) {
+ css.textOverflow = 'ellipsis';
+ }
+ }
+
+ // Set the explicit or automatic label alignment
+ this.labelAlign = labelOptions.align ||
+ this.autoLabelAlign(this.labelRotation);
+ if (this.labelAlign) {
+ attr.align = this.labelAlign;
+ }
+
+ // Apply general and specific CSS
+ each(tickPositions, function (pos) {
+ var tick = ticks[pos],
+ label = tick && tick.label;
+ if (label) {
+ // This needs to go before the CSS in old IE (#4502)
+ label.attr(attr);
+
+ if (css) {
+ label.css(merge(css, label.specCss));
+ }
+ delete label.specCss;
+ tick.rotation = attr.rotation;
+ }
+ });
+
+ // Note: Why is this not part of getLabelPosition?
+ this.tickRotCorr = renderer.rotCorr(
+ labelMetrics.b,
+ this.labelRotation || 0,
+ this.side !== 0
+ );
+ },
+
+ /**
+ * Return true if the axis has associated data.
+ *
+ * @return {Boolean}
+ * True if the axis has associated visible series and those series
+ * have either valid data points or explicit `min` and `max`
+ * settings.
+ */
+ hasData: function () {
+ return (
+ this.hasVisibleSeries ||
+ (
+ defined(this.min) &&
+ defined(this.max) &&
+ this.tickPositions &&
+ this.tickPositions.length > 0
+ )
+ );
+ },
+
+ /**
+ * Adds the title defined in axis.options.title.
+ * @param {Boolean} display - whether or not to display the title
+ */
+ addTitle: function (display) {
+ var axis = this,
+ renderer = axis.chart.renderer,
+ horiz = axis.horiz,
+ opposite = axis.opposite,
+ options = axis.options,
+ axisTitleOptions = options.title,
+ textAlign;
+
+ if (!axis.axisTitle) {
+ textAlign = axisTitleOptions.textAlign;
+ if (!textAlign) {
+ textAlign = (horiz ? {
+ low: 'left',
+ middle: 'center',
+ high: 'right'
+ } : {
+ low: opposite ? 'right' : 'left',
+ middle: 'center',
+ high: opposite ? 'left' : 'right'
+ })[axisTitleOptions.align];
+ }
+ axis.axisTitle = renderer.text(
+ axisTitleOptions.text,
+ 0,
+ 0,
+ axisTitleOptions.useHTML
+ )
+ .attr({
+ zIndex: 7,
+ rotation: axisTitleOptions.rotation || 0,
+ align: textAlign
+ })
+ .addClass('highcharts-axis-title')
+
+ .css(axisTitleOptions.style)
+
+ .add(axis.axisGroup);
+ axis.axisTitle.isNew = true;
+ }
+
+ // Max width defaults to the length of the axis
+
+ if (!axisTitleOptions.style.width && !axis.isRadial) {
+
+ axis.axisTitle.css({
+ width: axis.len
+ });
+
+ }
+
+
+
+ // hide or show the title depending on whether showEmpty is set
+ axis.axisTitle[display ? 'show' : 'hide'](true);
+ },
+
+ /**
+ * Generates a tick for initial positioning.
+ *
+ * @private
+ * @param {number} pos
+ * The tick position in axis values.
+ * @param {number} i
+ * The index of the tick in {@link Axis.tickPositions}.
+ */
+ generateTick: function (pos) {
+ var ticks = this.ticks;
+
+ if (!ticks[pos]) {
+ ticks[pos] = new Tick(this, pos);
+ } else {
+ ticks[pos].addLabel(); // update labels depending on tick interval
+ }
+ },
+
+ /**
+ * Render the tick labels to a preliminary position to get their sizes.
+ *
+ * @private
+ */
+ getOffset: function () {
+ var axis = this,
+ chart = axis.chart,
+ renderer = chart.renderer,
+ options = axis.options,
+ tickPositions = axis.tickPositions,
+ ticks = axis.ticks,
+ horiz = axis.horiz,
+ side = axis.side,
+ invertedSide = chart.inverted &&
+ !axis.isZAxis ? [1, 0, 3, 2][side] : side,
+ hasData,
+ showAxis,
+ titleOffset = 0,
+ titleOffsetOption,
+ titleMargin = 0,
+ axisTitleOptions = options.title,
+ labelOptions = options.labels,
+ labelOffset = 0, // reset
+ labelOffsetPadded,
+ axisOffset = chart.axisOffset,
+ clipOffset = chart.clipOffset,
+ clip,
+ directionFactor = [-1, 1, 1, -1][side],
+ className = options.className,
+ axisParent = axis.axisParent, // Used in color axis
+ lineHeightCorrection,
+ tickSize = this.tickSize('tick');
+
+ // For reuse in Axis.render
+ hasData = axis.hasData();
+ axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);
+
+ // Set/reset staggerLines
+ axis.staggerLines = axis.horiz && labelOptions.staggerLines;
+
+ // Create the axisGroup and gridGroup elements on first iteration
+ if (!axis.axisGroup) {
+ axis.gridGroup = renderer.g('grid')
+ .attr({ zIndex: options.gridZIndex || 1 })
+ .addClass(
+ 'highcharts-' + this.coll.toLowerCase() + '-grid ' +
+ (className || '')
+ )
+ .add(axisParent);
+ axis.axisGroup = renderer.g('axis')
+ .attr({ zIndex: options.zIndex || 2 })
+ .addClass(
+ 'highcharts-' + this.coll.toLowerCase() + ' ' +
+ (className || '')
+ )
+ .add(axisParent);
+ axis.labelGroup = renderer.g('axis-labels')
+ .attr({ zIndex: labelOptions.zIndex || 7 })
+ .addClass(
+ 'highcharts-' + axis.coll.toLowerCase() + '-labels ' +
+ (className || '')
+ )
+ .add(axisParent);
+ }
+
+ if (hasData || axis.isLinked) {
+
+ // Generate ticks
+ each(tickPositions, function (pos, i) {
+ // i is not used here, but may be used in overrides
+ axis.generateTick(pos, i);
+ });
+
+ axis.renderUnsquish();
+
+
+ // Left side must be align: right and right side must
+ // have align: left for labels
+ axis.reserveSpaceDefault = (
+ side === 0 ||
+ side === 2 ||
+ { 1: 'left', 3: 'right' }[side] === axis.labelAlign
+ );
+ if (pick(
+ labelOptions.reserveSpace,
+ axis.labelAlign === 'center' ? true : null,
+ axis.reserveSpaceDefault)
+ ) {
+ each(tickPositions, function (pos) {
+ // get the highest offset
+ labelOffset = Math.max(
+ ticks[pos].getLabelSize(),
+ labelOffset
+ );
+ });
+ }
+
+ if (axis.staggerLines) {
+ labelOffset *= axis.staggerLines;
+ }
+ axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1);
+
+ } else { // doesn't have data
+ objectEach(ticks, function (tick, n) {
+ tick.destroy();
+ delete ticks[n];
+ });
+ }
+
+ if (
+ axisTitleOptions &&
+ axisTitleOptions.text &&
+ axisTitleOptions.enabled !== false
+ ) {
+ axis.addTitle(showAxis);
+
+ if (showAxis && axisTitleOptions.reserveSpace !== false) {
+ axis.titleOffset = titleOffset =
+ axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
+ titleOffsetOption = axisTitleOptions.offset;
+ titleMargin = defined(titleOffsetOption) ?
+ 0 :
+ pick(axisTitleOptions.margin, horiz ? 5 : 10);
+ }
+ }
+
+ // Render the axis line
+ axis.renderLine();
+
+ // handle automatic or user set offset
+ axis.offset = directionFactor * pick(options.offset, axisOffset[side]);
+
+ axis.tickRotCorr = axis.tickRotCorr || { x: 0, y: 0 }; // polar
+ if (side === 0) {
+ lineHeightCorrection = -axis.labelMetrics().h;
+ } else if (side === 2) {
+ lineHeightCorrection = axis.tickRotCorr.y;
+ } else {
+ lineHeightCorrection = 0;
+ }
+
+ // Find the padded label offset
+ labelOffsetPadded = Math.abs(labelOffset) + titleMargin;
+ if (labelOffset) {
+ labelOffsetPadded -= lineHeightCorrection;
+ labelOffsetPadded += directionFactor * (
+ horiz ?
+ pick(
+ labelOptions.y,
+ axis.tickRotCorr.y + directionFactor * 8
+ ) :
+ labelOptions.x
+ );
+ }
+
+ axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded);
+
+ axisOffset[side] = Math.max(
+ axisOffset[side],
+ axis.axisTitleMargin + titleOffset + directionFactor * axis.offset,
+ labelOffsetPadded, // #3027
+ hasData && tickPositions.length && tickSize ?
+ tickSize[0] + directionFactor * axis.offset :
+ 0 // #4866
+ );
+
+ // Decide the clipping needed to keep the graph inside
+ // the plot area and axis lines
+ clip = options.offset ?
+ 0 :
+ Math.floor(axis.axisLine.strokeWidth() / 2) * 2; // #4308, #4371
+ clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip);
+ },
+
+ /**
+ * Internal function to get the path for the axis line. Extended for polar
+ * charts.
+ *
+ * @param {Number} lineWidth
+ * The line width in pixels.
+ * @return {Array}
+ * The SVG path definition in array form.
+ */
+ getLinePath: function (lineWidth) {
+ var chart = this.chart,
+ opposite = this.opposite,
+ offset = this.offset,
+ horiz = this.horiz,
+ lineLeft = this.left + (opposite ? this.width : 0) + offset,
+ lineTop = chart.chartHeight - this.bottom -
+ (opposite ? this.height : 0) + offset;
+
+ if (opposite) {
+ lineWidth *= -1; // crispify the other way - #1480, #1687
+ }
+
+ return chart.renderer
+ .crispLine([
+ 'M',
+ horiz ?
+ this.left :
+ lineLeft,
+ horiz ?
+ lineTop :
+ this.top,
+ 'L',
+ horiz ?
+ chart.chartWidth - this.right :
+ lineLeft,
+ horiz ?
+ lineTop :
+ chart.chartHeight - this.bottom
+ ], lineWidth);
+ },
+
+ /**
+ * Render the axis line. Called internally when rendering and redrawing the
+ * axis.
+ */
+ renderLine: function () {
+ if (!this.axisLine) {
+ this.axisLine = this.chart.renderer.path()
+ .addClass('highcharts-axis-line')
+ .add(this.axisGroup);
+
+
+ this.axisLine.attr({
+ stroke: this.options.lineColor,
+ 'stroke-width': this.options.lineWidth,
+ zIndex: 7
+ });
+
+ }
+ },
+
+ /**
+ * Position the axis title.
+ *
+ * @private
+ *
+ * @return {Object}
+ * X and Y positions for the title.
+ */
+ getTitlePosition: function () {
+ // compute anchor points for each of the title align options
+ var horiz = this.horiz,
+ axisLeft = this.left,
+ axisTop = this.top,
+ axisLength = this.len,
+ axisTitleOptions = this.options.title,
+ margin = horiz ? axisLeft : axisTop,
+ opposite = this.opposite,
+ offset = this.offset,
+ xOption = axisTitleOptions.x || 0,
+ yOption = axisTitleOptions.y || 0,
+ axisTitle = this.axisTitle,
+ fontMetrics = this.chart.renderer.fontMetrics(
+ axisTitleOptions.style && axisTitleOptions.style.fontSize,
+ axisTitle
+ ),
+ // The part of a multiline text that is below the baseline of the
+ // first line. Subtract 1 to preserve pixel-perfectness from the
+ // old behaviour (v5.0.12), where only one line was allowed.
+ textHeightOvershoot = Math.max(
+ axisTitle.getBBox(null, 0).height - fontMetrics.h - 1,
+ 0
+ ),
+
+ // the position in the length direction of the axis
+ alongAxis = {
+ low: margin + (horiz ? 0 : axisLength),
+ middle: margin + axisLength / 2,
+ high: margin + (horiz ? axisLength : 0)
+ }[axisTitleOptions.align],
+
+ // the position in the perpendicular direction of the axis
+ offAxis = (horiz ? axisTop + this.height : axisLeft) +
+ (horiz ? 1 : -1) * // horizontal axis reverses the margin
+ (opposite ? -1 : 1) * // so does opposite axes
+ this.axisTitleMargin +
+ [
+ -textHeightOvershoot, // top
+ textHeightOvershoot, // right
+ fontMetrics.f, // bottom
+ -textHeightOvershoot // left
+ ][this.side];
+
+
+ return {
+ x: horiz ?
+ alongAxis + xOption :
+ offAxis + (opposite ? this.width : 0) + offset + xOption,
+ y: horiz ?
+ offAxis + yOption - (opposite ? this.height : 0) + offset :
+ alongAxis + yOption
+ };
+ },
+
+ /**
+ * Render a minor tick into the given position. If a minor tick already
+ * exists in this position, move it.
+ *
+ * @param {number} pos
+ * The position in axis values.
+ */
+ renderMinorTick: function (pos) {
+ var slideInTicks = this.chart.hasRendered && isNumber(this.oldMin),
+ minorTicks = this.minorTicks;
+
+ if (!minorTicks[pos]) {
+ minorTicks[pos] = new Tick(this, pos, 'minor');
+ }
+
+ // Render new ticks in old position
+ if (slideInTicks && minorTicks[pos].isNew) {
+ minorTicks[pos].render(null, true);
+ }
+
+ minorTicks[pos].render(null, false, 1);
+ },
+
+ /**
+ * Render a major tick into the given position. If a tick already exists
+ * in this position, move it.
+ *
+ * @param {number} pos
+ * The position in axis values.
+ * @param {number} i
+ * The tick index.
+ */
+ renderTick: function (pos, i) {
+ var isLinked = this.isLinked,
+ ticks = this.ticks,
+ slideInTicks = this.chart.hasRendered && isNumber(this.oldMin);
+
+ // Linked axes need an extra check to find out if
+ if (!isLinked || (pos >= this.min && pos <= this.max)) {
+
+ if (!ticks[pos]) {
+ ticks[pos] = new Tick(this, pos);
+ }
+
+ // render new ticks in old position
+ if (slideInTicks && ticks[pos].isNew) {
+ ticks[pos].render(i, true, 0.1);
+ }
+
+ ticks[pos].render(i);
+ }
+ },
+
+ /**
+ * Render the axis.
+ *
+ * @private
+ */
+ render: function () {
+ var axis = this,
+ chart = axis.chart,
+ renderer = chart.renderer,
+ options = axis.options,
+ isLog = axis.isLog,
+ lin2log = axis.lin2log,
+ isLinked = axis.isLinked,
+ tickPositions = axis.tickPositions,
+ axisTitle = axis.axisTitle,
+ ticks = axis.ticks,
+ minorTicks = axis.minorTicks,
+ alternateBands = axis.alternateBands,
+ stackLabelOptions = options.stackLabels,
+ alternateGridColor = options.alternateGridColor,
+ tickmarkOffset = axis.tickmarkOffset,
+ axisLine = axis.axisLine,
+ showAxis = axis.showAxis,
+ animation = animObject(renderer.globalAnimation),
+ from,
+ to;
+
+ // Reset
+ axis.labelEdge.length = 0;
+ axis.overlap = false;
+
+ // Mark all elements inActive before we go over and mark the active ones
+ each([ticks, minorTicks, alternateBands], function (coll) {
+ objectEach(coll, function (tick) {
+ tick.isActive = false;
+ });
+ });
+
+ // If the series has data draw the ticks. Else only the line and title
+ if (axis.hasData() || isLinked) {
+
+ // minor ticks
+ if (axis.minorTickInterval && !axis.categories) {
+ each(axis.getMinorTickPositions(), function (pos) {
+ axis.renderMinorTick(pos);
+ });
+ }
+
+ // Major ticks. Pull out the first item and render it last so that
+ // we can get the position of the neighbour label. #808.
+ if (tickPositions.length) { // #1300
+ each(tickPositions, function (pos, i) {
+ axis.renderTick(pos, i);
+ });
+ // In a categorized axis, the tick marks are displayed
+ // between labels. So we need to add a tick mark and
+ // grid line at the left edge of the X axis.
+ if (tickmarkOffset && (axis.min === 0 || axis.single)) {
+ if (!ticks[-1]) {
+ ticks[-1] = new Tick(axis, -1, null, true);
+ }
+ ticks[-1].render(-1);
+ }
+
+ }
+
+ // alternate grid color
+ if (alternateGridColor) {
+ each(tickPositions, function (pos, i) {
+ to = tickPositions[i + 1] !== undefined ?
+ tickPositions[i + 1] + tickmarkOffset :
+ axis.max - tickmarkOffset;
+
+ if (
+ i % 2 === 0 &&
+ pos < axis.max &&
+ to <= axis.max + (
+ chart.polar ?
+ -tickmarkOffset :
+ tickmarkOffset
+ )
+ ) { // #2248, #4660
+ if (!alternateBands[pos]) {
+ alternateBands[pos] = new H.PlotLineOrBand(axis);
+ }
+ from = pos + tickmarkOffset; // #949
+ alternateBands[pos].options = {
+ from: isLog ? lin2log(from) : from,
+ to: isLog ? lin2log(to) : to,
+ color: alternateGridColor
+ };
+ alternateBands[pos].render();
+ alternateBands[pos].isActive = true;
+ }
+ });
+ }
+
+ // custom plot lines and bands
+ if (!axis._addedPlotLB) { // only first time
+ each(
+ (options.plotLines || []).concat(options.plotBands || []),
+ function (plotLineOptions) {
+ axis.addPlotBandOrLine(plotLineOptions);
+ }
+ );
+ axis._addedPlotLB = true;
+ }
+
+ } // end if hasData
+
+ // Remove inactive ticks
+ each([ticks, minorTicks, alternateBands], function (coll) {
+ var i,
+ forDestruction = [],
+ delay = animation.duration,
+ destroyInactiveItems = function () {
+ i = forDestruction.length;
+ while (i--) {
+ // When resizing rapidly, the same items
+ // may be destroyed in different timeouts,
+ // or the may be reactivated
+ if (
+ coll[forDestruction[i]] &&
+ !coll[forDestruction[i]].isActive
+ ) {
+ coll[forDestruction[i]].destroy();
+ delete coll[forDestruction[i]];
+ }
+ }
+
+ };
+
+ objectEach(coll, function (tick, pos) {
+ if (!tick.isActive) {
+ // Render to zero opacity
+ tick.render(pos, false, 0);
+ tick.isActive = false;
+ forDestruction.push(pos);
+ }
+ });
+
+ // When the objects are finished fading out, destroy them
+ syncTimeout(
+ destroyInactiveItems,
+ coll === alternateBands ||
+ !chart.hasRendered ||
+ !delay ?
+ 0 :
+ delay
+ );
+ });
+
+ // Set the axis line path
+ if (axisLine) {
+ axisLine[axisLine.isPlaced ? 'animate' : 'attr']({
+ d: this.getLinePath(axisLine.strokeWidth())
+ });
+ axisLine.isPlaced = true;
+
+ // Show or hide the line depending on options.showEmpty
+ axisLine[showAxis ? 'show' : 'hide'](true);
+ }
+
+ if (axisTitle && showAxis) {
+ var titleXy = axis.getTitlePosition();
+ if (isNumber(titleXy.y)) {
+ axisTitle[axisTitle.isNew ? 'attr' : 'animate'](titleXy);
+ axisTitle.isNew = false;
+ } else {
+ axisTitle.attr('y', -9999);
+ axisTitle.isNew = true;
+ }
+ }
+
+ // Stacked totals:
+ if (stackLabelOptions && stackLabelOptions.enabled) {
+ axis.renderStackTotals();
+ }
+ // End stacked totals
+
+ axis.isDirty = false;
+ },
+
+ /**
+ * Redraw the axis to reflect changes in the data or axis extremes. Called
+ * internally from {@link Chart#redraw}.
+ *
+ * @private
+ */
+ redraw: function () {
+
+ if (this.visible) {
+ // render the axis
+ this.render();
+
+ // move plot lines and bands
+ each(this.plotLinesAndBands, function (plotLine) {
+ plotLine.render();
+ });
+ }
+
+ // mark associated series as dirty and ready for redraw
+ each(this.series, function (series) {
+ series.isDirty = true;
+ });
+
+ },
+
+ // Properties to survive after destroy, needed for Axis.update (#4317,
+ // #5773, #5881).
+ keepProps: ['extKey', 'hcEvents', 'names', 'series', 'userMax', 'userMin'],
+
+ /**
+ * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint
+ * to fully remove the axis.
+ *
+ * @private
+ * @param {Boolean} keepEvents
+ * Whether to preserve events, used internally in Axis.update.
+ */
+ destroy: function (keepEvents) {
+ var axis = this,
+ stacks = axis.stacks,
+ plotLinesAndBands = axis.plotLinesAndBands,
+ plotGroup,
+ i;
+
+ // Remove the events
+ if (!keepEvents) {
+ removeEvent(axis);
+ }
+
+ // Destroy each stack total
+ objectEach(stacks, function (stack, stackKey) {
+ destroyObjectProperties(stack);
+
+ stacks[stackKey] = null;
+ });
+
+ // Destroy collections
+ each(
+ [axis.ticks, axis.minorTicks, axis.alternateBands],
+ function (coll) {
+ destroyObjectProperties(coll);
+ }
+ );
+ if (plotLinesAndBands) {
+ i = plotLinesAndBands.length;
+ while (i--) { // #1975
+ plotLinesAndBands[i].destroy();
+ }
+ }
+
+ // Destroy local variables
+ each(
+ ['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup',
+ 'gridGroup', 'labelGroup', 'cross'],
+ function (prop) {
+ if (axis[prop]) {
+ axis[prop] = axis[prop].destroy();
+ }
+ }
+ );
+
+ // Destroy each generated group for plotlines and plotbands
+ for (plotGroup in axis.plotLinesAndBandsGroups) {
+ axis.plotLinesAndBandsGroups[plotGroup] =
+ axis.plotLinesAndBandsGroups[plotGroup].destroy();
+ }
+
+ // Delete all properties and fall back to the prototype.
+ objectEach(axis, function (val, key) {
+ if (inArray(key, axis.keepProps) === -1) {
+ delete axis[key];
+ }
+ });
+ },
+
+ /**
+ * Internal function to draw a crosshair.
+ *
+ * @param {PointerEvent} [e]
+ * The event arguments from the modified pointer event, extended
+ * with `chartX` and `chartY`
+ * @param {Point} [point]
+ * The Point object if the crosshair snaps to points.
+ */
+ drawCrosshair: function (e, point) {
+
+ var path,
+ options = this.crosshair,
+ snap = pick(options.snap, true),
+ pos,
+ categorized,
+ graphic = this.cross;
+
+ // Use last available event when updating non-snapped crosshairs without
+ // mouse interaction (#5287)
+ if (!e) {
+ e = this.cross && this.cross.e;
+ }
+
+ if (
+ // Disabled in options
+ !this.crosshair ||
+ // Snap
+ ((defined(point) || !snap) === false)
+ ) {
+ this.hideCrosshair();
+ } else {
+
+ // Get the path
+ if (!snap) {
+ pos = e &&
+ (
+ this.horiz ?
+ e.chartX - this.pos :
+ this.len - e.chartY + this.pos
+ );
+ } else if (defined(point)) {
+ // #3834
+ pos = this.isXAxis ? point.plotX : this.len - point.plotY;
+ }
+
+ if (defined(pos)) {
+ path = this.getPlotLinePath(
+ // First argument, value, only used on radial
+ point && (this.isXAxis ?
+ point.x :
+ pick(point.stackY, point.y)
+ ),
+ null,
+ null,
+ null,
+ pos // Translated position
+ ) || null; // #3189
+ }
+
+ if (!defined(path)) {
+ this.hideCrosshair();
+ return;
+ }
+
+ categorized = this.categories && !this.isRadial;
+
+ // Draw the cross
+ if (!graphic) {
+ this.cross = graphic = this.chart.renderer
+ .path()
+ .addClass(
+ 'highcharts-crosshair highcharts-crosshair-' +
+ (categorized ? 'category ' : 'thin ') +
+ options.className
+ )
+ .attr({
+ zIndex: pick(options.zIndex, 2)
+ })
+ .add();
+
+
+ // Presentational attributes
+ graphic.attr({
+ 'stroke': options.color ||
+ (
+ categorized ?
+ color('#ccd6eb')
+ .setOpacity(0.25).get() :
+ '#cccccc'
+ ),
+ 'stroke-width': pick(options.width, 1)
+ }).css({
+ 'pointer-events': 'none'
+ });
+ if (options.dashStyle) {
+ graphic.attr({
+ dashstyle: options.dashStyle
+ });
+ }
+
+
+ }
+
+ graphic.show().attr({
+ d: path
+ });
+
+ if (categorized && !options.width) {
+ graphic.attr({
+ 'stroke-width': this.transA
+ });
+ }
+ this.cross.e = e;
+ }
+ },
+
+ /**
+ * Hide the crosshair if visible.
+ */
+ hideCrosshair: function () {
+ if (this.cross) {
+ this.cross.hide();
+ }
+ }
+}); // end Axis
+
+H.Axis = Axis;
+return Axis;
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var Axis = H.Axis,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ getMagnitude = H.getMagnitude,
+ normalizeTickInterval = H.normalizeTickInterval,
+ pick = H.pick,
+ timeUnits = H.timeUnits;
+/**
+ * Set the tick positions to a time unit that makes sense, for example
+ * on the first of each month or on every Monday. Return an array
+ * with the time positions. Used in datetime axes as well as for grouping
+ * data on a datetime axis.
+ *
+ * @param {Object} normalizedInterval The interval in axis values (ms) and the count
+ * @param {Number} min The minimum in axis values
+ * @param {Number} max The maximum in axis values
+ * @param {Number} startOfWeek
+ */
+Axis.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek) {
+ var time = this.chart.time,
+ Date = time.Date,
+ tickPositions = [],
+ i,
+ higherRanks = {},
+ minYear, // used in months and years as a basis for Date.UTC()
+ // When crossing DST, use the max. Resolves #6278.
+ minDate = new Date(
+ min - Math.max(
+ time.getTimezoneOffset(min),
+ time.getTimezoneOffset(max)
+ )
+ ),
+ interval = normalizedInterval.unitRange,
+ count = normalizedInterval.count,
+ baseOffset, // #6797
+ variableDayLength;
+
+ if (defined(min)) { // #1300
+ minDate[time.setMilliseconds](interval >= timeUnits.second ? 0 : // #3935
+ count * Math.floor(minDate.getMilliseconds() / count)); // #3652, #3654
+
+ if (interval >= timeUnits.second) { // second
+ minDate[time.setSeconds](interval >= timeUnits.minute ? 0 : // #3935
+ count * Math.floor(minDate.getSeconds() / count));
+ }
+
+ if (interval >= timeUnits.minute) { // minute
+ minDate[time.setMinutes](interval >= timeUnits.hour ? 0 :
+ count * Math.floor(minDate[time.getMinutes]() / count));
+ }
+
+ if (interval >= timeUnits.hour) { // hour
+ minDate[time.setHours](interval >= timeUnits.day ? 0 :
+ count * Math.floor(minDate[time.getHours]() / count));
+ }
+
+ if (interval >= timeUnits.day) { // day
+ minDate[time.setDate](interval >= timeUnits.month ? 1 :
+ count * Math.floor(minDate[time.getDate]() / count));
+ }
+
+ if (interval >= timeUnits.month) { // month
+ minDate[time.setMonth](interval >= timeUnits.year ? 0 :
+ count * Math.floor(minDate[time.getMonth]() / count));
+ minYear = minDate[time.getFullYear]();
+ }
+
+ if (interval >= timeUnits.year) { // year
+ minYear -= minYear % count;
+ minDate[time.setFullYear](minYear);
+ }
+
+ // week is a special case that runs outside the hierarchy
+ if (interval === timeUnits.week) {
+ // get start of current week, independent of count
+ minDate[time.setDate](minDate[time.getDate]() - minDate[time.getDay]() +
+ pick(startOfWeek, 1));
+ }
+
+
+ // Get basics for variable time spans
+ minYear = minDate[time.getFullYear]();
+ var minMonth = minDate[time.getMonth](),
+ minDateDate = minDate[time.getDate](),
+ minHours = minDate[time.getHours]();
+
+ // Redefine min to the floored/rounded minimum time (#7432)
+ min = minDate.getTime();
+
+ // Handle local timezone offset
+ if (time.variableTimezone) {
+
+ // Detect whether we need to take the DST crossover into
+ // consideration. If we're crossing over DST, the day length may be
+ // 23h or 25h and we need to compute the exact clock time for each
+ // tick instead of just adding hours. This comes at a cost, so first
+ // we find out if it is needed (#4951).
+ variableDayLength = (
+ // Long range, assume we're crossing over.
+ max - min > 4 * timeUnits.month ||
+ // Short range, check if min and max are in different time
+ // zones.
+ time.getTimezoneOffset(min) !== time.getTimezoneOffset(max)
+ );
+ }
+
+
+ // Adjust minDate to the offset date
+ baseOffset = time.getTimezoneOffset(minDate);
+ if (baseOffset) {
+ minDate = new Date(min + baseOffset);
+ }
+
+
+ // Iterate and add tick positions at appropriate values
+ var t = minDate.getTime();
+ i = 1;
+ while (t < max) {
+ tickPositions.push(t);
+
+ // if the interval is years, use Date.UTC to increase years
+ if (interval === timeUnits.year) {
+ t = time.makeTime(minYear + i * count, 0);
+
+ // if the interval is months, use Date.UTC to increase months
+ } else if (interval === timeUnits.month) {
+ t = time.makeTime(minYear, minMonth + i * count);
+
+ // if we're using global time, the interval is not fixed as it jumps
+ // one hour at the DST crossover
+ } else if (
+ variableDayLength &&
+ (interval === timeUnits.day || interval === timeUnits.week)
+ ) {
+ t = time.makeTime(minYear, minMonth, minDateDate +
+ i * count * (interval === timeUnits.day ? 1 : 7));
+
+ } else if (variableDayLength && interval === timeUnits.hour) {
+ // corrected by the start date time zone offset (baseOffset)
+ // to hide duplicated label (#6797)
+ t = time.makeTime(minYear, minMonth, minDateDate, minHours +
+ i * count, 0, 0, baseOffset) - baseOffset;
+
+ // else, the interval is fixed and we use simple addition
+ } else {
+ t += interval * count;
+ }
+
+ i++;
+ }
+
+ // push the last time
+ tickPositions.push(t);
+
+
+ // Handle higher ranks. Mark new days if the time is on midnight
+ // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold to
+ // prevent looping over dense data grouping (#6156).
+ if (interval <= timeUnits.hour && tickPositions.length < 10000) {
+ each(tickPositions, function (t) {
+ if (
+ // Speed optimization, no need to run dateFormat unless
+ // we're on a full or half hour
+ t % 1800000 === 0 &&
+ // Check for local or global midnight
+ time.dateFormat('%H%M%S%L', t) === '000000000'
+ ) {
+ higherRanks[t] = 'day';
+ }
+ });
+ }
+ }
+
+
+ // record information on the chosen unit - for dynamic label formatter
+ tickPositions.info = extend(normalizedInterval, {
+ higherRanks: higherRanks,
+ totalRange: interval * count
+ });
+
+ return tickPositions;
+};
+
+/**
+ * Get a normalized tick interval for dates. Returns a configuration object with
+ * unit range (interval), count and name. Used to prepare data for getTimeTicks.
+ * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
+ * of segments in stock charts, the normalizing logic was extracted in order to
+ * prevent it for running over again for each segment having the same interval.
+ * #662, #697.
+ */
+Axis.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) {
+ var units = unitsOption || [[
+ 'millisecond', // unit name
+ [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
+ ], [
+ 'second',
+ [1, 2, 5, 10, 15, 30]
+ ], [
+ 'minute',
+ [1, 2, 5, 10, 15, 30]
+ ], [
+ 'hour',
+ [1, 2, 3, 4, 6, 8, 12]
+ ], [
+ 'day',
+ [1, 2]
+ ], [
+ 'week',
+ [1, 2]
+ ], [
+ 'month',
+ [1, 2, 3, 4, 6]
+ ], [
+ 'year',
+ null
+ ]],
+ unit = units[units.length - 1], // default unit is years
+ interval = timeUnits[unit[0]],
+ multiples = unit[1],
+ count,
+ i;
+
+ // loop through the units to find the one that best fits the tickInterval
+ for (i = 0; i < units.length; i++) {
+ unit = units[i];
+ interval = timeUnits[unit[0]];
+ multiples = unit[1];
+
+
+ if (units[i + 1]) {
+ // lessThan is in the middle between the highest multiple and the next unit.
+ var lessThan = (interval * multiples[multiples.length - 1] +
+ timeUnits[units[i + 1][0]]) / 2;
+
+ // break and keep the current unit
+ if (tickInterval <= lessThan) {
+ break;
+ }
+ }
+ }
+
+ // prevent 2.5 years intervals, though 25, 250 etc. are allowed
+ if (interval === timeUnits.year && tickInterval < 5 * interval) {
+ multiples = [1, 2, 5];
+ }
+
+ // get the count
+ count = normalizeTickInterval(
+ tickInterval / interval,
+ multiples,
+ unit[0] === 'year' ? Math.max(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360
+ );
+
+ return {
+ unitRange: interval,
+ count: count,
+ unitName: unit[0]
+ };
+};
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var Axis = H.Axis,
+ getMagnitude = H.getMagnitude,
+ map = H.map,
+ normalizeTickInterval = H.normalizeTickInterval,
+ pick = H.pick;
+/**
+ * Methods defined on the Axis prototype
+ */
+
+/**
+ * Set the tick positions of a logarithmic axis
+ */
+Axis.prototype.getLogTickPositions = function (interval, min, max, minor) {
+ var axis = this,
+ options = axis.options,
+ axisLength = axis.len,
+ lin2log = axis.lin2log,
+ log2lin = axis.log2lin,
+ // Since we use this method for both major and minor ticks,
+ // use a local variable and return the result
+ positions = [];
+
+ // Reset
+ if (!minor) {
+ axis._minorAutoInterval = null;
+ }
+
+ // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
+ if (interval >= 0.5) {
+ interval = Math.round(interval);
+ positions = axis.getLinearTickPositions(interval, min, max);
+
+ // Second case: We need intermediary ticks. For example
+ // 1, 2, 4, 6, 8, 10, 20, 40 etc.
+ } else if (interval >= 0.08) {
+ var roundedMin = Math.floor(min),
+ intermediate,
+ i,
+ j,
+ len,
+ pos,
+ lastPos,
+ break2;
+
+ if (interval > 0.3) {
+ intermediate = [1, 2, 4];
+ } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
+ intermediate = [1, 2, 4, 6, 8];
+ } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
+ intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
+ }
+
+ for (i = roundedMin; i < max + 1 && !break2; i++) {
+ len = intermediate.length;
+ for (j = 0; j < len && !break2; j++) {
+ pos = log2lin(lin2log(i) * intermediate[j]);
+ if (pos > min && (!minor || lastPos <= max) && lastPos !== undefined) { // #1670, lastPos is #3113
+ positions.push(lastPos);
+ }
+
+ if (lastPos > max) {
+ break2 = true;
+ }
+ lastPos = pos;
+ }
+ }
+
+ // Third case: We are so deep in between whole logarithmic values that
+ // we might as well handle the tick positions like a linear axis. For
+ // example 1.01, 1.02, 1.03, 1.04.
+ } else {
+ var realMin = lin2log(min),
+ realMax = lin2log(max),
+ tickIntervalOption = minor ?
+ this.getMinorTickInterval() :
+ options.tickInterval,
+ filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
+ tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
+ totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;
+
+ interval = pick(
+ filteredTickIntervalOption,
+ axis._minorAutoInterval,
+ (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
+ );
+
+ interval = normalizeTickInterval(
+ interval,
+ null,
+ getMagnitude(interval)
+ );
+
+ positions = map(axis.getLinearTickPositions(
+ interval,
+ realMin,
+ realMax
+ ), log2lin);
+
+ if (!minor) {
+ axis._minorAutoInterval = interval / 5;
+ }
+ }
+
+ // Set the axis-level tickInterval variable
+ if (!minor) {
+ axis.tickInterval = interval;
+ }
+ return positions;
+};
+
+Axis.prototype.log2lin = function (num) {
+ return Math.log(num) / Math.LN10;
+};
+
+Axis.prototype.lin2log = function (num) {
+ return Math.pow(10, num);
+};
+
+}(Highcharts));
+(function (H, Axis) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var arrayMax = H.arrayMax,
+ arrayMin = H.arrayMin,
+ defined = H.defined,
+ destroyObjectProperties = H.destroyObjectProperties,
+ each = H.each,
+ erase = H.erase,
+ merge = H.merge,
+ pick = H.pick;
+/*
+ * The object wrapper for plot lines and plot bands
+ * @param {Object} options
+ */
+H.PlotLineOrBand = function (axis, options) {
+ this.axis = axis;
+
+ if (options) {
+ this.options = options;
+ this.id = options.id;
+ }
+};
+
+H.PlotLineOrBand.prototype = {
+
+ /**
+ * Render the plot line or plot band. If it is already existing,
+ * move it.
+ */
+ render: function () {
+ var plotLine = this,
+ axis = plotLine.axis,
+ horiz = axis.horiz,
+ options = plotLine.options,
+ optionsLabel = options.label,
+ label = plotLine.label,
+ to = options.to,
+ from = options.from,
+ value = options.value,
+ isBand = defined(from) && defined(to),
+ isLine = defined(value),
+ svgElem = plotLine.svgElem,
+ isNew = !svgElem,
+ path = [],
+ color = options.color,
+ zIndex = pick(options.zIndex, 0),
+ events = options.events,
+ attribs = {
+ 'class': 'highcharts-plot-' + (isBand ? 'band ' : 'line ') +
+ (options.className || '')
+ },
+ groupAttribs = {},
+ renderer = axis.chart.renderer,
+ groupName = isBand ? 'bands' : 'lines',
+ group,
+ log2lin = axis.log2lin;
+
+ // logarithmic conversion
+ if (axis.isLog) {
+ from = log2lin(from);
+ to = log2lin(to);
+ value = log2lin(value);
+ }
+
+
+ // Set the presentational attributes
+ if (isLine) {
+ attribs = {
+ stroke: color,
+ 'stroke-width': options.width
+ };
+ if (options.dashStyle) {
+ attribs.dashstyle = options.dashStyle;
+ }
+
+ } else if (isBand) { // plot band
+ if (color) {
+ attribs.fill = color;
+ }
+ if (options.borderWidth) {
+ attribs.stroke = options.borderColor;
+ attribs['stroke-width'] = options.borderWidth;
+ }
+ }
+
+
+ // Grouping and zIndex
+ groupAttribs.zIndex = zIndex;
+ groupName += '-' + zIndex;
+
+ group = axis.plotLinesAndBandsGroups[groupName];
+ if (!group) {
+ axis.plotLinesAndBandsGroups[groupName] = group =
+ renderer.g('plot-' + groupName)
+ .attr(groupAttribs).add();
+ }
+
+ // Create the path
+ if (isNew) {
+ plotLine.svgElem = svgElem =
+ renderer
+ .path()
+ .attr(attribs).add(group);
+ }
+
+
+ // Set the path or return
+ if (isLine) {
+ path = axis.getPlotLinePath(value, svgElem.strokeWidth());
+ } else if (isBand) { // plot band
+ path = axis.getPlotBandPath(from, to, options);
+ } else {
+ return;
+ }
+
+
+ // common for lines and bands
+ if (isNew && path && path.length) {
+ svgElem.attr({ d: path });
+
+ // events
+ if (events) {
+ H.objectEach(events, function (event, eventType) {
+ svgElem.on(eventType, function (e) {
+ events[eventType].apply(plotLine, [e]);
+ });
+ });
+ }
+ } else if (svgElem) {
+ if (path) {
+ svgElem.show();
+ svgElem.animate({ d: path });
+ } else {
+ svgElem.hide();
+ if (label) {
+ plotLine.label = label = label.destroy();
+ }
+ }
+ }
+
+ // the plot band/line label
+ if (
+ optionsLabel &&
+ defined(optionsLabel.text) &&
+ path &&
+ path.length &&
+ axis.width > 0 &&
+ axis.height > 0 &&
+ !path.flat
+ ) {
+ // apply defaults
+ optionsLabel = merge({
+ align: horiz && isBand && 'center',
+ x: horiz ? !isBand && 4 : 10,
+ verticalAlign: !horiz && isBand && 'middle',
+ y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
+ rotation: horiz && !isBand && 90
+ }, optionsLabel);
+
+ this.renderLabel(optionsLabel, path, isBand, zIndex);
+
+ } else if (label) { // move out of sight
+ label.hide();
+ }
+
+ // chainable
+ return plotLine;
+ },
+
+ /**
+ * Render and align label for plot line or band.
+ */
+ renderLabel: function (optionsLabel, path, isBand, zIndex) {
+ var plotLine = this,
+ label = plotLine.label,
+ renderer = plotLine.axis.chart.renderer,
+ attribs,
+ xBounds,
+ yBounds,
+ x,
+ y;
+
+ // add the SVG element
+ if (!label) {
+ attribs = {
+ align: optionsLabel.textAlign || optionsLabel.align,
+ rotation: optionsLabel.rotation,
+ 'class': 'highcharts-plot-' + (isBand ? 'band' : 'line') +
+ '-label ' + (optionsLabel.className || '')
+ };
+
+ attribs.zIndex = zIndex;
+
+ plotLine.label = label = renderer.text(
+ optionsLabel.text,
+ 0,
+ 0,
+ optionsLabel.useHTML
+ )
+ .attr(attribs)
+ .add();
+
+
+ label.css(optionsLabel.style);
+
+ }
+
+ // get the bounding box and align the label
+ // #3000 changed to better handle choice between plotband or plotline
+ xBounds = path.xBounds ||
+ [path[1], path[4], (isBand ? path[6] : path[1])];
+ yBounds = path.yBounds ||
+ [path[2], path[5], (isBand ? path[7] : path[2])];
+
+ x = arrayMin(xBounds);
+ y = arrayMin(yBounds);
+
+ label.align(optionsLabel, false, {
+ x: x,
+ y: y,
+ width: arrayMax(xBounds) - x,
+ height: arrayMax(yBounds) - y
+ });
+ label.show();
+ },
+
+ /**
+ * Remove the plot line or band
+ */
+ destroy: function () {
+ // remove it from the lookup
+ erase(this.axis.plotLinesAndBands, this);
+
+ delete this.axis;
+ destroyObjectProperties(this);
+ }
+};
+
+/**
+ * Object with members for extending the Axis prototype
+ * @todo Extend directly instead of adding object to Highcharts first
+ */
+
+H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
+
+ /**
+ * Internal function to create the SVG path definition for a plot band.
+ *
+ * @param {Number} from
+ * The axis value to start from.
+ * @param {Number} to
+ * The axis value to end on.
+ *
+ * @return {Array.}
+ * The SVG path definition in array form.
+ */
+ getPlotBandPath: function (from, to) {
+ var toPath = this.getPlotLinePath(to, null, null, true),
+ path = this.getPlotLinePath(from, null, null, true),
+ result = [],
+ i,
+ // #4964 check if chart is inverted or plotband is on yAxis
+ horiz = this.horiz,
+ plus = 1,
+ flat,
+ outside =
+ (from < this.min && to < this.min) ||
+ (from > this.max && to > this.max);
+
+ if (path && toPath) {
+
+ // Flat paths don't need labels (#3836)
+ if (outside) {
+ flat = path.toString() === toPath.toString();
+ plus = 0;
+ }
+
+ // Go over each subpath - for panes in Highstock
+ for (i = 0; i < path.length; i += 6) {
+
+ // Add 1 pixel when coordinates are the same
+ if (horiz && toPath[i + 1] === path[i + 1]) {
+ toPath[i + 1] += plus;
+ toPath[i + 4] += plus;
+ } else if (!horiz && toPath[i + 2] === path[i + 2]) {
+ toPath[i + 2] += plus;
+ toPath[i + 5] += plus;
+ }
+
+ result.push(
+ 'M',
+ path[i + 1],
+ path[i + 2],
+ 'L',
+ path[i + 4],
+ path[i + 5],
+ toPath[i + 4],
+ toPath[i + 5],
+ toPath[i + 1],
+ toPath[i + 2],
+ 'z'
+ );
+ result.flat = flat;
+ }
+
+ } else { // outside the axis area
+ path = null;
+ }
+
+ return result;
+ },
+
+ /**
+ * Add a plot band after render time.
+ *
+ * @param {AxisPlotBandsOptions} options
+ * A configuration object for the plot band, as defined in {@link
+ * https://api.highcharts.com/highcharts/xAxis.plotBands|
+ * xAxis.plotBands}.
+ * @return {Object}
+ * The added plot band.
+ * @sample highcharts/members/axis-addplotband/
+ * Toggle the plot band from a button
+ */
+ addPlotBand: function (options) {
+ return this.addPlotBandOrLine(options, 'plotBands');
+ },
+
+ /**
+ * Add a plot line after render time.
+ *
+ * @param {AxisPlotLinesOptions} options
+ * A configuration object for the plot line, as defined in {@link
+ * https://api.highcharts.com/highcharts/xAxis.plotLines|
+ * xAxis.plotLines}.
+ * @return {Object}
+ * The added plot line.
+ * @sample highcharts/members/axis-addplotline/
+ * Toggle the plot line from a button
+ */
+ addPlotLine: function (options) {
+ return this.addPlotBandOrLine(options, 'plotLines');
+ },
+
+ /**
+ * Add a plot band or plot line after render time. Called from addPlotBand
+ * and addPlotLine internally.
+ *
+ * @private
+ * @param options {AxisPlotLinesOptions|AxisPlotBandsOptions}
+ * The plotBand or plotLine configuration object.
+ */
+ addPlotBandOrLine: function (options, coll) {
+ var obj = new H.PlotLineOrBand(this, options).render(),
+ userOptions = this.userOptions;
+
+ if (obj) { // #2189
+ // Add it to the user options for exporting and Axis.update
+ if (coll) {
+ userOptions[coll] = userOptions[coll] || [];
+ userOptions[coll].push(options);
+ }
+ this.plotLinesAndBands.push(obj);
+ }
+
+ return obj;
+ },
+
+ /**
+ * Remove a plot band or plot line from the chart by id. Called internally
+ * from `removePlotBand` and `removePlotLine`.
+ *
+ * @private
+ * @param {String} id
+ */
+ removePlotBandOrLine: function (id) {
+ var plotLinesAndBands = this.plotLinesAndBands,
+ options = this.options,
+ userOptions = this.userOptions,
+ i = plotLinesAndBands.length;
+ while (i--) {
+ if (plotLinesAndBands[i].id === id) {
+ plotLinesAndBands[i].destroy();
+ }
+ }
+ each([
+ options.plotLines || [],
+ userOptions.plotLines || [],
+ options.plotBands || [],
+ userOptions.plotBands || []
+ ], function (arr) {
+ i = arr.length;
+ while (i--) {
+ if (arr[i].id === id) {
+ erase(arr, arr[i]);
+ }
+ }
+ });
+ },
+
+ /**
+ * Remove a plot band by its id.
+ *
+ * @param {String} id
+ * The plot band's `id` as given in the original configuration
+ * object or in the `addPlotBand` option.
+ * @sample highcharts/members/axis-removeplotband/
+ * Remove plot band by id
+ * @sample highcharts/members/axis-addplotband/
+ * Toggle the plot band from a button
+ */
+ removePlotBand: function (id) {
+ this.removePlotBandOrLine(id);
+ },
+
+ /**
+ * Remove a plot line by its id.
+ * @param {String} id
+ * The plot line's `id` as given in the original configuration
+ * object or in the `addPlotLine` option.
+ * @sample highcharts/xaxis/plotlines-id/
+ * Remove plot line by id
+ * @sample highcharts/members/axis-addplotline/
+ * Toggle the plot line from a button
+ */
+ removePlotLine: function (id) {
+ this.removePlotBandOrLine(id);
+ }
+});
+
+}(Highcharts, Axis));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var each = H.each,
+ extend = H.extend,
+ format = H.format,
+ isNumber = H.isNumber,
+ map = H.map,
+ merge = H.merge,
+ pick = H.pick,
+ splat = H.splat,
+ syncTimeout = H.syncTimeout,
+ timeUnits = H.timeUnits;
+/**
+ * The tooltip object
+ * @param {Object} chart The chart instance
+ * @param {Object} options Tooltip options
+ */
+H.Tooltip = function () {
+ this.init.apply(this, arguments);
+};
+
+H.Tooltip.prototype = {
+
+ init: function (chart, options) {
+
+ // Save the chart and options
+ this.chart = chart;
+ this.options = options;
+
+ // List of crosshairs
+ this.crosshairs = [];
+
+ // Current values of x and y when animating
+ this.now = { x: 0, y: 0 };
+
+ // The tooltip is initially hidden
+ this.isHidden = true;
+
+
+
+ // Public property for getting the shared state.
+ this.split = options.split && !chart.inverted;
+ this.shared = options.shared || this.split;
+
+ },
+
+ /**
+ * Destroy the single tooltips in a split tooltip.
+ * If the tooltip is active then it is not destroyed, unless forced to.
+ * @param {boolean} force Force destroy all tooltips.
+ * @return {undefined}
+ */
+ cleanSplit: function (force) {
+ each(this.chart.series, function (series) {
+ var tt = series && series.tt;
+ if (tt) {
+ if (!tt.isActive || force) {
+ series.tt = tt.destroy();
+ } else {
+ tt.isActive = false;
+ }
+ }
+ });
+ },
+
+
+
+
+ /**
+ * Create the Tooltip label element if it doesn't exist, then return the
+ * label.
+ */
+ getLabel: function () {
+
+ var renderer = this.chart.renderer,
+ options = this.options;
+
+ if (!this.label) {
+ // Create the label
+ if (this.split) {
+ this.label = renderer.g('tooltip');
+ } else {
+ this.label = renderer.label(
+ '',
+ 0,
+ 0,
+ options.shape || 'callout',
+ null,
+ null,
+ options.useHTML,
+ null,
+ 'tooltip'
+ )
+ .attr({
+ padding: options.padding,
+ r: options.borderRadius
+ });
+
+
+ this.label
+ .attr({
+ 'fill': options.backgroundColor,
+ 'stroke-width': options.borderWidth
+ })
+ // #2301, #2657
+ .css(options.style)
+ .shadow(options.shadow);
+
+ }
+
+
+
+ this.label
+ .attr({
+ zIndex: 8
+ })
+ .add();
+ }
+ return this.label;
+ },
+
+ update: function (options) {
+ this.destroy();
+ // Update user options (#6218)
+ merge(true, this.chart.options.tooltip.userOptions, options);
+ this.init(this.chart, merge(true, this.options, options));
+ },
+
+ /**
+ * Destroy the tooltip and its elements.
+ */
+ destroy: function () {
+ // Destroy and clear local variables
+ if (this.label) {
+ this.label = this.label.destroy();
+ }
+ if (this.split && this.tt) {
+ this.cleanSplit(this.chart, true);
+ this.tt = this.tt.destroy();
+ }
+ clearTimeout(this.hideTimer);
+ clearTimeout(this.tooltipTimeout);
+ },
+
+ /**
+ * Provide a soft movement for the tooltip
+ *
+ * @param {Number} x
+ * @param {Number} y
+ * @private
+ */
+ move: function (x, y, anchorX, anchorY) {
+ var tooltip = this,
+ now = tooltip.now,
+ animate = tooltip.options.animation !== false &&
+ !tooltip.isHidden &&
+ // When we get close to the target position, abort animation and
+ // land on the right place (#3056)
+ (Math.abs(x - now.x) > 1 || Math.abs(y - now.y) > 1),
+ skipAnchor = tooltip.followPointer || tooltip.len > 1;
+
+ // Get intermediate values for animation
+ extend(now, {
+ x: animate ? (2 * now.x + x) / 3 : x,
+ y: animate ? (now.y + y) / 2 : y,
+ anchorX: skipAnchor ?
+ undefined :
+ animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
+ anchorY: skipAnchor ?
+ undefined :
+ animate ? (now.anchorY + anchorY) / 2 : anchorY
+ });
+
+ // Move to the intermediate value
+ tooltip.getLabel().attr(now);
+
+
+ // Run on next tick of the mouse tracker
+ if (animate) {
+
+ // Never allow two timeouts
+ clearTimeout(this.tooltipTimeout);
+
+ // Set the fixed interval ticking for the smooth tooltip
+ this.tooltipTimeout = setTimeout(function () {
+ // The interval function may still be running during destroy,
+ // so check that the chart is really there before calling.
+ if (tooltip) {
+ tooltip.move(x, y, anchorX, anchorY);
+ }
+ }, 32);
+
+ }
+ },
+
+ /**
+ * Hide the tooltip
+ */
+ hide: function (delay) {
+ var tooltip = this;
+ // disallow duplicate timers (#1728, #1766)
+ clearTimeout(this.hideTimer);
+ delay = pick(delay, this.options.hideDelay, 500);
+ if (!this.isHidden) {
+ this.hideTimer = syncTimeout(function () {
+ tooltip.getLabel()[delay ? 'fadeOut' : 'hide']();
+ tooltip.isHidden = true;
+ }, delay);
+ }
+ },
+
+ /**
+ * Extendable method to get the anchor position of the tooltip
+ * from a point or set of points
+ */
+ getAnchor: function (points, mouseEvent) {
+ var ret,
+ chart = this.chart,
+ inverted = chart.inverted,
+ plotTop = chart.plotTop,
+ plotLeft = chart.plotLeft,
+ plotX = 0,
+ plotY = 0,
+ yAxis,
+ xAxis;
+
+ points = splat(points);
+
+ // Pie uses a special tooltipPos
+ ret = points[0].tooltipPos;
+
+ // When tooltip follows mouse, relate the position to the mouse
+ if (this.followPointer && mouseEvent) {
+ if (mouseEvent.chartX === undefined) {
+ mouseEvent = chart.pointer.normalize(mouseEvent);
+ }
+ ret = [
+ mouseEvent.chartX - chart.plotLeft,
+ mouseEvent.chartY - plotTop
+ ];
+ }
+ // When shared, use the average position
+ if (!ret) {
+ each(points, function (point) {
+ yAxis = point.series.yAxis;
+ xAxis = point.series.xAxis;
+ plotX += point.plotX +
+ (!inverted && xAxis ? xAxis.left - plotLeft : 0);
+ plotY +=
+ (
+ point.plotLow ?
+ (point.plotLow + point.plotHigh) / 2 :
+ point.plotY
+ ) +
+ (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
+ });
+
+ plotX /= points.length;
+ plotY /= points.length;
+
+ ret = [
+ inverted ? chart.plotWidth - plotY : plotX,
+ this.shared && !inverted && points.length > 1 && mouseEvent ?
+ // place shared tooltip next to the mouse (#424)
+ mouseEvent.chartY - plotTop :
+ inverted ? chart.plotHeight - plotX : plotY
+ ];
+ }
+
+ return map(ret, Math.round);
+ },
+
+ /**
+ * Place the tooltip in a chart without spilling over
+ * and not covering the point it self.
+ */
+ getPosition: function (boxWidth, boxHeight, point) {
+
+ var chart = this.chart,
+ distance = this.distance,
+ ret = {},
+ // Don't use h if chart isn't inverted (#7242)
+ h = (chart.inverted && point.h) || 0, // #4117
+ swapped,
+ first = ['y', chart.chartHeight, boxHeight,
+ point.plotY + chart.plotTop, chart.plotTop,
+ chart.plotTop + chart.plotHeight],
+ second = ['x', chart.chartWidth, boxWidth,
+ point.plotX + chart.plotLeft, chart.plotLeft,
+ chart.plotLeft + chart.plotWidth],
+ // The far side is right or bottom
+ preferFarSide = !this.followPointer && pick(
+ point.ttBelow,
+ !chart.inverted === !!point.negative
+ ), // #4984
+
+ /**
+ * Handle the preferred dimension. When the preferred dimension is
+ * tooltip on top or bottom of the point, it will look for space
+ * there.
+ */
+ firstDimension = function (
+ dim,
+ outerSize,
+ innerSize,
+ point,
+ min,
+ max
+ ) {
+ var roomLeft = innerSize < point - distance,
+ roomRight = point + distance + innerSize < outerSize,
+ alignedLeft = point - distance - innerSize,
+ alignedRight = point + distance;
+
+ if (preferFarSide && roomRight) {
+ ret[dim] = alignedRight;
+ } else if (!preferFarSide && roomLeft) {
+ ret[dim] = alignedLeft;
+ } else if (roomLeft) {
+ ret[dim] = Math.min(
+ max - innerSize,
+ alignedLeft - h < 0 ? alignedLeft : alignedLeft - h
+ );
+ } else if (roomRight) {
+ ret[dim] = Math.max(
+ min,
+ alignedRight + h + innerSize > outerSize ?
+ alignedRight :
+ alignedRight + h
+ );
+ } else {
+ return false;
+ }
+ },
+ /**
+ * Handle the secondary dimension. If the preferred dimension is
+ * tooltip on top or bottom of the point, the second dimension is to
+ * align the tooltip above the point, trying to align center but
+ * allowing left or right align within the chart box.
+ */
+ secondDimension = function (dim, outerSize, innerSize, point) {
+ var retVal;
+
+ // Too close to the edge, return false and swap dimensions
+ if (point < distance || point > outerSize - distance) {
+ retVal = false;
+ // Align left/top
+ } else if (point < innerSize / 2) {
+ ret[dim] = 1;
+ // Align right/bottom
+ } else if (point > outerSize - innerSize / 2) {
+ ret[dim] = outerSize - innerSize - 2;
+ // Align center
+ } else {
+ ret[dim] = point - innerSize / 2;
+ }
+ return retVal;
+ },
+ /**
+ * Swap the dimensions
+ */
+ swap = function (count) {
+ var temp = first;
+ first = second;
+ second = temp;
+ swapped = count;
+ },
+ run = function () {
+ if (firstDimension.apply(0, first) !== false) {
+ if (
+ secondDimension.apply(0, second) === false &&
+ !swapped
+ ) {
+ swap(true);
+ run();
+ }
+ } else if (!swapped) {
+ swap(true);
+ run();
+ } else {
+ ret.x = ret.y = 0;
+ }
+ };
+
+ // Under these conditions, prefer the tooltip on the side of the point
+ if (chart.inverted || this.len > 1) {
+ swap();
+ }
+ run();
+
+ return ret;
+
+ },
+
+ /**
+ * In case no user defined formatter is given, this will be used. Note that
+ * the context here is an object holding point, series, x, y etc.
+ *
+ * @returns {String|Array}
+ */
+ defaultFormatter: function (tooltip) {
+ var items = this.points || splat(this),
+ s;
+
+ // Build the header
+ s = [tooltip.tooltipFooterHeaderFormatter(items[0])];
+
+ // build the values
+ s = s.concat(tooltip.bodyFormatter(items));
+
+ // footer
+ s.push(tooltip.tooltipFooterHeaderFormatter(items[0], true));
+
+ return s;
+ },
+
+ /**
+ * Refresh the tooltip's text and position.
+ * @param {Object|Array} pointOrPoints Rither a point or an array of points
+ */
+ refresh: function (pointOrPoints, mouseEvent) {
+ var tooltip = this,
+ label,
+ options = tooltip.options,
+ x,
+ y,
+ point = pointOrPoints,
+ anchor,
+ textConfig = {},
+ text,
+ pointConfig = [],
+ formatter = options.formatter || tooltip.defaultFormatter,
+ shared = tooltip.shared,
+ currentSeries;
+
+ if (!options.enabled) {
+ return;
+ }
+
+ clearTimeout(this.hideTimer);
+
+ // get the reference point coordinates (pie charts use tooltipPos)
+ tooltip.followPointer = splat(point)[0].series.tooltipOptions
+ .followPointer;
+ anchor = tooltip.getAnchor(point, mouseEvent);
+ x = anchor[0];
+ y = anchor[1];
+
+ // shared tooltip, array is sent over
+ if (shared && !(point.series && point.series.noSharedTooltip)) {
+ each(point, function (item) {
+ item.setState('hover');
+
+ pointConfig.push(item.getLabelConfig());
+ });
+
+ textConfig = {
+ x: point[0].category,
+ y: point[0].y
+ };
+ textConfig.points = pointConfig;
+ point = point[0];
+
+ // single point tooltip
+ } else {
+ textConfig = point.getLabelConfig();
+ }
+ this.len = pointConfig.length; // #6128
+ text = formatter.call(textConfig, tooltip);
+
+ // register the current series
+ currentSeries = point.series;
+ this.distance = pick(currentSeries.tooltipOptions.distance, 16);
+
+ // update the inner HTML
+ if (text === false) {
+ this.hide();
+ } else {
+
+ label = tooltip.getLabel();
+
+ // show it
+ if (tooltip.isHidden) {
+ label.attr({
+ opacity: 1
+ }).show();
+ }
+
+ // update text
+ if (tooltip.split) {
+ this.renderSplit(text, splat(pointOrPoints));
+ } else {
+
+ // Prevent the tooltip from flowing over the chart box (#6659)
+
+ if (!options.style.width) {
+
+ label.css({
+ width: this.chart.spacingBox.width
+ });
+
+ }
+
+
+ label.attr({
+ text: text && text.join ? text.join('') : text
+ });
+
+ // Set the stroke color of the box to reflect the point
+ label.removeClass(/highcharts-color-[\d]+/g)
+ .addClass(
+ 'highcharts-color-' +
+ pick(point.colorIndex, currentSeries.colorIndex)
+ );
+
+
+ label.attr({
+ stroke: (
+ options.borderColor ||
+ point.color ||
+ currentSeries.color ||
+ '#666666'
+ )
+ });
+
+
+ tooltip.updatePosition({
+ plotX: x,
+ plotY: y,
+ negative: point.negative,
+ ttBelow: point.ttBelow,
+ h: anchor[2] || 0
+ });
+ }
+
+ this.isHidden = false;
+ }
+ },
+
+ /**
+ * Render the split tooltip. Loops over each point's text and adds
+ * a label next to the point, then uses the distribute function to
+ * find best non-overlapping positions.
+ */
+ renderSplit: function (labels, points) {
+ var tooltip = this,
+ boxes = [],
+ chart = this.chart,
+ ren = chart.renderer,
+ rightAligned = true,
+ options = this.options,
+ headerHeight = 0,
+ tooltipLabel = this.getLabel();
+
+ // Graceful degradation for legacy formatters
+ if (H.isString(labels)) {
+ labels = [false, labels];
+ }
+ // Create the individual labels for header and points, ignore footer
+ each(labels.slice(0, points.length + 1), function (str, i) {
+ if (str !== false) {
+ var point = points[i - 1] ||
+ // Item 0 is the header. Instead of this, we could also
+ // use the crosshair label
+ { isHeader: true, plotX: points[0].plotX },
+ owner = point.series || tooltip,
+ tt = owner.tt,
+ series = point.series || {},
+ colorClass = 'highcharts-color-' + pick(
+ point.colorIndex,
+ series.colorIndex,
+ 'none'
+ ),
+ target,
+ x,
+ bBox,
+ boxWidth;
+
+ // Store the tooltip referance on the series
+ if (!tt) {
+ owner.tt = tt = ren.label(
+ null,
+ null,
+ null,
+ 'callout',
+ null,
+ null,
+ options.useHTML
+ )
+ .addClass('highcharts-tooltip-box ' + colorClass)
+ .attr({
+ 'padding': options.padding,
+ 'r': options.borderRadius,
+
+ 'fill': options.backgroundColor,
+ 'stroke': (
+ options.borderColor ||
+ point.color ||
+ series.color ||
+ '#333333'
+ ),
+ 'stroke-width': options.borderWidth
+
+ })
+ .add(tooltipLabel);
+ }
+
+ tt.isActive = true;
+ tt.attr({
+ text: str
+ });
+
+ tt.css(options.style)
+ .shadow(options.shadow);
+
+
+ // Get X position now, so we can move all to the other side in
+ // case of overflow
+ bBox = tt.getBBox();
+ boxWidth = bBox.width + tt.strokeWidth();
+ if (point.isHeader) {
+ headerHeight = bBox.height;
+ x = Math.max(
+ 0, // No left overflow
+ Math.min(
+ point.plotX + chart.plotLeft - boxWidth / 2,
+ // No right overflow (#5794)
+ chart.chartWidth - boxWidth
+ )
+ );
+ } else {
+ x = point.plotX + chart.plotLeft -
+ pick(options.distance, 16) - boxWidth;
+ }
+
+
+ // If overflow left, we don't use this x in the next loop
+ if (x < 0) {
+ rightAligned = false;
+ }
+
+ // Prepare for distribution
+ target = (point.series && point.series.yAxis &&
+ point.series.yAxis.pos) + (point.plotY || 0);
+ target -= chart.plotTop;
+ boxes.push({
+ target: point.isHeader ?
+ chart.plotHeight + headerHeight :
+ target,
+ rank: point.isHeader ? 1 : 0,
+ size: owner.tt.getBBox().height + 1,
+ point: point,
+ x: x,
+ tt: tt
+ });
+ }
+ });
+
+ // Clean previous run (for missing points)
+ this.cleanSplit();
+
+ // Distribute and put in place
+ H.distribute(boxes, chart.plotHeight + headerHeight);
+ each(boxes, function (box) {
+ var point = box.point,
+ series = point.series;
+
+ // Put the label in place
+ box.tt.attr({
+ visibility: box.pos === undefined ? 'hidden' : 'inherit',
+ x: (rightAligned || point.isHeader ?
+ box.x :
+ point.plotX + chart.plotLeft + pick(options.distance, 16)),
+ y: box.pos + chart.plotTop,
+ anchorX: point.isHeader ?
+ point.plotX + chart.plotLeft :
+ point.plotX + series.xAxis.pos,
+ anchorY: point.isHeader ?
+ box.pos + chart.plotTop - 15 :
+ point.plotY + series.yAxis.pos
+ });
+ });
+ },
+
+ /**
+ * Find the new position and perform the move
+ */
+ updatePosition: function (point) {
+ var chart = this.chart,
+ label = this.getLabel(),
+ pos = (this.options.positioner || this.getPosition).call(
+ this,
+ label.width,
+ label.height,
+ point
+ );
+
+ // do the move
+ this.move(
+ Math.round(pos.x),
+ Math.round(pos.y || 0), // can be undefined (#3977)
+ point.plotX + chart.plotLeft,
+ point.plotY + chart.plotTop
+ );
+ },
+
+ /**
+ * Get the optimal date format for a point, based on a range.
+ * @param {number} range - The time range
+ * @param {number|Date} date - The date of the point in question
+ * @param {number} startOfWeek - An integer representing the first day of
+ * the week, where 0 is Sunday
+ * @param {Object} dateTimeLabelFormats - A map of time units to formats
+ * @return {string} - the optimal date format for a point
+ */
+ getDateFormat: function (range, date, startOfWeek, dateTimeLabelFormats) {
+ var time = this.chart.time,
+ dateStr = time.dateFormat('%m-%d %H:%M:%S.%L', date),
+ format,
+ n,
+ blank = '01-01 00:00:00.000',
+ strpos = {
+ millisecond: 15,
+ second: 12,
+ minute: 9,
+ hour: 6,
+ day: 3
+ },
+ lastN = 'millisecond'; // for sub-millisecond data, #4223
+ for (n in timeUnits) {
+
+ // If the range is exactly one week and we're looking at a
+ // Sunday/Monday, go for the week format
+ if (
+ range === timeUnits.week &&
+ +time.dateFormat('%w', date) === startOfWeek &&
+ dateStr.substr(6) === blank.substr(6)
+ ) {
+ n = 'week';
+ break;
+ }
+
+ // The first format that is too great for the range
+ if (timeUnits[n] > range) {
+ n = lastN;
+ break;
+ }
+
+ // If the point is placed every day at 23:59, we need to show
+ // the minutes as well. #2637.
+ if (
+ strpos[n] &&
+ dateStr.substr(strpos[n]) !== blank.substr(strpos[n])
+ ) {
+ break;
+ }
+
+ // Weeks are outside the hierarchy, only apply them on
+ // Mondays/Sundays like in the first condition
+ if (n !== 'week') {
+ lastN = n;
+ }
+ }
+
+ if (n) {
+ format = dateTimeLabelFormats[n];
+ }
+
+ return format;
+ },
+
+ /**
+ * Get the best X date format based on the closest point range on the axis.
+ */
+ getXDateFormat: function (point, options, xAxis) {
+ var xDateFormat,
+ dateTimeLabelFormats = options.dateTimeLabelFormats,
+ closestPointRange = xAxis && xAxis.closestPointRange;
+
+ if (closestPointRange) {
+ xDateFormat = this.getDateFormat(
+ closestPointRange,
+ point.x,
+ xAxis.options.startOfWeek,
+ dateTimeLabelFormats
+ );
+ } else {
+ xDateFormat = dateTimeLabelFormats.day;
+ }
+
+ return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581
+ },
+
+ /**
+ * Format the footer/header of the tooltip
+ * #3397: abstraction to enable formatting of footer and header
+ */
+ tooltipFooterHeaderFormatter: function (labelConfig, isFooter) {
+ var footOrHead = isFooter ? 'footer' : 'header',
+ series = labelConfig.series,
+ tooltipOptions = series.tooltipOptions,
+ xDateFormat = tooltipOptions.xDateFormat,
+ xAxis = series.xAxis,
+ isDateTime = (
+ xAxis &&
+ xAxis.options.type === 'datetime' &&
+ isNumber(labelConfig.key)
+ ),
+ formatString = tooltipOptions[footOrHead + 'Format'];
+
+ // Guess the best date format based on the closest point distance (#568,
+ // #3418)
+ if (isDateTime && !xDateFormat) {
+ xDateFormat = this.getXDateFormat(
+ labelConfig,
+ tooltipOptions,
+ xAxis
+ );
+ }
+
+ // Insert the footer date format if any
+ if (isDateTime && xDateFormat) {
+ each(
+ (labelConfig.point && labelConfig.point.tooltipDateKeys) ||
+ ['key'],
+ function (key) {
+ formatString = formatString.replace(
+ '{point.' + key + '}',
+ '{point.' + key + ':' + xDateFormat + '}'
+ );
+ }
+ );
+ }
+
+ return format(formatString, {
+ point: labelConfig,
+ series: series
+ }, this.chart.time);
+ },
+
+ /**
+ * Build the body (lines) of the tooltip by iterating over the items and
+ * returning one entry for each item, abstracting this functionality allows
+ * to easily overwrite and extend it.
+ */
+ bodyFormatter: function (items) {
+ return map(items, function (item) {
+ var tooltipOptions = item.series.tooltipOptions;
+ return (
+ tooltipOptions[
+ (item.point.formatPrefix || 'point') + 'Formatter'
+ ] ||
+ item.point.tooltipFormatter
+ ).call(
+ item.point,
+ tooltipOptions[(item.point.formatPrefix || 'point') + 'Format']
+ );
+ });
+ }
+
+};
+
+}(Highcharts));
+(function (Highcharts) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var H = Highcharts,
+ addEvent = H.addEvent,
+ attr = H.attr,
+ charts = H.charts,
+ color = H.color,
+ css = H.css,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ find = H.find,
+ fireEvent = H.fireEvent,
+ isObject = H.isObject,
+ offset = H.offset,
+ pick = H.pick,
+ splat = H.splat,
+ Tooltip = H.Tooltip;
+
+/**
+ * The mouse and touch tracker object. Each {@link Chart} item has one
+ * assosiated Pointer item that can be accessed from the {@link Chart.pointer}
+ * property.
+ *
+ * @class
+ * @param {Chart} chart
+ * The Chart instance.
+ * @param {Options} options
+ * The root options object. The pointer uses options from the chart and
+ * tooltip structures.
+ */
+Highcharts.Pointer = function (chart, options) {
+ this.init(chart, options);
+};
+
+Highcharts.Pointer.prototype = {
+ /**
+ * Initialize the Pointer.
+ *
+ * @private
+ */
+ init: function (chart, options) {
+
+ // Store references
+ this.options = options;
+ this.chart = chart;
+
+ // Do we need to handle click on a touch device?
+ this.runChartClick = options.chart.events && !!options.chart.events.click;
+
+ this.pinchDown = [];
+ this.lastValidTouch = {};
+
+ if (Tooltip) {
+ chart.tooltip = new Tooltip(chart, options.tooltip);
+ this.followTouchMove = pick(options.tooltip.followTouchMove, true);
+ }
+
+ this.setDOMEvents();
+ },
+
+ /**
+ * Resolve the zoomType option, this is reset on all touch start and mouse
+ * down events.
+ *
+ * @private
+ */
+ zoomOption: function (e) {
+ var chart = this.chart,
+ options = chart.options.chart,
+ zoomType = options.zoomType || '',
+ inverted = chart.inverted,
+ zoomX,
+ zoomY;
+
+ // Look for the pinchType option
+ if (/touch/.test(e.type)) {
+ zoomType = pick(options.pinchType, zoomType);
+ }
+
+ this.zoomX = zoomX = /x/.test(zoomType);
+ this.zoomY = zoomY = /y/.test(zoomType);
+ this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
+ this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
+ this.hasZoom = zoomX || zoomY;
+ },
+
+ /**
+ * @typedef {Object} PointerEvent
+ * A native browser mouse or touch event, extended with position
+ * information relative to the {@link Chart.container}.
+ * @property {Number} chartX
+ * The X coordinate of the pointer interaction relative to the
+ * chart.
+ * @property {Number} chartY
+ * The Y coordinate of the pointer interaction relative to the
+ * chart.
+ *
+ */
+ /**
+ * Takes a browser event object and extends it with custom Highcharts
+ * properties `chartX` and `chartY` in order to work on the internal
+ * coordinate system.
+ *
+ * @param {Object} e
+ * The event object in standard browsers.
+ *
+ * @return {PointerEvent}
+ * A browser event with extended properties `chartX` and `chartY`.
+ */
+ normalize: function (e, chartPosition) {
+ var ePos;
+
+ // iOS (#2757)
+ ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e;
+
+ // Get mouse position
+ if (!chartPosition) {
+ this.chartPosition = chartPosition = offset(this.chart.container);
+ }
+
+ return extend(e, {
+ chartX: Math.round(ePos.pageX - chartPosition.left),
+ chartY: Math.round(ePos.pageY - chartPosition.top)
+ });
+ },
+
+ /**
+ * Get the click position in terms of axis values.
+ *
+ * @param {PointerEvent} e
+ * A pointer event, extended with `chartX` and `chartY`
+ * properties.
+ */
+ getCoordinates: function (e) {
+ var coordinates = {
+ xAxis: [],
+ yAxis: []
+ };
+
+ each(this.chart.axes, function (axis) {
+ coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
+ axis: axis,
+ value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
+ });
+ });
+ return coordinates;
+ },
+ /**
+ * Finds the closest point to a set of coordinates, using the k-d-tree
+ * algorithm.
+ *
+ * @param {Array.} series
+ * All the series to search in.
+ * @param {boolean} shared
+ * Whether it is a shared tooltip or not.
+ * @param {object} coordinates
+ * Chart coordinates of the pointer.
+ * @param {number} coordinates.chartX
+ * @param {number} coordinates.chartY
+ *
+ * @return {Point|undefined} The point closest to given coordinates.
+ */
+ findNearestKDPoint: function (series, shared, coordinates) {
+ var closest,
+ sort = function (p1, p2) {
+ var isCloserX = p1.distX - p2.distX,
+ isCloser = p1.dist - p2.dist,
+ isAbove =
+ (p2.series.group && p2.series.group.zIndex) -
+ (p1.series.group && p1.series.group.zIndex),
+ result;
+
+ // We have two points which are not in the same place on xAxis
+ // and shared tooltip:
+ if (isCloserX !== 0 && shared) { // #5721
+ result = isCloserX;
+ // Points are not exactly in the same place on x/yAxis:
+ } else if (isCloser !== 0) {
+ result = isCloser;
+ // The same xAxis and yAxis position, sort by z-index:
+ } else if (isAbove !== 0) {
+ result = isAbove;
+ // The same zIndex, sort by array index:
+ } else {
+ result = p1.series.index > p2.series.index ? -1 : 1;
+ }
+ return result;
+ };
+ each(series, function (s) {
+ var noSharedTooltip = s.noSharedTooltip && shared,
+ compareX = (
+ !noSharedTooltip &&
+ s.options.findNearestPointBy.indexOf('y') < 0
+ ),
+ point = s.searchPoint(
+ coordinates,
+ compareX
+ );
+ if (
+ // Check that we actually found a point on the series.
+ isObject(point, true) &&
+ // Use the new point if it is closer.
+ (!isObject(closest, true) || (sort(closest, point) > 0))
+ ) {
+ closest = point;
+ }
+ });
+ return closest;
+ },
+ getPointFromEvent: function (e) {
+ var target = e.target,
+ point;
+
+ while (target && !point) {
+ point = target.point;
+ target = target.parentNode;
+ }
+ return point;
+ },
+
+ getChartCoordinatesFromPoint: function (point, inverted) {
+ var series = point.series,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis,
+ plotX = pick(point.clientX, point.plotX);
+
+ if (xAxis && yAxis) {
+ return inverted ? {
+ chartX: xAxis.len + xAxis.pos - plotX,
+ chartY: yAxis.len + yAxis.pos - point.plotY
+ } : {
+ chartX: plotX + xAxis.pos,
+ chartY: point.plotY + yAxis.pos
+ };
+ }
+ },
+
+ /**
+ * Calculates what is the current hovered point/points and series.
+ *
+ * @private
+ *
+ * @param {undefined|Point} existingHoverPoint
+ * The point currrently beeing hovered.
+ * @param {undefined|Series} existingHoverSeries
+ * The series currently beeing hovered.
+ * @param {Array.} series
+ * All the series in the chart.
+ * @param {boolean} isDirectTouch
+ * Is the pointer directly hovering the point.
+ * @param {boolean} shared
+ * Whether it is a shared tooltip or not.
+ * @param {object} coordinates
+ * Chart coordinates of the pointer.
+ * @param {number} coordinates.chartX
+ * @param {number} coordinates.chartY
+ *
+ * @return {object}
+ * Object containing resulting hover data.
+ */
+ getHoverData: function (
+ existingHoverPoint,
+ existingHoverSeries,
+ series,
+ isDirectTouch,
+ shared,
+ coordinates,
+ params
+ ) {
+ var hoverPoint,
+ hoverPoints = [],
+ hoverSeries = existingHoverSeries,
+ isBoosting = params && params.isBoosting,
+ useExisting = !!(isDirectTouch && existingHoverPoint),
+ notSticky = hoverSeries && !hoverSeries.stickyTracking,
+ filter = function (s) {
+ return (
+ s.visible &&
+ !(!shared && s.directTouch) && // #3821
+ pick(s.options.enableMouseTracking, true)
+ );
+ },
+ // Which series to look in for the hover point
+ searchSeries = notSticky ?
+ // Only search on hovered series if it has stickyTracking false
+ [hoverSeries] :
+ // Filter what series to look in.
+ H.grep(series, function (s) {
+ return filter(s) && s.stickyTracking;
+ });
+
+ // Use existing hovered point or find the one closest to coordinates.
+ hoverPoint = useExisting ?
+ existingHoverPoint :
+ this.findNearestKDPoint(searchSeries, shared, coordinates);
+
+ // Assign hover series
+ hoverSeries = hoverPoint && hoverPoint.series;
+
+ // If we have a hoverPoint, assign hoverPoints.
+ if (hoverPoint) {
+ // When tooltip is shared, it displays more than one point
+ if (shared && !hoverSeries.noSharedTooltip) {
+ searchSeries = H.grep(series, function (s) {
+ return filter(s) && !s.noSharedTooltip;
+ });
+
+ // Get all points with the same x value as the hoverPoint
+ each(searchSeries, function (s) {
+ var point = find(s.points, function (p) {
+ return p.x === hoverPoint.x && !p.isNull;
+ });
+ if (isObject(point)) {
+ /*
+ * Boost returns a minimal point. Convert it to a usable
+ * point for tooltip and states.
+ */
+ if (isBoosting) {
+ point = s.getPoint(point);
+ }
+ hoverPoints.push(point);
+ }
+ });
+ } else {
+ hoverPoints.push(hoverPoint);
+ }
+ }
+ return {
+ hoverPoint: hoverPoint,
+ hoverSeries: hoverSeries,
+ hoverPoints: hoverPoints
+ };
+ },
+ /**
+ * With line type charts with a single tracker, get the point closest to the
+ * mouse. Run Point.onMouseOver and display tooltip for the point or points.
+ *
+ * @private
+ */
+ runPointActions: function (e, p) {
+ var pointer = this,
+ chart = pointer.chart,
+ series = chart.series,
+ tooltip = chart.tooltip && chart.tooltip.options.enabled ?
+ chart.tooltip :
+ undefined,
+ shared = tooltip ? tooltip.shared : false,
+ hoverPoint = p || chart.hoverPoint,
+ hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries,
+ // onMouseOver or already hovering a series with directTouch
+ isDirectTouch = !!p || (
+ (hoverSeries && hoverSeries.directTouch) &&
+ pointer.isDirectTouch
+ ),
+ hoverData = this.getHoverData(
+ hoverPoint,
+ hoverSeries,
+ series,
+ isDirectTouch,
+ shared,
+ e,
+ { isBoosting: chart.isBoosting }
+ ),
+ useSharedTooltip,
+ followPointer,
+ anchor,
+ points;
+
+ // Update variables from hoverData.
+ hoverPoint = hoverData.hoverPoint;
+ points = hoverData.hoverPoints;
+ hoverSeries = hoverData.hoverSeries;
+ followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
+ useSharedTooltip = shared && hoverSeries && !hoverSeries.noSharedTooltip;
+
+ // Refresh tooltip for kdpoint if new hover point or tooltip was hidden
+ // #3926, #4200
+ if (
+ hoverPoint &&
+ // !(hoverSeries && hoverSeries.directTouch) &&
+ (hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden))
+ ) {
+ each(chart.hoverPoints || [], function (p) {
+ if (H.inArray(p, points) === -1) {
+ p.setState();
+ }
+ });
+ // Do mouseover on all points (#3919, #3985, #4410, #5622)
+ each(points || [], function (p) {
+ p.setState('hover');
+ });
+ // set normal state to previous series
+ if (chart.hoverSeries !== hoverSeries) {
+ hoverSeries.onMouseOver();
+ }
+
+ // If tracking is on series in stead of on each point,
+ // fire mouseOver on hover point. // #4448
+ if (chart.hoverPoint) {
+ chart.hoverPoint.firePointEvent('mouseOut');
+ }
+
+ // Hover point may have been destroyed in the event handlers (#7127)
+ if (!hoverPoint.series) {
+ return;
+ }
+
+ hoverPoint.firePointEvent('mouseOver');
+ chart.hoverPoints = points;
+ chart.hoverPoint = hoverPoint;
+ // Draw tooltip if necessary
+ if (tooltip) {
+ tooltip.refresh(useSharedTooltip ? points : hoverPoint, e);
+ }
+ // Update positions (regardless of kdpoint or hoverPoint)
+ } else if (followPointer && tooltip && !tooltip.isHidden) {
+ anchor = tooltip.getAnchor([{}], e);
+ tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
+ }
+
+ // Start the event listener to pick up the tooltip and crosshairs
+ if (!pointer.unDocMouseMove) {
+ pointer.unDocMouseMove = addEvent(
+ chart.container.ownerDocument,
+ 'mousemove',
+ function (e) {
+ var chart = charts[H.hoverChartIndex];
+ if (chart) {
+ chart.pointer.onDocumentMouseMove(e);
+ }
+ }
+ );
+ }
+
+ // Issues related to crosshair #4927, #5269 #5066, #5658
+ each(chart.axes, function drawAxisCrosshair(axis) {
+ var snap = pick(axis.crosshair.snap, true),
+ point = !snap ?
+ undefined :
+ H.find(points, function (p) {
+ return p.series[axis.coll] === axis;
+ });
+
+ // Axis has snapping crosshairs, and one of the hover points belongs
+ // to axis. Always call drawCrosshair when it is not snap.
+ if (point || !snap) {
+ axis.drawCrosshair(e, point);
+ // Axis has snapping crosshairs, but no hover point belongs to axis
+ } else {
+ axis.hideCrosshair();
+ }
+ });
+ },
+
+ /**
+ * Reset the tracking by hiding the tooltip, the hover series state and the
+ * hover point
+ *
+ * @param allowMove {Boolean}
+ * Instead of destroying the tooltip altogether, allow moving it if
+ * possible.
+ */
+ reset: function (allowMove, delay) {
+ var pointer = this,
+ chart = pointer.chart,
+ hoverSeries = chart.hoverSeries,
+ hoverPoint = chart.hoverPoint,
+ hoverPoints = chart.hoverPoints,
+ tooltip = chart.tooltip,
+ tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint;
+
+ // Check if the points have moved outside the plot area (#1003, #4736, #5101)
+ if (allowMove && tooltipPoints) {
+ each(splat(tooltipPoints), function (point) {
+ if (point.series.isCartesian && point.plotX === undefined) {
+ allowMove = false;
+ }
+ });
+ }
+
+ // Just move the tooltip, #349
+ if (allowMove) {
+ if (tooltip && tooltipPoints) {
+ tooltip.refresh(tooltipPoints);
+ if (hoverPoint) { // #2500
+ hoverPoint.setState(hoverPoint.state, true);
+ each(chart.axes, function (axis) {
+ if (axis.crosshair) {
+ axis.drawCrosshair(null, hoverPoint);
+ }
+ });
+ }
+ }
+
+ // Full reset
+ } else {
+
+ if (hoverPoint) {
+ hoverPoint.onMouseOut();
+ }
+
+ if (hoverPoints) {
+ each(hoverPoints, function (point) {
+ point.setState();
+ });
+ }
+
+ if (hoverSeries) {
+ hoverSeries.onMouseOut();
+ }
+
+ if (tooltip) {
+ tooltip.hide(delay);
+ }
+
+ if (pointer.unDocMouseMove) {
+ pointer.unDocMouseMove = pointer.unDocMouseMove();
+ }
+
+ // Remove crosshairs
+ each(chart.axes, function (axis) {
+ axis.hideCrosshair();
+ });
+
+ pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null;
+ }
+ },
+
+ /**
+ * Scale series groups to a certain scale and translation.
+ *
+ * @private
+ */
+ scaleGroups: function (attribs, clip) {
+
+ var chart = this.chart,
+ seriesAttribs;
+
+ // Scale each series
+ each(chart.series, function (series) {
+ seriesAttribs = attribs || series.getPlotBox(); // #1701
+ if (series.xAxis && series.xAxis.zoomEnabled && series.group) {
+ series.group.attr(seriesAttribs);
+ if (series.markerGroup) {
+ series.markerGroup.attr(seriesAttribs);
+ series.markerGroup.clip(clip ? chart.clipRect : null);
+ }
+ if (series.dataLabelsGroup) {
+ series.dataLabelsGroup.attr(seriesAttribs);
+ }
+ }
+ });
+
+ // Clip
+ chart.clipRect.attr(clip || chart.clipBox);
+ },
+
+ /**
+ * Start a drag operation.
+ *
+ * @private
+ */
+ dragStart: function (e) {
+ var chart = this.chart;
+
+ // Record the start position
+ chart.mouseIsDown = e.type;
+ chart.cancelClick = false;
+ chart.mouseDownX = this.mouseDownX = e.chartX;
+ chart.mouseDownY = this.mouseDownY = e.chartY;
+ },
+
+ /**
+ * Perform a drag operation in response to a mousemove event while the mouse
+ * is down.
+ *
+ * @private
+ */
+ drag: function (e) {
+
+ var chart = this.chart,
+ chartOptions = chart.options.chart,
+ chartX = e.chartX,
+ chartY = e.chartY,
+ zoomHor = this.zoomHor,
+ zoomVert = this.zoomVert,
+ plotLeft = chart.plotLeft,
+ plotTop = chart.plotTop,
+ plotWidth = chart.plotWidth,
+ plotHeight = chart.plotHeight,
+ clickedInside,
+ size,
+ selectionMarker = this.selectionMarker,
+ mouseDownX = this.mouseDownX,
+ mouseDownY = this.mouseDownY,
+ panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key'];
+
+ // If the device supports both touch and mouse (like IE11), and we are touch-dragging
+ // inside the plot area, don't handle the mouse event. #4339.
+ if (selectionMarker && selectionMarker.touch) {
+ return;
+ }
+
+ // If the mouse is outside the plot area, adjust to cooordinates
+ // inside to prevent the selection marker from going outside
+ if (chartX < plotLeft) {
+ chartX = plotLeft;
+ } else if (chartX > plotLeft + plotWidth) {
+ chartX = plotLeft + plotWidth;
+ }
+
+ if (chartY < plotTop) {
+ chartY = plotTop;
+ } else if (chartY > plotTop + plotHeight) {
+ chartY = plotTop + plotHeight;
+ }
+
+ // determine if the mouse has moved more than 10px
+ this.hasDragged = Math.sqrt(
+ Math.pow(mouseDownX - chartX, 2) +
+ Math.pow(mouseDownY - chartY, 2)
+ );
+
+ if (this.hasDragged > 10) {
+ clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);
+
+ // make a selection
+ if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) {
+ if (!selectionMarker) {
+ this.selectionMarker = selectionMarker = chart.renderer.rect(
+ plotLeft,
+ plotTop,
+ zoomHor ? 1 : plotWidth,
+ zoomVert ? 1 : plotHeight,
+ 0
+ )
+ .attr({
+
+ fill: chartOptions.selectionMarkerFill || color('#335cad').setOpacity(0.25).get(),
+
+ 'class': 'highcharts-selection-marker',
+ 'zIndex': 7
+ })
+ .add();
+ }
+ }
+
+ // adjust the width of the selection marker
+ if (selectionMarker && zoomHor) {
+ size = chartX - mouseDownX;
+ selectionMarker.attr({
+ width: Math.abs(size),
+ x: (size > 0 ? 0 : size) + mouseDownX
+ });
+ }
+ // adjust the height of the selection marker
+ if (selectionMarker && zoomVert) {
+ size = chartY - mouseDownY;
+ selectionMarker.attr({
+ height: Math.abs(size),
+ y: (size > 0 ? 0 : size) + mouseDownY
+ });
+ }
+
+ // panning
+ if (clickedInside && !selectionMarker && chartOptions.panning) {
+ chart.pan(e, chartOptions.panning);
+ }
+ }
+ },
+
+ /**
+ * On mouse up or touch end across the entire document, drop the selection.
+ *
+ * @private
+ */
+ drop: function (e) {
+ var pointer = this,
+ chart = this.chart,
+ hasPinched = this.hasPinched;
+
+ if (this.selectionMarker) {
+ var selectionData = {
+ originalEvent: e, // #4890
+ xAxis: [],
+ yAxis: []
+ },
+ selectionBox = this.selectionMarker,
+ selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x,
+ selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y,
+ selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width,
+ selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height,
+ runZoom;
+
+ // a selection has been made
+ if (this.hasDragged || hasPinched) {
+
+ // record each axis' min and max
+ each(chart.axes, function (axis) {
+ if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{ xAxis: 'zoomX', yAxis: 'zoomY' }[axis.coll]])) { // #859, #3569
+ var horiz = axis.horiz,
+ minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding : 0, // #1207, #3075
+ selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) + minPixelPadding),
+ selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight) - minPixelPadding);
+
+ selectionData[axis.coll].push({
+ axis: axis,
+ min: Math.min(selectionMin, selectionMax), // for reversed axes
+ max: Math.max(selectionMin, selectionMax)
+ });
+ runZoom = true;
+ }
+ });
+ if (runZoom) {
+ fireEvent(chart, 'selection', selectionData, function (args) {
+ chart.zoom(extend(args, hasPinched ? { animation: false } : null));
+ });
+ }
+
+ }
+ this.selectionMarker = this.selectionMarker.destroy();
+
+ // Reset scaling preview
+ if (hasPinched) {
+ this.scaleGroups();
+ }
+ }
+
+ // Reset all
+ if (chart) { // it may be destroyed on mouse up - #877
+ css(chart.container, { cursor: chart._cursor });
+ chart.cancelClick = this.hasDragged > 10; // #370
+ chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
+ this.pinchDown = [];
+ }
+ },
+
+ onContainerMouseDown: function (e) {
+
+ if (e.button !== 2) {
+
+ e = this.normalize(e);
+
+ this.zoomOption(e);
+
+ // issue #295, dragging not always working in Firefox
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+
+ this.dragStart(e);
+ }
+ },
+
+
+
+ onDocumentMouseUp: function (e) {
+ if (charts[H.hoverChartIndex]) {
+ charts[H.hoverChartIndex].pointer.drop(e);
+ }
+ },
+
+ /**
+ * Special handler for mouse move that will hide the tooltip when the mouse
+ * leaves the plotarea. Issue #149 workaround. The mouseleave event does not
+ * always fire.
+ *
+ * @private
+ */
+ onDocumentMouseMove: function (e) {
+ var chart = this.chart,
+ chartPosition = this.chartPosition;
+
+ e = this.normalize(e, chartPosition);
+
+ // If we're outside, hide the tooltip
+ if (chartPosition && !this.inClass(e.target, 'highcharts-tracker') &&
+ !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
+ this.reset();
+ }
+ },
+
+ /**
+ * When mouse leaves the container, hide the tooltip.
+ *
+ * @private
+ */
+ onContainerMouseLeave: function (e) {
+ var chart = charts[H.hoverChartIndex];
+ if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target
+ chart.pointer.reset();
+ chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix
+ }
+ },
+
+ // The mousemove, touchmove and touchstart event handler
+ onContainerMouseMove: function (e) {
+
+ var chart = this.chart;
+
+ if (!defined(H.hoverChartIndex) || !charts[H.hoverChartIndex] || !charts[H.hoverChartIndex].mouseIsDown) {
+ H.hoverChartIndex = chart.index;
+ }
+
+ e = this.normalize(e);
+ e.returnValue = false; // #2251, #3224
+
+ if (chart.mouseIsDown === 'mousedown') {
+ this.drag(e);
+ }
+
+ // Show the tooltip and run mouse over events (#977)
+ if ((this.inClass(e.target, 'highcharts-tracker') ||
+ chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
+ this.runPointActions(e);
+ }
+ },
+
+ /**
+ * Utility to detect whether an element has, or has a parent with, a specific
+ * class name. Used on detection of tracker objects and on deciding whether
+ * hovering the tooltip should cause the active series to mouse out.
+ *
+ * @param {SVGDOMElement|HTMLDOMElement} element
+ * The element to investigate.
+ * @param {String} className
+ * The class name to look for.
+ *
+ * @return {Boolean}
+ * True if either the element or one of its parents has the given
+ * class name.
+ */
+ inClass: function (element, className) {
+ var elemClassName;
+ while (element) {
+ elemClassName = attr(element, 'class');
+ if (elemClassName) {
+ if (elemClassName.indexOf(className) !== -1) {
+ return true;
+ }
+ if (elemClassName.indexOf('highcharts-container') !== -1) {
+ return false;
+ }
+ }
+ element = element.parentNode;
+ }
+ },
+
+ onTrackerMouseOut: function (e) {
+ var series = this.chart.hoverSeries,
+ relatedTarget = e.relatedTarget || e.toElement;
+
+ this.isDirectTouch = false;
+
+ if (
+ series &&
+ relatedTarget &&
+ !series.stickyTracking &&
+ !this.inClass(relatedTarget, 'highcharts-tooltip') &&
+ (
+ !this.inClass(
+ relatedTarget,
+ 'highcharts-series-' + series.index
+ ) || // #2499, #4465
+ !this.inClass(relatedTarget, 'highcharts-tracker') // #5553
+ )
+ ) {
+ series.onMouseOut();
+ }
+ },
+
+ onContainerClick: function (e) {
+ var chart = this.chart,
+ hoverPoint = chart.hoverPoint,
+ plotLeft = chart.plotLeft,
+ plotTop = chart.plotTop;
+
+ e = this.normalize(e);
+
+ if (!chart.cancelClick) {
+
+ // On tracker click, fire the series and point events. #783, #1583
+ if (hoverPoint && this.inClass(e.target, 'highcharts-tracker')) {
+
+ // the series click event
+ fireEvent(hoverPoint.series, 'click', extend(e, {
+ point: hoverPoint
+ }));
+
+ // the point click event
+ if (chart.hoverPoint) { // it may be destroyed (#1844)
+ hoverPoint.firePointEvent('click', e);
+ }
+
+ // When clicking outside a tracker, fire a chart event
+ } else {
+ extend(e, this.getCoordinates(e));
+
+ // fire a click event in the chart
+ if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
+ fireEvent(chart, 'click', e);
+ }
+ }
+
+
+ }
+ },
+
+ /**
+ * Set the JS DOM events on the container and document. This method should contain
+ * a one-to-one assignment between methods and their handlers. Any advanced logic should
+ * be moved to the handler reflecting the event's name.
+ *
+ * @private
+ */
+ setDOMEvents: function () {
+
+ var pointer = this,
+ container = pointer.chart.container,
+ ownerDoc = container.ownerDocument;
+
+ container.onmousedown = function (e) {
+ pointer.onContainerMouseDown(e);
+ };
+ container.onmousemove = function (e) {
+ pointer.onContainerMouseMove(e);
+ };
+ container.onclick = function (e) {
+ pointer.onContainerClick(e);
+ };
+ this.unbindContainerMouseLeave = addEvent(
+ container,
+ 'mouseleave',
+ pointer.onContainerMouseLeave
+ );
+ if (!H.unbindDocumentMouseUp) {
+ H.unbindDocumentMouseUp = addEvent(
+ ownerDoc,
+ 'mouseup',
+ pointer.onDocumentMouseUp
+ );
+ }
+ if (H.hasTouch) {
+ container.ontouchstart = function (e) {
+ pointer.onContainerTouchStart(e);
+ };
+ container.ontouchmove = function (e) {
+ pointer.onContainerTouchMove(e);
+ };
+ if (!H.unbindDocumentTouchEnd) {
+ H.unbindDocumentTouchEnd = addEvent(
+ ownerDoc,
+ 'touchend',
+ pointer.onDocumentTouchEnd
+ );
+ }
+ }
+
+ },
+
+ /**
+ * Destroys the Pointer object and disconnects DOM events.
+ */
+ destroy: function () {
+ var pointer = this;
+
+ if (pointer.unDocMouseMove) {
+ pointer.unDocMouseMove();
+ }
+
+ this.unbindContainerMouseLeave();
+
+ if (!H.chartCount) {
+ if (H.unbindDocumentMouseUp) {
+ H.unbindDocumentMouseUp = H.unbindDocumentMouseUp();
+ }
+ if (H.unbindDocumentTouchEnd) {
+ H.unbindDocumentTouchEnd = H.unbindDocumentTouchEnd();
+ }
+ }
+
+ // memory and CPU leak
+ clearInterval(pointer.tooltipTimeout);
+
+ H.objectEach(pointer, function (val, prop) {
+ pointer[prop] = null;
+ });
+ }
+};
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var charts = H.charts,
+ each = H.each,
+ extend = H.extend,
+ map = H.map,
+ noop = H.noop,
+ pick = H.pick,
+ Pointer = H.Pointer;
+
+/* Support for touch devices */
+extend(Pointer.prototype, /** @lends Pointer.prototype */ {
+
+ /**
+ * Run translation operations
+ */
+ pinchTranslate: function (
+ pinchDown,
+ touches,
+ transform,
+ selectionMarker,
+ clip,
+ lastValidTouch
+ ) {
+ if (this.zoomHor) {
+ this.pinchTranslateDirection(
+ true,
+ pinchDown,
+ touches,
+ transform,
+ selectionMarker,
+ clip,
+ lastValidTouch
+ );
+ }
+ if (this.zoomVert) {
+ this.pinchTranslateDirection(
+ false,
+ pinchDown,
+ touches,
+ transform,
+ selectionMarker,
+ clip,
+ lastValidTouch
+ );
+ }
+ },
+
+ /**
+ * Run translation operations for each direction (horizontal and vertical)
+ * independently
+ */
+ pinchTranslateDirection: function (horiz, pinchDown, touches, transform,
+ selectionMarker, clip, lastValidTouch, forcedScale) {
+ var chart = this.chart,
+ xy = horiz ? 'x' : 'y',
+ XY = horiz ? 'X' : 'Y',
+ sChartXY = 'chart' + XY,
+ wh = horiz ? 'width' : 'height',
+ plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
+ selectionWH,
+ selectionXY,
+ clipXY,
+ scale = forcedScale || 1,
+ inverted = chart.inverted,
+ bounds = chart.bounds[horiz ? 'h' : 'v'],
+ singleTouch = pinchDown.length === 1,
+ touch0Start = pinchDown[0][sChartXY],
+ touch0Now = touches[0][sChartXY],
+ touch1Start = !singleTouch && pinchDown[1][sChartXY],
+ touch1Now = !singleTouch && touches[1][sChartXY],
+ outOfBounds,
+ transformScale,
+ scaleKey,
+ setScale = function () {
+ // Don't zoom if fingers are too close on this axis
+ if (!singleTouch && Math.abs(touch0Start - touch1Start) > 20) {
+ scale = forcedScale ||
+ Math.abs(touch0Now - touch1Now) /
+ Math.abs(touch0Start - touch1Start);
+ }
+
+ clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
+ selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] /
+ scale;
+ };
+
+ // Set the scale, first pass
+ setScale();
+
+ // The clip position (x or y) is altered if out of bounds, the selection
+ // position is not
+ selectionXY = clipXY;
+
+ // Out of bounds
+ if (selectionXY < bounds.min) {
+ selectionXY = bounds.min;
+ outOfBounds = true;
+ } else if (selectionXY + selectionWH > bounds.max) {
+ selectionXY = bounds.max - selectionWH;
+ outOfBounds = true;
+ }
+
+ // Is the chart dragged off its bounds, determined by dataMin and
+ // dataMax?
+ if (outOfBounds) {
+
+ // Modify the touchNow position in order to create an elastic drag
+ // movement. This indicates to the user that the chart is responsive
+ // but can't be dragged further.
+ touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
+ if (!singleTouch) {
+ touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
+ }
+
+ // Set the scale, second pass to adapt to the modified touchNow
+ // positions
+ setScale();
+
+ } else {
+ lastValidTouch[xy] = [touch0Now, touch1Now];
+ }
+
+ // Set geometry for clipping, selection and transformation
+ if (!inverted) {
+ clip[xy] = clipXY - plotLeftTop;
+ clip[wh] = selectionWH;
+ }
+ scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
+ transformScale = inverted ? 1 / scale : scale;
+
+ selectionMarker[wh] = selectionWH;
+ selectionMarker[xy] = selectionXY;
+ transform[scaleKey] = scale;
+ transform['translate' + XY] = (transformScale * plotLeftTop) +
+ (touch0Now - (transformScale * touch0Start));
+ },
+
+ /**
+ * Handle touch events with two touches
+ */
+ pinch: function (e) {
+
+ var self = this,
+ chart = self.chart,
+ pinchDown = self.pinchDown,
+ touches = e.touches,
+ touchesLength = touches.length,
+ lastValidTouch = self.lastValidTouch,
+ hasZoom = self.hasZoom,
+ selectionMarker = self.selectionMarker,
+ transform = {},
+ fireClickEvent = touchesLength === 1 &&
+ ((self.inClass(e.target, 'highcharts-tracker') &&
+ chart.runTrackerClick) || self.runChartClick),
+ clip = {};
+
+ // Don't initiate panning until the user has pinched. This prevents us
+ // from blocking page scrolling as users scroll down a long page
+ // (#4210).
+ if (touchesLength > 1) {
+ self.initiated = true;
+ }
+
+ // On touch devices, only proceed to trigger click if a handler is
+ // defined
+ if (hasZoom && self.initiated && !fireClickEvent) {
+ e.preventDefault();
+ }
+
+ // Normalize each touch
+ map(touches, function (e) {
+ return self.normalize(e);
+ });
+
+ // Register the touch start position
+ if (e.type === 'touchstart') {
+ each(touches, function (e, i) {
+ pinchDown[i] = { chartX: e.chartX, chartY: e.chartY };
+ });
+ lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] &&
+ pinchDown[1].chartX];
+ lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] &&
+ pinchDown[1].chartY];
+
+ // Identify the data bounds in pixels
+ each(chart.axes, function (axis) {
+ if (axis.zoomEnabled) {
+ var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
+ minPixelPadding = axis.minPixelPadding,
+ min = axis.toPixels(
+ pick(axis.options.min, axis.dataMin)
+ ),
+ max = axis.toPixels(
+ pick(axis.options.max, axis.dataMax)
+ ),
+ absMin = Math.min(min, max),
+ absMax = Math.max(min, max);
+
+ // Store the bounds for use in the touchmove handler
+ bounds.min = Math.min(axis.pos, absMin - minPixelPadding);
+ bounds.max = Math.max(
+ axis.pos + axis.len,
+ absMax + minPixelPadding
+ );
+ }
+ });
+ self.res = true; // reset on next move
+
+ // Optionally move the tooltip on touchmove
+ } else if (self.followTouchMove && touchesLength === 1) {
+ this.runPointActions(self.normalize(e));
+
+ // Event type is touchmove, handle panning and pinching
+ } else if (pinchDown.length) { // can be 0 when releasing, if touchend
+ // fires first
+
+
+ // Set the marker
+ if (!selectionMarker) {
+ self.selectionMarker = selectionMarker = extend({
+ destroy: noop,
+ touch: true
+ }, chart.plotBox);
+ }
+
+ self.pinchTranslate(
+ pinchDown,
+ touches,
+ transform,
+ selectionMarker,
+ clip,
+ lastValidTouch
+ );
+
+ self.hasPinched = hasZoom;
+
+ // Scale and translate the groups to provide visual feedback during
+ // pinching
+ self.scaleGroups(transform, clip);
+
+ if (self.res) {
+ self.res = false;
+ this.reset(false, 0);
+ }
+ }
+ },
+
+ /**
+ * General touch handler shared by touchstart and touchmove.
+ */
+ touch: function (e, start) {
+ var chart = this.chart,
+ hasMoved,
+ pinchDown,
+ isInside;
+
+ if (chart.index !== H.hoverChartIndex) {
+ this.onContainerMouseLeave({ relatedTarget: true });
+ }
+ H.hoverChartIndex = chart.index;
+
+ if (e.touches.length === 1) {
+
+ e = this.normalize(e);
+
+ isInside = chart.isInsidePlot(
+ e.chartX - chart.plotLeft,
+ e.chartY - chart.plotTop
+ );
+ if (isInside && !chart.openMenu) {
+
+ // Run mouse events and display tooltip etc
+ if (start) {
+ this.runPointActions(e);
+ }
+
+ // Android fires touchmove events after the touchstart even if
+ // the finger hasn't moved, or moved only a pixel or two. In iOS
+ // however, the touchmove doesn't fire unless the finger moves
+ // more than ~4px. So we emulate this behaviour in Android by
+ // checking how much it moved, and cancelling on small
+ // distances. #3450.
+ if (e.type === 'touchmove') {
+ pinchDown = this.pinchDown;
+ hasMoved = pinchDown[0] ? Math.sqrt( // #5266
+ Math.pow(pinchDown[0].chartX - e.chartX, 2) +
+ Math.pow(pinchDown[0].chartY - e.chartY, 2)
+ ) >= 4 : false;
+ }
+
+ if (pick(hasMoved, true)) {
+ this.pinch(e);
+ }
+
+ } else if (start) {
+ // Hide the tooltip on touching outside the plot area (#1203)
+ this.reset();
+ }
+
+ } else if (e.touches.length === 2) {
+ this.pinch(e);
+ }
+ },
+
+ onContainerTouchStart: function (e) {
+ this.zoomOption(e);
+ this.touch(e, true);
+ },
+
+ onContainerTouchMove: function (e) {
+ this.touch(e);
+ },
+
+ onDocumentTouchEnd: function (e) {
+ if (charts[H.hoverChartIndex]) {
+ charts[H.hoverChartIndex].pointer.drop(e);
+ }
+ }
+
+});
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ charts = H.charts,
+ css = H.css,
+ doc = H.doc,
+ extend = H.extend,
+ hasTouch = H.hasTouch,
+ noop = H.noop,
+ Pointer = H.Pointer,
+ removeEvent = H.removeEvent,
+ win = H.win,
+ wrap = H.wrap;
+
+if (!hasTouch && (win.PointerEvent || win.MSPointerEvent)) {
+
+ // The touches object keeps track of the points being touched at all times
+ var touches = {},
+ hasPointerEvent = !!win.PointerEvent,
+ getWebkitTouches = function () {
+ var fake = [];
+ fake.item = function (i) {
+ return this[i];
+ };
+ H.objectEach(touches, function (touch) {
+ fake.push({
+ pageX: touch.pageX,
+ pageY: touch.pageY,
+ target: touch.target
+ });
+ });
+ return fake;
+ },
+ translateMSPointer = function (e, method, wktype, func) {
+ var p;
+ if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
+ func(e);
+ p = charts[H.hoverChartIndex].pointer;
+ p[method]({
+ type: wktype,
+ target: e.currentTarget,
+ preventDefault: noop,
+ touches: getWebkitTouches()
+ });
+ }
+ };
+
+ /**
+ * Extend the Pointer prototype with methods for each event handler and more
+ */
+ extend(Pointer.prototype, /** @lends Pointer.prototype */ {
+ onContainerPointerDown: function (e) {
+ translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) {
+ touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY, target: e.currentTarget };
+ });
+ },
+ onContainerPointerMove: function (e) {
+ translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) {
+ touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY };
+ if (!touches[e.pointerId].target) {
+ touches[e.pointerId].target = e.currentTarget;
+ }
+ });
+ },
+ onDocumentPointerUp: function (e) {
+ translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function (e) {
+ delete touches[e.pointerId];
+ });
+ },
+
+ /**
+ * Add or remove the MS Pointer specific events
+ */
+ batchMSEvents: function (fn) {
+ fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
+ fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
+ fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
+ }
+ });
+
+ // Disable default IE actions for pinch and such on chart element
+ wrap(Pointer.prototype, 'init', function (proceed, chart, options) {
+ proceed.call(this, chart, options);
+ if (this.hasZoom) { // #4014
+ css(chart.container, {
+ '-ms-touch-action': 'none',
+ 'touch-action': 'none'
+ });
+ }
+ });
+
+ // Add IE specific touch events to chart
+ wrap(Pointer.prototype, 'setDOMEvents', function (proceed) {
+ proceed.apply(this);
+ if (this.hasZoom || this.followTouchMove) {
+ this.batchMSEvents(addEvent);
+ }
+ });
+ // Destroy MS events also
+ wrap(Pointer.prototype, 'destroy', function (proceed) {
+ this.batchMSEvents(removeEvent);
+ proceed.call(this);
+ });
+}
+
+}(Highcharts));
+(function (Highcharts) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var H = Highcharts,
+
+ addEvent = H.addEvent,
+ css = H.css,
+ discardElement = H.discardElement,
+ defined = H.defined,
+ each = H.each,
+ isFirefox = H.isFirefox,
+ marginNames = H.marginNames,
+ merge = H.merge,
+ pick = H.pick,
+ setAnimation = H.setAnimation,
+ stableSort = H.stableSort,
+ win = H.win,
+ wrap = H.wrap;
+
+/**
+ * The overview of the chart's series. The legend object is instanciated
+ * internally in the chart constructor, and available from `chart.legend`. Each
+ * chart has only one legend.
+ *
+ * @class
+ */
+Highcharts.Legend = function (chart, options) {
+ this.init(chart, options);
+};
+
+Highcharts.Legend.prototype = {
+
+ /**
+ * Initialize the legend.
+ *
+ * @private
+ */
+ init: function (chart, options) {
+
+ this.chart = chart;
+
+ this.setOptions(options);
+
+ if (options.enabled) {
+
+ // Render it
+ this.render();
+
+ // move checkboxes
+ addEvent(this.chart, 'endResize', function () {
+ this.legend.positionCheckboxes();
+ });
+ }
+ },
+
+ setOptions: function (options) {
+
+ var padding = pick(options.padding, 8);
+
+ this.options = options;
+
+
+ this.itemStyle = options.itemStyle;
+ this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle);
+
+ this.itemMarginTop = options.itemMarginTop || 0;
+ this.padding = padding;
+ this.initialItemY = padding - 5; // 5 is pixels above the text
+ this.maxItemWidth = 0;
+ this.itemHeight = 0;
+ this.symbolWidth = pick(options.symbolWidth, 16);
+ this.pages = [];
+
+ },
+
+ /**
+ * Update the legend with new options. Equivalent to running `chart.update`
+ * with a legend configuration option.
+ * @param {LegendOptions} options
+ * Legend options.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart.
+ *
+ * @sample highcharts/legend/legend-update/
+ * Legend update
+ */
+ update: function (options, redraw) {
+ var chart = this.chart;
+
+ this.setOptions(merge(true, this.options, options));
+ this.destroy();
+ chart.isDirtyLegend = chart.isDirtyBox = true;
+ if (pick(redraw, true)) {
+ chart.redraw();
+ }
+ },
+
+ /**
+ * Set the colors for the legend item.
+ *
+ * @private
+ * @param {Series|Point} item
+ * A Series or Point instance
+ * @param {Boolean} visible
+ * Dimmed or colored
+ */
+ colorizeItem: function (item, visible) {
+ item.legendGroup[visible ? 'removeClass' : 'addClass'](
+ 'highcharts-legend-item-hidden'
+ );
+
+
+ var legend = this,
+ options = legend.options,
+ legendItem = item.legendItem,
+ legendLine = item.legendLine,
+ legendSymbol = item.legendSymbol,
+ hiddenColor = legend.itemHiddenStyle.color,
+ textColor = visible ? options.itemStyle.color : hiddenColor,
+ symbolColor = visible ? (item.color || hiddenColor) : hiddenColor,
+ markerOptions = item.options && item.options.marker,
+ symbolAttr = { fill: symbolColor };
+
+ if (legendItem) {
+ legendItem.css({
+ fill: textColor,
+ color: textColor // #1553, oldIE
+ });
+ }
+ if (legendLine) {
+ legendLine.attr({ stroke: symbolColor });
+ }
+
+ if (legendSymbol) {
+
+ // Apply marker options
+ if (markerOptions && legendSymbol.isMarker) { // #585
+ symbolAttr = item.pointAttribs();
+ if (!visible) {
+ symbolAttr.stroke = symbolAttr.fill = hiddenColor; // #6769
+ }
+ }
+
+ legendSymbol.attr(symbolAttr);
+ }
+
+ },
+
+ /**
+ * Position the legend item.
+ *
+ * @private
+ * @param {Series|Point} item
+ * The item to position
+ */
+ positionItem: function (item) {
+ var legend = this,
+ options = legend.options,
+ symbolPadding = options.symbolPadding,
+ ltr = !options.rtl,
+ legendItemPos = item._legendItemPos,
+ itemX = legendItemPos[0],
+ itemY = legendItemPos[1],
+ checkbox = item.checkbox,
+ legendGroup = item.legendGroup;
+
+ if (legendGroup && legendGroup.element) {
+ legendGroup.translate(
+ ltr ?
+ itemX :
+ legend.legendWidth - itemX - 2 * symbolPadding - 4,
+ itemY
+ );
+ }
+
+ if (checkbox) {
+ checkbox.x = itemX;
+ checkbox.y = itemY;
+ }
+ },
+
+ /**
+ * Destroy a single legend item, used internally on removing series items.
+ *
+ * @param {Series|Point} item
+ * The item to remove
+ */
+ destroyItem: function (item) {
+ var checkbox = item.checkbox;
+
+ // destroy SVG elements
+ each(
+ ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'],
+ function (key) {
+ if (item[key]) {
+ item[key] = item[key].destroy();
+ }
+ }
+ );
+
+ if (checkbox) {
+ discardElement(item.checkbox);
+ }
+ },
+
+ /**
+ * Destroy the legend. Used internally. To reflow objects, `chart.redraw`
+ * must be called after destruction.
+ */
+ destroy: function () {
+ function destroyItems(key) {
+ if (this[key]) {
+ this[key] = this[key].destroy();
+ }
+ }
+
+ // Destroy items
+ each(this.getAllItems(), function (item) {
+ each(['legendItem', 'legendGroup'], destroyItems, item);
+ });
+
+ // Destroy legend elements
+ each([
+ 'clipRect',
+ 'up',
+ 'down',
+ 'pager',
+ 'nav',
+ 'box',
+ 'title',
+ 'group'
+ ], destroyItems, this);
+ this.display = null; // Reset in .render on update.
+ },
+
+ /**
+ * Position the checkboxes after the width is determined.
+ *
+ * @private
+ */
+ positionCheckboxes: function () {
+ var alignAttr = this.group && this.group.alignAttr,
+ translateY,
+ clipHeight = this.clipHeight || this.legendHeight,
+ titleHeight = this.titleHeight;
+
+ if (alignAttr) {
+ translateY = alignAttr.translateY;
+ each(this.allItems, function (item) {
+ var checkbox = item.checkbox,
+ top;
+
+ if (checkbox) {
+ top = translateY + titleHeight + checkbox.y +
+ (this.scrollOffset || 0) + 3;
+ css(checkbox, {
+ left: (alignAttr.translateX + item.checkboxOffset +
+ checkbox.x - 20) + 'px',
+ top: top + 'px',
+ display: top > translateY - 6 && top < translateY +
+ clipHeight - 6 ? '' : 'none'
+ });
+ }
+ }, this);
+ }
+ },
+
+ /**
+ * Render the legend title on top of the legend.
+ *
+ * @private
+ */
+ renderTitle: function () {
+ var options = this.options,
+ padding = this.padding,
+ titleOptions = options.title,
+ titleHeight = 0,
+ bBox;
+
+ if (titleOptions.text) {
+ if (!this.title) {
+ this.title = this.chart.renderer.label(
+ titleOptions.text,
+ padding - 3,
+ padding - 4,
+ null,
+ null,
+ null,
+ options.useHTML,
+ null,
+ 'legend-title'
+ )
+ .attr({ zIndex: 1 })
+
+ .css(titleOptions.style)
+
+ .add(this.group);
+ }
+ bBox = this.title.getBBox();
+ titleHeight = bBox.height;
+ this.offsetWidth = bBox.width; // #1717
+ this.contentGroup.attr({ translateY: titleHeight });
+ }
+ this.titleHeight = titleHeight;
+ },
+
+ /**
+ * Set the legend item text.
+ *
+ * @param {Series|Point} item
+ * The item for which to update the text in the legend.
+ */
+ setText: function (item) {
+ var options = this.options;
+ item.legendItem.attr({
+ text: options.labelFormat ?
+ H.format(options.labelFormat, item, this.chart.time) :
+ options.labelFormatter.call(item)
+ });
+ },
+
+ /**
+ * Render a single specific legend item. Called internally from the `render`
+ * function.
+ *
+ * @private
+ * @param {Series|Point} item
+ * The item to render.
+ */
+ renderItem: function (item) {
+ var legend = this,
+ chart = legend.chart,
+ renderer = chart.renderer,
+ options = legend.options,
+ horizontal = options.layout === 'horizontal',
+ symbolWidth = legend.symbolWidth,
+ symbolPadding = options.symbolPadding,
+
+ itemStyle = legend.itemStyle,
+ itemHiddenStyle = legend.itemHiddenStyle,
+
+ padding = legend.padding,
+ itemDistance = horizontal ? pick(options.itemDistance, 20) : 0,
+ ltr = !options.rtl,
+ itemHeight,
+ widthOption = options.width,
+ itemMarginBottom = options.itemMarginBottom || 0,
+ itemMarginTop = legend.itemMarginTop,
+ bBox,
+ itemWidth,
+ li = item.legendItem,
+ isSeries = !item.series,
+ series = !isSeries && item.series.drawLegendSymbol ?
+ item.series :
+ item,
+ seriesOptions = series.options,
+ showCheckbox = legend.createCheckboxForItem &&
+ seriesOptions &&
+ seriesOptions.showCheckbox,
+ // full width minus text width
+ itemExtraWidth = symbolWidth + symbolPadding + itemDistance +
+ (showCheckbox ? 20 : 0),
+ useHTML = options.useHTML,
+ fontSize = 12,
+ itemClassName = item.options.className;
+
+ if (!li) { // generate it once, later move it
+
+ // Generate the group box, a group to hold the symbol and text. Text
+ // is to be appended in Legend class.
+ item.legendGroup = renderer.g('legend-item')
+ .addClass(
+ 'highcharts-' + series.type + '-series ' +
+ 'highcharts-color-' + item.colorIndex +
+ (itemClassName ? ' ' + itemClassName : '') +
+ (isSeries ? ' highcharts-series-' + item.index : '')
+ )
+ .attr({ zIndex: 1 })
+ .add(legend.scrollGroup);
+
+ // Generate the list item text and add it to the group
+ item.legendItem = li = renderer.text(
+ '',
+ ltr ? symbolWidth + symbolPadding : -symbolPadding,
+ legend.baseline || 0,
+ useHTML
+ )
+
+ // merge to prevent modifying original (#1021)
+ .css(merge(item.visible ? itemStyle : itemHiddenStyle))
+
+ .attr({
+ align: ltr ? 'left' : 'right',
+ zIndex: 2
+ })
+ .add(item.legendGroup);
+
+ // Get the baseline for the first item - the font size is equal for
+ // all
+ if (!legend.baseline) {
+
+ fontSize = itemStyle.fontSize;
+
+ legend.fontMetrics = renderer.fontMetrics(
+ fontSize,
+ li
+ );
+ legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop;
+ li.attr('y', legend.baseline);
+ }
+
+ // Draw the legend symbol inside the group box
+ legend.symbolHeight = options.symbolHeight || legend.fontMetrics.f;
+ series.drawLegendSymbol(legend, item);
+
+ if (legend.setItemEvents) {
+ legend.setItemEvents(item, li, useHTML);
+ }
+
+ // add the HTML checkbox on top
+ if (showCheckbox) {
+ legend.createCheckboxForItem(item);
+ }
+ }
+
+ // Colorize the items
+ legend.colorizeItem(item, item.visible);
+
+ // Take care of max width and text overflow (#6659)
+
+ if (!itemStyle.width) {
+
+ li.css({
+ width: (
+ options.itemWidth ||
+ options.width ||
+ chart.spacingBox.width
+ ) - itemExtraWidth
+ });
+
+ }
+
+
+ // Always update the text
+ legend.setText(item);
+
+ // calculate the positions for the next line
+ bBox = li.getBBox();
+
+ itemWidth = item.checkboxOffset =
+ options.itemWidth ||
+ item.legendItemWidth ||
+ bBox.width + itemExtraWidth;
+ legend.itemHeight = itemHeight = Math.round(
+ item.legendItemHeight || bBox.height || legend.symbolHeight
+ );
+
+ // If the item exceeds the width, start a new line
+ if (
+ horizontal &&
+ legend.itemX - padding + itemWidth > (
+ widthOption || (
+ chart.spacingBox.width - 2 * padding - options.x
+ )
+ )
+ ) {
+ legend.itemX = padding;
+ legend.itemY += itemMarginTop + legend.lastLineHeight +
+ itemMarginBottom;
+ legend.lastLineHeight = 0; // reset for next line (#915, #3976)
+ }
+
+ // If the item exceeds the height, start a new column
+ /*
+ if (!horizontal && legend.itemY + options.y +
+ itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
+ legend.itemY = legend.initialItemY;
+ legend.itemX += legend.maxItemWidth;
+ legend.maxItemWidth = 0;
+ }
+ */
+
+ // Set the edge positions
+ legend.maxItemWidth = Math.max(legend.maxItemWidth, itemWidth);
+ legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
+ legend.lastLineHeight = Math.max( // #915
+ itemHeight,
+ legend.lastLineHeight
+ );
+
+ // cache the position of the newly generated or reordered items
+ item._legendItemPos = [legend.itemX, legend.itemY];
+
+ // advance
+ if (horizontal) {
+ legend.itemX += itemWidth;
+
+ } else {
+ legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
+ legend.lastLineHeight = itemHeight;
+ }
+
+ // the width of the widest item
+ legend.offsetWidth = widthOption || Math.max(
+ (
+ horizontal ? legend.itemX - padding - (item.checkbox ?
+ // decrease by itemDistance only when no checkbox #4853
+ 0 :
+ itemDistance
+ ) : itemWidth
+ ) + padding,
+ legend.offsetWidth
+ );
+ },
+
+ /**
+ * Get all items, which is one item per series for most series and one
+ * item per point for pie series and its derivatives.
+ *
+ * @return {Array.}
+ * The current items in the legend.
+ */
+ getAllItems: function () {
+ var allItems = [];
+ each(this.chart.series, function (series) {
+ var seriesOptions = series && series.options;
+
+ // Handle showInLegend. If the series is linked to another series,
+ // defaults to false.
+ if (series && pick(
+ seriesOptions.showInLegend,
+ !defined(seriesOptions.linkedTo) ? undefined : false, true
+ )) {
+
+ // Use points or series for the legend item depending on
+ // legendType
+ allItems = allItems.concat(
+ series.legendItems ||
+ (
+ seriesOptions.legendType === 'point' ?
+ series.data :
+ series
+ )
+ );
+ }
+ });
+ return allItems;
+ },
+
+ /**
+ * Get a short, three letter string reflecting the alignment and layout.
+ *
+ * @private
+ * @return {String} The alignment, empty string if floating
+ */
+ getAlignment: function () {
+ var options = this.options;
+
+ // Use the first letter of each alignment option in order to detect
+ // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
+ return options.floating ? '' : (
+ options.align.charAt(0) +
+ options.verticalAlign.charAt(0) +
+ options.layout.charAt(0)
+ );
+ },
+
+ /**
+ * Adjust the chart margins by reserving space for the legend on only one
+ * side of the chart. If the position is set to a corner, top or bottom is
+ * reserved for horizontal legends and left or right for vertical ones.
+ *
+ * @private
+ */
+ adjustMargins: function (margin, spacing) {
+ var chart = this.chart,
+ options = this.options,
+ alignment = this.getAlignment();
+
+ if (alignment) {
+
+ each([
+ /(lth|ct|rth)/,
+ /(rtv|rm|rbv)/,
+ /(rbh|cb|lbh)/,
+ /(lbv|lm|ltv)/
+ ], function (alignments, side) {
+ if (alignments.test(alignment) && !defined(margin[side])) {
+
+ // Now we have detected on which side of the chart we should
+ // reserve space for the legend
+ chart[marginNames[side]] = Math.max(
+ chart[marginNames[side]],
+ (
+ chart.legend[
+ (side + 1) % 2 ? 'legendHeight' : 'legendWidth'
+ ] +
+ [1, -1, -1, 1][side] * options[
+ (side % 2) ? 'x' : 'y'
+ ] +
+ pick(options.margin, 12) +
+ spacing[side] +
+ (
+ side === 0 ?
+ chart.titleOffset +
+ chart.options.title.margin :
+ 0
+ ) // #7428
+ )
+ );
+ }
+ });
+ }
+ },
+
+ /**
+ * Render the legend. This method can be called both before and after
+ * `chart.render`. If called after, it will only rearrange items instead
+ * of creating new ones. Called internally on initial render and after
+ * redraws.
+ */
+ render: function () {
+ var legend = this,
+ chart = legend.chart,
+ renderer = chart.renderer,
+ legendGroup = legend.group,
+ allItems,
+ display,
+ legendWidth,
+ legendHeight,
+ box = legend.box,
+ options = legend.options,
+ padding = legend.padding,
+ alignTo;
+
+ legend.itemX = padding;
+ legend.itemY = legend.initialItemY;
+ legend.offsetWidth = 0;
+ legend.lastItemY = 0;
+
+ if (!legendGroup) {
+ legend.group = legendGroup = renderer.g('legend')
+ .attr({ zIndex: 7 })
+ .add();
+ legend.contentGroup = renderer.g()
+ .attr({ zIndex: 1 }) // above background
+ .add(legendGroup);
+ legend.scrollGroup = renderer.g()
+ .add(legend.contentGroup);
+ }
+
+ legend.renderTitle();
+
+ // add each series or point
+ allItems = legend.getAllItems();
+
+ // sort by legendIndex
+ stableSort(allItems, function (a, b) {
+ return ((a.options && a.options.legendIndex) || 0) -
+ ((b.options && b.options.legendIndex) || 0);
+ });
+
+ // reversed legend
+ if (options.reversed) {
+ allItems.reverse();
+ }
+
+ legend.allItems = allItems;
+ legend.display = display = !!allItems.length;
+
+ // render the items
+ legend.lastLineHeight = 0;
+ each(allItems, function (item) {
+ legend.renderItem(item);
+ });
+
+ // Get the box
+ legendWidth = (options.width || legend.offsetWidth) + padding;
+ legendHeight = legend.lastItemY + legend.lastLineHeight +
+ legend.titleHeight;
+ legendHeight = legend.handleOverflow(legendHeight);
+ legendHeight += padding;
+
+ // Draw the border and/or background
+ if (!box) {
+ legend.box = box = renderer.rect()
+ .addClass('highcharts-legend-box')
+ .attr({
+ r: options.borderRadius
+ })
+ .add(legendGroup);
+ box.isNew = true;
+ }
+
+
+ // Presentational
+ box
+ .attr({
+ stroke: options.borderColor,
+ 'stroke-width': options.borderWidth || 0,
+ fill: options.backgroundColor || 'none'
+ })
+ .shadow(options.shadow);
+
+
+ if (legendWidth > 0 && legendHeight > 0) {
+ box[box.isNew ? 'attr' : 'animate'](
+ box.crisp.call({}, { // #7260
+ x: 0,
+ y: 0,
+ width: legendWidth,
+ height: legendHeight
+ }, box.strokeWidth())
+ );
+ box.isNew = false;
+ }
+
+ // hide the border if no items
+ box[display ? 'show' : 'hide']();
+
+
+
+ legend.legendWidth = legendWidth;
+ legend.legendHeight = legendHeight;
+
+ // Now that the legend width and height are established, put the items
+ // in the final position
+ each(allItems, function (item) {
+ legend.positionItem(item);
+ });
+
+ if (display) {
+ // If aligning to the top and the layout is horizontal, adjust for
+ // the title (#7428)
+ alignTo = chart.spacingBox;
+ if (/(lth|ct|rth)/.test(legend.getAlignment())) {
+ alignTo = merge(alignTo, {
+ y: alignTo.y + chart.titleOffset +
+ chart.options.title.margin
+ });
+ }
+
+ legendGroup.align(merge(options, {
+ width: legendWidth,
+ height: legendHeight
+ }), true, alignTo);
+ }
+
+ if (!chart.isResizing) {
+ this.positionCheckboxes();
+ }
+ },
+
+ /**
+ * Set up the overflow handling by adding navigation with up and down arrows
+ * below the legend.
+ *
+ * @private
+ */
+ handleOverflow: function (legendHeight) {
+ var legend = this,
+ chart = this.chart,
+ renderer = chart.renderer,
+ options = this.options,
+ optionsY = options.y,
+ alignTop = options.verticalAlign === 'top',
+ padding = this.padding,
+ spaceHeight = chart.spacingBox.height +
+ (alignTop ? -optionsY : optionsY) - padding,
+ maxHeight = options.maxHeight,
+ clipHeight,
+ clipRect = this.clipRect,
+ navOptions = options.navigation,
+ animation = pick(navOptions.animation, true),
+ arrowSize = navOptions.arrowSize || 12,
+ nav = this.nav,
+ pages = this.pages,
+ lastY,
+ allItems = this.allItems,
+ clipToHeight = function (height) {
+ if (typeof height === 'number') {
+ clipRect.attr({
+ height: height
+ });
+ } else if (clipRect) { // Reset (#5912)
+ legend.clipRect = clipRect.destroy();
+ legend.contentGroup.clip();
+ }
+
+ // useHTML
+ if (legend.contentGroup.div) {
+ legend.contentGroup.div.style.clip = height ?
+ 'rect(' + padding + 'px,9999px,' +
+ (padding + height) + 'px,0)' :
+ 'auto';
+ }
+ };
+
+
+ // Adjust the height
+ if (
+ options.layout === 'horizontal' &&
+ options.verticalAlign !== 'middle' &&
+ !options.floating
+ ) {
+ spaceHeight /= 2;
+ }
+ if (maxHeight) {
+ spaceHeight = Math.min(spaceHeight, maxHeight);
+ }
+
+ // Reset the legend height and adjust the clipping rectangle
+ pages.length = 0;
+ if (legendHeight > spaceHeight && navOptions.enabled !== false) {
+
+ this.clipHeight = clipHeight =
+ Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
+ this.currentPage = pick(this.currentPage, 1);
+ this.fullHeight = legendHeight;
+
+ // Fill pages with Y positions so that the top of each a legend item
+ // defines the scroll top for each page (#2098)
+ each(allItems, function (item, i) {
+ var y = item._legendItemPos[1],
+ h = Math.round(item.legendItem.getBBox().height),
+ len = pages.length;
+
+ if (!len || (y - pages[len - 1] > clipHeight &&
+ (lastY || y) !== pages[len - 1])) {
+ pages.push(lastY || y);
+ len++;
+ }
+
+ // Keep track of which page each item is on
+ item.pageIx = len - 1;
+ if (lastY) {
+ allItems[i - 1].pageIx = len - 1;
+ }
+
+ if (i === allItems.length - 1 &&
+ y + h - pages[len - 1] > clipHeight) {
+ pages.push(y);
+ item.pageIx = len;
+ }
+ if (y !== lastY) {
+ lastY = y;
+ }
+ });
+
+ // Only apply clipping if needed. Clipping causes blurred legend in
+ // PDF export (#1787)
+ if (!clipRect) {
+ clipRect = legend.clipRect =
+ renderer.clipRect(0, padding, 9999, 0);
+ legend.contentGroup.clip(clipRect);
+ }
+
+ clipToHeight(clipHeight);
+
+ // Add navigation elements
+ if (!nav) {
+ this.nav = nav = renderer.g()
+ .attr({ zIndex: 1 })
+ .add(this.group);
+
+ this.up = renderer
+ .symbol(
+ 'triangle',
+ 0,
+ 0,
+ arrowSize,
+ arrowSize
+ )
+ .on('click', function () {
+ legend.scroll(-1, animation);
+ })
+ .add(nav);
+
+ this.pager = renderer.text('', 15, 10)
+ .addClass('highcharts-legend-navigation')
+
+ .css(navOptions.style)
+
+ .add(nav);
+
+ this.down = renderer
+ .symbol(
+ 'triangle-down',
+ 0,
+ 0,
+ arrowSize,
+ arrowSize
+ )
+ .on('click', function () {
+ legend.scroll(1, animation);
+ })
+ .add(nav);
+ }
+
+ // Set initial position
+ legend.scroll(0);
+
+ legendHeight = spaceHeight;
+
+ // Reset
+ } else if (nav) {
+ clipToHeight();
+ this.nav = nav.destroy(); // #6322
+ this.scrollGroup.attr({
+ translateY: 1
+ });
+ this.clipHeight = 0; // #1379
+ }
+
+ return legendHeight;
+ },
+
+ /**
+ * Scroll the legend by a number of pages.
+ * @param {Number} scrollBy
+ * The number of pages to scroll.
+ * @param {AnimationOptions} animation
+ * Whether and how to apply animation.
+ */
+ scroll: function (scrollBy, animation) {
+ var pages = this.pages,
+ pageCount = pages.length,
+ currentPage = this.currentPage + scrollBy,
+ clipHeight = this.clipHeight,
+ navOptions = this.options.navigation,
+ pager = this.pager,
+ padding = this.padding;
+
+ // When resizing while looking at the last page
+ if (currentPage > pageCount) {
+ currentPage = pageCount;
+ }
+
+ if (currentPage > 0) {
+
+ if (animation !== undefined) {
+ setAnimation(animation, this.chart);
+ }
+
+ this.nav.attr({
+ translateX: padding,
+ translateY: clipHeight + this.padding + 7 + this.titleHeight,
+ visibility: 'visible'
+ });
+ this.up.attr({
+ 'class': currentPage === 1 ?
+ 'highcharts-legend-nav-inactive' :
+ 'highcharts-legend-nav-active'
+ });
+ pager.attr({
+ text: currentPage + '/' + pageCount
+ });
+ this.down.attr({
+ 'x': 18 + this.pager.getBBox().width, // adjust to text width
+ 'class': currentPage === pageCount ?
+ 'highcharts-legend-nav-inactive' :
+ 'highcharts-legend-nav-active'
+ });
+
+
+ this.up
+ .attr({
+ fill: currentPage === 1 ?
+ navOptions.inactiveColor :
+ navOptions.activeColor
+ })
+ .css({
+ cursor: currentPage === 1 ? 'default' : 'pointer'
+ });
+ this.down
+ .attr({
+ fill: currentPage === pageCount ?
+ navOptions.inactiveColor :
+ navOptions.activeColor
+ })
+ .css({
+ cursor: currentPage === pageCount ? 'default' : 'pointer'
+ });
+
+
+ this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;
+
+ this.scrollGroup.animate({
+ translateY: this.scrollOffset
+ });
+
+ this.currentPage = currentPage;
+ this.positionCheckboxes();
+ }
+
+ }
+
+};
+
+/*
+ * LegendSymbolMixin
+ */
+
+H.LegendSymbolMixin = {
+
+ /**
+ * Get the series' symbol in the legend
+ *
+ * @param {Object} legend The legend object
+ * @param {Object} item The series (this) or point
+ */
+ drawRectangle: function (legend, item) {
+ var options = legend.options,
+ symbolHeight = legend.symbolHeight,
+ square = options.squareSymbol,
+ symbolWidth = square ? symbolHeight : legend.symbolWidth;
+
+ item.legendSymbol = this.chart.renderer.rect(
+ square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
+ legend.baseline - symbolHeight + 1, // #3988
+ symbolWidth,
+ symbolHeight,
+ pick(legend.options.symbolRadius, symbolHeight / 2)
+ )
+ .addClass('highcharts-point')
+ .attr({
+ zIndex: 3
+ }).add(item.legendGroup);
+
+ },
+
+ /**
+ * Get the series' symbol in the legend. This method should be overridable
+ * to create custom symbols through
+ * Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
+ *
+ * @param {Object} legend The legend object
+ */
+ drawLineMarker: function (legend) {
+
+ var options = this.options,
+ markerOptions = options.marker,
+ radius,
+ legendSymbol,
+ symbolWidth = legend.symbolWidth,
+ symbolHeight = legend.symbolHeight,
+ generalRadius = symbolHeight / 2,
+ renderer = this.chart.renderer,
+ legendItemGroup = this.legendGroup,
+ verticalCenter = legend.baseline -
+ Math.round(legend.fontMetrics.b * 0.3),
+ attr = {};
+
+ // Draw the line
+
+ attr = {
+ 'stroke-width': options.lineWidth || 0
+ };
+ if (options.dashStyle) {
+ attr.dashstyle = options.dashStyle;
+ }
+
+
+ this.legendLine = renderer.path([
+ 'M',
+ 0,
+ verticalCenter,
+ 'L',
+ symbolWidth,
+ verticalCenter
+ ])
+ .addClass('highcharts-graph')
+ .attr(attr)
+ .add(legendItemGroup);
+
+ // Draw the marker
+ if (markerOptions && markerOptions.enabled !== false) {
+
+ // Do not allow the marker to be larger than the symbolHeight
+ radius = Math.min(
+ pick(markerOptions.radius, generalRadius),
+ generalRadius
+ );
+
+ // Restrict symbol markers size
+ if (this.symbol.indexOf('url') === 0) {
+ markerOptions = merge(markerOptions, {
+ width: symbolHeight,
+ height: symbolHeight
+ });
+ radius = 0;
+ }
+
+ this.legendSymbol = legendSymbol = renderer.symbol(
+ this.symbol,
+ (symbolWidth / 2) - radius,
+ verticalCenter - radius,
+ 2 * radius,
+ 2 * radius,
+ markerOptions
+ )
+ .addClass('highcharts-point')
+ .add(legendItemGroup);
+ legendSymbol.isMarker = true;
+ }
+ }
+};
+
+// Workaround for #2030, horizontal legend items not displaying in IE11 Preview,
+// and for #2580, a similar drawing flaw in Firefox 26.
+// Explore if there's a general cause for this. The problem may be related
+// to nested group elements, as the legend item texts are within 4 group
+// elements.
+if (/Trident\/7\.0/.test(win.navigator.userAgent) || isFirefox) {
+ wrap(Highcharts.Legend.prototype, 'positionItem', function (proceed, item) {
+ var legend = this,
+ // If chart destroyed in sync, this is undefined (#2030)
+ runPositionItem = function () {
+ if (item._legendItemPos) {
+ proceed.call(legend, item);
+ }
+ };
+
+ // Do it now, for export and to get checkbox placement
+ runPositionItem();
+
+ // Do it after to work around the core issue
+ setTimeout(runPositionItem);
+ });
+}
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ animate = H.animate,
+ animObject = H.animObject,
+ attr = H.attr,
+ doc = H.doc,
+ Axis = H.Axis, // @todo add as requirement
+ createElement = H.createElement,
+ defaultOptions = H.defaultOptions,
+ discardElement = H.discardElement,
+ charts = H.charts,
+ css = H.css,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ find = H.find,
+ fireEvent = H.fireEvent,
+ grep = H.grep,
+ isNumber = H.isNumber,
+ isObject = H.isObject,
+ isString = H.isString,
+ Legend = H.Legend, // @todo add as requirement
+ marginNames = H.marginNames,
+ merge = H.merge,
+ objectEach = H.objectEach,
+ Pointer = H.Pointer, // @todo add as requirement
+ pick = H.pick,
+ pInt = H.pInt,
+ removeEvent = H.removeEvent,
+ seriesTypes = H.seriesTypes,
+ splat = H.splat,
+ svg = H.svg,
+ syncTimeout = H.syncTimeout,
+ win = H.win;
+/**
+ * The Chart class. The recommended constructor is {@link Highcharts#chart}.
+ * @class Highcharts.Chart
+ * @param {String|HTMLDOMElement} renderTo
+ * The DOM element to render to, or its id.
+ * @param {Options} options
+ * The chart options structure.
+ * @param {Function} [callback]
+ * Function to run when the chart has loaded and and all external images
+ * are loaded. Defining a {@link
+ * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
+ * handler is equivalent.
+ *
+ * @example
+ * var chart = Highcharts.chart('container', {
+ * title: {
+ * text: 'My chart'
+ * },
+ * series: [{
+ * data: [1, 3, 2, 4]
+ * }]
+ * })
+ */
+var Chart = H.Chart = function () {
+ this.getArgs.apply(this, arguments);
+};
+
+/**
+ * Factory function for basic charts.
+ *
+ * @function #chart
+ * @memberOf Highcharts
+ * @param {String|HTMLDOMElement} renderTo - The DOM element to render to, or
+ * its id.
+ * @param {Options} options - The chart options structure.
+ * @param {Function} [callback] - Function to run when the chart has loaded and
+ * and all external images are loaded. Defining a {@link
+ * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
+ * handler is equivalent.
+ * @return {Highcharts.Chart} - Returns the Chart object.
+ *
+ * @example
+ * // Render a chart in to div#container
+ * var chart = Highcharts.chart('container', {
+ * title: {
+ * text: 'My chart'
+ * },
+ * series: [{
+ * data: [1, 3, 2, 4]
+ * }]
+ * });
+ */
+H.chart = function (a, b, c) {
+ return new Chart(a, b, c);
+};
+
+extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
+
+ // Hook for adding callbacks in modules
+ callbacks: [],
+
+ /**
+ * Handle the arguments passed to the constructor.
+ *
+ * @private
+ * @returns {Array} Arguments without renderTo
+ */
+ getArgs: function () {
+ var args = [].slice.call(arguments);
+
+ // Remove the optional first argument, renderTo, and
+ // set it on this.
+ if (isString(args[0]) || args[0].nodeName) {
+ this.renderTo = args.shift();
+ }
+ this.init(args[0], args[1]);
+ },
+
+ /**
+ * Overridable function that initializes the chart. The constructor's
+ * arguments are passed on directly.
+ */
+ init: function (userOptions, callback) {
+
+ // Handle regular options
+ var options,
+ type,
+ seriesOptions = userOptions.series, // skip merging data points to increase performance
+ userPlotOptions = userOptions.plotOptions || {};
+
+ userOptions.series = null;
+ options = merge(defaultOptions, userOptions); // do the merge
+
+ // Override (by copy of user options) or clear tooltip options
+ // in chart.options.plotOptions (#6218)
+ for (type in options.plotOptions) {
+ options.plotOptions[type].tooltip = (
+ userPlotOptions[type] &&
+ merge(userPlotOptions[type].tooltip) // override by copy
+ ) || undefined; // or clear
+ }
+ // User options have higher priority than default options (#6218).
+ // In case of exporting: path is changed
+ options.tooltip.userOptions = (userOptions.chart &&
+ userOptions.chart.forExport && userOptions.tooltip.userOptions) ||
+ userOptions.tooltip;
+
+ options.series = userOptions.series = seriesOptions; // set back the series data
+ this.userOptions = userOptions;
+
+ var optionsChart = options.chart;
+
+ var chartEvents = optionsChart.events;
+
+ this.margin = [];
+ this.spacing = [];
+
+ this.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom
+
+ // An array of functions that returns labels that should be considered
+ // for anti-collision
+ this.labelCollectors = [];
+
+ this.callback = callback;
+ this.isResizing = 0;
+
+ /**
+ * The options structure for the chart. It contains members for the sub
+ * elements like series, legend, tooltip etc.
+ *
+ * @memberof Highcharts.Chart
+ * @name options
+ * @type {Options}
+ */
+ this.options = options;
+ /**
+ * All the axes in the chart.
+ *
+ * @memberof Highcharts.Chart
+ * @name axes
+ * @see Highcharts.Chart.xAxis
+ * @see Highcharts.Chart.yAxis
+ * @type {Array.}
+ */
+ this.axes = [];
+
+ /**
+ * All the current series in the chart.
+ *
+ * @memberof Highcharts.Chart
+ * @name series
+ * @type {Array.}
+ */
+ this.series = [];
+
+ /**
+ * The chart title. The title has an `update` method that allows
+ * modifying the options directly or indirectly via `chart.update`.
+ *
+ * @memberof Highcharts.Chart
+ * @name title
+ * @type Object
+ *
+ * @sample highcharts/members/title-update/
+ * Updating titles
+ */
+
+ /**
+ * The chart subtitle. The subtitle has an `update` method that allows
+ * modifying the options directly or indirectly via `chart.update`.
+ *
+ * @memberof Highcharts.Chart
+ * @name subtitle
+ * @type Object
+ */
+
+ /**
+ * The `Time` object associated with the chart. Since v6.0.5, time
+ * settings can be applied individually for each chart. If no individual
+ * settings apply, the `Time` object is shared by all instances.
+ *
+ * @memberof Highcharts.Chart
+ * @name time
+ * @type Highcharts.Time
+ */
+ this.time = userOptions.time ? new H.Time(this) : H.time;
+
+
+ this.hasCartesianSeries = optionsChart.showAxes;
+
+ var chart = this;
+
+ // Add the chart to the global lookup
+ chart.index = charts.length;
+
+ charts.push(chart);
+ H.chartCount++;
+
+ // Chart event handlers
+ if (chartEvents) {
+ objectEach(chartEvents, function (event, eventType) {
+ addEvent(chart, eventType, event);
+ });
+ }
+
+ /**
+ * A collection of the X axes in the chart.
+ * @type {Array.}
+ * @name xAxis
+ * @memberOf Highcharts.Chart
+ */
+ chart.xAxis = [];
+ /**
+ * A collection of the Y axes in the chart.
+ * @type {Array.}
+ * @name yAxis
+ * @memberOf Highcharts.Chart
+ */
+ chart.yAxis = [];
+
+ chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
+
+ chart.firstRender();
+ },
+
+ /**
+ * Internal function to unitialize an individual series.
+ *
+ * @private
+ */
+ initSeries: function (options) {
+ var chart = this,
+ optionsChart = chart.options.chart,
+ type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
+ series,
+ Constr = seriesTypes[type];
+
+ // No such series type
+ if (!Constr) {
+ H.error(17, true);
+ }
+
+ series = new Constr();
+ series.init(this, options);
+ return series;
+ },
+
+ /**
+ * Order all series above a given index. When series are added and ordered
+ * by configuration, only the last series is handled (#248, #1123, #2456,
+ * #6112). This function is called on series initialization and destroy.
+ *
+ * @private
+ *
+ * @param {number} fromIndex
+ * If this is given, only the series above this index are handled.
+ */
+ orderSeries: function (fromIndex) {
+ var series = this.series,
+ i = fromIndex || 0;
+ for (; i < series.length; i++) {
+ if (series[i]) {
+ series[i].index = i;
+ series[i].name = series[i].name ||
+ 'Series ' + (series[i].index + 1);
+ }
+ }
+ },
+
+ /**
+ * Check whether a given point is within the plot area.
+ *
+ * @param {Number} plotX
+ * Pixel x relative to the plot area.
+ * @param {Number} plotY
+ * Pixel y relative to the plot area.
+ * @param {Boolean} inverted
+ * Whether the chart is inverted.
+ *
+ * @return {Boolean}
+ * Returns true if the given point is inside the plot area.
+ */
+ isInsidePlot: function (plotX, plotY, inverted) {
+ var x = inverted ? plotY : plotX,
+ y = inverted ? plotX : plotY;
+
+ return x >= 0 &&
+ x <= this.plotWidth &&
+ y >= 0 &&
+ y <= this.plotHeight;
+ },
+
+ /**
+ * Redraw the chart after changes have been done to the data, axis extremes
+ * chart size or chart elements. All methods for updating axes, series or
+ * points have a parameter for redrawing the chart. This is `true` by
+ * default. But in many cases you want to do more than one operation on the
+ * chart before redrawing, for example add a number of points. In those
+ * cases it is a waste of resources to redraw the chart for each new point
+ * added. So you add the points and call `chart.redraw()` after.
+ *
+ * @param {AnimationOptions} animation
+ * If or how to apply animation to the redraw.
+ */
+ redraw: function (animation) {
+ var chart = this,
+ axes = chart.axes,
+ series = chart.series,
+ pointer = chart.pointer,
+ legend = chart.legend,
+ redrawLegend = chart.isDirtyLegend,
+ hasStackedSeries,
+ hasDirtyStacks,
+ hasCartesianSeries = chart.hasCartesianSeries,
+ isDirtyBox = chart.isDirtyBox,
+ i,
+ serie,
+ renderer = chart.renderer,
+ isHiddenChart = renderer.isHidden(),
+ afterRedraw = [];
+
+ // Handle responsive rules, not only on resize (#6130)
+ if (chart.setResponsive) {
+ chart.setResponsive(false);
+ }
+
+ H.setAnimation(animation, chart);
+
+ if (isHiddenChart) {
+ chart.temporaryDisplay();
+ }
+
+ // Adjust title layout (reflow multiline text)
+ chart.layOutTitles();
+
+ // link stacked series
+ i = series.length;
+ while (i--) {
+ serie = series[i];
+
+ if (serie.options.stacking) {
+ hasStackedSeries = true;
+
+ if (serie.isDirty) {
+ hasDirtyStacks = true;
+ break;
+ }
+ }
+ }
+ if (hasDirtyStacks) { // mark others as dirty
+ i = series.length;
+ while (i--) {
+ serie = series[i];
+ if (serie.options.stacking) {
+ serie.isDirty = true;
+ }
+ }
+ }
+
+ // Handle updated data in the series
+ each(series, function (serie) {
+ if (serie.isDirty) {
+ if (serie.options.legendType === 'point') {
+ if (serie.updateTotals) {
+ serie.updateTotals();
+ }
+ redrawLegend = true;
+ }
+ }
+ if (serie.isDirtyData) {
+ fireEvent(serie, 'updatedData');
+ }
+ });
+
+ // handle added or removed series
+ if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
+ // draw legend graphics
+ legend.render();
+
+ chart.isDirtyLegend = false;
+ }
+
+ // reset stacks
+ if (hasStackedSeries) {
+ chart.getStacks();
+ }
+
+
+ if (hasCartesianSeries) {
+ // set axes scales
+ each(axes, function (axis) {
+ axis.updateNames();
+ axis.setScale();
+ });
+ }
+
+ chart.getMargins(); // #3098
+
+ if (hasCartesianSeries) {
+ // If one axis is dirty, all axes must be redrawn (#792, #2169)
+ each(axes, function (axis) {
+ if (axis.isDirty) {
+ isDirtyBox = true;
+ }
+ });
+
+ // redraw axes
+ each(axes, function (axis) {
+
+ // Fire 'afterSetExtremes' only if extremes are set
+ var key = axis.min + ',' + axis.max;
+ if (axis.extKey !== key) { // #821, #4452
+ axis.extKey = key;
+ afterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119)
+ fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751
+ delete axis.eventArgs;
+ });
+ }
+ if (isDirtyBox || hasStackedSeries) {
+ axis.redraw();
+ }
+ });
+ }
+
+ // the plot areas size has changed
+ if (isDirtyBox) {
+ chart.drawChartBox();
+ }
+
+ // Fire an event before redrawing series, used by the boost module to
+ // clear previous series renderings.
+ fireEvent(chart, 'predraw');
+
+ // redraw affected series
+ each(series, function (serie) {
+ if ((isDirtyBox || serie.isDirty) && serie.visible) {
+ serie.redraw();
+ }
+ // Set it here, otherwise we will have unlimited 'updatedData' calls
+ // for a hidden series after setData(). Fixes #6012
+ serie.isDirtyData = false;
+ });
+
+ // move tooltip or reset
+ if (pointer) {
+ pointer.reset(true);
+ }
+
+ // redraw if canvas
+ renderer.draw();
+
+ // Fire the events
+ fireEvent(chart, 'redraw');
+ fireEvent(chart, 'render');
+
+ if (isHiddenChart) {
+ chart.temporaryDisplay(true);
+ }
+
+ // Fire callbacks that are put on hold until after the redraw
+ each(afterRedraw, function (callback) {
+ callback.call();
+ });
+ },
+
+ /**
+ * Get an axis, series or point object by `id` as given in the configuration
+ * options. Returns `undefined` if no item is found.
+ * @param id {String} The id as given in the configuration options.
+ * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
+ * The retrieved item.
+ * @sample highcharts/plotoptions/series-id/
+ * Get series by id
+ */
+ get: function (id) {
+
+ var ret,
+ series = this.series,
+ i;
+
+ function itemById(item) {
+ return item.id === id || (item.options && item.options.id === id);
+ }
+
+ ret =
+ // Search axes
+ find(this.axes, itemById) ||
+
+ // Search series
+ find(this.series, itemById);
+
+ // Search points
+ for (i = 0; !ret && i < series.length; i++) {
+ ret = find(series[i].points || [], itemById);
+ }
+
+ return ret;
+ },
+
+ /**
+ * Create the Axis instances based on the config options.
+ *
+ * @private
+ */
+ getAxes: function () {
+ var chart = this,
+ options = this.options,
+ xAxisOptions = options.xAxis = splat(options.xAxis || {}),
+ yAxisOptions = options.yAxis = splat(options.yAxis || {}),
+ optionsArray;
+
+ // make sure the options are arrays and add some members
+ each(xAxisOptions, function (axis, i) {
+ axis.index = i;
+ axis.isX = true;
+ });
+
+ each(yAxisOptions, function (axis, i) {
+ axis.index = i;
+ });
+
+ // concatenate all axis options into one array
+ optionsArray = xAxisOptions.concat(yAxisOptions);
+
+ each(optionsArray, function (axisOptions) {
+ new Axis(chart, axisOptions); // eslint-disable-line no-new
+ });
+ },
+
+
+ /**
+ * Returns an array of all currently selected points in the chart. Points
+ * can be selected by clicking or programmatically by the {@link
+ * Highcharts.Point#select} function.
+ *
+ * @return {Array.}
+ * The currently selected points.
+ *
+ * @sample highcharts/plotoptions/series-allowpointselect-line/
+ * Get selected points
+ */
+ getSelectedPoints: function () {
+ var points = [];
+ each(this.series, function (serie) {
+ // series.data - for points outside of viewed range (#6445)
+ points = points.concat(grep(serie.data || [], function (point) {
+ return point.selected;
+ }));
+ });
+ return points;
+ },
+
+ /**
+ * Returns an array of all currently selected series in the chart. Series
+ * can be selected either programmatically by the {@link
+ * Highcharts.Series#select} function or by checking the checkbox next to
+ * the legend item if {@link
+ * https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox|
+ * series.showCheckBox} is true.
+ *
+ * @return {Array.}
+ * The currently selected series.
+ *
+ * @sample highcharts/members/chart-getselectedseries/
+ * Get selected series
+ */
+ getSelectedSeries: function () {
+ return grep(this.series, function (serie) {
+ return serie.selected;
+ });
+ },
+
+ /**
+ * Set a new title or subtitle for the chart.
+ *
+ * @param titleOptions {TitleOptions}
+ * New title options. The title text itself is set by the
+ * `titleOptions.text` property.
+ * @param subtitleOptions {SubtitleOptions}
+ * New subtitle options. The subtitle text itself is set by the
+ * `subtitleOptions.text` property.
+ * @param redraw {Boolean}
+ * Whether to redraw the chart or wait for a later call to
+ * `chart.redraw()`.
+ *
+ * @sample highcharts/members/chart-settitle/ Set title text and styles
+ *
+ */
+ setTitle: function (titleOptions, subtitleOptions, redraw) {
+ var chart = this,
+ options = chart.options,
+ chartTitleOptions,
+ chartSubtitleOptions;
+
+ chartTitleOptions = options.title = merge(
+
+ // Default styles
+ {
+ style: {
+ color: '#333333',
+ fontSize: options.isStock ? '16px' : '18px' // #2944
+ }
+ },
+
+ options.title,
+ titleOptions
+ );
+ chartSubtitleOptions = options.subtitle = merge(
+
+ // Default styles
+ {
+ style: {
+ color: '#666666'
+ }
+ },
+
+ options.subtitle,
+ subtitleOptions
+ );
+
+ // add title and subtitle
+ each([
+ ['title', titleOptions, chartTitleOptions],
+ ['subtitle', subtitleOptions, chartSubtitleOptions]
+ ], function (arr, i) {
+ var name = arr[0],
+ title = chart[name],
+ titleOptions = arr[1],
+ chartTitleOptions = arr[2];
+
+ if (title && titleOptions) {
+ chart[name] = title = title.destroy(); // remove old
+ }
+
+ if (chartTitleOptions && !title) {
+ chart[name] = chart.renderer.text(
+ chartTitleOptions.text,
+ 0,
+ 0,
+ chartTitleOptions.useHTML
+ )
+ .attr({
+ align: chartTitleOptions.align,
+ 'class': 'highcharts-' + name,
+ zIndex: chartTitleOptions.zIndex || 4
+ })
+ .add();
+
+ // Update methods, shortcut to Chart.setTitle
+ chart[name].update = function (o) {
+ chart.setTitle(!i && o, i && o);
+ };
+
+
+ // Presentational
+ chart[name].css(chartTitleOptions.style);
+
+
+ }
+ });
+ chart.layOutTitles(redraw);
+ },
+
+ /**
+ * Internal function to lay out the chart titles and cache the full offset
+ * height for use in `getMargins`. The result is stored in
+ * `this.titleOffset`.
+ *
+ * @private
+ */
+ layOutTitles: function (redraw) {
+ var titleOffset = 0,
+ requiresDirtyBox,
+ renderer = this.renderer,
+ spacingBox = this.spacingBox;
+
+ // Lay out the title and the subtitle respectively
+ each(['title', 'subtitle'], function (key) {
+ var title = this[key],
+ titleOptions = this.options[key],
+ offset = key === 'title' ? -3 :
+ // Floating subtitle (#6574)
+ titleOptions.verticalAlign ? 0 : titleOffset + 2,
+ titleSize;
+
+ if (title) {
+
+ titleSize = titleOptions.style.fontSize;
+
+ titleSize = renderer.fontMetrics(titleSize, title).b;
+
+ title
+ .css({
+ width: (titleOptions.width ||
+ spacingBox.width + titleOptions.widthAdjust) + 'px'
+ })
+ .align(extend({
+ y: offset + titleSize
+ }, titleOptions), false, 'spacingBox');
+
+ if (!titleOptions.floating && !titleOptions.verticalAlign) {
+ titleOffset = Math.ceil(
+ titleOffset +
+ // Skip the cache for HTML (#3481)
+ title.getBBox(titleOptions.useHTML).height
+ );
+ }
+ }
+ }, this);
+
+ requiresDirtyBox = this.titleOffset !== titleOffset;
+ this.titleOffset = titleOffset; // used in getMargins
+
+ if (!this.isDirtyBox && requiresDirtyBox) {
+ this.isDirtyBox = requiresDirtyBox;
+ // Redraw if necessary (#2719, #2744)
+ if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) {
+ this.redraw();
+ }
+ }
+ },
+
+ /**
+ * Internal function to get the chart width and height according to options
+ * and container size. Sets {@link Chart.chartWidth} and {@link
+ * Chart.chartHeight}.
+ */
+ getChartSize: function () {
+ var chart = this,
+ optionsChart = chart.options.chart,
+ widthOption = optionsChart.width,
+ heightOption = optionsChart.height,
+ renderTo = chart.renderTo;
+
+ // Get inner width and height
+ if (!defined(widthOption)) {
+ chart.containerWidth = H.getStyle(renderTo, 'width');
+ }
+ if (!defined(heightOption)) {
+ chart.containerHeight = H.getStyle(renderTo, 'height');
+ }
+
+ /**
+ * The current pixel width of the chart.
+ *
+ * @name chartWidth
+ * @memberOf Chart
+ * @type {Number}
+ */
+ chart.chartWidth = Math.max( // #1393
+ 0,
+ widthOption || chart.containerWidth || 600 // #1460
+ );
+ /**
+ * The current pixel height of the chart.
+ *
+ * @name chartHeight
+ * @memberOf Chart
+ * @type {Number}
+ */
+ chart.chartHeight = Math.max(
+ 0,
+ H.relativeLength(
+ heightOption,
+ chart.chartWidth
+ ) ||
+ (chart.containerHeight > 1 ? chart.containerHeight : 400)
+ );
+ },
+
+ /**
+ * If the renderTo element has no offsetWidth, most likely one or more of
+ * its parents are hidden. Loop up the DOM tree to temporarily display the
+ * parents, then save the original display properties, and when the true
+ * size is retrieved, reset them. Used on first render and on redraws.
+ *
+ * @private
+ *
+ * @param {Boolean} revert
+ * Revert to the saved original styles.
+ */
+ temporaryDisplay: function (revert) {
+ var node = this.renderTo,
+ tempStyle;
+ if (!revert) {
+ while (node && node.style) {
+
+ // When rendering to a detached node, it needs to be temporarily
+ // attached in order to read styling and bounding boxes (#5783,
+ // #7024).
+ if (!doc.body.contains(node) && !node.parentNode) {
+ node.hcOrigDetached = true;
+ doc.body.appendChild(node);
+ }
+ if (
+ H.getStyle(node, 'display', false) === 'none' ||
+ node.hcOricDetached
+ ) {
+ node.hcOrigStyle = {
+ display: node.style.display,
+ height: node.style.height,
+ overflow: node.style.overflow
+ };
+ tempStyle = {
+ display: 'block',
+ overflow: 'hidden'
+ };
+ if (node !== this.renderTo) {
+ tempStyle.height = 0;
+ }
+
+ H.css(node, tempStyle);
+
+ // If it still doesn't have an offset width after setting
+ // display to block, it probably has an !important priority
+ // #2631, 6803
+ if (!node.offsetWidth) {
+ node.style.setProperty('display', 'block', 'important');
+ }
+ }
+ node = node.parentNode;
+
+ if (node === doc.body) {
+ break;
+ }
+ }
+ } else {
+ while (node && node.style) {
+ if (node.hcOrigStyle) {
+ H.css(node, node.hcOrigStyle);
+ delete node.hcOrigStyle;
+ }
+ if (node.hcOrigDetached) {
+ doc.body.removeChild(node);
+ node.hcOrigDetached = false;
+ }
+ node = node.parentNode;
+ }
+ }
+ },
+
+ /**
+ * Set the {@link Chart.container|chart container's} class name, in
+ * addition to `highcharts-container`.
+ */
+ setClassName: function (className) {
+ this.container.className = 'highcharts-container ' + (className || '');
+ },
+
+ /**
+ * Get the containing element, determine the size and create the inner
+ * container div to hold the chart.
+ *
+ * @private
+ */
+ getContainer: function () {
+ var chart = this,
+ container,
+ options = chart.options,
+ optionsChart = options.chart,
+ chartWidth,
+ chartHeight,
+ renderTo = chart.renderTo,
+ indexAttrName = 'data-highcharts-chart',
+ oldChartIndex,
+ Ren,
+ containerId = H.uniqueKey(),
+ containerStyle,
+ key;
+
+ if (!renderTo) {
+ chart.renderTo = renderTo = optionsChart.renderTo;
+ }
+
+ if (isString(renderTo)) {
+ chart.renderTo = renderTo = doc.getElementById(renderTo);
+ }
+
+ // Display an error if the renderTo is wrong
+ if (!renderTo) {
+ H.error(13, true);
+ }
+
+ // If the container already holds a chart, destroy it. The check for
+ // hasRendered is there because web pages that are saved to disk from
+ // the browser, will preserve the data-highcharts-chart attribute and
+ // the SVG contents, but not an interactive chart. So in this case,
+ // charts[oldChartIndex] will point to the wrong chart if any (#2609).
+ oldChartIndex = pInt(attr(renderTo, indexAttrName));
+ if (
+ isNumber(oldChartIndex) &&
+ charts[oldChartIndex] &&
+ charts[oldChartIndex].hasRendered
+ ) {
+ charts[oldChartIndex].destroy();
+ }
+
+ // Make a reference to the chart from the div
+ attr(renderTo, indexAttrName, chart.index);
+
+ // remove previous chart
+ renderTo.innerHTML = '';
+
+ // If the container doesn't have an offsetWidth, it has or is a child of
+ // a node that has display:none. We need to temporarily move it out to a
+ // visible state to determine the size, else the legend and tooltips
+ // won't render properly. The skipClone option is used in sparklines as
+ // a micro optimization, saving about 1-2 ms each chart.
+ if (!optionsChart.skipClone && !renderTo.offsetWidth) {
+ chart.temporaryDisplay();
+ }
+
+ // get the width and height
+ chart.getChartSize();
+ chartWidth = chart.chartWidth;
+ chartHeight = chart.chartHeight;
+
+ // Create the inner container
+
+ containerStyle = extend({
+ position: 'relative',
+ overflow: 'hidden', // needed for context menu (avoid scrollbars)
+ // and content overflow in IE
+ width: chartWidth + 'px',
+ height: chartHeight + 'px',
+ textAlign: 'left',
+ lineHeight: 'normal', // #427
+ zIndex: 0, // #1072
+ '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
+ }, optionsChart.style);
+
+
+ /**
+ * The containing HTML element of the chart. The container is
+ * dynamically inserted into the element given as the `renderTo`
+ * parameterin the {@link Highcharts#chart} constructor.
+ *
+ * @memberOf Highcharts.Chart
+ * @type {HTMLDOMElement}
+ */
+ container = createElement(
+ 'div',
+ {
+ id: containerId
+ },
+ containerStyle,
+ renderTo
+ );
+ chart.container = container;
+
+ // cache the cursor (#1650)
+ chart._cursor = container.style.cursor;
+
+ // Initialize the renderer
+ Ren = H[optionsChart.renderer] || H.Renderer;
+
+ /**
+ * The renderer instance of the chart. Each chart instance has only one
+ * associated renderer.
+ * @type {SVGRenderer}
+ * @name renderer
+ * @memberOf Chart
+ */
+ chart.renderer = new Ren(
+ container,
+ chartWidth,
+ chartHeight,
+ null,
+ optionsChart.forExport,
+ options.exporting && options.exporting.allowHTML
+ );
+
+
+ chart.setClassName(optionsChart.className);
+
+ chart.renderer.setStyle(optionsChart.style);
+
+
+ // Add a reference to the charts index
+ chart.renderer.chartIndex = chart.index;
+ },
+
+ /**
+ * Calculate margins by rendering axis labels in a preliminary position.
+ * Title, subtitle and legend have already been rendered at this stage, but
+ * will be moved into their final positions.
+ *
+ * @private
+ */
+ getMargins: function (skipAxes) {
+ var chart = this,
+ spacing = chart.spacing,
+ margin = chart.margin,
+ titleOffset = chart.titleOffset;
+
+ chart.resetMargins();
+
+ // Adjust for title and subtitle
+ if (titleOffset && !defined(margin[0])) {
+ chart.plotTop = Math.max(
+ chart.plotTop,
+ titleOffset + chart.options.title.margin + spacing[0]
+ );
+ }
+
+ // Adjust for legend
+ if (chart.legend && chart.legend.display) {
+ chart.legend.adjustMargins(margin, spacing);
+ }
+
+ // adjust for scroller
+ if (chart.extraMargin) {
+ chart[chart.extraMargin.type] =
+ (chart[chart.extraMargin.type] || 0) + chart.extraMargin.value;
+ }
+
+ // adjust for rangeSelector
+ if (chart.adjustPlotArea) {
+ chart.adjustPlotArea();
+ }
+
+ if (!skipAxes) {
+ this.getAxisMargins();
+ }
+ },
+
+ getAxisMargins: function () {
+
+ var chart = this,
+ // [top, right, bottom, left]
+ axisOffset = chart.axisOffset = [0, 0, 0, 0],
+ margin = chart.margin;
+
+ // pre-render axes to get labels offset width
+ if (chart.hasCartesianSeries) {
+ each(chart.axes, function (axis) {
+ if (axis.visible) {
+ axis.getOffset();
+ }
+ });
+ }
+
+ // Add the axis offsets
+ each(marginNames, function (m, side) {
+ if (!defined(margin[side])) {
+ chart[m] += axisOffset[side];
+ }
+ });
+
+ chart.setChartSize();
+
+ },
+
+ /**
+ * Reflows the chart to its container. By default, the chart reflows
+ * automatically to its container following a `window.resize` event, as per
+ * the {@link https://api.highcharts/highcharts/chart.reflow|chart.reflow}
+ * option. However, there are no reliable events for div resize, so if the
+ * container is resized without a window resize event, this must be called
+ * explicitly.
+ *
+ * @param {Object} e
+ * Event arguments. Used primarily when the function is called
+ * internally as a response to window resize.
+ *
+ * @sample highcharts/members/chart-reflow/
+ * Resize div and reflow
+ * @sample highcharts/chart/events-container/
+ * Pop up and reflow
+ */
+ reflow: function (e) {
+ var chart = this,
+ optionsChart = chart.options.chart,
+ renderTo = chart.renderTo,
+ hasUserSize = (
+ defined(optionsChart.width) &&
+ defined(optionsChart.height)
+ ),
+ width = optionsChart.width || H.getStyle(renderTo, 'width'),
+ height = optionsChart.height || H.getStyle(renderTo, 'height'),
+ target = e ? e.target : win;
+
+ // Width and height checks for display:none. Target is doc in IE8 and
+ // Opera, win in Firefox, Chrome and IE9.
+ if (
+ !hasUserSize &&
+ !chart.isPrinting &&
+ width &&
+ height &&
+ (target === win || target === doc)
+ ) {
+ if (
+ width !== chart.containerWidth ||
+ height !== chart.containerHeight
+ ) {
+ clearTimeout(chart.reflowTimeout);
+ // When called from window.resize, e is set, else it's called
+ // directly (#2224)
+ chart.reflowTimeout = syncTimeout(function () {
+ // Set size, it may have been destroyed in the meantime
+ // (#1257)
+ if (chart.container) {
+ chart.setSize(undefined, undefined, false);
+ }
+ }, e ? 100 : 0);
+ }
+ chart.containerWidth = width;
+ chart.containerHeight = height;
+ }
+ },
+
+ /**
+ * Add the event handlers necessary for auto resizing, depending on the
+ * `chart.events.reflow` option.
+ *
+ * @private
+ */
+ initReflow: function () {
+ var chart = this,
+ unbind;
+
+ unbind = addEvent(win, 'resize', function (e) {
+ chart.reflow(e);
+ });
+ addEvent(chart, 'destroy', unbind);
+
+ // The following will add listeners to re-fit the chart before and after
+ // printing (#2284). However it only works in WebKit. Should have worked
+ // in Firefox, but not supported in IE.
+ /*
+ if (win.matchMedia) {
+ win.matchMedia('print').addListener(function reflow() {
+ chart.reflow();
+ });
+ }
+ */
+ },
+
+ /**
+ * Resize the chart to a given width and height. In order to set the width
+ * only, the height argument may be skipped. To set the height only, pass
+ * `undefined for the width.
+ * @param {Number|undefined|null} [width]
+ * The new pixel width of the chart. Since v4.2.6, the argument can
+ * be `undefined` in order to preserve the current value (when
+ * setting height only), or `null` to adapt to the width of the
+ * containing element.
+ * @param {Number|undefined|null} [height]
+ * The new pixel height of the chart. Since v4.2.6, the argument can
+ * be `undefined` in order to preserve the current value, or `null`
+ * in order to adapt to the height of the containing element.
+ * @param {AnimationOptions} [animation=true]
+ * Whether and how to apply animation.
+ *
+ * @sample highcharts/members/chart-setsize-button/
+ * Test resizing from buttons
+ * @sample highcharts/members/chart-setsize-jquery-resizable/
+ * Add a jQuery UI resizable
+ * @sample stock/members/chart-setsize/
+ * Highstock with UI resizable
+ */
+ setSize: function (width, height, animation) {
+ var chart = this,
+ renderer = chart.renderer,
+ globalAnimation;
+
+ // Handle the isResizing counter
+ chart.isResizing += 1;
+
+ // set the animation for the current process
+ H.setAnimation(animation, chart);
+
+ chart.oldChartHeight = chart.chartHeight;
+ chart.oldChartWidth = chart.chartWidth;
+ if (width !== undefined) {
+ chart.options.chart.width = width;
+ }
+ if (height !== undefined) {
+ chart.options.chart.height = height;
+ }
+ chart.getChartSize();
+
+ // Resize the container with the global animation applied if enabled
+ // (#2503)
+
+ globalAnimation = renderer.globalAnimation;
+ (globalAnimation ? animate : css)(chart.container, {
+ width: chart.chartWidth + 'px',
+ height: chart.chartHeight + 'px'
+ }, globalAnimation);
+
+
+ chart.setChartSize(true);
+ renderer.setSize(chart.chartWidth, chart.chartHeight, animation);
+
+ // handle axes
+ each(chart.axes, function (axis) {
+ axis.isDirty = true;
+ axis.setScale();
+ });
+
+ chart.isDirtyLegend = true; // force legend redraw
+ chart.isDirtyBox = true; // force redraw of plot and chart border
+
+ chart.layOutTitles(); // #2857
+ chart.getMargins();
+
+ chart.redraw(animation);
+
+
+ chart.oldChartHeight = null;
+ fireEvent(chart, 'resize');
+
+ // Fire endResize and set isResizing back. If animation is disabled,
+ // fire without delay
+ syncTimeout(function () {
+ if (chart) {
+ fireEvent(chart, 'endResize', null, function () {
+ chart.isResizing -= 1;
+ });
+ }
+ }, animObject(globalAnimation).duration);
+ },
+
+ /**
+ * Set the public chart properties. This is done before and after the
+ * pre-render to determine margin sizes.
+ *
+ * @private
+ */
+ setChartSize: function (skipAxes) {
+ var chart = this,
+ inverted = chart.inverted,
+ renderer = chart.renderer,
+ chartWidth = chart.chartWidth,
+ chartHeight = chart.chartHeight,
+ optionsChart = chart.options.chart,
+ spacing = chart.spacing,
+ clipOffset = chart.clipOffset,
+ clipX,
+ clipY,
+ plotLeft,
+ plotTop,
+ plotWidth,
+ plotHeight,
+ plotBorderWidth;
+
+ /**
+ * The current left position of the plot area in pixels.
+ *
+ * @name plotLeft
+ * @memberOf Chart
+ * @type {Number}
+ */
+ chart.plotLeft = plotLeft = Math.round(chart.plotLeft);
+
+ /**
+ * The current top position of the plot area in pixels.
+ *
+ * @name plotTop
+ * @memberOf Chart
+ * @type {Number}
+ */
+ chart.plotTop = plotTop = Math.round(chart.plotTop);
+
+ /**
+ * The current width of the plot area in pixels.
+ *
+ * @name plotWidth
+ * @memberOf Chart
+ * @type {Number}
+ */
+ chart.plotWidth = plotWidth = Math.max(
+ 0,
+ Math.round(chartWidth - plotLeft - chart.marginRight)
+ );
+
+ /**
+ * The current height of the plot area in pixels.
+ *
+ * @name plotHeight
+ * @memberOf Chart
+ * @type {Number}
+ */
+ chart.plotHeight = plotHeight = Math.max(
+ 0,
+ Math.round(chartHeight - plotTop - chart.marginBottom)
+ );
+
+ chart.plotSizeX = inverted ? plotHeight : plotWidth;
+ chart.plotSizeY = inverted ? plotWidth : plotHeight;
+
+ chart.plotBorderWidth = optionsChart.plotBorderWidth || 0;
+
+ // Set boxes used for alignment
+ chart.spacingBox = renderer.spacingBox = {
+ x: spacing[3],
+ y: spacing[0],
+ width: chartWidth - spacing[3] - spacing[1],
+ height: chartHeight - spacing[0] - spacing[2]
+ };
+ chart.plotBox = renderer.plotBox = {
+ x: plotLeft,
+ y: plotTop,
+ width: plotWidth,
+ height: plotHeight
+ };
+
+ plotBorderWidth = 2 * Math.floor(chart.plotBorderWidth / 2);
+ clipX = Math.ceil(Math.max(plotBorderWidth, clipOffset[3]) / 2);
+ clipY = Math.ceil(Math.max(plotBorderWidth, clipOffset[0]) / 2);
+ chart.clipBox = {
+ x: clipX,
+ y: clipY,
+ width: Math.floor(
+ chart.plotSizeX -
+ Math.max(plotBorderWidth, clipOffset[1]) / 2 -
+ clipX
+ ),
+ height: Math.max(
+ 0,
+ Math.floor(
+ chart.plotSizeY -
+ Math.max(plotBorderWidth, clipOffset[2]) / 2 -
+ clipY
+ )
+ )
+ };
+
+ if (!skipAxes) {
+ each(chart.axes, function (axis) {
+ axis.setAxisSize();
+ axis.setAxisTranslation();
+ });
+ }
+ },
+
+ /**
+ * Initial margins before auto size margins are applied.
+ *
+ * @private
+ */
+ resetMargins: function () {
+ var chart = this,
+ chartOptions = chart.options.chart;
+
+ // Create margin and spacing array
+ each(['margin', 'spacing'], function splashArrays(target) {
+ var value = chartOptions[target],
+ values = isObject(value) ? value : [value, value, value, value];
+
+ each(['Top', 'Right', 'Bottom', 'Left'], function (sideName, side) {
+ chart[target][side] = pick(
+ chartOptions[target + sideName],
+ values[side]
+ );
+ });
+ });
+
+ // Set margin names like chart.plotTop, chart.plotLeft,
+ // chart.marginRight, chart.marginBottom.
+ each(marginNames, function (m, side) {
+ chart[m] = pick(chart.margin[side], chart.spacing[side]);
+ });
+ chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
+ chart.clipOffset = [0, 0, 0, 0];
+ },
+
+ /**
+ * Internal function to draw or redraw the borders and backgrounds for chart
+ * and plot area.
+ *
+ * @private
+ */
+ drawChartBox: function () {
+ var chart = this,
+ optionsChart = chart.options.chart,
+ renderer = chart.renderer,
+ chartWidth = chart.chartWidth,
+ chartHeight = chart.chartHeight,
+ chartBackground = chart.chartBackground,
+ plotBackground = chart.plotBackground,
+ plotBorder = chart.plotBorder,
+ chartBorderWidth,
+
+ plotBGImage = chart.plotBGImage,
+ chartBackgroundColor = optionsChart.backgroundColor,
+ plotBackgroundColor = optionsChart.plotBackgroundColor,
+ plotBackgroundImage = optionsChart.plotBackgroundImage,
+
+ mgn,
+ bgAttr,
+ plotLeft = chart.plotLeft,
+ plotTop = chart.plotTop,
+ plotWidth = chart.plotWidth,
+ plotHeight = chart.plotHeight,
+ plotBox = chart.plotBox,
+ clipRect = chart.clipRect,
+ clipBox = chart.clipBox,
+ verb = 'animate';
+
+ // Chart area
+ if (!chartBackground) {
+ chart.chartBackground = chartBackground = renderer.rect()
+ .addClass('highcharts-background')
+ .add();
+ verb = 'attr';
+ }
+
+
+ // Presentational
+ chartBorderWidth = optionsChart.borderWidth || 0;
+ mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);
+
+ bgAttr = {
+ fill: chartBackgroundColor || 'none'
+ };
+
+ if (chartBorderWidth || chartBackground['stroke-width']) { // #980
+ bgAttr.stroke = optionsChart.borderColor;
+ bgAttr['stroke-width'] = chartBorderWidth;
+ }
+ chartBackground
+ .attr(bgAttr)
+ .shadow(optionsChart.shadow);
+
+ chartBackground[verb]({
+ x: mgn / 2,
+ y: mgn / 2,
+ width: chartWidth - mgn - chartBorderWidth % 2,
+ height: chartHeight - mgn - chartBorderWidth % 2,
+ r: optionsChart.borderRadius
+ });
+
+ // Plot background
+ verb = 'animate';
+ if (!plotBackground) {
+ verb = 'attr';
+ chart.plotBackground = plotBackground = renderer.rect()
+ .addClass('highcharts-plot-background')
+ .add();
+ }
+ plotBackground[verb](plotBox);
+
+
+ // Presentational attributes for the background
+ plotBackground
+ .attr({
+ fill: plotBackgroundColor || 'none'
+ })
+ .shadow(optionsChart.plotShadow);
+
+ // Create the background image
+ if (plotBackgroundImage) {
+ if (!plotBGImage) {
+ chart.plotBGImage = renderer.image(
+ plotBackgroundImage,
+ plotLeft,
+ plotTop,
+ plotWidth,
+ plotHeight
+ ).add();
+ } else {
+ plotBGImage.animate(plotBox);
+ }
+ }
+
+
+ // Plot clip
+ if (!clipRect) {
+ chart.clipRect = renderer.clipRect(clipBox);
+ } else {
+ clipRect.animate({
+ width: clipBox.width,
+ height: clipBox.height
+ });
+ }
+
+ // Plot area border
+ verb = 'animate';
+ if (!plotBorder) {
+ verb = 'attr';
+ chart.plotBorder = plotBorder = renderer.rect()
+ .addClass('highcharts-plot-border')
+ .attr({
+ zIndex: 1 // Above the grid
+ })
+ .add();
+ }
+
+
+ // Presentational
+ plotBorder.attr({
+ stroke: optionsChart.plotBorderColor,
+ 'stroke-width': optionsChart.plotBorderWidth || 0,
+ fill: 'none'
+ });
+
+
+ plotBorder[verb](plotBorder.crisp({
+ x: plotLeft,
+ y: plotTop,
+ width: plotWidth,
+ height: plotHeight
+ }, -plotBorder.strokeWidth())); // #3282 plotBorder should be negative;
+
+ // reset
+ chart.isDirtyBox = false;
+ },
+
+ /**
+ * Detect whether a certain chart property is needed based on inspecting its
+ * options and series. This mainly applies to the chart.inverted property,
+ * and in extensions to the chart.angular and chart.polar properties.
+ *
+ * @private
+ */
+ propFromSeries: function () {
+ var chart = this,
+ optionsChart = chart.options.chart,
+ klass,
+ seriesOptions = chart.options.series,
+ i,
+ value;
+
+
+ each(['inverted', 'angular', 'polar'], function (key) {
+
+ // The default series type's class
+ klass = seriesTypes[optionsChart.type ||
+ optionsChart.defaultSeriesType];
+
+ // Get the value from available chart-wide properties
+ value =
+ optionsChart[key] || // It is set in the options
+ (klass && klass.prototype[key]); // The default series class
+ // requires it
+
+ // 4. Check if any the chart's series require it
+ i = seriesOptions && seriesOptions.length;
+ while (!value && i--) {
+ klass = seriesTypes[seriesOptions[i].type];
+ if (klass && klass.prototype[key]) {
+ value = true;
+ }
+ }
+
+ // Set the chart property
+ chart[key] = value;
+ });
+
+ },
+
+ /**
+ * Internal function to link two or more series together, based on the
+ * `linkedTo` option. This is done from `Chart.render`, and after
+ * `Chart.addSeries` and `Series.remove`.
+ *
+ * @private
+ */
+ linkSeries: function () {
+ var chart = this,
+ chartSeries = chart.series;
+
+ // Reset links
+ each(chartSeries, function (series) {
+ series.linkedSeries.length = 0;
+ });
+
+ // Apply new links
+ each(chartSeries, function (series) {
+ var linkedTo = series.options.linkedTo;
+ if (isString(linkedTo)) {
+ if (linkedTo === ':previous') {
+ linkedTo = chart.series[series.index - 1];
+ } else {
+ linkedTo = chart.get(linkedTo);
+ }
+ // #3341 avoid mutual linking
+ if (linkedTo && linkedTo.linkedParent !== series) {
+ linkedTo.linkedSeries.push(series);
+ series.linkedParent = linkedTo;
+ series.visible = pick(
+ series.options.visible,
+ linkedTo.options.visible,
+ series.visible
+ ); // #3879
+ }
+ }
+ });
+ },
+
+ /**
+ * Render series for the chart.
+ *
+ * @private
+ */
+ renderSeries: function () {
+ each(this.series, function (serie) {
+ serie.translate();
+ serie.render();
+ });
+ },
+
+ /**
+ * Render labels for the chart.
+ *
+ * @private
+ */
+ renderLabels: function () {
+ var chart = this,
+ labels = chart.options.labels;
+ if (labels.items) {
+ each(labels.items, function (label) {
+ var style = extend(labels.style, label.style),
+ x = pInt(style.left) + chart.plotLeft,
+ y = pInt(style.top) + chart.plotTop + 12;
+
+ // delete to prevent rewriting in IE
+ delete style.left;
+ delete style.top;
+
+ chart.renderer.text(
+ label.html,
+ x,
+ y
+ )
+ .attr({ zIndex: 2 })
+ .css(style)
+ .add();
+
+ });
+ }
+ },
+
+ /**
+ * Render all graphics for the chart. Runs internally on initialization.
+ *
+ * @private
+ */
+ render: function () {
+ var chart = this,
+ axes = chart.axes,
+ renderer = chart.renderer,
+ options = chart.options,
+ tempWidth,
+ tempHeight,
+ redoHorizontal,
+ redoVertical;
+
+ // Title
+ chart.setTitle();
+
+
+ // Legend
+ chart.legend = new Legend(chart, options.legend);
+
+ // Get stacks
+ if (chart.getStacks) {
+ chart.getStacks();
+ }
+
+ // Get chart margins
+ chart.getMargins(true);
+ chart.setChartSize();
+
+ // Record preliminary dimensions for later comparison
+ tempWidth = chart.plotWidth;
+ // 21 is the most common correction for X axis labels
+ // use Math.max to prevent negative plotHeight
+ tempHeight = chart.plotHeight = Math.max(chart.plotHeight - 21, 0);
+
+ // Get margins by pre-rendering axes
+ each(axes, function (axis) {
+ axis.setScale();
+ });
+ chart.getAxisMargins();
+
+ // If the plot area size has changed significantly, calculate tick positions again
+ redoHorizontal = tempWidth / chart.plotWidth > 1.1;
+ redoVertical = tempHeight / chart.plotHeight > 1.05; // Height is more sensitive
+
+ if (redoHorizontal || redoVertical) {
+
+ each(axes, function (axis) {
+ if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) {
+ axis.setTickInterval(true); // update to reflect the new margins
+ }
+ });
+ chart.getMargins(); // second pass to check for new labels
+ }
+
+ // Draw the borders and backgrounds
+ chart.drawChartBox();
+
+
+ // Axes
+ if (chart.hasCartesianSeries) {
+ each(axes, function (axis) {
+ if (axis.visible) {
+ axis.render();
+ }
+ });
+ }
+
+ // The series
+ if (!chart.seriesGroup) {
+ chart.seriesGroup = renderer.g('series-group')
+ .attr({ zIndex: 3 })
+ .add();
+ }
+ chart.renderSeries();
+
+ // Labels
+ chart.renderLabels();
+
+ // Credits
+ chart.addCredits();
+
+ // Handle responsiveness
+ if (chart.setResponsive) {
+ chart.setResponsive();
+ }
+
+ // Set flag
+ chart.hasRendered = true;
+
+ },
+
+ /**
+ * Set a new credits label for the chart.
+ *
+ * @param {CreditOptions} options
+ * A configuration object for the new credits.
+ * @sample highcharts/credits/credits-update/ Add and update credits
+ */
+ addCredits: function (credits) {
+ var chart = this;
+
+ credits = merge(true, this.options.credits, credits);
+ if (credits.enabled && !this.credits) {
+
+ /**
+ * The chart's credits label. The label has an `update` method that
+ * allows setting new options as per the {@link
+ * https://api.highcharts.com/highcharts/credits|
+ * credits options set}.
+ *
+ * @memberof Highcharts.Chart
+ * @name credits
+ * @type {Highcharts.SVGElement}
+ */
+ this.credits = this.renderer.text(
+ credits.text + (this.mapCredits || ''),
+ 0,
+ 0
+ )
+ .addClass('highcharts-credits')
+ .on('click', function () {
+ if (credits.href) {
+ win.location.href = credits.href;
+ }
+ })
+ .attr({
+ align: credits.position.align,
+ zIndex: 8
+ })
+
+ .css(credits.style)
+
+ .add()
+ .align(credits.position);
+
+ // Dynamically update
+ this.credits.update = function (options) {
+ chart.credits = chart.credits.destroy();
+ chart.addCredits(options);
+ };
+ }
+ },
+
+ /**
+ * Remove the chart and purge memory. This method is called internally
+ * before adding a second chart into the same container, as well as on
+ * window unload to prevent leaks.
+ *
+ * @sample highcharts/members/chart-destroy/
+ * Destroy the chart from a button
+ * @sample stock/members/chart-destroy/
+ * Destroy with Highstock
+ */
+ destroy: function () {
+ var chart = this,
+ axes = chart.axes,
+ series = chart.series,
+ container = chart.container,
+ i,
+ parentNode = container && container.parentNode;
+
+ // fire the chart.destoy event
+ fireEvent(chart, 'destroy');
+
+ // Delete the chart from charts lookup array
+ if (chart.renderer.forExport) {
+ H.erase(charts, chart); // #6569
+ } else {
+ charts[chart.index] = undefined;
+ }
+ H.chartCount--;
+ chart.renderTo.removeAttribute('data-highcharts-chart');
+
+ // remove events
+ removeEvent(chart);
+
+ // ==== Destroy collections:
+ // Destroy axes
+ i = axes.length;
+ while (i--) {
+ axes[i] = axes[i].destroy();
+ }
+
+ // Destroy scroller & scroller series before destroying base series
+ if (this.scroller && this.scroller.destroy) {
+ this.scroller.destroy();
+ }
+
+ // Destroy each series
+ i = series.length;
+ while (i--) {
+ series[i] = series[i].destroy();
+ }
+
+ // ==== Destroy chart properties:
+ each([
+ 'title', 'subtitle', 'chartBackground', 'plotBackground',
+ 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
+ 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
+ 'renderer'
+ ], function (name) {
+ var prop = chart[name];
+
+ if (prop && prop.destroy) {
+ chart[name] = prop.destroy();
+ }
+ });
+
+ // remove container and all SVG
+ if (container) { // can break in IE when destroyed before finished loading
+ container.innerHTML = '';
+ removeEvent(container);
+ if (parentNode) {
+ discardElement(container);
+ }
+
+ }
+
+ // clean it all up
+ objectEach(chart, function (val, key) {
+ delete chart[key];
+ });
+
+ },
+
+
+ /**
+ * VML namespaces can't be added until after complete. Listening
+ * for Perini's doScroll hack is not enough.
+ *
+ * @private
+ */
+ isReadyToRender: function () {
+ var chart = this;
+
+ // Note: win == win.top is required
+ if ((!svg && (win == win.top && doc.readyState !== 'complete'))) { // eslint-disable-line eqeqeq
+ doc.attachEvent('onreadystatechange', function () {
+ doc.detachEvent('onreadystatechange', chart.firstRender);
+ if (doc.readyState === 'complete') {
+ chart.firstRender();
+ }
+ });
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Prepare for first rendering after all data are loaded.
+ *
+ * @private
+ */
+ firstRender: function () {
+ var chart = this,
+ options = chart.options;
+
+ // Check whether the chart is ready to render
+ if (!chart.isReadyToRender()) {
+ return;
+ }
+
+ // Create the container
+ chart.getContainer();
+
+ // Run an early event after the container and renderer are established
+ fireEvent(chart, 'init');
+
+
+ chart.resetMargins();
+ chart.setChartSize();
+
+ // Set the common chart properties (mainly invert) from the given series
+ chart.propFromSeries();
+
+ // get axes
+ chart.getAxes();
+
+ // Initialize the series
+ each(options.series || [], function (serieOptions) {
+ chart.initSeries(serieOptions);
+ });
+
+ chart.linkSeries();
+
+ // Run an event after axes and series are initialized, but before render. At this stage,
+ // the series data is indexed and cached in the xData and yData arrays, so we can access
+ // those before rendering. Used in Highstock.
+ fireEvent(chart, 'beforeRender');
+
+ // depends on inverted and on margins being set
+ if (Pointer) {
+
+ /**
+ * The Pointer that keeps track of mouse and touch interaction.
+ *
+ * @memberof Chart
+ * @name pointer
+ * @type Pointer
+ */
+ chart.pointer = new Pointer(chart, options);
+ }
+
+ chart.render();
+
+ // Fire the load event if there are no external images
+ if (!chart.renderer.imgCount && chart.onload) {
+ chart.onload();
+ }
+
+ // If the chart was rendered outside the top container, put it back in (#3679)
+ chart.temporaryDisplay(true);
+
+ },
+
+ /**
+ * Internal function that runs on chart load, async if any images are loaded
+ * in the chart. Runs the callbacks and triggers the `load` and `render`
+ * events.
+ *
+ * @private
+ */
+ onload: function () {
+
+ // Run callbacks
+ each([this.callback].concat(this.callbacks), function (fn) {
+ if (fn && this.index !== undefined) { // Chart destroyed in its own callback (#3600)
+ fn.apply(this, [this]);
+ }
+ }, this);
+
+ fireEvent(this, 'load');
+ fireEvent(this, 'render');
+
+
+ // Set up auto resize, check for not destroyed (#6068)
+ if (defined(this.index) && this.options.chart.reflow !== false) {
+ this.initReflow();
+ }
+
+ // Don't run again
+ this.onload = null;
+ }
+
+}); // end Chart
+
+}(Highcharts));
+(function (Highcharts) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var Point,
+ H = Highcharts,
+
+ each = H.each,
+ extend = H.extend,
+ erase = H.erase,
+ fireEvent = H.fireEvent,
+ format = H.format,
+ isArray = H.isArray,
+ isNumber = H.isNumber,
+ pick = H.pick,
+ removeEvent = H.removeEvent;
+
+/**
+ * The Point object. The point objects are generated from the `series.data`
+ * configuration objects or raw numbers. They can be accessed from the
+ * `Series.points` array. Other ways to instaniate points are through {@link
+ * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
+ *
+ * @class
+ */
+
+Highcharts.Point = Point = function () {};
+Highcharts.Point.prototype = {
+
+ /**
+ * Initialize the point. Called internally based on the `series.data`
+ * option.
+ * @param {Series} series
+ * The series object containing this point.
+ * @param {Number|Array|Object} options
+ * The data in either number, array or object format.
+ * @param {Number} x Optionally, the X value of the point.
+ * @return {Point} The Point instance.
+ */
+ init: function (series, options, x) {
+
+ var point = this,
+ colors,
+ colorCount = series.chart.options.chart.colorCount,
+ colorIndex;
+
+ /**
+ * The series object associated with the point.
+ *
+ * @name series
+ * @memberof Highcharts.Point
+ * @type Highcharts.Series
+ */
+ point.series = series;
+
+
+ /**
+ * The point's current color.
+ * @name color
+ * @memberof Highcharts.Point
+ * @type {Color}
+ */
+ point.color = series.color; // #3445
+
+ point.applyOptions(options, x);
+
+ if (series.options.colorByPoint) {
+
+ colors = series.options.colors || series.chart.options.colors;
+ point.color = point.color || colors[series.colorCounter];
+ colorCount = colors.length;
+
+ colorIndex = series.colorCounter;
+ series.colorCounter++;
+ // loop back to zero
+ if (series.colorCounter === colorCount) {
+ series.colorCounter = 0;
+ }
+ } else {
+ colorIndex = series.colorIndex;
+ }
+
+ /**
+ * The point's current color index, used in styled mode instead of
+ * `color`. The color index is inserted in class names used for styling.
+ * @name colorIndex
+ * @memberof Highcharts.Point
+ * @type {Number}
+ */
+ point.colorIndex = pick(point.colorIndex, colorIndex);
+
+ series.chart.pointCount++;
+ return point;
+ },
+ /**
+ * Apply the options containing the x and y data and possible some extra
+ * properties. Called on point init or from point.update.
+ *
+ * @private
+ * @param {Object} options The point options as defined in series.data.
+ * @param {Number} x Optionally, the X value.
+ * @returns {Object} The Point instance.
+ */
+ applyOptions: function (options, x) {
+ var point = this,
+ series = point.series,
+ pointValKey = series.options.pointValKey || series.pointValKey;
+
+ options = Point.prototype.optionsToObject.call(this, options);
+
+ // copy options directly to point
+ extend(point, options);
+ point.options = point.options ? extend(point.options, options) : options;
+
+ // Since options are copied into the Point instance, some accidental options must be shielded (#5681)
+ if (options.group) {
+ delete point.group;
+ }
+
+ // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
+ if (pointValKey) {
+ point.y = point[pointValKey];
+ }
+ point.isNull = pick(
+ point.isValid && !point.isValid(),
+ point.x === null || !isNumber(point.y, true)
+ ); // #3571, check for NaN
+
+ // The point is initially selected by options (#5777)
+ if (point.selected) {
+ point.state = 'select';
+ }
+
+ // If no x is set by now, get auto incremented value. All points must have an
+ // x value, however the y value can be null to create a gap in the series
+ if ('name' in point && x === undefined && series.xAxis && series.xAxis.hasNames) {
+ point.x = series.xAxis.nameToX(point);
+ }
+ if (point.x === undefined && series) {
+ if (x === undefined) {
+ point.x = series.autoIncrement(point);
+ } else {
+ point.x = x;
+ }
+ }
+
+ return point;
+ },
+
+ /**
+ * Transform number or array configs into objects. Used internally to unify
+ * the different configuration formats for points. For example, a simple
+ * number `10` in a line series will be transformed to `{ y: 10 }`, and an
+ * array config like `[1, 10]` in a scatter series will be transformed to
+ * `{ x: 1, y: 10 }`.
+ *
+ * @param {Number|Array|Object} options
+ * The input options
+ * @return {Object} Transformed options.
+ */
+ optionsToObject: function (options) {
+ var ret = {},
+ series = this.series,
+ keys = series.options.keys,
+ pointArrayMap = keys || series.pointArrayMap || ['y'],
+ valueCount = pointArrayMap.length,
+ firstItemType,
+ i = 0,
+ j = 0;
+
+ if (isNumber(options) || options === null) {
+ ret[pointArrayMap[0]] = options;
+
+ } else if (isArray(options)) {
+ // with leading x value
+ if (!keys && options.length > valueCount) {
+ firstItemType = typeof options[0];
+ if (firstItemType === 'string') {
+ ret.name = options[0];
+ } else if (firstItemType === 'number') {
+ ret.x = options[0];
+ }
+ i++;
+ }
+ while (j < valueCount) {
+ if (!keys || options[i] !== undefined) { // Skip undefined positions for keys
+ ret[pointArrayMap[j]] = options[i];
+ }
+ i++;
+ j++;
+ }
+ } else if (typeof options === 'object') {
+ ret = options;
+
+ // This is the fastest way to detect if there are individual point dataLabels that need
+ // to be considered in drawDataLabels. These can only occur in object configs.
+ if (options.dataLabels) {
+ series._hasPointLabels = true;
+ }
+
+ // Same approach as above for markers
+ if (options.marker) {
+ series._hasPointMarkers = true;
+ }
+ }
+ return ret;
+ },
+
+ /**
+ * Get the CSS class names for individual points. Used internally where the
+ * returned value is set on every point.
+ *
+ * @returns {String} The class names.
+ */
+ getClassName: function () {
+ return 'highcharts-point' +
+ (this.selected ? ' highcharts-point-select' : '') +
+ (this.negative ? ' highcharts-negative' : '') +
+ (this.isNull ? ' highcharts-null-point' : '') +
+ (this.colorIndex !== undefined ? ' highcharts-color-' +
+ this.colorIndex : '') +
+ (this.options.className ? ' ' + this.options.className : '') +
+ (this.zone && this.zone.className ? ' ' +
+ this.zone.className.replace('highcharts-negative', '') : '');
+ },
+
+ /**
+ * In a series with `zones`, return the zone that the point belongs to.
+ *
+ * @return {Object}
+ * The zone item.
+ */
+ getZone: function () {
+ var series = this.series,
+ zones = series.zones,
+ zoneAxis = series.zoneAxis || 'y',
+ i = 0,
+ zone;
+
+ zone = zones[i];
+ while (this[zoneAxis] >= zone.value) {
+ zone = zones[++i];
+ }
+
+ if (zone && zone.color && !this.options.color) {
+ this.color = zone.color;
+ }
+
+ return zone;
+ },
+
+ /**
+ * Destroy a point to clear memory. Its reference still stays in
+ * `series.data`.
+ *
+ * @private
+ */
+ destroy: function () {
+ var point = this,
+ series = point.series,
+ chart = series.chart,
+ hoverPoints = chart.hoverPoints,
+ prop;
+
+ chart.pointCount--;
+
+ if (hoverPoints) {
+ point.setState();
+ erase(hoverPoints, point);
+ if (!hoverPoints.length) {
+ chart.hoverPoints = null;
+ }
+
+ }
+ if (point === chart.hoverPoint) {
+ point.onMouseOut();
+ }
+
+ // remove all events
+ if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
+ removeEvent(point);
+ point.destroyElements();
+ }
+
+ if (point.legendItem) { // pies have legend items
+ chart.legend.destroyItem(point);
+ }
+
+ for (prop in point) {
+ point[prop] = null;
+ }
+
+
+ },
+
+ /**
+ * Destroy SVG elements associated with the point.
+ *
+ * @private
+ */
+ destroyElements: function () {
+ var point = this,
+ props = ['graphic', 'dataLabel', 'dataLabelUpper', 'connector', 'shadowGroup'],
+ prop,
+ i = 6;
+ while (i--) {
+ prop = props[i];
+ if (point[prop]) {
+ point[prop] = point[prop].destroy();
+ }
+ }
+ },
+
+ /**
+ * Return the configuration hash needed for the data label and tooltip
+ * formatters.
+ *
+ * @returns {Object}
+ * Abstract object used in formatters and formats.
+ */
+ getLabelConfig: function () {
+ return {
+ x: this.category,
+ y: this.y,
+ color: this.color,
+ colorIndex: this.colorIndex,
+ key: this.name || this.category,
+ series: this.series,
+ point: this,
+ percentage: this.percentage,
+ total: this.total || this.stackTotal
+ };
+ },
+
+ /**
+ * Extendable method for formatting each point's tooltip line.
+ *
+ * @param {String} pointFormat
+ * The point format.
+ * @return {String}
+ * A string to be concatenated in to the common tooltip text.
+ */
+ tooltipFormatter: function (pointFormat) {
+
+ // Insert options for valueDecimals, valuePrefix, and valueSuffix
+ var series = this.series,
+ seriesTooltipOptions = series.tooltipOptions,
+ valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
+ valuePrefix = seriesTooltipOptions.valuePrefix || '',
+ valueSuffix = seriesTooltipOptions.valueSuffix || '';
+
+ // Loop over the point array map and replace unformatted values with sprintf formatting markup
+ each(series.pointArrayMap || ['y'], function (key) {
+ key = '{point.' + key; // without the closing bracket
+ if (valuePrefix || valueSuffix) {
+ pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
+ }
+ pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
+ });
+
+ return format(pointFormat, {
+ point: this,
+ series: this.series
+ }, series.chart.time);
+ },
+
+ /**
+ * Fire an event on the Point object.
+ *
+ * @private
+ * @param {String} eventType
+ * @param {Object} eventArgs Additional event arguments
+ * @param {Function} defaultFunction Default event handler
+ */
+ firePointEvent: function (eventType, eventArgs, defaultFunction) {
+ var point = this,
+ series = this.series,
+ seriesOptions = series.options;
+
+ // load event handlers on demand to save time on mouseover/out
+ if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
+ this.importEvents();
+ }
+
+ // add default handler if in selection mode
+ if (eventType === 'click' && seriesOptions.allowPointSelect) {
+ defaultFunction = function (event) {
+ // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
+ if (point.select) { // Could be destroyed by prior event handlers (#2911)
+ point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
+ }
+ };
+ }
+
+ fireEvent(this, eventType, eventArgs, defaultFunction);
+ },
+
+ /**
+ * For certain series types, like pie charts, where individual points can
+ * be shown or hidden.
+ *
+ * @name visible
+ * @memberOf Highcharts.Point
+ * @type {Boolean}
+ */
+ visible: true
+};
+
+/**
+ * For categorized axes this property holds the category name for the
+ * point. For other axes it holds the X value.
+ *
+ * @name category
+ * @memberOf Highcharts.Point
+ * @type {String|Number}
+ */
+
+/**
+ * The name of the point. The name can be given as the first position of the
+ * point configuration array, or as a `name` property in the configuration:
+ *
+ * @example
+ * // Array config
+ * data: [
+ * ['John', 1],
+ * ['Jane', 2]
+ * ]
+ *
+ * // Object config
+ * data: [{
+ * name: 'John',
+ * y: 1
+ * }, {
+ * name: 'Jane',
+ * y: 2
+ * }]
+ *
+ * @name name
+ * @memberOf Highcharts.Point
+ * @type {String}
+ */
+
+
+/**
+ * The percentage for points in a stacked series or pies.
+ *
+ * @name percentage
+ * @memberOf Highcharts.Point
+ * @type {Number}
+ */
+
+/**
+ * The total of values in either a stack for stacked series, or a pie in a pie
+ * series.
+ *
+ * @name total
+ * @memberOf Highcharts.Point
+ * @type {Number}
+ */
+
+/**
+ * The x value of the point.
+ *
+ * @name x
+ * @memberOf Highcharts.Point
+ * @type {Number}
+ */
+
+/**
+ * The y value of the point.
+ *
+ * @name y
+ * @memberOf Highcharts.Point
+ * @type {Number}
+ */
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ animObject = H.animObject,
+ arrayMax = H.arrayMax,
+ arrayMin = H.arrayMin,
+ correctFloat = H.correctFloat,
+ defaultOptions = H.defaultOptions,
+ defaultPlotOptions = H.defaultPlotOptions,
+ defined = H.defined,
+ each = H.each,
+ erase = H.erase,
+ extend = H.extend,
+ fireEvent = H.fireEvent,
+ grep = H.grep,
+ isArray = H.isArray,
+ isNumber = H.isNumber,
+ isString = H.isString,
+ LegendSymbolMixin = H.LegendSymbolMixin, // @todo add as a requirement
+ merge = H.merge,
+ objectEach = H.objectEach,
+ pick = H.pick,
+ Point = H.Point, // @todo add as a requirement
+ removeEvent = H.removeEvent,
+ splat = H.splat,
+ SVGElement = H.SVGElement,
+ syncTimeout = H.syncTimeout,
+ win = H.win;
+
+/**
+ * This is the base series prototype that all other series types inherit from.
+ * A new series is initialized either through the
+ * {@link https://api.highcharts.com/highcharts/series|series} option structure,
+ * or after the chart is initialized, through
+ * {@link Highcharts.Chart#addSeries}.
+ *
+ * The object can be accessed in a number of ways. All series and point event
+ * handlers give a reference to the `series` object. The chart object has a
+ * {@link Highcharts.Chart.series|series} property that is a collection of all
+ * the chart's series. The point objects and axis objects also have the same
+ * reference.
+ *
+ * Another way to reference the series programmatically is by `id`. Add an id
+ * in the series configuration options, and get the series object by {@link
+ * Highcharts.Chart#get}.
+ *
+ * Configuration options for the series are given in three levels. Options for
+ * all series in a chart are given in the
+ * {@link https://api.highcharts.com/highcharts/plotOptions.series|
+ * plotOptions.series} object. Then options for all series of a specific type
+ * are given in the plotOptions of that type, for example `plotOptions.line`.
+ * Next, options for one single series are given in the series array, or as
+ * arguements to `chart.addSeries`.
+ *
+ * The data in the series is stored in various arrays.
+ *
+ * - First, `series.options.data` contains all the original config options for
+ * each point whether added by options or methods like `series.addPoint`.
+ * - Next, `series.data` contains those values converted to points, but in case
+ * the series data length exceeds the `cropThreshold`, or if the data is
+ * grouped, `series.data` doesn't contain all the points. It only contains the
+ * points that have been created on demand.
+ * - Then there's `series.points` that contains all currently visible point
+ * objects. In case of cropping, the cropped-away points are not part of this
+ * array. The `series.points` array starts at `series.cropStart` compared to
+ * `series.data` and `series.options.data`. If however the series data is
+ * grouped, these can't be correlated one to one.
+ * - `series.xData` and `series.processedXData` contain clean x values,
+ * equivalent to `series.data` and `series.points`.
+ * - `series.yData` and `series.processedYData` contain clean y values,
+ * equivalent to `series.data` and `series.points`.
+ *
+ * @class Highcharts.Series
+ * @param {Highcharts.Chart} chart
+ * The chart instance.
+ * @param {Options.plotOptions.series} options
+ * The series options.
+ *
+ */
+
+/**
+ * General options for all series types.
+ * @optionparent plotOptions.series
+ */
+H.Series = H.seriesType('line', null, { // base series options
+
+ /**
+ * The SVG value used for the `stroke-linecap` and `stroke-linejoin`
+ * of a line graph. Round means that lines are rounded in the ends and
+ * bends.
+ *
+ * @validvalue ["round", "butt", "square"]
+ * @type {String}
+ * @default round
+ * @since 3.0.7
+ * @apioption plotOptions.line.linecap
+ */
+
+ /**
+ * Pixel with of the graph line.
+ *
+ * @type {Number}
+ * @see In styled mode, the line stroke-width can be set with the
+ * `.highcharts-graph` class name.
+ * @sample {highcharts} highcharts/plotoptions/series-linewidth-general/
+ * On all series
+ * @sample {highcharts} highcharts/plotoptions/series-linewidth-specific/
+ * On one single series
+ * @default 2
+ * @product highcharts highstock
+ */
+ lineWidth: 2,
+
+
+ /**
+ * For some series, there is a limit that shuts down initial animation
+ * by default when the total number of points in the chart is too high.
+ * For example, for a column chart and its derivatives, animation doesn't
+ * run if there is more than 250 points totally. To disable this cap, set
+ * `animationLimit` to `Infinity`.
+ *
+ * @type {Number}
+ * @apioption plotOptions.series.animationLimit
+ */
+
+ /**
+ * Allow this series' points to be selected by clicking on the graphic
+ * (columns, point markers, pie slices, map areas etc).
+ *
+ * @see [Chart#getSelectedPoints]
+ * (../class-reference/Highcharts.Chart#getSelectedPoints).
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-line/
+ * Line
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-allowpointselect-column/
+ * Column
+ * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-pie/
+ * Pie
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/
+ * Map area
+ * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
+ * Map bubble
+ * @default false
+ * @since 1.2.0
+ */
+ allowPointSelect: false,
+
+
+
+ /**
+ * If true, a checkbox is displayed next to the legend item to allow
+ * selecting the series. The state of the checkbox is determined by
+ * the `selected` option.
+ *
+ * @productdesc {highmaps}
+ * Note that if a `colorAxis` is defined, the color axis is represented in
+ * the legend, not the series.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/
+ * Show select box
+ * @default false
+ * @since 1.2.0
+ */
+ showCheckbox: false,
+
+
+
+ /**
+ * Enable or disable the initial animation when a series is displayed.
+ * The animation can also be set as a configuration object. Please
+ * note that this option only applies to the initial animation of the
+ * series itself. For other animations, see [chart.animation](#chart.
+ * animation) and the animation parameter under the API methods. The
+ * following properties are supported:
+ *
+ *
+ *
+ * duration
+ *
+ * The duration of the animation in milliseconds.
+ *
+ * easing
+ *
+ * A string reference to an easing function set on the `Math` object.
+ * See the _Custom easing function_ demo below.
+ *
+ *
+ *
+ * Due to poor performance, animation is disabled in old IE browsers
+ * for several chart types.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-animation-disabled/
+ * Animation disabled
+ * @sample {highcharts} highcharts/plotoptions/series-animation-slower/
+ * Slower animation
+ * @sample {highcharts} highcharts/plotoptions/series-animation-easing/
+ * Custom easing function
+ * @sample {highstock} stock/plotoptions/animation-slower/
+ * Slower animation
+ * @sample {highstock} stock/plotoptions/animation-easing/
+ * Custom easing function
+ * @sample {highmaps} maps/plotoptions/series-animation-true/
+ * Animation enabled on map series
+ * @sample {highmaps} maps/plotoptions/mapbubble-animation-false/
+ * Disabled on mapbubble series
+ * @default {highcharts} true
+ * @default {highstock} true
+ * @default {highmaps} false
+ */
+ animation: {
+ duration: 1000
+ },
+
+ /**
+ * A class name to apply to the series' graphical elements.
+ *
+ * @type {String}
+ * @since 5.0.0
+ * @apioption plotOptions.series.className
+ */
+
+ /**
+ * The main color of the series. In line type series it applies to the
+ * line and the point markers unless otherwise specified. In bar type
+ * series it applies to the bars unless a color is specified per point.
+ * The default value is pulled from the `options.colors` array.
+ *
+ * In styled mode, the color can be defined by the
+ * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series
+ * color can be set with the `.highcharts-series`, `.highcharts-color-{n}`,
+ * `.highcharts-{type}-series` or `.highcharts-series-{n}` class, or
+ * individual classes given by the `className` option.
+ *
+ * @productdesc {highmaps}
+ * In maps, the series color is rarely used, as most choropleth maps use the
+ * color to denote the value of each point. The series color can however be
+ * used in a map with multiple series holding categorized data.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/series-color-general/
+ * General plot option
+ * @sample {highcharts} highcharts/plotoptions/series-color-specific/
+ * One specific series
+ * @sample {highcharts} highcharts/plotoptions/series-color-area/
+ * Area color
+ * @sample {highmaps} maps/demo/category-map/
+ * Category map by multiple series
+ * @apioption plotOptions.series.color
+ */
+
+ /**
+ * Styled mode only. A specific color index to use for the series, so its
+ * graphic representations are given the class name `highcharts-color-
+ * {n}`.
+ *
+ * @type {Number}
+ * @since 5.0.0
+ * @apioption plotOptions.series.colorIndex
+ */
+
+
+ /**
+ * Whether to connect a graph line across null points, or render a gap
+ * between the two points on either side of the null.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/
+ * False by default
+ * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/
+ * True
+ * @product highcharts highstock
+ * @apioption plotOptions.series.connectNulls
+ */
+
+
+ /**
+ * You can set the cursor to "pointer" if you have click events attached
+ * to the series, to signal to the user that the points and lines can
+ * be clicked.
+ *
+ * @validvalue [null, "default", "none", "help", "pointer", "crosshair"]
+ * @type {String}
+ * @see In styled mode, the series cursor can be set with the same classes
+ * as listed under [series.color](#plotOptions.series.color).
+ * @sample {highcharts} highcharts/plotoptions/series-cursor-line/
+ * On line graph
+ * @sample {highcharts} highcharts/plotoptions/series-cursor-column/
+ * On columns
+ * @sample {highcharts} highcharts/plotoptions/series-cursor-scatter/
+ * On scatter markers
+ * @sample {highstock} stock/plotoptions/cursor/
+ * Pointer on a line graph
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/
+ * Map area
+ * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
+ * Map bubble
+ * @apioption plotOptions.series.cursor
+ */
+
+
+ /**
+ * A name for the dash style to use for the graph, or for some series types
+ * the outline of each shape. The value for the `dashStyle` include:
+ *
+ * * Solid
+ * * ShortDash
+ * * ShortDot
+ * * ShortDashDot
+ * * ShortDashDotDot
+ * * Dot
+ * * Dash
+ * * LongDash
+ * * DashDot
+ * * LongDashDot
+ * * LongDashDotDot
+ *
+ * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
+ * "ShortDashDotDot", "Dot", "Dash" ,"LongDash", "DashDot",
+ * "LongDashDot", "LongDashDotDot"]
+ * @type {String}
+ * @see In styled mode, the [stroke dash-array](http://jsfiddle.net/gh/get/
+ * library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/
+ * series-dashstyle/) can be set with the same classes as listed under
+ * [series.color](#plotOptions.series.color).
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/
+ * Possible values demonstrated
+ * @sample {highcharts} highcharts/plotoptions/series-dashstyle/
+ * Chart suitable for printing in black and white
+ * @sample {highstock} highcharts/plotoptions/series-dashstyle-all/
+ * Possible values demonstrated
+ * @sample {highmaps} highcharts/plotoptions/series-dashstyle-all/
+ * Possible values demonstrated
+ * @sample {highmaps} maps/plotoptions/series-dashstyle/
+ * Dotted borders on a map
+ * @default Solid
+ * @since 2.1
+ * @apioption plotOptions.series.dashStyle
+ */
+
+ /**
+ * Requires the Accessibility module.
+ *
+ * A description of the series to add to the screen reader information
+ * about the series.
+ *
+ * @type {String}
+ * @default undefined
+ * @since 5.0.0
+ * @apioption plotOptions.series.description
+ */
+
+
+
+
+
+ /**
+ * Enable or disable the mouse tracking for a specific series. This
+ * includes point tooltips and click events on graphs and points. For
+ * large datasets it improves performance.
+ *
+ * @type {Boolean}
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-enablemousetracking-false/
+ * No mouse tracking
+ * @sample {highmaps}
+ * maps/plotoptions/series-enablemousetracking-false/
+ * No mouse tracking
+ * @default true
+ * @apioption plotOptions.series.enableMouseTracking
+ */
+
+ /**
+ * By default, series are exposed to screen readers as regions. By enabling
+ * this option, the series element itself will be exposed in the same
+ * way as the data points. This is useful if the series is not used
+ * as a grouping entity in the chart, but you still want to attach a
+ * description to the series.
+ *
+ * Requires the Accessibility module.
+ *
+ * @type {Boolean}
+ * @sample highcharts/accessibility/art-grants/
+ * Accessible data visualization
+ * @default undefined
+ * @since 5.0.12
+ * @apioption plotOptions.series.exposeElementToA11y
+ */
+
+ /**
+ * Whether to use the Y extremes of the total chart width or only the
+ * zoomed area when zooming in on parts of the X axis. By default, the
+ * Y axis adjusts to the min and max of the visible data. Cartesian
+ * series only.
+ *
+ * @type {Boolean}
+ * @default false
+ * @since 4.1.6
+ * @product highcharts highstock
+ * @apioption plotOptions.series.getExtremesFromAll
+ */
+
+ /**
+ * An id for the series. This can be used after render time to get a
+ * pointer to the series object through `chart.get()`.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/series-id/ Get series by id
+ * @since 1.2.0
+ * @apioption series.id
+ */
+
+ /**
+ * The index of the series in the chart, affecting the internal index
+ * in the `chart.series` array, the visible Z index as well as the order
+ * in the legend.
+ *
+ * @type {Number}
+ * @default undefined
+ * @since 2.3.0
+ * @apioption series.index
+ */
+
+ /**
+ * An array specifying which option maps to which key in the data point
+ * array. This makes it convenient to work with unstructured data arrays
+ * from different sources.
+ *
+ * @type {Array}
+ * @see [series.data](#series.line.data)
+ * @sample {highcharts|highstock} highcharts/series/data-keys/
+ * An extended data array with keys
+ * @since 4.1.6
+ * @product highcharts highstock
+ * @apioption plotOptions.series.keys
+ */
+
+ /**
+ * The sequential index of the series in the legend.
+ *
+ * @sample {highcharts|highstock} highcharts/series/legendindex/
+ * Legend in opposite order
+ * @type {Number}
+ * @see [legend.reversed](#legend.reversed), [yAxis.reversedStacks](#yAxis.
+ * reversedStacks)
+ * @apioption series.legendIndex
+ */
+
+ /**
+ * The line cap used for line ends and line joins on the graph.
+ *
+ * @validvalue ["round", "square"]
+ * @type {String}
+ * @default round
+ * @product highcharts highstock
+ * @apioption plotOptions.series.linecap
+ */
+
+ /**
+ * The [id](#series.id) of another series to link to. Additionally,
+ * the value can be ":previous" to link to the previous series. When
+ * two series are linked, only the first one appears in the legend.
+ * Toggling the visibility of this also toggles the linked series.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/demo/arearange-line/ Linked series
+ * @sample {highstock} highcharts/demo/arearange-line/ Linked series
+ * @since 3.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.linkedTo
+ */
+
+ /**
+ * The name of the series as shown in the legend, tooltip etc.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/series/name/ Series name
+ * @sample {highmaps} maps/demo/category-map/ Series name
+ * @apioption series.name
+ */
+
+ /**
+ * The color for the parts of the graph or points that are below the
+ * [threshold](#plotOptions.series.threshold).
+ *
+ * @type {Color}
+ * @see In styled mode, a negative color is applied by setting this
+ * option to `true` combined with the `.highcharts-negative` class name.
+ *
+ * @sample {highcharts} highcharts/plotoptions/series-negative-color/
+ * Spline, area and column
+ * @sample {highcharts} highcharts/plotoptions/arearange-negativecolor/
+ * Arearange
+ * @sample {highcharts} highcharts/css/series-negative-color/
+ * Styled mode
+ * @sample {highstock} highcharts/plotoptions/series-negative-color/
+ * Spline, area and column
+ * @sample {highstock} highcharts/plotoptions/arearange-negativecolor/
+ * Arearange
+ * @sample {highmaps} highcharts/plotoptions/series-negative-color/
+ * Spline, area and column
+ * @sample {highmaps} highcharts/plotoptions/arearange-negativecolor/
+ * Arearange
+ * @default null
+ * @since 3.0
+ * @apioption plotOptions.series.negativeColor
+ */
+
+ /**
+ * Same as [accessibility.pointDescriptionFormatter](#accessibility.
+ * pointDescriptionFormatter), but for an individual series. Overrides
+ * the chart wide configuration.
+ *
+ * @type {Function}
+ * @since 5.0.12
+ * @apioption plotOptions.series.pointDescriptionFormatter
+ */
+
+ /**
+ * If no x values are given for the points in a series, `pointInterval`
+ * defines the interval of the x values. For example, if a series contains
+ * one value every decade starting from year 0, set `pointInterval` to
+ * `10`. In true `datetime` axes, the `pointInterval` is set in
+ * milliseconds.
+ *
+ * It can be also be combined with `pointIntervalUnit` to draw irregular
+ * time intervals.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
+ * Datetime X axis
+ * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
+ * Using pointStart and pointInterval
+ * @default 1
+ * @product highcharts highstock
+ * @apioption plotOptions.series.pointInterval
+ */
+
+ /**
+ * On datetime series, this allows for setting the
+ * [pointInterval](#plotOptions.series.pointInterval) to irregular time
+ * units, `day`, `month` and `year`. A day is usually the same as 24 hours,
+ * but `pointIntervalUnit` also takes the DST crossover into consideration
+ * when dealing with local time. Combine this option with `pointInterval`
+ * to draw weeks, quarters, 6 months, 10 years etc.
+ *
+ * @validvalue [null, "day", "month", "year"]
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/
+ * One point a month
+ * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/
+ * One point a month
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.pointIntervalUnit
+ */
+
+ /**
+ * Possible values: `null`, `"on"`, `"between"`.
+ *
+ * In a column chart, when pointPlacement is `"on"`, the point will
+ * not create any padding of the X axis. In a polar column chart this
+ * means that the first column points directly north. If the pointPlacement
+ * is `"between"`, the columns will be laid out between ticks. This
+ * is useful for example for visualising an amount between two points
+ * in time or in a certain sector of a polar chart.
+ *
+ * Since Highcharts 3.0.2, the point placement can also be numeric,
+ * where 0 is on the axis value, -0.5 is between this value and the
+ * previous, and 0.5 is between this value and the next. Unlike the
+ * textual options, numeric point placement options won't affect axis
+ * padding.
+ *
+ * Note that pointPlacement needs a [pointRange](#plotOptions.series.
+ * pointRange) to work. For column series this is computed, but for
+ * line-type series it needs to be set.
+ *
+ * Defaults to `null` in cartesian charts, `"between"` in polar charts.
+ *
+ * @validvalue [null, "on", "between"]
+ * @type {String|Number}
+ * @see [xAxis.tickmarkPlacement](#xAxis.tickmarkPlacement)
+ * @sample {highcharts|highstock}
+ * highcharts/plotoptions/series-pointplacement-between/
+ * Between in a column chart
+ * @sample {highcharts|highstock}
+ * highcharts/plotoptions/series-pointplacement-numeric/
+ * Numeric placement for custom layout
+ * @default null
+ * @since 2.3.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.pointPlacement
+ */
+
+ /**
+ * If no x values are given for the points in a series, pointStart defines
+ * on what value to start. For example, if a series contains one yearly
+ * value starting from 1945, set pointStart to 1945.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-pointstart-linear/
+ * Linear
+ * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
+ * Datetime
+ * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
+ * Using pointStart and pointInterval
+ * @default 0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.pointStart
+ */
+
+ /**
+ * Whether to select the series initially. If `showCheckbox` is true,
+ * the checkbox next to the series name in the legend will be checked for a
+ * selected series.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-selected/
+ * One out of two series selected
+ * @default false
+ * @since 1.2.0
+ * @apioption plotOptions.series.selected
+ */
+
+ /**
+ * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
+ * can be an object configuration containing `color`, `offsetX`, `offsetY`,
+ * `opacity` and `width`.
+ *
+ * @type {Boolean|Object}
+ * @sample {highcharts} highcharts/plotoptions/series-shadow/ Shadow enabled
+ * @default false
+ * @apioption plotOptions.series.shadow
+ */
+
+ /**
+ * Whether to display this particular series or series type in the legend.
+ * The default value is `true` for standalone series, `false` for linked
+ * series.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
+ * One series in the legend, one hidden
+ * @default true
+ * @apioption plotOptions.series.showInLegend
+ */
+
+ /**
+ * If set to `True`, the accessibility module will skip past the points
+ * in this series for keyboard navigation.
+ *
+ * @type {Boolean}
+ * @since 5.0.12
+ * @apioption plotOptions.series.skipKeyboardNavigation
+ */
+
+ /**
+ * This option allows grouping series in a stacked chart. The stack
+ * option can be a string or a number or anything else, as long as the
+ * grouped series' stack options match each other.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/series/stack/ Stacked and grouped columns
+ * @default null
+ * @since 2.1
+ * @product highcharts highstock
+ * @apioption series.stack
+ */
+
+ /**
+ * Whether to stack the values of each series on top of each other.
+ * Possible values are `null` to disable, `"normal"` to stack by value or
+ * `"percent"`. When stacking is enabled, data must be sorted in ascending
+ * X order. A special stacking option is with the streamgraph series type,
+ * where the stacking option is set to `"stream"`.
+ *
+ * @validvalue [null, "normal", "percent"]
+ * @type {String}
+ * @see [yAxis.reversedStacks](#yAxis.reversedStacks)
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-line/
+ * Line
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-column/
+ * Column
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-bar/
+ * Bar
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-area/
+ * Area
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-line/
+ * Line
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-stacking-percent-column/
+ * Column
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-bar/
+ * Bar
+ * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-area/
+ * Area
+ * @sample {highstock} stock/plotoptions/stacking/
+ * Area
+ * @default null
+ * @product highcharts highstock
+ * @apioption plotOptions.series.stacking
+ */
+
+ /**
+ * Whether to apply steps to the line. Possible values are `left`, `center`
+ * and `right`.
+ *
+ * @validvalue [null, "left", "center", "right"]
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/line-step/
+ * Different step line options
+ * @sample {highcharts} highcharts/plotoptions/area-step/
+ * Stepped, stacked area
+ * @sample {highstock} stock/plotoptions/line-step/
+ * Step line
+ * @default {highcharts} null
+ * @default {highstock} false
+ * @since 1.2.5
+ * @product highcharts highstock
+ * @apioption plotOptions.series.step
+ */
+
+ /**
+ * The threshold, also called zero level or base level. For line type
+ * series this is only used in conjunction with
+ * [negativeColor](#plotOptions.series.negativeColor).
+ *
+ * @type {Number}
+ * @see [softThreshold](#plotOptions.series.softThreshold).
+ * @default 0
+ * @since 3.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.threshold
+ */
+
+ /**
+ * The type of series, for example `line` or `column`. By default, the
+ * series type is inherited from [chart.type](#chart.type), so unless the
+ * chart is a combination of series types, there is no need to set it on the
+ * series level.
+ *
+ * @validvalue [null, "line", "spline", "column", "area", "areaspline",
+ * "pie", "arearange", "areasplinerange", "boxplot", "bubble",
+ * "columnrange", "errorbar", "funnel", "gauge", "scatter",
+ * "waterfall"]
+ * @type {String}
+ * @sample {highcharts} highcharts/series/type/
+ * Line and column in the same chart
+ * @sample {highmaps} maps/demo/mapline-mappoint/
+ * Multiple types in the same map
+ * @apioption series.type
+ */
+
+ /**
+ * Set the initial visibility of the series.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-visible/
+ * Two series, one hidden and one visible
+ * @sample {highstock} stock/plotoptions/series-visibility/
+ * Hidden series
+ * @default true
+ * @apioption plotOptions.series.visible
+ */
+
+ /**
+ * When using dual or multiple x axes, this number defines which xAxis
+ * the particular series is connected to. It refers to either the [axis
+ * id](#xAxis.id) or the index of the axis in the xAxis array, with
+ * 0 being the first.
+ *
+ * @type {Number|String}
+ * @default 0
+ * @product highcharts highstock
+ * @apioption series.xAxis
+ */
+
+ /**
+ * When using dual or multiple y axes, this number defines which yAxis
+ * the particular series is connected to. It refers to either the [axis
+ * id](#yAxis.id) or the index of the axis in the yAxis array, with
+ * 0 being the first.
+ *
+ * @type {Number|String}
+ * @sample {highcharts} highcharts/series/yaxis/
+ * Apply the column series to the secondary Y axis
+ * @default 0
+ * @product highcharts highstock
+ * @apioption series.yAxis
+ */
+
+ /**
+ * Defines the Axis on which the zones are applied.
+ *
+ * @type {String}
+ * @see [zones](#plotOptions.series.zones)
+ * @sample {highcharts} highcharts/series/color-zones-zoneaxis-x/
+ * Zones on the X-Axis
+ * @sample {highstock} highcharts/series/color-zones-zoneaxis-x/
+ * Zones on the X-Axis
+ * @default y
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zoneAxis
+ */
+
+ /**
+ * Define the visual z index of the series.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-zindex-default/
+ * With no z index, the series defined last are on top
+ * @sample {highcharts} highcharts/plotoptions/series-zindex/
+ * With a z index, the series with the highest z index is on top
+ * @sample {highstock} highcharts/plotoptions/series-zindex-default/
+ * With no z index, the series defined last are on top
+ * @sample {highstock} highcharts/plotoptions/series-zindex/
+ * With a z index, the series with the highest z index is on top
+ * @product highcharts highstock
+ * @apioption series.zIndex
+ */
+
+ /**
+ * General event handlers for the series items. These event hooks can also
+ * be attached to the series at run time using the `Highcharts.addEvent`
+ * function.
+ */
+ events: {
+
+ /**
+ * Fires after the series has finished its initial animation, or in
+ * case animation is disabled, immediately as the series is displayed.
+ *
+ * @type {Function}
+ * @context Series
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-events-afteranimate/
+ * Show label after animate
+ * @sample {highstock}
+ * highcharts/plotoptions/series-events-afteranimate/
+ * Show label after animate
+ * @since 4.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.events.afterAnimate
+ */
+
+ /**
+ * Fires when the checkbox next to the series' name in the legend is
+ * clicked. One parameter, `event`, is passed to the function. The state
+ * of the checkbox is found by `event.checked`. The checked item is
+ * found by `event.item`. Return `false` to prevent the default action
+ * which is to toggle the select state of the series.
+ *
+ * @type {Function}
+ * @context Series
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-events-checkboxclick/
+ * Alert checkbox status
+ * @since 1.2.0
+ * @apioption plotOptions.series.events.checkboxClick
+ */
+
+ /**
+ * Fires when the series is clicked. One parameter, `event`, is passed
+ * to the function, containing common event information. Additionally,
+ * `event.point` holds a pointer to the nearest point on the graph.
+ *
+ * @type {Function}
+ * @context Series
+ * @sample {highcharts} highcharts/plotoptions/series-events-click/
+ * Alert click info
+ * @sample {highstock} stock/plotoptions/series-events-click/
+ * Alert click info
+ * @sample {highmaps} maps/plotoptions/series-events-click/
+ * Display click info in subtitle
+ * @apioption plotOptions.series.events.click
+ */
+
+ /**
+ * Fires when the series is hidden after chart generation time, either
+ * by clicking the legend item or by calling `.hide()`.
+ *
+ * @type {Function}
+ * @context Series
+ * @sample {highcharts} highcharts/plotoptions/series-events-hide/
+ * Alert when the series is hidden by clicking the legend item
+ * @since 1.2.0
+ * @apioption plotOptions.series.events.hide
+ */
+
+ /**
+ * Fires when the legend item belonging to the series is clicked. One
+ * parameter, `event`, is passed to the function. The default action
+ * is to toggle the visibility of the series. This can be prevented
+ * by returning `false` or calling `event.preventDefault()`.
+ *
+ * @type {Function}
+ * @context Series
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-events-legenditemclick/
+ * Confirm hiding and showing
+ * @apioption plotOptions.series.events.legendItemClick
+ */
+
+ /**
+ * Fires when the mouse leaves the graph. One parameter, `event`, is
+ * passed to the function, containing common event information. If the
+ * [stickyTracking](#plotOptions.series) option is true, `mouseOut`
+ * doesn't happen before the mouse enters another graph or leaves the
+ * plot area.
+ *
+ * @type {Function}
+ * @context Series
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-events-mouseover-sticky/
+ * With sticky tracking by default
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-events-mouseover-no-sticky/
+ * Without sticky tracking
+ * @apioption plotOptions.series.events.mouseOut
+ */
+
+ /**
+ * Fires when the mouse enters the graph. One parameter, `event`, is
+ * passed to the function, containing common event information.
+ *
+ * @type {Function}
+ * @context Series
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-events-mouseover-sticky/
+ * With sticky tracking by default
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-events-mouseover-no-sticky/
+ * Without sticky tracking
+ * @apioption plotOptions.series.events.mouseOver
+ */
+
+ /**
+ * Fires when the series is shown after chart generation time, either
+ * by clicking the legend item or by calling `.show()`.
+ *
+ * @type {Function}
+ * @context Series
+ * @sample {highcharts} highcharts/plotoptions/series-events-show/
+ * Alert when the series is shown by clicking the legend item.
+ * @since 1.2.0
+ * @apioption plotOptions.series.events.show
+ */
+
+ },
+
+
+
+ /**
+ * Options for the point markers of line-like series. Properties like
+ * `fillColor`, `lineColor` and `lineWidth` define the visual appearance
+ * of the markers. Other series types, like column series, don't have
+ * markers, but have visual options on the series level instead.
+ *
+ * In styled mode, the markers can be styled with the `.highcharts-point`,
+ * `.highcharts-point-hover` and `.highcharts-point-select`
+ * class names.
+ *
+ * @product highcharts highstock
+ */
+ marker: {
+
+
+
+ /**
+ * The width of the point marker's outline.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
+ * 2px blue marker
+ * @default 0
+ * @product highcharts highstock
+ */
+ lineWidth: 0,
+
+
+ /**
+ * The color of the point marker's outline. When `null`, the series'
+ * or point's color is used.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
+ * Inherit from series color (null)
+ * @product highcharts highstock
+ */
+ lineColor: '#ffffff',
+
+ /**
+ * The fill color of the point marker. When `null`, the series' or
+ * point's color is used.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
+ * White fill
+ * @default null
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.fillColor
+ */
+
+
+
+ /**
+ * Enable or disable the point marker. If `null`, the markers are hidden
+ * when the data is dense, and shown for more widespread data points.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-enabled/
+ * Disabled markers
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-marker-enabled-false/
+ * Disabled in normal state but enabled on hover
+ * @sample {highstock} stock/plotoptions/series-marker/
+ * Enabled markers
+ * @default {highcharts} null
+ * @default {highstock} false
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.enabled
+ */
+
+ /**
+ * Image markers only. Set the image width explicitly. When using this
+ * option, a `width` must also be set.
+ *
+ * @type {Number}
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-marker-width-height/
+ * Fixed width and height
+ * @sample {highstock}
+ * highcharts/plotoptions/series-marker-width-height/
+ * Fixed width and height
+ * @default null
+ * @since 4.0.4
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.height
+ */
+
+ /**
+ * A predefined shape or symbol for the marker. When null, the symbol
+ * is pulled from options.symbols. Other possible values are "circle",
+ * "square", "diamond", "triangle" and "triangle-down".
+ *
+ * Additionally, the URL to a graphic can be given on this form:
+ * "url(graphic.png)". Note that for the image to be applied to exported
+ * charts, its URL needs to be accessible by the export server.
+ *
+ * Custom callbacks for symbol path generation can also be added to
+ * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
+ * used by its method name, as shown in the demo.
+ *
+ * @validvalue [null, "circle", "square", "diamond", "triangle",
+ * "triangle-down"]
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
+ * Predefined, graphic and custom markers
+ * @sample {highstock} highcharts/plotoptions/series-marker-symbol/
+ * Predefined, graphic and custom markers
+ * @default null
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.symbol
+ */
+
+ /**
+ * The radius of the point marker.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-radius/
+ * Bigger markers
+ * @default 4
+ * @product highcharts highstock
+ */
+ radius: 4,
+
+ /**
+ * Image markers only. Set the image width explicitly. When using this
+ * option, a `height` must also be set.
+ *
+ * @type {Number}
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-marker-width-height/
+ * Fixed width and height
+ * @sample {highstock}
+ * highcharts/plotoptions/series-marker-width-height/
+ * Fixed width and height
+ * @default null
+ * @since 4.0.4
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.width
+ */
+
+
+ /**
+ * States for a single point marker.
+ * @product highcharts highstock
+ */
+ states: {
+ /**
+ * The hover state for a single point marker.
+ * @product highcharts highstock
+ */
+ hover: {
+
+ /**
+ * Animation when hovering over the marker.
+ * @type {Boolean|Object}
+ */
+ animation: {
+ duration: 50
+ },
+
+ /**
+ * Enable or disable the point marker.
+ *
+ * @type {Boolean}
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-marker-states-hover-enabled/
+ * Disabled hover state
+ * @default true
+ * @product highcharts highstock
+ */
+ enabled: true,
+
+ /**
+ * The fill color of the marker in hover state.
+ *
+ * @type {Color}
+ * @default null
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.states.hover.fillColor
+ */
+
+ /**
+ * The color of the point marker's outline. When `null`, the
+ * series' or point's color is used.
+ *
+ * @type {Color}
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-marker-states-hover-linecolor/
+ * White fill color, black line color
+ * @default #ffffff
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.states.hover.lineColor
+ */
+
+ /**
+ * The width of the point marker's outline.
+ *
+ * @type {Number}
+ * @sample {highcharts}
+ * highcharts/plotoptions/series-marker-states-hover-linewidth/
+ * 3px line width
+ * @default 0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.states.hover.lineWidth
+ */
+
+ /**
+ * The radius of the point marker. In hover state, it defaults to the
+ * normal state's radius + 2 as per the [radiusPlus](#plotOptions.series.
+ * marker.states.hover.radiusPlus) option.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/ 10px radius
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.states.hover.radius
+ */
+
+ /**
+ * The number of pixels to increase the radius of the hovered point.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
+ * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
+ * @default 2
+ * @since 4.0.3
+ * @product highcharts highstock
+ */
+ radiusPlus: 2,
+
+
+
+ /**
+ * The additional line width for a hovered point.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 2 pixels wider on hover
+ * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 2 pixels wider on hover
+ * @default 1
+ * @since 4.0.3
+ * @product highcharts highstock
+ */
+ lineWidthPlus: 1
+
+ },
+
+
+
+
+ /**
+ * The appearance of the point marker when selected. In order to
+ * allow a point to be selected, set the `series.allowPointSelect`
+ * option to true.
+ *
+ * @product highcharts highstock
+ */
+ select: {
+
+ /**
+ * Enable or disable visible feedback for selection.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/
+ * Disabled select state
+ * @default true
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.states.select.enabled
+ */
+
+ /**
+ * The fill color of the point marker.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/
+ * Solid red discs for selected points
+ * @default null
+ * @product highcharts highstock
+ */
+ fillColor: '#cccccc',
+
+
+
+ /**
+ * The color of the point marker's outline. When `null`, the series'
+ * or point's color is used.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/
+ * Red line color for selected points
+ * @default #000000
+ * @product highcharts highstock
+ */
+ lineColor: '#000000',
+
+
+
+ /**
+ * The width of the point marker's outline.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/
+ * 3px line width for selected points
+ * @default 0
+ * @product highcharts highstock
+ */
+ lineWidth: 2
+
+ /**
+ * The radius of the point marker. In hover state, it defaults to the
+ * normal state's radius + 2.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/
+ * 10px radius for selected points
+ * @product highcharts highstock
+ * @apioption plotOptions.series.marker.states.select.radius
+ */
+
+ }
+
+ }
+ },
+
+
+
+ /**
+ * Properties for each single point.
+ */
+ point: {
+
+
+ /**
+ * Events for each single point.
+ */
+ events: {
+
+ /**
+ * Fires when a point is clicked. One parameter, `event`, is passed
+ * to the function, containing common event information.
+ *
+ * If the `series.allowPointSelect` option is true, the default action
+ * for the point's click event is to toggle the point's select state.
+ * Returning `false` cancels this action.
+ *
+ * @type {Function}
+ * @context Point
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-click/ Click marker to alert values
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-click-column/ Click column
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-click-url/ Go to URL
+ * @sample {highmaps} maps/plotoptions/series-point-events-click/ Click marker to display values
+ * @sample {highmaps} maps/plotoptions/series-point-events-click-url/ Go to URL
+ * @apioption plotOptions.series.point.events.click
+ */
+
+ /**
+ * Fires when the mouse leaves the area close to the point. One parameter,
+ * `event`, is passed to the function, containing common event information.
+ *
+ * @type {Function}
+ * @context Point
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
+ * @apioption plotOptions.series.point.events.mouseOut
+ */
+
+ /**
+ * Fires when the mouse enters the area close to the point. One parameter,
+ * `event`, is passed to the function, containing common event information.
+ *
+ * @type {Function}
+ * @context Point
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
+ * @apioption plotOptions.series.point.events.mouseOver
+ */
+
+ /**
+ * Fires when the point is removed using the `.remove()` method. One
+ * parameter, `event`, is passed to the function. Returning `false`
+ * cancels the operation.
+ *
+ * @type {Function}
+ * @context Point
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/ Remove point and confirm
+ * @since 1.2.0
+ * @apioption plotOptions.series.point.events.remove
+ */
+
+ /**
+ * Fires when the point is selected either programmatically or following
+ * a click on the point. One parameter, `event`, is passed to the function.
+ * Returning `false` cancels the operation.
+ *
+ * @type {Function}
+ * @context Point
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-select/ Report the last selected point
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
+ * @since 1.2.0
+ * @apioption plotOptions.series.point.events.select
+ */
+
+ /**
+ * Fires when the point is unselected either programmatically or following
+ * a click on the point. One parameter, `event`, is passed to the function.
+ * Returning `false` cancels the operation.
+ *
+ * @type {Function}
+ * @context Point
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/ Report the last unselected point
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
+ * @since 1.2.0
+ * @apioption plotOptions.series.point.events.unselect
+ */
+
+ /**
+ * Fires when the point is updated programmatically through the `.update()`
+ * method. One parameter, `event`, is passed to the function. The new
+ * point options can be accessed through `event.options`. Returning
+ * `false` cancels the operation.
+ *
+ * @type {Function}
+ * @context Point
+ * @sample {highcharts} highcharts/plotoptions/series-point-events-update/ Confirm point updating
+ * @since 1.2.0
+ * @apioption plotOptions.series.point.events.update
+ */
+
+ }
+ },
+
+
+
+ /**
+ * Options for the series data labels, appearing next to each data
+ * point.
+ *
+ * In styled mode, the data labels can be styled wtih the `.highcharts-data-label-box` and `.highcharts-data-label` class names ([see example](http://jsfiddle.
+ * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
+ * datalabels)).
+ */
+ dataLabels: {
+
+
+ /**
+ * The alignment of the data label compared to the point. If `right`,
+ * the right side of the label should be touching the point. For
+ * points with an extent, like columns, the alignments also dictates
+ * how to align it inside the box, as given with the [inside](#plotOptions.
+ * column.dataLabels.inside) option. Can be one of "left", "center"
+ * or "right".
+ *
+ * @validvalue ["left", "center", "right"]
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/ Left aligned
+ * @default center
+ */
+ align: 'center',
+
+
+ /**
+ * Whether to allow data labels to overlap. To make the labels less
+ * sensitive for overlapping, the [dataLabels.padding](#plotOptions.
+ * series.dataLabels.padding) can be set to 0.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
+ * @sample {highmaps} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
+ * @default false
+ * @since 4.1.0
+ * @apioption plotOptions.series.dataLabels.allowOverlap
+ */
+
+
+ /**
+ * The border radius in pixels for the data label.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
+ * @default 0
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.borderRadius
+ */
+
+
+ /**
+ * The border width in pixels for the data label.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @default 0
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.borderWidth
+ */
+
+ /**
+ * A class name for the data label. Particularly in styled mode, this can
+ * be used to give each series' or point's data label unique styling.
+ * In addition to this option, a default color class name is added
+ * so that we can give the labels a [contrast text shadow](http://jsfiddle.
+ * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/data-
+ * label-contrast/).
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/css/series-datalabels/ Styling by CSS
+ * @sample {highstock} highcharts/css/series-datalabels/ Styling by CSS
+ * @sample {highmaps} highcharts/css/series-datalabels/ Styling by CSS
+ * @since 5.0.0
+ * @apioption plotOptions.series.dataLabels.className
+ */
+
+ /**
+ * The text color for the data labels. Defaults to `null`. For certain series
+ * types, like column or map, the data labels can be drawn inside the points.
+ * In this case the data label will be drawn with maximum contrast by default.
+ * Additionally, it will be given a `text-outline` style with the opposite
+ * color, to further increase the contrast. This can be overridden by setting
+ * the `text-outline` style to `none` in the `dataLabels.style` option.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/
+ * Red data labels
+ * @sample {highmaps} maps/demo/color-axis/
+ * White data labels
+ * @apioption plotOptions.series.dataLabels.color
+ */
+
+ /**
+ * Whether to hide data labels that are outside the plot area. By default,
+ * the data label is moved inside the plot area according to the [overflow](#plotOptions.
+ * series.dataLabels.overflow) option.
+ *
+ * @type {Boolean}
+ * @default true
+ * @since 2.3.3
+ * @apioption plotOptions.series.dataLabels.crop
+ */
+
+ /**
+ * Whether to defer displaying the data labels until the initial series
+ * animation has finished.
+ *
+ * @type {Boolean}
+ * @default true
+ * @since 4.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.dataLabels.defer
+ */
+
+ /**
+ * Enable or disable the data labels.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/ Data labels enabled
+ * @sample {highmaps} maps/demo/color-axis/ Data labels enabled
+ * @default false
+ * @apioption plotOptions.series.dataLabels.enabled
+ */
+
+ /**
+ * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
+ * and-string-formatting) for the data label. Available variables are
+ * the same as for `formatter`.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ Add a unit
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-format/ Add a unit
+ * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value in the data label
+ * @default {highcharts} {y}
+ * @default {highstock} {y}
+ * @default {highmaps} {point.value}
+ * @since 3.0
+ * @apioption plotOptions.series.dataLabels.format
+ */
+
+ /**
+ * Callback JavaScript function to format the data label. Note that
+ * if a `format` is defined, the format takes precedence and the formatter
+ * is ignored. Available data are:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * `this.percentage`
+ *
+ * Stacked series and pies only. The point's percentage of the
+ * total.
+ *
+ *
+ *
+ *
+ *
+ * `this.point`
+ *
+ * The point object. The point name, if defined, is available
+ * through `this.point.name`.
+ *
+ *
+ *
+ *
+ *
+ * `this.series`:
+ *
+ * The series object. The series name is available through `this.
+ * series.name`.
+ *
+ *
+ *
+ *
+ *
+ * `this.total`
+ *
+ * Stacked series only. The total value at this point's x value.
+ *
+ *
+ *
+ *
+ *
+ *
+ * `this.x`:
+ *
+ * The x value.
+ *
+ *
+ *
+ *
+ *
+ * `this.y`:
+ *
+ * The y value.
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * @type {Function}
+ * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value
+ */
+ formatter: function () {
+ return this.y === null ? '' : H.numberFormat(this.y, -1);
+ },
+
+
+
+ /**
+ * Styles for the label. The default `color` setting is `"contrast"`,
+ * which is a pseudo color that Highcharts picks up and applies the
+ * maximum contrast to the underlying point item, for example the
+ * bar in a bar chart.
+ *
+ * The `textOutline` is a pseudo property that
+ * applies an outline of the given width with the given color, which
+ * by default is the maximum contrast to the text. So a bright text
+ * color will result in a black text outline for maximum readability
+ * on a mixed background. In some cases, especially with grayscale
+ * text, the text outline doesn't work well, in which cases it can
+ * be disabled by setting it to `"none"`. When `useHTML` is true, the
+ * `textOutline` will not be picked up. In this, case, the same effect
+ * can be acheived through the `text-shadow` CSS property.
+ *
+ * @type {CSSObject}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/
+ * Bold labels
+ * @sample {highmaps} maps/demo/color-axis/ Bold labels
+ * @default {"color": "contrast", "fontSize": "11px", "fontWeight": "bold", "textOutline": "1px contrast" }
+ * @since 4.1.0
+ */
+ style: {
+ fontSize: '11px',
+ fontWeight: 'bold',
+ color: 'contrast',
+ textOutline: '1px contrast'
+ },
+
+ /**
+ * The background color or gradient for the data label.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.backgroundColor
+ */
+
+ /**
+ * The border color for the data label. Defaults to `undefined`.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @default undefined
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.borderColor
+ */
+
+ /**
+ * The shadow of the box. Works best with `borderWidth` or `backgroundColor`.
+ * Since 2.3 the shadow can be an object configuration containing `color`,
+ * `offsetX`, `offsetY`, `opacity` and `width`.
+ *
+ * @type {Boolean|Object}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @default false
+ * @since 2.2.1
+ * @apioption plotOptions.series.dataLabels.shadow
+ */
+
+
+ /**
+ * For points with an extent, like columns or map areas, whether to align the data
+ * label inside the box or to the actual value point. Defaults to `false`
+ * in most cases, `true` in stacked columns.
+ *
+ * @type {Boolean}
+ * @since 3.0
+ * @apioption plotOptions.series.dataLabels.inside
+ */
+
+ /**
+ * How to handle data labels that flow outside the plot area. The default
+ * is `justify`, which aligns them inside the plot area. For columns
+ * and bars, this means it will be moved inside the bar. To display
+ * data labels outside the plot area, set `crop` to `false` and `overflow`
+ * to `"none"`.
+ *
+ * @validvalue ["justify", "none"]
+ * @type {String}
+ * @default justify
+ * @since 3.0.6
+ * @apioption plotOptions.series.dataLabels.overflow
+ */
+
+ /**
+ * Text rotation in degrees. Note that due to a more complex structure,
+ * backgrounds, borders and padding will be lost on a rotated data
+ * label.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical labels
+ * @default 0
+ * @apioption plotOptions.series.dataLabels.rotation
+ */
+
+ /**
+ * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
+ * and-string-formatting#html) to render the labels.
+ *
+ * @type {Boolean}
+ * @default false
+ * @apioption plotOptions.series.dataLabels.useHTML
+ */
+
+ /**
+ * The vertical alignment of a data label. Can be one of `top`, `middle`
+ * or `bottom`. The default value depends on the data, for instance
+ * in a column chart, the label is above positive values and below
+ * negative values.
+ *
+ * @validvalue ["top", "middle", "bottom"]
+ * @type {String}
+ * @since 2.3.3
+ */
+ verticalAlign: 'bottom', // above singular point
+
+
+ /**
+ * The x position offset of the label relative to the point.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
+ * @default 0
+ */
+ x: 0,
+
+
+ /**
+ * The y position offset of the label relative to the point.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
+ * @default -6
+ */
+ y: 0,
+
+
+ /**
+ * When either the `borderWidth` or the `backgroundColor` is set,
+ * this is the padding within the box.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
+ * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
+ * @default {highcharts} 5
+ * @default {highstock} 5
+ * @default {highmaps} 0
+ * @since 2.2.1
+ */
+ padding: 5
+
+ /**
+ * The name of a symbol to use for the border around the label. Symbols
+ * are predefined functions on the Renderer object.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
+ * @sample {highstock} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
+ * @sample {highmaps} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations (Highcharts demo)
+ * @default square
+ * @since 4.1.2
+ * @apioption plotOptions.series.dataLabels.shape
+ */
+
+ /**
+ * The Z index of the data labels. The default Z index puts it above
+ * the series. Use a Z index of 2 to display it behind the series.
+ *
+ * @type {Number}
+ * @default 6
+ * @since 2.3.5
+ * @apioption plotOptions.series.dataLabels.zIndex
+ */
+
+ /**
+ * A declarative filter for which data labels to display. The
+ * declarative filter is designed for use when callback functions are
+ * not available, like when the chart options require a pure JSON
+ * structure or for use with graphical editors. For programmatic
+ * control, use the `formatter` instead, and return `false` to disable
+ * a single data label.
+ *
+ * @example
+ * filter: {
+ * property: 'percentage',
+ * operator: '>',
+ * value: 4
+ * }
+ *
+ * @sample highcharts/demo/pie-monochrome
+ * Data labels filtered by percentage
+ *
+ * @type {Object}
+ * @since 6.0.3
+ * @apioption plotOptions.series.dataLabels.filter
+ */
+
+ /**
+ * The point property to filter by. Point options are passed directly to
+ * properties, additionally there are `y` value, `percentage` and others
+ * listed under [Point](https://api.highcharts.com/class-reference/Highcharts.Point)
+ * members.
+ *
+ * @type {String}
+ * @apioption plotOptions.series.dataLabels.filter.property
+ */
+
+ /**
+ * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`, `==`,
+ * and `===`.
+ *
+ * @type {String}
+ * @validvalue [">", "<", ">=", "<=", "==", "===""]
+ * @apioption plotOptions.series.dataLabels.filter.operator
+ */
+
+ /**
+ * The value to compare against.
+ *
+ * @type {Mixed}
+ * @apioption plotOptions.series.dataLabels.filter.value
+ */
+ },
+ // draw points outside the plot area when the number of points is less than
+ // this
+
+
+
+ /**
+ * When the series contains less points than the crop threshold, all
+ * points are drawn, even if the points fall outside the visible plot
+ * area at the current zoom. The advantage of drawing all points (including
+ * markers and columns), is that animation is performed on updates.
+ * On the other hand, when the series contains more points than the
+ * crop threshold, the series data is cropped to only contain points
+ * that fall within the plot area. The advantage of cropping away invisible
+ * points is to increase performance on large series.
+ *
+ * @type {Number}
+ * @default 300
+ * @since 2.2
+ * @product highcharts highstock
+ */
+ cropThreshold: 300,
+
+
+
+ /**
+ * The width of each point on the x axis. For example in a column chart
+ * with one value each day, the pointRange would be 1 day (= 24 * 3600
+ * * 1000 milliseconds). This is normally computed automatically, but
+ * this option can be used to override the automatic value.
+ *
+ * @type {Number}
+ * @default 0
+ * @product highstock
+ */
+ pointRange: 0,
+
+ /**
+ * When this is true, the series will not cause the Y axis to cross
+ * the zero plane (or [threshold](#plotOptions.series.threshold) option)
+ * unless the data actually crosses the plane.
+ *
+ * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
+ * 3 will make the Y axis show negative values according to the `minPadding`
+ * option. If `softThreshold` is `true`, the Y axis starts at 0.
+ *
+ * @type {Boolean}
+ * @default true
+ * @since 4.1.9
+ * @product highcharts highstock
+ */
+ softThreshold: true,
+
+
+
+ /**
+ * A wrapper object for all the series options in specific states.
+ *
+ * @type {plotOptions.series.states}
+ */
+ states: {
+
+
+ /**
+ * Options for the hovered series. These settings override the normal
+ * state options when a series is moused over or touched.
+ *
+ */
+ hover: {
+
+ /**
+ * Enable separate styles for the hovered series to visualize that the
+ * user hovers either the series itself or the legend. .
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled/ Line
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-column/ Column
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-pie/ Pie
+ * @default true
+ * @since 1.2
+ * @apioption plotOptions.series.states.hover.enabled
+ */
+
+
+ /**
+ * Animation setting for hovering the graph in line-type series.
+ *
+ * @type {Boolean|Object}
+ * @default { "duration": 50 }
+ * @since 5.0.8
+ * @product highcharts
+ */
+ animation: {
+ /**
+ * The duration of the hover animation in milliseconds. By
+ * default the hover state animates quickly in, and slowly back
+ * to normal.
+ */
+ duration: 50
+ },
+
+ /**
+ * Pixel with of the graph line. By default this property is
+ * undefined, and the `lineWidthPlus` property dictates how much
+ * to increase the linewidth from normal state.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/
+ * 5px line on hover
+ * @default undefined
+ * @product highcharts highstock
+ * @apioption plotOptions.series.states.hover.lineWidth
+ */
+
+
+ /**
+ * The additional line width for the graph of a hovered series.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
+ * 5 pixels wider
+ * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
+ * 5 pixels wider
+ * @default 1
+ * @since 4.0.3
+ * @product highcharts highstock
+ */
+ lineWidthPlus: 1,
+
+
+
+ /**
+ * In Highcharts 1.0, the appearance of all markers belonging to
+ * the hovered series. For settings on the hover state of the individual
+ * point, see [marker.states.hover](#plotOptions.series.marker.states.
+ * hover).
+ *
+ * @extends plotOptions.series.marker
+ * @deprecated
+ * @product highcharts highstock
+ */
+ marker: {
+ // lineWidth: base + 1,
+ // radius: base + 1
+ },
+
+
+
+ /**
+ * Options for the halo appearing around the hovered point in line-
+ * type series as well as outside the hovered slice in pie charts.
+ * By default the halo is filled by the current point or series
+ * color with an opacity of 0.25\. The halo can be disabled by setting
+ * the `halo` option to `false`.
+ *
+ * In styled mode, the halo is styled with the `.highcharts-halo` class, with colors inherited from `.highcharts-color-{n}`.
+ *
+ * @type {Object}
+ * @sample {highcharts} highcharts/plotoptions/halo/ Halo options
+ * @sample {highstock} highcharts/plotoptions/halo/ Halo options
+ * @since 4.0
+ * @product highcharts highstock
+ */
+ halo: {
+
+ /**
+ * A collection of SVG attributes to override the appearance of the
+ * halo, for example `fill`, `stroke` and `stroke-width`.
+ *
+ * @type {Object}
+ * @since 4.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.states.hover.halo.attributes
+ */
+
+
+ /**
+ * The pixel size of the halo. For point markers this is the radius
+ * of the halo. For pie slices it is the width of the halo outside
+ * the slice. For bubbles it defaults to 5 and is the width of the
+ * halo outside the bubble.
+ *
+ * @type {Number}
+ * @default 10
+ * @since 4.0
+ * @product highcharts highstock
+ */
+ size: 10,
+
+
+
+
+ /**
+ * Opacity for the halo unless a specific fill is overridden using
+ * the `attributes` setting. Note that Highcharts is only able to
+ * apply opacity to colors of hex or rgb(a) formats.
+ *
+ * @type {Number}
+ * @default 0.25
+ * @since 4.0
+ * @product highcharts highstock
+ */
+ opacity: 0.25
+
+ }
+ },
+
+
+ /**
+ * Specific options for point in selected states, after being selected
+ * by [allowPointSelect](#plotOptions.series.allowPointSelect) or
+ * programmatically.
+ *
+ * @type {Object}
+ * @extends plotOptions.series.states.hover
+ * @excluding brightness
+ * @sample {highmaps} maps/plotoptions/series-allowpointselect/
+ * Allow point select demo
+ * @product highmaps
+ */
+ select: {
+ marker: {}
+ }
+ },
+
+
+
+ /**
+ * Sticky tracking of mouse events. When true, the `mouseOut` event
+ * on a series isn't triggered until the mouse moves over another series,
+ * or out of the plot area. When false, the `mouseOut` event on a
+ * series is triggered when the mouse leaves the area around the series'
+ * graph or markers. This also implies the tooltip when not shared. When
+ * `stickyTracking` is false and `tooltip.shared` is false, the tooltip will
+ * be hidden when moving the mouse between series. Defaults to true for line
+ * and area type series, but to false for columns, pies etc.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-stickytracking-true/
+ * True by default
+ * @sample {highcharts} highcharts/plotoptions/series-stickytracking-false/
+ * False
+ * @default {highcharts} true
+ * @default {highstock} true
+ * @default {highmaps} false
+ * @since 2.0
+ */
+ stickyTracking: true,
+
+ /**
+ * A configuration object for the tooltip rendering of each single series.
+ * Properties are inherited from [tooltip](#tooltip), but only the
+ * following properties can be defined on a series level.
+ *
+ * @type {Object}
+ * @extends tooltip
+ * @excluding animation,backgroundColor,borderColor,borderRadius,borderWidth,crosshairs,enabled,formatter,positioner,shadow,shared,shape,snap,style,useHTML
+ * @since 2.3
+ * @apioption plotOptions.series.tooltip
+ */
+
+ /**
+ * When a series contains a data array that is longer than this, only
+ * one dimensional arrays of numbers, or two dimensional arrays with
+ * x and y values are allowed. Also, only the first point is tested,
+ * and the rest are assumed to be the same format. This saves expensive
+ * data checking and indexing in long series. Set it to `0` disable.
+ *
+ * @type {Number}
+ * @default 1000
+ * @since 2.2
+ * @product highcharts highstock
+ */
+ turboThreshold: 1000,
+
+ /**
+ * An array defining zones within a series. Zones can be applied to
+ * the X axis, Y axis or Z axis for bubbles, according to the `zoneAxis`
+ * option.
+ *
+ * In styled mode, the color zones are styled with the `.highcharts-
+ * zone-{n}` class, or custom classed from the `className` option ([view
+ * live demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-
+ * zones/)).
+ *
+ * @type {Array}
+ * @see [zoneAxis](#plotOptions.series.zoneAxis)
+ * @sample {highcharts} highcharts/series/color-zones-simple/ Color zones
+ * @sample {highstock} highcharts/series/color-zones-simple/ Color zones
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones
+ */
+
+ /**
+ * Styled mode only. A custom class name for the zone.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/css/color-zones/ Zones styled by class name
+ * @sample {highstock} highcharts/css/color-zones/ Zones styled by class name
+ * @sample {highmaps} highcharts/css/color-zones/ Zones styled by class name
+ * @since 5.0.0
+ * @apioption plotOptions.series.zones.className
+ */
+
+ /**
+ * Defines the color of the series.
+ *
+ * @type {Color}
+ * @see [series color](#plotOptions.series.color)
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones.color
+ */
+
+ /**
+ * A name for the dash style to use for the graph.
+ *
+ * @type {String}
+ * @see [series.dashStyle](#plotOptions.series.dashStyle)
+ * @sample {highcharts} highcharts/series/color-zones-dashstyle-dot/
+ * Dashed line indicates prognosis
+ * @sample {highstock} highcharts/series/color-zones-dashstyle-dot/
+ * Dashed line indicates prognosis
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones.dashStyle
+ */
+
+ /**
+ * Defines the fill color for the series (in area type series)
+ *
+ * @type {Color}
+ * @see [fillColor](#plotOptions.area.fillColor)
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones.fillColor
+ */
+
+ /**
+ * The value up to where the zone extends, if undefined the zones stretches
+ * to the last value in the series.
+ *
+ * @type {Number}
+ * @default undefined
+ * @since 4.1.0
+ * @product highcharts highstock
+ * @apioption plotOptions.series.zones.value
+ */
+
+
+
+ /**
+ * Determines whether the series should look for the nearest point
+ * in both dimensions or just the x-dimension when hovering the series.
+ * Defaults to `'xy'` for scatter series and `'x'` for most other
+ * series. If the data has duplicate x-values, it is recommended to
+ * set this to `'xy'` to allow hovering over all points.
+ *
+ * Applies only to series types using nearest neighbor search (not
+ * direct hover) for tooltip.
+ *
+ * @validvalue ['x', 'xy']
+ * @type {String}
+ * @sample {highcharts} highcharts/series/findnearestpointby/
+ * Different hover behaviors
+ * @sample {highstock} highcharts/series/findnearestpointby/
+ * Different hover behaviors
+ * @sample {highmaps} highcharts/series/findnearestpointby/
+ * Different hover behaviors
+ * @since 5.0.10
+ */
+ findNearestPointBy: 'x'
+
+}, /** @lends Highcharts.Series.prototype */ {
+ isCartesian: true,
+ pointClass: Point,
+ sorted: true, // requires the data to be sorted
+ requireSorting: true,
+ directTouch: false,
+ axisTypes: ['xAxis', 'yAxis'],
+ colorCounter: 0,
+ // each point's x and y values are stored in this.xData and this.yData
+ parallelArrays: ['x', 'y'],
+ coll: 'series',
+ init: function (chart, options) {
+ var series = this,
+ events,
+ chartSeries = chart.series,
+ lastSeries;
+
+ /**
+ * Read only. The chart that the series belongs to.
+ *
+ * @name chart
+ * @memberOf Series
+ * @type {Chart}
+ */
+ series.chart = chart;
+
+ /**
+ * Read only. The series' type, like "line", "area", "column" etc. The
+ * type in the series options anc can be altered using {@link
+ * Series#update}.
+ *
+ * @name type
+ * @memberOf Series
+ * @type String
+ */
+
+ /**
+ * Read only. The series' current options. To update, use {@link
+ * Series#update}.
+ *
+ * @name options
+ * @memberOf Series
+ * @type SeriesOptions
+ */
+ series.options = options = series.setOptions(options);
+ series.linkedSeries = [];
+
+ // bind the axes
+ series.bindAxes();
+
+ // set some variables
+ extend(series, {
+ /**
+ * The series name as given in the options. Defaults to
+ * "Series {n}".
+ *
+ * @name name
+ * @memberOf Series
+ * @type {String}
+ */
+ name: options.name,
+ state: '',
+ /**
+ * Read only. The series' visibility state as set by {@link
+ * Series#show}, {@link Series#hide}, or in the initial
+ * configuration.
+ *
+ * @name visible
+ * @memberOf Series
+ * @type {Boolean}
+ */
+ visible: options.visible !== false, // true by default
+ /**
+ * Read only. The series' selected state as set by {@link
+ * Highcharts.Series#select}.
+ *
+ * @name selected
+ * @memberOf Series
+ * @type {Boolean}
+ */
+ selected: options.selected === true // false by default
+ });
+
+ // register event listeners
+ events = options.events;
+
+ objectEach(events, function (event, eventType) {
+ addEvent(series, eventType, event);
+ });
+ if (
+ (events && events.click) ||
+ (
+ options.point &&
+ options.point.events &&
+ options.point.events.click
+ ) ||
+ options.allowPointSelect
+ ) {
+ chart.runTrackerClick = true;
+ }
+
+ series.getColor();
+ series.getSymbol();
+
+ // Set the data
+ each(series.parallelArrays, function (key) {
+ series[key + 'Data'] = [];
+ });
+ series.setData(options.data, false);
+
+ // Mark cartesian
+ if (series.isCartesian) {
+ chart.hasCartesianSeries = true;
+ }
+
+ // Get the index and register the series in the chart. The index is one
+ // more than the current latest series index (#5960).
+ if (chartSeries.length) {
+ lastSeries = chartSeries[chartSeries.length - 1];
+ }
+ series._i = pick(lastSeries && lastSeries._i, -1) + 1;
+
+ // Insert the series and re-order all series above the insertion point.
+ chart.orderSeries(this.insert(chartSeries));
+ },
+
+ /**
+ * Insert the series in a collection with other series, either the chart
+ * series or yAxis series, in the correct order according to the index
+ * option. Used internally when adding series.
+ *
+ * @private
+ * @param {Array.} collection
+ * A collection of series, like `chart.series` or `xAxis.series`.
+ * @returns {Number} The index of the series in the collection.
+ */
+ insert: function (collection) {
+ var indexOption = this.options.index,
+ i;
+
+ // Insert by index option
+ if (isNumber(indexOption)) {
+ i = collection.length;
+ while (i--) {
+ // Loop down until the interted element has higher index
+ if (indexOption >=
+ pick(collection[i].options.index, collection[i]._i)) {
+ collection.splice(i + 1, 0, this);
+ break;
+ }
+ }
+ if (i === -1) {
+ collection.unshift(this);
+ }
+ i = i + 1;
+
+ // Or just push it to the end
+ } else {
+ collection.push(this);
+ }
+ return pick(i, collection.length - 1);
+ },
+
+ /**
+ * Set the xAxis and yAxis properties of cartesian series, and register the
+ * series in the `axis.series` array.
+ *
+ * @private
+ */
+ bindAxes: function () {
+ var series = this,
+ seriesOptions = series.options,
+ chart = series.chart,
+ axisOptions;
+
+ // repeat for xAxis and yAxis
+ each(series.axisTypes || [], function (AXIS) {
+
+ // loop through the chart's axis objects
+ each(chart[AXIS], function (axis) {
+ axisOptions = axis.options;
+
+ // apply if the series xAxis or yAxis option mathches the number
+ // of the axis, or if undefined, use the first axis
+ if (
+ seriesOptions[AXIS] === axisOptions.index ||
+ (
+ seriesOptions[AXIS] !== undefined &&
+ seriesOptions[AXIS] === axisOptions.id
+ ) ||
+ (
+ seriesOptions[AXIS] === undefined &&
+ axisOptions.index === 0
+ )
+ ) {
+
+ // register this series in the axis.series lookup
+ series.insert(axis.series);
+
+ // set this series.xAxis or series.yAxis reference
+ /**
+ * Read only. The unique xAxis object associated with the
+ * series.
+ *
+ * @name xAxis
+ * @memberOf Series
+ * @type Axis
+ */
+ /**
+ * Read only. The unique yAxis object associated with the
+ * series.
+ *
+ * @name yAxis
+ * @memberOf Series
+ * @type Axis
+ */
+ series[AXIS] = axis;
+
+ // mark dirty for redraw
+ axis.isDirty = true;
+ }
+ });
+
+ // The series needs an X and an Y axis
+ if (!series[AXIS] && series.optionalAxis !== AXIS) {
+ H.error(18, true);
+ }
+
+ });
+ },
+
+ /**
+ * For simple series types like line and column, the data values are held in
+ * arrays like xData and yData for quick lookup to find extremes and more.
+ * For multidimensional series like bubble and map, this can be extended
+ * with arrays like zData and valueData by adding to the
+ * `series.parallelArrays` array.
+ *
+ * @private
+ */
+ updateParallelArrays: function (point, i) {
+ var series = point.series,
+ args = arguments,
+ fn = isNumber(i) ?
+ // Insert the value in the given position
+ function (key) {
+ var val = key === 'y' && series.toYData ?
+ series.toYData(point) :
+ point[key];
+ series[key + 'Data'][i] = val;
+ } :
+ // Apply the method specified in i with the following arguments
+ // as arguments
+ function (key) {
+ Array.prototype[i].apply(
+ series[key + 'Data'],
+ Array.prototype.slice.call(args, 2)
+ );
+ };
+
+ each(series.parallelArrays, fn);
+ },
+
+ /**
+ * Return an auto incremented x value based on the pointStart and
+ * pointInterval options. This is only used if an x value is not given for
+ * the point that calls autoIncrement.
+ *
+ * @private
+ */
+ autoIncrement: function () {
+
+ var options = this.options,
+ xIncrement = this.xIncrement,
+ date,
+ pointInterval,
+ pointIntervalUnit = options.pointIntervalUnit,
+ dstCrossover = 0,
+ time = this.chart.time;
+
+ xIncrement = pick(xIncrement, options.pointStart, 0);
+
+ this.pointInterval = pointInterval = pick(
+ this.pointInterval,
+ options.pointInterval,
+ 1
+ );
+
+ // Added code for pointInterval strings
+ if (pointIntervalUnit) {
+ date = new time.Date(xIncrement);
+
+ if (pointIntervalUnit === 'day') {
+ date = +date[time.setDate](
+ date[time.getDate]() + pointInterval
+ );
+ } else if (pointIntervalUnit === 'month') {
+ date = +date[time.setMonth](
+ date[time.getMonth]() + pointInterval
+ );
+ } else if (pointIntervalUnit === 'year') {
+ date = +date[time.setFullYear](
+ date[time.getFullYear]() + pointInterval
+ );
+ }
+
+ if (time.variableTimezone) {
+ dstCrossover = (
+ time.getTimezoneOffset(date) -
+ time.getTimezoneOffset(xIncrement)
+ );
+ }
+ pointInterval = date - xIncrement + dstCrossover;
+
+ }
+
+ this.xIncrement = xIncrement + pointInterval;
+ return xIncrement;
+ },
+
+ /**
+ * Set the series options by merging from the options tree. Called
+ * internally on initiating and updating series. This function will not
+ * redraw the series. For API usage, use {@link Series#update}.
+ *
+ * @param {Options.plotOptions.series} itemOptions
+ * The series options.
+ */
+ setOptions: function (itemOptions) {
+ var chart = this.chart,
+ chartOptions = chart.options,
+ plotOptions = chartOptions.plotOptions,
+ userOptions = chart.userOptions || {},
+ userPlotOptions = userOptions.plotOptions || {},
+ typeOptions = plotOptions[this.type],
+ options,
+ zones;
+
+ this.userOptions = itemOptions;
+
+ // General series options take precedence over type options because
+ // otherwise, default type options like column.animation would be
+ // overwritten by the general option. But issues have been raised here
+ // (#3881), and the solution may be to distinguish between default
+ // option and userOptions like in the tooltip below.
+ options = merge(
+ typeOptions,
+ plotOptions.series,
+ itemOptions
+ );
+
+ // The tooltip options are merged between global and series specific
+ // options. Importance order asscendingly:
+ // globals: (1)tooltip, (2)plotOptions.series, (3)plotOptions[this.type]
+ // init userOptions with possible later updates: 4-6 like 1-3 and
+ // (7)this series options
+ this.tooltipOptions = merge(
+ defaultOptions.tooltip, // 1
+ defaultOptions.plotOptions.series &&
+ defaultOptions.plotOptions.series.tooltip, // 2
+ defaultOptions.plotOptions[this.type].tooltip, // 3
+ chartOptions.tooltip.userOptions, // 4
+ plotOptions.series && plotOptions.series.tooltip, // 5
+ plotOptions[this.type].tooltip, // 6
+ itemOptions.tooltip // 7
+ );
+
+ // When shared tooltip, stickyTracking is true by default,
+ // unless user says otherwise.
+ this.stickyTracking = pick(
+ itemOptions.stickyTracking,
+ userPlotOptions[this.type] &&
+ userPlotOptions[this.type].stickyTracking,
+ userPlotOptions.series && userPlotOptions.series.stickyTracking,
+ (
+ this.tooltipOptions.shared && !this.noSharedTooltip ?
+ true :
+ options.stickyTracking
+ )
+ );
+
+ // Delete marker object if not allowed (#1125)
+ if (typeOptions.marker === null) {
+ delete options.marker;
+ }
+
+ // Handle color zones
+ this.zoneAxis = options.zoneAxis;
+ zones = this.zones = (options.zones || []).slice();
+ if (
+ (options.negativeColor || options.negativeFillColor) &&
+ !options.zones
+ ) {
+ zones.push({
+ value:
+ options[this.zoneAxis + 'Threshold'] ||
+ options.threshold ||
+ 0,
+ className: 'highcharts-negative',
+
+ color: options.negativeColor,
+ fillColor: options.negativeFillColor
+
+ });
+ }
+ if (zones.length) { // Push one extra zone for the rest
+ if (defined(zones[zones.length - 1].value)) {
+ zones.push({
+
+ color: this.color,
+ fillColor: this.fillColor
+
+ });
+ }
+ }
+ return options;
+ },
+
+ getCyclic: function (prop, value, defaults) {
+ var i,
+ chart = this.chart,
+ userOptions = this.userOptions,
+ indexName = prop + 'Index',
+ counterName = prop + 'Counter',
+ len = defaults ? defaults.length : pick(
+ chart.options.chart[prop + 'Count'],
+ chart[prop + 'Count']
+ ),
+ setting;
+
+ if (!value) {
+ // Pick up either the colorIndex option, or the _colorIndex after
+ // Series.update()
+ setting = pick(
+ userOptions[indexName],
+ userOptions['_' + indexName]
+ );
+ if (defined(setting)) { // after Series.update()
+ i = setting;
+ } else {
+ // #6138
+ if (!chart.series.length) {
+ chart[counterName] = 0;
+ }
+ userOptions['_' + indexName] = i = chart[counterName] % len;
+ chart[counterName] += 1;
+ }
+ if (defaults) {
+ value = defaults[i];
+ }
+ }
+ // Set the colorIndex
+ if (i !== undefined) {
+ this[indexName] = i;
+ }
+ this[prop] = value;
+ },
+
+ /**
+ * Get the series' color based on either the options or pulled from global
+ * options.
+ *
+ * @return {Color} The series color.
+ */
+
+ getColor: function () {
+ if (this.options.colorByPoint) {
+ // #4359, selected slice got series.color even when colorByPoint was
+ // set.
+ this.options.color = null;
+ } else {
+ this.getCyclic(
+ 'color',
+ this.options.color || defaultPlotOptions[this.type].color,
+ this.chart.options.colors
+ );
+ }
+ },
+
+ /**
+ * Get the series' symbol based on either the options or pulled from global
+ * options.
+ */
+ getSymbol: function () {
+ var seriesMarkerOption = this.options.marker;
+
+ this.getCyclic(
+ 'symbol',
+ seriesMarkerOption.symbol,
+ this.chart.options.symbols
+ );
+ },
+
+ drawLegendSymbol: LegendSymbolMixin.drawLineMarker,
+
+ /**
+ * Apply a new set of data to the series and optionally redraw it. The new
+ * data array is passed by reference (except in case of `updatePoints`), and
+ * may later be mutated when updating the chart data.
+ *
+ * Note the difference in behaviour when setting the same amount of points,
+ * or a different amount of points, as handled by the `updatePoints`
+ * parameter.
+ *
+ * @param {SeriesDataOptions} data
+ * Takes an array of data in the same format as described under
+ * `series.typedata` for the given series type.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after the series is altered. If doing
+ * more operations on the chart, it is a good idea to set redraw to
+ * false and call {@link Chart#redraw} after.
+ * @param {AnimationOptions} [animation]
+ * When the updated data is the same length as the existing data,
+ * points will be updated by default, and animation visualizes how
+ * the points are changed. Set false to disable animation, or a
+ * configuration object to set duration or easing.
+ * @param {Boolean} [updatePoints=true]
+ * When the updated data is the same length as the existing data,
+ * points will be updated instead of replaced. This allows updating
+ * with animation and performs better. In this case, the original
+ * array is not passed by reference. Set false to prevent.
+ *
+ * @sample highcharts/members/series-setdata/
+ * Set new data from a button
+ * @sample highcharts/members/series-setdata-pie/
+ * Set data in a pie
+ * @sample stock/members/series-setdata/
+ * Set new data in Highstock
+ * @sample maps/members/series-setdata/
+ * Set new data in Highmaps
+ */
+ setData: function (data, redraw, animation, updatePoints) {
+ var series = this,
+ oldData = series.points,
+ oldDataLength = (oldData && oldData.length) || 0,
+ dataLength,
+ options = series.options,
+ chart = series.chart,
+ firstPoint = null,
+ xAxis = series.xAxis,
+ i,
+ turboThreshold = options.turboThreshold,
+ pt,
+ xData = this.xData,
+ yData = this.yData,
+ pointArrayMap = series.pointArrayMap,
+ valueCount = pointArrayMap && pointArrayMap.length;
+
+ data = data || [];
+ dataLength = data.length;
+ redraw = pick(redraw, true);
+
+ // If the point count is the same as is was, just run Point.update which
+ // is cheaper, allows animation, and keeps references to points.
+ if (
+ updatePoints !== false &&
+ dataLength &&
+ oldDataLength === dataLength &&
+ !series.cropped &&
+ !series.hasGroupedData &&
+ series.visible
+ ) {
+ each(data, function (point, i) {
+ // .update doesn't exist on a linked, hidden series (#3709)
+ if (oldData[i].update && point !== options.data[i]) {
+ oldData[i].update(point, false, null, false);
+ }
+ });
+
+ } else {
+
+ // Reset properties
+ series.xIncrement = null;
+
+ series.colorCounter = 0; // for series with colorByPoint (#1547)
+
+ // Update parallel arrays
+ each(this.parallelArrays, function (key) {
+ series[key + 'Data'].length = 0;
+ });
+
+ // In turbo mode, only one- or twodimensional arrays of numbers are
+ // allowed. The first value is tested, and we assume that all the
+ // rest are defined the same way. Although the 'for' loops are
+ // similar, they are repeated inside each if-else conditional for
+ // max performance.
+ if (turboThreshold && dataLength > turboThreshold) {
+
+ // find the first non-null point
+ i = 0;
+ while (firstPoint === null && i < dataLength) {
+ firstPoint = data[i];
+ i++;
+ }
+
+
+ if (isNumber(firstPoint)) { // assume all points are numbers
+ for (i = 0; i < dataLength; i++) {
+ xData[i] = this.autoIncrement();
+ yData[i] = data[i];
+ }
+
+ // Assume all points are arrays when first point is
+ } else if (isArray(firstPoint)) {
+ if (valueCount) { // [x, low, high] or [x, o, h, l, c]
+ for (i = 0; i < dataLength; i++) {
+ pt = data[i];
+ xData[i] = pt[0];
+ yData[i] = pt.slice(1, valueCount + 1);
+ }
+ } else { // [x, y]
+ for (i = 0; i < dataLength; i++) {
+ pt = data[i];
+ xData[i] = pt[0];
+ yData[i] = pt[1];
+ }
+ }
+ } else {
+ // Highcharts expects configs to be numbers or arrays in
+ // turbo mode
+ H.error(12);
+ }
+ } else {
+ for (i = 0; i < dataLength; i++) {
+ if (data[i] !== undefined) { // stray commas in oldIE
+ pt = { series: series };
+ series.pointClass.prototype.applyOptions.apply(
+ pt,
+ [data[i]]
+ );
+ series.updateParallelArrays(pt, i);
+ }
+ }
+ }
+
+ // Forgetting to cast strings to numbers is a common caveat when
+ // handling CSV or JSON
+ if (yData && isString(yData[0])) {
+ H.error(14, true);
+ }
+
+ series.data = [];
+ series.options.data = series.userOptions.data = data;
+
+ // destroy old points
+ i = oldDataLength;
+ while (i--) {
+ if (oldData[i] && oldData[i].destroy) {
+ oldData[i].destroy();
+ }
+ }
+
+ // reset minRange (#878)
+ if (xAxis) {
+ xAxis.minRange = xAxis.userMinRange;
+ }
+
+ // redraw
+ series.isDirty = chart.isDirtyBox = true;
+ series.isDirtyData = !!oldData;
+ animation = false;
+ }
+
+ // Typically for pie series, points need to be processed and generated
+ // prior to rendering the legend
+ if (options.legendType === 'point') {
+ this.processData();
+ this.generatePoints();
+ }
+
+ if (redraw) {
+ chart.redraw(animation);
+ }
+ },
+
+ /**
+ * Internal function to process the data by cropping away unused data points
+ * if the series is longer than the crop threshold. This saves computing
+ * time for large series. In Highstock, this function is extended to
+ * provide data grouping.
+ *
+ * @private
+ * @param {Boolean} force
+ * Force data grouping.
+ */
+ processData: function (force) {
+ var series = this,
+ processedXData = series.xData, // copied during slice operation
+ processedYData = series.yData,
+ dataLength = processedXData.length,
+ croppedData,
+ cropStart = 0,
+ cropped,
+ distance,
+ closestPointRange,
+ xAxis = series.xAxis,
+ i, // loop variable
+ options = series.options,
+ cropThreshold = options.cropThreshold,
+ getExtremesFromAll =
+ series.getExtremesFromAll ||
+ options.getExtremesFromAll, // #4599
+ isCartesian = series.isCartesian,
+ xExtremes,
+ val2lin = xAxis && xAxis.val2lin,
+ isLog = xAxis && xAxis.isLog,
+ throwOnUnsorted = series.requireSorting,
+ min,
+ max;
+
+ // If the series data or axes haven't changed, don't go through this.
+ // Return false to pass the message on to override methods like in data
+ // grouping.
+ if (
+ isCartesian &&
+ !series.isDirty &&
+ !xAxis.isDirty &&
+ !series.yAxis.isDirty &&
+ !force
+ ) {
+ return false;
+ }
+
+ if (xAxis) {
+ xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053)
+ min = xExtremes.min;
+ max = xExtremes.max;
+ }
+
+ // optionally filter out points outside the plot area
+ if (
+ isCartesian &&
+ series.sorted &&
+ !getExtremesFromAll &&
+ (!cropThreshold || dataLength > cropThreshold || series.forceCrop)
+ ) {
+
+ // it's outside current extremes
+ if (
+ processedXData[dataLength - 1] < min ||
+ processedXData[0] > max
+ ) {
+ processedXData = [];
+ processedYData = [];
+
+ // only crop if it's actually spilling out
+ } else if (
+ processedXData[0] < min ||
+ processedXData[dataLength - 1] > max
+ ) {
+ croppedData = this.cropData(
+ series.xData,
+ series.yData,
+ min,
+ max
+ );
+ processedXData = croppedData.xData;
+ processedYData = croppedData.yData;
+ cropStart = croppedData.start;
+ cropped = true;
+ }
+ }
+
+
+ // Find the closest distance between processed points
+ i = processedXData.length || 1;
+ while (--i) {
+ distance = isLog ?
+ val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) :
+ processedXData[i] - processedXData[i - 1];
+
+ if (
+ distance > 0 &&
+ (
+ closestPointRange === undefined ||
+ distance < closestPointRange
+ )
+ ) {
+ closestPointRange = distance;
+
+ // Unsorted data is not supported by the line tooltip, as well as
+ // data grouping and navigation in Stock charts (#725) and width
+ // calculation of columns (#1900)
+ } else if (distance < 0 && throwOnUnsorted) {
+ H.error(15);
+ throwOnUnsorted = false; // Only once
+ }
+ }
+
+ // Record the properties
+ series.cropped = cropped; // undefined or true
+ series.cropStart = cropStart;
+ series.processedXData = processedXData;
+ series.processedYData = processedYData;
+
+ series.closestPointRange = closestPointRange;
+
+ },
+
+ /**
+ * Iterate over xData and crop values between min and max. Returns object
+ * containing crop start/end cropped xData with corresponding part of yData,
+ * dataMin and dataMax within the cropped range.
+ *
+ * @private
+ */
+ cropData: function (xData, yData, min, max) {
+ var dataLength = xData.length,
+ cropStart = 0,
+ cropEnd = dataLength,
+ // line-type series need one point outside
+ cropShoulder = pick(this.cropShoulder, 1),
+ i,
+ j;
+
+ // iterate up to find slice start
+ for (i = 0; i < dataLength; i++) {
+ if (xData[i] >= min) {
+ cropStart = Math.max(0, i - cropShoulder);
+ break;
+ }
+ }
+
+ // proceed to find slice end
+ for (j = i; j < dataLength; j++) {
+ if (xData[j] > max) {
+ cropEnd = j + cropShoulder;
+ break;
+ }
+ }
+
+ return {
+ xData: xData.slice(cropStart, cropEnd),
+ yData: yData.slice(cropStart, cropEnd),
+ start: cropStart,
+ end: cropEnd
+ };
+ },
+
+
+ /**
+ * Generate the data point after the data has been processed by cropping
+ * away unused points and optionally grouped in Highcharts Stock.
+ *
+ * @private
+ */
+ generatePoints: function () {
+ var series = this,
+ options = series.options,
+ dataOptions = options.data,
+ data = series.data,
+ dataLength,
+ processedXData = series.processedXData,
+ processedYData = series.processedYData,
+ PointClass = series.pointClass,
+ processedDataLength = processedXData.length,
+ cropStart = series.cropStart || 0,
+ cursor,
+ hasGroupedData = series.hasGroupedData,
+ keys = options.keys,
+ point,
+ points = [],
+ i;
+
+ if (!data && !hasGroupedData) {
+ var arr = [];
+ arr.length = dataOptions.length;
+ data = series.data = arr;
+ }
+
+ if (keys && hasGroupedData) {
+ // grouped data has already applied keys (#6590)
+ series.options.keys = false;
+ }
+
+ for (i = 0; i < processedDataLength; i++) {
+ cursor = cropStart + i;
+ if (!hasGroupedData) {
+ point = data[cursor];
+ if (!point && dataOptions[cursor] !== undefined) { // #970
+ data[cursor] = point = (new PointClass()).init(
+ series,
+ dataOptions[cursor],
+ processedXData[i]
+ );
+ }
+ } else {
+ // splat the y data in case of ohlc data array
+ point = (new PointClass()).init(
+ series,
+ [processedXData[i]].concat(splat(processedYData[i]))
+ );
+
+ /**
+ * Highstock only. If a point object is created by data
+ * grouping, it doesn't reflect actual points in the raw data.
+ * In this case, the `dataGroup` property holds information
+ * that points back to the raw data.
+ *
+ * - `dataGroup.start` is the index of the first raw data point
+ * in the group.
+ * - `dataGroup.length` is the amount of points in the group.
+ *
+ * @name dataGroup
+ * @memberOf Point
+ * @type {Object}
+ *
+ */
+ point.dataGroup = series.groupMap[i];
+ }
+ if (point) { // #6279
+ point.index = cursor; // For faster access in Point.update
+ points[i] = point;
+ }
+ }
+
+ // restore keys options (#6590)
+ series.options.keys = keys;
+
+ // Hide cropped-away points - this only runs when the number of points
+ // is above cropThreshold, or when swithching view from non-grouped
+ // data to grouped data (#637)
+ if (
+ data &&
+ (
+ processedDataLength !== (dataLength = data.length) ||
+ hasGroupedData
+ )
+ ) {
+ for (i = 0; i < dataLength; i++) {
+ // when has grouped data, clear all points
+ if (i === cropStart && !hasGroupedData) {
+ i += processedDataLength;
+ }
+ if (data[i]) {
+ data[i].destroyElements();
+ data[i].plotX = undefined; // #1003
+ }
+ }
+ }
+
+ /**
+ * Read only. An array containing those values converted to points, but
+ * in case the series data length exceeds the `cropThreshold`, or if the
+ * data is grouped, `series.data` doesn't contain all the points. It
+ * only contains the points that have been created on demand. To
+ * modify the data, use {@link Highcharts.Series#setData} or {@link
+ * Highcharts.Point#update}.
+ *
+ * @name data
+ * @memberOf Highcharts.Series
+ * @see Series.points
+ * @type {Array.}
+ */
+ series.data = data;
+
+ /**
+ * An array containing all currently visible point objects. In case of
+ * cropping, the cropped-away points are not part of this array. The
+ * `series.points` array starts at `series.cropStart` compared to
+ * `series.data` and `series.options.data`. If however the series data
+ * is grouped, these can't be correlated one to one. To
+ * modify the data, use {@link Highcharts.Series#setData} or {@link
+ * Highcharts.Point#update}.
+ * @name points
+ * @memberof Series
+ * @type {Array.}
+ */
+ series.points = points;
+ },
+
+ /**
+ * Calculate Y extremes for the visible data. The result is set as
+ * `dataMin` and `dataMax` on the Series item.
+ *
+ * @param {Array.} [yData]
+ * The data to inspect. Defaults to the current data within the
+ * visible range.
+ *
+ */
+ getExtremes: function (yData) {
+ var xAxis = this.xAxis,
+ yAxis = this.yAxis,
+ xData = this.processedXData,
+ yDataLength,
+ activeYData = [],
+ activeCounter = 0,
+ // #2117, need to compensate for log X axis
+ xExtremes = xAxis.getExtremes(),
+ xMin = xExtremes.min,
+ xMax = xExtremes.max,
+ validValue,
+ withinRange,
+ x,
+ y,
+ i,
+ j;
+
+ yData = yData || this.stackedYData || this.processedYData || [];
+ yDataLength = yData.length;
+
+ for (i = 0; i < yDataLength; i++) {
+
+ x = xData[i];
+ y = yData[i];
+
+ // For points within the visible range, including the first point
+ // outside the visible range (#7061), consider y extremes.
+ validValue =
+ (isNumber(y, true) || isArray(y)) &&
+ (!yAxis.positiveValuesOnly || (y.length || y > 0));
+ withinRange =
+ this.getExtremesFromAll ||
+ this.options.getExtremesFromAll ||
+ this.cropped ||
+ ((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax);
+
+ if (validValue && withinRange) {
+
+ j = y.length;
+ if (j) { // array, like ohlc or range data
+ while (j--) {
+ if (typeof y[j] === 'number') { // #7380
+ activeYData[activeCounter++] = y[j];
+ }
+ }
+ } else {
+ activeYData[activeCounter++] = y;
+ }
+ }
+ }
+
+ this.dataMin = arrayMin(activeYData);
+ this.dataMax = arrayMax(activeYData);
+ },
+
+ /**
+ * Translate data points from raw data values to chart specific positioning
+ * data needed later in the `drawPoints` and `drawGraph` functions. This
+ * function can be overridden in plugins and custom series type
+ * implementations.
+ */
+ translate: function () {
+ if (!this.processedXData) { // hidden series
+ this.processData();
+ }
+ this.generatePoints();
+ var series = this,
+ options = series.options,
+ stacking = options.stacking,
+ xAxis = series.xAxis,
+ categories = xAxis.categories,
+ yAxis = series.yAxis,
+ points = series.points,
+ dataLength = points.length,
+ hasModifyValue = !!series.modifyValue,
+ i,
+ pointPlacement = options.pointPlacement,
+ dynamicallyPlaced =
+ pointPlacement === 'between' ||
+ isNumber(pointPlacement),
+ threshold = options.threshold,
+ stackThreshold = options.startFromThreshold ? threshold : 0,
+ plotX,
+ plotY,
+ lastPlotX,
+ stackIndicator,
+ closestPointRangePx = Number.MAX_VALUE;
+
+ /*
+ * Plotted coordinates need to be within a limited range. Drawing too
+ * far outside the viewport causes various rendering issues (#3201,
+ * #3923, #7555).
+ */
+ function limitedRange(val) {
+ return Math.min(Math.max(-1e5, val), 1e5);
+ }
+
+ // Point placement is relative to each series pointRange (#5889)
+ if (pointPlacement === 'between') {
+ pointPlacement = 0.5;
+ }
+ if (isNumber(pointPlacement)) {
+ pointPlacement *= pick(options.pointRange || xAxis.pointRange);
+ }
+
+ // Translate each point
+ for (i = 0; i < dataLength; i++) {
+ var point = points[i],
+ xValue = point.x,
+ yValue = point.y,
+ yBottom = point.low,
+ stack = stacking && yAxis.stacks[(
+ series.negStacks &&
+ yValue < (stackThreshold ? 0 : threshold) ? '-' : ''
+ ) + series.stackKey],
+ pointStack,
+ stackValues;
+
+ // Discard disallowed y values for log axes (#3434)
+ if (yAxis.positiveValuesOnly && yValue !== null && yValue <= 0) {
+ point.isNull = true;
+ }
+
+ // Get the plotX translation
+ point.plotX = plotX = correctFloat( // #5236
+ limitedRange(xAxis.translate( // #3923
+ xValue,
+ 0,
+ 0,
+ 0,
+ 1,
+ pointPlacement,
+ this.type === 'flags'
+ )) // #3923
+ );
+
+ // Calculate the bottom y value for stacked series
+ if (
+ stacking &&
+ series.visible &&
+ !point.isNull &&
+ stack &&
+ stack[xValue]
+ ) {
+ stackIndicator = series.getStackIndicator(
+ stackIndicator,
+ xValue,
+ series.index
+ );
+ pointStack = stack[xValue];
+ stackValues = pointStack.points[stackIndicator.key];
+ yBottom = stackValues[0];
+ yValue = stackValues[1];
+
+ if (
+ yBottom === stackThreshold &&
+ stackIndicator.key === stack[xValue].base
+ ) {
+ yBottom = pick(threshold, yAxis.min);
+ }
+ if (yAxis.positiveValuesOnly && yBottom <= 0) { // #1200, #1232
+ yBottom = null;
+ }
+
+ point.total = point.stackTotal = pointStack.total;
+ point.percentage =
+ pointStack.total &&
+ (point.y / pointStack.total * 100);
+ point.stackY = yValue;
+
+ // Place the stack label
+ pointStack.setOffset(
+ series.pointXOffset || 0,
+ series.barW || 0
+ );
+
+ }
+
+ // Set translated yBottom or remove it
+ point.yBottom = defined(yBottom) ?
+ limitedRange(yAxis.translate(yBottom, 0, 1, 0, 1)) :
+ null;
+
+ // general hook, used for Highstock compare mode
+ if (hasModifyValue) {
+ yValue = series.modifyValue(yValue, point);
+ }
+
+ // Set the the plotY value, reset it for redraws
+ point.plotY = plotY =
+ (typeof yValue === 'number' && yValue !== Infinity) ?
+ limitedRange(yAxis.translate(yValue, 0, 1, 0, 1)) : // #3201
+ undefined;
+
+ point.isInside =
+ plotY !== undefined &&
+ plotY >= 0 &&
+ plotY <= yAxis.len && // #3519
+ plotX >= 0 &&
+ plotX <= xAxis.len;
+
+
+ // Set client related positions for mouse tracking
+ point.clientX = dynamicallyPlaced ?
+ correctFloat(
+ xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)
+ ) :
+ plotX; // #1514, #5383, #5518
+
+ point.negative = point.y < (threshold || 0);
+
+ // some API data
+ point.category = categories && categories[point.x] !== undefined ?
+ categories[point.x] : point.x;
+
+ // Determine auto enabling of markers (#3635, #5099)
+ if (!point.isNull) {
+ if (lastPlotX !== undefined) {
+ closestPointRangePx = Math.min(
+ closestPointRangePx,
+ Math.abs(plotX - lastPlotX)
+ );
+ }
+ lastPlotX = plotX;
+ }
+
+ // Find point zone
+ point.zone = this.zones.length && point.getZone();
+ }
+ series.closestPointRangePx = closestPointRangePx;
+ },
+
+ /**
+ * Return the series points with null points filtered out.
+ *
+ * @param {Array.} [points]
+ * The points to inspect, defaults to {@link Series.points}.
+ * @param {Boolean} [insideOnly=false]
+ * Whether to inspect only the points that are inside the visible
+ * view.
+ *
+ * @return {Array.}
+ * The valid points.
+ */
+ getValidPoints: function (points, insideOnly) {
+ var chart = this.chart;
+ // #3916, #5029, #5085
+ return grep(points || this.points || [], function isValidPoint(point) {
+ if (insideOnly && !chart.isInsidePlot(
+ point.plotX,
+ point.plotY,
+ chart.inverted
+ )) {
+ return false;
+ }
+ return !point.isNull;
+ });
+ },
+
+ /**
+ * Set the clipping for the series. For animated series it is called twice,
+ * first to initiate animating the clip then the second time without the
+ * animation to set the final clip.
+ *
+ * @private
+ */
+ setClip: function (animation) {
+ var chart = this.chart,
+ options = this.options,
+ renderer = chart.renderer,
+ inverted = chart.inverted,
+ seriesClipBox = this.clipBox,
+ clipBox = seriesClipBox || chart.clipBox,
+ sharedClipKey =
+ this.sharedClipKey ||
+ [
+ '_sharedClip',
+ animation && animation.duration,
+ animation && animation.easing,
+ clipBox.height,
+ options.xAxis,
+ options.yAxis
+ ].join(','), // #4526
+ clipRect = chart[sharedClipKey],
+ markerClipRect = chart[sharedClipKey + 'm'];
+
+ // If a clipping rectangle with the same properties is currently present
+ // in the chart, use that.
+ if (!clipRect) {
+
+ // When animation is set, prepare the initial positions
+ if (animation) {
+ clipBox.width = 0;
+ if (inverted) {
+ clipBox.x = chart.plotSizeX;
+ }
+
+ chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
+ inverted ? chart.plotSizeX + 99 : -99, // include the width of the first marker
+ inverted ? -chart.plotLeft : -chart.plotTop,
+ 99,
+ inverted ? chart.chartWidth : chart.chartHeight
+ );
+ }
+ chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox);
+ // Create hashmap for series indexes
+ clipRect.count = { length: 0 };
+
+ }
+ if (animation) {
+ if (!clipRect.count[this.index]) {
+ clipRect.count[this.index] = true;
+ clipRect.count.length += 1;
+ }
+ }
+
+ if (options.clip !== false) {
+ this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect);
+ this.markerGroup.clip(markerClipRect);
+ this.sharedClipKey = sharedClipKey;
+ }
+
+ // Remove the shared clipping rectangle when all series are shown
+ if (!animation) {
+ if (clipRect.count[this.index]) {
+ delete clipRect.count[this.index];
+ clipRect.count.length -= 1;
+ }
+
+ if (clipRect.count.length === 0 && sharedClipKey && chart[sharedClipKey]) {
+ if (!seriesClipBox) {
+ chart[sharedClipKey] = chart[sharedClipKey].destroy();
+ }
+ if (chart[sharedClipKey + 'm']) {
+ chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
+ }
+ }
+ }
+ },
+
+ /**
+ * Animate in the series. Called internally twice. First with the `init`
+ * parameter set to true, which sets up the initial state of the animation.
+ * Then when ready, it is called with the `init` parameter undefined, in
+ * order to perform the actual animation. After the second run, the function
+ * is removed.
+ *
+ * @param {Boolean} init
+ * Initialize the animation.
+ */
+ animate: function (init) {
+ var series = this,
+ chart = series.chart,
+ clipRect,
+ animation = animObject(series.options.animation),
+ sharedClipKey;
+
+ // Initialize the animation. Set up the clipping rectangle.
+ if (init) {
+
+ series.setClip(animation);
+
+ // Run the animation
+ } else {
+ sharedClipKey = this.sharedClipKey;
+ clipRect = chart[sharedClipKey];
+ if (clipRect) {
+ clipRect.animate({
+ width: chart.plotSizeX,
+ x: 0
+ }, animation);
+ }
+ if (chart[sharedClipKey + 'm']) {
+ chart[sharedClipKey + 'm'].animate({
+ width: chart.plotSizeX + 99,
+ x: 0
+ }, animation);
+ }
+
+ // Delete this function to allow it only once
+ series.animate = null;
+
+ }
+ },
+
+ /**
+ * This runs after animation to land on the final plot clipping.
+ *
+ * @private
+ */
+ afterAnimate: function () {
+ this.setClip();
+ fireEvent(this, 'afterAnimate');
+ this.finishedAnimating = true;
+ },
+
+ /**
+ * Draw the markers for line-like series types, and columns or other
+ * graphical representation for {@link Point} objects for other series
+ * types. The resulting element is typically stored as {@link
+ * Point.graphic}, and is created on the first call and updated and moved on
+ * subsequent calls.
+ */
+ drawPoints: function () {
+ var series = this,
+ points = series.points,
+ chart = series.chart,
+ i,
+ point,
+ symbol,
+ graphic,
+ options = series.options,
+ seriesMarkerOptions = options.marker,
+ pointMarkerOptions,
+ hasPointMarker,
+ enabled,
+ isInside,
+ markerGroup = series[series.specialGroup] || series.markerGroup,
+ xAxis = series.xAxis,
+ markerAttribs,
+ globallyEnabled = pick(
+ seriesMarkerOptions.enabled,
+ xAxis.isRadial ? true : null,
+ // Use larger or equal as radius is null in bubbles (#6321)
+ series.closestPointRangePx >= 2 * seriesMarkerOptions.radius
+ );
+
+ if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {
+
+ for (i = 0; i < points.length; i++) {
+ point = points[i];
+ graphic = point.graphic;
+ pointMarkerOptions = point.marker || {};
+ hasPointMarker = !!point.marker;
+ enabled = (globallyEnabled && pointMarkerOptions.enabled === undefined) || pointMarkerOptions.enabled;
+ isInside = point.isInside;
+
+ // only draw the point if y is defined
+ if (enabled && !point.isNull) {
+
+ // Shortcuts
+ symbol = pick(pointMarkerOptions.symbol, series.symbol);
+ point.hasImage = symbol.indexOf('url') === 0;
+
+ markerAttribs = series.markerAttribs(
+ point,
+ point.selected && 'select'
+ );
+
+ if (graphic) { // update
+ graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled
+ .animate(markerAttribs);
+ } else if (isInside && (markerAttribs.width > 0 || point.hasImage)) {
+
+ /**
+ * The graphic representation of the point. Typically
+ * this is a simple shape, like a `rect` for column
+ * charts or `path` for line markers, but for some
+ * complex series types like boxplot or 3D charts, the
+ * graphic may be a `g` element containing other shapes.
+ * The graphic is generated the first time {@link
+ * Series#drawPoints} runs, and updated and moved on
+ * subsequent runs.
+ *
+ * @memberof Point
+ * @name graphic
+ * @type {SVGElement}
+ */
+ point.graphic = graphic = chart.renderer.symbol(
+ symbol,
+ markerAttribs.x,
+ markerAttribs.y,
+ markerAttribs.width,
+ markerAttribs.height,
+ hasPointMarker ? pointMarkerOptions : seriesMarkerOptions
+ )
+ .add(markerGroup);
+ }
+
+
+ // Presentational attributes
+ if (graphic) {
+ graphic.attr(series.pointAttribs(point, point.selected && 'select'));
+ }
+
+
+ if (graphic) {
+ graphic.addClass(point.getClassName(), true);
+ }
+
+ } else if (graphic) {
+ point.graphic = graphic.destroy(); // #1269
+ }
+ }
+ }
+
+ },
+
+ /**
+ * Get non-presentational attributes for a point. Used internally for both
+ * styled mode and classic. Can be overridden for different series types.
+ *
+ * @see Series#pointAttribs
+ *
+ * @param {Point} point
+ * The Point to inspect.
+ * @param {String} [state]
+ * The state, can be either `hover`, `select` or undefined.
+ *
+ * @return {SVGAttributes}
+ * A hash containing those attributes that are not settable from
+ * CSS.
+ */
+ markerAttribs: function (point, state) {
+ var seriesMarkerOptions = this.options.marker,
+ seriesStateOptions,
+ pointMarkerOptions = point.marker || {},
+ pointStateOptions,
+ radius = pick(
+ pointMarkerOptions.radius,
+ seriesMarkerOptions.radius
+ ),
+ attribs;
+
+ // Handle hover and select states
+ if (state) {
+ seriesStateOptions = seriesMarkerOptions.states[state];
+ pointStateOptions = pointMarkerOptions.states &&
+ pointMarkerOptions.states[state];
+
+ radius = pick(
+ pointStateOptions && pointStateOptions.radius,
+ seriesStateOptions && seriesStateOptions.radius,
+ radius + (seriesStateOptions && seriesStateOptions.radiusPlus || 0)
+ );
+ }
+
+ if (point.hasImage) {
+ radius = 0; // and subsequently width and height is not set
+ }
+
+ attribs = {
+ x: Math.floor(point.plotX) - radius, // Math.floor for #1843
+ y: point.plotY - radius
+ };
+
+ if (radius) {
+ attribs.width = attribs.height = 2 * radius;
+ }
+
+ return attribs;
+
+ },
+
+
+ /**
+ * Internal function to get presentational attributes for each point. Unlike
+ * {@link Series#markerAttribs}, this function should return those
+ * attributes that can also be set in CSS. In styled mode, `pointAttribs`
+ * won't be called.
+ *
+ * @param {Point} point
+ * The point instance to inspect.
+ * @param {String} [state]
+ * The point state, can be either `hover`, `select` or undefined for
+ * normal state.
+ *
+ * @return {SVGAttributes}
+ * The presentational attributes to be set on the point.
+ */
+ pointAttribs: function (point, state) {
+ var seriesMarkerOptions = this.options.marker,
+ seriesStateOptions,
+ pointOptions = point && point.options,
+ pointMarkerOptions = (pointOptions && pointOptions.marker) || {},
+ pointStateOptions,
+ color = this.color,
+ pointColorOption = pointOptions && pointOptions.color,
+ pointColor = point && point.color,
+ strokeWidth = pick(
+ pointMarkerOptions.lineWidth,
+ seriesMarkerOptions.lineWidth
+ ),
+ zoneColor = point && point.zone && point.zone.color,
+ fill,
+ stroke;
+
+ color = pointColorOption || zoneColor || pointColor || color;
+ fill = pointMarkerOptions.fillColor || seriesMarkerOptions.fillColor || color;
+ stroke = pointMarkerOptions.lineColor || seriesMarkerOptions.lineColor || color;
+
+ // Handle hover and select states
+ if (state) {
+ seriesStateOptions = seriesMarkerOptions.states[state];
+ pointStateOptions = (pointMarkerOptions.states && pointMarkerOptions.states[state]) || {};
+ strokeWidth = pick(
+ pointStateOptions.lineWidth,
+ seriesStateOptions.lineWidth,
+ strokeWidth + pick(
+ pointStateOptions.lineWidthPlus,
+ seriesStateOptions.lineWidthPlus,
+ 0
+ )
+ );
+ fill = pointStateOptions.fillColor || seriesStateOptions.fillColor || fill;
+ stroke = pointStateOptions.lineColor || seriesStateOptions.lineColor || stroke;
+ }
+
+ return {
+ 'stroke': stroke,
+ 'stroke-width': strokeWidth,
+ 'fill': fill
+ };
+ },
+
+ /**
+ * Clear DOM objects and free up memory.
+ *
+ * @private
+ */
+ destroy: function () {
+ var series = this,
+ chart = series.chart,
+ issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent),
+ destroy,
+ i,
+ data = series.data || [],
+ point,
+ axis;
+
+ // add event hook
+ fireEvent(series, 'destroy');
+
+ // remove all events
+ removeEvent(series);
+
+ // erase from axes
+ each(series.axisTypes || [], function (AXIS) {
+ axis = series[AXIS];
+ if (axis && axis.series) {
+ erase(axis.series, series);
+ axis.isDirty = axis.forceRedraw = true;
+ }
+ });
+
+ // remove legend items
+ if (series.legendItem) {
+ series.chart.legend.destroyItem(series);
+ }
+
+ // destroy all points with their elements
+ i = data.length;
+ while (i--) {
+ point = data[i];
+ if (point && point.destroy) {
+ point.destroy();
+ }
+ }
+ series.points = null;
+
+ // Clear the animation timeout if we are destroying the series during initial animation
+ clearTimeout(series.animationTimeout);
+
+ // Destroy all SVGElements associated to the series
+ objectEach(series, function (val, prop) {
+ if (val instanceof SVGElement && !val.survive) { // Survive provides a hook for not destroying
+
+ // issue 134 workaround
+ destroy = issue134 && prop === 'group' ?
+ 'hide' :
+ 'destroy';
+
+ val[destroy]();
+ }
+ });
+
+ // remove from hoverSeries
+ if (chart.hoverSeries === series) {
+ chart.hoverSeries = null;
+ }
+ erase(chart.series, series);
+ chart.orderSeries();
+
+ // clear all members
+ objectEach(series, function (val, prop) {
+ delete series[prop];
+ });
+ },
+
+ /**
+ * Get the graph path.
+ *
+ * @private
+ */
+ getGraphPath: function (points, nullsAsZeroes, connectCliffs) {
+ var series = this,
+ options = series.options,
+ step = options.step,
+ reversed,
+ graphPath = [],
+ xMap = [],
+ gap;
+
+ points = points || series.points;
+
+ // Bottom of a stack is reversed
+ reversed = points.reversed;
+ if (reversed) {
+ points.reverse();
+ }
+ // Reverse the steps (#5004)
+ step = { right: 1, center: 2 }[step] || (step && 3);
+ if (step && reversed) {
+ step = 4 - step;
+ }
+
+ // Remove invalid points, especially in spline (#5015)
+ if (options.connectNulls && !nullsAsZeroes && !connectCliffs) {
+ points = this.getValidPoints(points);
+ }
+
+ // Build the line
+ each(points, function (point, i) {
+
+ var plotX = point.plotX,
+ plotY = point.plotY,
+ lastPoint = points[i - 1],
+ pathToPoint; // the path to this point from the previous
+
+ if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) {
+ gap = true; // ... and continue
+ }
+
+ // Line series, nullsAsZeroes is not handled
+ if (point.isNull && !defined(nullsAsZeroes) && i > 0) {
+ gap = !options.connectNulls;
+
+ // Area series, nullsAsZeroes is set
+ } else if (point.isNull && !nullsAsZeroes) {
+ gap = true;
+
+ } else {
+
+ if (i === 0 || gap) {
+ pathToPoint = ['M', point.plotX, point.plotY];
+
+ } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object
+
+ pathToPoint = series.getPointSpline(points, point, i);
+
+ } else if (step) {
+
+ if (step === 1) { // right
+ pathToPoint = [
+ 'L',
+ lastPoint.plotX,
+ plotY
+ ];
+
+ } else if (step === 2) { // center
+ pathToPoint = [
+ 'L',
+ (lastPoint.plotX + plotX) / 2,
+ lastPoint.plotY,
+ 'L',
+ (lastPoint.plotX + plotX) / 2,
+ plotY
+ ];
+
+ } else {
+ pathToPoint = [
+ 'L',
+ plotX,
+ lastPoint.plotY
+ ];
+ }
+ pathToPoint.push('L', plotX, plotY);
+
+ } else {
+ // normal line to next point
+ pathToPoint = [
+ 'L',
+ plotX,
+ plotY
+ ];
+ }
+
+ // Prepare for animation. When step is enabled, there are two path nodes for each x value.
+ xMap.push(point.x);
+ if (step) {
+ xMap.push(point.x);
+ }
+
+ graphPath.push.apply(graphPath, pathToPoint);
+ gap = false;
+ }
+ });
+
+ graphPath.xMap = xMap;
+ series.graphPath = graphPath;
+
+ return graphPath;
+
+ },
+
+ /**
+ * Draw the graph. Called internally when rendering line-like series types.
+ * The first time it generates the `series.graph` item and optionally other
+ * series-wide items like `series.area` for area charts. On subsequent calls
+ * these items are updated with new positions and attributes.
+ */
+ drawGraph: function () {
+ var series = this,
+ options = this.options,
+ graphPath = (this.gappedPath || this.getGraphPath).call(this),
+ props = [[
+ 'graph',
+ 'highcharts-graph',
+
+ options.lineColor || this.color,
+ options.dashStyle
+
+ ]];
+
+ // Add the zone properties if any
+ each(this.zones, function (zone, i) {
+ props.push([
+ 'zone-graph-' + i,
+ 'highcharts-graph highcharts-zone-graph-' + i + ' ' + (zone.className || ''),
+
+ zone.color || series.color,
+ zone.dashStyle || options.dashStyle
+
+ ]);
+ });
+
+ // Draw the graph
+ each(props, function (prop, i) {
+ var graphKey = prop[0],
+ graph = series[graphKey],
+ attribs;
+
+ if (graph) {
+ graph.endX = series.preventGraphAnimation ?
+ null :
+ graphPath.xMap;
+ graph.animate({ d: graphPath });
+
+ } else if (graphPath.length) { // #1487
+
+ series[graphKey] = series.chart.renderer.path(graphPath)
+ .addClass(prop[1])
+ .attr({ zIndex: 1 }) // #1069
+ .add(series.group);
+
+
+ attribs = {
+ 'stroke': prop[2],
+ 'stroke-width': options.lineWidth,
+ 'fill': (series.fillGraph && series.color) || 'none' // Polygon series use filled graph
+ };
+
+ if (prop[3]) {
+ attribs.dashstyle = prop[3];
+ } else if (options.linecap !== 'square') {
+ attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';
+ }
+
+ graph = series[graphKey]
+ .attr(attribs)
+ .shadow((i < 2) && options.shadow); // add shadow to normal series (0) or to first zone (1) #3932
+
+ }
+
+ // Helpers for animation
+ if (graph) {
+ graph.startX = graphPath.xMap;
+ graph.isArea = graphPath.isArea; // For arearange animation
+ }
+ });
+ },
+
+ /**
+ * Clip the graphs into zones for colors and styling.
+ *
+ * @private
+ */
+ applyZones: function () {
+ var series = this,
+ chart = this.chart,
+ renderer = chart.renderer,
+ zones = this.zones,
+ translatedFrom,
+ translatedTo,
+ clips = this.clips || [],
+ clipAttr,
+ graph = this.graph,
+ area = this.area,
+ chartSizeMax = Math.max(chart.chartWidth, chart.chartHeight),
+ axis = this[(this.zoneAxis || 'y') + 'Axis'],
+ extremes,
+ reversed,
+ inverted = chart.inverted,
+ horiz,
+ pxRange,
+ pxPosMin,
+ pxPosMax,
+ ignoreZones = false;
+
+ if (zones.length && (graph || area) && axis && axis.min !== undefined) {
+ reversed = axis.reversed;
+ horiz = axis.horiz;
+ // The use of the Color Threshold assumes there are no gaps
+ // so it is safe to hide the original graph and area
+ if (graph) {
+ graph.hide();
+ }
+ if (area) {
+ area.hide();
+ }
+
+ // Create the clips
+ extremes = axis.getExtremes();
+ each(zones, function (threshold, i) {
+
+ translatedFrom = reversed ?
+ (horiz ? chart.plotWidth : 0) :
+ (horiz ? 0 : axis.toPixels(extremes.min));
+ translatedFrom = Math.min(Math.max(pick(translatedTo, translatedFrom), 0), chartSizeMax);
+ translatedTo = Math.min(Math.max(Math.round(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax);
+
+ if (ignoreZones) {
+ translatedFrom = translatedTo = axis.toPixels(extremes.max);
+ }
+
+ pxRange = Math.abs(translatedFrom - translatedTo);
+ pxPosMin = Math.min(translatedFrom, translatedTo);
+ pxPosMax = Math.max(translatedFrom, translatedTo);
+ if (axis.isXAxis) {
+ clipAttr = {
+ x: inverted ? pxPosMax : pxPosMin,
+ y: 0,
+ width: pxRange,
+ height: chartSizeMax
+ };
+ if (!horiz) {
+ clipAttr.x = chart.plotHeight - clipAttr.x;
+ }
+ } else {
+ clipAttr = {
+ x: 0,
+ y: inverted ? pxPosMax : pxPosMin,
+ width: chartSizeMax,
+ height: pxRange
+ };
+ if (horiz) {
+ clipAttr.y = chart.plotWidth - clipAttr.y;
+ }
+ }
+
+
+ // VML SUPPPORT
+ if (inverted && renderer.isVML) {
+ if (axis.isXAxis) {
+ clipAttr = {
+ x: 0,
+ y: reversed ? pxPosMin : pxPosMax,
+ height: clipAttr.width,
+ width: chart.chartWidth
+ };
+ } else {
+ clipAttr = {
+ x: clipAttr.y - chart.plotLeft - chart.spacingBox.x,
+ y: 0,
+ width: clipAttr.height,
+ height: chart.chartHeight
+ };
+ }
+ }
+ // END OF VML SUPPORT
+
+
+ if (clips[i]) {
+ clips[i].animate(clipAttr);
+ } else {
+ clips[i] = renderer.clipRect(clipAttr);
+
+ if (graph) {
+ series['zone-graph-' + i].clip(clips[i]);
+ }
+
+ if (area) {
+ series['zone-area-' + i].clip(clips[i]);
+ }
+ }
+ // if this zone extends out of the axis, ignore the others
+ ignoreZones = threshold.value > extremes.max;
+ });
+ this.clips = clips;
+ }
+ },
+
+ /**
+ * Initialize and perform group inversion on series.group and
+ * series.markerGroup.
+ *
+ * @private
+ */
+ invertGroups: function (inverted) {
+ var series = this,
+ chart = series.chart,
+ remover;
+
+ function setInvert() {
+ each(['group', 'markerGroup'], function (groupName) {
+ if (series[groupName]) {
+
+ // VML/HTML needs explicit attributes for flipping
+ if (chart.renderer.isVML) {
+ series[groupName].attr({
+ width: series.yAxis.len,
+ height: series.xAxis.len
+ });
+ }
+
+ series[groupName].width = series.yAxis.len;
+ series[groupName].height = series.xAxis.len;
+ series[groupName].invert(inverted);
+ }
+ });
+ }
+
+ // Pie, go away (#1736)
+ if (!series.xAxis) {
+ return;
+ }
+
+ // A fixed size is needed for inversion to work
+ remover = addEvent(chart, 'resize', setInvert);
+ addEvent(series, 'destroy', remover);
+
+ // Do it now
+ setInvert(inverted); // do it now
+
+ // On subsequent render and redraw, just do setInvert without setting up events again
+ series.invertGroups = setInvert;
+ },
+
+ /**
+ * General abstraction for creating plot groups like series.group,
+ * series.dataLabelsGroup and series.markerGroup. On subsequent calls, the
+ * group will only be adjusted to the updated plot size.
+ *
+ * @private
+ */
+ plotGroup: function (prop, name, visibility, zIndex, parent) {
+ var group = this[prop],
+ isNew = !group;
+
+ // Generate it on first call
+ if (isNew) {
+ this[prop] = group = this.chart.renderer.g()
+ .attr({
+ zIndex: zIndex || 0.1 // IE8 and pointer logic use this
+ })
+ .add(parent);
+
+ }
+
+ // Add the class names, and replace existing ones as response to
+ // Series.update (#6660)
+ group.addClass(
+ (
+ 'highcharts-' + name +
+ ' highcharts-series-' + this.index +
+ ' highcharts-' + this.type + '-series ' +
+ (
+ defined(this.colorIndex) ?
+ 'highcharts-color-' + this.colorIndex + ' ' :
+ ''
+ ) +
+ (this.options.className || '') +
+ (group.hasClass('highcharts-tracker') ? ' highcharts-tracker' : '')
+ ),
+ true
+ );
+
+ // Place it on first and subsequent (redraw) calls
+ group.attr({ visibility: visibility })[isNew ? 'attr' : 'animate'](
+ this.getPlotBox()
+ );
+ return group;
+ },
+
+ /**
+ * Get the translation and scale for the plot area of this series.
+ */
+ getPlotBox: function () {
+ var chart = this.chart,
+ xAxis = this.xAxis,
+ yAxis = this.yAxis;
+
+ // Swap axes for inverted (#2339)
+ if (chart.inverted) {
+ xAxis = yAxis;
+ yAxis = this.xAxis;
+ }
+ return {
+ translateX: xAxis ? xAxis.left : chart.plotLeft,
+ translateY: yAxis ? yAxis.top : chart.plotTop,
+ scaleX: 1, // #1623
+ scaleY: 1
+ };
+ },
+
+ /**
+ * Render the graph and markers. Called internally when first rendering and
+ * later when redrawing the chart. This function can be extended in plugins,
+ * but normally shouldn't be called directly.
+ */
+ render: function () {
+ var series = this,
+ chart = series.chart,
+ group,
+ options = series.options,
+ // Animation doesn't work in IE8 quirks when the group div is
+ // hidden, and looks bad in other oldIE
+ animDuration = (
+ !!series.animate &&
+ chart.renderer.isSVG &&
+ animObject(options.animation).duration
+ ),
+ visibility = series.visible ? 'inherit' : 'hidden', // #2597
+ zIndex = options.zIndex,
+ hasRendered = series.hasRendered,
+ chartSeriesGroup = chart.seriesGroup,
+ inverted = chart.inverted;
+
+ // the group
+ group = series.plotGroup(
+ 'group',
+ 'series',
+ visibility,
+ zIndex,
+ chartSeriesGroup
+ );
+
+ series.markerGroup = series.plotGroup(
+ 'markerGroup',
+ 'markers',
+ visibility,
+ zIndex,
+ chartSeriesGroup
+ );
+
+ // initiate the animation
+ if (animDuration) {
+ series.animate(true);
+ }
+
+ // SVGRenderer needs to know this before drawing elements (#1089, #1795)
+ group.inverted = series.isCartesian ? inverted : false;
+
+ // draw the graph if any
+ if (series.drawGraph) {
+ series.drawGraph();
+ series.applyZones();
+ }
+
+/* each(series.points, function (point) {
+ if (point.redraw) {
+ point.redraw();
+ }
+ });*/
+
+ // draw the data labels (inn pies they go before the points)
+ if (series.drawDataLabels) {
+ series.drawDataLabels();
+ }
+
+ // draw the points
+ if (series.visible) {
+ series.drawPoints();
+ }
+
+
+ // draw the mouse tracking area
+ if (
+ series.drawTracker &&
+ series.options.enableMouseTracking !== false
+ ) {
+ series.drawTracker();
+ }
+
+ // Handle inverted series and tracker groups
+ series.invertGroups(inverted);
+
+ // Initial clipping, must be defined after inverting groups for VML.
+ // Applies to columns etc. (#3839).
+ if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
+ group.clip(chart.clipRect);
+ }
+
+ // Run the animation
+ if (animDuration) {
+ series.animate();
+ }
+
+ // Call the afterAnimate function on animation complete (but don't
+ // overwrite the animation.complete option which should be available to
+ // the user).
+ if (!hasRendered) {
+ series.animationTimeout = syncTimeout(function () {
+ series.afterAnimate();
+ }, animDuration);
+ }
+
+ series.isDirty = false; // means data is in accordance with what you see
+ // (See #322) series.isDirty = series.isDirtyData = false; // means
+ // data is in accordance with what you see
+ series.hasRendered = true;
+ },
+
+ /**
+ * Redraw the series. This function is called internally from `chart.redraw`
+ * and normally shouldn't be called directly.
+ *
+ * @private
+ */
+ redraw: function () {
+ var series = this,
+ chart = series.chart,
+ // cache it here as it is set to false in render, but used after
+ wasDirty = series.isDirty || series.isDirtyData,
+ group = series.group,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis;
+
+ // reposition on resize
+ if (group) {
+ if (chart.inverted) {
+ group.attr({
+ width: chart.plotWidth,
+ height: chart.plotHeight
+ });
+ }
+
+ group.animate({
+ translateX: pick(xAxis && xAxis.left, chart.plotLeft),
+ translateY: pick(yAxis && yAxis.top, chart.plotTop)
+ });
+ }
+
+ series.translate();
+ series.render();
+ if (wasDirty) { // #3868, #3945
+ delete this.kdTree;
+ }
+ },
+
+ kdAxisArray: ['clientX', 'plotY'],
+
+ searchPoint: function (e, compareX) {
+ var series = this,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis,
+ inverted = series.chart.inverted;
+
+ return this.searchKDTree({
+ clientX: inverted ?
+ xAxis.len - e.chartY + xAxis.pos :
+ e.chartX - xAxis.pos,
+ plotY: inverted ?
+ yAxis.len - e.chartX + yAxis.pos :
+ e.chartY - yAxis.pos
+ }, compareX);
+ },
+
+ /**
+ * Build the k-d-tree that is used by mouse and touch interaction to get the
+ * closest point. Line-like series typically have a one-dimensional tree
+ * where points are searched along the X axis, while scatter-like series
+ * typically search in two dimensions, X and Y.
+ *
+ * @private
+ */
+ buildKDTree: function () {
+
+ // Prevent multiple k-d-trees from being built simultaneously (#6235)
+ this.buildingKdTree = true;
+
+ var series = this,
+ dimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
+ 2 : 1;
+
+ // Internal function
+ function _kdtree(points, depth, dimensions) {
+ var axis,
+ median,
+ length = points && points.length;
+
+ if (length) {
+
+ // alternate between the axis
+ axis = series.kdAxisArray[depth % dimensions];
+
+ // sort point array
+ points.sort(function (a, b) {
+ return a[axis] - b[axis];
+ });
+
+ median = Math.floor(length / 2);
+
+ // build and return nod
+ return {
+ point: points[median],
+ left: _kdtree(
+ points.slice(0, median), depth + 1, dimensions
+ ),
+ right: _kdtree(
+ points.slice(median + 1), depth + 1, dimensions
+ )
+ };
+
+ }
+ }
+
+ // Start the recursive build process with a clone of the points array
+ // and null points filtered out (#3873)
+ function startRecursive() {
+ series.kdTree = _kdtree(
+ series.getValidPoints(
+ null,
+ // For line-type series restrict to plot area, but
+ // column-type series not (#3916, #4511)
+ !series.directTouch
+ ),
+ dimensions,
+ dimensions
+ );
+ series.buildingKdTree = false;
+ }
+ delete series.kdTree;
+
+ // For testing tooltips, don't build async
+ syncTimeout(startRecursive, series.options.kdNow ? 0 : 1);
+ },
+
+ searchKDTree: function (point, compareX) {
+ var series = this,
+ kdX = this.kdAxisArray[0],
+ kdY = this.kdAxisArray[1],
+ kdComparer = compareX ? 'distX' : 'dist',
+ kdDimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
+ 2 : 1;
+
+ // Set the one and two dimensional distance on the point object
+ function setDistance(p1, p2) {
+ var x = (defined(p1[kdX]) && defined(p2[kdX])) ?
+ Math.pow(p1[kdX] - p2[kdX], 2) :
+ null,
+ y = (defined(p1[kdY]) && defined(p2[kdY])) ?
+ Math.pow(p1[kdY] - p2[kdY], 2) :
+ null,
+ r = (x || 0) + (y || 0);
+
+ p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE;
+ p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE;
+ }
+ function _search(search, tree, depth, dimensions) {
+ var point = tree.point,
+ axis = series.kdAxisArray[depth % dimensions],
+ tdist,
+ sideA,
+ sideB,
+ ret = point,
+ nPoint1,
+ nPoint2;
+
+ setDistance(search, point);
+
+ // Pick side based on distance to splitting point
+ tdist = search[axis] - point[axis];
+ sideA = tdist < 0 ? 'left' : 'right';
+ sideB = tdist < 0 ? 'right' : 'left';
+
+ // End of tree
+ if (tree[sideA]) {
+ nPoint1 = _search(search, tree[sideA], depth + 1, dimensions);
+
+ ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point);
+ }
+ if (tree[sideB]) {
+ // compare distance to current best to splitting point to decide
+ // wether to check side B or not
+ if (Math.sqrt(tdist * tdist) < ret[kdComparer]) {
+ nPoint2 = _search(
+ search,
+ tree[sideB],
+ depth + 1,
+ dimensions
+ );
+ ret = nPoint2[kdComparer] < ret[kdComparer] ?
+ nPoint2 :
+ ret;
+ }
+ }
+
+ return ret;
+ }
+
+ if (!this.kdTree && !this.buildingKdTree) {
+ this.buildKDTree();
+ }
+
+ if (this.kdTree) {
+ return _search(point, this.kdTree, kdDimensions, kdDimensions);
+ }
+ }
+
+}); // end Series prototype
+
+/**
+ * A line series displays information as a series of data points connected by
+ * straight line segments.
+ *
+ * @sample {highcharts} highcharts/demo/line-basic/ Line chart
+ * @sample {highstock} stock/demo/basic-line/ Line chart
+ *
+ * @extends plotOptions.series
+ * @product highcharts highstock
+ * @apioption plotOptions.line
+ */
+
+/**
+ * A `line` series. If the [type](#series.line.type) option is not
+ * specified, it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * line](#plotOptions.line).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.line
+ * @excluding dataParser,dataURL
+ * @product highcharts highstock
+ * @apioption series.line
+ */
+
+/**
+ * An array of data points for the series. For the `line` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values
+ * will be interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options. If the axis has
+ * categories, these will be used. Example:
+ *
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond
+ * to `x,y`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred.
+ *
+ * ```js
+ * data: [
+ * [0, 1],
+ * [1, 2],
+ * [2, 8]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.line.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 6,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
+ * @apioption series.line.data
+ */
+
+/**
+ * An additional, individual class name for the data point's graphic
+ * representation.
+ *
+ * @type {String}
+ * @since 5.0.0
+ * @product highcharts
+ * @apioption series.line.data.className
+ */
+
+/**
+ * Individual color for the point. By default the color is pulled from
+ * the global `colors` array.
+ *
+ * In styled mode, the `color` option doesn't take effect. Instead, use
+ * `colorIndex`.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/point/color/ Mark the highest point
+ * @default undefined
+ * @product highcharts highstock
+ * @apioption series.line.data.color
+ */
+
+/**
+ * Styled mode only. A specific color index to use for the point, so its
+ * graphic representations are given the class name
+ * `highcharts-color-{n}`.
+ *
+ * @type {Number}
+ * @since 5.0.0
+ * @product highcharts
+ * @apioption series.line.data.colorIndex
+ */
+
+/**
+ * Individual data label for each point. The options are the same as
+ * the ones for [plotOptions.series.dataLabels](#plotOptions.series.
+ * dataLabels)
+ *
+ * @type {Object}
+ * @sample {highcharts} highcharts/point/datalabels/ Show a label for the last value
+ * @sample {highstock} highcharts/point/datalabels/ Show a label for the last value
+ * @product highcharts highstock
+ * @apioption series.line.data.dataLabels
+ */
+
+/**
+ * A description of the point to add to the screen reader information
+ * about the point. Requires the Accessibility module.
+ *
+ * @type {String}
+ * @default undefined
+ * @since 5.0.0
+ * @apioption series.line.data.description
+ */
+
+/**
+ * An id for the point. This can be used after render time to get a
+ * pointer to the point object through `chart.get()`.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/point/id/ Remove an id'd point
+ * @default null
+ * @since 1.2.0
+ * @product highcharts highstock
+ * @apioption series.line.data.id
+ */
+
+/**
+ * The rank for this point's data label in case of collision. If two
+ * data labels are about to overlap, only the one with the highest `labelrank`
+ * will be drawn.
+ *
+ * @type {Number}
+ * @apioption series.line.data.labelrank
+ */
+
+/**
+ * The name of the point as shown in the legend, tooltip, dataLabel
+ * etc.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/series/data-array-of-objects/ Point names
+ * @see [xAxis.uniqueNames](#xAxis.uniqueNames)
+ * @apioption series.line.data.name
+ */
+
+/**
+ * Whether the data point is selected initially.
+ *
+ * @type {Boolean}
+ * @default false
+ * @product highcharts highstock
+ * @apioption series.line.data.selected
+ */
+
+/**
+ * The x value of the point. For datetime axes, the X value is the timestamp
+ * in milliseconds since 1970.
+ *
+ * @type {Number}
+ * @product highcharts highstock
+ * @apioption series.line.data.x
+ */
+
+/**
+ * The y value of the point.
+ *
+ * @type {Number}
+ * @default null
+ * @product highcharts highstock
+ * @apioption series.line.data.y
+ */
+
+/**
+ * Individual point events
+ *
+ * @extends plotOptions.series.point.events
+ * @product highcharts highstock
+ * @apioption series.line.data.events
+ */
+
+/**
+ * @extends plotOptions.series.marker
+ * @product highcharts highstock
+ * @apioption series.line.data.marker
+ */
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var Axis = H.Axis,
+ Chart = H.Chart,
+ correctFloat = H.correctFloat,
+ defined = H.defined,
+ destroyObjectProperties = H.destroyObjectProperties,
+ each = H.each,
+ format = H.format,
+ objectEach = H.objectEach,
+ pick = H.pick,
+ Series = H.Series;
+
+/**
+ * The class for stacks. Each stack, on a specific X value and either negative
+ * or positive, has its own stack item.
+ *
+ * @class
+ */
+H.StackItem = function (axis, options, isNegative, x, stackOption) {
+
+ var inverted = axis.chart.inverted;
+
+ this.axis = axis;
+
+ // Tells if the stack is negative
+ this.isNegative = isNegative;
+
+ // Save the options to be able to style the label
+ this.options = options;
+
+ // Save the x value to be able to position the label later
+ this.x = x;
+
+ // Initialize total value
+ this.total = null;
+
+ // This will keep each points' extremes stored by series.index and point
+ // index
+ this.points = {};
+
+ // Save the stack option on the series configuration object, and whether to
+ // treat it as percent
+ this.stack = stackOption;
+ this.leftCliff = 0;
+ this.rightCliff = 0;
+
+ // The align options and text align varies on whether the stack is negative
+ // and if the chart is inverted or not.
+ // First test the user supplied value, then use the dynamic.
+ this.alignOptions = {
+ align: options.align ||
+ (inverted ? (isNegative ? 'left' : 'right') : 'center'),
+ verticalAlign: options.verticalAlign ||
+ (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
+ y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
+ x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
+ };
+
+ this.textAlign = options.textAlign ||
+ (inverted ? (isNegative ? 'right' : 'left') : 'center');
+};
+
+H.StackItem.prototype = {
+ destroy: function () {
+ destroyObjectProperties(this, this.axis);
+ },
+
+ /**
+ * Renders the stack total label and adds it to the stack label group.
+ */
+ render: function (group) {
+ var chart = this.axis.chart,
+ options = this.options,
+ formatOption = options.format,
+ str = formatOption ?
+ format(formatOption, this, chart.time) :
+ options.formatter.call(this); // format the text in the label
+
+ // Change the text to reflect the new total and set visibility to hidden
+ // in case the serie is hidden
+ if (this.label) {
+ this.label.attr({ text: str, visibility: 'hidden' });
+ // Create new label
+ } else {
+ this.label =
+ chart.renderer.text(str, null, null, options.useHTML)
+ .css(options.style)
+ .attr({
+ align: this.textAlign,
+ rotation: options.rotation,
+ visibility: 'hidden' // hidden until setOffset is called
+ })
+ .add(group); // add to the labels-group
+ }
+ },
+
+ /**
+ * Sets the offset that the stack has from the x value and repositions the
+ * label.
+ */
+ setOffset: function (xOffset, xWidth) {
+ var stackItem = this,
+ axis = stackItem.axis,
+ chart = axis.chart,
+ // stack value translated mapped to chart coordinates
+ y = axis.translate(
+ axis.usePercentage ? 100 : stackItem.total,
+ 0,
+ 0,
+ 0,
+ 1
+ ),
+ yZero = axis.translate(0), // stack origin
+ h = Math.abs(y - yZero), // stack height
+ x = chart.xAxis[0].translate(stackItem.x) + xOffset, // x position
+ stackBox = stackItem.getStackBox(chart, stackItem, x, y, xWidth, h),
+ label = stackItem.label,
+ alignAttr;
+
+ if (label) {
+ // Align the label to the box
+ label.align(stackItem.alignOptions, null, stackBox);
+
+ // Set visibility (#678)
+ alignAttr = label.alignAttr;
+ label[
+ stackItem.options.crop === false || chart.isInsidePlot(
+ alignAttr.x,
+ alignAttr.y
+ ) ? 'show' : 'hide'](true);
+ }
+ },
+ getStackBox: function (chart, stackItem, x, y, xWidth, h) {
+ var reversed = stackItem.axis.reversed,
+ inverted = chart.inverted,
+ plotHeight = chart.plotHeight,
+ neg = (stackItem.isNegative && !reversed) ||
+ (!stackItem.isNegative && reversed); // #4056
+
+ return { // this is the box for the complete stack
+ x: inverted ? (neg ? y : y - h) : x,
+ y: inverted ?
+ plotHeight - x - xWidth :
+ (neg ?
+ (plotHeight - y - h) :
+ plotHeight - y
+ ),
+ width: inverted ? h : xWidth,
+ height: inverted ? xWidth : h
+ };
+ }
+};
+
+/**
+ * Generate stacks for each series and calculate stacks total values
+ */
+Chart.prototype.getStacks = function () {
+ var chart = this;
+
+ // reset stacks for each yAxis
+ each(chart.yAxis, function (axis) {
+ if (axis.stacks && axis.hasVisibleSeries) {
+ axis.oldStacks = axis.stacks;
+ }
+ });
+
+ each(chart.series, function (series) {
+ if (series.options.stacking && (series.visible === true ||
+ chart.options.chart.ignoreHiddenSeries === false)) {
+ series.stackKey = series.type + pick(series.options.stack, '');
+ }
+ });
+};
+
+
+// Stacking methods defined on the Axis prototype
+
+/**
+ * Build the stacks from top down
+ */
+Axis.prototype.buildStacks = function () {
+ var axisSeries = this.series,
+ reversedStacks = pick(this.options.reversedStacks, true),
+ len = axisSeries.length,
+ i;
+ if (!this.isXAxis) {
+ this.usePercentage = false;
+ i = len;
+ while (i--) {
+ axisSeries[reversedStacks ? i : len - i - 1].setStackedPoints();
+ }
+
+ // Loop up again to compute percent and stream stack
+ for (i = 0; i < len; i++) {
+ axisSeries[i].modifyStacks();
+ }
+ }
+};
+
+Axis.prototype.renderStackTotals = function () {
+ var axis = this,
+ chart = axis.chart,
+ renderer = chart.renderer,
+ stacks = axis.stacks,
+ stackTotalGroup = axis.stackTotalGroup;
+
+ // Create a separate group for the stack total labels
+ if (!stackTotalGroup) {
+ axis.stackTotalGroup = stackTotalGroup =
+ renderer.g('stack-labels')
+ .attr({
+ visibility: 'visible',
+ zIndex: 6
+ })
+ .add();
+ }
+
+ // plotLeft/Top will change when y axis gets wider so we need to translate
+ // the stackTotalGroup at every render call. See bug #506 and #516
+ stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
+
+ // Render each stack total
+ objectEach(stacks, function (type) {
+ objectEach(type, function (stack) {
+ stack.render(stackTotalGroup);
+ });
+ });
+};
+
+/**
+ * Set all the stacks to initial states and destroy unused ones.
+ */
+Axis.prototype.resetStacks = function () {
+ var axis = this,
+ stacks = axis.stacks;
+ if (!axis.isXAxis) {
+ objectEach(stacks, function (type) {
+ objectEach(type, function (stack, key) {
+ // Clean up memory after point deletion (#1044, #4320)
+ if (stack.touched < axis.stacksTouched) {
+ stack.destroy();
+ delete type[key];
+
+ // Reset stacks
+ } else {
+ stack.total = null;
+ stack.cumulative = null;
+ }
+ });
+ });
+ }
+};
+
+Axis.prototype.cleanStacks = function () {
+ var stacks;
+
+ if (!this.isXAxis) {
+ if (this.oldStacks) {
+ stacks = this.stacks = this.oldStacks;
+ }
+
+ // reset stacks
+ objectEach(stacks, function (type) {
+ objectEach(type, function (stack) {
+ stack.cumulative = stack.total;
+ });
+ });
+ }
+};
+
+
+// Stacking methods defnied for Series prototype
+
+/**
+ * Adds series' points value to corresponding stack
+ */
+Series.prototype.setStackedPoints = function () {
+ if (!this.options.stacking || (this.visible !== true &&
+ this.chart.options.chart.ignoreHiddenSeries !== false)) {
+ return;
+ }
+
+ var series = this,
+ xData = series.processedXData,
+ yData = series.processedYData,
+ stackedYData = [],
+ yDataLength = yData.length,
+ seriesOptions = series.options,
+ threshold = seriesOptions.threshold,
+ stackThreshold = pick(seriesOptions.startFromThreshold && threshold, 0),
+ stackOption = seriesOptions.stack,
+ stacking = seriesOptions.stacking,
+ stackKey = series.stackKey,
+ negKey = '-' + stackKey,
+ negStacks = series.negStacks,
+ yAxis = series.yAxis,
+ stacks = yAxis.stacks,
+ oldStacks = yAxis.oldStacks,
+ stackIndicator,
+ isNegative,
+ stack,
+ other,
+ key,
+ pointKey,
+ i,
+ x,
+ y;
+
+
+ yAxis.stacksTouched += 1;
+
+ // loop over the non-null y values and read them into a local array
+ for (i = 0; i < yDataLength; i++) {
+ x = xData[i];
+ y = yData[i];
+ stackIndicator = series.getStackIndicator(
+ stackIndicator,
+ x,
+ series.index
+ );
+ pointKey = stackIndicator.key;
+ // Read stacked values into a stack based on the x value,
+ // the sign of y and the stack key. Stacking is also handled for null
+ // values (#739)
+ isNegative = negStacks && y < (stackThreshold ? 0 : threshold);
+ key = isNegative ? negKey : stackKey;
+
+ // Create empty object for this stack if it doesn't exist yet
+ if (!stacks[key]) {
+ stacks[key] = {};
+ }
+
+ // Initialize StackItem for this x
+ if (!stacks[key][x]) {
+ if (oldStacks[key] && oldStacks[key][x]) {
+ stacks[key][x] = oldStacks[key][x];
+ stacks[key][x].total = null;
+ } else {
+ stacks[key][x] = new H.StackItem(
+ yAxis,
+ yAxis.options.stackLabels,
+ isNegative,
+ x,
+ stackOption
+ );
+ }
+ }
+
+ // If the StackItem doesn't exist, create it first
+ stack = stacks[key][x];
+ if (y !== null) {
+ stack.points[pointKey] = stack.points[series.index] =
+ [pick(stack.cumulative, stackThreshold)];
+
+ // Record the base of the stack
+ if (!defined(stack.cumulative)) {
+ stack.base = pointKey;
+ }
+ stack.touched = yAxis.stacksTouched;
+
+
+ // In area charts, if there are multiple points on the same X value,
+ // let the area fill the full span of those points
+ if (stackIndicator.index > 0 && series.singleStacks === false) {
+ stack.points[pointKey][0] =
+ stack.points[series.index + ',' + x + ',0'][0];
+ }
+
+ // When updating to null, reset the point stack (#7493)
+ } else {
+ stack.points[pointKey] = stack.points[series.index] = null;
+ }
+
+ // Add value to the stack total
+ if (stacking === 'percent') {
+
+ // Percent stacked column, totals are the same for the positive and
+ // negative stacks
+ other = isNegative ? stackKey : negKey;
+ if (negStacks && stacks[other] && stacks[other][x]) {
+ other = stacks[other][x];
+ stack.total = other.total =
+ Math.max(other.total, stack.total) + Math.abs(y) || 0;
+
+ // Percent stacked areas
+ } else {
+ stack.total = correctFloat(stack.total + (Math.abs(y) || 0));
+ }
+ } else {
+ stack.total = correctFloat(stack.total + (y || 0));
+ }
+
+ stack.cumulative = pick(stack.cumulative, stackThreshold) + (y || 0);
+
+ if (y !== null) {
+ stack.points[pointKey].push(stack.cumulative);
+ stackedYData[i] = stack.cumulative;
+ }
+
+ }
+
+ if (stacking === 'percent') {
+ yAxis.usePercentage = true;
+ }
+
+ this.stackedYData = stackedYData; // To be used in getExtremes
+
+ // Reset old stacks
+ yAxis.oldStacks = {};
+};
+
+/**
+ * Iterate over all stacks and compute the absolute values to percent
+ */
+Series.prototype.modifyStacks = function () {
+ var series = this,
+ stackKey = series.stackKey,
+ stacks = series.yAxis.stacks,
+ processedXData = series.processedXData,
+ stackIndicator,
+ stacking = series.options.stacking;
+
+ if (series[stacking + 'Stacker']) { // Modifier function exists
+ each([stackKey, '-' + stackKey], function (key) {
+ var i = processedXData.length,
+ x,
+ stack,
+ pointExtremes;
+
+ while (i--) {
+ x = processedXData[i];
+ stackIndicator = series.getStackIndicator(
+ stackIndicator,
+ x,
+ series.index,
+ key
+ );
+ stack = stacks[key] && stacks[key][x];
+ pointExtremes = stack && stack.points[stackIndicator.key];
+ if (pointExtremes) {
+ series[stacking + 'Stacker'](pointExtremes, stack, i);
+ }
+ }
+ });
+ }
+};
+
+/**
+ * Modifier function for percent stacks. Blows up the stack to 100%.
+ */
+Series.prototype.percentStacker = function (pointExtremes, stack, i) {
+ var totalFactor = stack.total ? 100 / stack.total : 0;
+ // Y bottom value
+ pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor);
+ // Y value
+ pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor);
+ this.stackedYData[i] = pointExtremes[1];
+};
+
+/**
+* Get stack indicator, according to it's x-value, to determine points with the
+* same x-value
+*/
+Series.prototype.getStackIndicator = function (stackIndicator, x, index, key) {
+ // Update stack indicator, when:
+ // first point in a stack || x changed || stack type (negative vs positive)
+ // changed:
+ if (!defined(stackIndicator) || stackIndicator.x !== x ||
+ (key && stackIndicator.key !== key)) {
+ stackIndicator = {
+ x: x,
+ index: 0,
+ key: key
+ };
+ } else {
+ stackIndicator.index++;
+ }
+
+ stackIndicator.key = [index, x, stackIndicator.index].join(',');
+
+ return stackIndicator;
+};
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ animate = H.animate,
+ Axis = H.Axis,
+ Chart = H.Chart,
+ createElement = H.createElement,
+ css = H.css,
+ defined = H.defined,
+ each = H.each,
+ erase = H.erase,
+ extend = H.extend,
+ fireEvent = H.fireEvent,
+ inArray = H.inArray,
+ isNumber = H.isNumber,
+ isObject = H.isObject,
+ isArray = H.isArray,
+ merge = H.merge,
+ objectEach = H.objectEach,
+ pick = H.pick,
+ Point = H.Point,
+ Series = H.Series,
+ seriesTypes = H.seriesTypes,
+ setAnimation = H.setAnimation,
+ splat = H.splat;
+
+// Extend the Chart prototype for dynamic methods
+extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {
+
+ /**
+ * Add a series to the chart after render time. Note that this method should
+ * never be used when adding data synchronously at chart render time, as it
+ * adds expense to the calculations and rendering. When adding data at the
+ * same time as the chart is initialized, add the series as a configuration
+ * option instead. With multiple axes, the `offset` is dynamically adjusted.
+ *
+ * @param {SeriesOptions} options
+ * The config options for the series.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after adding.
+ * @param {AnimationOptions} animation
+ * Whether to apply animation, and optionally animation
+ * configuration.
+ *
+ * @return {Highcharts.Series}
+ * The newly created series object.
+ *
+ * @sample highcharts/members/chart-addseries/
+ * Add a series from a button
+ * @sample stock/members/chart-addseries/
+ * Add a series in Highstock
+ */
+ addSeries: function (options, redraw, animation) {
+ var series,
+ chart = this;
+
+ if (options) {
+ redraw = pick(redraw, true); // defaults to true
+
+ fireEvent(chart, 'addSeries', { options: options }, function () {
+ series = chart.initSeries(options);
+
+ chart.isDirtyLegend = true; // the series array is out of sync with the display
+ chart.linkSeries();
+ if (redraw) {
+ chart.redraw(animation);
+ }
+ });
+ }
+
+ return series;
+ },
+
+ /**
+ * Add an axis to the chart after render time. Note that this method should
+ * never be used when adding data synchronously at chart render time, as it
+ * adds expense to the calculations and rendering. When adding data at the
+ * same time as the chart is initialized, add the axis as a configuration
+ * option instead.
+ * @param {AxisOptions} options
+ * The axis options.
+ * @param {Boolean} [isX=false]
+ * Whether it is an X axis or a value axis.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after adding.
+ * @param {AnimationOptions} [animation=true]
+ * Whether and how to apply animation in the redraw.
+ *
+ * @sample highcharts/members/chart-addaxis/ Add and remove axes
+ *
+ * @return {Axis}
+ * The newly generated Axis object.
+ */
+ addAxis: function (options, isX, redraw, animation) {
+ var key = isX ? 'xAxis' : 'yAxis',
+ chartOptions = this.options,
+ userOptions = merge(options, {
+ index: this[key].length,
+ isX: isX
+ }),
+ axis;
+
+ axis = new Axis(this, userOptions);
+
+ // Push the new axis options to the chart options
+ chartOptions[key] = splat(chartOptions[key] || {});
+ chartOptions[key].push(userOptions);
+
+ if (pick(redraw, true)) {
+ this.redraw(animation);
+ }
+
+ return axis;
+ },
+
+ /**
+ * Dim the chart and show a loading text or symbol. Options for the loading
+ * screen are defined in {@link
+ * https://api.highcharts.com/highcharts/loading|the loading options}.
+ *
+ * @param {String} str
+ * An optional text to show in the loading label instead of the
+ * default one. The default text is set in {@link
+ * http://api.highcharts.com/highcharts/lang.loading|lang.loading}.
+ *
+ * @sample highcharts/members/chart-hideloading/
+ * Show and hide loading from a button
+ * @sample highcharts/members/chart-showloading/
+ * Apply different text labels
+ * @sample stock/members/chart-show-hide-loading/
+ * Toggle loading in Highstock
+ */
+ showLoading: function (str) {
+ var chart = this,
+ options = chart.options,
+ loadingDiv = chart.loadingDiv,
+ loadingOptions = options.loading,
+ setLoadingSize = function () {
+ if (loadingDiv) {
+ css(loadingDiv, {
+ left: chart.plotLeft + 'px',
+ top: chart.plotTop + 'px',
+ width: chart.plotWidth + 'px',
+ height: chart.plotHeight + 'px'
+ });
+ }
+ };
+
+ // create the layer at the first call
+ if (!loadingDiv) {
+ chart.loadingDiv = loadingDiv = createElement('div', {
+ className: 'highcharts-loading highcharts-loading-hidden'
+ }, null, chart.container);
+
+ chart.loadingSpan = createElement(
+ 'span',
+ { className: 'highcharts-loading-inner' },
+ null,
+ loadingDiv
+ );
+ addEvent(chart, 'redraw', setLoadingSize); // #1080
+ }
+
+ loadingDiv.className = 'highcharts-loading';
+
+ // Update text
+ chart.loadingSpan.innerHTML = str || options.lang.loading;
+
+
+ // Update visuals
+ css(loadingDiv, extend(loadingOptions.style, {
+ zIndex: 10
+ }));
+ css(chart.loadingSpan, loadingOptions.labelStyle);
+
+ // Show it
+ if (!chart.loadingShown) {
+ css(loadingDiv, {
+ opacity: 0,
+ display: ''
+ });
+ animate(loadingDiv, {
+ opacity: loadingOptions.style.opacity || 0.5
+ }, {
+ duration: loadingOptions.showDuration || 0
+ });
+ }
+
+
+ chart.loadingShown = true;
+ setLoadingSize();
+ },
+
+ /**
+ * Hide the loading layer.
+ *
+ * @see Highcharts.Chart#showLoading
+ * @sample highcharts/members/chart-hideloading/
+ * Show and hide loading from a button
+ * @sample stock/members/chart-show-hide-loading/
+ * Toggle loading in Highstock
+ */
+ hideLoading: function () {
+ var options = this.options,
+ loadingDiv = this.loadingDiv;
+
+ if (loadingDiv) {
+ loadingDiv.className = 'highcharts-loading highcharts-loading-hidden';
+
+ animate(loadingDiv, {
+ opacity: 0
+ }, {
+ duration: options.loading.hideDuration || 100,
+ complete: function () {
+ css(loadingDiv, { display: 'none' });
+ }
+ });
+
+ }
+ this.loadingShown = false;
+ },
+
+ /**
+ * These properties cause isDirtyBox to be set to true when updating. Can be extended from plugins.
+ */
+ propsRequireDirtyBox: ['backgroundColor', 'borderColor', 'borderWidth', 'margin', 'marginTop', 'marginRight',
+ 'marginBottom', 'marginLeft', 'spacing', 'spacingTop', 'spacingRight', 'spacingBottom', 'spacingLeft',
+ 'borderRadius', 'plotBackgroundColor', 'plotBackgroundImage', 'plotBorderColor', 'plotBorderWidth',
+ 'plotShadow', 'shadow'],
+
+ /**
+ * These properties cause all series to be updated when updating. Can be
+ * extended from plugins.
+ */
+ propsRequireUpdateSeries: ['chart.inverted', 'chart.polar',
+ 'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions',
+ 'time', 'tooltip'],
+
+ /**
+ * A generic function to update any element of the chart. Elements can be
+ * enabled and disabled, moved, re-styled, re-formatted etc.
+ *
+ * A special case is configuration objects that take arrays, for example
+ * {@link https://api.highcharts.com/highcharts/xAxis|xAxis},
+ * {@link https://api.highcharts.com/highcharts/yAxis|yAxis} or
+ * {@link https://api.highcharts.com/highcharts/series|series}. For these
+ * collections, an `id` option is used to map the new option set to an
+ * existing object. If an existing object of the same id is not found, the
+ * corresponding item is updated. So for example, running `chart.update`
+ * with a series item without an id, will cause the existing chart's series
+ * with the same index in the series array to be updated. When the
+ * `oneToOne` parameter is true, `chart.update` will also take care of
+ * adding and removing items from the collection. Read more under the
+ * parameter description below.
+ *
+ * See also the {@link https://api.highcharts.com/highcharts/responsive|
+ * responsive option set}. Switching between `responsive.rules` basically
+ * runs `chart.update` under the hood.
+ *
+ * @param {Options} options
+ * A configuration object for the new chart options.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart.
+ * @param {Boolean} [oneToOne=false]
+ * When `true`, the `series`, `xAxis` and `yAxis` collections will
+ * be updated one to one, and items will be either added or removed
+ * to match the new updated options. For example, if the chart has
+ * two series and we call `chart.update` with a configuration
+ * containing three series, one will be added. If we call
+ * `chart.update` with one series, one will be removed. Setting an
+ * empty `series` array will remove all series, but leaving out the
+ * `series` property will leave all series untouched. If the series
+ * have id's, the new series options will be matched by id, and the
+ * remaining ones removed.
+ *
+ * @sample highcharts/members/chart-update/
+ * Update chart geometry
+ */
+ update: function (options, redraw, oneToOne) {
+ var chart = this,
+ adders = {
+ credits: 'addCredits',
+ title: 'setTitle',
+ subtitle: 'setSubtitle'
+ },
+ optionsChart = options.chart,
+ updateAllAxes,
+ updateAllSeries,
+ newWidth,
+ newHeight,
+ itemsForRemoval = [];
+
+ // If the top-level chart option is present, some special updates are required
+ if (optionsChart) {
+ merge(true, chart.options.chart, optionsChart);
+
+ // Setter function
+ if ('className' in optionsChart) {
+ chart.setClassName(optionsChart.className);
+ }
+
+ if ('inverted' in optionsChart || 'polar' in optionsChart) {
+ // Parse options.chart.inverted and options.chart.polar together
+ // with the available series.
+ chart.propFromSeries();
+ updateAllAxes = true;
+ }
+
+ if ('alignTicks' in optionsChart) { // #6452
+ updateAllAxes = true;
+ }
+
+ objectEach(optionsChart, function (val, key) {
+ if (inArray('chart.' + key, chart.propsRequireUpdateSeries) !== -1) {
+ updateAllSeries = true;
+ }
+ // Only dirty box
+ if (inArray(key, chart.propsRequireDirtyBox) !== -1) {
+ chart.isDirtyBox = true;
+ }
+ });
+
+
+ if ('style' in optionsChart) {
+ chart.renderer.setStyle(optionsChart.style);
+ }
+
+ }
+
+ // Moved up, because tooltip needs updated plotOptions (#6218)
+
+ if (options.colors) {
+ this.options.colors = options.colors;
+ }
+
+
+ if (options.plotOptions) {
+ merge(true, this.options.plotOptions, options.plotOptions);
+ }
+
+ // Some option stuctures correspond one-to-one to chart objects that
+ // have update methods, for example
+ // options.credits => chart.credits
+ // options.legend => chart.legend
+ // options.title => chart.title
+ // options.tooltip => chart.tooltip
+ // options.subtitle => chart.subtitle
+ // options.mapNavigation => chart.mapNavigation
+ // options.navigator => chart.navigator
+ // options.scrollbar => chart.scrollbar
+ objectEach(options, function (val, key) {
+ if (chart[key] && typeof chart[key].update === 'function') {
+ chart[key].update(val, false);
+
+ // If a one-to-one object does not exist, look for an adder function
+ } else if (typeof chart[adders[key]] === 'function') {
+ chart[adders[key]](val);
+ }
+
+ if (
+ key !== 'chart' &&
+ inArray(key, chart.propsRequireUpdateSeries) !== -1
+ ) {
+ updateAllSeries = true;
+ }
+ });
+
+ // Setters for collections. For axes and series, each item is referred
+ // by an id. If the id is not found, it defaults to the corresponding
+ // item in the collection, so setting one series without an id, will
+ // update the first series in the chart. Setting two series without
+ // an id will update the first and the second respectively (#6019)
+ // chart.update and responsive.
+ each([
+ 'xAxis',
+ 'yAxis',
+ 'zAxis',
+ 'series',
+ 'colorAxis',
+ 'pane'
+ ], function (coll) {
+ if (options[coll]) {
+ each(splat(options[coll]), function (newOptions, i) {
+ var item = (
+ defined(newOptions.id) &&
+ chart.get(newOptions.id)
+ ) || chart[coll][i];
+ if (item && item.coll === coll) {
+ item.update(newOptions, false);
+
+ if (oneToOne) {
+ item.touched = true;
+ }
+ }
+
+ // If oneToOne and no matching item is found, add one
+ if (!item && oneToOne) {
+ if (coll === 'series') {
+ chart.addSeries(newOptions, false)
+ .touched = true;
+ } else if (coll === 'xAxis' || coll === 'yAxis') {
+ chart.addAxis(newOptions, coll === 'xAxis', false)
+ .touched = true;
+ }
+ }
+
+ });
+
+ // Add items for removal
+ if (oneToOne) {
+ each(chart[coll], function (item) {
+ if (!item.touched) {
+ itemsForRemoval.push(item);
+ } else {
+ delete item.touched;
+ }
+ });
+ }
+
+
+ }
+ });
+
+ each(itemsForRemoval, function (item) {
+ item.remove(false);
+ });
+
+ if (updateAllAxes) {
+ each(chart.axes, function (axis) {
+ axis.update({}, false);
+ });
+ }
+
+ // Certain options require the whole series structure to be thrown away
+ // and rebuilt
+ if (updateAllSeries) {
+ each(chart.series, function (series) {
+ series.update({}, false);
+ });
+ }
+
+ // For loading, just update the options, do not redraw
+ if (options.loading) {
+ merge(true, chart.options.loading, options.loading);
+ }
+
+ // Update size. Redraw is forced.
+ newWidth = optionsChart && optionsChart.width;
+ newHeight = optionsChart && optionsChart.height;
+ if ((isNumber(newWidth) && newWidth !== chart.chartWidth) ||
+ (isNumber(newHeight) && newHeight !== chart.chartHeight)) {
+ chart.setSize(newWidth, newHeight);
+ } else if (pick(redraw, true)) {
+ chart.redraw();
+ }
+ },
+
+ /**
+ * Shortcut to set the subtitle options. This can also be done from {@link
+ * Chart#update} or {@link Chart#setTitle}.
+ *
+ * @param {SubtitleOptions} options
+ * New subtitle options. The subtitle text itself is set by the
+ * `options.text` property.
+ */
+ setSubtitle: function (options) {
+ this.setTitle(undefined, options);
+ }
+
+
+});
+
+// extend the Point prototype for dynamic methods
+extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
+ /**
+ * Update point with new options (typically x/y data) and optionally redraw
+ * the series.
+ *
+ * @param {Object} options
+ * The point options. Point options are handled as described under
+ * the `series.type.data` item for each series type. For example
+ * for a line series, if options is a single number, the point will
+ * be given that number as the main y value. If it is an array, it
+ * will be interpreted as x and y values respectively. If it is an
+ * object, advanced options are applied.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after the point is updated. If doing
+ * more operations on the chart, it is best practice to set
+ * `redraw` to false and call `chart.redraw()` after.
+ * @param {AnimationOptions} [animation=true]
+ * Whether to apply animation, and optionally animation
+ * configuration.
+ *
+ * @sample highcharts/members/point-update-column/
+ * Update column value
+ * @sample highcharts/members/point-update-pie/
+ * Update pie slice
+ * @sample maps/members/point-update/
+ * Update map area value in Highmaps
+ */
+ update: function (options, redraw, animation, runEvent) {
+ var point = this,
+ series = point.series,
+ graphic = point.graphic,
+ i,
+ chart = series.chart,
+ seriesOptions = series.options;
+
+ redraw = pick(redraw, true);
+
+ function update() {
+
+ point.applyOptions(options);
+
+ // Update visuals
+ if (point.y === null && graphic) { // #4146
+ point.graphic = graphic.destroy();
+ }
+ if (isObject(options, true)) {
+ // Destroy so we can get new elements
+ if (graphic && graphic.element) {
+ // "null" is also a valid symbol
+ if (options && options.marker && options.marker.symbol !== undefined) {
+ point.graphic = graphic.destroy();
+ }
+ }
+ if (options && options.dataLabels && point.dataLabel) { // #2468
+ point.dataLabel = point.dataLabel.destroy();
+ }
+ if (point.connector) {
+ point.connector = point.connector.destroy(); // #7243
+ }
+ }
+
+ // record changes in the parallel arrays
+ i = point.index;
+ series.updateParallelArrays(point, i);
+
+ // Record the options to options.data. If the old or the new config
+ // is an object, use point options, otherwise use raw options
+ // (#4701, #4916).
+ seriesOptions.data[i] = (
+ isObject(seriesOptions.data[i], true) ||
+ isObject(options, true)
+ ) ?
+ point.options :
+ options;
+
+ // redraw
+ series.isDirty = series.isDirtyData = true;
+ if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320
+ chart.isDirtyBox = true;
+ }
+
+ if (seriesOptions.legendType === 'point') { // #1831, #1885
+ chart.isDirtyLegend = true;
+ }
+ if (redraw) {
+ chart.redraw(animation);
+ }
+ }
+
+ // Fire the event with a default handler of doing the update
+ if (runEvent === false) { // When called from setData
+ update();
+ } else {
+ point.firePointEvent('update', { options: options }, update);
+ }
+ },
+
+ /**
+ * Remove a point and optionally redraw the series and if necessary the axes
+ * @param {Boolean} redraw
+ * Whether to redraw the chart or wait for an explicit call. When
+ * doing more operations on the chart, for example running
+ * `point.remove()` in a loop, it is best practice to set `redraw`
+ * to false and call `chart.redraw()` after.
+ * @param {AnimationOptions} [animation=false]
+ * Whether to apply animation, and optionally animation
+ * configuration.
+ *
+ * @sample highcharts/plotoptions/series-point-events-remove/
+ * Remove point and confirm
+ * @sample highcharts/members/point-remove/
+ * Remove pie slice
+ * @sample maps/members/point-remove/
+ * Remove selected points in Highmaps
+ */
+ remove: function (redraw, animation) {
+ this.series.removePoint(inArray(this, this.series.data), redraw, animation);
+ }
+});
+
+// Extend the series prototype for dynamic methods
+extend(Series.prototype, /** @lends Series.prototype */ {
+ /**
+ * Add a point to the series after render time. The point can be added at
+ * the end, or by giving it an X value, to the start or in the middle of the
+ * series.
+ *
+ * @param {Number|Array|Object} options
+ * The point options. If options is a single number, a point with
+ * that y value is appended to the series.If it is an array, it will
+ * be interpreted as x and y values respectively. If it is an
+ * object, advanced options as outlined under `series.data` are
+ * applied.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after the point is added. When adding
+ * more than one point, it is highly recommended that the redraw
+ * option be set to false, and instead {@link Chart#redraw}
+ * is explicitly called after the adding of points is finished.
+ * Otherwise, the chart will redraw after adding each point.
+ * @param {Boolean} [shift=false]
+ * If true, a point is shifted off the start of the series as one is
+ * appended to the end.
+ * @param {AnimationOptions} [animation]
+ * Whether to apply animation, and optionally animation
+ * configuration.
+ *
+ * @sample highcharts/members/series-addpoint-append/
+ * Append point
+ * @sample highcharts/members/series-addpoint-append-and-shift/
+ * Append and shift
+ * @sample highcharts/members/series-addpoint-x-and-y/
+ * Both X and Y values given
+ * @sample highcharts/members/series-addpoint-pie/
+ * Append pie slice
+ * @sample stock/members/series-addpoint/
+ * Append 100 points in Highstock
+ * @sample stock/members/series-addpoint-shift/
+ * Append and shift in Highstock
+ * @sample maps/members/series-addpoint/
+ * Add a point in Highmaps
+ */
+ addPoint: function (options, redraw, shift, animation) {
+ var series = this,
+ seriesOptions = series.options,
+ data = series.data,
+ chart = series.chart,
+ xAxis = series.xAxis,
+ names = xAxis && xAxis.hasNames && xAxis.names,
+ dataOptions = seriesOptions.data,
+ point,
+ isInTheMiddle,
+ xData = series.xData,
+ i,
+ x;
+
+ // Optional redraw, defaults to true
+ redraw = pick(redraw, true);
+
+ // Get options and push the point to xData, yData and series.options. In series.generatePoints
+ // the Point instance will be created on demand and pushed to the series.data array.
+ point = { series: series };
+ series.pointClass.prototype.applyOptions.apply(point, [options]);
+ x = point.x;
+
+ // Get the insertion point
+ i = xData.length;
+ if (series.requireSorting && x < xData[i - 1]) {
+ isInTheMiddle = true;
+ while (i && xData[i - 1] > x) {
+ i--;
+ }
+ }
+
+ series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item
+ series.updateParallelArrays(point, i); // update it
+
+ if (names && point.name) {
+ names[x] = point.name;
+ }
+ dataOptions.splice(i, 0, options);
+
+ if (isInTheMiddle) {
+ series.data.splice(i, 0, null);
+ series.processData();
+ }
+
+ // Generate points to be added to the legend (#1329)
+ if (seriesOptions.legendType === 'point') {
+ series.generatePoints();
+ }
+
+ // Shift the first point off the parallel arrays
+ if (shift) {
+ if (data[0] && data[0].remove) {
+ data[0].remove(false);
+ } else {
+ data.shift();
+ series.updateParallelArrays(point, 'shift');
+
+ dataOptions.shift();
+ }
+ }
+
+ // redraw
+ series.isDirty = true;
+ series.isDirtyData = true;
+
+ if (redraw) {
+ chart.redraw(animation); // Animation is set anyway on redraw, #5665
+ }
+ },
+
+ /**
+ * Remove a point from the series. Unlike the {@link Highcharts.Point#remove}
+ * method, this can also be done on a point that is not instanciated because
+ * it is outside the view or subject to Highstock data grouping.
+ *
+ * @param {Number} i
+ * The index of the point in the {@link Highcharts.Series.data|data}
+ * array.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after the point is added. When
+ * removing more than one point, it is highly recommended that the
+ * `redraw` option be set to `false`, and instead {@link
+ * Highcharts.Chart#redraw} is explicitly called after the adding of
+ * points is finished.
+ * @param {AnimationOptions} [animation]
+ * Whether and optionally how the series should be animated.
+ *
+ * @sample highcharts/members/series-removepoint/
+ * Remove cropped point
+ */
+ removePoint: function (i, redraw, animation) {
+
+ var series = this,
+ data = series.data,
+ point = data[i],
+ points = series.points,
+ chart = series.chart,
+ remove = function () {
+
+ if (points && points.length === data.length) { // #4935
+ points.splice(i, 1);
+ }
+ data.splice(i, 1);
+ series.options.data.splice(i, 1);
+ series.updateParallelArrays(point || { series: series }, 'splice', i, 1);
+
+ if (point) {
+ point.destroy();
+ }
+
+ // redraw
+ series.isDirty = true;
+ series.isDirtyData = true;
+ if (redraw) {
+ chart.redraw();
+ }
+ };
+
+ setAnimation(animation, chart);
+ redraw = pick(redraw, true);
+
+ // Fire the event with a default handler of removing the point
+ if (point) {
+ point.firePointEvent('remove', null, remove);
+ } else {
+ remove();
+ }
+ },
+
+ /**
+ * Remove a series and optionally redraw the chart.
+ *
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart or wait for an explicit call to
+ * {@link Highcharts.Chart#redraw}.
+ * @param {AnimationOptions} [animation]
+ * Whether to apply animation, and optionally animation
+ * configuration
+ * @param {Boolean} [withEvent=true]
+ * Used internally, whether to fire the series `remove` event.
+ *
+ * @sample highcharts/members/series-remove/
+ * Remove first series from a button
+ */
+ remove: function (redraw, animation, withEvent) {
+ var series = this,
+ chart = series.chart;
+
+ function remove() {
+
+ // Destroy elements
+ series.destroy();
+
+ // Redraw
+ chart.isDirtyLegend = chart.isDirtyBox = true;
+ chart.linkSeries();
+
+ if (pick(redraw, true)) {
+ chart.redraw(animation);
+ }
+ }
+
+ // Fire the event with a default handler of removing the point
+ if (withEvent !== false) {
+ fireEvent(series, 'remove', null, remove);
+ } else {
+ remove();
+ }
+ },
+
+ /**
+ * Update the series with a new set of options. For a clean and precise
+ * handling of new options, all methods and elements from the series are
+ * removed, and it is initiated from scratch. Therefore, this method is more
+ * performance expensive than some other utility methods like {@link
+ * Series#setData} or {@link Series#setVisible}.
+ *
+ * @param {SeriesOptions} options
+ * New options that will be merged with the series' existing
+ * options.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after the series is altered. If doing
+ * more operations on the chart, it is a good idea to set redraw to
+ * false and call {@link Chart#redraw} after.
+ *
+ * @sample highcharts/members/series-update/
+ * Updating series options
+ * @sample maps/members/series-update/
+ * Update series options in Highmaps
+ */
+ update: function (newOptions, redraw) {
+ var series = this,
+ chart = series.chart,
+ // must use user options when changing type because series.options
+ // is merged in with type specific plotOptions
+ oldOptions = series.userOptions,
+ oldType = series.oldType || series.type,
+ newType = newOptions.type || oldOptions.type || chart.options.chart.type,
+ proto = seriesTypes[oldType].prototype,
+ n,
+ groups = [
+ 'group',
+ 'markerGroup',
+ 'dataLabelsGroup'
+ ],
+ preserve = [
+ 'navigatorSeries',
+ 'baseSeries'
+ ],
+
+ // Animation must be enabled when calling update before the initial
+ // animation has first run. This happens when calling update
+ // directly after chart initialization, or when applying responsive
+ // rules (#6912).
+ animation = series.finishedAnimating && { animation: false };
+
+ // Running Series.update to update the data only is an intuitive usage,
+ // so we want to make sure that when used like this, we run the
+ // cheaper setData function and allow animation instead of completely
+ // recreating the series instance.
+ if (Object.keys && Object.keys(newOptions).toString() === 'data') {
+ return this.setData(newOptions.data, redraw);
+ }
+
+ // Make sure preserved properties are not destroyed (#3094)
+ preserve = groups.concat(preserve);
+ each(preserve, function (prop) {
+ preserve[prop] = series[prop];
+ delete series[prop];
+ });
+
+ // Do the merge, with some forced options
+ newOptions = merge(oldOptions, animation, {
+ index: series.index,
+ pointStart: series.xData[0] // when updating after addPoint
+ }, { data: series.options.data }, newOptions);
+
+ // Destroy the series and delete all properties. Reinsert all methods
+ // and properties from the new type prototype (#2270, #3719)
+ series.remove(false, null, false);
+ for (n in proto) {
+ series[n] = undefined;
+ }
+ extend(series, seriesTypes[newType || oldType].prototype);
+
+ // Re-register groups (#3094) and other preserved properties
+ each(preserve, function (prop) {
+ series[prop] = preserve[prop];
+ });
+
+ series.init(chart, newOptions);
+
+ // Update the Z index of groups (#3380, #7397)
+ if (newOptions.zIndex !== oldOptions.zIndex) {
+ each(groups, function (groupName) {
+ if (series[groupName]) {
+ series[groupName].attr({
+ zIndex: newOptions.zIndex
+ });
+ }
+ });
+ }
+
+
+ series.oldType = oldType;
+ chart.linkSeries(); // Links are lost in series.remove (#3028)
+ if (pick(redraw, true)) {
+ chart.redraw(false);
+ }
+ }
+});
+
+// Extend the Axis.prototype for dynamic methods
+extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {
+
+ /**
+ * Update an axis object with a new set of options. The options are merged
+ * with the existing options, so only new or altered options need to be
+ * specified.
+ *
+ * @param {Object} options
+ * The new options that will be merged in with existing options on
+ * the axis.
+ * @sample highcharts/members/axis-update/ Axis update demo
+ */
+ update: function (options, redraw) {
+ var chart = this.chart;
+
+ options = chart.options[this.coll][this.options.index] =
+ merge(this.userOptions, options);
+
+ this.destroy(true);
+
+ this.init(chart, extend(options, { events: undefined }));
+
+ chart.isDirtyBox = true;
+ if (pick(redraw, true)) {
+ chart.redraw();
+ }
+ },
+
+ /**
+ * Remove the axis from the chart.
+ *
+ * @param {Boolean} [redraw=true] Whether to redraw the chart following the
+ * remove.
+ *
+ * @sample highcharts/members/chart-addaxis/ Add and remove axes
+ */
+ remove: function (redraw) {
+ var chart = this.chart,
+ key = this.coll, // xAxis or yAxis
+ axisSeries = this.series,
+ i = axisSeries.length;
+
+ // Remove associated series (#2687)
+ while (i--) {
+ if (axisSeries[i]) {
+ axisSeries[i].remove(false);
+ }
+ }
+
+ // Remove the axis
+ erase(chart.axes, this);
+ erase(chart[key], this);
+
+ if (isArray(chart.options[key])) {
+ chart.options[key].splice(this.options.index, 1);
+ } else { // color axis, #6488
+ delete chart.options[key];
+ }
+
+ each(chart[key], function (axis, i) { // Re-index, #1706
+ axis.options.index = i;
+ });
+ this.destroy();
+ chart.isDirtyBox = true;
+
+ if (pick(redraw, true)) {
+ chart.redraw();
+ }
+ },
+
+ /**
+ * Update the axis title by options after render time.
+ *
+ * @param {TitleOptions} titleOptions
+ * The additional title options.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after setting the title.
+ * @sample highcharts/members/axis-settitle/ Set a new Y axis title
+ */
+ setTitle: function (titleOptions, redraw) {
+ this.update({ title: titleOptions }, redraw);
+ },
+
+ /**
+ * Set new axis categories and optionally redraw.
+ * @param {Array.} categories - The new categories.
+ * @param {Boolean} [redraw=true] - Whether to redraw the chart.
+ * @sample highcharts/members/axis-setcategories/ Set categories by click on
+ * a button
+ */
+ setCategories: function (categories, redraw) {
+ this.update({ categories: categories }, redraw);
+ }
+
+});
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var color = H.color,
+ each = H.each,
+ LegendSymbolMixin = H.LegendSymbolMixin,
+ map = H.map,
+ pick = H.pick,
+ Series = H.Series,
+ seriesType = H.seriesType;
+
+/**
+ * Area series type.
+ * @constructor seriesTypes.area
+ * @extends {Series}
+ */
+/**
+ * The area series type.
+ * @extends {plotOptions.line}
+ * @product highcharts highstock
+ * @sample {highcharts} highcharts/demo/area-basic/
+ * Area chart
+ * @sample {highstock} stock/demo/area/
+ * Area chart
+ * @optionparent plotOptions.area
+ */
+seriesType('area', 'line', {
+
+ /**
+ * Fill color or gradient for the area. When `null`, the series' `color`
+ * is used with the series' `fillOpacity`.
+ *
+ * @type {Color}
+ * @see In styled mode, the fill color can be set with the `.highcharts-area` class name.
+ * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/ Null by default
+ * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/ Gradient
+ * @default null
+ * @product highcharts highstock
+ * @apioption plotOptions.area.fillColor
+ */
+
+ /**
+ * Fill opacity for the area. When you set an explicit `fillColor`,
+ * the `fillOpacity` is not applied. Instead, you should define the
+ * opacity in the `fillColor` with an rgba color definition. The `fillOpacity`
+ * setting, also the default setting, overrides the alpha component
+ * of the `color` setting.
+ *
+ * @type {Number}
+ * @see In styled mode, the fill opacity can be set with the `.highcharts-area` class name.
+ * @sample {highcharts} highcharts/plotoptions/area-fillopacity/ Automatic fill color and fill opacity of 0.1
+ * @default {highcharts} 0.75
+ * @default {highstock} .75
+ * @product highcharts highstock
+ * @apioption plotOptions.area.fillOpacity
+ */
+
+ /**
+ * A separate color for the graph line. By default the line takes the
+ * `color` of the series, but the lineColor setting allows setting a
+ * separate color for the line without altering the `fillColor`.
+ *
+ * @type {Color}
+ * @see In styled mode, the line stroke can be set with the `.highcharts-graph` class name.
+ * @sample {highcharts} highcharts/plotoptions/area-linecolor/ Dark gray line
+ * @default null
+ * @product highcharts highstock
+ * @apioption plotOptions.area.lineColor
+ */
+
+ /**
+ * A separate color for the negative part of the area.
+ *
+ * @type {Color}
+ * @see [negativeColor](#plotOptions.area.negativeColor). In styled mode, a negative
+ * color is set with the `.highcharts-negative` class name ([view live
+ * demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
+ * negative-color/)).
+ * @since 3.0
+ * @product highcharts
+ * @apioption plotOptions.area.negativeFillColor
+ */
+
+ /**
+ * When this is true, the series will not cause the Y axis to cross
+ * the zero plane (or [threshold](#plotOptions.series.threshold) option)
+ * unless the data actually crosses the plane.
+ *
+ * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
+ * 3 will make the Y axis show negative values according to the `minPadding`
+ * option. If `softThreshold` is `true`, the Y axis starts at 0.
+ *
+ * @type {Boolean}
+ * @default false
+ * @since 4.1.9
+ * @product highcharts highstock
+ */
+ softThreshold: false,
+
+ /**
+ * The Y axis value to serve as the base for the area, for distinguishing
+ * between values above and below a threshold. If `null`, the area
+ * behaves like a line series with fill between the graph and the Y
+ * axis minimum.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/area-threshold/ A threshold of 100
+ * @default 0
+ * @since 2.0
+ * @product highcharts highstock
+ */
+ threshold: 0
+
+ /**
+ * Whether the whole area or just the line should respond to mouseover
+ * tooltips and other mouse or touch events.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the area is hovered
+ * @sample {highstock} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the area is hovered
+ * @default false
+ * @since 1.1.6
+ * @product highcharts highstock
+ * @apioption plotOptions.area.trackByArea
+ */
+
+
+}, /** @lends seriesTypes.area.prototype */ {
+ singleStacks: false,
+ /**
+ * Return an array of stacked points, where null and missing points are replaced by
+ * dummy points in order for gaps to be drawn correctly in stacks.
+ */
+ getStackPoints: function (points) {
+ var series = this,
+ segment = [],
+ keys = [],
+ xAxis = this.xAxis,
+ yAxis = this.yAxis,
+ stack = yAxis.stacks[this.stackKey],
+ pointMap = {},
+ seriesIndex = series.index,
+ yAxisSeries = yAxis.series,
+ seriesLength = yAxisSeries.length,
+ visibleSeries,
+ upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1,
+ i;
+
+
+ points = points || this.points;
+
+ if (this.options.stacking) {
+
+ for (i = 0; i < points.length; i++) {
+ // Reset after point update (#7326)
+ points[i].leftNull = points[i].rightNull = null;
+
+ // Create a map where we can quickly look up the points by their
+ // X values.
+ pointMap[points[i].x] = points[i];
+ }
+
+ // Sort the keys (#1651)
+ H.objectEach(stack, function (stackX, x) {
+ if (stackX.total !== null) { // nulled after switching between grouping and not (#1651, #2336)
+ keys.push(x);
+ }
+ });
+ keys.sort(function (a, b) {
+ return a - b;
+ });
+
+ visibleSeries = map(yAxisSeries, function () {
+ return this.visible;
+ });
+
+ each(keys, function (x, idx) {
+ var y = 0,
+ stackPoint,
+ stackedValues;
+
+ if (pointMap[x] && !pointMap[x].isNull) {
+ segment.push(pointMap[x]);
+
+ // Find left and right cliff. -1 goes left, 1 goes right.
+ each([-1, 1], function (direction) {
+ var nullName = direction === 1 ? 'rightNull' : 'leftNull',
+ cliffName = direction === 1 ? 'rightCliff' : 'leftCliff',
+ cliff = 0,
+ otherStack = stack[keys[idx + direction]];
+
+ // If there is a stack next to this one, to the left or to the right...
+ if (otherStack) {
+ i = seriesIndex;
+ while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks
+ stackPoint = otherStack.points[i];
+ if (!stackPoint) {
+ // If the next point in this series is missing, mark the point
+ // with point.leftNull or point.rightNull = true.
+ if (i === seriesIndex) {
+ pointMap[x][nullName] = true;
+
+ // If there are missing points in the next stack in any of the
+ // series below this one, we need to substract the missing values
+ // and add a hiatus to the left or right.
+ } else if (visibleSeries[i]) {
+ stackedValues = stack[x].points[i];
+ if (stackedValues) {
+ cliff -= stackedValues[1] - stackedValues[0];
+ }
+ }
+ }
+ // When reversedStacks is true, loop up, else loop down
+ i += upOrDown;
+ }
+ }
+ pointMap[x][cliffName] = cliff;
+ });
+
+
+ // There is no point for this X value in this series, so we
+ // insert a dummy point in order for the areas to be drawn
+ // correctly.
+ } else {
+
+ // Loop down the stack to find the series below this one that has
+ // a value (#1991)
+ i = seriesIndex;
+ while (i >= 0 && i < seriesLength) {
+ stackPoint = stack[x].points[i];
+ if (stackPoint) {
+ y = stackPoint[1];
+ break;
+ }
+ // When reversedStacks is true, loop up, else loop down
+ i += upOrDown;
+ }
+ y = yAxis.translate(y, 0, 1, 0, 1); // #6272
+ segment.push({
+ isNull: true,
+ plotX: xAxis.translate(x, 0, 0, 0, 1), // #6272
+ x: x,
+ plotY: y,
+ yBottom: y
+ });
+ }
+ });
+
+ }
+
+ return segment;
+ },
+
+ getGraphPath: function (points) {
+ var getGraphPath = Series.prototype.getGraphPath,
+ graphPath,
+ options = this.options,
+ stacking = options.stacking,
+ yAxis = this.yAxis,
+ topPath,
+ bottomPath,
+ bottomPoints = [],
+ graphPoints = [],
+ seriesIndex = this.index,
+ i,
+ areaPath,
+ plotX,
+ stacks = yAxis.stacks[this.stackKey],
+ threshold = options.threshold,
+ translatedThreshold = yAxis.getThreshold(options.threshold),
+ isNull,
+ yBottom,
+ connectNulls = options.connectNulls || stacking === 'percent',
+ /**
+ * To display null points in underlying stacked series, this series graph must be
+ * broken, and the area also fall down to fill the gap left by the null point. #2069
+ */
+ addDummyPoints = function (i, otherI, side) {
+ var point = points[i],
+ stackedValues = stacking && stacks[point.x].points[seriesIndex],
+ nullVal = point[side + 'Null'] || 0,
+ cliffVal = point[side + 'Cliff'] || 0,
+ top,
+ bottom,
+ isNull = true;
+
+ if (cliffVal || nullVal) {
+
+ top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal;
+ bottom = stackedValues[0] + cliffVal;
+ isNull = !!nullVal;
+
+ } else if (!stacking && points[otherI] && points[otherI].isNull) {
+ top = bottom = threshold;
+ }
+
+ // Add to the top and bottom line of the area
+ if (top !== undefined) {
+ graphPoints.push({
+ plotX: plotX,
+ plotY: top === null ? translatedThreshold : yAxis.getThreshold(top),
+ isNull: isNull,
+ isCliff: true
+ });
+ bottomPoints.push({
+ plotX: plotX,
+ plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom),
+ doCurve: false // #1041, gaps in areaspline areas
+ });
+ }
+ };
+
+ // Find what points to use
+ points = points || this.points;
+
+ // Fill in missing points
+ if (stacking) {
+ points = this.getStackPoints(points);
+ }
+
+ for (i = 0; i < points.length; i++) {
+ isNull = points[i].isNull;
+ plotX = pick(points[i].rectPlotX, points[i].plotX);
+ yBottom = pick(points[i].yBottom, translatedThreshold);
+
+ if (!isNull || connectNulls) {
+
+ if (!connectNulls) {
+ addDummyPoints(i, i - 1, 'left');
+ }
+
+ if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true
+ graphPoints.push(points[i]);
+ bottomPoints.push({
+ x: i,
+ plotX: plotX,
+ plotY: yBottom
+ });
+ }
+
+ if (!connectNulls) {
+ addDummyPoints(i, i + 1, 'right');
+ }
+ }
+ }
+
+ topPath = getGraphPath.call(this, graphPoints, true, true);
+
+ bottomPoints.reversed = true;
+ bottomPath = getGraphPath.call(this, bottomPoints, true, true);
+ if (bottomPath.length) {
+ bottomPath[0] = 'L';
+ }
+
+ areaPath = topPath.concat(bottomPath);
+ graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls?
+
+ areaPath.xMap = topPath.xMap;
+ this.areaPath = areaPath;
+
+ return graphPath;
+ },
+
+ /**
+ * Draw the graph and the underlying area. This method calls the Series base
+ * function and adds the area. The areaPath is calculated in the getSegmentPath
+ * method called from Series.prototype.drawGraph.
+ */
+ drawGraph: function () {
+
+ // Define or reset areaPath
+ this.areaPath = [];
+
+ // Call the base method
+ Series.prototype.drawGraph.apply(this);
+
+ // Define local variables
+ var series = this,
+ areaPath = this.areaPath,
+ options = this.options,
+ zones = this.zones,
+ props = [[
+ 'area',
+ 'highcharts-area',
+
+ this.color,
+ options.fillColor
+
+ ]]; // area name, main color, fill color
+
+ each(zones, function (zone, i) {
+ props.push([
+ 'zone-area-' + i,
+ 'highcharts-area highcharts-zone-area-' + i + ' ' + zone.className,
+
+ zone.color || series.color,
+ zone.fillColor || options.fillColor
+
+ ]);
+ });
+
+ each(props, function (prop) {
+ var areaKey = prop[0],
+ area = series[areaKey];
+
+ // Create or update the area
+ if (area) { // update
+ area.endX = series.preventGraphAnimation ? null : areaPath.xMap;
+ area.animate({ d: areaPath });
+
+ } else { // create
+ area = series[areaKey] = series.chart.renderer.path(areaPath)
+ .addClass(prop[1])
+ .attr({
+
+ fill: pick(
+ prop[3],
+ color(prop[2]).setOpacity(pick(options.fillOpacity, 0.75)).get()
+ ),
+
+ zIndex: 0 // #1069
+ }).add(series.group);
+ area.isArea = true;
+ }
+ area.startX = areaPath.xMap;
+ area.shiftUnit = options.step ? 2 : 1;
+ });
+ },
+
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle
+});
+
+/**
+ * A `area` series. If the [type](#series.area.type) option is not
+ * specified, it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * area](#plotOptions.area).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.area
+ * @excluding dataParser,dataURL
+ * @product highcharts highstock
+ * @apioption series.area
+ */
+
+/**
+ * An array of data points for the series. For the `area` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values
+ * will be interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options. If the axis has
+ * categories, these will be used. Example:
+ *
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond
+ * to `x,y`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred.
+ *
+ * ```js
+ * data: [
+ * [0, 9],
+ * [1, 7],
+ * [2, 6]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 6,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
+ * @product highcharts highstock
+ * @apioption series.area.data
+ */
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var pick = H.pick,
+ seriesType = H.seriesType;
+
+/**
+ * A spline series is a special type of line series, where the segments between
+ * the data points are smoothed.
+ *
+ * @sample {highcharts} highcharts/demo/spline-irregular-time/ Spline chart
+ * @sample {highstock} stock/demo/spline/ Spline chart
+ *
+ * @extends plotOptions.series
+ * @excluding step
+ * @product highcharts highstock
+ * @apioption plotOptions.spline
+ */
+
+/**
+ * Spline series type.
+ * @constructor seriesTypes.spline
+ * @extends {Series}
+ */
+seriesType('spline', 'line', {}, /** @lends seriesTypes.spline.prototype */ {
+ /**
+ * Get the spline segment from a given point's previous neighbour to the
+ * given point
+ */
+ getPointSpline: function (points, point, i) {
+ var
+ // 1 means control points midway between points, 2 means 1/3 from
+ // the point, 3 is 1/4 etc
+ smoothing = 1.5,
+ denom = smoothing + 1,
+ plotX = point.plotX,
+ plotY = point.plotY,
+ lastPoint = points[i - 1],
+ nextPoint = points[i + 1],
+ leftContX,
+ leftContY,
+ rightContX,
+ rightContY,
+ ret;
+
+ function doCurve(otherPoint) {
+ return otherPoint &&
+ !otherPoint.isNull &&
+ otherPoint.doCurve !== false &&
+ !point.isCliff; // #6387, area splines next to null
+ }
+
+ // Find control points
+ if (doCurve(lastPoint) && doCurve(nextPoint)) {
+ var lastX = lastPoint.plotX,
+ lastY = lastPoint.plotY,
+ nextX = nextPoint.plotX,
+ nextY = nextPoint.plotY,
+ correction = 0;
+
+ leftContX = (smoothing * plotX + lastX) / denom;
+ leftContY = (smoothing * plotY + lastY) / denom;
+ rightContX = (smoothing * plotX + nextX) / denom;
+ rightContY = (smoothing * plotY + nextY) / denom;
+
+ // Have the two control points make a straight line through main
+ // point
+ if (rightContX !== leftContX) { // #5016, division by zero
+ correction = ((rightContY - leftContY) * (rightContX - plotX)) /
+ (rightContX - leftContX) + plotY - rightContY;
+ }
+
+ leftContY += correction;
+ rightContY += correction;
+
+ // to prevent false extremes, check that control points are between
+ // neighbouring points' y values
+ if (leftContY > lastY && leftContY > plotY) {
+ leftContY = Math.max(lastY, plotY);
+ // mirror of left control point
+ rightContY = 2 * plotY - leftContY;
+ } else if (leftContY < lastY && leftContY < plotY) {
+ leftContY = Math.min(lastY, plotY);
+ rightContY = 2 * plotY - leftContY;
+ }
+ if (rightContY > nextY && rightContY > plotY) {
+ rightContY = Math.max(nextY, plotY);
+ leftContY = 2 * plotY - rightContY;
+ } else if (rightContY < nextY && rightContY < plotY) {
+ rightContY = Math.min(nextY, plotY);
+ leftContY = 2 * plotY - rightContY;
+ }
+
+ // record for drawing in next point
+ point.rightContX = rightContX;
+ point.rightContY = rightContY;
+
+
+ }
+
+ // Visualize control points for debugging
+ /*
+ if (leftContX) {
+ this.chart.renderer.circle(
+ leftContX + this.chart.plotLeft,
+ leftContY + this.chart.plotTop,
+ 2
+ )
+ .attr({
+ stroke: 'red',
+ 'stroke-width': 2,
+ fill: 'none',
+ zIndex: 9
+ })
+ .add();
+ this.chart.renderer.path(['M', leftContX + this.chart.plotLeft,
+ leftContY + this.chart.plotTop,
+ 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
+ .attr({
+ stroke: 'red',
+ 'stroke-width': 2,
+ zIndex: 9
+ })
+ .add();
+ }
+ if (rightContX) {
+ this.chart.renderer.circle(
+ rightContX + this.chart.plotLeft,
+ rightContY + this.chart.plotTop,
+ 2
+ )
+ .attr({
+ stroke: 'green',
+ 'stroke-width': 2,
+ fill: 'none',
+ zIndex: 9
+ })
+ .add();
+ this.chart.renderer.path(['M', rightContX + this.chart.plotLeft,
+ rightContY + this.chart.plotTop,
+ 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
+ .attr({
+ stroke: 'green',
+ 'stroke-width': 2,
+ zIndex: 9
+ })
+ .add();
+ }
+ // */
+ ret = [
+ 'C',
+ pick(lastPoint.rightContX, lastPoint.plotX),
+ pick(lastPoint.rightContY, lastPoint.plotY),
+ pick(leftContX, plotX),
+ pick(leftContY, plotY),
+ plotX,
+ plotY
+ ];
+ // reset for updating series later
+ lastPoint.rightContX = lastPoint.rightContY = null;
+ return ret;
+ }
+});
+
+/**
+ * A `spline` series. If the [type](#series.spline.type) option is
+ * not specified, it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * spline](#plotOptions.spline).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.spline
+ * @excluding dataParser,dataURL
+ * @product highcharts highstock
+ * @apioption series.spline
+ */
+
+/**
+ * An array of data points for the series. For the `spline` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values
+ * will be interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options. If the axis has
+ * categories, these will be used. Example:
+ *
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond
+ * to `x,y`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred.
+ *
+ * ```js
+ * data: [
+ * [0, 9],
+ * [1, 2],
+ * [2, 8]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.spline.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 0,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ * @product highcharts highstock
+ * @apioption series.spline.data
+ */
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var areaProto = H.seriesTypes.area.prototype,
+ defaultPlotOptions = H.defaultPlotOptions,
+ LegendSymbolMixin = H.LegendSymbolMixin,
+ seriesType = H.seriesType;
+/**
+ * AreaSplineSeries object
+ */
+/**
+ * The area spline series is an area series where the graph between the points
+ * is smoothed into a spline.
+ *
+ * @extends plotOptions.area
+ * @excluding step
+ * @sample {highcharts} highcharts/demo/areaspline/ Area spline chart
+ * @sample {highstock} stock/demo/areaspline/ Area spline chart
+ * @product highcharts highstock
+ * @apioption plotOptions.areaspline
+ */
+seriesType('areaspline', 'spline', defaultPlotOptions.area, {
+ getStackPoints: areaProto.getStackPoints,
+ getGraphPath: areaProto.getGraphPath,
+ drawGraph: areaProto.drawGraph,
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle
+});
+/**
+ * A `areaspline` series. If the [type](#series.areaspline.type) option
+ * is not specified, it is inherited from [chart.type](#chart.type).
+ *
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * areaspline](#plotOptions.areaspline).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.areaspline
+ * @excluding dataParser,dataURL
+ * @product highcharts highstock
+ * @apioption series.areaspline
+ */
+
+
+/**
+ * An array of data points for the series. For the `areaspline` series
+ * type, points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values
+ * will be interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options. If the axis has
+ * categories, these will be used. Example:
+ *
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond
+ * to `x,y`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred.
+ *
+ * ```js
+ * data: [
+ * [0, 10],
+ * [1, 9],
+ * [2, 3]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.areaspline.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 4,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 4,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
+ * @product highcharts highstock
+ * @apioption series.areaspline.data
+ */
+
+
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var animObject = H.animObject,
+ color = H.color,
+ each = H.each,
+ extend = H.extend,
+ isNumber = H.isNumber,
+ LegendSymbolMixin = H.LegendSymbolMixin,
+ merge = H.merge,
+ noop = H.noop,
+ pick = H.pick,
+ Series = H.Series,
+ seriesType = H.seriesType,
+ svg = H.svg;
+/**
+ * The column series type.
+ *
+ * @constructor seriesTypes.column
+ * @augments Series
+ */
+
+/**
+ * Column series display one column per value along an X axis.
+ *
+ * @sample {highcharts} highcharts/demo/column-basic/ Column chart
+ * @sample {highstock} stock/demo/column/ Column chart
+ *
+ * @extends {plotOptions.line}
+ * @product highcharts highstock
+ * @excluding connectNulls,dashStyle,gapSize,gapUnit,linecap,lineWidth,marker,
+ * connectEnds,step
+ * @optionparent plotOptions.column
+ */
+seriesType('column', 'line', {
+
+ /**
+ * The corner radius of the border surrounding each column or bar.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/column-borderradius/
+ * Rounded columns
+ * @default 0
+ * @product highcharts highstock
+ */
+ borderRadius: 0,
+
+ /**
+ * The width of the border surrounding each column or bar.
+ *
+ * In styled mode, the stroke width can be set with the `.highcharts-point`
+ * rule.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
+ * 2px black border
+ * @default 1
+ * @product highcharts highstock
+ * @apioption plotOptions.column.borderWidth
+ */
+
+ /**
+ * When using automatic point colors pulled from the `options.colors`
+ * collection, this option determines whether the chart should receive
+ * one color per series or one color per point.
+ *
+ * @type {Boolean}
+ * @see [series colors](#plotOptions.column.colors)
+ * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/
+ * False by default
+ * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/
+ * True
+ * @default false
+ * @since 2.0
+ * @product highcharts highstock
+ * @apioption plotOptions.column.colorByPoint
+ */
+
+ /**
+ * A series specific or series type specific color set to apply instead
+ * of the global [colors](#colors) when [colorByPoint](#plotOptions.
+ * column.colorByPoint) is true.
+ *
+ * @type {Array}
+ * @since 3.0
+ * @product highcharts highstock
+ * @apioption plotOptions.column.colors
+ */
+
+ /**
+ * When true, each column edge is rounded to its nearest pixel in order
+ * to render sharp on screen. In some cases, when there are a lot of
+ * densely packed columns, this leads to visible difference in column
+ * widths or distance between columns. In these cases, setting `crisp`
+ * to `false` may look better, even though each column is rendered
+ * blurry.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/column-crisp-false/
+ * Crisp is false
+ * @default true
+ * @since 5.0.10
+ * @product highcharts highstock
+ */
+ crisp: true,
+
+ /**
+ * Padding between each value groups, in x axis units.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/column-grouppadding-default/
+ * 0.2 by default
+ * @sample {highcharts} highcharts/plotoptions/column-grouppadding-none/
+ * No group padding - all columns are evenly spaced
+ * @default 0.2
+ * @product highcharts highstock
+ */
+ groupPadding: 0.2,
+
+ /**
+ * Whether to group non-stacked columns or to let them render independent
+ * of each other. Non-grouped columns will be laid out individually
+ * and overlap each other.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/column-grouping-false/
+ * Grouping disabled
+ * @sample {highstock} highcharts/plotoptions/column-grouping-false/
+ * Grouping disabled
+ * @default true
+ * @since 2.3.0
+ * @product highcharts highstock
+ * @apioption plotOptions.column.grouping
+ */
+
+ marker: null, // point options are specified in the base options
+
+ /**
+ * The maximum allowed pixel width for a column, translated to the height
+ * of a bar in a bar chart. This prevents the columns from becoming
+ * too wide when there is a small number of points in the chart.
+ *
+ * @type {Number}
+ * @see [pointWidth](#plotOptions.column.pointWidth)
+ * @sample {highcharts} highcharts/plotoptions/column-maxpointwidth-20/
+ * Limited to 50
+ * @sample {highstock} highcharts/plotoptions/column-maxpointwidth-20/
+ * Limited to 50
+ * @default null
+ * @since 4.1.8
+ * @product highcharts highstock
+ * @apioption plotOptions.column.maxPointWidth
+ */
+
+ /**
+ * Padding between each column or bar, in x axis units.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/column-pointpadding-default/
+ * 0.1 by default
+ * @sample {highcharts} highcharts/plotoptions/column-pointpadding-025/
+ * 0.25
+ * @sample {highcharts} highcharts/plotoptions/column-pointpadding-none/
+ * 0 for tightly packed columns
+ * @default 0.1
+ * @product highcharts highstock
+ */
+ pointPadding: 0.1,
+
+ /**
+ * A pixel value specifying a fixed width for each column or bar. When
+ * `null`, the width is calculated from the `pointPadding` and
+ * `groupPadding`.
+ *
+ * @type {Number}
+ * @see [maxPointWidth](#plotOptions.column.maxPointWidth)
+ * @sample {highcharts} highcharts/plotoptions/column-pointwidth-20/
+ * 20px wide columns regardless of chart width or the amount of data
+ * points
+ * @default null
+ * @since 1.2.5
+ * @product highcharts highstock
+ * @apioption plotOptions.column.pointWidth
+ */
+
+ /**
+ * The minimal height for a column or width for a bar. By default,
+ * 0 values are not shown. To visualize a 0 (or close to zero) point,
+ * set the minimal point length to a pixel value like 3\. In stacked
+ * column charts, minPointLength might not be respected for tightly
+ * packed values.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/column-minpointlength/
+ * Zero base value
+ * @sample {highcharts} highcharts/plotoptions/column-minpointlength-pos-and-neg/
+ * Positive and negative close to zero values
+ * @default 0
+ * @product highcharts highstock
+ */
+ minPointLength: 0,
+
+ /**
+ * When the series contains less points than the crop threshold, all
+ * points are drawn, event if the points fall outside the visible plot
+ * area at the current zoom. The advantage of drawing all points (including
+ * markers and columns), is that animation is performed on updates.
+ * On the other hand, when the series contains more points than the
+ * crop threshold, the series data is cropped to only contain points
+ * that fall within the plot area. The advantage of cropping away invisible
+ * points is to increase performance on large series. .
+ *
+ * @type {Number}
+ * @default 50
+ * @product highcharts highstock
+ */
+ cropThreshold: 50,
+
+ /**
+ * The X axis range that each point is valid for. This determines the
+ * width of the column. On a categorized axis, the range will be 1
+ * by default (one category unit). On linear and datetime axes, the
+ * range will be computed as the distance between the two closest data
+ * points.
+ *
+ * The default `null` means it is computed automatically, but this option
+ * can be used to override the automatic value.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/column-pointrange/
+ * Set the point range to one day on a data set with one week
+ * between the points
+ * @default null
+ * @since 2.3
+ * @product highcharts highstock
+ */
+ pointRange: null,
+
+ states: {
+
+ /**
+ * Options for the hovered point. These settings override the normal
+ * state options when a point is moused over or touched.
+ *
+ * @extends plotOptions.series.states.hover
+ * @excluding halo,lineWidth,lineWidthPlus,marker
+ * @product highcharts highstock
+ */
+ hover: {
+
+ /**
+ * @ignore-option
+ */
+ halo: false,
+ /**
+ * A specific border color for the hovered point. Defaults to
+ * inherit the normal state border color.
+ *
+ * @type {Color}
+ * @product highcharts
+ * @apioption plotOptions.column.states.hover.borderColor
+ */
+
+ /**
+ * A specific color for the hovered point.
+ *
+ * @type {Color}
+ * @default undefined
+ * @product highcharts
+ * @apioption plotOptions.column.states.hover.color
+ */
+
+
+
+ /**
+ * How much to brighten the point on interaction. Requires the main
+ * color to be defined in hex or rgb(a) format.
+ *
+ * In styled mode, the hover brightening is by default replaced
+ * with a fill-opacity set in the `.highcharts-point:hover` rule.
+ *
+ * @sample {highcharts}
+ * highcharts/plotoptions/column-states-hover-brightness/
+ * Brighten by 0.5
+ * @product highcharts highstock
+ */
+ brightness: 0.1
+
+
+ },
+
+
+ /**
+ * Options for the selected point. These settings override the normal
+ * state options when a point is selected.
+ *
+ * @excluding halo,lineWidth,lineWidthPlus,marker
+ * @product highcharts highstock
+ */
+ select: {
+ /**
+ * A specific color for the selected point.
+ *
+ * @type {Color}
+ * @default #cccccc
+ * @product highcharts highstock
+ */
+ color: '#cccccc',
+
+ /**
+ * A specific border color for the selected point.
+ *
+ * @type {Color}
+ * @default #000000
+ * @product highcharts highstock
+ */
+ borderColor: '#000000'
+ }
+
+ },
+
+ dataLabels: {
+ align: null, // auto
+ verticalAlign: null, // auto
+ y: null
+ },
+
+ /**
+ * When this is true, the series will not cause the Y axis to cross
+ * the zero plane (or [threshold](#plotOptions.series.threshold) option)
+ * unless the data actually crosses the plane.
+ *
+ * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
+ * 3 will make the Y axis show negative values according to the `minPadding`
+ * option. If `softThreshold` is `true`, the Y axis starts at 0.
+ *
+ * @type {Boolean}
+ * @default {highcharts} true
+ * @default {highstock} false
+ * @since 4.1.9
+ * @product highcharts highstock
+ */
+ softThreshold: false,
+
+ // false doesn't work well: http://jsfiddle.net/highcharts/hz8fopan/14/
+ /** @ignore */
+ startFromThreshold: true,
+
+ stickyTracking: false,
+
+ tooltip: {
+ distance: 6
+ },
+
+ /**
+ * The Y axis value to serve as the base for the columns, for distinguishing
+ * between values above and below a threshold. If `null`, the columns
+ * extend from the padding Y axis minimum.
+ *
+ * @type {Number}
+ * @default 0
+ * @since 2.0
+ * @product highcharts
+ */
+ threshold: 0,
+
+
+ /**
+ * The color of the border surrounding each column or bar.
+ *
+ * In styled mode, the border stroke can be set with the `.highcharts-point`
+ * rule.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/column-bordercolor/
+ * Dark gray border
+ * @default #ffffff
+ * @product highcharts highstock
+ */
+ borderColor: '#ffffff'
+ // borderWidth: 1
+
+
+}, /** @lends seriesTypes.column.prototype */ {
+ cropShoulder: 0,
+ // When tooltip is not shared, this series (and derivatives) requires direct
+ // touch/hover. KD-tree does not apply.
+ directTouch: true,
+ trackerGroups: ['group', 'dataLabelsGroup'],
+ // use separate negative stacks, unlike area stacks where a negative point
+ // is substracted from previous (#1910)
+ negStacks: true,
+
+ /**
+ * Initialize the series. Extends the basic Series.init method by
+ * marking other series of the same type as dirty.
+ *
+ * @function #init
+ * @memberOf seriesTypes.column
+ *
+ */
+ init: function () {
+ Series.prototype.init.apply(this, arguments);
+
+ var series = this,
+ chart = series.chart;
+
+ // if the series is added dynamically, force redraw of other
+ // series affected by a new column
+ if (chart.hasRendered) {
+ each(chart.series, function (otherSeries) {
+ if (otherSeries.type === series.type) {
+ otherSeries.isDirty = true;
+ }
+ });
+ }
+ },
+
+ /**
+ * Return the width and x offset of the columns adjusted for grouping,
+ * groupPadding, pointPadding, pointWidth etc.
+ */
+ getColumnMetrics: function () {
+
+ var series = this,
+ options = series.options,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis,
+ reversedXAxis = xAxis.reversed,
+ stackKey,
+ stackGroups = {},
+ columnCount = 0;
+
+ // Get the total number of column type series. This is called on every
+ // series. Consider moving this logic to a chart.orderStacks() function
+ // and call it on init, addSeries and removeSeries
+ if (options.grouping === false) {
+ columnCount = 1;
+ } else {
+ each(series.chart.series, function (otherSeries) {
+ var otherOptions = otherSeries.options,
+ otherYAxis = otherSeries.yAxis,
+ columnIndex;
+ if (
+ otherSeries.type === series.type &&
+ (
+ otherSeries.visible ||
+ !series.chart.options.chart.ignoreHiddenSeries
+ ) &&
+ yAxis.len === otherYAxis.len &&
+ yAxis.pos === otherYAxis.pos
+ ) { // #642, #2086
+ if (otherOptions.stacking) {
+ stackKey = otherSeries.stackKey;
+ if (stackGroups[stackKey] === undefined) {
+ stackGroups[stackKey] = columnCount++;
+ }
+ columnIndex = stackGroups[stackKey];
+ } else if (otherOptions.grouping !== false) { // #1162
+ columnIndex = columnCount++;
+ }
+ otherSeries.columnIndex = columnIndex;
+ }
+ });
+ }
+
+ var categoryWidth = Math.min(
+ Math.abs(xAxis.transA) * (
+ xAxis.ordinalSlope ||
+ options.pointRange ||
+ xAxis.closestPointRange ||
+ xAxis.tickInterval ||
+ 1
+ ), // #2610
+ xAxis.len // #1535
+ ),
+ groupPadding = categoryWidth * options.groupPadding,
+ groupWidth = categoryWidth - 2 * groupPadding,
+ pointOffsetWidth = groupWidth / (columnCount || 1),
+ pointWidth = Math.min(
+ options.maxPointWidth || xAxis.len,
+ pick(
+ options.pointWidth,
+ pointOffsetWidth * (1 - 2 * options.pointPadding)
+ )
+ ),
+ pointPadding = (pointOffsetWidth - pointWidth) / 2,
+ // #1251, #3737
+ colIndex = (series.columnIndex || 0) + (reversedXAxis ? 1 : 0),
+ pointXOffset =
+ pointPadding +
+ (
+ groupPadding +
+ colIndex * pointOffsetWidth -
+ (categoryWidth / 2)
+ ) * (reversedXAxis ? -1 : 1);
+
+ // Save it for reading in linked series (Error bars particularly)
+ series.columnMetrics = {
+ width: pointWidth,
+ offset: pointXOffset
+ };
+ return series.columnMetrics;
+
+ },
+
+ /**
+ * Make the columns crisp. The edges are rounded to the nearest full pixel.
+ */
+ crispCol: function (x, y, w, h) {
+ var chart = this.chart,
+ borderWidth = this.borderWidth,
+ xCrisp = -(borderWidth % 2 ? 0.5 : 0),
+ yCrisp = borderWidth % 2 ? 0.5 : 1,
+ right,
+ bottom,
+ fromTop;
+
+ if (chart.inverted && chart.renderer.isVML) {
+ yCrisp += 1;
+ }
+
+ // Horizontal. We need to first compute the exact right edge, then round
+ // it and compute the width from there.
+ if (this.options.crisp) {
+ right = Math.round(x + w) + xCrisp;
+ x = Math.round(x) + xCrisp;
+ w = right - x;
+ }
+
+ // Vertical
+ bottom = Math.round(y + h) + yCrisp;
+ fromTop = Math.abs(y) <= 0.5 && bottom > 0.5; // #4504, #4656
+ y = Math.round(y) + yCrisp;
+ h = bottom - y;
+
+ // Top edges are exceptions
+ if (fromTop && h) { // #5146
+ y -= 1;
+ h += 1;
+ }
+
+ return {
+ x: x,
+ y: y,
+ width: w,
+ height: h
+ };
+ },
+
+ /**
+ * Translate each point to the plot area coordinate system and find shape
+ * positions
+ */
+ translate: function () {
+ var series = this,
+ chart = series.chart,
+ options = series.options,
+ dense = series.dense =
+ series.closestPointRange * series.xAxis.transA < 2,
+ borderWidth = series.borderWidth = pick(
+ options.borderWidth,
+ dense ? 0 : 1 // #3635
+ ),
+ yAxis = series.yAxis,
+ threshold = options.threshold,
+ translatedThreshold = series.translatedThreshold =
+ yAxis.getThreshold(threshold),
+ minPointLength = pick(options.minPointLength, 5),
+ metrics = series.getColumnMetrics(),
+ pointWidth = metrics.width,
+ // postprocessed for border width
+ seriesBarW = series.barW =
+ Math.max(pointWidth, 1 + 2 * borderWidth),
+ pointXOffset = series.pointXOffset = metrics.offset;
+
+ if (chart.inverted) {
+ translatedThreshold -= 0.5; // #3355
+ }
+
+ // When the pointPadding is 0, we want the columns to be packed tightly,
+ // so we allow individual columns to have individual sizes. When
+ // pointPadding is greater, we strive for equal-width columns (#2694).
+ if (options.pointPadding) {
+ seriesBarW = Math.ceil(seriesBarW);
+ }
+
+ Series.prototype.translate.apply(series);
+
+ // Record the new values
+ each(series.points, function (point) {
+ var yBottom = pick(point.yBottom, translatedThreshold),
+ safeDistance = 999 + Math.abs(yBottom),
+ plotY = Math.min(
+ Math.max(-safeDistance, point.plotY),
+ yAxis.len + safeDistance
+ ), // Don't draw too far outside plot area (#1303, #2241, #4264)
+ barX = point.plotX + pointXOffset,
+ barW = seriesBarW,
+ barY = Math.min(plotY, yBottom),
+ up,
+ barH = Math.max(plotY, yBottom) - barY;
+
+ // Handle options.minPointLength
+ if (minPointLength && Math.abs(barH) < minPointLength) {
+ barH = minPointLength;
+ up = (!yAxis.reversed && !point.negative) ||
+ (yAxis.reversed && point.negative);
+
+ // Reverse zeros if there's no positive value in the series
+ // in visible range (#7046)
+ if (
+ point.y === threshold &&
+ series.dataMax <= threshold &&
+ yAxis.min < threshold // and if there's room for it (#7311)
+ ) {
+ up = !up;
+ }
+
+ // If stacked...
+ barY = Math.abs(barY - translatedThreshold) > minPointLength ?
+ // ...keep position
+ yBottom - minPointLength :
+ // #1485, #4051
+ translatedThreshold - (up ? minPointLength : 0);
+ }
+
+ // Cache for access in polar
+ point.barX = barX;
+ point.pointWidth = pointWidth;
+
+ // Fix the tooltip on center of grouped columns (#1216, #424, #3648)
+ point.tooltipPos = chart.inverted ?
+ [
+ yAxis.len + yAxis.pos - chart.plotLeft - plotY,
+ series.xAxis.len - barX - barW / 2, barH
+ ] :
+ [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];
+
+ // Register shape type and arguments to be used in drawPoints
+ point.shapeType = 'rect';
+ point.shapeArgs = series.crispCol.apply(
+ series,
+ point.isNull ?
+ // #3169, drilldown from null must have a position to work
+ // from #6585, dataLabel should be placed on xAxis, not
+ // floating in the middle of the chart
+ [barX, translatedThreshold, barW, 0] :
+ [barX, barY, barW, barH]
+ );
+ });
+
+ },
+
+ getSymbol: noop,
+
+ /**
+ * Use a solid rectangle like the area series types
+ */
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
+
+
+ /**
+ * Columns have no graph
+ */
+ drawGraph: function () {
+ this.group[
+ this.dense ? 'addClass' : 'removeClass'
+ ]('highcharts-dense-data');
+ },
+
+
+ /**
+ * Get presentational attributes
+ */
+ pointAttribs: function (point, state) {
+ var options = this.options,
+ stateOptions,
+ ret,
+ p2o = this.pointAttrToOptions || {},
+ strokeOption = p2o.stroke || 'borderColor',
+ strokeWidthOption = p2o['stroke-width'] || 'borderWidth',
+ fill = (point && point.color) || this.color,
+ stroke = (point && point[strokeOption]) || options[strokeOption] ||
+ this.color || fill, // set to fill when borderColor null
+ strokeWidth = (point && point[strokeWidthOption]) ||
+ options[strokeWidthOption] || this[strokeWidthOption] || 0,
+ dashstyle = options.dashStyle,
+ zone,
+ brightness;
+
+ // Handle zone colors
+ if (point && this.zones.length) {
+ zone = point.getZone();
+ // When zones are present, don't use point.color (#4267). Changed
+ // order (#6527)
+ fill = point.options.color || (zone && zone.color) || this.color;
+ }
+
+ // Select or hover states
+ if (state) {
+ stateOptions = merge(
+ options.states[state],
+ // #6401
+ point.options.states && point.options.states[state] || {}
+ );
+ brightness = stateOptions.brightness;
+ fill = stateOptions.color ||
+ (
+ brightness !== undefined &&
+ color(fill).brighten(stateOptions.brightness).get()
+ ) ||
+ fill;
+ stroke = stateOptions[strokeOption] || stroke;
+ strokeWidth = stateOptions[strokeWidthOption] || strokeWidth;
+ dashstyle = stateOptions.dashStyle || dashstyle;
+ }
+
+ ret = {
+ 'fill': fill,
+ 'stroke': stroke,
+ 'stroke-width': strokeWidth
+ };
+
+ if (dashstyle) {
+ ret.dashstyle = dashstyle;
+ }
+
+ return ret;
+ },
+
+
+ /**
+ * Draw the columns. For bars, the series.group is rotated, so the same
+ * coordinates apply for columns and bars. This method is inherited by
+ * scatter series.
+ */
+ drawPoints: function () {
+ var series = this,
+ chart = this.chart,
+ options = series.options,
+ renderer = chart.renderer,
+ animationLimit = options.animationLimit || 250,
+ shapeArgs;
+
+ // draw the columns
+ each(series.points, function (point) {
+ var plotY = point.plotY,
+ graphic = point.graphic;
+
+ if (isNumber(plotY) && point.y !== null) {
+ shapeArgs = point.shapeArgs;
+
+ if (graphic) { // update
+ graphic[
+ chart.pointCount < animationLimit ? 'animate' : 'attr'
+ ](
+ merge(shapeArgs)
+ );
+
+ } else {
+ point.graphic = graphic =
+ renderer[point.shapeType](shapeArgs)
+ .add(point.group || series.group);
+ }
+
+ // Border radius is not stylable (#6900)
+ if (options.borderRadius) {
+ graphic.attr({
+ r: options.borderRadius
+ });
+ }
+
+
+ // Presentational
+ graphic
+ .attr(series.pointAttribs(
+ point,
+ point.selected && 'select'
+ ))
+ .shadow(
+ options.shadow,
+ null,
+ options.stacking && !options.borderRadius
+ );
+
+
+ graphic.addClass(point.getClassName(), true);
+
+
+ } else if (graphic) {
+ point.graphic = graphic.destroy(); // #1269
+ }
+ });
+ },
+
+ /**
+ * Animate the column heights one by one from zero
+ * @param {Boolean} init Whether to initialize the animation or run it
+ */
+ animate: function (init) {
+ var series = this,
+ yAxis = this.yAxis,
+ options = series.options,
+ inverted = this.chart.inverted,
+ attr = {},
+ translateProp = inverted ? 'translateX' : 'translateY',
+ translateStart,
+ translatedThreshold;
+
+ if (svg) { // VML is too slow anyway
+ if (init) {
+ attr.scaleY = 0.001;
+ translatedThreshold = Math.min(
+ yAxis.pos + yAxis.len,
+ Math.max(yAxis.pos, yAxis.toPixels(options.threshold))
+ );
+ if (inverted) {
+ attr.translateX = translatedThreshold - yAxis.len;
+ } else {
+ attr.translateY = translatedThreshold;
+ }
+ series.group.attr(attr);
+
+ } else { // run the animation
+ translateStart = series.group.attr(translateProp);
+ series.group.animate(
+ { scaleY: 1 },
+ extend(animObject(series.options.animation
+ ), {
+ // Do the scale synchronously to ensure smooth updating
+ // (#5030, #7228)
+ step: function (val, fx) {
+
+ attr[translateProp] =
+ translateStart +
+ fx.pos * (yAxis.pos - translateStart);
+ series.group.attr(attr);
+ }
+ }));
+
+ // delete this function to allow it only once
+ series.animate = null;
+ }
+ }
+ },
+
+ /**
+ * Remove this series from the chart
+ */
+ remove: function () {
+ var series = this,
+ chart = series.chart;
+
+ // column and bar series affects other series of the same type
+ // as they are either stacked or grouped
+ if (chart.hasRendered) {
+ each(chart.series, function (otherSeries) {
+ if (otherSeries.type === series.type) {
+ otherSeries.isDirty = true;
+ }
+ });
+ }
+
+ Series.prototype.remove.apply(series, arguments);
+ }
+});
+
+
+/**
+ * A `column` series. If the [type](#series.column.type) option is
+ * not specified, it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * column](#plotOptions.column).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.column
+ * @excluding dataParser,dataURL,marker
+ *
+ * @product highcharts highstock
+ * @apioption series.column
+ */
+
+/**
+ * @excluding halo,lineWidth,lineWidthPlus,marker
+ * @product highcharts highstock
+ * @apioption series.column.states.hover
+ */
+
+/**
+ * @excluding halo,lineWidth,lineWidthPlus,marker
+ * @product highcharts highstock
+ * @apioption series.column.states.select
+ */
+
+/**
+ * An array of data points for the series. For the `column` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values
+ * will be interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options. If the axis has
+ * categories, these will be used. Example:
+ *
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond
+ * to `x,y`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred.
+ *
+ * ```js
+ * data: [
+ * [0, 6],
+ * [1, 2],
+ * [2, 6]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.column.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 9,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 6,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @excluding marker
+ * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ * @product highcharts highstock
+ * @apioption series.column.data
+ */
+
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+var seriesType = H.seriesType;
+
+/**
+ * The Bar series class
+ */
+seriesType('bar', 'column', null, {
+ inverted: true
+});
+/**
+ * A bar series is a special type of column series where the columns are
+ * horizontal.
+ *
+ * @sample highcharts/demo/bar-basic/ Bar chart
+ * @extends {plotOptions.column}
+ * @product highcharts
+ * @optionparent plotOptions.bar
+ */
+
+
+/**
+ * A `bar` series. If the [type](#series.bar.type) option is not specified,
+ * it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * bar](#plotOptions.bar).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.bar
+ * @excluding dataParser,dataURL
+ * @product highcharts
+ * @apioption series.bar
+ */
+
+/**
+ * An array of data points for the series. For the `bar` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values
+ * will be interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options. If the axis has
+ * categories, these will be used. Example:
+ *
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond
+ * to `x,y`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred.
+ *
+ * ```js
+ * data: [
+ * [0, 5],
+ * [1, 10],
+ * [2, 3]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.bar.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 1,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 10,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @extends series.column.data
+ * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
+ * @product highcharts
+ * @apioption series.bar.data
+ */
+
+/**
+ * Alignment of the data label relative to the data point.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
+ * Data labels inside the bar
+ * @default left
+ * @product highcharts
+ * @apioption plotOptions.bar.dataLabels.align
+ */
+
+/**
+ * The x position of the data label relative to the data point.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
+ * Data labels inside the bar
+ * @default 5
+ * @product highcharts
+ * @apioption plotOptions.bar.dataLabels.x
+ */
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var Series = H.Series,
+ seriesType = H.seriesType;
+
+/**
+ * A scatter plot uses cartesian coordinates to display values for two variables
+ * for a set of data.
+ *
+ * @sample {highcharts} highcharts/demo/scatter/ Scatter plot
+ *
+ * @extends {plotOptions.line}
+ * @product highcharts highstock
+ * @optionparent plotOptions.scatter
+ */
+seriesType('scatter', 'line', {
+
+ /**
+ * The width of the line connecting the data points.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/
+ * 0 by default
+ * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/
+ * 1px
+ * @default 0
+ * @product highcharts highstock
+ */
+ lineWidth: 0,
+
+ findNearestPointBy: 'xy',
+ marker: {
+ enabled: true // Overrides auto-enabling in line series (#3647)
+ },
+
+ /**
+ * Sticky tracking of mouse events. When true, the `mouseOut` event
+ * on a series isn't triggered until the mouse moves over another series,
+ * or out of the plot area. When false, the `mouseOut` event on a series
+ * is triggered when the mouse leaves the area around the series' graph
+ * or markers. This also implies the tooltip. When `stickyTracking`
+ * is false and `tooltip.shared` is false, the tooltip will be hidden
+ * when moving the mouse between series.
+ *
+ * @type {Boolean}
+ * @default false
+ * @product highcharts highstock
+ * @apioption plotOptions.scatter.stickyTracking
+ */
+
+ /**
+ * A configuration object for the tooltip rendering of each single
+ * series. Properties are inherited from #tooltip .
+ * Overridable properties are `headerFormat`, `pointFormat`, `yDecimals`,
+ * `xDateFormat`, `yPrefix` and `ySuffix`. Unlike other series, in
+ * a scatter plot the series.name by default shows in the headerFormat
+ * and point.x and point.y in the pointFormat.
+ *
+ * @product highcharts highstock
+ */
+ tooltip: {
+
+ headerFormat:
+ '\u25CF ' +
+ ' {series.name} ',
+
+
+ pointFormat: 'x: {point.x} y: {point.y} '
+ }
+
+// Prototype members
+}, {
+ sorted: false,
+ requireSorting: false,
+ noSharedTooltip: true,
+ trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
+ takeOrdinalPosition: false, // #2342
+ drawGraph: function () {
+ if (this.options.lineWidth) {
+ Series.prototype.drawGraph.call(this);
+ }
+ }
+});
+
+/**
+ * A `scatter` series. If the [type](#series.scatter.type) option is
+ * not specified, it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * scatter](#plotOptions.scatter).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.scatter
+ * @excluding dataParser,dataURL,stack
+ * @product highcharts highstock
+ * @apioption series.scatter
+ */
+
+/**
+ * An array of data points for the series. For the `scatter` series
+ * type, points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values
+ * will be interpreted as `y` options. The `x` values will be automatically
+ * calculated, either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options. If the axis has
+ * categories, these will be used. Example:
+ *
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of arrays with 2 values. In this case, the values correspond
+ * to `x,y`. If the first value is a string, it is applied as the name
+ * of the point, and the `x` value is inferred.
+ *
+ * ```js
+ * data: [
+ * [0, 0],
+ * [1, 8],
+ * [2, 9]
+ * ]
+ * ```
+ *
+ * 3. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.scatter.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * y: 2,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * y: 4,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @sample {highcharts} highcharts/chart/reflow-true/
+ * Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/
+ * Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
+ * Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/
+ * Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/
+ * Config objects
+ * @product highcharts highstock
+ * @apioption series.scatter.data
+ */
+
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var deg2rad = H.deg2rad,
+ isNumber = H.isNumber,
+ pick = H.pick,
+ relativeLength = H.relativeLength;
+H.CenteredSeriesMixin = {
+ /**
+ * Get the center of the pie based on the size and center options relative to the
+ * plot area. Borrowed by the polar and gauge series types.
+ */
+ getCenter: function () {
+
+ var options = this.options,
+ chart = this.chart,
+ slicingRoom = 2 * (options.slicedOffset || 0),
+ handleSlicingRoom,
+ plotWidth = chart.plotWidth - 2 * slicingRoom,
+ plotHeight = chart.plotHeight - 2 * slicingRoom,
+ centerOption = options.center,
+ positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
+ smallestSize = Math.min(plotWidth, plotHeight),
+ i,
+ value;
+
+ for (i = 0; i < 4; ++i) {
+ value = positions[i];
+ handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value));
+
+ // i == 0: centerX, relative to width
+ // i == 1: centerY, relative to height
+ // i == 2: size, relative to smallestSize
+ // i == 3: innerSize, relative to size
+ positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) +
+ (handleSlicingRoom ? slicingRoom : 0);
+
+ }
+ // innerSize cannot be larger than size (#3632)
+ if (positions[3] > positions[2]) {
+ positions[3] = positions[2];
+ }
+ return positions;
+ },
+ /**
+ * getStartAndEndRadians - Calculates start and end angles in radians.
+ * Used in series types such as pie and sunburst.
+ *
+ * @param {Number} start Start angle in degrees.
+ * @param {Number} end Start angle in degrees.
+ * @return {object} Returns an object containing start and end angles as
+ * radians.
+ */
+ getStartAndEndRadians: function getStartAndEndRadians(start, end) {
+ var startAngle = isNumber(start) ? start : 0, // must be a number
+ endAngle = (
+ (
+ isNumber(end) && // must be a number
+ end > startAngle && // must be larger than the start angle
+ // difference must be less than 360 degrees
+ (end - startAngle) < 360
+ ) ?
+ end :
+ startAngle + 360
+ ),
+ correction = -90;
+ return {
+ start: deg2rad * (startAngle + correction),
+ end: deg2rad * (endAngle + correction)
+ };
+ }
+};
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ CenteredSeriesMixin = H.CenteredSeriesMixin,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians,
+ inArray = H.inArray,
+ LegendSymbolMixin = H.LegendSymbolMixin,
+ noop = H.noop,
+ pick = H.pick,
+ Point = H.Point,
+ Series = H.Series,
+ seriesType = H.seriesType,
+ seriesTypes = H.seriesTypes,
+ setAnimation = H.setAnimation;
+
+/**
+ * The pie series type.
+ *
+ * @constructor seriesTypes.pie
+ * @augments Series
+ */
+
+/**
+ * A pie chart is a circular graphic which is divided into slices to illustrate
+ * numerical proportion.
+ *
+ * @sample highcharts/demo/pie-basic/ Pie chart
+ *
+ * @extends {plotOptions.line}
+ * @excluding animationLimit,boostThreshold,connectEnds,connectNulls,
+ * cropThreshold,dashStyle,findNearestPointBy,getExtremesFromAll,
+ * lineWidth,marker,negativeColor,pointInterval,pointIntervalUnit,
+ * pointPlacement,pointStart,softThreshold,stacking,step,threshold,
+ * turboThreshold,zoneAxis,zones
+ * @product highcharts
+ * @optionparent plotOptions.pie
+ */
+seriesType('pie', 'line', {
+
+ /**
+ * The center of the pie chart relative to the plot area. Can be percentages
+ * or pixel values. The default behaviour (as of 3.0) is to center
+ * the pie so that all slices and data labels are within the plot area.
+ * As a consequence, the pie may actually jump around in a chart with
+ * dynamic values, as the data labels move. In that case, the center
+ * should be explicitly set, for example to `["50%", "50%"]`.
+ *
+ * @type {Array}
+ * @sample {highcharts} highcharts/plotoptions/pie-center/ Centered at 100, 100
+ * @default [null, null]
+ * @product highcharts
+ */
+ center: [null, null],
+
+ clip: false,
+
+ /** @ignore */
+ colorByPoint: true, // always true for pies
+
+ /**
+ * A series specific or series type specific color set to use instead
+ * of the global [colors](#colors).
+ *
+ * @type {Array}
+ * @sample {highcharts} highcharts/demo/pie-monochrome/ Set default colors for all pies
+ * @since 3.0
+ * @product highcharts
+ * @apioption plotOptions.pie.colors
+ */
+
+ /**
+ * @extends plotOptions.series.dataLabels
+ * @excluding align,allowOverlap,staggerLines,step
+ * @product highcharts
+ */
+ dataLabels: {
+ /**
+ * The color of the line connecting the data label to the pie slice.
+ * The default color is the same as the point's color.
+ *
+ * In styled mode, the connector stroke is given in the
+ * `.highcharts-data-label-connector` class.
+ *
+ * @type {String}
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ Blue connectors
+ * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
+ * @default {point.color}
+ * @since 2.1
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.connectorColor
+ */
+
+ /**
+ * The distance from the data label to the connector.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ No padding
+ * @default 5
+ * @since 2.1
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.connectorPadding
+ */
+
+ /**
+ * The width of the line connecting the data label to the pie slice.
+ *
+ *
+ * In styled mode, the connector stroke width is given in the
+ * `.highcharts-data-label-connector` class.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ Disable the connector
+ * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
+ * @default 1
+ * @since 2.1
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.connectorWidth
+ */
+
+ /**
+ * The distance of the data label from the pie's edge. Negative numbers
+ * put the data label on top of the pie slices. Connectors are only
+ * shown for data labels outside the pie.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ Data labels on top of the pie
+ * @default 30
+ * @since 2.1
+ * @product highcharts
+ */
+ distance: 30,
+
+ /**
+ * Enable or disable the data labels.
+ *
+ * @type {Boolean}
+ * @since 2.1
+ * @product highcharts
+ */
+ enabled: true,
+
+ formatter: function () { // #2945
+ return this.point.isNull ? undefined : this.point.name;
+ },
+
+ /**
+ * Whether to render the connector as a soft arc or a line with sharp
+ * break.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ Soft
+ * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ Non soft
+ * @since 2.1.7
+ * @product highcharts
+ * @apioption plotOptions.pie.dataLabels.softConnector
+ */
+
+ x: 0
+ // y: 0
+ },
+
+ /**
+ * The end angle of the pie in degrees where 0 is top and 90 is right.
+ * Defaults to `startAngle` plus 360.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/demo/pie-semi-circle/ Semi-circle donut
+ * @default null
+ * @since 1.3.6
+ * @product highcharts
+ * @apioption plotOptions.pie.endAngle
+ */
+
+ /**
+ * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries),
+ * this option tells whether the series shall be redrawn as if the
+ * hidden point were `null`.
+ *
+ * The default value changed from `false` to `true` with Highcharts
+ * 3.0.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ True, the hiddden point is ignored
+ * @default true
+ * @since 2.3.0
+ * @product highcharts
+ */
+ ignoreHiddenPoint: true,
+
+ /**
+ * The size of the inner diameter for the pie. A size greater than 0
+ * renders a donut chart. Can be a percentage or pixel value. Percentages
+ * are relative to the pie size. Pixel values are given as integers.
+ *
+ *
+ * Note: in Highcharts < 4.1.2, the percentage was relative to the plot
+ * area, not the pie size.
+ *
+ * @type {String|Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/ 80px inner size
+ * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/ 50% of the plot area
+ * @sample {highcharts} highcharts/demo/3d-pie-donut/ 3D donut
+ * @default 0
+ * @since 2.0
+ * @product highcharts
+ * @apioption plotOptions.pie.innerSize
+ */
+
+ /** @ignore */
+ legendType: 'point',
+
+ /** @ignore */
+ marker: null, // point options are specified in the base options
+
+ /**
+ * The minimum size for a pie in response to auto margins. The pie will
+ * try to shrink to make room for data labels in side the plot area,
+ * but only to this size.
+ *
+ * @type {Number}
+ * @default 80
+ * @since 3.0
+ * @product highcharts
+ * @apioption plotOptions.pie.minSize
+ */
+
+ /**
+ * The diameter of the pie relative to the plot area. Can be a percentage
+ * or pixel value. Pixel values are given as integers. The default
+ * behaviour (as of 3.0) is to scale to the plot area and give room
+ * for data labels within the plot area. As a consequence, the size
+ * of the pie may vary when points are updated and data labels more
+ * around. In that case it is best to set a fixed value, for example
+ * `"75%"`.
+ *
+ * @type {String|Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-size/ Smaller pie
+ * @default
+ * @product highcharts
+ */
+ size: null,
+
+ /**
+ * Whether to display this particular series or series type in the
+ * legend. Since 2.1, pies are not shown in the legend by default.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ One series in the legend, one hidden
+ * @product highcharts
+ */
+ showInLegend: false,
+
+ /**
+ * If a point is sliced, moved out from the center, how many pixels
+ * should it be moved?.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ 20px offset
+ * @default 10
+ * @product highcharts
+ */
+ slicedOffset: 10,
+
+ /**
+ * The start angle of the pie slices in degrees where 0 is top and 90
+ * right.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ Start from right
+ * @default 0
+ * @since 2.3.4
+ * @product highcharts
+ * @apioption plotOptions.pie.startAngle
+ */
+
+ /**
+ * Sticky tracking of mouse events. When true, the `mouseOut` event
+ * on a series isn't triggered until the mouse moves over another series,
+ * or out of the plot area. When false, the `mouseOut` event on a
+ * series is triggered when the mouse leaves the area around the series'
+ * graph or markers. This also implies the tooltip. When `stickyTracking`
+ * is false and `tooltip.shared` is false, the tooltip will be hidden
+ * when moving the mouse between series.
+ *
+ * @product highcharts
+ */
+ stickyTracking: false,
+
+ tooltip: {
+ followPointer: true
+ },
+
+
+ /**
+ * The color of the border surrounding each slice. When `null`, the
+ * border takes the same color as the slice fill. This can be used
+ * together with a `borderWidth` to fill drawing gaps created by antialiazing
+ * artefacts in borderless pies.
+ *
+ * In styled mode, the border stroke is given in the `.highcharts-point` class.
+ *
+ * @type {Color}
+ * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/ Black border
+ * @default #ffffff
+ * @product highcharts
+ */
+ borderColor: '#ffffff',
+
+ /**
+ * The width of the border surrounding each slice.
+ *
+ * When setting the border width to 0, there may be small gaps between
+ * the slices due to SVG antialiasing artefacts. To work around this,
+ * keep the border width at 0.5 or 1, but set the `borderColor` to
+ * `null` instead.
+ *
+ * In styled mode, the border stroke width is given in the `.highcharts-point` class.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/ 3px border
+ * @default 1
+ * @product highcharts
+ */
+ borderWidth: 1,
+
+ states: {
+
+ /**
+ * @extends plotOptions.series.states.hover
+ * @product highcharts
+ */
+ hover: {
+
+ /**
+ * How much to brighten the point on interaction. Requires the main
+ * color to be defined in hex or rgb(a) format.
+ *
+ * In styled mode, the hover brightness is by default replaced
+ * by a fill-opacity given in the `.highcharts-point-hover` class.
+ *
+ * @type {Number}
+ * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/ Brightened by 0.5
+ * @default 0.1
+ * @product highcharts
+ */
+ brightness: 0.1,
+
+ shadow: false
+ }
+ }
+
+
+}, /** @lends seriesTypes.pie.prototype */ {
+ isCartesian: false,
+ requireSorting: false,
+ directTouch: true,
+ noSharedTooltip: true,
+ trackerGroups: ['group', 'dataLabelsGroup'],
+ axisTypes: [],
+ pointAttribs: seriesTypes.column.prototype.pointAttribs,
+ /**
+ * Animate the pies in
+ */
+ animate: function (init) {
+ var series = this,
+ points = series.points,
+ startAngleRad = series.startAngleRad;
+
+ if (!init) {
+ each(points, function (point) {
+ var graphic = point.graphic,
+ args = point.shapeArgs;
+
+ if (graphic) {
+ // start values
+ graphic.attr({
+ r: point.startR || (series.center[3] / 2), // animate from inner radius (#779)
+ start: startAngleRad,
+ end: startAngleRad
+ });
+
+ // animate
+ graphic.animate({
+ r: args.r,
+ start: args.start,
+ end: args.end
+ }, series.options.animation);
+ }
+ });
+
+ // delete this function to allow it only once
+ series.animate = null;
+ }
+ },
+
+ /**
+ * Recompute total chart sum and update percentages of points.
+ */
+ updateTotals: function () {
+ var i,
+ total = 0,
+ points = this.points,
+ len = points.length,
+ point,
+ ignoreHiddenPoint = this.options.ignoreHiddenPoint;
+
+ // Get the total sum
+ for (i = 0; i < len; i++) {
+ point = points[i];
+ total += (ignoreHiddenPoint && !point.visible) ?
+ 0 :
+ point.isNull ? 0 : point.y;
+ }
+ this.total = total;
+
+ // Set each point's properties
+ for (i = 0; i < len; i++) {
+ point = points[i];
+ point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0;
+ point.total = total;
+ }
+ },
+
+ /**
+ * Extend the generatePoints method by adding total and percentage properties to each point
+ */
+ generatePoints: function () {
+ Series.prototype.generatePoints.call(this);
+ this.updateTotals();
+ },
+
+ /**
+ * Do translation for pie slices
+ */
+ translate: function (positions) {
+ this.generatePoints();
+
+ var series = this,
+ cumulative = 0,
+ precision = 1000, // issue #172
+ options = series.options,
+ slicedOffset = options.slicedOffset,
+ connectorOffset = slicedOffset + (options.borderWidth || 0),
+ finalConnectorOffset,
+ start,
+ end,
+ angle,
+ radians = getStartAndEndRadians(options.startAngle, options.endAngle),
+ startAngleRad = series.startAngleRad = radians.start,
+ endAngleRad = series.endAngleRad = radians.end,
+ circ = endAngleRad - startAngleRad, // 2 * Math.PI,
+ points = series.points,
+ radiusX, // the x component of the radius vector for a given point
+ radiusY,
+ labelDistance = options.dataLabels.distance,
+ ignoreHiddenPoint = options.ignoreHiddenPoint,
+ i,
+ len = points.length,
+ point;
+
+ // Get positions - either an integer or a percentage string must be given.
+ // If positions are passed as a parameter, we're in a recursive loop for adjusting
+ // space for data labels.
+ if (!positions) {
+ series.center = positions = series.getCenter();
+ }
+
+ // Utility for getting the x value from a given y, used for anticollision
+ // logic in data labels.
+ // Added point for using specific points' label distance.
+ series.getX = function (y, left, point) {
+ angle = Math.asin(Math.min((y - positions[1]) / (positions[2] / 2 + point.labelDistance), 1));
+ return positions[0] +
+ (left ? -1 : 1) *
+ (Math.cos(angle) * (positions[2] / 2 + point.labelDistance));
+ };
+
+ // Calculate the geometry for each point
+ for (i = 0; i < len; i++) {
+
+ point = points[i];
+
+ // Used for distance calculation for specific point.
+ point.labelDistance = pick(
+ point.options.dataLabels && point.options.dataLabels.distance,
+ labelDistance
+ );
+
+ // Saved for later dataLabels distance calculation.
+ series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance);
+
+ // set start and end angle
+ start = startAngleRad + (cumulative * circ);
+ if (!ignoreHiddenPoint || point.visible) {
+ cumulative += point.percentage / 100;
+ }
+ end = startAngleRad + (cumulative * circ);
+
+ // set the shape
+ point.shapeType = 'arc';
+ point.shapeArgs = {
+ x: positions[0],
+ y: positions[1],
+ r: positions[2] / 2,
+ innerR: positions[3] / 2,
+ start: Math.round(start * precision) / precision,
+ end: Math.round(end * precision) / precision
+ };
+
+ // The angle must stay within -90 and 270 (#2645)
+ angle = (end + start) / 2;
+ if (angle > 1.5 * Math.PI) {
+ angle -= 2 * Math.PI;
+ } else if (angle < -Math.PI / 2) {
+ angle += 2 * Math.PI;
+ }
+
+ // Center for the sliced out slice
+ point.slicedTranslation = {
+ translateX: Math.round(Math.cos(angle) * slicedOffset),
+ translateY: Math.round(Math.sin(angle) * slicedOffset)
+ };
+
+ // set the anchor point for tooltips
+ radiusX = Math.cos(angle) * positions[2] / 2;
+ radiusY = Math.sin(angle) * positions[2] / 2;
+ point.tooltipPos = [
+ positions[0] + radiusX * 0.7,
+ positions[1] + radiusY * 0.7
+ ];
+
+ point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ? 1 : 0;
+ point.angle = angle;
+
+ // Set the anchor point for data labels. Use point.labelDistance
+ // instead of labelDistance // #1174
+ // finalConnectorOffset - not override connectorOffset value.
+ finalConnectorOffset = Math.min(connectorOffset, point.labelDistance / 5); // #1678
+ point.labelPos = [
+ positions[0] + radiusX + Math.cos(angle) * point.labelDistance, // first break of connector
+ positions[1] + radiusY + Math.sin(angle) * point.labelDistance, // a/a
+ positions[0] + radiusX + Math.cos(angle) * finalConnectorOffset, // second break, right outside pie
+ positions[1] + radiusY + Math.sin(angle) * finalConnectorOffset, // a/a
+ positions[0] + radiusX, // landing point for connector
+ positions[1] + radiusY, // a/a
+ point.labelDistance < 0 ? // alignment
+ 'center' :
+ point.half ? 'right' : 'left', // alignment
+ angle // center angle
+ ];
+
+ }
+ },
+
+ drawGraph: null,
+
+ /**
+ * Draw the data points
+ */
+ drawPoints: function () {
+ var series = this,
+ chart = series.chart,
+ renderer = chart.renderer,
+ groupTranslation,
+ graphic,
+ pointAttr,
+ shapeArgs;
+
+
+ var shadow = series.options.shadow;
+ if (shadow && !series.shadowGroup) {
+ series.shadowGroup = renderer.g('shadow')
+ .add(series.group);
+ }
+
+
+ // draw the slices
+ each(series.points, function (point) {
+ graphic = point.graphic;
+ if (!point.isNull) {
+ shapeArgs = point.shapeArgs;
+
+
+ // If the point is sliced, use special translation, else use
+ // plot area traslation
+ groupTranslation = point.getTranslate();
+
+
+ // Put the shadow behind all points
+ var shadowGroup = point.shadowGroup;
+ if (shadow && !shadowGroup) {
+ shadowGroup = point.shadowGroup = renderer.g('shadow')
+ .add(series.shadowGroup);
+ }
+
+ if (shadowGroup) {
+ shadowGroup.attr(groupTranslation);
+ }
+ pointAttr = series.pointAttribs(point, point.selected && 'select');
+
+
+ // Draw the slice
+ if (graphic) {
+ graphic
+ .setRadialReference(series.center)
+
+ .attr(pointAttr)
+
+ .animate(extend(shapeArgs, groupTranslation));
+ } else {
+
+ point.graphic = graphic = renderer[point.shapeType](shapeArgs)
+ .setRadialReference(series.center)
+ .attr(groupTranslation)
+ .add(series.group);
+
+ if (!point.visible) {
+ graphic.attr({ visibility: 'hidden' });
+ }
+
+
+ graphic
+ .attr(pointAttr)
+ .attr({ 'stroke-linejoin': 'round' })
+ .shadow(shadow, shadowGroup);
+
+ }
+
+ graphic.addClass(point.getClassName());
+
+ } else if (graphic) {
+ point.graphic = graphic.destroy();
+ }
+ });
+
+ },
+
+
+ searchPoint: noop,
+
+ /**
+ * Utility for sorting data labels
+ */
+ sortByAngle: function (points, sign) {
+ points.sort(function (a, b) {
+ return a.angle !== undefined && (b.angle - a.angle) * sign;
+ });
+ },
+
+ /**
+ * Use a simple symbol from LegendSymbolMixin
+ */
+ drawLegendSymbol: LegendSymbolMixin.drawRectangle,
+
+ /**
+ * Use the getCenter method from drawLegendSymbol
+ */
+ getCenter: CenteredSeriesMixin.getCenter,
+
+ /**
+ * Pies don't have point marker symbols
+ */
+ getSymbol: noop
+
+
+/**
+ * @constructor seriesTypes.pie.prototype.pointClass
+ * @extends {Point}
+ */
+}, /** @lends seriesTypes.pie.prototype.pointClass.prototype */ {
+ /**
+ * Initiate the pie slice
+ */
+ init: function () {
+
+ Point.prototype.init.apply(this, arguments);
+
+ var point = this,
+ toggleSlice;
+
+ point.name = pick(point.name, 'Slice');
+
+ // add event listener for select
+ toggleSlice = function (e) {
+ point.slice(e.type === 'select');
+ };
+ addEvent(point, 'select', toggleSlice);
+ addEvent(point, 'unselect', toggleSlice);
+
+ return point;
+ },
+
+ /**
+ * Negative points are not valid (#1530, #3623, #5322)
+ */
+ isValid: function () {
+ return H.isNumber(this.y, true) && this.y >= 0;
+ },
+
+ /**
+ * Toggle the visibility of the pie slice
+ * @param {Boolean} vis Whether to show the slice or not. If undefined, the
+ * visibility is toggled
+ */
+ setVisible: function (vis, redraw) {
+ var point = this,
+ series = point.series,
+ chart = series.chart,
+ ignoreHiddenPoint = series.options.ignoreHiddenPoint;
+
+ redraw = pick(redraw, ignoreHiddenPoint);
+
+ if (vis !== point.visible) {
+
+ // If called without an argument, toggle visibility
+ point.visible = point.options.visible = vis = vis === undefined ? !point.visible : vis;
+ series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
+
+ // Show and hide associated elements. This is performed regardless of redraw or not,
+ // because chart.redraw only handles full series.
+ each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) {
+ if (point[key]) {
+ point[key][vis ? 'show' : 'hide'](true);
+ }
+ });
+
+ if (point.legendItem) {
+ chart.legend.colorizeItem(point, vis);
+ }
+
+ // #4170, hide halo after hiding point
+ if (!vis && point.state === 'hover') {
+ point.setState('');
+ }
+
+ // Handle ignore hidden slices
+ if (ignoreHiddenPoint) {
+ series.isDirty = true;
+ }
+
+ if (redraw) {
+ chart.redraw();
+ }
+ }
+ },
+
+ /**
+ * Set or toggle whether the slice is cut out from the pie
+ * @param {Boolean} sliced When undefined, the slice state is toggled
+ * @param {Boolean} redraw Whether to redraw the chart. True by default.
+ */
+ slice: function (sliced, redraw, animation) {
+ var point = this,
+ series = point.series,
+ chart = series.chart;
+
+ setAnimation(animation, chart);
+
+ // redraw is true by default
+ redraw = pick(redraw, true);
+
+ // if called without an argument, toggle
+ point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
+ series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data
+
+ point.graphic.animate(this.getTranslate());
+
+
+ if (point.shadowGroup) {
+ point.shadowGroup.animate(this.getTranslate());
+ }
+
+ },
+
+ getTranslate: function () {
+ return this.sliced ? this.slicedTranslation : {
+ translateX: 0,
+ translateY: 0
+ };
+ },
+
+ haloPath: function (size) {
+ var shapeArgs = this.shapeArgs;
+
+ return this.sliced || !this.visible ?
+ [] :
+ this.series.chart.renderer.symbols.arc(
+ shapeArgs.x,
+ shapeArgs.y,
+ shapeArgs.r + size,
+ shapeArgs.r + size, {
+ // Substract 1px to ensure the background is not bleeding
+ // through between the halo and the slice (#7495).
+ innerR: this.shapeArgs.r - 1,
+ start: shapeArgs.start,
+ end: shapeArgs.end
+ }
+ );
+ }
+});
+
+/**
+ * A `pie` series. If the [type](#series.pie.type) option is not specified,
+ * it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * pie](#plotOptions.pie).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.pie
+ * @excluding dataParser,dataURL,stack,xAxis,yAxis
+ * @product highcharts
+ * @apioption series.pie
+ */
+
+/**
+ * An array of data points for the series. For the `pie` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of numerical values. In this case, the numerical values
+ * will be interpreted as `y` options. Example:
+ *
+ * ```js
+ * data: [0, 5, 3, 5]
+ * ```
+ *
+ * 2. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.pie.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * y: 1,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * y: 7,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @excluding marker,x
+ * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
+ * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
+ * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
+ * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
+ * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
+ * @product highcharts
+ * @apioption series.pie.data
+ */
+
+/**
+ * The sequential index of the data point in the legend.
+ *
+ * @type {Number}
+ * @product highcharts
+ * @apioption series.pie.data.legendIndex
+ */
+
+/**
+ * Whether to display a slice offset from the center.
+ *
+ * @type {Boolean}
+ * @sample {highcharts} highcharts/point/sliced/ One sliced point
+ * @product highcharts
+ * @apioption series.pie.data.sliced
+ */
+
+/**
+ * Fires when the checkbox next to the point name in the legend is clicked.
+ * One parameter, event, is passed to the function. The state of the
+ * checkbox is found by event.checked. The checked item is found by
+ * event.item. Return false to prevent the default action which is to
+ * toggle the select state of the series.
+ *
+ * @type {Function}
+ * @context Point
+ * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
+ * Alert checkbox status
+ * @since 1.2.0
+ * @product highcharts
+ * @apioption plotOptions.pie.events.checkboxClick
+ */
+
+/**
+ * Not applicable to pies, as the legend item is per point. See point.
+ * events.
+ *
+ * @type {Function}
+ * @since 1.2.0
+ * @product highcharts
+ * @apioption plotOptions.pie.events.legendItemClick
+ */
+
+/**
+ * Fires when the legend item belonging to the pie point (slice) is
+ * clicked. The `this` keyword refers to the point itself. One parameter,
+ * `event`, is passed to the function, containing common event information. The
+ * default action is to toggle the visibility of the point. This can be
+ * prevented by calling `event.preventDefault()`.
+ *
+ * @type {Function}
+ * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/
+ * Confirm toggle visibility
+ * @since 1.2.0
+ * @product highcharts
+ * @apioption plotOptions.pie.point.events.legendItemClick
+ */
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ arrayMax = H.arrayMax,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ format = H.format,
+ map = H.map,
+ merge = H.merge,
+ noop = H.noop,
+ pick = H.pick,
+ relativeLength = H.relativeLength,
+ Series = H.Series,
+ seriesTypes = H.seriesTypes,
+ stableSort = H.stableSort;
+
+/* eslint max-len: ["warn", 80, 4] */
+/**
+ * General distribution algorithm for distributing labels of differing size
+ * along a confined length in two dimensions. The algorithm takes an array of
+ * objects containing a size, a target and a rank. It will place the labels as
+ * close as possible to their targets, skipping the lowest ranked labels if
+ * necessary.
+ */
+H.distribute = function (boxes, len) {
+
+ var i,
+ overlapping = true,
+ origBoxes = boxes, // Original array will be altered with added .pos
+ restBoxes = [], // The outranked overshoot
+ box,
+ target,
+ total = 0;
+
+ function sortByTarget(a, b) {
+ return a.target - b.target;
+ }
+
+ // If the total size exceeds the len, remove those boxes with the lowest
+ // rank
+ i = boxes.length;
+ while (i--) {
+ total += boxes[i].size;
+ }
+
+ // Sort by rank, then slice away overshoot
+ if (total > len) {
+ stableSort(boxes, function (a, b) {
+ return (b.rank || 0) - (a.rank || 0);
+ });
+ i = 0;
+ total = 0;
+ while (total <= len) {
+ total += boxes[i].size;
+ i++;
+ }
+ restBoxes = boxes.splice(i - 1, boxes.length);
+ }
+
+ // Order by target
+ stableSort(boxes, sortByTarget);
+
+
+ // So far we have been mutating the original array. Now
+ // create a copy with target arrays
+ boxes = map(boxes, function (box) {
+ return {
+ size: box.size,
+ targets: [box.target],
+ align: pick(box.align, 0.5)
+ };
+ });
+
+ while (overlapping) {
+ // Initial positions: target centered in box
+ i = boxes.length;
+ while (i--) {
+ box = boxes[i];
+ // Composite box, average of targets
+ target = (
+ Math.min.apply(0, box.targets) +
+ Math.max.apply(0, box.targets)
+ ) / 2;
+ box.pos = Math.min(
+ Math.max(0, target - box.size * box.align),
+ len - box.size
+ );
+ }
+
+ // Detect overlap and join boxes
+ i = boxes.length;
+ overlapping = false;
+ while (i--) {
+ // Overlap
+ if (i > 0 && boxes[i - 1].pos + boxes[i - 1].size > boxes[i].pos) {
+ // Add this size to the previous box
+ boxes[i - 1].size += boxes[i].size;
+ boxes[i - 1].targets = boxes[i - 1]
+ .targets
+ .concat(boxes[i].targets);
+ boxes[i - 1].align = 0.5;
+
+ // Overlapping right, push left
+ if (boxes[i - 1].pos + boxes[i - 1].size > len) {
+ boxes[i - 1].pos = len - boxes[i - 1].size;
+ }
+ boxes.splice(i, 1); // Remove this item
+ overlapping = true;
+ }
+ }
+ }
+
+ // Now the composite boxes are placed, we need to put the original boxes
+ // within them
+ i = 0;
+ each(boxes, function (box) {
+ var posInCompositeBox = 0;
+ each(box.targets, function () {
+ origBoxes[i].pos = box.pos + posInCompositeBox;
+ posInCompositeBox += origBoxes[i].size;
+ i++;
+ });
+ });
+
+ // Add the rest (hidden) boxes and sort by target
+ origBoxes.push.apply(origBoxes, restBoxes);
+ stableSort(origBoxes, sortByTarget);
+};
+
+
+/**
+ * Draw the data labels
+ */
+Series.prototype.drawDataLabels = function () {
+ var series = this,
+ chart = series.chart,
+ seriesOptions = series.options,
+ options = seriesOptions.dataLabels,
+ points = series.points,
+ pointOptions,
+ generalOptions,
+ hasRendered = series.hasRendered || 0,
+ str,
+ dataLabelsGroup,
+ defer = pick(options.defer, !!seriesOptions.animation),
+ renderer = chart.renderer;
+
+ /*
+ * Handle the dataLabels.filter option.
+ */
+ function applyFilter(point, options) {
+ var filter = options.filter,
+ op,
+ prop,
+ val;
+ if (filter) {
+ op = filter.operator;
+ prop = point[filter.property];
+ val = filter.value;
+ if (
+ (op === '>' && prop > val) ||
+ (op === '<' && prop < val) ||
+ (op === '>=' && prop >= val) ||
+ (op === '<=' && prop <= val) ||
+ (op === '==' && prop == val) || // eslint-disable-line eqeqeq
+ (op === '===' && prop === val)
+ ) {
+ return true;
+ }
+ return false;
+ }
+ return true;
+ }
+
+ if (options.enabled || series._hasPointLabels) {
+
+ // Process default alignment of data labels for columns
+ if (series.dlProcessOptions) {
+ series.dlProcessOptions(options);
+ }
+
+ // Create a separate group for the data labels to avoid rotation
+ dataLabelsGroup = series.plotGroup(
+ 'dataLabelsGroup',
+ 'data-labels',
+ defer && !hasRendered ? 'hidden' : 'visible', // #5133
+ options.zIndex || 6
+ );
+
+ if (defer) {
+ dataLabelsGroup.attr({ opacity: +hasRendered }); // #3300
+ if (!hasRendered) {
+ addEvent(series, 'afterAnimate', function () {
+ if (series.visible) { // #2597, #3023, #3024
+ dataLabelsGroup.show(true);
+ }
+ dataLabelsGroup[
+ seriesOptions.animation ? 'animate' : 'attr'
+ ]({ opacity: 1 }, { duration: 200 });
+ });
+ }
+ }
+
+ // Make the labels for each point
+ generalOptions = options;
+ each(points, function (point) {
+ var enabled,
+ dataLabel = point.dataLabel,
+ labelConfig,
+ attr,
+ rotation,
+ connector = point.connector,
+ isNew = !dataLabel,
+ style,
+ formatString;
+
+ // Determine if each data label is enabled
+ // @note dataLabelAttribs (like pointAttribs) would eradicate
+ // the need for dlOptions, and simplify the section below.
+ pointOptions = point.dlOptions || // dlOptions is used in treemaps
+ (point.options && point.options.dataLabels);
+ enabled = pick(
+ pointOptions && pointOptions.enabled,
+ generalOptions.enabled
+ ) && !point.isNull; // #2282, #4641, #7112
+
+ if (enabled) {
+ enabled = applyFilter(point, pointOptions || options) === true;
+ }
+
+ if (enabled) {
+ // Create individual options structure that can be extended
+ // without affecting others
+ options = merge(generalOptions, pointOptions);
+ labelConfig = point.getLabelConfig();
+ formatString = (
+ options[point.formatPrefix + 'Format'] ||
+ options.format
+ );
+
+ str = defined(formatString) ?
+ format(formatString, labelConfig, chart.time) :
+ (
+ options[point.formatPrefix + 'Formatter'] ||
+ options.formatter
+ ).call(labelConfig, options);
+
+ style = options.style;
+ rotation = options.rotation;
+
+ // Determine the color
+ style.color = pick(
+ options.color,
+ style.color,
+ series.color,
+ '#000000'
+ );
+ // Get automated contrast color
+ if (style.color === 'contrast') {
+ point.contrastColor =
+ renderer.getContrast(point.color || series.color);
+ style.color = options.inside ||
+ pick(point.labelDistance, options.distance) < 0 ||
+ !!seriesOptions.stacking ?
+ point.contrastColor :
+ '#000000';
+ }
+ if (seriesOptions.cursor) {
+ style.cursor = seriesOptions.cursor;
+ }
+
+
+ attr = {
+
+ fill: options.backgroundColor,
+ stroke: options.borderColor,
+ 'stroke-width': options.borderWidth,
+
+ r: options.borderRadius || 0,
+ rotation: rotation,
+ padding: options.padding,
+ zIndex: 1
+ };
+
+ // Remove unused attributes (#947)
+ H.objectEach(attr, function (val, name) {
+ if (val === undefined) {
+ delete attr[name];
+ }
+ });
+ }
+ // If the point is outside the plot area, destroy it. #678, #820
+ if (dataLabel && (!enabled || !defined(str))) {
+ point.dataLabel = dataLabel = dataLabel.destroy();
+ if (connector) {
+ point.connector = connector.destroy();
+ }
+ // Individual labels are disabled if the are explicitly disabled
+ // in the point options, or if they fall outside the plot area.
+ } else if (enabled && defined(str)) {
+ // create new label
+ if (!dataLabel) {
+ dataLabel = point.dataLabel = rotation ?
+
+ renderer.text(str, 0, -9999) // labels don't rotate
+ .addClass('highcharts-data-label') :
+
+ renderer.label(
+ str,
+ 0,
+ -9999,
+ options.shape,
+ null,
+ null,
+ options.useHTML,
+ null,
+ 'data-label'
+ );
+
+ dataLabel.addClass(
+ ' highcharts-data-label-color-' + point.colorIndex +
+ ' ' + (options.className || '') +
+ (options.useHTML ? 'highcharts-tracker' : '') // #3398
+ );
+ } else {
+ attr.text = str;
+ }
+ dataLabel.attr(attr);
+
+ // Styles must be applied before add in order to read text
+ // bounding box
+ dataLabel.css(style).shadow(options.shadow);
+
+
+ if (!dataLabel.added) {
+ dataLabel.add(dataLabelsGroup);
+ }
+ // Now the data label is created and placed at 0,0, so we need
+ // to align it
+ series.alignDataLabel(point, dataLabel, options, null, isNew);
+ }
+ });
+ }
+};
+
+/**
+ * Align each individual data label
+ */
+Series.prototype.alignDataLabel = function (
+ point,
+ dataLabel,
+ options,
+ alignTo,
+ isNew
+) {
+ var chart = this.chart,
+ inverted = chart.inverted,
+ plotX = pick(point.dlBox && point.dlBox.centerX, point.plotX, -9999),
+ plotY = pick(point.plotY, -9999),
+ bBox = dataLabel.getBBox(),
+ fontSize,
+ baseline,
+ rotation = options.rotation,
+ normRotation,
+ negRotation,
+ align = options.align,
+ rotCorr, // rotation correction
+ // Math.round for rounding errors (#2683), alignTo to allow column
+ // labels (#2700)
+ visible =
+ this.visible &&
+ (
+ point.series.forceDL ||
+ chart.isInsidePlot(plotX, Math.round(plotY), inverted) ||
+ (
+ alignTo && chart.isInsidePlot(
+ plotX,
+ inverted ?
+ alignTo.x + 1 :
+ alignTo.y + alignTo.height - 1,
+ inverted
+ )
+ )
+ ),
+ alignAttr, // the final position;
+ justify = pick(options.overflow, 'justify') === 'justify';
+
+ if (visible) {
+
+
+ fontSize = options.style.fontSize;
+
+
+ baseline = chart.renderer.fontMetrics(fontSize, dataLabel).b;
+
+ // The alignment box is a singular point
+ alignTo = extend({
+ x: inverted ? this.yAxis.len - plotY : plotX,
+ y: Math.round(inverted ? this.xAxis.len - plotX : plotY),
+ width: 0,
+ height: 0
+ }, alignTo);
+
+ // Add the text size for alignment calculation
+ extend(options, {
+ width: bBox.width,
+ height: bBox.height
+ });
+
+ // Allow a hook for changing alignment in the last moment, then do the
+ // alignment
+ if (rotation) {
+ justify = false; // Not supported for rotated text
+ rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723
+ alignAttr = {
+ x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x,
+ y: (
+ alignTo.y +
+ options.y +
+ { top: 0, middle: 0.5, bottom: 1 }[options.verticalAlign] *
+ alignTo.height
+ )
+ };
+ dataLabel[isNew ? 'attr' : 'animate'](alignAttr)
+ .attr({ // #3003
+ align: align
+ });
+
+ // Compensate for the rotated label sticking out on the sides
+ normRotation = (rotation + 720) % 360;
+ negRotation = normRotation > 180 && normRotation < 360;
+
+ if (align === 'left') {
+ alignAttr.y -= negRotation ? bBox.height : 0;
+ } else if (align === 'center') {
+ alignAttr.x -= bBox.width / 2;
+ alignAttr.y -= bBox.height / 2;
+ } else if (align === 'right') {
+ alignAttr.x -= bBox.width;
+ alignAttr.y -= negRotation ? 0 : bBox.height;
+ }
+
+
+ } else {
+ dataLabel.align(options, null, alignTo);
+ alignAttr = dataLabel.alignAttr;
+ }
+
+ // Handle justify or crop
+ if (justify) {
+ point.isLabelJustified = this.justifyDataLabel(
+ dataLabel,
+ options,
+ alignAttr,
+ bBox,
+ alignTo,
+ isNew
+ );
+
+ // Now check that the data label is within the plot area
+ } else if (pick(options.crop, true)) {
+ visible =
+ chart.isInsidePlot(
+ alignAttr.x,
+ alignAttr.y
+ ) &&
+ chart.isInsidePlot(
+ alignAttr.x + bBox.width,
+ alignAttr.y + bBox.height
+ );
+ }
+
+ // When we're using a shape, make it possible with a connector or an
+ // arrow pointing to thie point
+ if (options.shape && !rotation) {
+ dataLabel[isNew ? 'attr' : 'animate']({
+ anchorX: inverted ? chart.plotWidth - point.plotY : point.plotX,
+ anchorY: inverted ? chart.plotHeight - point.plotX : point.plotY
+ });
+ }
+ }
+
+ // Show or hide based on the final aligned position
+ if (!visible) {
+ dataLabel.attr({ y: -9999 });
+ dataLabel.placed = false; // don't animate back in
+ }
+
+};
+
+/**
+ * If data labels fall partly outside the plot area, align them back in, in a
+ * way that doesn't hide the point.
+ */
+Series.prototype.justifyDataLabel = function (
+ dataLabel,
+ options,
+ alignAttr,
+ bBox,
+ alignTo,
+ isNew
+) {
+ var chart = this.chart,
+ align = options.align,
+ verticalAlign = options.verticalAlign,
+ off,
+ justified,
+ padding = dataLabel.box ? 0 : (dataLabel.padding || 0);
+
+ // Off left
+ off = alignAttr.x + padding;
+ if (off < 0) {
+ if (align === 'right') {
+ options.align = 'left';
+ } else {
+ options.x = -off;
+ }
+ justified = true;
+ }
+
+ // Off right
+ off = alignAttr.x + bBox.width - padding;
+ if (off > chart.plotWidth) {
+ if (align === 'left') {
+ options.align = 'right';
+ } else {
+ options.x = chart.plotWidth - off;
+ }
+ justified = true;
+ }
+
+ // Off top
+ off = alignAttr.y + padding;
+ if (off < 0) {
+ if (verticalAlign === 'bottom') {
+ options.verticalAlign = 'top';
+ } else {
+ options.y = -off;
+ }
+ justified = true;
+ }
+
+ // Off bottom
+ off = alignAttr.y + bBox.height - padding;
+ if (off > chart.plotHeight) {
+ if (verticalAlign === 'top') {
+ options.verticalAlign = 'bottom';
+ } else {
+ options.y = chart.plotHeight - off;
+ }
+ justified = true;
+ }
+
+ if (justified) {
+ dataLabel.placed = !isNew;
+ dataLabel.align(options, null, alignTo);
+ }
+
+ return justified;
+};
+
+/**
+ * Override the base drawDataLabels method by pie specific functionality
+ */
+if (seriesTypes.pie) {
+ seriesTypes.pie.prototype.drawDataLabels = function () {
+ var series = this,
+ data = series.data,
+ point,
+ chart = series.chart,
+ options = series.options.dataLabels,
+ connectorPadding = pick(options.connectorPadding, 10),
+ connectorWidth = pick(options.connectorWidth, 1),
+ plotWidth = chart.plotWidth,
+ plotHeight = chart.plotHeight,
+ connector,
+ seriesCenter = series.center,
+ radius = seriesCenter[2] / 2,
+ centerY = seriesCenter[1],
+ dataLabel,
+ dataLabelWidth,
+ labelPos,
+ labelHeight,
+ // divide the points into right and left halves for anti collision
+ halves = [
+ [], // right
+ [] // left
+ ],
+ x,
+ y,
+ visibility,
+ j,
+ overflow = [0, 0, 0, 0]; // top, right, bottom, left
+
+ // get out if not enabled
+ if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
+ return;
+ }
+
+ // Reset all labels that have been shortened
+ each(data, function (point) {
+ if (point.dataLabel && point.visible && point.dataLabel.shortened) {
+ point.dataLabel
+ .attr({
+ width: 'auto'
+ }).css({
+ width: 'auto',
+ textOverflow: 'clip'
+ });
+ point.dataLabel.shortened = false;
+ }
+ });
+
+
+ // run parent method
+ Series.prototype.drawDataLabels.apply(series);
+
+ each(data, function (point) {
+ if (point.dataLabel && point.visible) { // #407, #2510
+
+ // Arrange points for detection collision
+ halves[point.half].push(point);
+
+ // Reset positions (#4905)
+ point.dataLabel._pos = null;
+ }
+ });
+
+ /* Loop over the points in each half, starting from the top and bottom
+ * of the pie to detect overlapping labels.
+ */
+ each(halves, function (points, i) {
+
+ var top,
+ bottom,
+ length = points.length,
+ positions = [],
+ naturalY,
+ sideOverflow,
+ positionsIndex, // Point index in positions array.
+ size;
+
+ if (!length) {
+ return;
+ }
+
+ // Sort by angle
+ series.sortByAngle(points, i - 0.5);
+ // Only do anti-collision when we have dataLabels outside the pie
+ // and have connectors. (#856)
+ if (series.maxLabelDistance > 0) {
+ top = Math.max(
+ 0,
+ centerY - radius - series.maxLabelDistance
+ );
+ bottom = Math.min(
+ centerY + radius + series.maxLabelDistance,
+ chart.plotHeight
+ );
+ each(points, function (point) {
+ // check if specific points' label is outside the pie
+ if (point.labelDistance > 0 && point.dataLabel) {
+ // point.top depends on point.labelDistance value
+ // Used for calculation of y value in getX method
+ point.top = Math.max(
+ 0,
+ centerY - radius - point.labelDistance
+ );
+ point.bottom = Math.min(
+ centerY + radius + point.labelDistance,
+ chart.plotHeight
+ );
+ size = point.dataLabel.getBBox().height || 21;
+
+ // point.positionsIndex is needed for getting index of
+ // parameter related to specific point inside positions
+ // array - not every point is in positions array.
+ point.positionsIndex = positions.push({
+ target: point.labelPos[1] - point.top + size / 2,
+ size: size,
+ rank: point.y
+ }) - 1;
+ }
+ });
+ H.distribute(positions, bottom + size - top);
+ }
+
+ // Now the used slots are sorted, fill them up sequentially
+ for (j = 0; j < length; j++) {
+
+ point = points[j];
+ positionsIndex = point.positionsIndex;
+ labelPos = point.labelPos;
+ dataLabel = point.dataLabel;
+ visibility = point.visible === false ? 'hidden' : 'inherit';
+ naturalY = labelPos[1];
+ y = naturalY;
+
+ if (positions && defined(positions[positionsIndex])) {
+ if (positions[positionsIndex].pos === undefined) {
+ visibility = 'hidden';
+ } else {
+ labelHeight = positions[positionsIndex].size;
+ y = point.top + positions[positionsIndex].pos;
+ }
+ }
+
+ // It is needed to delete point.positionIndex for
+ // dynamically added points etc.
+
+ delete point.positionIndex;
+
+ // get the x - use the natural x position for labels near the
+ // top and bottom, to prevent the top and botton slice
+ // connectors from touching each other on either side
+ if (options.justify) {
+ x = seriesCenter[0] +
+ (i ? -1 : 1) * (radius + point.labelDistance);
+ } else {
+ x = series.getX(
+ y < point.top + 2 || y > point.bottom - 2 ?
+ naturalY :
+ y,
+ i,
+ point
+ );
+ }
+
+
+ // Record the placement and visibility
+ dataLabel._attr = {
+ visibility: visibility,
+ align: labelPos[6]
+ };
+ dataLabel._pos = {
+ x: (
+ x +
+ options.x +
+ ({
+ left: connectorPadding,
+ right: -connectorPadding
+ }[labelPos[6]] || 0)
+ ),
+
+ // 10 is for the baseline (label vs text)
+ y: y + options.y - 10
+ };
+ labelPos.x = x;
+ labelPos.y = y;
+
+
+ // Detect overflowing data labels
+ if (pick(options.crop, true)) {
+ dataLabelWidth = dataLabel.getBBox().width;
+
+ sideOverflow = null;
+ // Overflow left
+ if (x - dataLabelWidth < connectorPadding) {
+ sideOverflow = Math.round(
+ dataLabelWidth - x + connectorPadding
+ );
+ overflow[3] = Math.max(sideOverflow, overflow[3]);
+
+ // Overflow right
+ } else if (
+ x + dataLabelWidth >
+ plotWidth - connectorPadding
+ ) {
+ sideOverflow = Math.round(
+ x + dataLabelWidth - plotWidth + connectorPadding
+ );
+ overflow[1] = Math.max(sideOverflow, overflow[1]);
+ }
+
+ // Overflow top
+ if (y - labelHeight / 2 < 0) {
+ overflow[0] = Math.max(
+ Math.round(-y + labelHeight / 2),
+ overflow[0]
+ );
+
+ // Overflow left
+ } else if (y + labelHeight / 2 > plotHeight) {
+ overflow[2] = Math.max(
+ Math.round(y + labelHeight / 2 - plotHeight),
+ overflow[2]
+ );
+ }
+ dataLabel.sideOverflow = sideOverflow;
+ }
+ } // for each point
+ }); // for each half
+
+ // Do not apply the final placement and draw the connectors until we
+ // have verified that labels are not spilling over.
+ if (
+ arrayMax(overflow) === 0 ||
+ this.verifyDataLabelOverflow(overflow)
+ ) {
+
+ // Place the labels in the final position
+ this.placeDataLabels();
+
+ // Draw the connectors
+ if (connectorWidth) {
+ each(this.points, function (point) {
+ var isNew;
+
+ connector = point.connector;
+ dataLabel = point.dataLabel;
+
+ if (
+ dataLabel &&
+ dataLabel._pos &&
+ point.visible &&
+ point.labelDistance > 0
+ ) {
+ visibility = dataLabel._attr.visibility;
+
+ isNew = !connector;
+
+ if (isNew) {
+ point.connector = connector = chart.renderer.path()
+ .addClass('highcharts-data-label-connector ' +
+ ' highcharts-color-' + point.colorIndex)
+ .add(series.dataLabelsGroup);
+
+
+ connector.attr({
+ 'stroke-width': connectorWidth,
+ 'stroke': (
+ options.connectorColor ||
+ point.color ||
+ '#666666'
+ )
+ });
+
+ }
+ connector[isNew ? 'attr' : 'animate']({
+ d: series.connectorPath(point.labelPos)
+ });
+ connector.attr('visibility', visibility);
+
+ } else if (connector) {
+ point.connector = connector.destroy();
+ }
+ });
+ }
+ }
+ };
+
+ /**
+ * Extendable method for getting the path of the connector between the data
+ * label and the pie slice.
+ */
+ seriesTypes.pie.prototype.connectorPath = function (labelPos) {
+ var x = labelPos.x,
+ y = labelPos.y;
+ return pick(this.options.dataLabels.softConnector, true) ? [
+ 'M',
+ // end of the string at the label
+ x + (labelPos[6] === 'left' ? 5 : -5), y,
+ 'C',
+ x, y, // first break, next to the label
+ 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
+ labelPos[2], labelPos[3], // second break
+ 'L',
+ labelPos[4], labelPos[5] // base
+ ] : [
+ 'M',
+ // end of the string at the label
+ x + (labelPos[6] === 'left' ? 5 : -5), y,
+ 'L',
+ labelPos[2], labelPos[3], // second break
+ 'L',
+ labelPos[4], labelPos[5] // base
+ ];
+ };
+
+ /**
+ * Perform the final placement of the data labels after we have verified
+ * that they fall within the plot area.
+ */
+ seriesTypes.pie.prototype.placeDataLabels = function () {
+ each(this.points, function (point) {
+ var dataLabel = point.dataLabel,
+ _pos;
+ if (dataLabel && point.visible) {
+ _pos = dataLabel._pos;
+ if (_pos) {
+
+ // Shorten data labels with ellipsis if they still overflow
+ // after the pie has reached minSize (#223).
+ if (dataLabel.sideOverflow) {
+ dataLabel._attr.width =
+ dataLabel.getBBox().width - dataLabel.sideOverflow;
+ dataLabel.css({
+ width: dataLabel._attr.width + 'px',
+ textOverflow: 'ellipsis'
+ });
+ dataLabel.shortened = true;
+ }
+
+ dataLabel.attr(dataLabel._attr);
+ dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
+ dataLabel.moved = true;
+ } else if (dataLabel) {
+ dataLabel.attr({ y: -9999 });
+ }
+ }
+ }, this);
+ };
+
+ seriesTypes.pie.prototype.alignDataLabel = noop;
+
+ /**
+ * Verify whether the data labels are allowed to draw, or we should run more
+ * translation and data label positioning to keep them inside the plot area.
+ * Returns true when data labels are ready to draw.
+ */
+ seriesTypes.pie.prototype.verifyDataLabelOverflow = function (overflow) {
+
+ var center = this.center,
+ options = this.options,
+ centerOption = options.center,
+ minSize = options.minSize || 80,
+ newSize = minSize,
+ // If a size is set, return true and don't try to shrink the pie
+ // to fit the labels.
+ ret = options.size !== null;
+
+ if (!ret) {
+ // Handle horizontal size and center
+ if (centerOption[0] !== null) { // Fixed center
+ newSize = Math.max(center[2] -
+ Math.max(overflow[1], overflow[3]), minSize);
+
+ } else { // Auto center
+ newSize = Math.max(
+ // horizontal overflow
+ center[2] - overflow[1] - overflow[3],
+ minSize
+ );
+ // horizontal center
+ center[0] += (overflow[3] - overflow[1]) / 2;
+ }
+
+ // Handle vertical size and center
+ if (centerOption[1] !== null) { // Fixed center
+ newSize = Math.max(Math.min(newSize, center[2] -
+ Math.max(overflow[0], overflow[2])), minSize);
+
+ } else { // Auto center
+ newSize = Math.max(
+ Math.min(
+ newSize,
+ // vertical overflow
+ center[2] - overflow[0] - overflow[2]
+ ),
+ minSize
+ );
+ // vertical center
+ center[1] += (overflow[0] - overflow[2]) / 2;
+ }
+
+ // If the size must be decreased, we need to run translate and
+ // drawDataLabels again
+ if (newSize < center[2]) {
+ center[2] = newSize;
+ center[3] = Math.min( // #3632
+ relativeLength(options.innerSize || 0, newSize),
+ newSize
+ );
+ this.translate(center);
+
+ if (this.drawDataLabels) {
+ this.drawDataLabels();
+ }
+ // Else, return true to indicate that the pie and its labels is
+ // within the plot area
+ } else {
+ ret = true;
+ }
+ }
+ return ret;
+ };
+}
+
+if (seriesTypes.column) {
+
+ /**
+ * Override the basic data label alignment by adjusting for the position of
+ * the column
+ */
+ seriesTypes.column.prototype.alignDataLabel = function (
+ point,
+ dataLabel,
+ options,
+ alignTo,
+ isNew
+ ) {
+ var inverted = this.chart.inverted,
+ series = point.series,
+ // data label box for alignment
+ dlBox = point.dlBox || point.shapeArgs,
+ below = pick(
+ point.below, // range series
+ point.plotY > pick(this.translatedThreshold, series.yAxis.len)
+ ),
+ // draw it inside the box?
+ inside = pick(options.inside, !!this.options.stacking),
+ overshoot;
+
+ // Align to the column itself, or the top of it
+ if (dlBox) { // Area range uses this method but not alignTo
+ alignTo = merge(dlBox);
+
+ if (alignTo.y < 0) {
+ alignTo.height += alignTo.y;
+ alignTo.y = 0;
+ }
+ overshoot = alignTo.y + alignTo.height - series.yAxis.len;
+ if (overshoot > 0) {
+ alignTo.height -= overshoot;
+ }
+
+ if (inverted) {
+ alignTo = {
+ x: series.yAxis.len - alignTo.y - alignTo.height,
+ y: series.xAxis.len - alignTo.x - alignTo.width,
+ width: alignTo.height,
+ height: alignTo.width
+ };
+ }
+
+ // Compute the alignment box
+ if (!inside) {
+ if (inverted) {
+ alignTo.x += below ? 0 : alignTo.width;
+ alignTo.width = 0;
+ } else {
+ alignTo.y += below ? alignTo.height : 0;
+ alignTo.height = 0;
+ }
+ }
+ }
+
+
+ // When alignment is undefined (typically columns and bars), display the
+ // individual point below or above the point depending on the threshold
+ options.align = pick(
+ options.align,
+ !inverted || inside ? 'center' : below ? 'right' : 'left'
+ );
+ options.verticalAlign = pick(
+ options.verticalAlign,
+ inverted || inside ? 'middle' : below ? 'top' : 'bottom'
+ );
+
+ // Call the parent method
+ Series.prototype.alignDataLabel.call(
+ this,
+ point,
+ dataLabel,
+ options,
+ alignTo,
+ isNew
+ );
+
+ // If label was justified and we have contrast, set it:
+ if (point.isLabelJustified && point.contrastColor) {
+ point.dataLabel.css({
+ color: point.contrastColor
+ });
+ }
+ };
+}
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2009-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+/**
+ * Highcharts module to hide overlapping data labels. This module is included in
+ * Highcharts.
+ */
+var Chart = H.Chart,
+ each = H.each,
+ objectEach = H.objectEach,
+ pick = H.pick,
+ addEvent = H.addEvent;
+
+// Collect potensial overlapping data labels. Stack labels probably don't need
+// to be considered because they are usually accompanied by data labels that lie
+// inside the columns.
+addEvent(Chart.prototype, 'render', function collectAndHide() {
+ var labels = [];
+
+ // Consider external label collectors
+ each(this.labelCollectors || [], function (collector) {
+ labels = labels.concat(collector());
+ });
+
+ each(this.yAxis || [], function (yAxis) {
+ if (
+ yAxis.options.stackLabels &&
+ !yAxis.options.stackLabels.allowOverlap
+ ) {
+ objectEach(yAxis.stacks, function (stack) {
+ objectEach(stack, function (stackItem) {
+ labels.push(stackItem.label);
+ });
+ });
+ }
+ });
+
+ each(this.series || [], function (series) {
+ var dlOptions = series.options.dataLabels,
+ // Range series have two collections
+ collections = series.dataLabelCollections || ['dataLabel'];
+
+ if (
+ (dlOptions.enabled || series._hasPointLabels) &&
+ !dlOptions.allowOverlap &&
+ series.visible
+ ) { // #3866
+ each(collections, function (coll) {
+ each(series.points, function (point) {
+ if (point[coll]) {
+ point[coll].labelrank = pick(
+ point.labelrank,
+ point.shapeArgs && point.shapeArgs.height
+ ); // #4118
+ labels.push(point[coll]);
+ }
+ });
+ });
+ }
+ });
+ this.hideOverlappingLabels(labels);
+});
+
+/**
+ * Hide overlapping labels. Labels are moved and faded in and out on zoom to
+ * provide a smooth visual imression.
+ */
+Chart.prototype.hideOverlappingLabels = function (labels) {
+
+ var len = labels.length,
+ label,
+ i,
+ j,
+ label1,
+ label2,
+ isIntersecting,
+ pos1,
+ pos2,
+ parent1,
+ parent2,
+ padding,
+ bBox,
+ intersectRect = function (x1, y1, w1, h1, x2, y2, w2, h2) {
+ return !(
+ x2 > x1 + w1 ||
+ x2 + w2 < x1 ||
+ y2 > y1 + h1 ||
+ y2 + h2 < y1
+ );
+ };
+
+ for (i = 0; i < len; i++) {
+ label = labels[i];
+ if (label) {
+
+ // Mark with initial opacity
+ label.oldOpacity = label.opacity;
+ label.newOpacity = 1;
+
+ // Get width and height if pure text nodes (stack labels)
+ if (!label.width) {
+ bBox = label.getBBox();
+ label.width = bBox.width;
+ label.height = bBox.height;
+ }
+ }
+ }
+
+ // Prevent a situation in a gradually rising slope, that each label will
+ // hide the previous one because the previous one always has lower rank.
+ labels.sort(function (a, b) {
+ return (b.labelrank || 0) - (a.labelrank || 0);
+ });
+
+ // Detect overlapping labels
+ for (i = 0; i < len; i++) {
+ label1 = labels[i];
+
+ for (j = i + 1; j < len; ++j) {
+ label2 = labels[j];
+ if (
+ label1 && label2 &&
+ label1 !== label2 && // #6465, polar chart with connectEnds
+ label1.placed && label2.placed &&
+ label1.newOpacity !== 0 && label2.newOpacity !== 0
+ ) {
+ pos1 = label1.alignAttr;
+ pos2 = label2.alignAttr;
+ // Different panes have different positions
+ parent1 = label1.parentGroup;
+ parent2 = label2.parentGroup;
+ // Substract the padding if no background or border (#4333)
+ padding = 2 * (label1.box ? 0 : (label1.padding || 0));
+ isIntersecting = intersectRect(
+ pos1.x + parent1.translateX,
+ pos1.y + parent1.translateY,
+ label1.width - padding,
+ label1.height - padding,
+ pos2.x + parent2.translateX,
+ pos2.y + parent2.translateY,
+ label2.width - padding,
+ label2.height - padding
+ );
+
+ if (isIntersecting) {
+ (label1.labelrank < label2.labelrank ? label1 : label2)
+ .newOpacity = 0;
+ }
+ }
+ }
+ }
+
+ // Hide or show
+ each(labels, function (label) {
+ var complete,
+ newOpacity;
+
+ if (label) {
+ newOpacity = label.newOpacity;
+
+ if (label.oldOpacity !== newOpacity && label.placed) {
+
+ // Make sure the label is completely hidden to avoid catching
+ // clicks (#4362)
+ if (newOpacity) {
+ label.show(true);
+ } else {
+ complete = function () {
+ label.hide();
+ };
+ }
+
+ // Animate or set the opacity
+ label.alignAttr.opacity = newOpacity;
+ label[label.isOld ? 'animate' : 'attr'](
+ label.alignAttr,
+ null,
+ complete
+ );
+
+ }
+ label.isOld = true;
+ }
+ });
+};
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ Chart = H.Chart,
+ createElement = H.createElement,
+ css = H.css,
+ defaultOptions = H.defaultOptions,
+ defaultPlotOptions = H.defaultPlotOptions,
+ each = H.each,
+ extend = H.extend,
+ fireEvent = H.fireEvent,
+ hasTouch = H.hasTouch,
+ inArray = H.inArray,
+ isObject = H.isObject,
+ Legend = H.Legend,
+ merge = H.merge,
+ pick = H.pick,
+ Point = H.Point,
+ Series = H.Series,
+ seriesTypes = H.seriesTypes,
+ svg = H.svg,
+ TrackerMixin;
+
+/**
+ * TrackerMixin for points and graphs.
+ */
+TrackerMixin = H.TrackerMixin = {
+
+ /**
+ * Draw the tracker for a point.
+ */
+ drawTrackerPoint: function () {
+ var series = this,
+ chart = series.chart,
+ pointer = chart.pointer,
+ onMouseOver = function (e) {
+ var point = pointer.getPointFromEvent(e);
+ // undefined on graph in scatterchart
+ if (point !== undefined) {
+ pointer.isDirectTouch = true;
+ point.onMouseOver(e);
+ }
+ };
+
+ // Add reference to the point
+ each(series.points, function (point) {
+ if (point.graphic) {
+ point.graphic.element.point = point;
+ }
+ if (point.dataLabel) {
+ if (point.dataLabel.div) {
+ point.dataLabel.div.point = point;
+ } else {
+ point.dataLabel.element.point = point;
+ }
+ }
+ });
+
+ // Add the event listeners, we need to do this only once
+ if (!series._hasTracking) {
+ each(series.trackerGroups, function (key) {
+ if (series[key]) { // we don't always have dataLabelsGroup
+ series[key]
+ .addClass('highcharts-tracker')
+ .on('mouseover', onMouseOver)
+ .on('mouseout', function (e) {
+ pointer.onTrackerMouseOut(e);
+ });
+ if (hasTouch) {
+ series[key].on('touchstart', onMouseOver);
+ }
+
+
+ if (series.options.cursor) {
+ series[key]
+ .css(css)
+ .css({ cursor: series.options.cursor });
+ }
+
+ }
+ });
+ series._hasTracking = true;
+ }
+ },
+
+ /**
+ * Draw the tracker object that sits above all data labels and markers to
+ * track mouse events on the graph or points. For the line type charts
+ * the tracker uses the same graphPath, but with a greater stroke width
+ * for better control.
+ */
+ drawTrackerGraph: function () {
+ var series = this,
+ options = series.options,
+ trackByArea = options.trackByArea,
+ trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
+ trackerPathLength = trackerPath.length,
+ chart = series.chart,
+ pointer = chart.pointer,
+ renderer = chart.renderer,
+ snap = chart.options.tooltip.snap,
+ tracker = series.tracker,
+ i,
+ onMouseOver = function () {
+ if (chart.hoverSeries !== series) {
+ series.onMouseOver();
+ }
+ },
+ /*
+ * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable
+ * IE6: 0.002
+ * IE7: 0.002
+ * IE8: 0.002
+ * IE9: 0.00000000001 (unlimited)
+ * IE10: 0.0001 (exporting only)
+ * FF: 0.00000000001 (unlimited)
+ * Chrome: 0.000001
+ * Safari: 0.000001
+ * Opera: 0.00000000001 (unlimited)
+ */
+ TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')';
+
+ // Extend end points. A better way would be to use round linecaps,
+ // but those are not clickable in VML.
+ if (trackerPathLength && !trackByArea) {
+ i = trackerPathLength + 1;
+ while (i--) {
+ if (trackerPath[i] === 'M') { // extend left side
+ trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], 'L');
+ }
+ if ((i && trackerPath[i] === 'M') || i === trackerPathLength) { // extend right side
+ trackerPath.splice(i, 0, 'L', trackerPath[i - 2] + snap, trackerPath[i - 1]);
+ }
+ }
+ }
+
+ // draw the tracker
+ if (tracker) {
+ tracker.attr({ d: trackerPath });
+ } else if (series.graph) { // create
+
+ series.tracker = renderer.path(trackerPath)
+ .attr({
+ 'stroke-linejoin': 'round', // #1225
+ visibility: series.visible ? 'visible' : 'hidden',
+ stroke: TRACKER_FILL,
+ fill: trackByArea ? TRACKER_FILL : 'none',
+ 'stroke-width': series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap),
+ zIndex: 2
+ })
+ .add(series.group);
+
+ // The tracker is added to the series group, which is clipped, but is covered
+ // by the marker group. So the marker group also needs to capture events.
+ each([series.tracker, series.markerGroup], function (tracker) {
+ tracker.addClass('highcharts-tracker')
+ .on('mouseover', onMouseOver)
+ .on('mouseout', function (e) {
+ pointer.onTrackerMouseOut(e);
+ });
+
+
+ if (options.cursor) {
+ tracker.css({ cursor: options.cursor });
+ }
+
+
+ if (hasTouch) {
+ tracker.on('touchstart', onMouseOver);
+ }
+ });
+ }
+ }
+};
+/* End TrackerMixin */
+
+
+/**
+ * Add tracking event listener to the series group, so the point graphics
+ * themselves act as trackers
+ */
+
+if (seriesTypes.column) {
+ seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
+}
+
+if (seriesTypes.pie) {
+ seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
+}
+
+if (seriesTypes.scatter) {
+ seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
+}
+
+/*
+ * Extend Legend for item events
+ */
+extend(Legend.prototype, {
+
+ setItemEvents: function (item, legendItem, useHTML) {
+ var legend = this,
+ boxWrapper = legend.chart.renderer.boxWrapper,
+ activeClass = 'highcharts-legend-' +
+ (item instanceof Point ? 'point' : 'series') + '-active';
+
+ // Set the events on the item group, or in case of useHTML, the item itself (#1249)
+ (useHTML ? legendItem : item.legendGroup).on('mouseover', function () {
+ item.setState('hover');
+
+ // A CSS class to dim or hide other than the hovered series
+ boxWrapper.addClass(activeClass);
+
+
+ legendItem.css(legend.options.itemHoverStyle);
+
+ })
+ .on('mouseout', function () {
+
+ legendItem.css(merge(item.visible ? legend.itemStyle : legend.itemHiddenStyle));
+
+
+ // A CSS class to dim or hide other than the hovered series
+ boxWrapper.removeClass(activeClass);
+
+ item.setState();
+ })
+ .on('click', function (event) {
+ var strLegendItemClick = 'legendItemClick',
+ fnLegendItemClick = function () {
+ if (item.setVisible) {
+ item.setVisible();
+ }
+ };
+
+ // A CSS class to dim or hide other than the hovered series. Event
+ // handling in iOS causes the activeClass to be added prior to click
+ // in some cases (#7418).
+ boxWrapper.removeClass(activeClass);
+
+ // Pass over the click/touch event. #4.
+ event = {
+ browserEvent: event
+ };
+
+ // click the name or symbol
+ if (item.firePointEvent) { // point
+ item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
+ } else {
+ fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
+ }
+ });
+ },
+
+ createCheckboxForItem: function (item) {
+ var legend = this;
+
+ item.checkbox = createElement('input', {
+ type: 'checkbox',
+ checked: item.selected,
+ defaultChecked: item.selected // required by IE7
+ }, legend.options.itemCheckboxStyle, legend.chart.container);
+
+ addEvent(item.checkbox, 'click', function (event) {
+ var target = event.target;
+ fireEvent(
+ item.series || item,
+ 'checkboxClick',
+ { // #3712
+ checked: target.checked,
+ item: item
+ },
+ function () {
+ item.select();
+ }
+ );
+ });
+ }
+});
+
+
+
+// Add pointer cursor to legend itemstyle in defaultOptions
+defaultOptions.legend.itemStyle.cursor = 'pointer';
+
+
+
+/*
+ * Extend the Chart object with interaction
+ */
+
+extend(Chart.prototype, /** @lends Chart.prototype */ {
+ /**
+ * Display the zoom button.
+ *
+ * @private
+ */
+ showResetZoom: function () {
+ var chart = this,
+ lang = defaultOptions.lang,
+ btnOptions = chart.options.chart.resetZoomButton,
+ theme = btnOptions.theme,
+ states = theme.states,
+ alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';
+
+ function zoomOut() {
+ chart.zoomOut();
+ }
+
+ this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover)
+ .attr({
+ align: btnOptions.position.align,
+ title: lang.resetZoomTitle
+ })
+ .addClass('highcharts-reset-zoom')
+ .add()
+ .align(btnOptions.position, false, alignTo);
+
+ },
+
+ /**
+ * Zoom out to 1:1.
+ *
+ * @private
+ */
+ zoomOut: function () {
+ var chart = this;
+ fireEvent(chart, 'selection', { resetSelection: true }, function () {
+ chart.zoom();
+ });
+ },
+
+ /**
+ * Zoom into a given portion of the chart given by axis coordinates.
+ * @param {Object} event
+ *
+ * @private
+ */
+ zoom: function (event) {
+ var chart = this,
+ hasZoomed,
+ pointer = chart.pointer,
+ displayButton = false,
+ resetZoomButton;
+
+ // If zoom is called with no arguments, reset the axes
+ if (!event || event.resetSelection) {
+ each(chart.axes, function (axis) {
+ hasZoomed = axis.zoom();
+ });
+ pointer.initiated = false; // #6804
+
+ } else { // else, zoom in on all axes
+ each(event.xAxis.concat(event.yAxis), function (axisData) {
+ var axis = axisData.axis,
+ isXAxis = axis.isXAxis;
+
+ // don't zoom more than minRange
+ if (pointer[isXAxis ? 'zoomX' : 'zoomY']) {
+ hasZoomed = axis.zoom(axisData.min, axisData.max);
+ if (axis.displayBtn) {
+ displayButton = true;
+ }
+ }
+ });
+ }
+
+ // Show or hide the Reset zoom button
+ resetZoomButton = chart.resetZoomButton;
+ if (displayButton && !resetZoomButton) {
+ chart.showResetZoom();
+ } else if (!displayButton && isObject(resetZoomButton)) {
+ chart.resetZoomButton = resetZoomButton.destroy();
+ }
+
+
+ // Redraw
+ if (hasZoomed) {
+ chart.redraw(
+ pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
+ );
+ }
+ },
+
+ /**
+ * Pan the chart by dragging the mouse across the pane. This function is
+ * called on mouse move, and the distance to pan is computed from chartX
+ * compared to the first chartX position in the dragging operation.
+ *
+ * @private
+ */
+ pan: function (e, panning) {
+
+ var chart = this,
+ hoverPoints = chart.hoverPoints,
+ doRedraw;
+
+ // remove active points for shared tooltip
+ if (hoverPoints) {
+ each(hoverPoints, function (point) {
+ point.setState();
+ });
+ }
+
+ each(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps
+ var axis = chart[isX ? 'xAxis' : 'yAxis'][0],
+ horiz = axis.horiz,
+ mousePos = e[horiz ? 'chartX' : 'chartY'],
+ mouseDown = horiz ? 'mouseDownX' : 'mouseDownY',
+ startPos = chart[mouseDown],
+ halfPointRange = (axis.pointRange || 0) / 2,
+ extremes = axis.getExtremes(),
+ panMin = axis.toValue(startPos - mousePos, true) +
+ halfPointRange,
+ panMax = axis.toValue(startPos + axis.len - mousePos, true) -
+ halfPointRange,
+ flipped = panMax < panMin,
+ newMin = flipped ? panMax : panMin,
+ newMax = flipped ? panMin : panMax,
+ paddedMin = Math.min(
+ extremes.dataMin,
+ halfPointRange ?
+ extremes.min :
+ axis.toValue(
+ axis.toPixels(extremes.min) - axis.minPixelPadding
+ )
+ ),
+ paddedMax = Math.max(
+ extremes.dataMax,
+ halfPointRange ?
+ extremes.max :
+ axis.toValue(
+ axis.toPixels(extremes.max) + axis.minPixelPadding
+ )
+ ),
+ spill;
+
+ // If the new range spills over, either to the min or max, adjust
+ // the new range.
+ spill = paddedMin - newMin;
+ if (spill > 0) {
+ newMax += spill;
+ newMin = paddedMin;
+ }
+ spill = newMax - paddedMax;
+ if (spill > 0) {
+ newMax = paddedMax;
+ newMin -= spill;
+ }
+
+ // Set new extremes if they are actually new
+ if (axis.series.length && newMin !== extremes.min && newMax !== extremes.max) {
+ axis.setExtremes(
+ newMin,
+ newMax,
+ false,
+ false,
+ { trigger: 'pan' }
+ );
+ doRedraw = true;
+ }
+
+ chart[mouseDown] = mousePos; // set new reference for next run
+ });
+
+ if (doRedraw) {
+ chart.redraw(false);
+ }
+ css(chart.container, { cursor: 'move' });
+ }
+});
+
+/*
+ * Extend the Point object with interaction
+ */
+extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
+ /**
+ * Toggle the selection status of a point.
+ * @param {Boolean} [selected]
+ * When `true`, the point is selected. When `false`, the point is
+ * unselected. When `null` or `undefined`, the selection state is
+ * toggled.
+ * @param {Boolean} [accumulate=false]
+ * When `true`, the selection is added to other selected points.
+ * When `false`, other selected points are deselected. Internally in
+ * Highcharts, when {@link http://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect|allowPointSelect}
+ * is `true`, selected points are accumulated on Control, Shift or
+ * Cmd clicking the point.
+ *
+ * @see Highcharts.Chart#getSelectedPoints
+ *
+ * @sample highcharts/members/point-select/
+ * Select a point from a button
+ * @sample highcharts/chart/events-selection-points/
+ * Select a range of points through a drag selection
+ * @sample maps/series/data-id/
+ * Select a point in Highmaps
+ */
+ select: function (selected, accumulate) {
+ var point = this,
+ series = point.series,
+ chart = series.chart;
+
+ selected = pick(selected, !point.selected);
+
+ // fire the event with the default handler
+ point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
+
+ /**
+ * Whether the point is selected or not.
+ * @see Point#select
+ * @see Chart#getSelectedPoints
+ * @memberof Point
+ * @name selected
+ * @type {Boolean}
+ */
+ point.selected = point.options.selected = selected;
+ series.options.data[inArray(point, series.data)] = point.options;
+
+ point.setState(selected && 'select');
+
+ // unselect all other points unless Ctrl or Cmd + click
+ if (!accumulate) {
+ each(chart.getSelectedPoints(), function (loopPoint) {
+ if (loopPoint.selected && loopPoint !== point) {
+ loopPoint.selected = loopPoint.options.selected = false;
+ series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
+ loopPoint.setState('');
+ loopPoint.firePointEvent('unselect');
+ }
+ });
+ }
+ });
+ },
+
+ /**
+ * Runs on mouse over the point. Called internally from mouse and touch
+ * events.
+ *
+ * @param {Object} e The event arguments
+ */
+ onMouseOver: function (e) {
+ var point = this,
+ series = point.series,
+ chart = series.chart,
+ pointer = chart.pointer;
+ e = e ?
+ pointer.normalize(e) :
+ // In cases where onMouseOver is called directly without an event
+ pointer.getChartCoordinatesFromPoint(point, chart.inverted);
+ pointer.runPointActions(e, point);
+ },
+
+ /**
+ * Runs on mouse out from the point. Called internally from mouse and touch
+ * events.
+ */
+ onMouseOut: function () {
+ var point = this,
+ chart = point.series.chart;
+ point.firePointEvent('mouseOut');
+ each(chart.hoverPoints || [], function (p) {
+ p.setState();
+ });
+ chart.hoverPoints = chart.hoverPoint = null;
+ },
+
+ /**
+ * Import events from the series' and point's options. Only do it on
+ * demand, to save processing time on hovering.
+ *
+ * @private
+ */
+ importEvents: function () {
+ if (!this.hasImportedEvents) {
+ var point = this,
+ options = merge(point.series.options.point, point.options),
+ events = options.events;
+
+ point.events = events;
+
+ H.objectEach(events, function (event, eventType) {
+ addEvent(point, eventType, event);
+ });
+ this.hasImportedEvents = true;
+
+ }
+ },
+
+ /**
+ * Set the point's state.
+ * @param {String} [state]
+ * The new state, can be one of `''` (an empty string), `hover` or
+ * `select`.
+ */
+ setState: function (state, move) {
+ var point = this,
+ plotX = Math.floor(point.plotX), // #4586
+ plotY = point.plotY,
+ series = point.series,
+ stateOptions = series.options.states[state] || {},
+ markerOptions = defaultPlotOptions[series.type].marker &&
+ series.options.marker,
+ normalDisabled = markerOptions && markerOptions.enabled === false,
+ markerStateOptions = (markerOptions && markerOptions.states &&
+ markerOptions.states[state]) || {},
+ stateDisabled = markerStateOptions.enabled === false,
+ stateMarkerGraphic = series.stateMarkerGraphic,
+ pointMarker = point.marker || {},
+ chart = series.chart,
+ halo = series.halo,
+ haloOptions,
+ markerAttribs,
+ hasMarkers = markerOptions && series.markerAttribs,
+ newSymbol;
+
+ state = state || ''; // empty string
+
+ if (
+ // already has this state
+ (state === point.state && !move) ||
+
+ // selected points don't respond to hover
+ (point.selected && state !== 'select') ||
+
+ // series' state options is disabled
+ (stateOptions.enabled === false) ||
+
+ // general point marker's state options is disabled
+ (state && (
+ stateDisabled ||
+ (normalDisabled && markerStateOptions.enabled === false)
+ )) ||
+
+ // individual point marker's state options is disabled
+ (
+ state &&
+ pointMarker.states &&
+ pointMarker.states[state] &&
+ pointMarker.states[state].enabled === false
+ ) // #1610
+
+ ) {
+ return;
+ }
+
+ if (hasMarkers) {
+ markerAttribs = series.markerAttribs(point, state);
+ }
+
+ // Apply hover styles to the existing point
+ if (point.graphic) {
+
+ if (point.state) {
+ point.graphic.removeClass('highcharts-point-' + point.state);
+ }
+ if (state) {
+ point.graphic.addClass('highcharts-point-' + state);
+ }
+
+
+ point.graphic.animate(
+ series.pointAttribs(point, state),
+ pick(
+ chart.options.chart.animation,
+ stateOptions.animation
+ )
+ );
+
+
+ if (markerAttribs) {
+ point.graphic.animate(
+ markerAttribs,
+ pick(
+ chart.options.chart.animation, // Turn off globally
+ markerStateOptions.animation,
+ markerOptions.animation
+ )
+ );
+ }
+
+ // Zooming in from a range with no markers to a range with markers
+ if (stateMarkerGraphic) {
+ stateMarkerGraphic.hide();
+ }
+ } else {
+ // if a graphic is not applied to each point in the normal state, create a shared
+ // graphic for the hover state
+ if (state && markerStateOptions) {
+ newSymbol = pointMarker.symbol || series.symbol;
+
+ // If the point has another symbol than the previous one, throw away the
+ // state marker graphic and force a new one (#1459)
+ if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {
+ stateMarkerGraphic = stateMarkerGraphic.destroy();
+ }
+
+ // Add a new state marker graphic
+ if (!stateMarkerGraphic) {
+ if (newSymbol) {
+ series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
+ newSymbol,
+ markerAttribs.x,
+ markerAttribs.y,
+ markerAttribs.width,
+ markerAttribs.height
+ )
+ .add(series.markerGroup);
+ stateMarkerGraphic.currentSymbol = newSymbol;
+ }
+
+ // Move the existing graphic
+ } else {
+ stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054
+ x: markerAttribs.x,
+ y: markerAttribs.y
+ });
+ }
+
+ if (stateMarkerGraphic) {
+ stateMarkerGraphic.attr(series.pointAttribs(point, state));
+ }
+
+ }
+
+ if (stateMarkerGraphic) {
+ stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450
+ stateMarkerGraphic.element.point = point; // #4310
+ }
+ }
+
+ // Show me your halo
+ haloOptions = stateOptions.halo;
+ if (haloOptions && haloOptions.size) {
+ if (!halo) {
+ series.halo = halo = chart.renderer.path()
+ // #5818, #5903, #6705
+ .add((point.graphic || stateMarkerGraphic).parentGroup);
+ }
+ halo[move ? 'animate' : 'attr']({
+ d: point.haloPath(haloOptions.size)
+ });
+ halo.attr({
+ 'class': 'highcharts-halo highcharts-color-' +
+ pick(point.colorIndex, series.colorIndex)
+ });
+ halo.point = point; // #6055
+
+
+ halo.attr(extend({
+ 'fill': point.color || series.color,
+ 'fill-opacity': haloOptions.opacity,
+ 'zIndex': -1 // #4929, IE8 added halo above everything
+ }, haloOptions.attributes));
+
+
+ } else if (halo && halo.point && halo.point.haloPath) {
+ // Animate back to 0 on the current halo point (#6055)
+ halo.animate({ d: halo.point.haloPath(0) });
+ }
+
+ point.state = state;
+ },
+
+ /**
+ * Get the path definition for the halo, which is usually a shadow-like
+ * circle around the currently hovered point.
+ * @param {Number} size
+ * The radius of the circular halo.
+ * @return {Array} The path definition
+ */
+ haloPath: function (size) {
+ var series = this.series,
+ chart = series.chart;
+
+ return chart.renderer.symbols.circle(
+ Math.floor(this.plotX) - size,
+ this.plotY - size,
+ size * 2,
+ size * 2
+ );
+ }
+});
+
+/*
+ * Extend the Series object with interaction
+ */
+
+extend(Series.prototype, /** @lends Highcharts.Series.prototype */ {
+ /**
+ * Runs on mouse over the series graphical items.
+ */
+ onMouseOver: function () {
+ var series = this,
+ chart = series.chart,
+ hoverSeries = chart.hoverSeries;
+
+ // set normal state to previous series
+ if (hoverSeries && hoverSeries !== series) {
+ hoverSeries.onMouseOut();
+ }
+
+ // trigger the event, but to save processing time,
+ // only if defined
+ if (series.options.events.mouseOver) {
+ fireEvent(series, 'mouseOver');
+ }
+
+ // hover this
+ series.setState('hover');
+ chart.hoverSeries = series;
+ },
+
+ /**
+ * Runs on mouse out of the series graphical items.
+ */
+ onMouseOut: function () {
+ // trigger the event only if listeners exist
+ var series = this,
+ options = series.options,
+ chart = series.chart,
+ tooltip = chart.tooltip,
+ hoverPoint = chart.hoverPoint;
+
+ chart.hoverSeries = null; // #182, set to null before the mouseOut event fires
+
+ // trigger mouse out on the point, which must be in this series
+ if (hoverPoint) {
+ hoverPoint.onMouseOut();
+ }
+
+ // fire the mouse out event
+ if (series && options.events.mouseOut) {
+ fireEvent(series, 'mouseOut');
+ }
+
+
+ // hide the tooltip
+ if (tooltip && !series.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
+ tooltip.hide();
+ }
+
+ // set normal state
+ series.setState();
+ },
+
+ /**
+ * Set the state of the series. Called internally on mouse interaction
+ * operations, but it can also be called directly to visually
+ * highlight a series.
+ *
+ * @param {String} [state]
+ * Can be either `hover` or undefined to set to normal
+ * state.
+ */
+ setState: function (state) {
+ var series = this,
+ options = series.options,
+ graph = series.graph,
+ stateOptions = options.states,
+ lineWidth = options.lineWidth,
+ attribs,
+ i = 0;
+
+ state = state || '';
+
+ if (series.state !== state) {
+
+ // Toggle class names
+ each([
+ series.group,
+ series.markerGroup,
+ series.dataLabelsGroup
+ ], function (group) {
+ if (group) {
+ // Old state
+ if (series.state) {
+ group.removeClass('highcharts-series-' + series.state);
+ }
+ // New state
+ if (state) {
+ group.addClass('highcharts-series-' + state);
+ }
+ }
+ });
+
+ series.state = state;
+
+
+
+ if (stateOptions[state] && stateOptions[state].enabled === false) {
+ return;
+ }
+
+ if (state) {
+ lineWidth = stateOptions[state].lineWidth || lineWidth + (stateOptions[state].lineWidthPlus || 0); // #4035
+ }
+
+ if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
+ attribs = {
+ 'stroke-width': lineWidth
+ };
+
+ // Animate the graph stroke-width. By default a quick animation
+ // to hover, slower to un-hover.
+ graph.animate(
+ attribs,
+ pick(
+ series.chart.options.chart.animation,
+ stateOptions[state] && stateOptions[state].animation
+ )
+ );
+ while (series['zone-graph-' + i]) {
+ series['zone-graph-' + i].attr(attribs);
+ i = i + 1;
+ }
+ }
+
+ }
+ },
+
+ /**
+ * Show or hide the series.
+ *
+ * @param {Boolean} [visible]
+ * True to show the series, false to hide. If undefined, the
+ * visibility is toggled.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart after the series is altered. If doing
+ * more operations on the chart, it is a good idea to set redraw to
+ * false and call {@link Chart#redraw|chart.redraw()} after.
+ */
+ setVisible: function (vis, redraw) {
+ var series = this,
+ chart = series.chart,
+ legendItem = series.legendItem,
+ showOrHide,
+ ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
+ oldVisibility = series.visible;
+
+ // if called without an argument, toggle visibility
+ series.visible = vis = series.options.visible = series.userOptions.visible = vis === undefined ? !oldVisibility : vis; // #5618
+ showOrHide = vis ? 'show' : 'hide';
+
+ // show or hide elements
+ each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker', 'tt'], function (key) {
+ if (series[key]) {
+ series[key][showOrHide]();
+ }
+ });
+
+
+ // hide tooltip (#1361)
+ if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) {
+ series.onMouseOut();
+ }
+
+
+ if (legendItem) {
+ chart.legend.colorizeItem(series, vis);
+ }
+
+
+ // rescale or adapt to resized chart
+ series.isDirty = true;
+ // in a stack, all other series are affected
+ if (series.options.stacking) {
+ each(chart.series, function (otherSeries) {
+ if (otherSeries.options.stacking && otherSeries.visible) {
+ otherSeries.isDirty = true;
+ }
+ });
+ }
+
+ // show or hide linked series
+ each(series.linkedSeries, function (otherSeries) {
+ otherSeries.setVisible(vis, false);
+ });
+
+ if (ignoreHiddenSeries) {
+ chart.isDirtyBox = true;
+ }
+ if (redraw !== false) {
+ chart.redraw();
+ }
+
+ fireEvent(series, showOrHide);
+ },
+
+ /**
+ * Show the series if hidden.
+ *
+ * @sample highcharts/members/series-hide/
+ * Toggle visibility from a button
+ */
+ show: function () {
+ this.setVisible(true);
+ },
+
+ /**
+ * Hide the series if visible. If the {@link
+ * https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries|
+ * chart.ignoreHiddenSeries} option is true, the chart is redrawn without
+ * this series.
+ *
+ * @sample highcharts/members/series-hide/
+ * Toggle visibility from a button
+ */
+ hide: function () {
+ this.setVisible(false);
+ },
+
+
+ /**
+ * Select or unselect the series. This means its {@link
+ * Highcharts.Series.selected|selected} property is set, the checkbox in the
+ * legend is toggled and when selected, the series is returned by the
+ * {@link Highcharts.Chart#getSelectedSeries} function.
+ *
+ * @param {Boolean} [selected]
+ * True to select the series, false to unselect. If undefined, the
+ * selection state is toggled.
+ *
+ * @sample highcharts/members/series-select/
+ * Select a series from a button
+ */
+ select: function (selected) {
+ var series = this;
+
+ series.selected = selected = (selected === undefined) ?
+ !series.selected :
+ selected;
+
+ if (series.checkbox) {
+ series.checkbox.checked = selected;
+ }
+
+ fireEvent(series, selected ? 'select' : 'unselect');
+ },
+
+ drawTracker: TrackerMixin.drawTrackerGraph
+});
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var Chart = H.Chart,
+ each = H.each,
+ inArray = H.inArray,
+ isArray = H.isArray,
+ isObject = H.isObject,
+ pick = H.pick,
+ splat = H.splat;
+
+
+/**
+ * Allows setting a set of rules to apply for different screen or chart
+ * sizes. Each rule specifies additional chart options.
+ *
+ * @sample {highstock} stock/demo/responsive/ Stock chart
+ * @sample highcharts/responsive/axis/ Axis
+ * @sample highcharts/responsive/legend/ Legend
+ * @sample highcharts/responsive/classname/ Class name
+ * @since 5.0.0
+ * @apioption responsive
+ */
+
+/**
+ * A set of rules for responsive settings. The rules are executed from
+ * the top down.
+ *
+ * @type {Array}
+ * @sample {highcharts} highcharts/responsive/axis/ Axis changes
+ * @sample {highstock} highcharts/responsive/axis/ Axis changes
+ * @sample {highmaps} highcharts/responsive/axis/ Axis changes
+ * @since 5.0.0
+ * @apioption responsive.rules
+ */
+
+/**
+ * A full set of chart options to apply as overrides to the general
+ * chart options. The chart options are applied when the given rule
+ * is active.
+ *
+ * A special case is configuration objects that take arrays, for example
+ * [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these
+ * collections, an `id` option is used to map the new option set to
+ * an existing object. If an existing object of the same id is not found,
+ * the item of the same indexupdated. So for example, setting `chartOptions`
+ * with two series items without an `id`, will cause the existing chart's
+ * two series to be updated with respective options.
+ *
+ * @type {Object}
+ * @sample {highstock} stock/demo/responsive/ Stock chart
+ * @sample highcharts/responsive/axis/ Axis
+ * @sample highcharts/responsive/legend/ Legend
+ * @sample highcharts/responsive/classname/ Class name
+ * @since 5.0.0
+ * @apioption responsive.rules.chartOptions
+ */
+
+/**
+ * Under which conditions the rule applies.
+ *
+ * @type {Object}
+ * @since 5.0.0
+ * @apioption responsive.rules.condition
+ */
+
+/**
+ * A callback function to gain complete control on when the responsive
+ * rule applies. Return `true` if it applies. This opens for checking
+ * against other metrics than the chart size, or example the document
+ * size or other elements.
+ *
+ * @type {Function}
+ * @context Chart
+ * @since 5.0.0
+ * @apioption responsive.rules.condition.callback
+ */
+
+/**
+ * The responsive rule applies if the chart height is less than this.
+ *
+ * @type {Number}
+ * @since 5.0.0
+ * @apioption responsive.rules.condition.maxHeight
+ */
+
+/**
+ * The responsive rule applies if the chart width is less than this.
+ *
+ * @type {Number}
+ * @sample highcharts/responsive/axis/ Max width is 500
+ * @since 5.0.0
+ * @apioption responsive.rules.condition.maxWidth
+ */
+
+/**
+ * The responsive rule applies if the chart height is greater than this.
+ *
+ * @type {Number}
+ * @default 0
+ * @since 5.0.0
+ * @apioption responsive.rules.condition.minHeight
+ */
+
+/**
+ * The responsive rule applies if the chart width is greater than this.
+ *
+ * @type {Number}
+ * @default 0
+ * @since 5.0.0
+ * @apioption responsive.rules.condition.minWidth
+ */
+
+/**
+ * Update the chart based on the current chart/document size and options for
+ * responsiveness.
+ */
+Chart.prototype.setResponsive = function (redraw) {
+ var options = this.options.responsive,
+ ruleIds = [],
+ currentResponsive = this.currentResponsive,
+ currentRuleIds;
+
+ if (options && options.rules) {
+ each(options.rules, function (rule) {
+ if (rule._id === undefined) {
+ rule._id = H.uniqueKey();
+ }
+
+ this.matchResponsiveRule(rule, ruleIds, redraw);
+ }, this);
+ }
+
+ // Merge matching rules
+ var mergedOptions = H.merge.apply(0, H.map(ruleIds, function (ruleId) {
+ return H.find(options.rules, function (rule) {
+ return rule._id === ruleId;
+ }).chartOptions;
+ }));
+
+ // Stringified key for the rules that currently apply.
+ ruleIds = ruleIds.toString() || undefined;
+ currentRuleIds = currentResponsive && currentResponsive.ruleIds;
+
+
+ // Changes in what rules apply
+ if (ruleIds !== currentRuleIds) {
+
+ // Undo previous rules. Before we apply a new set of rules, we need to
+ // roll back completely to base options (#6291).
+ if (currentResponsive) {
+ this.update(currentResponsive.undoOptions, redraw);
+ }
+
+ if (ruleIds) {
+ // Get undo-options for matching rules
+ this.currentResponsive = {
+ ruleIds: ruleIds,
+ mergedOptions: mergedOptions,
+ undoOptions: this.currentOptions(mergedOptions)
+ };
+
+ this.update(mergedOptions, redraw);
+
+ } else {
+ this.currentResponsive = undefined;
+ }
+ }
+};
+
+/**
+ * Handle a single responsiveness rule
+ */
+Chart.prototype.matchResponsiveRule = function (rule, matches) {
+ var condition = rule.condition,
+ fn = condition.callback || function () {
+ return this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
+ this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) &&
+ this.chartWidth >= pick(condition.minWidth, 0) &&
+ this.chartHeight >= pick(condition.minHeight, 0);
+ };
+
+ if (fn.call(this)) {
+ matches.push(rule._id);
+ }
+
+};
+
+/**
+ * Get the current values for a given set of options. Used before we update
+ * the chart with a new responsiveness rule.
+ * TODO: Restore axis options (by id?)
+ */
+Chart.prototype.currentOptions = function (options) {
+
+ var ret = {};
+
+ /**
+ * Recurse over a set of options and its current values,
+ * and store the current values in the ret object.
+ */
+ function getCurrent(options, curr, ret, depth) {
+ var i;
+ H.objectEach(options, function (val, key) {
+ if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
+ val = splat(val);
+
+ ret[key] = [];
+
+ // Iterate over collections like series, xAxis or yAxis and map
+ // the items by index.
+ for (i = 0; i < val.length; i++) {
+ if (curr[key][i]) { // Item exists in current data (#6347)
+ ret[key][i] = {};
+ getCurrent(
+ val[i],
+ curr[key][i],
+ ret[key][i],
+ depth + 1
+ );
+ }
+ }
+ } else if (isObject(val)) {
+ ret[key] = isArray(val) ? [] : {};
+ getCurrent(val, curr[key] || {}, ret[key], depth + 1);
+ } else {
+ ret[key] = curr[key] || null;
+ }
+ });
+ }
+
+ getCurrent(options, this.options, ret, 0);
+ return ret;
+};
+
+}(Highcharts));
+var Highcharts = (function (Highcharts) {
+
+return Highcharts;
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ Axis = H.Axis,
+ Chart = H.Chart,
+ css = H.css,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ noop = H.noop,
+ pick = H.pick,
+ Series = H.Series,
+ timeUnits = H.timeUnits,
+ wrap = H.wrap;
+
+/* ****************************************************************************
+ * Start ordinal axis logic *
+ *****************************************************************************/
+
+
+wrap(Series.prototype, 'init', function (proceed) {
+ var series = this,
+ xAxis;
+
+ // call the original function
+ proceed.apply(this, Array.prototype.slice.call(arguments, 1));
+
+ xAxis = series.xAxis;
+
+ // Destroy the extended ordinal index on updated data
+ if (xAxis && xAxis.options.ordinal) {
+ addEvent(series, 'updatedData', function () {
+ delete xAxis.ordinalIndex;
+ });
+ }
+});
+
+/**
+ * In an ordinal axis, there might be areas with dense consentrations of points, then large
+ * gaps between some. Creating equally distributed ticks over this entire range
+ * may lead to a huge number of ticks that will later be removed. So instead, break the
+ * positions up in segments, find the tick positions for each segment then concatenize them.
+ * This method is used from both data grouping logic and X axis tick position logic.
+ */
+wrap(Axis.prototype, 'getTimeTicks', function (proceed, normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) {
+
+ var start = 0,
+ end,
+ segmentPositions,
+ higherRanks = {},
+ hasCrossedHigherRank,
+ info,
+ posLength,
+ outsideMax,
+ groupPositions = [],
+ lastGroupPosition = -Number.MAX_VALUE,
+ tickPixelIntervalOption = this.options.tickPixelInterval,
+ time = this.chart.time;
+
+ // The positions are not always defined, for example for ordinal positions when data
+ // has regular interval (#1557, #2090)
+ if ((!this.options.ordinal && !this.options.breaks) || !positions || positions.length < 3 || min === undefined) {
+ return proceed.call(this, normalizedInterval, min, max, startOfWeek);
+ }
+
+ // Analyze the positions array to split it into segments on gaps larger than 5 times
+ // the closest distance. The closest distance is already found at this point, so
+ // we reuse that instead of computing it again.
+ posLength = positions.length;
+
+ for (end = 0; end < posLength; end++) {
+
+ outsideMax = end && positions[end - 1] > max;
+
+ if (positions[end] < min) { // Set the last position before min
+ start = end;
+ }
+
+ if (end === posLength - 1 || positions[end + 1] - positions[end] > closestDistance * 5 || outsideMax) {
+
+ // For each segment, calculate the tick positions from the getTimeTicks utility
+ // function. The interval will be the same regardless of how long the segment is.
+ if (positions[end] > lastGroupPosition) { // #1475
+
+ segmentPositions = proceed.call(this, normalizedInterval, positions[start], positions[end], startOfWeek);
+
+ // Prevent duplicate groups, for example for multiple segments within one larger time frame (#1475)
+ while (segmentPositions.length && segmentPositions[0] <= lastGroupPosition) {
+ segmentPositions.shift();
+ }
+ if (segmentPositions.length) {
+ lastGroupPosition = segmentPositions[segmentPositions.length - 1];
+ }
+
+ groupPositions = groupPositions.concat(segmentPositions);
+ }
+ // Set start of next segment
+ start = end + 1;
+ }
+
+ if (outsideMax) {
+ break;
+ }
+ }
+
+ // Get the grouping info from the last of the segments. The info is the same for
+ // all segments.
+ info = segmentPositions.info;
+
+ // Optionally identify ticks with higher rank, for example when the ticks
+ // have crossed midnight.
+ if (findHigherRanks && info.unitRange <= timeUnits.hour) {
+ end = groupPositions.length - 1;
+
+ // Compare points two by two
+ for (start = 1; start < end; start++) {
+ if (
+ time.dateFormat('%d', groupPositions[start]) !==
+ time.dateFormat('%d', groupPositions[start - 1])
+ ) {
+ higherRanks[groupPositions[start]] = 'day';
+ hasCrossedHigherRank = true;
+ }
+ }
+
+ // If the complete array has crossed midnight, we want to mark the first
+ // positions also as higher rank
+ if (hasCrossedHigherRank) {
+ higherRanks[groupPositions[0]] = 'day';
+ }
+ info.higherRanks = higherRanks;
+ }
+
+ // Save the info
+ groupPositions.info = info;
+
+
+
+ // Don't show ticks within a gap in the ordinal axis, where the space between
+ // two points is greater than a portion of the tick pixel interval
+ if (findHigherRanks && defined(tickPixelIntervalOption)) { // check for squashed ticks
+
+ var length = groupPositions.length,
+ i = length,
+ itemToRemove,
+ translated,
+ translatedArr = [],
+ lastTranslated,
+ medianDistance,
+ distance,
+ distances = [];
+
+ // Find median pixel distance in order to keep a reasonably even distance between
+ // ticks (#748)
+ while (i--) {
+ translated = this.translate(groupPositions[i]);
+ if (lastTranslated) {
+ distances[i] = lastTranslated - translated;
+ }
+ translatedArr[i] = lastTranslated = translated;
+ }
+ distances.sort();
+ medianDistance = distances[Math.floor(distances.length / 2)];
+ if (medianDistance < tickPixelIntervalOption * 0.6) {
+ medianDistance = null;
+ }
+
+ // Now loop over again and remove ticks where needed
+ i = groupPositions[length - 1] > max ? length - 1 : length; // #817
+ lastTranslated = undefined;
+ while (i--) {
+ translated = translatedArr[i];
+ distance = Math.abs(lastTranslated - translated);
+ // #4175 - when axis is reversed, the distance, is negative but
+ // tickPixelIntervalOption positive, so we need to compare the same values
+
+ // Remove ticks that are closer than 0.6 times the pixel interval from the one to the right,
+ // but not if it is close to the median distance (#748).
+ if (lastTranslated && distance < tickPixelIntervalOption * 0.8 &&
+ (medianDistance === null || distance < medianDistance * 0.8)) {
+
+ // Is this a higher ranked position with a normal position to the right?
+ if (higherRanks[groupPositions[i]] && !higherRanks[groupPositions[i + 1]]) {
+
+ // Yes: remove the lower ranked neighbour to the right
+ itemToRemove = i + 1;
+ lastTranslated = translated; // #709
+
+ } else {
+
+ // No: remove this one
+ itemToRemove = i;
+ }
+
+ groupPositions.splice(itemToRemove, 1);
+
+ } else {
+ lastTranslated = translated;
+ }
+ }
+ }
+ return groupPositions;
+});
+
+// Extend the Axis prototype
+extend(Axis.prototype, /** @lends Axis.prototype */ {
+
+ /**
+ * Calculate the ordinal positions before tick positions are calculated.
+ */
+ beforeSetTickPositions: function () {
+ var axis = this,
+ len,
+ ordinalPositions = [],
+ useOrdinal = false,
+ dist,
+ extremes = axis.getExtremes(),
+ min = extremes.min,
+ max = extremes.max,
+ minIndex,
+ maxIndex,
+ slope,
+ hasBreaks = axis.isXAxis && !!axis.options.breaks,
+ isOrdinal = axis.options.ordinal,
+ overscrollPointsRange = Number.MAX_VALUE,
+ ignoreHiddenSeries = axis.chart.options.chart.ignoreHiddenSeries,
+ isNavigatorAxis = axis.options.className === 'highcharts-navigator-xaxis',
+ i;
+
+ if (
+ axis.options.overscroll &&
+ axis.max === axis.dataMax &&
+ (
+ // Panning is an execption,
+ // We don't want to apply overscroll when panning over the dataMax
+ !axis.chart.mouseIsDown ||
+ isNavigatorAxis
+ ) && (
+ // Scrollbar buttons are the other execption:
+ !axis.eventArgs ||
+ axis.eventArgs && axis.eventArgs.trigger !== 'navigator'
+ )
+ ) {
+ axis.max += axis.options.overscroll;
+
+ // Live data and buttons require translation for the min:
+ if (!isNavigatorAxis && defined(axis.userMin)) {
+ axis.min += axis.options.overscroll;
+ }
+ }
+
+ // Apply the ordinal logic
+ if (isOrdinal || hasBreaks) { // #4167 YAxis is never ordinal ?
+
+ each(axis.series, function (series, i) {
+
+ if (
+ (!ignoreHiddenSeries || series.visible !== false) &&
+ (series.takeOrdinalPosition !== false || hasBreaks)
+ ) {
+
+ // concatenate the processed X data into the existing positions, or the empty array
+ ordinalPositions = ordinalPositions.concat(series.processedXData);
+ len = ordinalPositions.length;
+
+ // remove duplicates (#1588)
+ ordinalPositions.sort(function (a, b) {
+ return a - b; // without a custom function it is sorted as strings
+ });
+
+ overscrollPointsRange = Math.min(
+ overscrollPointsRange,
+ pick(
+ // Check for a single-point series:
+ series.closestPointRange,
+ overscrollPointsRange
+ )
+ );
+
+ if (len) {
+ i = len - 1;
+ while (i--) {
+ if (ordinalPositions[i] === ordinalPositions[i + 1]) {
+ ordinalPositions.splice(i, 1);
+ }
+ }
+ }
+ }
+
+ });
+
+ // cache the length
+ len = ordinalPositions.length;
+
+ // Check if we really need the overhead of mapping axis data against the ordinal positions.
+ // If the series consist of evenly spaced data any way, we don't need any ordinal logic.
+ if (len > 2) { // two points have equal distance by default
+ dist = ordinalPositions[1] - ordinalPositions[0];
+ i = len - 1;
+ while (i-- && !useOrdinal) {
+ if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) {
+ useOrdinal = true;
+ }
+ }
+
+ // When zooming in on a week, prevent axis padding for weekends even though the data within
+ // the week is evenly spaced.
+ if (
+ !axis.options.keepOrdinalPadding &&
+ (
+ ordinalPositions[0] - min > dist ||
+ max - ordinalPositions[ordinalPositions.length - 1] > dist
+ )
+ ) {
+ useOrdinal = true;
+ }
+ } else if (axis.options.overscroll) {
+ if (len === 2) {
+ // Exactly two points, distance for overscroll is fixed:
+ overscrollPointsRange = ordinalPositions[1] - ordinalPositions[0];
+ } else if (len === 1) {
+ // We have just one point, closest distance is unknown.
+ // Assume then it is last point and overscrolled range:
+ overscrollPointsRange = axis.options.overscroll;
+ ordinalPositions = [ordinalPositions[0], ordinalPositions[0] + overscrollPointsRange];
+ } else {
+ // In case of zooming in on overscrolled range, stick to the old range:
+ overscrollPointsRange = axis.overscrollPointsRange;
+ }
+ }
+
+ // Record the slope and offset to compute the linear values from the array index.
+ // Since the ordinal positions may exceed the current range, get the start and
+ // end positions within it (#719, #665b)
+ if (useOrdinal) {
+
+ if (axis.options.overscroll) {
+ axis.overscrollPointsRange = overscrollPointsRange;
+ ordinalPositions = ordinalPositions.concat(axis.getOverscrollPositions());
+ }
+
+ // Register
+ axis.ordinalPositions = ordinalPositions;
+
+ // This relies on the ordinalPositions being set. Use Math.max
+ // and Math.min to prevent padding on either sides of the data.
+ minIndex = axis.ordinal2lin( // #5979
+ Math.max(
+ min,
+ ordinalPositions[0]
+ ),
+ true
+ );
+ maxIndex = Math.max(axis.ordinal2lin(
+ Math.min(
+ max,
+ ordinalPositions[ordinalPositions.length - 1]
+ ),
+ true
+ ), 1); // #3339
+
+ // Set the slope and offset of the values compared to the indices in the ordinal positions
+ axis.ordinalSlope = slope = (max - min) / (maxIndex - minIndex);
+ axis.ordinalOffset = min - (minIndex * slope);
+
+ } else {
+ axis.overscrollPointsRange = pick(axis.closestPointRange, axis.overscrollPointsRange);
+ axis.ordinalPositions = axis.ordinalSlope = axis.ordinalOffset = undefined;
+ }
+ }
+
+ axis.isOrdinal = isOrdinal && useOrdinal; // #3818, #4196, #4926
+ axis.groupIntervalFactor = null; // reset for next run
+ },
+ /**
+ * Translate from a linear axis value to the corresponding ordinal axis position. If there
+ * are no gaps in the ordinal axis this will be the same. The translated value is the value
+ * that the point would have if the axis were linear, using the same min and max.
+ *
+ * @param Number val The axis value
+ * @param Boolean toIndex Whether to return the index in the ordinalPositions or the new value
+ */
+ val2lin: function (val, toIndex) {
+ var axis = this,
+ ordinalPositions = axis.ordinalPositions,
+ ret;
+
+ if (!ordinalPositions) {
+ ret = val;
+
+ } else {
+
+ var ordinalLength = ordinalPositions.length,
+ i,
+ distance,
+ ordinalIndex;
+
+ // first look for an exact match in the ordinalpositions array
+ i = ordinalLength;
+ while (i--) {
+ if (ordinalPositions[i] === val) {
+ ordinalIndex = i;
+ break;
+ }
+ }
+
+ // if that failed, find the intermediate position between the two nearest values
+ i = ordinalLength - 1;
+ while (i--) {
+ if (val > ordinalPositions[i] || i === 0) { // interpolate
+ distance = (val - ordinalPositions[i]) / (ordinalPositions[i + 1] - ordinalPositions[i]); // something between 0 and 1
+ ordinalIndex = i + distance;
+ break;
+ }
+ }
+ ret = toIndex ?
+ ordinalIndex :
+ axis.ordinalSlope * (ordinalIndex || 0) + axis.ordinalOffset;
+ }
+ return ret;
+ },
+ /**
+ * Translate from linear (internal) to axis value
+ *
+ * @param Number val The linear abstracted value
+ * @param Boolean fromIndex Translate from an index in the ordinal positions rather than a value
+ */
+ lin2val: function (val, fromIndex) {
+ var axis = this,
+ ordinalPositions = axis.ordinalPositions,
+ ret;
+
+ if (!ordinalPositions) { // the visible range contains only equally spaced values
+ ret = val;
+
+ } else {
+
+ var ordinalSlope = axis.ordinalSlope,
+ ordinalOffset = axis.ordinalOffset,
+ i = ordinalPositions.length - 1,
+ linearEquivalentLeft,
+ linearEquivalentRight,
+ distance;
+
+
+ // Handle the case where we translate from the index directly, used only
+ // when panning an ordinal axis
+ if (fromIndex) {
+
+ if (val < 0) { // out of range, in effect panning to the left
+ val = ordinalPositions[0];
+ } else if (val > i) { // out of range, panning to the right
+ val = ordinalPositions[i];
+ } else { // split it up
+ i = Math.floor(val);
+ distance = val - i; // the decimal
+ }
+
+ // Loop down along the ordinal positions. When the linear equivalent of i matches
+ // an ordinal position, interpolate between the left and right values.
+ } else {
+ while (i--) {
+ linearEquivalentLeft = (ordinalSlope * i) + ordinalOffset;
+ if (val >= linearEquivalentLeft) {
+ linearEquivalentRight = (ordinalSlope * (i + 1)) + ordinalOffset;
+ distance = (val - linearEquivalentLeft) / (linearEquivalentRight - linearEquivalentLeft); // something between 0 and 1
+ break;
+ }
+ }
+ }
+
+ // If the index is within the range of the ordinal positions, return the associated
+ // or interpolated value. If not, just return the value
+ return distance !== undefined && ordinalPositions[i] !== undefined ?
+ ordinalPositions[i] + (distance ? distance * (ordinalPositions[i + 1] - ordinalPositions[i]) : 0) :
+ val;
+ }
+ return ret;
+ },
+ /**
+ * Get the ordinal positions for the entire data set. This is necessary in chart panning
+ * because we need to find out what points or data groups are available outside the
+ * visible range. When a panning operation starts, if an index for the given grouping
+ * does not exists, it is created and cached. This index is deleted on updated data, so
+ * it will be regenerated the next time a panning operation starts.
+ */
+ getExtendedPositions: function () {
+ var axis = this,
+ chart = axis.chart,
+ grouping = axis.series[0].currentDataGrouping,
+ ordinalIndex = axis.ordinalIndex,
+ key = grouping ? grouping.count + grouping.unitName : 'raw',
+ overscroll = axis.options.overscroll,
+ extremes = axis.getExtremes(),
+ fakeAxis,
+ fakeSeries;
+
+ // If this is the first time, or the ordinal index is deleted by updatedData,
+ // create it.
+ if (!ordinalIndex) {
+ ordinalIndex = axis.ordinalIndex = {};
+ }
+
+
+ if (!ordinalIndex[key]) {
+
+ // Create a fake axis object where the extended ordinal positions are emulated
+ fakeAxis = {
+ series: [],
+ chart: chart,
+ getExtremes: function () {
+ return {
+ min: extremes.dataMin,
+ max: extremes.dataMax + overscroll
+ };
+ },
+ options: {
+ ordinal: true
+ },
+ val2lin: Axis.prototype.val2lin, // #2590
+ ordinal2lin: Axis.prototype.ordinal2lin // #6276
+ };
+
+ // Add the fake series to hold the full data, then apply processData to it
+ each(axis.series, function (series) {
+ fakeSeries = {
+ xAxis: fakeAxis,
+ xData: series.xData.slice(),
+ chart: chart,
+ destroyGroupedData: noop
+ };
+
+ fakeSeries.xData = fakeSeries.xData.concat(axis.getOverscrollPositions());
+
+ fakeSeries.options = {
+ dataGrouping: grouping ? {
+ enabled: true,
+ forced: true,
+ approximation: 'open', // doesn't matter which, use the fastest
+ units: [[grouping.unitName, [grouping.count]]]
+ } : {
+ enabled: false
+ }
+ };
+ series.processData.apply(fakeSeries);
+
+
+ fakeAxis.series.push(fakeSeries);
+ });
+
+ // Run beforeSetTickPositions to compute the ordinalPositions
+ axis.beforeSetTickPositions.apply(fakeAxis);
+
+ // Cache it
+ ordinalIndex[key] = fakeAxis.ordinalPositions;
+ }
+ return ordinalIndex[key];
+ },
+
+ /**
+ * Get ticks for an ordinal axis within a range where points don't exist.
+ * It is required when overscroll is enabled. We can't base on points,
+ * because we may not have any, so we use approximated pointRange and
+ * generate these ticks between
+ * evenly spaced. Used in panning and navigator scrolling.
+ *
+ * @returns positions {Array} Generated ticks
+ * @private
+ */
+ getOverscrollPositions: function () {
+ var axis = this,
+ extraRange = axis.options.overscroll,
+ distance = axis.overscrollPointsRange,
+ positions = [],
+ max = axis.dataMax;
+
+ if (H.defined(distance)) {
+ // Max + pointRange because we need to scroll to the last
+
+ positions.push(max);
+
+ while (max <= axis.dataMax + extraRange) {
+ max += distance;
+ positions.push(max);
+ }
+
+ }
+
+ return positions;
+ },
+
+ /**
+ * Find the factor to estimate how wide the plot area would have been if ordinal
+ * gaps were included. This value is used to compute an imagined plot width in order
+ * to establish the data grouping interval.
+ *
+ * A real world case is the intraday-candlestick
+ * example. Without this logic, it would show the correct data grouping when viewing
+ * a range within each day, but once moving the range to include the gap between two
+ * days, the interval would include the cut-away night hours and the data grouping
+ * would be wrong. So the below method tries to compensate by identifying the most
+ * common point interval, in this case days.
+ *
+ * An opposite case is presented in issue #718. We have a long array of daily data,
+ * then one point is appended one hour after the last point. We expect the data grouping
+ * not to change.
+ *
+ * In the future, if we find cases where this estimation doesn't work optimally, we
+ * might need to add a second pass to the data grouping logic, where we do another run
+ * with a greater interval if the number of data groups is more than a certain fraction
+ * of the desired group count.
+ */
+ getGroupIntervalFactor: function (xMin, xMax, series) {
+ var i,
+ processedXData = series.processedXData,
+ len = processedXData.length,
+ distances = [],
+ median,
+ groupIntervalFactor = this.groupIntervalFactor;
+
+ // Only do this computation for the first series, let the other inherit it (#2416)
+ if (!groupIntervalFactor) {
+
+ // Register all the distances in an array
+ for (i = 0; i < len - 1; i++) {
+ distances[i] = processedXData[i + 1] - processedXData[i];
+ }
+
+ // Sort them and find the median
+ distances.sort(function (a, b) {
+ return a - b;
+ });
+ median = distances[Math.floor(len / 2)];
+
+ // Compensate for series that don't extend through the entire axis extent. #1675.
+ xMin = Math.max(xMin, processedXData[0]);
+ xMax = Math.min(xMax, processedXData[len - 1]);
+
+ this.groupIntervalFactor = groupIntervalFactor = (len * median) / (xMax - xMin);
+ }
+
+ // Return the factor needed for data grouping
+ return groupIntervalFactor;
+ },
+
+ /**
+ * Make the tick intervals closer because the ordinal gaps make the ticks spread out or cluster
+ */
+ postProcessTickInterval: function (tickInterval) {
+ // Problem: http://jsfiddle.net/highcharts/FQm4E/1/
+ // This is a case where this algorithm doesn't work optimally. In this case, the
+ // tick labels are spread out per week, but all the gaps reside within weeks. So
+ // we have a situation where the labels are courser than the ordinal gaps, and
+ // thus the tick interval should not be altered
+ var ordinalSlope = this.ordinalSlope,
+ ret;
+
+
+ if (ordinalSlope) {
+ if (!this.options.breaks) {
+ ret = tickInterval / (ordinalSlope / this.closestPointRange);
+ } else {
+ ret = this.closestPointRange || tickInterval; // #7275
+ }
+ } else {
+ ret = tickInterval;
+ }
+ return ret;
+ }
+});
+
+// Record this to prevent overwriting by broken-axis module (#5979)
+Axis.prototype.ordinal2lin = Axis.prototype.val2lin;
+
+// Extending the Chart.pan method for ordinal axes
+wrap(Chart.prototype, 'pan', function (proceed, e) {
+ var chart = this,
+ xAxis = chart.xAxis[0],
+ overscroll = xAxis.options.overscroll,
+ chartX = e.chartX,
+ runBase = false;
+
+ if (xAxis.options.ordinal && xAxis.series.length) {
+
+ var mouseDownX = chart.mouseDownX,
+ extremes = xAxis.getExtremes(),
+ dataMax = extremes.dataMax,
+ min = extremes.min,
+ max = extremes.max,
+ trimmedRange,
+ hoverPoints = chart.hoverPoints,
+ closestPointRange = xAxis.closestPointRange || xAxis.overscrollPointsRange,
+ pointPixelWidth = xAxis.translationSlope * (xAxis.ordinalSlope || closestPointRange),
+ movedUnits = (mouseDownX - chartX) / pointPixelWidth, // how many ordinal units did we move?
+ extendedAxis = { ordinalPositions: xAxis.getExtendedPositions() }, // get index of all the chart's points
+ ordinalPositions,
+ searchAxisLeft,
+ lin2val = xAxis.lin2val,
+ val2lin = xAxis.val2lin,
+ searchAxisRight;
+
+ if (!extendedAxis.ordinalPositions) { // we have an ordinal axis, but the data is equally spaced
+ runBase = true;
+
+ } else if (Math.abs(movedUnits) > 1) {
+
+ // Remove active points for shared tooltip
+ if (hoverPoints) {
+ each(hoverPoints, function (point) {
+ point.setState();
+ });
+ }
+
+ if (movedUnits < 0) {
+ searchAxisLeft = extendedAxis;
+ searchAxisRight = xAxis.ordinalPositions ? xAxis : extendedAxis;
+ } else {
+ searchAxisLeft = xAxis.ordinalPositions ? xAxis : extendedAxis;
+ searchAxisRight = extendedAxis;
+ }
+
+ // In grouped data series, the last ordinal position represents the grouped data, which is
+ // to the left of the real data max. If we don't compensate for this, we will be allowed
+ // to pan grouped data series passed the right of the plot area.
+ ordinalPositions = searchAxisRight.ordinalPositions;
+ if (dataMax > ordinalPositions[ordinalPositions.length - 1]) {
+ ordinalPositions.push(dataMax);
+ }
+
+ // Get the new min and max values by getting the ordinal index for the current extreme,
+ // then add the moved units and translate back to values. This happens on the
+ // extended ordinal positions if the new position is out of range, else it happens
+ // on the current x axis which is smaller and faster.
+ chart.fixedRange = max - min;
+ trimmedRange = xAxis.toFixedRange(null, null,
+ lin2val.apply(searchAxisLeft, [
+ val2lin.apply(searchAxisLeft, [min, true]) + movedUnits, // the new index
+ true // translate from index
+ ]),
+ lin2val.apply(searchAxisRight, [
+ val2lin.apply(searchAxisRight, [max, true]) + movedUnits, // the new index
+ true // translate from index
+ ])
+ );
+
+ // Apply it if it is within the available data range
+ if (
+ trimmedRange.min >= Math.min(extremes.dataMin, min) &&
+ trimmedRange.max <= Math.max(dataMax, max) + overscroll
+ ) {
+ xAxis.setExtremes(trimmedRange.min, trimmedRange.max, true, false, { trigger: 'pan' });
+ }
+
+ chart.mouseDownX = chartX; // set new reference for next run
+ css(chart.container, { cursor: 'move' });
+ }
+
+ } else {
+ runBase = true;
+ }
+
+ // revert to the linear chart.pan version
+ if (runBase) {
+ if (overscroll) {
+ xAxis.max = xAxis.dataMax + overscroll;
+ }
+ // call the original function
+ proceed.apply(this, Array.prototype.slice.call(arguments, 1));
+ }
+});
+
+/* ****************************************************************************
+ * End ordinal axis logic *
+ *****************************************************************************/
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2009-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+var pick = H.pick,
+ wrap = H.wrap,
+ each = H.each,
+ extend = H.extend,
+ isArray = H.isArray,
+ fireEvent = H.fireEvent,
+ Axis = H.Axis,
+ Series = H.Series;
+
+function stripArguments() {
+ return Array.prototype.slice.call(arguments, 1);
+}
+
+extend(Axis.prototype, {
+ isInBreak: function (brk, val) {
+ var ret,
+ repeat = brk.repeat || Infinity,
+ from = brk.from,
+ length = brk.to - brk.from,
+ test = (val >= from ? (val - from) % repeat : repeat - ((from - val) % repeat));
+
+ if (!brk.inclusive) {
+ ret = test < length && test !== 0;
+ } else {
+ ret = test <= length;
+ }
+ return ret;
+ },
+
+ isInAnyBreak: function (val, testKeep) {
+
+ var breaks = this.options.breaks,
+ i = breaks && breaks.length,
+ inbrk,
+ keep,
+ ret;
+
+
+ if (i) {
+
+ while (i--) {
+ if (this.isInBreak(breaks[i], val)) {
+ inbrk = true;
+ if (!keep) {
+ keep = pick(breaks[i].showPoints, this.isXAxis ? false : true);
+ }
+ }
+ }
+
+ if (inbrk && testKeep) {
+ ret = inbrk && !keep;
+ } else {
+ ret = inbrk;
+ }
+ }
+ return ret;
+ }
+});
+
+wrap(Axis.prototype, 'setTickPositions', function (proceed) {
+ proceed.apply(this, Array.prototype.slice.call(arguments, 1));
+
+ if (this.options.breaks) {
+ var axis = this,
+ tickPositions = this.tickPositions,
+ info = this.tickPositions.info,
+ newPositions = [],
+ i;
+
+ for (i = 0; i < tickPositions.length; i++) {
+ if (!axis.isInAnyBreak(tickPositions[i])) {
+ newPositions.push(tickPositions[i]);
+ }
+ }
+
+ this.tickPositions = newPositions;
+ this.tickPositions.info = info;
+ }
+});
+
+wrap(Axis.prototype, 'init', function (proceed, chart, userOptions) {
+ var axis = this,
+ breaks;
+ // Force Axis to be not-ordinal when breaks are defined
+ if (userOptions.breaks && userOptions.breaks.length) {
+ userOptions.ordinal = false;
+ }
+ proceed.call(this, chart, userOptions);
+ breaks = this.options.breaks;
+ axis.isBroken = (isArray(breaks) && !!breaks.length);
+ if (axis.isBroken) {
+ axis.val2lin = function (val) {
+ var nval = val,
+ brk,
+ i;
+
+ for (i = 0; i < axis.breakArray.length; i++) {
+ brk = axis.breakArray[i];
+ if (brk.to <= val) {
+ nval -= brk.len;
+ } else if (brk.from >= val) {
+ break;
+ } else if (axis.isInBreak(brk, val)) {
+ nval -= (val - brk.from);
+ break;
+ }
+ }
+
+ return nval;
+ };
+
+ axis.lin2val = function (val) {
+ var nval = val,
+ brk,
+ i;
+
+ for (i = 0; i < axis.breakArray.length; i++) {
+ brk = axis.breakArray[i];
+ if (brk.from >= nval) {
+ break;
+ } else if (brk.to < nval) {
+ nval += brk.len;
+ } else if (axis.isInBreak(brk, nval)) {
+ nval += brk.len;
+ }
+ }
+ return nval;
+ };
+
+ axis.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) {
+ // If trying to set extremes inside a break, extend it to before and after the break ( #3857 )
+ while (this.isInAnyBreak(newMin)) {
+ newMin -= this.closestPointRange;
+ }
+ while (this.isInAnyBreak(newMax)) {
+ newMax -= this.closestPointRange;
+ }
+ Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments);
+ };
+
+ axis.setAxisTranslation = function (saveOld) {
+ Axis.prototype.setAxisTranslation.call(this, saveOld);
+
+ var breaks = axis.options.breaks,
+ breakArrayT = [], // Temporary one
+ breakArray = [],
+ length = 0,
+ inBrk,
+ repeat,
+ min = axis.userMin || axis.min,
+ max = axis.userMax || axis.max,
+ pointRangePadding = pick(axis.pointRangePadding, 0),
+ start,
+ i;
+
+ // Min & max check (#4247)
+ each(breaks, function (brk) {
+ repeat = brk.repeat || Infinity;
+ if (axis.isInBreak(brk, min)) {
+ min += (brk.to % repeat) - (min % repeat);
+ }
+ if (axis.isInBreak(brk, max)) {
+ max -= (max % repeat) - (brk.from % repeat);
+ }
+ });
+
+ // Construct an array holding all breaks in the axis
+ each(breaks, function (brk) {
+ start = brk.from;
+ repeat = brk.repeat || Infinity;
+
+ while (start - repeat > min) {
+ start -= repeat;
+ }
+ while (start < min) {
+ start += repeat;
+ }
+
+ for (i = start; i < max; i += repeat) {
+ breakArrayT.push({
+ value: i,
+ move: 'in'
+ });
+ breakArrayT.push({
+ value: i + (brk.to - brk.from),
+ move: 'out',
+ size: brk.breakSize
+ });
+ }
+ });
+
+ breakArrayT.sort(function (a, b) {
+ var ret;
+ if (a.value === b.value) {
+ ret = (a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1);
+ } else {
+ ret = a.value - b.value;
+ }
+ return ret;
+ });
+
+ // Simplify the breaks
+ inBrk = 0;
+ start = min;
+
+ each(breakArrayT, function (brk) {
+ inBrk += (brk.move === 'in' ? 1 : -1);
+
+ if (inBrk === 1 && brk.move === 'in') {
+ start = brk.value;
+ }
+ if (inBrk === 0) {
+ breakArray.push({
+ from: start,
+ to: brk.value,
+ len: brk.value - start - (brk.size || 0)
+ });
+ length += brk.value - start - (brk.size || 0);
+ }
+ });
+
+ axis.breakArray = breakArray;
+
+ // Used with staticScale, and below, the actual axis length when
+ // breaks are substracted.
+ axis.unitLength = max - min - length + pointRangePadding;
+
+ fireEvent(axis, 'afterBreaks');
+
+ if (axis.options.staticScale) {
+ axis.transA = axis.options.staticScale;
+ } else if (axis.unitLength) {
+ axis.transA *= (max - axis.min + pointRangePadding) /
+ axis.unitLength;
+ }
+
+ if (pointRangePadding) {
+ axis.minPixelPadding = axis.transA * axis.minPointOffset;
+ }
+
+ axis.min = min;
+ axis.max = max;
+ };
+ }
+});
+
+wrap(Series.prototype, 'generatePoints', function (proceed) {
+
+ proceed.apply(this, stripArguments(arguments));
+
+ var series = this,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis,
+ points = series.points,
+ point,
+ i = points.length,
+ connectNulls = series.options.connectNulls,
+ nullGap;
+
+
+ if (xAxis && yAxis && (xAxis.options.breaks || yAxis.options.breaks)) {
+ while (i--) {
+ point = points[i];
+
+ nullGap = point.y === null && connectNulls === false; // respect nulls inside the break (#4275)
+ if (!nullGap && (xAxis.isInAnyBreak(point.x, true) || yAxis.isInAnyBreak(point.y, true))) {
+ points.splice(i, 1);
+ if (this.data[i]) {
+ this.data[i].destroyElements(); // removes the graphics for this point if they exist
+ }
+ }
+ }
+ }
+
+});
+
+function drawPointsWrapped(proceed) {
+ proceed.apply(this);
+ this.drawBreaks(this.xAxis, ['x']);
+ this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y']));
+}
+
+H.Series.prototype.drawBreaks = function (axis, keys) {
+ var series = this,
+ points = series.points,
+ breaks,
+ threshold,
+ eventName,
+ y;
+
+ if (!axis) {
+ return; // #5950
+ }
+
+ each(keys, function (key) {
+ breaks = axis.breakArray || [];
+ threshold = axis.isXAxis ? axis.min : pick(series.options.threshold, axis.min);
+ each(points, function (point) {
+ y = pick(point['stack' + key.toUpperCase()], point[key]);
+ each(breaks, function (brk) {
+ eventName = false;
+
+ if ((threshold < brk.from && y > brk.to) || (threshold > brk.from && y < brk.from)) {
+ eventName = 'pointBreak';
+ } else if ((threshold < brk.from && y > brk.from && y < brk.to) || (threshold > brk.from && y > brk.to && y < brk.from)) { // point falls inside the break
+ eventName = 'pointInBreak';
+ }
+ if (eventName) {
+ fireEvent(axis, eventName, { point: point, brk: brk });
+ }
+ });
+ });
+ });
+};
+
+
+/**
+ * Extend getGraphPath by identifying gaps in the data so that we can draw a gap
+ * in the line or area. This was moved from ordinal axis module to broken axis
+ * module as of #5045.
+ */
+H.Series.prototype.gappedPath = function () {
+ var gapSize = this.options.gapSize,
+ points = this.points.slice(),
+ i = points.length - 1,
+ yAxis = this.yAxis,
+ xRange,
+ stack;
+
+ /**
+ * Defines when to display a gap in the graph, together with the
+ * [gapUnit](plotOptions.series.gapUnit) option.
+ *
+ * In practice, this option is most often used to visualize gaps in
+ * time series. In a stock chart, intraday data is available for daytime
+ * hours, while gaps will appear in nights and weekends.
+ *
+ * @type {Number}
+ * @see [gapUnit](plotOptions.series.gapUnit) and [xAxis.breaks](#xAxis.breaks)
+ * @sample {highstock} stock/plotoptions/series-gapsize/
+ * Setting the gap size to 2 introduces gaps for weekends in daily
+ * datasets.
+ * @default 0
+ * @product highstock
+ * @apioption plotOptions.series.gapSize
+ */
+
+ /**
+ * Together with [gapSize](plotOptions.series.gapSize), this option defines
+ * where to draw gaps in the graph.
+ *
+ * When the `gapUnit` is `relative` (default), a gap size of 5 means
+ * that if the distance between two points is greater than five times
+ * that of the two closest points, the graph will be broken.
+ *
+ * When the `gapUnit` is `value`, the gap is based on absolute axis values,
+ * which on a datetime axis is milliseconds. Note that this may give
+ * unexpected results if `dataGrouping` is enabled (as it is by default),
+ * because if a series of points are grouped into a larger time span, the
+ * grouped points may have a greater distance than the absolute `gapSize`.
+ * This will cause the whole graph to disappear. This also applies to the
+ * navigator series that inherits gap options from the base series.
+ *
+ * @type {String}
+ * @see [gapSize](plotOptions.series.gapSize)
+ * @default relative
+ * @validvalue ["relative", "value"]
+ * @since 5.0.13
+ * @product highstock
+ * @apioption plotOptions.series.gapUnit
+ */
+
+ if (gapSize && i > 0) { // #5008
+
+ // Gap unit is relative
+ if (this.options.gapUnit !== 'value') {
+ gapSize *= this.closestPointRange;
+ }
+
+ // extension for ordinal breaks
+ while (i--) {
+ if (points[i + 1].x - points[i].x > gapSize) {
+ xRange = (points[i].x + points[i + 1].x) / 2;
+
+ points.splice( // insert after this one
+ i + 1,
+ 0,
+ {
+ isNull: true,
+ x: xRange
+ }
+ );
+
+ // For stacked chart generate empty stack items, #6546
+ if (this.options.stacking) {
+ stack = yAxis.stacks[this.stackKey][xRange] = new H.StackItem(
+ yAxis,
+ yAxis.options.stackLabels,
+ false,
+ xRange,
+ this.stack
+ );
+ stack.total = 0;
+ }
+ }
+ }
+ }
+
+ // Call base method
+ return this.getGraphPath(points);
+};
+
+wrap(H.seriesTypes.column.prototype, 'drawPoints', drawPointsWrapped);
+wrap(H.Series.prototype, 'drawPoints', drawPointsWrapped);
+
+}(Highcharts));
+(function () {
+
+
+}());
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var arrayMax = H.arrayMax,
+ arrayMin = H.arrayMin,
+ Axis = H.Axis,
+ defaultPlotOptions = H.defaultPlotOptions,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ format = H.format,
+ isNumber = H.isNumber,
+ merge = H.merge,
+ pick = H.pick,
+ Point = H.Point,
+ Series = H.Series,
+ Tooltip = H.Tooltip,
+ wrap = H.wrap;
+
+/* ****************************************************************************
+ * Start data grouping module *
+ ******************************************************************************/
+
+/**
+ * Data grouping is the concept of sampling the data values into larger
+ * blocks in order to ease readability and increase performance of the
+ * JavaScript charts. Highstock by default applies data grouping when
+ * the points become closer than a certain pixel value, determined by
+ * the `groupPixelWidth` option.
+ *
+ * If data grouping is applied, the grouping information of grouped
+ * points can be read from the [Point.dataGroup](#Point.dataGroup).
+ *
+ * @product highstock
+ * @apioption plotOptions.series.dataGrouping
+ */
+
+/**
+ * The method of approximation inside a group. When for example 30 days
+ * are grouped into one month, this determines what value should represent
+ * the group. Possible values are "average", "averages", "open", "high",
+ * "low", "close" and "sum". For OHLC and candlestick series the approximation
+ * is "ohlc" by default, which finds the open, high, low and close values
+ * within all the grouped data. For ranges, the approximation is "range",
+ * which finds the low and high values. For multi-dimensional data,
+ * like ranges and OHLC, "averages" will compute the average for each
+ * dimension.
+ *
+ * Custom aggregate methods can be added by assigning a callback function
+ * as the approximation. This function takes a numeric array as the
+ * argument and should return a single numeric value or `null`. Note
+ * that the numeric array will never contain null values, only true
+ * numbers. Instead, if null values are present in the raw data, the
+ * numeric array will have an `.hasNulls` property set to `true`. For
+ * single-value data sets the data is available in the first argument
+ * of the callback function. For OHLC data sets, all the open values
+ * are in the first argument, all high values in the second etc.
+ *
+ * Since v4.2.7, grouping meta data is available in the approximation
+ * callback from `this.dataGroupInfo`. It can be used to extract information
+ * from the raw data.
+ *
+ * Defaults to `average` for line-type series, `sum` for columns, `range`
+ * for range series and `ohlc` for OHLC and candlestick.
+ *
+ * @validvalue ["average", "averages", "open", "high", "low", "close", "sum"]
+ * @type {String|Function}
+ * @sample {highstock} stock/plotoptions/series-datagrouping-approximation Approximation callback with custom data
+ * @product highstock
+ * @apioption plotOptions.series.dataGrouping.approximation
+ */
+
+/**
+ * Datetime formats for the header of the tooltip in a stock chart.
+ * The format can vary within a chart depending on the currently selected
+ * time range and the current data grouping.
+ *
+ * The default formats are:
+ *
+ * {
+ * millisecond: ['%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'],
+ * second: ['%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S'],
+ * minute: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
+ * hour: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
+ * day: ['%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
+ * week: ['Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
+ * month: ['%B %Y', '%B', '-%B %Y'],
+ * year: ['%Y', '%Y', '-%Y']
+ * }
+ *
+ * For each of these array definitions, the first item is the format
+ * used when the active time span is one unit. For instance, if the
+ * current data applies to one week, the first item of the week array
+ * is used. The second and third items are used when the active time
+ * span is more than two units. For instance, if the current data applies
+ * to two weeks, the second and third item of the week array are used,
+ * and applied to the start and end date of the time span.
+ *
+ * @type {Object}
+ * @product highstock
+ * @apioption plotOptions.series.dataGrouping.dateTimeLabelFormats
+ */
+
+/**
+ * Enable or disable data grouping.
+ *
+ * @type {Boolean}
+ * @default true
+ * @product highstock
+ * @apioption plotOptions.series.dataGrouping.enabled
+ */
+
+/**
+ * When data grouping is forced, it runs no matter how small the intervals
+ * are. This can be handy for example when the sum should be calculated
+ * for values appearing at random times within each hour.
+ *
+ * @type {Boolean}
+ * @default false
+ * @product highstock
+ * @apioption plotOptions.series.dataGrouping.forced
+ */
+
+/**
+ * The approximate pixel width of each group. If for example a series
+ * with 30 points is displayed over a 600 pixel wide plot area, no grouping
+ * is performed. If however the series contains so many points that
+ * the spacing is less than the groupPixelWidth, Highcharts will try
+ * to group it into appropriate groups so that each is more or less
+ * two pixels wide. If multiple series with different group pixel widths
+ * are drawn on the same x axis, all series will take the greatest width.
+ * For example, line series have 2px default group width, while column
+ * series have 10px. If combined, both the line and the column will
+ * have 10px by default.
+ *
+ * @type {Number}
+ * @default 2
+ * @product highstock
+ * @apioption plotOptions.series.dataGrouping.groupPixelWidth
+ */
+
+/**
+ * Normally, a group is indexed by the start of that group, so for example
+ * when 30 daily values are grouped into one month, that month's x value
+ * will be the 1st of the month. This apparently shifts the data to
+ * the left. When the smoothed option is true, this is compensated for.
+ * The data is shifted to the middle of the group, and min and max
+ * values are preserved. Internally, this is used in the Navigator series.
+ *
+ * @type {Boolean}
+ * @default false
+ * @product highstock
+ * @apioption plotOptions.series.dataGrouping.smoothed
+ */
+
+/**
+ * An array determining what time intervals the data is allowed to be
+ * grouped to. Each array item is an array where the first value is
+ * the time unit and the second value another array of allowed multiples.
+ * Defaults to:
+ *
+ * units: [[
+ * 'millisecond', // unit name
+ * [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
+ * ], [
+ * 'second',
+ * [1, 2, 5, 10, 15, 30]
+ * ], [
+ * 'minute',
+ * [1, 2, 5, 10, 15, 30]
+ * ], [
+ * 'hour',
+ * [1, 2, 3, 4, 6, 8, 12]
+ * ], [
+ * 'day',
+ * [1]
+ * ], [
+ * 'week',
+ * [1]
+ * ], [
+ * 'month',
+ * [1, 3, 6]
+ * ], [
+ * 'year',
+ * null
+ * ]]
+ *
+ * @type {Array}
+ * @product highstock
+ * @apioption plotOptions.series.dataGrouping.units
+ */
+
+/**
+ * The approximate pixel width of each group. If for example a series
+ * with 30 points is displayed over a 600 pixel wide plot area, no grouping
+ * is performed. If however the series contains so many points that
+ * the spacing is less than the groupPixelWidth, Highcharts will try
+ * to group it into appropriate groups so that each is more or less
+ * two pixels wide. Defaults to `10`.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/plotoptions/series-datagrouping-grouppixelwidth/
+ * Two series with the same data density but different groupPixelWidth
+ * @default 10
+ * @product highstock
+ * @apioption plotOptions.column.dataGrouping.groupPixelWidth
+ */
+
+var seriesProto = Series.prototype,
+ baseProcessData = seriesProto.processData,
+ baseGeneratePoints = seriesProto.generatePoints,
+
+ /**
+ *
+ */
+ commonOptions = {
+ approximation: 'average', // average, open, high, low, close, sum
+ // enabled: null, // (true for stock charts, false for basic),
+ // forced: undefined,
+ groupPixelWidth: 2,
+ // the first one is the point or start value, the second is the start value if we're dealing with range,
+ // the third one is the end value if dealing with a range
+ dateTimeLabelFormats: {
+ millisecond: ['%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L'],
+ second: ['%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S'],
+ minute: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
+ hour: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'],
+ day: ['%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
+ week: ['Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'],
+ month: ['%B %Y', '%B', '-%B %Y'],
+ year: ['%Y', '%Y', '-%Y']
+ }
+ // smoothed = false, // enable this for navigator series only
+ },
+
+ specificOptions = { // extends common options
+ line: {},
+ spline: {},
+ area: {},
+ areaspline: {},
+ column: {
+ approximation: 'sum',
+ groupPixelWidth: 10
+ },
+ arearange: {
+ approximation: 'range'
+ },
+ areasplinerange: {
+ approximation: 'range'
+ },
+ columnrange: {
+ approximation: 'range',
+ groupPixelWidth: 10
+ },
+ candlestick: {
+ approximation: 'ohlc',
+ groupPixelWidth: 10
+ },
+ ohlc: {
+ approximation: 'ohlc',
+ groupPixelWidth: 5
+ }
+ },
+
+ // units are defined in a separate array to allow complete overriding in case of a user option
+ defaultDataGroupingUnits = H.defaultDataGroupingUnits = [
+ [
+ 'millisecond', // unit name
+ [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
+ ], [
+ 'second',
+ [1, 2, 5, 10, 15, 30]
+ ], [
+ 'minute',
+ [1, 2, 5, 10, 15, 30]
+ ], [
+ 'hour',
+ [1, 2, 3, 4, 6, 8, 12]
+ ], [
+ 'day',
+ [1]
+ ], [
+ 'week',
+ [1]
+ ], [
+ 'month',
+ [1, 3, 6]
+ ], [
+ 'year',
+ null
+ ]
+ ],
+
+
+ /**
+ * Define the available approximation types. The data grouping
+ * approximations takes an array or numbers as the first parameter. In case
+ * of ohlc, four arrays are sent in as four parameters. Each array consists
+ * only of numbers. In case null values belong to the group, the property
+ * .hasNulls will be set to true on the array.
+ */
+ approximations = H.approximations = {
+ sum: function (arr) {
+ var len = arr.length,
+ ret;
+
+ // 1. it consists of nulls exclusively
+ if (!len && arr.hasNulls) {
+ ret = null;
+ // 2. it has a length and real values
+ } else if (len) {
+ ret = 0;
+ while (len--) {
+ ret += arr[len];
+ }
+ }
+ // 3. it has zero length, so just return undefined
+ // => doNothing()
+
+ return ret;
+ },
+ average: function (arr) {
+ var len = arr.length,
+ ret = approximations.sum(arr);
+
+ // If we have a number, return it divided by the length. If not,
+ // return null or undefined based on what the sum method finds.
+ if (isNumber(ret) && len) {
+ ret = ret / len;
+ }
+
+ return ret;
+ },
+ // The same as average, but for series with multiple values, like area
+ // ranges.
+ averages: function () { // #5479
+ var ret = [];
+
+ each(arguments, function (arr) {
+ ret.push(approximations.average(arr));
+ });
+
+ // Return undefined when first elem. is undefined and let
+ // sum method handle null (#7377)
+ return ret[0] === undefined ? undefined : ret;
+ },
+ open: function (arr) {
+ return arr.length ? arr[0] : (arr.hasNulls ? null : undefined);
+ },
+ high: function (arr) {
+ return arr.length ? arrayMax(arr) : (arr.hasNulls ? null : undefined);
+ },
+ low: function (arr) {
+ return arr.length ? arrayMin(arr) : (arr.hasNulls ? null : undefined);
+ },
+ close: function (arr) {
+ return arr.length ? arr[arr.length - 1] : (arr.hasNulls ? null : undefined);
+ },
+ // ohlc and range are special cases where a multidimensional array is input and an array is output
+ ohlc: function (open, high, low, close) {
+ open = approximations.open(open);
+ high = approximations.high(high);
+ low = approximations.low(low);
+ close = approximations.close(close);
+
+ if (isNumber(open) || isNumber(high) || isNumber(low) || isNumber(close)) {
+ return [open, high, low, close];
+ }
+ // else, return is undefined
+ },
+ range: function (low, high) {
+ low = approximations.low(low);
+ high = approximations.high(high);
+
+ if (isNumber(low) || isNumber(high)) {
+ return [low, high];
+ } else if (low === null && high === null) {
+ return null;
+ }
+ // else, return is undefined
+ }
+ };
+
+/**
+ * Takes parallel arrays of x and y data and groups the data into intervals
+ * defined by groupPositions, a collection of starting x values for each group.
+ */
+seriesProto.groupData = function (xData, yData, groupPositions, approximation) {
+ var series = this,
+ data = series.data,
+ dataOptions = series.options.data,
+ groupedXData = [],
+ groupedYData = [],
+ groupMap = [],
+ dataLength = xData.length,
+ pointX,
+ pointY,
+ groupedY,
+ // when grouping the fake extended axis for panning,
+ // we don't need to consider y
+ handleYData = !!yData,
+ values = [],
+ approximationFn = typeof approximation === 'function' ?
+ approximation :
+ approximations[approximation] ||
+ // if the approximation is not found use default series type
+ // approximation (#2914)
+ (
+ specificOptions[series.type] &&
+ approximations[specificOptions[series.type].approximation]
+ ) || approximations[commonOptions.approximation],
+ pointArrayMap = series.pointArrayMap,
+ pointArrayMapLength = pointArrayMap && pointArrayMap.length,
+ pos = 0,
+ start = 0,
+ valuesLen,
+ i, j;
+
+ // Calculate values array size from pointArrayMap length
+ if (pointArrayMapLength) {
+ each(pointArrayMap, function () {
+ values.push([]);
+ });
+ } else {
+ values.push([]);
+ }
+ valuesLen = pointArrayMapLength || 1;
+
+ // Start with the first point within the X axis range (#2696)
+ for (i = 0; i <= dataLength; i++) {
+ if (xData[i] >= groupPositions[0]) {
+ break;
+ }
+ }
+
+ for (i; i <= dataLength; i++) {
+
+ // when a new group is entered, summarize and initiate
+ // the previous group
+ while ((
+ groupPositions[pos + 1] !== undefined &&
+ xData[i] >= groupPositions[pos + 1]
+ ) || i === dataLength) { // get the last group
+
+ // get group x and y
+ pointX = groupPositions[pos];
+ series.dataGroupInfo = { start: start, length: values[0].length };
+ groupedY = approximationFn.apply(series, values);
+
+ // push the grouped data
+ if (groupedY !== undefined) {
+ groupedXData.push(pointX);
+ groupedYData.push(groupedY);
+ groupMap.push(series.dataGroupInfo);
+ }
+
+ // reset the aggregate arrays
+ start = i;
+ for (j = 0; j < valuesLen; j++) {
+ values[j].length = 0; // faster than values[j] = []
+ values[j].hasNulls = false;
+ }
+
+ // Advance on the group positions
+ pos += 1;
+
+ // don't loop beyond the last group
+ if (i === dataLength) {
+ break;
+ }
+ }
+
+ // break out
+ if (i === dataLength) {
+ break;
+ }
+
+ // for each raw data point, push it to an array that contains all values
+ // for this specific group
+ if (pointArrayMap) {
+
+ var index = series.cropStart + i,
+ point = (data && data[index]) ||
+ series.pointClass.prototype.applyOptions.apply({
+ series: series
+ }, [dataOptions[index]]),
+ val;
+
+ for (j = 0; j < pointArrayMapLength; j++) {
+ val = point[pointArrayMap[j]];
+ if (isNumber(val)) {
+ values[j].push(val);
+ } else if (val === null) {
+ values[j].hasNulls = true;
+ }
+ }
+
+ } else {
+ pointY = handleYData ? yData[i] : null;
+
+ if (isNumber(pointY)) {
+ values[0].push(pointY);
+ } else if (pointY === null) {
+ values[0].hasNulls = true;
+ }
+ }
+ }
+
+ return [groupedXData, groupedYData, groupMap];
+};
+
+/**
+ * Extend the basic processData method, that crops the data to the current zoom
+ * range, with data grouping logic.
+ */
+seriesProto.processData = function () {
+ var series = this,
+ chart = series.chart,
+ options = series.options,
+ dataGroupingOptions = options.dataGrouping,
+ groupingEnabled = series.allowDG !== false && dataGroupingOptions &&
+ pick(dataGroupingOptions.enabled, chart.options.isStock),
+ visible = series.visible || !chart.options.chart.ignoreHiddenSeries,
+ hasGroupedData,
+ skip,
+ lastDataGrouping = this.currentDataGrouping,
+ currentDataGrouping;
+
+ // run base method
+ series.forceCrop = groupingEnabled; // #334
+ series.groupPixelWidth = null; // #2110
+ series.hasProcessed = true; // #2692
+
+ // skip if processData returns false or if grouping is disabled (in that order)
+ skip = baseProcessData.apply(series, arguments) === false || !groupingEnabled;
+ if (!skip) {
+ series.destroyGroupedData();
+
+ var i,
+ processedXData = series.processedXData,
+ processedYData = series.processedYData,
+ plotSizeX = chart.plotSizeX,
+ xAxis = series.xAxis,
+ ordinal = xAxis.options.ordinal,
+ groupPixelWidth = series.groupPixelWidth = xAxis.getGroupPixelWidth && xAxis.getGroupPixelWidth();
+
+ // Execute grouping if the amount of points is greater than the limit defined in groupPixelWidth
+ if (groupPixelWidth) {
+ hasGroupedData = true;
+
+ series.isDirty = true; // force recreation of point instances in series.translate, #5699
+ series.points = null; // #6709
+
+ var extremes = xAxis.getExtremes(),
+ xMin = extremes.min,
+ xMax = extremes.max,
+ groupIntervalFactor = (ordinal && xAxis.getGroupIntervalFactor(xMin, xMax, series)) || 1,
+ interval = (groupPixelWidth * (xMax - xMin) / plotSizeX) * groupIntervalFactor,
+ groupPositions = xAxis.getTimeTicks(
+ xAxis.normalizeTimeTickInterval(interval, dataGroupingOptions.units || defaultDataGroupingUnits),
+ Math.min(xMin, processedXData[0]), // Processed data may extend beyond axis (#4907)
+ Math.max(xMax, processedXData[processedXData.length - 1]),
+ xAxis.options.startOfWeek,
+ processedXData,
+ series.closestPointRange
+ ),
+ groupedData = seriesProto.groupData.apply(series, [processedXData, processedYData, groupPositions, dataGroupingOptions.approximation]),
+ groupedXData = groupedData[0],
+ groupedYData = groupedData[1];
+
+ // prevent the smoothed data to spill out left and right, and make
+ // sure data is not shifted to the left
+ if (dataGroupingOptions.smoothed && groupedXData.length) {
+ i = groupedXData.length - 1;
+ groupedXData[i] = Math.min(groupedXData[i], xMax);
+ while (i-- && i > 0) {
+ groupedXData[i] += interval / 2;
+ }
+ groupedXData[0] = Math.max(groupedXData[0], xMin);
+ }
+
+ // record what data grouping values were used
+ currentDataGrouping = groupPositions.info;
+ series.closestPointRange = groupPositions.info.totalRange;
+ series.groupMap = groupedData[2];
+
+ // Make sure the X axis extends to show the first group (#2533)
+ // But only for visible series (#5493, #6393)
+ if (defined(groupedXData[0]) && groupedXData[0] < xAxis.dataMin && visible) {
+ if (xAxis.min === xAxis.dataMin) {
+ xAxis.min = groupedXData[0];
+ }
+ xAxis.dataMin = groupedXData[0];
+ }
+
+ // set series props
+ series.processedXData = groupedXData;
+ series.processedYData = groupedYData;
+ } else {
+ series.groupMap = null;
+ }
+ series.hasGroupedData = hasGroupedData;
+ series.currentDataGrouping = currentDataGrouping;
+
+ series.preventGraphAnimation =
+ (lastDataGrouping && lastDataGrouping.totalRange) !==
+ (currentDataGrouping && currentDataGrouping.totalRange);
+ }
+};
+
+/**
+ * Destroy the grouped data points. #622, #740
+ */
+seriesProto.destroyGroupedData = function () {
+
+ var groupedData = this.groupedData;
+
+ // clear previous groups
+ each(groupedData || [], function (point, i) {
+ if (point) {
+ groupedData[i] = point.destroy ? point.destroy() : null;
+ }
+ });
+ this.groupedData = null;
+};
+
+/**
+ * Override the generatePoints method by adding a reference to grouped data
+ */
+seriesProto.generatePoints = function () {
+
+ baseGeneratePoints.apply(this);
+
+ // record grouped data in order to let it be destroyed the next time processData runs
+ this.destroyGroupedData(); // #622
+ this.groupedData = this.hasGroupedData ? this.points : null;
+};
+
+/**
+ * Override point prototype to throw a warning when trying to update grouped points
+ */
+wrap(Point.prototype, 'update', function (proceed) {
+ if (this.dataGroup) {
+ H.error(24);
+ } else {
+ proceed.apply(this, [].slice.call(arguments, 1));
+ }
+});
+
+/**
+ * Extend the original method, make the tooltip's header reflect the grouped range
+ */
+wrap(Tooltip.prototype, 'tooltipFooterHeaderFormatter', function (proceed, labelConfig, isFooter) {
+ var tooltip = this,
+ time = this.chart.time,
+ series = labelConfig.series,
+ options = series.options,
+ tooltipOptions = series.tooltipOptions,
+ dataGroupingOptions = options.dataGrouping,
+ xDateFormat = tooltipOptions.xDateFormat,
+ xDateFormatEnd,
+ xAxis = series.xAxis,
+ currentDataGrouping,
+ dateTimeLabelFormats,
+ labelFormats,
+ formattedKey;
+
+ // apply only to grouped series
+ if (xAxis && xAxis.options.type === 'datetime' && dataGroupingOptions && isNumber(labelConfig.key)) {
+
+ // set variables
+ currentDataGrouping = series.currentDataGrouping;
+ dateTimeLabelFormats = dataGroupingOptions.dateTimeLabelFormats;
+
+ // if we have grouped data, use the grouping information to get the right format
+ if (currentDataGrouping) {
+ labelFormats = dateTimeLabelFormats[currentDataGrouping.unitName];
+ if (currentDataGrouping.count === 1) {
+ xDateFormat = labelFormats[0];
+ } else {
+ xDateFormat = labelFormats[1];
+ xDateFormatEnd = labelFormats[2];
+ }
+ // if not grouped, and we don't have set the xDateFormat option, get the best fit,
+ // so if the least distance between points is one minute, show it, but if the
+ // least distance is one day, skip hours and minutes etc.
+ } else if (!xDateFormat && dateTimeLabelFormats) {
+ xDateFormat = tooltip.getXDateFormat(labelConfig, tooltipOptions, xAxis);
+ }
+
+ // now format the key
+ formattedKey = time.dateFormat(xDateFormat, labelConfig.key);
+ if (xDateFormatEnd) {
+ formattedKey += time.dateFormat(
+ xDateFormatEnd,
+ labelConfig.key + currentDataGrouping.totalRange - 1
+ );
+ }
+
+ // return the replaced format
+ return format(tooltipOptions[(isFooter ? 'footer' : 'header') + 'Format'], {
+ point: extend(labelConfig.point, { key: formattedKey }),
+ series: series
+ }, time);
+
+ }
+
+ // else, fall back to the regular formatter
+ return proceed.call(tooltip, labelConfig, isFooter);
+});
+
+/**
+ * Destroy grouped data on series destroy
+ */
+wrap(seriesProto, 'destroy', function (proceed) {
+ this.destroyGroupedData();
+ proceed.call(this);
+});
+
+
+// Handle default options for data grouping. This must be set at runtime because some series types are
+// defined after this.
+wrap(seriesProto, 'setOptions', function (proceed, itemOptions) {
+
+ var options = proceed.call(this, itemOptions),
+ type = this.type,
+ plotOptions = this.chart.options.plotOptions,
+ defaultOptions = defaultPlotOptions[type].dataGrouping;
+
+ if (specificOptions[type]) { // #1284
+ if (!defaultOptions) {
+ defaultOptions = merge(commonOptions, specificOptions[type]);
+ }
+
+ options.dataGrouping = merge(
+ defaultOptions,
+ plotOptions.series && plotOptions.series.dataGrouping, // #1228
+ plotOptions[type].dataGrouping, // Set by the StockChart constructor
+ itemOptions.dataGrouping
+ );
+ }
+
+ if (this.chart.options.isStock) {
+ this.requireSorting = true;
+ }
+
+ return options;
+});
+
+
+/**
+ * When resetting the scale reset the hasProccessed flag to avoid taking previous data grouping
+ * of neighbour series into accound when determining group pixel width (#2692).
+ */
+wrap(Axis.prototype, 'setScale', function (proceed) {
+ proceed.call(this);
+ each(this.series, function (series) {
+ series.hasProcessed = false;
+ });
+});
+
+/**
+ * Get the data grouping pixel width based on the greatest defined individual width
+ * of the axis' series, and if whether one of the axes need grouping.
+ */
+Axis.prototype.getGroupPixelWidth = function () {
+
+ var series = this.series,
+ len = series.length,
+ i,
+ groupPixelWidth = 0,
+ doGrouping = false,
+ dataLength,
+ dgOptions;
+
+ // If multiple series are compared on the same x axis, give them the same
+ // group pixel width (#334)
+ i = len;
+ while (i--) {
+ dgOptions = series[i].options.dataGrouping;
+ if (dgOptions) {
+ groupPixelWidth = Math.max(groupPixelWidth, dgOptions.groupPixelWidth);
+
+ }
+ }
+
+ // If one of the series needs grouping, apply it to all (#1634)
+ i = len;
+ while (i--) {
+ dgOptions = series[i].options.dataGrouping;
+
+ if (dgOptions && series[i].hasProcessed) { // #2692
+
+ dataLength = (series[i].processedXData || series[i].data).length;
+
+ // Execute grouping if the amount of points is greater than the limit defined in groupPixelWidth
+ if (series[i].groupPixelWidth || dataLength > (this.chart.plotSizeX / groupPixelWidth) || (dataLength && dgOptions.forced)) {
+ doGrouping = true;
+ }
+ }
+ }
+
+ return doGrouping ? groupPixelWidth : 0;
+};
+
+/**
+ * Highstock only. Force data grouping on all the axis' series.
+ *
+ * @param {SeriesDatagroupingOptions} [dataGrouping]
+ * A `dataGrouping` configuration. Use `false` to disable data grouping
+ * dynamically.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart or wait for a later call to {@link
+ * Chart#redraw}.
+ *
+ * @function setDataGrouping
+ * @memberOf Axis.prototype
+ */
+Axis.prototype.setDataGrouping = function (dataGrouping, redraw) {
+ var i;
+
+ redraw = pick(redraw, true);
+
+ if (!dataGrouping) {
+ dataGrouping = {
+ forced: false,
+ units: null
+ };
+ }
+
+ // Axis is instantiated, update all series
+ if (this instanceof Axis) {
+ i = this.series.length;
+ while (i--) {
+ this.series[i].update({
+ dataGrouping: dataGrouping
+ }, false);
+ }
+
+ // Axis not yet instanciated, alter series options
+ } else {
+ each(this.chart.options.series, function (seriesOptions) {
+ seriesOptions.dataGrouping = dataGrouping;
+ }, false);
+ }
+
+ if (redraw) {
+ this.chart.redraw();
+ }
+};
+
+
+
+/* ****************************************************************************
+ * End data grouping module *
+ ******************************************************************************/
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var each = H.each,
+ Point = H.Point,
+ seriesType = H.seriesType,
+ seriesTypes = H.seriesTypes;
+
+/**
+ * The ohlc series type.
+ *
+ * @constructor seriesTypes.ohlc
+ * @augments seriesTypes.column
+ */
+/**
+ * An OHLC chart is a style of financial chart used to describe price
+ * movements over time. It displays open, high, low and close values per data
+ * point.
+ *
+ * @sample stock/demo/ohlc/ OHLC chart
+ * @extends {plotOptions.column}
+ * @excluding borderColor,borderRadius,borderWidth,crisp
+ * @product highstock
+ * @optionparent plotOptions.ohlc
+ */
+seriesType('ohlc', 'column', {
+
+ /**
+ * The approximate pixel width of each group. If for example a series
+ * with 30 points is displayed over a 600 pixel wide plot area, no grouping
+ * is performed. If however the series contains so many points that
+ * the spacing is less than the groupPixelWidth, Highcharts will try
+ * to group it into appropriate groups so that each is more or less
+ * two pixels wide. Defaults to `5`.
+ *
+ * @type {Number}
+ * @default 5
+ * @product highstock
+ * @apioption plotOptions.ohlc.dataGrouping.groupPixelWidth
+ */
+
+ /**
+ * The pixel width of the line/border. Defaults to `1`.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/plotoptions/ohlc-linewidth/
+ * A greater line width
+ * @default 1
+ * @product highstock
+ */
+ lineWidth: 1,
+
+ tooltip: {
+
+
+ pointFormat: '\u25CF {series.name} ' + // eslint-disable-line max-len
+ 'Open: {point.open} ' +
+ 'High: {point.high} ' +
+ 'Low: {point.low} ' +
+ 'Close: {point.close} '
+
+ },
+
+ threshold: null,
+
+
+ states: {
+
+ /**
+ * @extends plotOptions.column.states.hover
+ * @product highstock
+ */
+ hover: {
+
+ /**
+ * The pixel width of the line representing the OHLC point.
+ *
+ * @type {Number}
+ * @default 3
+ * @product highstock
+ */
+ lineWidth: 3
+ }
+ },
+
+
+ /**
+ * Line color for up points.
+ *
+ * @type {Color}
+ * @product highstock
+ * @apioption plotOptions.ohlc.upColor
+ */
+
+
+
+ stickyTracking: true
+
+}, /** @lends seriesTypes.ohlc */ {
+ directTouch: false,
+ pointArrayMap: ['open', 'high', 'low', 'close'],
+ toYData: function (point) { // return a plain array for speedy calculation
+ return [point.open, point.high, point.low, point.close];
+ },
+ pointValKey: 'close',
+
+
+ pointAttrToOptions: {
+ 'stroke': 'color',
+ 'stroke-width': 'lineWidth'
+ },
+
+ /**
+ * Postprocess mapping between options and SVG attributes
+ */
+ pointAttribs: function (point, state) {
+ var attribs = seriesTypes.column.prototype.pointAttribs.call(
+ this,
+ point,
+ state
+ ),
+ options = this.options;
+
+ delete attribs.fill;
+
+ if (
+ !point.options.color &&
+ options.upColor &&
+ point.open < point.close
+ ) {
+ attribs.stroke = options.upColor;
+ }
+
+ return attribs;
+ },
+
+
+ /**
+ * Translate data points from raw values x and y to plotX and plotY
+ */
+ translate: function () {
+ var series = this,
+ yAxis = series.yAxis,
+ hasModifyValue = !!series.modifyValue,
+ translated = [
+ 'plotOpen',
+ 'plotHigh',
+ 'plotLow',
+ 'plotClose',
+ 'yBottom'
+ ]; // translate OHLC for
+
+ seriesTypes.column.prototype.translate.apply(series);
+
+ // Do the translation
+ each(series.points, function (point) {
+ each(
+ [point.open, point.high, point.low, point.close, point.low],
+ function (value, i) {
+ if (value !== null) {
+ if (hasModifyValue) {
+ value = series.modifyValue(value);
+ }
+ point[translated[i]] = yAxis.toPixels(value, true);
+ }
+ }
+ );
+
+ // Align the tooltip to the high value to avoid covering the point
+ point.tooltipPos[1] =
+ point.plotHigh + yAxis.pos - series.chart.plotTop;
+ });
+ },
+
+ /**
+ * Draw the data points
+ */
+ drawPoints: function () {
+ var series = this,
+ points = series.points,
+ chart = series.chart;
+
+
+ each(points, function (point) {
+ var plotOpen,
+ plotClose,
+ crispCorr,
+ halfWidth,
+ path,
+ graphic = point.graphic,
+ crispX,
+ isNew = !graphic;
+
+ if (point.plotY !== undefined) {
+
+ // Create and/or update the graphic
+ if (!graphic) {
+ point.graphic = graphic = chart.renderer.path()
+ .add(series.group);
+ }
+
+
+ graphic.attr(
+ series.pointAttribs(point, point.selected && 'select')
+ ); // #3897
+
+
+ // crisp vector coordinates
+ crispCorr = (graphic.strokeWidth() % 2) / 2;
+ crispX = Math.round(point.plotX) - crispCorr; // #2596
+ halfWidth = Math.round(point.shapeArgs.width / 2);
+
+ // the vertical stem
+ path = [
+ 'M',
+ crispX, Math.round(point.yBottom),
+ 'L',
+ crispX, Math.round(point.plotHigh)
+ ];
+
+ // open
+ if (point.open !== null) {
+ plotOpen = Math.round(point.plotOpen) + crispCorr;
+ path.push(
+ 'M',
+ crispX,
+ plotOpen,
+ 'L',
+ crispX - halfWidth,
+ plotOpen
+ );
+ }
+
+ // close
+ if (point.close !== null) {
+ plotClose = Math.round(point.plotClose) + crispCorr;
+ path.push(
+ 'M',
+ crispX,
+ plotClose,
+ 'L',
+ crispX + halfWidth,
+ plotClose
+ );
+ }
+
+ graphic[isNew ? 'attr' : 'animate']({ d: path })
+ .addClass(point.getClassName(), true);
+
+ }
+
+
+ });
+
+ },
+
+ animate: null // Disable animation
+
+/**
+ * @constructor seriesTypes.ohlc.prototype.pointClass
+ * @extends {Point}
+ */
+}, /** @lends seriesTypes.ohlc.prototype.pointClass.prototype */ {
+ /**
+ * Extend the parent method by adding up or down to the class name.
+ */
+ getClassName: function () {
+ return Point.prototype.getClassName.call(this) +
+ (
+ this.open < this.close ?
+ ' highcharts-point-up' :
+ ' highcharts-point-down'
+ );
+ }
+});
+
+/**
+ * A `ohlc` series. If the [type](#series.ohlc.type) option is not
+ * specified, it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * ohlc](#plotOptions.ohlc).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.ohlc
+ * @excluding dataParser,dataURL
+ * @product highstock
+ * @apioption series.ohlc
+ */
+
+/**
+ * An array of data points for the series. For the `ohlc` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of arrays with 5 or 4 values. In this case, the values
+ * correspond to `x,open,high,low,close`. If the first value is a string,
+ * it is applied as the name of the point, and the `x` value is inferred.
+ * The `x` value can also be omitted, in which case the inner arrays
+ * should be of length 4\. Then the `x` value is automatically calculated,
+ * either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options.
+ *
+ * ```js
+ * data: [
+ * [0, 6, 5, 6, 7],
+ * [1, 9, 4, 8, 2],
+ * [2, 6, 3, 4, 10]
+ * ]
+ * ```
+ *
+ * 2. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.ohlc.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * open: 3,
+ * high: 4,
+ * low: 5,
+ * close: 2,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * open: 4,
+ * high: 3,
+ * low: 6,
+ * close: 7,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @extends series.arearange.data
+ * @excluding y,marker
+ * @product highstock
+ * @apioption series.ohlc.data
+ */
+
+/**
+ * The closing value of each data point.
+ *
+ * @type {Number}
+ * @product highstock
+ * @apioption series.ohlc.data.close
+ */
+
+/**
+ * The opening value of each data point.
+ *
+ * @type {Number}
+ * @product highstock
+ * @apioption series.ohlc.data.open
+ */
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var defaultPlotOptions = H.defaultPlotOptions,
+ each = H.each,
+ merge = H.merge,
+ seriesType = H.seriesType,
+ seriesTypes = H.seriesTypes;
+
+/**
+ * A candlestick chart is a style of financial chart used to describe price
+ * movements over time.
+ *
+ * @sample stock/demo/candlestick/ Candlestick chart
+ *
+ * @extends {plotOptions.ohlc}
+ * @excluding borderColor,borderRadius,borderWidth
+ * @product highstock
+ * @optionparent plotOptions.candlestick
+ */
+var candlestickOptions = {
+
+ states: {
+
+ /**
+ * @extends plotOptions.column.states.hover
+ * @product highstock
+ */
+ hover: {
+
+ /**
+ * The pixel width of the line/border around the candlestick.
+ *
+ * @type {Number}
+ * @default 2
+ * @product highstock
+ */
+ lineWidth: 2
+ }
+ },
+
+ /**
+ * @extends {plotOptions.ohlc.tooltip}
+ */
+ tooltip: defaultPlotOptions.ohlc.tooltip,
+
+ threshold: null,
+
+
+ /**
+ * The color of the line/border of the candlestick.
+ *
+ * In styled mode, the line stroke can be set with the `.highcharts-
+ * candlestick-series .highcahrts-point` rule.
+ *
+ * @type {Color}
+ * @see [upLineColor](#plotOptions.candlestick.upLineColor)
+ * @sample {highstock} stock/plotoptions/candlestick-linecolor/
+ * Candlestick line colors
+ * @default #000000
+ * @product highstock
+ */
+ lineColor: '#000000',
+
+ /**
+ * The pixel width of the candlestick line/border. Defaults to `1`.
+ *
+ *
+ * In styled mode, the line stroke width can be set with the `.
+ * highcharts-candlestick-series .highcahrts-point` rule.
+ *
+ * @type {Number}
+ * @default 1
+ * @product highstock
+ */
+ lineWidth: 1,
+
+ /**
+ * The fill color of the candlestick when values are rising.
+ *
+ * In styled mode, the up color can be set with the `.highcharts-
+ * candlestick-series .highcharts-point-up` rule.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/plotoptions/candlestick-color/ Custom colors
+ * @sample {highstock} highcharts/css/candlestick/ Colors in styled mode
+ * @default #ffffff
+ * @product highstock
+ */
+ upColor: '#ffffff',
+
+ stickyTracking: true
+
+ /**
+ * The specific line color for up candle sticks. The default is to inherit
+ * the general `lineColor` setting.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/plotoptions/candlestick-linecolor/ Candlestick line colors
+ * @default null
+ * @since 1.3.6
+ * @product highstock
+ * @apioption plotOptions.candlestick.upLineColor
+ */
+
+
+ /**
+ * @default ohlc
+ * @apioption plotOptions.candlestick.dataGrouping.approximation
+ */
+
+};
+
+/**
+ * The candlestick series type.
+ *
+ * @constructor seriesTypes.candlestick
+ * @augments seriesTypes.ohlc
+ */
+seriesType('candlestick', 'ohlc', merge(
+ defaultPlotOptions.column,
+ candlestickOptions
+), /** @lends seriesTypes.candlestick */ {
+
+ /**
+ * Postprocess mapping between options and SVG attributes
+ */
+ pointAttribs: function (point, state) {
+ var attribs = seriesTypes.column.prototype.pointAttribs.call(this, point, state),
+ options = this.options,
+ isUp = point.open < point.close,
+ stroke = options.lineColor || this.color,
+ stateOptions;
+
+ attribs['stroke-width'] = options.lineWidth;
+
+ attribs.fill = point.options.color || (isUp ? (options.upColor || this.color) : this.color);
+ attribs.stroke = point.lineColor || (isUp ? (options.upLineColor || stroke) : stroke);
+
+ // Select or hover states
+ if (state) {
+ stateOptions = options.states[state];
+ attribs.fill = stateOptions.color || attribs.fill;
+ attribs.stroke = stateOptions.lineColor || attribs.stroke;
+ attribs['stroke-width'] =
+ stateOptions.lineWidth || attribs['stroke-width'];
+ }
+
+
+ return attribs;
+ },
+
+ /**
+ * Draw the data points
+ */
+ drawPoints: function () {
+ var series = this,
+ points = series.points,
+ chart = series.chart;
+
+
+ each(points, function (point) {
+
+ var graphic = point.graphic,
+ plotOpen,
+ plotClose,
+ topBox,
+ bottomBox,
+ hasTopWhisker,
+ hasBottomWhisker,
+ crispCorr,
+ crispX,
+ path,
+ halfWidth,
+ isNew = !graphic;
+
+ if (point.plotY !== undefined) {
+
+ if (!graphic) {
+ point.graphic = graphic = chart.renderer.path()
+ .add(series.group);
+ }
+
+
+ graphic
+ .attr(series.pointAttribs(point, point.selected && 'select')) // #3897
+ .shadow(series.options.shadow);
+
+
+ // Crisp vector coordinates
+ crispCorr = (graphic.strokeWidth() % 2) / 2;
+ crispX = Math.round(point.plotX) - crispCorr; // #2596
+ plotOpen = point.plotOpen;
+ plotClose = point.plotClose;
+ topBox = Math.min(plotOpen, plotClose);
+ bottomBox = Math.max(plotOpen, plotClose);
+ halfWidth = Math.round(point.shapeArgs.width / 2);
+ hasTopWhisker = Math.round(topBox) !== Math.round(point.plotHigh);
+ hasBottomWhisker = bottomBox !== point.yBottom;
+ topBox = Math.round(topBox) + crispCorr;
+ bottomBox = Math.round(bottomBox) + crispCorr;
+
+ // Create the path. Due to a bug in Chrome 49, the path is first instanciated
+ // with no values, then the values pushed. For unknown reasons, instanciated
+ // the path array with all the values would lead to a crash when updating
+ // frequently (#5193).
+ path = [];
+ path.push(
+ 'M',
+ crispX - halfWidth, bottomBox,
+ 'L',
+ crispX - halfWidth, topBox,
+ 'L',
+ crispX + halfWidth, topBox,
+ 'L',
+ crispX + halfWidth, bottomBox,
+ 'Z', // Use a close statement to ensure a nice rectangle #2602
+ 'M',
+ crispX, topBox,
+ 'L',
+ crispX, hasTopWhisker ? Math.round(point.plotHigh) : topBox, // #460, #2094
+ 'M',
+ crispX, bottomBox,
+ 'L',
+ crispX, hasBottomWhisker ? Math.round(point.yBottom) : bottomBox // #460, #2094
+ );
+
+ graphic[isNew ? 'attr' : 'animate']({ d: path })
+ .addClass(point.getClassName(), true);
+
+ }
+ });
+
+ }
+
+
+});
+
+/**
+ * A `candlestick` series. If the [type](#series.candlestick.type)
+ * option is not specified, it is inherited from [chart.type](#chart.
+ * type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * candlestick](#plotOptions.candlestick).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.candlestick
+ * @excluding dataParser,dataURL
+ * @product highstock
+ * @apioption series.candlestick
+ */
+
+/**
+ * An array of data points for the series. For the `candlestick` series
+ * type, points can be given in the following ways:
+ *
+ * 1. An array of arrays with 5 or 4 values. In this case, the values
+ * correspond to `x,open,high,low,close`. If the first value is a string,
+ * it is applied as the name of the point, and the `x` value is inferred.
+ * The `x` value can also be omitted, in which case the inner arrays
+ * should be of length 4\. Then the `x` value is automatically calculated,
+ * either starting at 0 and incremented by 1, or from `pointStart`
+ * and `pointInterval` given in the series options.
+ *
+ * ```js
+ * data: [
+ * [0, 7, 2, 0, 4],
+ * [1, 1, 4, 2, 8],
+ * [2, 3, 3, 9, 3]
+ * ]
+ * ```
+ *
+ * 2. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.candlestick.
+ * turboThreshold), this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * open: 9,
+ * high: 2,
+ * low: 4,
+ * close: 6,
+ * name: "Point2",
+ * color: "#00FF00"
+ * }, {
+ * x: 1,
+ * open: 1,
+ * high: 4,
+ * low: 7,
+ * close: 7,
+ * name: "Point1",
+ * color: "#FF00FF"
+ * }]
+ * ```
+ *
+ * @type {Array}
+ * @extends series.ohlc.data
+ * @excluding y
+ * @product highstock
+ * @apioption series.candlestick.data
+ */
+
+}(Highcharts));
+var onSeriesMixin = (function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+
+var each = H.each,
+ seriesTypes = H.seriesTypes,
+ stableSort = H.stableSort;
+
+var onSeriesMixin = {
+ /**
+ * Extend the translate method by placing the point on the related series
+ */
+ translate: function () {
+
+ seriesTypes.column.prototype.translate.apply(this);
+
+ var series = this,
+ options = series.options,
+ chart = series.chart,
+ points = series.points,
+ cursor = points.length - 1,
+ point,
+ lastPoint,
+ optionsOnSeries = options.onSeries,
+ onSeries = optionsOnSeries && chart.get(optionsOnSeries),
+ onKey = options.onKey || 'y',
+ step = onSeries && onSeries.options.step,
+ onData = onSeries && onSeries.points,
+ i = onData && onData.length,
+ xAxis = series.xAxis,
+ yAxis = series.yAxis,
+ xAxisExt = xAxis.getExtremes(),
+ xOffset = 0,
+ leftPoint,
+ lastX,
+ rightPoint,
+ currentDataGrouping,
+ distanceRatio;
+
+ // relate to a master series
+ if (onSeries && onSeries.visible && i) {
+ xOffset = (onSeries.pointXOffset || 0) + (onSeries.barW || 0) / 2;
+ currentDataGrouping = onSeries.currentDataGrouping;
+ lastX = (
+ onData[i - 1].x +
+ (currentDataGrouping ? currentDataGrouping.totalRange : 0)
+ ); // #2374
+
+ // sort the data points
+ stableSort(points, function (a, b) {
+ return (a.x - b.x);
+ });
+
+ onKey = 'plot' + onKey[0].toUpperCase() + onKey.substr(1);
+ while (i-- && points[cursor]) {
+ leftPoint = onData[i];
+ point = points[cursor];
+ point.y = leftPoint.y;
+
+ if (leftPoint.x <= point.x && leftPoint[onKey] !== undefined) {
+ if (point.x <= lastX) { // #803
+
+ point.plotY = leftPoint[onKey];
+
+ // interpolate between points, #666
+ if (leftPoint.x < point.x && !step) {
+ rightPoint = onData[i + 1];
+ if (rightPoint && rightPoint[onKey] !== undefined) {
+ // the distance ratio, between 0 and 1
+ distanceRatio = (point.x - leftPoint.x) /
+ (rightPoint.x - leftPoint.x);
+ point.plotY +=
+ distanceRatio *
+ // the plotY distance
+ (rightPoint[onKey] - leftPoint[onKey]);
+ point.y +=
+ distanceRatio *
+ (rightPoint.y - leftPoint.y);
+ }
+ }
+ }
+ cursor--;
+ i++; // check again for points in the same x position
+ if (cursor < 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Add plotY position and handle stacking
+ each(points, function (point, i) {
+
+ var stackIndex;
+
+ // Undefined plotY means the point is either on axis, outside series
+ // range or hidden series. If the series is outside the range of the
+ // x axis it should fall through with an undefined plotY, but then
+ // we must remove the shapeArgs (#847).
+ if (point.plotY === undefined) {
+ if (point.x >= xAxisExt.min && point.x <= xAxisExt.max) {
+ // we're inside xAxis range
+ point.plotY = chart.chartHeight - xAxis.bottom -
+ (xAxis.opposite ? xAxis.height : 0) +
+ xAxis.offset - yAxis.top; // #3517
+ } else {
+ point.shapeArgs = {}; // 847
+ }
+ }
+ point.plotX += xOffset; // #2049
+ // if multiple flags appear at the same x, order them into a stack
+ lastPoint = points[i - 1];
+ if (lastPoint && lastPoint.plotX === point.plotX) {
+ if (lastPoint.stackIndex === undefined) {
+ lastPoint.stackIndex = 0;
+ }
+ stackIndex = lastPoint.stackIndex + 1;
+ }
+ point.stackIndex = stackIndex; // #3639
+ });
+
+
+ }
+};
+return onSeriesMixin;
+}(Highcharts));
+(function (H, onSeriesMixin) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ each = H.each,
+ merge = H.merge,
+ noop = H.noop,
+ Renderer = H.Renderer,
+ Series = H.Series,
+ seriesType = H.seriesType,
+ SVGRenderer = H.SVGRenderer,
+ TrackerMixin = H.TrackerMixin,
+ VMLRenderer = H.VMLRenderer,
+ symbols = SVGRenderer.prototype.symbols;
+
+/**
+ * The Flags series.
+ * @constructor seriesTypes.flags
+ * @augments seriesTypes.column
+ */
+/**
+ * Flags are used to mark events in stock charts. They can be added on the
+ * timeline, or attached to a specific series.
+ *
+ * @sample stock/demo/flags-general/ Flags on a line series
+ * @extends {plotOptions.column}
+ * @excluding animation,borderColor,borderRadius,borderWidth,colorByPoint,dataGrouping,pointPadding,pointWidth,turboThreshold
+ * @product highstock
+ * @optionparent plotOptions.flags
+ */
+seriesType('flags', 'column', {
+
+ /**
+ * In case the flag is placed on a series, on what point key to place
+ * it. Line and columns have one key, `y`. In range or OHLC-type series,
+ * however, the flag can optionally be placed on the `open`, `high`,
+ * `low` or `close` key.
+ *
+ * @validvalue ["y", "open", "high", "low", "close"]
+ * @type {String}
+ * @sample {highstock} stock/plotoptions/flags-onkey/ Range series, flag on high
+ * @default y
+ * @since 4.2.2
+ * @product highstock
+ * @apioption plotOptions.flags.onKey
+ */
+
+ /**
+ * The id of the series that the flags should be drawn on. If no id
+ * is given, the flags are drawn on the x axis.
+ *
+ * @type {String}
+ * @sample {highstock} stock/plotoptions/flags/ Flags on series and on x axis
+ * @default undefined
+ * @product highstock
+ * @apioption plotOptions.flags.onSeries
+ */
+
+ pointRange: 0, // #673
+
+ /**
+ * Whether the flags are allowed to overlap sideways. If `false`, the flags
+ * are moved sideways using an algorithm that seeks to place every flag as
+ * close as possible to its original position.
+ *
+ * @sample {highstock} stock/plotoptions/flags-allowoverlapx
+ * Allow sideways overlap
+ *
+ * @since 6.0.4
+ */
+ allowOverlapX: false,
+
+ /**
+ * The shape of the marker. Can be one of "flag", "circlepin", "squarepin",
+ * or an image on the format `url(/path-to-image.jpg)`. Individual
+ * shapes can also be set for each point.
+ *
+ * @validvalue ["flag", "circlepin", "squarepin"]
+ * @type {String}
+ * @sample {highstock} stock/plotoptions/flags/ Different shapes
+ * @default flag
+ * @product highstock
+ */
+ shape: 'flag',
+
+ /**
+ * When multiple flags in the same series fall on the same value, this
+ * number determines the vertical offset between them.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/plotoptions/flags-stackdistance/ A greater stack distance
+ * @default 12
+ * @product highstock
+ */
+ stackDistance: 12,
+
+ /**
+ * Text alignment for the text inside the flag.
+ *
+ * @validvalue ["left", "center", "right"]
+ * @type {String}
+ * @default center
+ * @since 5.0.0
+ * @product highstock
+ */
+ textAlign: 'center',
+
+ /**
+ * Specific tooltip options for flag series. Flag series tooltips are
+ * different from most other types in that a flag doesn't have a data
+ * value, so the tooltip rather displays the `text` option for each
+ * point.
+ *
+ * @type {Object}
+ * @extends plotOptions.series.tooltip
+ * @excluding changeDecimals,valueDecimals,valuePrefix,valueSuffix
+ * @product highstock
+ */
+ tooltip: {
+ pointFormat: '{point.text} '
+ },
+
+ threshold: null,
+
+ /**
+ * The text to display on each flag. This can be defined on series level,
+ * or individually for each point. Defaults to `"A"`.
+ *
+ * @type {String}
+ * @default A
+ * @product highstock
+ * @apioption plotOptions.flags.title
+ */
+
+ /**
+ * The y position of the top left corner of the flag relative to either
+ * the series (if onSeries is defined), or the x axis. Defaults to
+ * `-30`.
+ *
+ * @type {Number}
+ * @default -30
+ * @product highstock
+ */
+ y: -30,
+
+ /**
+ * Whether to use HTML to render the flag texts. Using HTML allows for
+ * advanced formatting, images and reliable bi-directional text rendering.
+ * Note that exported images won't respect the HTML, and that HTML
+ * won't respect Z-index settings.
+ *
+ * @type {Boolean}
+ * @default false
+ * @since 1.3
+ * @product highstock
+ * @apioption plotOptions.flags.useHTML
+ */
+
+
+
+ /**
+ * The fill color for the flags.
+ */
+ fillColor: '#ffffff',
+
+ /**
+ * The color of the line/border of the flag.
+ *
+ * In styled mode, the stroke is set in the `.highcharts-flag-series
+ * .highcharts-point` rule.
+ *
+ * @type {Color}
+ * @default #000000
+ * @product highstock
+ * @apioption plotOptions.flags.lineColor
+ */
+
+ /**
+ * The pixel width of the flag's line/border.
+ *
+ * @type {Number}
+ * @default 1
+ * @product highstock
+ */
+ lineWidth: 1,
+
+ states: {
+
+ /**
+ * @extends plotOptions.column.states.hover
+ * @product highstock
+ */
+ hover: {
+
+ /**
+ * The color of the line/border of the flag.
+ *
+ * @product highstock
+ */
+ lineColor: '#000000',
+
+ /**
+ * The fill or background color of the flag.
+ *
+ * @product highstock
+ */
+ fillColor: '#ccd6eb'
+ }
+ },
+
+ /**
+ * The text styles of the flag.
+ *
+ * In styled mode, the styles are set in the `.highcharts-flag-
+ * series .highcharts-point` rule.
+ *
+ * @type {CSSObject}
+ * @default { "fontSize": "11px", "fontWeight": "bold" }
+ * @product highstock
+ */
+ style: {
+ fontSize: '11px',
+ fontWeight: 'bold'
+ }
+
+
+}, /** @lends seriesTypes.flags.prototype */ {
+ sorted: false,
+ noSharedTooltip: true,
+ allowDG: false,
+ takeOrdinalPosition: false, // #1074
+ trackerGroups: ['markerGroup'],
+ forceCrop: true,
+ /**
+ * Inherit the initialization from base Series.
+ */
+ init: Series.prototype.init,
+
+
+ /**
+ * Get presentational attributes
+ */
+ pointAttribs: function (point, state) {
+ var options = this.options,
+ color = (point && point.color) || this.color,
+ lineColor = options.lineColor,
+ lineWidth = (point && point.lineWidth),
+ fill = (point && point.fillColor) || options.fillColor;
+
+ if (state) {
+ fill = options.states[state].fillColor;
+ lineColor = options.states[state].lineColor;
+ lineWidth = options.states[state].lineWidth;
+ }
+
+ return {
+ 'fill': fill || color,
+ 'stroke': lineColor || color,
+ 'stroke-width': lineWidth || options.lineWidth || 0
+ };
+ },
+
+
+ translate: onSeriesMixin.translate,
+
+ /**
+ * Draw the markers
+ */
+ drawPoints: function () {
+ var series = this,
+ points = series.points,
+ chart = series.chart,
+ renderer = chart.renderer,
+ plotX,
+ plotY,
+ options = series.options,
+ optionsY = options.y,
+ shape,
+ i,
+ point,
+ graphic,
+ stackIndex,
+ anchorY,
+ attribs,
+ outsideRight,
+ yAxis = series.yAxis,
+ boxesMap = {},
+ boxes = [];
+
+ i = points.length;
+ while (i--) {
+ point = points[i];
+ outsideRight = point.plotX > series.xAxis.len;
+ plotX = point.plotX;
+ stackIndex = point.stackIndex;
+ shape = point.options.shape || options.shape;
+ plotY = point.plotY;
+
+ if (plotY !== undefined) {
+ plotY = point.plotY + optionsY - (stackIndex !== undefined && stackIndex * options.stackDistance);
+ }
+ point.anchorX = stackIndex ? undefined : point.plotX; // skip connectors for higher level stacked points
+ anchorY = stackIndex ? undefined : point.plotY;
+
+ graphic = point.graphic;
+
+ // Only draw the point if y is defined and the flag is within the visible area
+ if (plotY !== undefined && plotX >= 0 && !outsideRight) {
+
+ // Create the flag
+ if (!graphic) {
+ graphic = point.graphic = renderer.label(
+ '',
+ null,
+ null,
+ shape,
+ null,
+ null,
+ options.useHTML
+ )
+
+ .attr(series.pointAttribs(point))
+ .css(merge(options.style, point.style))
+
+ .attr({
+ align: shape === 'flag' ? 'left' : 'center',
+ width: options.width,
+ height: options.height,
+ 'text-align': options.textAlign
+ })
+ .addClass('highcharts-point')
+ .add(series.markerGroup);
+
+ // Add reference to the point for tracker (#6303)
+ if (point.graphic.div) {
+ point.graphic.div.point = point;
+ }
+
+
+ graphic.shadow(options.shadow);
+
+ graphic.isNew = true;
+ }
+
+ if (plotX > 0) { // #3119
+ plotX -= graphic.strokeWidth() % 2; // #4285
+ }
+
+ // Plant the flag
+ attribs = {
+ y: plotY,
+ anchorY: anchorY
+ };
+ if (options.allowOverlapX) {
+ attribs.x = plotX;
+ attribs.anchorX = point.anchorX;
+ }
+ graphic.attr({
+ text: point.options.title || options.title || 'A'
+ })[graphic.isNew ? 'attr' : 'animate'](attribs);
+
+ // Rig for the distribute function
+ if (!options.allowOverlapX) {
+ if (!boxesMap[point.plotX]) {
+ boxesMap[point.plotX] = {
+ align: 0,
+ size: graphic.width,
+ target: plotX,
+ anchorX: plotX
+ };
+ } else {
+ boxesMap[point.plotX].size = Math.max(
+ boxesMap[point.plotX].size,
+ graphic.width
+ );
+ }
+ }
+
+ // Set the tooltip anchor position
+ point.tooltipPos = chart.inverted ?
+ [yAxis.len + yAxis.pos - chart.plotLeft - plotY, series.xAxis.len - plotX] :
+ [plotX, plotY + yAxis.pos - chart.plotTop]; // #6327
+
+ } else if (graphic) {
+ point.graphic = graphic.destroy();
+ }
+
+ }
+
+ // Handle X-dimension overlapping
+ if (!options.allowOverlapX) {
+ H.objectEach(boxesMap, function (box) {
+ box.plotX = box.anchorX;
+ boxes.push(box);
+ });
+
+ H.distribute(boxes, this.xAxis.len);
+
+ each(points, function (point) {
+ var box = point.graphic && boxesMap[point.plotX];
+ if (box) {
+ point.graphic[point.graphic.isNew ? 'attr' : 'animate']({
+ x: box.pos,
+ anchorX: point.anchorX
+ });
+ point.graphic.isNew = false;
+ }
+ });
+ }
+
+ // Might be a mix of SVG and HTML and we need events for both (#6303)
+ if (options.useHTML) {
+ H.wrap(series.markerGroup, 'on', function (proceed) {
+ return H.SVGElement.prototype.on.apply(
+ proceed.apply(this, [].slice.call(arguments, 1)), // for HTML
+ [].slice.call(arguments, 1)); // and for SVG
+ });
+ }
+
+ },
+
+ /**
+ * Extend the column trackers with listeners to expand and contract stacks
+ */
+ drawTracker: function () {
+ var series = this,
+ points = series.points;
+
+ TrackerMixin.drawTrackerPoint.apply(this);
+
+ // Bring each stacked flag up on mouse over, this allows readability of vertically
+ // stacked elements as well as tight points on the x axis. #1924.
+ each(points, function (point) {
+ var graphic = point.graphic;
+ if (graphic) {
+ addEvent(graphic.element, 'mouseover', function () {
+
+ // Raise this point
+ if (point.stackIndex > 0 && !point.raised) {
+ point._y = graphic.y;
+ graphic.attr({
+ y: point._y - 8
+ });
+ point.raised = true;
+ }
+
+ // Revert other raised points
+ each(points, function (otherPoint) {
+ if (otherPoint !== point && otherPoint.raised && otherPoint.graphic) {
+ otherPoint.graphic.attr({
+ y: otherPoint._y
+ });
+ otherPoint.raised = false;
+ }
+ });
+ });
+ }
+ });
+ },
+
+ animate: noop, // Disable animation
+ buildKDTree: noop,
+ setClip: noop
+
+});
+
+// create the flag icon with anchor
+symbols.flag = function (x, y, w, h, options) {
+ var anchorX = (options && options.anchorX) || x,
+ anchorY = (options && options.anchorY) || y;
+
+ return symbols.circle(anchorX - 1, anchorY - 1, 2, 2).concat(
+ [
+ 'M', anchorX, anchorY,
+ 'L', x, y + h,
+ x, y,
+ x + w, y,
+ x + w, y + h,
+ x, y + h,
+ 'Z'
+ ]
+ );
+};
+
/*
- Highstock JS v2.0.4 (2014-09-02)
+ * Create the circlepin and squarepin icons with anchor
+ */
+function createPinSymbol(shape) {
+ symbols[shape + 'pin'] = function (x, y, w, h, options) {
+
+ var anchorX = options && options.anchorX,
+ anchorY = options && options.anchorY,
+ path,
+ labelTopOrBottomY;
+
+ // For single-letter flags, make sure circular flags are not taller than their width
+ if (shape === 'circle' && h > w) {
+ x -= Math.round((h - w) / 2);
+ w = h;
+ }
+
+ path = symbols[shape](x, y, w, h);
+
+ if (anchorX && anchorY) {
+ // if the label is below the anchor, draw the connecting line from the top edge of the label
+ // otherwise start drawing from the bottom edge
+ labelTopOrBottomY = (y > anchorY) ? y : y + h;
+ path.push(
+ 'M',
+ shape === 'circle' ? path[1] - path[4] : path[1] + path[4] / 2,
+ labelTopOrBottomY,
+ 'L',
+ anchorX,
+ anchorY
+ );
+ path = path.concat(
+ symbols.circle(anchorX - 1, anchorY - 1, 2, 2)
+ );
+ }
+
+ return path;
+ };
+}
+createPinSymbol('circle');
+createPinSymbol('square');
+
+
+// The symbol callbacks are generated on the SVGRenderer object in all browsers. Even
+// VML browsers need this in order to generate shapes in export. Now share
+// them with the VMLRenderer.
+if (Renderer === VMLRenderer) {
+ each(['flag', 'circlepin', 'squarepin'], function (shape) {
+ VMLRenderer.prototype.symbols[shape] = symbols[shape];
+ });
+}
+
+
+/**
+ * A `flags` series. If the [type](#series.flags.type) option is not
+ * specified, it is inherited from [chart.type](#chart.type).
+ *
+ * For options that apply to multiple series, it is recommended to add
+ * them to the [plotOptions.series](#plotOptions.series) options structure.
+ * To apply to all series of this specific type, apply it to [plotOptions.
+ * flags](#plotOptions.flags).
+ *
+ * @type {Object}
+ * @extends series,plotOptions.flags
+ * @excluding dataParser,dataURL
+ * @product highstock
+ * @apioption series.flags
+ */
+
+/**
+ * An array of data points for the series. For the `flags` series type,
+ * points can be given in the following ways:
+ *
+ * 1. An array of objects with named values. The objects are point
+ * configuration objects as seen below. If the total number of data
+ * points exceeds the series' [turboThreshold](#series.flags.turboThreshold),
+ * this option is not available.
+ *
+ * ```js
+ * data: [{
+ * x: 1,
+ * title: "A",
+ * text: "First event"
+ * }, {
+ * x: 1,
+ * title: "B",
+ * text: "Second event"
+ * }]
+ *
+ * @type {Array}
+ * @extends series.line.data
+ * @excluding y,dataLabels,marker,name
+ * @product highstock
+ * @apioption series.flags.data
+ */
+
+/**
+ * The fill color of an individual flag. By default it inherits from
+ * the series color.
+ *
+ * @type {Color}
+ * @product highstock
+ * @apioption series.flags.data.fillColor
+ */
+
+/**
+ * The longer text to be shown in the flag's tooltip.
+ *
+ * @type {String}
+ * @product highstock
+ * @apioption series.flags.data.text
+ */
+
+/**
+ * The short text to be shown on the flag.
+ *
+ * @type {String}
+ * @product highstock
+ * @apioption series.flags.data.title
+ */
+
+
+}(Highcharts, onSeriesMixin));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ Axis = H.Axis,
+ correctFloat = H.correctFloat,
+ defaultOptions = H.defaultOptions,
+ defined = H.defined,
+ destroyObjectProperties = H.destroyObjectProperties,
+ each = H.each,
+ fireEvent = H.fireEvent,
+ hasTouch = H.hasTouch,
+ isTouchDevice = H.isTouchDevice,
+ merge = H.merge,
+ pick = H.pick,
+ removeEvent = H.removeEvent,
+ svg = H.svg,
+ wrap = H.wrap,
+ swapXY;
+
+/**
+ *
+ * The scrollbar is a means of panning over the X axis of a stock chart.
+ *
+ * In styled mode, all the presentational options for the
+ * scrollbar are replaced by the classes `.highcharts-scrollbar-thumb`,
+ * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
+ * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
+ *
+ * @product highstock
+ * @optionparent scrollbar
+ */
+var defaultScrollbarOptions = {
+
+ /**
+ * The height of the scrollbar. The height also applies to the width
+ * of the scroll arrows so that they are always squares. Defaults to
+ * 20 for touch devices and 14 for mouse devices.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/scrollbar/height/ A 30px scrollbar
+ * @product highstock
+ */
+ height: isTouchDevice ? 20 : 14,
+
+ /**
+ * The border rounding radius of the bar.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default 0
+ * @product highstock
+ */
+ barBorderRadius: 0,
+
+ /**
+ * The corner radius of the scrollbar buttons.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default 0
+ * @product highstock
+ */
+ buttonBorderRadius: 0,
+
+ /**
+ * Whether to redraw the main chart as the scrollbar or the navigator
+ * zoomed window is moved. Defaults to `true` for modern browsers and
+ * `false` for legacy IE browsers as well as mobile devices.
+ *
+ * @type {Boolean}
+ * @since 1.3
+ * @product highstock
+ */
+ liveRedraw: svg && !isTouchDevice,
+
+ /**
+ * The margin between the scrollbar and its axis when the scrollbar is
+ * applied directly to an axis.
+ */
+ margin: 10,
+
+ /**
+ * The minimum width of the scrollbar.
+ *
+ * @type {Number}
+ * @default 6
+ * @since 1.2.5
+ * @product highstock
+ */
+ minWidth: 6,
+
+ step: 0.2,
+
+ /**
+ * The z index of the scrollbar group.
+ */
+ zIndex: 3,
+
+
+ /**
+ * The background color of the scrollbar itself.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default #cccccc
+ * @product highstock
+ */
+ barBackgroundColor: '#cccccc',
+
+ /**
+ * The width of the bar's border.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default 1
+ * @product highstock
+ */
+ barBorderWidth: 1,
+
+ /**
+ * The color of the scrollbar's border.
+ *
+ * @type {Color}
+ * @default #cccccc
+ * @product highstock
+ */
+ barBorderColor: '#cccccc',
+
+ /**
+ * The color of the small arrow inside the scrollbar buttons.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default #333333
+ * @product highstock
+ */
+ buttonArrowColor: '#333333',
+
+ /**
+ * The color of scrollbar buttons.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default #e6e6e6
+ * @product highstock
+ */
+ buttonBackgroundColor: '#e6e6e6',
+
+ /**
+ * The color of the border of the scrollbar buttons.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default #cccccc
+ * @product highstock
+ */
+ buttonBorderColor: '#cccccc',
+
+ /**
+ * The border width of the scrollbar buttons.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default 1
+ * @product highstock
+ */
+ buttonBorderWidth: 1,
+
+ /**
+ * The color of the small rifles in the middle of the scrollbar.
+ *
+ * @type {Color}
+ * @default #333333
+ * @product highstock
+ */
+ rifleColor: '#333333',
+
+ /**
+ * The color of the track background.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default #f2f2f2
+ * @product highstock
+ */
+ trackBackgroundColor: '#f2f2f2',
+
+ /**
+ * The color of the border of the scrollbar track.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default #f2f2f2
+ * @product highstock
+ */
+ trackBorderColor: '#f2f2f2',
+
+ /**
+ * The width of the border of the scrollbar track.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/scrollbar/style/ Scrollbar styling
+ * @default 1
+ * @product highstock
+ */
+ trackBorderWidth: 1
+
+};
+
+defaultOptions.scrollbar = merge(true, defaultScrollbarOptions, defaultOptions.scrollbar);
+
+/**
+* When we have vertical scrollbar, rifles and arrow in buttons should be rotated.
+* The same method is used in Navigator's handles, to rotate them.
+* @param {Array} path - path to be rotated
+* @param {Boolean} vertical - if vertical scrollbar, swap x-y values
+*/
+H.swapXY = swapXY = function (path, vertical) {
+ var i,
+ len = path.length,
+ temp;
+
+ if (vertical) {
+ for (i = 0; i < len; i += 3) {
+ temp = path[i + 1];
+ path[i + 1] = path[i + 2];
+ path[i + 2] = temp;
+ }
+ }
+
+ return path;
+};
+
+/**
+ * A reusable scrollbar, internally used in Highstock's navigator and optionally
+ * on individual axes.
+ *
+ * @class
+ * @param {Object} renderer
+ * @param {Object} options
+ * @param {Object} chart
+ */
+function Scrollbar(renderer, options, chart) { // docs
+ this.init(renderer, options, chart);
+}
+
+Scrollbar.prototype = {
+
+ init: function (renderer, options, chart) {
+
+ this.scrollbarButtons = [];
+
+ this.renderer = renderer;
+
+ this.userOptions = options;
+ this.options = merge(defaultScrollbarOptions, options);
+
+ this.chart = chart;
+
+ this.size = pick(this.options.size, this.options.height); // backward compatibility
+
+ // Init
+ if (options.enabled) {
+ this.render();
+ this.initEvents();
+ this.addEvents();
+ }
+ },
+
+ /**
+ * Render scrollbar with all required items.
+ */
+ render: function () {
+ var scroller = this,
+ renderer = scroller.renderer,
+ options = scroller.options,
+ size = scroller.size,
+ group;
+
+ // Draw the scrollbar group
+ scroller.group = group = renderer.g('scrollbar').attr({
+ zIndex: options.zIndex,
+ translateY: -99999
+ }).add();
+
+ // Draw the scrollbar track:
+ scroller.track = renderer.rect()
+ .addClass('highcharts-scrollbar-track')
+ .attr({
+ x: 0,
+ r: options.trackBorderRadius || 0,
+ height: size,
+ width: size
+ }).add(group);
+
+
+ scroller.track.attr({
+ fill: options.trackBackgroundColor,
+ stroke: options.trackBorderColor,
+ 'stroke-width': options.trackBorderWidth
+ });
+
+ this.trackBorderWidth = scroller.track.strokeWidth();
+ scroller.track.attr({
+ y: -this.trackBorderWidth % 2 / 2
+ });
+
+
+ // Draw the scrollbar itself
+ scroller.scrollbarGroup = renderer.g().add(group);
+
+ scroller.scrollbar = renderer.rect()
+ .addClass('highcharts-scrollbar-thumb')
+ .attr({
+ height: size,
+ width: size,
+ r: options.barBorderRadius || 0
+ }).add(scroller.scrollbarGroup);
+
+ scroller.scrollbarRifles = renderer.path(
+ swapXY([
+ 'M',
+ -3, size / 4,
+ 'L',
+ -3, 2 * size / 3,
+ 'M',
+ 0, size / 4,
+ 'L',
+ 0, 2 * size / 3,
+ 'M',
+ 3, size / 4,
+ 'L',
+ 3, 2 * size / 3
+ ], options.vertical))
+ .addClass('highcharts-scrollbar-rifles')
+ .add(scroller.scrollbarGroup);
+
+
+ scroller.scrollbar.attr({
+ fill: options.barBackgroundColor,
+ stroke: options.barBorderColor,
+ 'stroke-width': options.barBorderWidth
+ });
+ scroller.scrollbarRifles.attr({
+ stroke: options.rifleColor,
+ 'stroke-width': 1
+ });
+
+ scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth();
+ scroller.scrollbarGroup.translate(
+ -scroller.scrollbarStrokeWidth % 2 / 2,
+ -scroller.scrollbarStrokeWidth % 2 / 2
+ );
+
+ // Draw the buttons:
+ scroller.drawScrollbarButton(0);
+ scroller.drawScrollbarButton(1);
+ },
+
+ /**
+ * Position the scrollbar, method called from a parent with defined dimensions
+ * @param {Number} x - x-position on the chart
+ * @param {Number} y - y-position on the chart
+ * @param {Number} width - width of the scrollbar
+ * @param {Number} height - height of the scorllbar
+ */
+ position: function (x, y, width, height) {
+ var scroller = this,
+ options = scroller.options,
+ vertical = options.vertical,
+ xOffset = height,
+ yOffset = 0,
+ method = scroller.rendered ? 'animate' : 'attr';
+
+ scroller.x = x;
+ scroller.y = y + this.trackBorderWidth;
+ scroller.width = width; // width with buttons
+ scroller.height = height;
+ scroller.xOffset = xOffset;
+ scroller.yOffset = yOffset;
+
+ // If Scrollbar is a vertical type, swap options:
+ if (vertical) {
+ scroller.width = scroller.yOffset = width = yOffset = scroller.size;
+ scroller.xOffset = xOffset = 0;
+ scroller.barWidth = height - width * 2; // width without buttons
+ scroller.x = x = x + scroller.options.margin;
+ } else {
+ scroller.height = scroller.xOffset = height = xOffset = scroller.size;
+ scroller.barWidth = width - height * 2; // width without buttons
+ scroller.y = scroller.y + scroller.options.margin;
+ }
+
+ // Set general position for a group:
+ scroller.group[method]({
+ translateX: x,
+ translateY: scroller.y
+ });
+
+ // Resize background/track:
+ scroller.track[method]({
+ width: width,
+ height: height
+ });
+
+ // Move right/bottom button ot it's place:
+ scroller.scrollbarButtons[1][method]({
+ translateX: vertical ? 0 : width - xOffset,
+ translateY: vertical ? height - yOffset : 0
+ });
+ },
+
+ /**
+ * Draw the scrollbar buttons with arrows
+ * @param {Number} index 0 is left, 1 is right
+ */
+ drawScrollbarButton: function (index) {
+ var scroller = this,
+ renderer = scroller.renderer,
+ scrollbarButtons = scroller.scrollbarButtons,
+ options = scroller.options,
+ size = scroller.size,
+ group,
+ tempElem;
+
+ group = renderer.g().add(scroller.group);
+ scrollbarButtons.push(group);
+
+ // Create a rectangle for the scrollbar button
+ tempElem = renderer.rect()
+ .addClass('highcharts-scrollbar-button')
+ .add(group);
+
+
+ // Presentational attributes
+ tempElem.attr({
+ stroke: options.buttonBorderColor,
+ 'stroke-width': options.buttonBorderWidth,
+ fill: options.buttonBackgroundColor
+ });
+
+
+ // Place the rectangle based on the rendered stroke width
+ tempElem.attr(tempElem.crisp({
+ x: -0.5,
+ y: -0.5,
+ width: size + 1, // +1 to compensate for crispifying in rect method
+ height: size + 1,
+ r: options.buttonBorderRadius
+ }, tempElem.strokeWidth()));
+
+ // Button arrow
+ tempElem = renderer
+ .path(swapXY([
+ 'M',
+ size / 2 + (index ? -1 : 1),
+ size / 2 - 3,
+ 'L',
+ size / 2 + (index ? -1 : 1),
+ size / 2 + 3,
+ 'L',
+ size / 2 + (index ? 2 : -2),
+ size / 2
+ ], options.vertical))
+ .addClass('highcharts-scrollbar-arrow')
+ .add(scrollbarButtons[index]);
+
+
+ tempElem.attr({
+ fill: options.buttonArrowColor
+ });
+
+ },
+
+ /**
+ * Set scrollbar size, with a given scale.
+ * @param {Number} from - scale (0-1) where bar should start
+ * @param {Number} to - scale (0-1) where bar should end
+ */
+ setRange: function (from, to) {
+ var scroller = this,
+ options = scroller.options,
+ vertical = options.vertical,
+ minWidth = options.minWidth,
+ fullWidth = scroller.barWidth,
+ fromPX,
+ toPX,
+ newPos,
+ newSize,
+ newRiflesPos,
+ method = this.rendered && !this.hasDragged ? 'animate' : 'attr';
+
+ if (!defined(fullWidth)) {
+ return;
+ }
+
+ from = Math.max(from, 0);
+ fromPX = Math.ceil(fullWidth * from);
+ toPX = fullWidth * Math.min(to, 1);
+ scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX);
+
+ // We need to recalculate position, if minWidth is used
+ if (newSize < minWidth) {
+ fromPX = (fullWidth - minWidth + newSize) * from;
+ newSize = minWidth;
+ }
+ newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset);
+ newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2
+
+ // Store current position:
+ scroller.from = from;
+ scroller.to = to;
+
+ if (!vertical) {
+ scroller.scrollbarGroup[method]({
+ translateX: newPos
+ });
+ scroller.scrollbar[method]({
+ width: newSize
+ });
+ scroller.scrollbarRifles[method]({
+ translateX: newRiflesPos
+ });
+ scroller.scrollbarLeft = newPos;
+ scroller.scrollbarTop = 0;
+ } else {
+ scroller.scrollbarGroup[method]({
+ translateY: newPos
+ });
+ scroller.scrollbar[method]({
+ height: newSize
+ });
+ scroller.scrollbarRifles[method]({
+ translateY: newRiflesPos
+ });
+ scroller.scrollbarTop = newPos;
+ scroller.scrollbarLeft = 0;
+ }
+
+ if (newSize <= 12) {
+ scroller.scrollbarRifles.hide();
+ } else {
+ scroller.scrollbarRifles.show(true);
+ }
+
+ // Show or hide the scrollbar based on the showFull setting
+ if (options.showFull === false) {
+ if (from <= 0 && to >= 1) {
+ scroller.group.hide();
+ } else {
+ scroller.group.show();
+ }
+ }
+
+ scroller.rendered = true;
+ },
+
+ /**
+ * Init events methods, so we have an access to the Scrollbar itself
+ */
+ initEvents: function () {
+ var scroller = this;
+ /**
+ * Event handler for the mouse move event.
+ */
+ scroller.mouseMoveHandler = function (e) {
+ var normalizedEvent = scroller.chart.pointer.normalize(e),
+ options = scroller.options,
+ direction = options.vertical ? 'chartY' : 'chartX',
+ initPositions = scroller.initPositions,
+ scrollPosition,
+ chartPosition,
+ change;
+
+ // In iOS, a mousemove event with e.pageX === 0 is fired when holding the finger
+ // down in the center of the scrollbar. This should be ignored.
+ if (scroller.grabbedCenter && (!e.touches || e.touches[0][direction] !== 0)) { // #4696, scrollbar failed on Android
+ chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction];
+ scrollPosition = scroller[direction];
+
+ change = chartPosition - scrollPosition;
+
+ scroller.hasDragged = true;
+ scroller.updatePosition(initPositions[0] + change, initPositions[1] + change);
+
+ if (scroller.hasDragged) {
+ fireEvent(scroller, 'changed', {
+ from: scroller.from,
+ to: scroller.to,
+ trigger: 'scrollbar',
+ DOMType: e.type,
+ DOMEvent: e
+ });
+ }
+ }
+ };
+
+ /**
+ * Event handler for the mouse up event.
+ */
+ scroller.mouseUpHandler = function (e) {
+ if (scroller.hasDragged) {
+ fireEvent(scroller, 'changed', {
+ from: scroller.from,
+ to: scroller.to,
+ trigger: 'scrollbar',
+ DOMType: e.type,
+ DOMEvent: e
+ });
+ }
+ scroller.grabbedCenter = scroller.hasDragged = scroller.chartX = scroller.chartY = null;
+ };
+
+ scroller.mouseDownHandler = function (e) {
+ var normalizedEvent = scroller.chart.pointer.normalize(e),
+ mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent);
+
+ scroller.chartX = mousePosition.chartX;
+ scroller.chartY = mousePosition.chartY;
+ scroller.initPositions = [scroller.from, scroller.to];
+
+ scroller.grabbedCenter = true;
+ };
+
+ scroller.buttonToMinClick = function (e) {
+ var range = correctFloat(scroller.to - scroller.from) * scroller.options.step;
+ scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range));
+ fireEvent(scroller, 'changed', {
+ from: scroller.from,
+ to: scroller.to,
+ trigger: 'scrollbar',
+ DOMEvent: e
+ });
+ };
+
+ scroller.buttonToMaxClick = function (e) {
+ var range = (scroller.to - scroller.from) * scroller.options.step;
+ scroller.updatePosition(scroller.from + range, scroller.to + range);
+ fireEvent(scroller, 'changed', {
+ from: scroller.from,
+ to: scroller.to,
+ trigger: 'scrollbar',
+ DOMEvent: e
+ });
+ };
+
+ scroller.trackClick = function (e) {
+ var normalizedEvent = scroller.chart.pointer.normalize(e),
+ range = scroller.to - scroller.from,
+ top = scroller.y + scroller.scrollbarTop,
+ left = scroller.x + scroller.scrollbarLeft;
+
+ if ((scroller.options.vertical && normalizedEvent.chartY > top) ||
+ (!scroller.options.vertical && normalizedEvent.chartX > left)) {
+ // On the top or on the left side of the track:
+ scroller.updatePosition(scroller.from + range, scroller.to + range);
+ } else {
+ // On the bottom or the right side of the track:
+ scroller.updatePosition(scroller.from - range, scroller.to - range);
+ }
+
+ fireEvent(scroller, 'changed', {
+ from: scroller.from,
+ to: scroller.to,
+ trigger: 'scrollbar',
+ DOMEvent: e
+ });
+ };
+ },
+
+ /**
+ * Get normalized (0-1) cursor position over the scrollbar
+ * @param {Event} normalizedEvent - normalized event, with chartX and chartY values
+ * @return {Object} Local position {chartX, chartY}
+ */
+ cursorToScrollbarPosition: function (normalizedEvent) {
+ var scroller = this,
+ options = scroller.options,
+ minWidthDifference = options.minWidth > scroller.calculatedWidth ? options.minWidth : 0; // minWidth distorts translation
+
+ return {
+ chartX: (normalizedEvent.chartX - scroller.x - scroller.xOffset) / (scroller.barWidth - minWidthDifference),
+ chartY: (normalizedEvent.chartY - scroller.y - scroller.yOffset) / (scroller.barWidth - minWidthDifference)
+ };
+ },
+
+ /**
+ * Update position option in the Scrollbar, with normalized 0-1 scale
+ */
+ updatePosition: function (from, to) {
+ if (to > 1) {
+ from = correctFloat(1 - correctFloat(to - from));
+ to = 1;
+ }
+
+ if (from < 0) {
+ to = correctFloat(to - from);
+ from = 0;
+ }
+
+ this.from = from;
+ this.to = to;
+ },
+
+ /**
+ * Update the scrollbar with new options
+ */
+ update: function (options) {
+ this.destroy();
+ this.init(this.chart.renderer, merge(true, this.options, options), this.chart);
+ },
+
+ /**
+ * Set up the mouse and touch events for the Scrollbar
+ */
+ addEvents: function () {
+ var buttonsOrder = this.options.inverted ? [1, 0] : [0, 1],
+ buttons = this.scrollbarButtons,
+ bar = this.scrollbarGroup.element,
+ track = this.track.element,
+ mouseDownHandler = this.mouseDownHandler,
+ mouseMoveHandler = this.mouseMoveHandler,
+ mouseUpHandler = this.mouseUpHandler,
+ _events;
+
+ // Mouse events
+ _events = [
+ [buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick],
+ [buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick],
+ [track, 'click', this.trackClick],
+ [bar, 'mousedown', mouseDownHandler],
+ [bar.ownerDocument, 'mousemove', mouseMoveHandler],
+ [bar.ownerDocument, 'mouseup', mouseUpHandler]
+ ];
+
+ // Touch events
+ if (hasTouch) {
+ _events.push(
+ [bar, 'touchstart', mouseDownHandler],
+ [bar.ownerDocument, 'touchmove', mouseMoveHandler],
+ [bar.ownerDocument, 'touchend', mouseUpHandler]
+ );
+ }
+
+ // Add them all
+ each(_events, function (args) {
+ addEvent.apply(null, args);
+ });
+ this._events = _events;
+ },
+
+ /**
+ * Removes the event handlers attached previously with addEvents.
+ */
+ removeEvents: function () {
+ each(this._events, function (args) {
+ removeEvent.apply(null, args);
+ });
+ this._events.length = 0;
+ },
+
+ /**
+ * Destroys allocated elements.
+ */
+ destroy: function () {
+
+ var scroller = this.chart.scroller;
+
+ // Disconnect events added in addEvents
+ this.removeEvents();
+
+ // Destroy properties
+ each(['track', 'scrollbarRifles', 'scrollbar', 'scrollbarGroup', 'group'], function (prop) {
+ if (this[prop] && this[prop].destroy) {
+ this[prop] = this[prop].destroy();
+ }
+ }, this);
+
+ if (scroller && this === scroller.scrollbar) { // #6421, chart may have more scrollbars
+ scroller.scrollbar = null;
+
+ // Destroy elements in collection
+ destroyObjectProperties(scroller.scrollbarButtons);
+ }
+ }
+};
+
+/**
+* Wrap axis initialization and create scrollbar if enabled:
+*/
+wrap(Axis.prototype, 'init', function (proceed) {
+ var axis = this;
+ proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
+
+ if (axis.options.scrollbar && axis.options.scrollbar.enabled) {
+ // Predefined options:
+ axis.options.scrollbar.vertical = !axis.horiz;
+ axis.options.startOnTick = axis.options.endOnTick = false;
+
+ axis.scrollbar = new Scrollbar(axis.chart.renderer, axis.options.scrollbar, axis.chart);
+
+ addEvent(axis.scrollbar, 'changed', function (e) {
+ var unitedMin = Math.min(pick(axis.options.min, axis.min), axis.min, axis.dataMin),
+ unitedMax = Math.max(pick(axis.options.max, axis.max), axis.max, axis.dataMax),
+ range = unitedMax - unitedMin,
+ to,
+ from;
+
+ if ((axis.horiz && !axis.reversed) || (!axis.horiz && axis.reversed)) {
+ to = unitedMin + range * this.to;
+ from = unitedMin + range * this.from;
+ } else {
+ // y-values in browser are reversed, but this also applies for reversed horizontal axis:
+ to = unitedMin + range * (1 - this.from);
+ from = unitedMin + range * (1 - this.to);
+ }
+
+ axis.setExtremes(from, to, true, false, e);
+ });
+ }
+});
+
+/**
+* Wrap rendering axis, and update scrollbar if one is created:
+*/
+wrap(Axis.prototype, 'render', function (proceed) {
+ var axis = this,
+ scrollMin = Math.min(
+ pick(axis.options.min, axis.min),
+ axis.min,
+ pick(axis.dataMin, axis.min) // #6930
+ ),
+ scrollMax = Math.max(
+ pick(axis.options.max, axis.max),
+ axis.max,
+ pick(axis.dataMax, axis.max) // #6930
+ ),
+ scrollbar = axis.scrollbar,
+ titleOffset = axis.titleOffset || 0,
+ offsetsIndex,
+ from,
+ to;
+
+ proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
+
+ if (scrollbar) {
+
+ if (axis.horiz) {
+ scrollbar.position(
+ axis.left,
+ axis.top + axis.height + 2 + axis.chart.scrollbarsOffsets[1] +
+ (axis.opposite ?
+ 0 :
+ titleOffset + axis.axisTitleMargin + axis.offset
+ ),
+ axis.width,
+ axis.height
+ );
+ offsetsIndex = 1;
+ } else {
+ scrollbar.position(
+ axis.left + axis.width + 2 + axis.chart.scrollbarsOffsets[0] +
+ (axis.opposite ?
+ titleOffset + axis.axisTitleMargin + axis.offset :
+ 0
+ ),
+ axis.top,
+ axis.width,
+ axis.height
+ );
+ offsetsIndex = 0;
+ }
+
+ if ((!axis.opposite && !axis.horiz) || (axis.opposite && axis.horiz)) {
+ axis.chart.scrollbarsOffsets[offsetsIndex] +=
+ axis.scrollbar.size + axis.scrollbar.options.margin;
+ }
+
+ if (isNaN(scrollMin) || isNaN(scrollMax) || !defined(axis.min) || !defined(axis.max)) {
+ scrollbar.setRange(0, 0); // default action: when there is not extremes on the axis, but scrollbar exists, make it full size
+ } else {
+ from = (axis.min - scrollMin) / (scrollMax - scrollMin);
+ to = (axis.max - scrollMin) / (scrollMax - scrollMin);
+
+ if ((axis.horiz && !axis.reversed) || (!axis.horiz && axis.reversed)) {
+ scrollbar.setRange(from, to);
+ } else {
+ scrollbar.setRange(1 - to, 1 - from); // inverse vertical axis
+ }
+ }
+ }
+});
+
+/**
+* Make space for a scrollbar
+*/
+wrap(Axis.prototype, 'getOffset', function (proceed) {
+ var axis = this,
+ index = axis.horiz ? 2 : 1,
+ scrollbar = axis.scrollbar;
+
+ proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
+
+ if (scrollbar) {
+ axis.chart.scrollbarsOffsets = [0, 0]; // reset scrollbars offsets
+ axis.chart.axisOffset[index] += scrollbar.size + scrollbar.options.margin;
+ }
+});
+
+/**
+* Destroy scrollbar when connected to the specific axis
+*/
+wrap(Axis.prototype, 'destroy', function (proceed) {
+ if (this.scrollbar) {
+ this.scrollbar = this.scrollbar.destroy();
+ }
+
+ proceed.apply(this, Array.prototype.slice.call(arguments, 1));
+});
+
+H.Scrollbar = Scrollbar;
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+/* eslint max-len: ["warn", 80, 4] */
+
+/**
+ * Options for the corresponding navigator series if `showInNavigator`
+ * is `true` for this series. Available options are the same as any
+ * series, documented at [plotOptions](#plotOptions.series) and
+ * [series](#series).
+ *
+ *
+ * These options are merged with options in [navigator.series](#navigator.
+ * series), and will take precedence if the same option is defined both
+ * places.
+ *
+ * @type {Object}
+ * @see [navigator.series](#navigator.series)
+ * @default undefined
+ * @since 5.0.0
+ * @product highstock
+ * @apioption plotOptions.series.navigatorOptions
+ */
+
+/**
+ * Whether or not to show the series in the navigator. Takes precedence
+ * over [navigator.baseSeries](#navigator.baseSeries) if defined.
+ *
+ * @type {Boolean}
+ * @default undefined
+ * @since 5.0.0
+ * @product highstock
+ * @apioption plotOptions.series.showInNavigator
+ */
+
+var addEvent = H.addEvent,
+ Axis = H.Axis,
+ Chart = H.Chart,
+ color = H.color,
+ defaultDataGroupingUnits = H.defaultDataGroupingUnits,
+ defaultOptions = H.defaultOptions,
+ defined = H.defined,
+ destroyObjectProperties = H.destroyObjectProperties,
+ each = H.each,
+ erase = H.erase,
+ error = H.error,
+ extend = H.extend,
+ grep = H.grep,
+ hasTouch = H.hasTouch,
+ isArray = H.isArray,
+ isNumber = H.isNumber,
+ isObject = H.isObject,
+ merge = H.merge,
+ pick = H.pick,
+ removeEvent = H.removeEvent,
+ Scrollbar = H.Scrollbar,
+ Series = H.Series,
+ seriesTypes = H.seriesTypes,
+ wrap = H.wrap,
+
+ units = [].concat(defaultDataGroupingUnits), // copy
+ defaultSeriesType,
+
+ // Finding the min or max of a set of variables where we don't know if they
+ // are defined, is a pattern that is repeated several places in Highcharts.
+ // Consider making this a global utility method.
+ numExt = function (extreme) {
+ var numbers = grep(arguments, isNumber);
+ if (numbers.length) {
+ return Math[extreme].apply(0, numbers);
+ }
+ };
+
+// add more resolution to units
+units[4] = ['day', [1, 2, 3, 4]]; // allow more days
+units[5] = ['week', [1, 2, 3]]; // allow more weeks
+
+defaultSeriesType = seriesTypes.areaspline === undefined ?
+ 'line' :
+ 'areaspline';
+
+extend(defaultOptions, {
+
+ /**
+ * The navigator is a small series below the main series, displaying
+ * a view of the entire data set. It provides tools to zoom in and
+ * out on parts of the data as well as panning across the dataset.
+ *
+ * @product highstock
+ * @optionparent navigator
+ */
+ navigator: {
+ /**
+ * The height of the navigator.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/navigator/height/ A higher navigator
+ * @default 40
+ * @product highstock
+ */
+ height: 40,
+
+ /**
+ * The distance from the nearest element, the X axis or X axis labels.
+ *
+ * @type {Number}
+ * @sample {highstock} stock/navigator/margin/
+ * A margin of 2 draws the navigator closer to the X axis labels
+ * @default 25
+ * @product highstock
+ */
+ margin: 25,
+
+ /**
+ * Whether the mask should be inside the range marking the zoomed
+ * range, or outside. In Highstock 1.x it was always `false`.
+ *
+ * @type {Boolean}
+ * @sample {highstock} stock/navigator/maskinside-false/
+ * False, mask outside
+ * @default true
+ * @since 2.0
+ * @product highstock
+ */
+ maskInside: true,
+
+ /**
+ * Options for the handles for dragging the zoomed area.
+ *
+ * @type {Object}
+ * @sample {highstock} stock/navigator/handles/ Colored handles
+ * @product highstock
+ */
+ handles: {
+ /**
+ * Width for handles.
+ *
+ * @type {Number}
+ * @default 7
+ * @product highstock
+ * @sample {highstock} stock/navigator/styled-handles/
+ * Styled handles
+ * @since 6.0.0
+ */
+ width: 7,
+
+ /**
+ * Height for handles.
+ *
+ * @type {Number}
+ * @default 15
+ * @product highstock
+ * @sample {highstock} stock/navigator/styled-handles/
+ * Styled handles
+ * @since 6.0.0
+ */
+ height: 15,
+
+ /**
+ * Array to define shapes of handles. 0-index for left, 1-index for
+ * right.
+ *
+ * Additionally, the URL to a graphic can be given on this form:
+ * `url(graphic.png)`. Note that for the image to be applied to
+ * exported charts, its URL needs to be accessible by the export
+ * server.
+ *
+ * Custom callbacks for symbol path generation can also be added to
+ * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
+ * used by its method name, as shown in the demo.
+ *
+ * @type {Array}
+ * @default ['navigator-handle', 'navigator-handle']
+ * @product highstock
+ * @sample {highstock} stock/navigator/styled-handles/
+ * Styled handles
+ * @since 6.0.0
+ */
+ symbols: ['navigator-handle', 'navigator-handle'],
+
+ /**
+ * Allows to enable/disable handles.
+ *
+ * @type {Boolean}
+ * @default true
+ * @product highstock
+ * @since 6.0.0
+ */
+ enabled: true,
+
+
+ /**
+ * The width for the handle border and the stripes inside.
+ *
+ * @type {Number}
+ * @default 7
+ * @product highstock
+ * @sample {highstock} stock/navigator/styled-handles/
+ * Styled handles
+ * @since 6.0.0
+ */
+ lineWidth: 1,
+
+ /**
+ * The fill for the handle.
+ *
+ * @type {Color}
+ * @product highstock
+ */
+ backgroundColor: '#f2f2f2',
+
+ /**
+ * The stroke for the handle border and the stripes inside.
+ *
+ * @type {Color}
+ * @product highstock
+ */
+ borderColor: '#999999'
+
+
+ },
+
+
+
+ /**
+ * The color of the mask covering the areas of the navigator series
+ * that are currently not visible in the main series. The default
+ * color is bluish with an opacity of 0.3 to see the series below.
+ *
+ * @type {Color}
+ * @see In styled mode, the mask is styled with the
+ * `.highcharts-navigator-mask` and
+ * `.highcharts-navigator-mask-inside` classes.
+ * @sample {highstock} stock/navigator/maskfill/
+ * Blue, semi transparent mask
+ * @default rgba(102,133,194,0.3)
+ * @product highstock
+ */
+ maskFill: color('#6685c2').setOpacity(0.3).get(),
+
+ /**
+ * The color of the line marking the currently zoomed area in the
+ * navigator.
+ *
+ * @type {Color}
+ * @sample {highstock} stock/navigator/outline/ 2px blue outline
+ * @default #cccccc
+ * @product highstock
+ */
+ outlineColor: '#cccccc',
+
+ /**
+ * The width of the line marking the currently zoomed area in the
+ * navigator.
+ *
+ * @type {Number}
+ * @see In styled mode, the outline stroke width is set with the `.
+ * highcharts-navigator-outline` class.
+ * @sample {highstock} stock/navigator/outline/ 2px blue outline
+ * @default 2
+ * @product highstock
+ */
+ outlineWidth: 1,
+
+
+ /**
+ * Options for the navigator series. Available options are the same
+ * as any series, documented at [plotOptions](#plotOptions.series)
+ * and [series](#series).
+ *
+ * Unless data is explicitly defined on navigator.series, the data
+ * is borrowed from the first series in the chart.
+ *
+ * Default series options for the navigator series are:
+ *
+ * series: {
+ * type: 'areaspline',
+ * fillOpacity: 0.05,
+ * dataGrouping: {
+ * smoothed: true
+ * },
+ * lineWidth: 1,
+ * marker: {
+ * enabled: false
+ * }
+ * }
+ *
+ * @type {Object}
+ * @see In styled mode, the navigator series is styled with the `.
+ * highcharts-navigator-series` class.
+ * @sample {highstock} stock/navigator/series-data/
+ * Using a separate data set for the navigator
+ * @sample {highstock} stock/navigator/series/
+ * A green navigator series
+ * @product highstock
+ */
+ series: {
+
+ /**
+ * The type of the navigator series. Defaults to `areaspline` if
+ * defined, otherwise `line`.
+ *
+ * @type {String}
+ */
+ type: defaultSeriesType,
+
+
+
+ /**
+ * The fill opacity of the navigator series.
+ */
+ fillOpacity: 0.05,
+
+ /**
+ * The pixel line width of the navigator series.
+ */
+ lineWidth: 1,
+
+
+ /**
+ * @ignore
+ */
+ compare: null,
+
+ /**
+ * Data grouping options for the navigator series.
+ *
+ * @extends {plotOptions.series.dataGrouping}
+ */
+ dataGrouping: {
+ approximation: 'average',
+ enabled: true,
+ groupPixelWidth: 2,
+ smoothed: true,
+ units: units
+ },
+
+ /**
+ * Data label options for the navigator series. Data labels are
+ * disabled by default on the navigator series.
+ *
+ * @extends {plotOptions.series.dataLabels}
+ */
+ dataLabels: {
+ enabled: false,
+ zIndex: 2 // #1839
+ },
+
+ id: 'highcharts-navigator-series',
+ className: 'highcharts-navigator-series',
+
+ /**
+ * Line color for the navigator series. Allows setting the color
+ * while disallowing the default candlestick setting.
+ *
+ * @type {Color}
+ */
+ lineColor: null, // #4602
+
+ marker: {
+ enabled: false
+ },
+
+ pointRange: 0,
+ /**
+ * The threshold option. Setting it to 0 will make the default
+ * navigator area series draw its area from the 0 value and up.
+ * @type {Number}
+ */
+ threshold: null
+ },
+
+ /**
+ * Options for the navigator X axis. Default series options
+ * for the navigator xAxis are:
+ *
+ * xAxis: {
+ * tickWidth: 0,
+ * lineWidth: 0,
+ * gridLineWidth: 1,
+ * tickPixelInterval: 200,
+ * labels: {
+ * align: 'left',
+ * style: {
+ * color: '#888'
+ * },
+ * x: 3,
+ * y: -4
+ * }
+ * }
+ *
+ * @type {Object}
+ * @extends {xAxis}
+ * @excluding linkedTo,maxZoom,minRange,opposite,range,scrollbar,
+ * showEmpty,maxRange
+ * @product highstock
+ */
+ xAxis: {
+ /**
+ * Additional range on the right side of the xAxis. Works similar to
+ * xAxis.maxPadding, but value is set in milliseconds.
+ * Can be set for both, main xAxis and navigator's xAxis.
+ *
+ * @type {Number}
+ * @default 0
+ * @since 6.0.0
+ * @product highstock
+ * @apioption xAxis.overscroll
+ */
+ overscroll: 0,
+
+ className: 'highcharts-navigator-xaxis',
+ tickLength: 0,
+
+
+ lineWidth: 0,
+ gridLineColor: '#e6e6e6',
+ gridLineWidth: 1,
+
+
+ tickPixelInterval: 200,
+
+ labels: {
+ align: 'left',
+
+
+ style: {
+ color: '#999999'
+ },
+
+
+ x: 3,
+ y: -4
+ },
+
+ crosshair: false
+ },
+
+ /**
+ * Options for the navigator Y axis. Default series options
+ * for the navigator yAxis are:
+ *
+ * yAxis: {
+ * gridLineWidth: 0,
+ * startOnTick: false,
+ * endOnTick: false,
+ * minPadding: 0.1,
+ * maxPadding: 0.1,
+ * labels: {
+ * enabled: false
+ * },
+ * title: {
+ * text: null
+ * },
+ * tickWidth: 0
+ * }
+ *
+ * @type {Object}
+ * @extends {yAxis}
+ * @excluding height,linkedTo,maxZoom,minRange,ordinal,range,showEmpty,
+ * scrollbar,top,units,maxRange
+ * @product highstock
+ */
+ yAxis: {
+
+ className: 'highcharts-navigator-yaxis',
+
+
+ gridLineWidth: 0,
+
+
+ startOnTick: false,
+ endOnTick: false,
+ minPadding: 0.1,
+ maxPadding: 0.1,
+ labels: {
+ enabled: false
+ },
+ crosshair: false,
+ title: {
+ text: null
+ },
+ tickLength: 0,
+ tickWidth: 0
+ }
+ }
+});
+
+/**
+ * Draw one of the handles on the side of the zoomed range in the navigator
+ * @param {Boolean} inverted flag for chart.inverted
+ * @returns {Array} Path to be used in a handle
+ */
+H.Renderer.prototype.symbols['navigator-handle'] = function (
+ x,
+ y,
+ w,
+ h,
+ options
+) {
+ var halfWidth = options.width / 2,
+ markerPosition = Math.round(halfWidth / 3) + 0.5,
+ height = options.height;
+
+ return [
+ 'M',
+ -halfWidth - 1, 0.5,
+ 'L',
+ halfWidth, 0.5,
+ 'L',
+ halfWidth, height + 0.5,
+ 'L',
+ -halfWidth - 1, height + 0.5,
+ 'L',
+ -halfWidth - 1, 0.5,
+ 'M',
+ -markerPosition, 4,
+ 'L',
+ -markerPosition, height - 3,
+ 'M',
+ markerPosition - 1, 4,
+ 'L',
+ markerPosition - 1, height - 3
+ ];
+};
+
+/**
+ * The Navigator class
+ * @param {Object} chart - Chart object
+ * @class
+ */
+function Navigator(chart) {
+ this.init(chart);
+}
+
+Navigator.prototype = {
+ /**
+ * Draw one of the handles on the side of the zoomed range in the navigator
+ * @param {Number} x The x center for the handle
+ * @param {Number} index 0 for left and 1 for right
+ * @param {Boolean} inverted flag for chart.inverted
+ * @param {String} verb use 'animate' or 'attr'
+ */
+ drawHandle: function (x, index, inverted, verb) {
+ var navigator = this,
+ height = navigator.navigatorOptions.handles.height;
+
+ // Place it
+ navigator.handles[index][verb](inverted ? {
+ translateX: Math.round(navigator.left + navigator.height / 2),
+ translateY: Math.round(
+ navigator.top + parseInt(x, 10) + 0.5 - height
+ )
+ } : {
+ translateX: Math.round(navigator.left + parseInt(x, 10)),
+ translateY: Math.round(
+ navigator.top + navigator.height / 2 - height / 2 - 1
+ )
+ });
+ },
+
+ /**
+ * Render outline around the zoomed range
+ * @param {Number} zoomedMin in pixels position where zoomed range starts
+ * @param {Number} zoomedMax in pixels position where zoomed range ends
+ * @param {Boolean} inverted flag if chart is inverted
+ * @param {String} verb use 'animate' or 'attr'
+ */
+ drawOutline: function (zoomedMin, zoomedMax, inverted, verb) {
+ var navigator = this,
+ maskInside = navigator.navigatorOptions.maskInside,
+ outlineWidth = navigator.outline.strokeWidth(),
+ halfOutline = outlineWidth / 2,
+ outlineCorrection = (outlineWidth % 2) / 2, // #5800
+ outlineHeight = navigator.outlineHeight,
+ scrollbarHeight = navigator.scrollbarHeight,
+ navigatorSize = navigator.size,
+ left = navigator.left - scrollbarHeight,
+ navigatorTop = navigator.top,
+ verticalMin,
+ path;
+
+ if (inverted) {
+ left -= halfOutline;
+ verticalMin = navigatorTop + zoomedMax + outlineCorrection;
+ zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
+
+ path = [
+ 'M',
+ left + outlineHeight,
+ navigatorTop - scrollbarHeight - outlineCorrection, // top edge
+ 'L',
+ left + outlineHeight,
+ verticalMin, // top right of zoomed range
+ 'L',
+ left,
+ verticalMin, // top left of z.r.
+ 'L',
+ left,
+ zoomedMax, // bottom left of z.r.
+ 'L',
+ left + outlineHeight,
+ zoomedMax, // bottom right of z.r.
+ 'L',
+ left + outlineHeight,
+ navigatorTop + navigatorSize + scrollbarHeight // bottom edge
+ ].concat(maskInside ? [
+ 'M',
+ left + outlineHeight,
+ verticalMin - halfOutline, // upper left of zoomed range
+ 'L',
+ left + outlineHeight,
+ zoomedMax + halfOutline // upper right of z.r.
+ ] : []);
+ } else {
+ zoomedMin += left + scrollbarHeight - outlineCorrection;
+ zoomedMax += left + scrollbarHeight - outlineCorrection;
+ navigatorTop += halfOutline;
+
+ path = [
+ 'M',
+ left,
+ navigatorTop, // left
+ 'L',
+ zoomedMin,
+ navigatorTop, // upper left of zoomed range
+ 'L',
+ zoomedMin,
+ navigatorTop + outlineHeight, // lower left of z.r.
+ 'L',
+ zoomedMax,
+ navigatorTop + outlineHeight, // lower right of z.r.
+ 'L',
+ zoomedMax,
+ navigatorTop, // upper right of z.r.
+ 'L',
+ left + navigatorSize + scrollbarHeight * 2,
+ navigatorTop // right
+ ].concat(maskInside ? [
+ 'M',
+ zoomedMin - halfOutline,
+ navigatorTop, // upper left of zoomed range
+ 'L',
+ zoomedMax + halfOutline,
+ navigatorTop // upper right of z.r.
+ ] : []);
+ }
+ navigator.outline[verb]({
+ d: path
+ });
+ },
+
+ /**
+ * Render outline around the zoomed range
+ * @param {Number} zoomedMin in pixels position where zoomed range starts
+ * @param {Number} zoomedMax in pixels position where zoomed range ends
+ * @param {Boolean} inverted flag if chart is inverted
+ * @param {String} verb use 'animate' or 'attr'
+ */
+ drawMasks: function (zoomedMin, zoomedMax, inverted, verb) {
+ var navigator = this,
+ left = navigator.left,
+ top = navigator.top,
+ navigatorHeight = navigator.height,
+ height,
+ width,
+ x,
+ y;
+
+ // Determine rectangle position & size
+ // According to (non)inverted position:
+ if (inverted) {
+ x = [left, left, left];
+ y = [top, top + zoomedMin, top + zoomedMax];
+ width = [navigatorHeight, navigatorHeight, navigatorHeight];
+ height = [
+ zoomedMin,
+ zoomedMax - zoomedMin,
+ navigator.size - zoomedMax
+ ];
+ } else {
+ x = [left, left + zoomedMin, left + zoomedMax];
+ y = [top, top, top];
+ width = [
+ zoomedMin,
+ zoomedMax - zoomedMin,
+ navigator.size - zoomedMax
+ ];
+ height = [navigatorHeight, navigatorHeight, navigatorHeight];
+ }
+ each(navigator.shades, function (shade, i) {
+ shade[verb]({
+ x: x[i],
+ y: y[i],
+ width: width[i],
+ height: height[i]
+ });
+ });
+ },
+
+ /**
+ * Generate DOM elements for a navigator:
+ * - main navigator group
+ * - all shades
+ * - outline
+ * - handles
+ */
+ renderElements: function () {
+ var navigator = this,
+ navigatorOptions = navigator.navigatorOptions,
+ maskInside = navigatorOptions.maskInside,
+ chart = navigator.chart,
+ inverted = chart.inverted,
+ renderer = chart.renderer,
+ navigatorGroup;
+
+ // Create the main navigator group
+ navigator.navigatorGroup = navigatorGroup = renderer.g('navigator')
+ .attr({
+ zIndex: 8,
+ visibility: 'hidden'
+ })
+ .add();
+
+
+
+ var mouseCursor = {
+ cursor: inverted ? 'ns-resize' : 'ew-resize'
+ };
+
+
+ // Create masks, each mask will get events and fill:
+ each([!maskInside, maskInside, !maskInside], function (hasMask, index) {
+ navigator.shades[index] = renderer.rect()
+ .addClass('highcharts-navigator-mask' +
+ (index === 1 ? '-inside' : '-outside'))
+
+ .attr({
+ fill: hasMask ? navigatorOptions.maskFill : 'rgba(0,0,0,0)'
+ })
+ .css(index === 1 && mouseCursor)
+
+ .add(navigatorGroup);
+ });
+
+ // Create the outline:
+ navigator.outline = renderer.path()
+ .addClass('highcharts-navigator-outline')
+
+ .attr({
+ 'stroke-width': navigatorOptions.outlineWidth,
+ stroke: navigatorOptions.outlineColor
+ })
+
+ .add(navigatorGroup);
+
+ // Create the handlers:
+ if (navigatorOptions.handles.enabled) {
+ each([0, 1], function (index) {
+ navigatorOptions.handles.inverted = chart.inverted;
+ navigator.handles[index] = renderer.symbol(
+ navigatorOptions.handles.symbols[index],
+ -navigatorOptions.handles.width / 2 - 1,
+ 0,
+ navigatorOptions.handles.width,
+ navigatorOptions.handles.height,
+ navigatorOptions.handles
+ );
+ // zIndex = 6 for right handle, 7 for left.
+ // Can't be 10, because of the tooltip in inverted chart #2908
+ navigator.handles[index].attr({ zIndex: 7 - index })
+ .addClass(
+ 'highcharts-navigator-handle ' +
+ 'highcharts-navigator-handle-' +
+ ['left', 'right'][index]
+ ).add(navigatorGroup);
+
+
+ var handlesOptions = navigatorOptions.handles;
+ navigator.handles[index]
+ .attr({
+ fill: handlesOptions.backgroundColor,
+ stroke: handlesOptions.borderColor,
+ 'stroke-width': handlesOptions.lineWidth
+ })
+ .css(mouseCursor);
+
+ });
+ }
+ },
+
+ /**
+ * Update navigator
+ * @param {Object} options Options to merge in when updating navigator
+ */
+ update: function (options) {
+ // Remove references to old navigator series in base series
+ each(this.series || [], function (series) {
+ if (series.baseSeries) {
+ delete series.baseSeries.navigatorSeries;
+ }
+ });
+ // Destroy and rebuild navigator
+ this.destroy();
+ var chartOptions = this.chart.options;
+ merge(true, chartOptions.navigator, this.options, options);
+ this.init(this.chart);
+ },
+
+ /**
+ * Render the navigator
+ * @param {Number} min X axis value minimum
+ * @param {Number} max X axis value maximum
+ * @param {Number} pxMin Pixel value minimum
+ * @param {Number} pxMax Pixel value maximum
+ */
+ render: function (min, max, pxMin, pxMax) {
+
+ var navigator = this,
+ chart = navigator.chart,
+ navigatorWidth,
+ scrollbarLeft,
+ scrollbarTop,
+ scrollbarHeight = navigator.scrollbarHeight,
+ navigatorSize,
+ xAxis = navigator.xAxis,
+ scrollbarXAxis = xAxis.fake ? chart.xAxis[0] : xAxis,
+ navigatorEnabled = navigator.navigatorEnabled,
+ zoomedMin,
+ zoomedMax,
+ rendered = navigator.rendered,
+ inverted = chart.inverted,
+ verb,
+ newMin,
+ newMax,
+ currentRange,
+ minRange = chart.xAxis[0].minRange,
+ maxRange = chart.xAxis[0].options.maxRange;
+
+ // Don't redraw while moving the handles (#4703).
+ if (this.hasDragged && !defined(pxMin)) {
+ return;
+ }
+
+ // Don't render the navigator until we have data (#486, #4202, #5172).
+ if (!isNumber(min) || !isNumber(max)) {
+ // However, if navigator was already rendered, we may need to resize
+ // it. For example hidden series, but visible navigator (#6022).
+ if (rendered) {
+ pxMin = 0;
+ pxMax = pick(xAxis.width, scrollbarXAxis.width);
+ } else {
+ return;
+ }
+ }
+
+ navigator.left = pick(
+ xAxis.left,
+ // in case of scrollbar only, without navigator
+ chart.plotLeft + scrollbarHeight + (inverted ? chart.plotWidth : 0)
+ );
+
+ navigator.size = zoomedMax = navigatorSize = pick(
+ xAxis.len,
+ (inverted ? chart.plotHeight : chart.plotWidth) -
+ 2 * scrollbarHeight
+ );
+
+ if (inverted) {
+ navigatorWidth = scrollbarHeight;
+ } else {
+ navigatorWidth = navigatorSize + 2 * scrollbarHeight;
+ }
+
+ // Get the pixel position of the handles
+ pxMin = pick(pxMin, xAxis.toPixels(min, true));
+ pxMax = pick(pxMax, xAxis.toPixels(max, true));
+
+ // Verify (#1851, #2238)
+ if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) {
+ pxMin = 0;
+ pxMax = navigatorWidth;
+ }
+
+ // Are we below the minRange? (#2618, #6191)
+ newMin = xAxis.toValue(pxMin, true);
+ newMax = xAxis.toValue(pxMax, true);
+ currentRange = Math.abs(H.correctFloat(newMax - newMin));
+ if (currentRange < minRange) {
+ if (this.grabbedLeft) {
+ pxMin = xAxis.toPixels(newMax - minRange, true);
+ } else if (this.grabbedRight) {
+ pxMax = xAxis.toPixels(newMin + minRange, true);
+ }
+ } else if (defined(maxRange) && currentRange > maxRange) {
+ /**
+ * Maximum range which can be set using the navigator's handles.
+ * Opposite of [xAxis.minRange](#xAxis.minRange).
+ *
+ * @type {Number}
+ * @default undefined
+ * @product highstock
+ * @sample {highstock} stock/navigator/maxrange/
+ * Defined max and min range
+ * @since 6.0.0
+ * @apioption xAxis.maxRange
+ */
+ if (this.grabbedLeft) {
+ pxMin = xAxis.toPixels(newMax - maxRange, true);
+ } else if (this.grabbedRight) {
+ pxMax = xAxis.toPixels(newMin + maxRange, true);
+ }
+ }
+
+ // Handles are allowed to cross, but never exceed the plot area
+ navigator.zoomedMax = Math.min(Math.max(pxMin, pxMax, 0), zoomedMax);
+ navigator.zoomedMin = Math.min(
+ Math.max(
+ navigator.fixedWidth ?
+ navigator.zoomedMax - navigator.fixedWidth :
+ Math.min(pxMin, pxMax),
+ 0
+ ),
+ zoomedMax
+ );
+
+ navigator.range = navigator.zoomedMax - navigator.zoomedMin;
+
+ zoomedMax = Math.round(navigator.zoomedMax);
+ zoomedMin = Math.round(navigator.zoomedMin);
+
+ if (navigatorEnabled) {
+ navigator.navigatorGroup.attr({
+ visibility: 'visible'
+ });
+ // Place elements
+ verb = rendered && !navigator.hasDragged ? 'animate' : 'attr';
+
+ navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
+ navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
+
+ if (navigator.navigatorOptions.handles.enabled) {
+ navigator.drawHandle(zoomedMin, 0, inverted, verb);
+ navigator.drawHandle(zoomedMax, 1, inverted, verb);
+ }
+ }
+
+ if (navigator.scrollbar) {
+ if (inverted) {
+ scrollbarTop = navigator.top - scrollbarHeight;
+ scrollbarLeft = navigator.left - scrollbarHeight +
+ (navigatorEnabled || !scrollbarXAxis.opposite ? 0 :
+ // Multiple axes has offsets:
+ (scrollbarXAxis.titleOffset || 0) +
+ // Self margin from the axis.title
+ scrollbarXAxis.axisTitleMargin
+ );
+ scrollbarHeight = navigatorSize + 2 * scrollbarHeight;
+ } else {
+ scrollbarTop = navigator.top +
+ (navigatorEnabled ? navigator.height : -scrollbarHeight);
+ scrollbarLeft = navigator.left - scrollbarHeight;
+ }
+ // Reposition scrollbar
+ navigator.scrollbar.position(
+ scrollbarLeft,
+ scrollbarTop,
+ navigatorWidth,
+ scrollbarHeight
+ );
+ // Keep scale 0-1
+ navigator.scrollbar.setRange(
+ // Use real value, not rounded because range can be very small
+ // (#1716)
+ navigator.zoomedMin / navigatorSize,
+ navigator.zoomedMax / navigatorSize
+ );
+ }
+ navigator.rendered = true;
+ },
+
+ /**
+ * Set up the mouse and touch events for the navigator
+ */
+ addMouseEvents: function () {
+ var navigator = this,
+ chart = navigator.chart,
+ container = chart.container,
+ eventsToUnbind = [],
+ mouseMoveHandler,
+ mouseUpHandler;
+
+ /**
+ * Create mouse events' handlers.
+ * Make them as separate functions to enable wrapping them:
+ */
+ navigator.mouseMoveHandler = mouseMoveHandler = function (e) {
+ navigator.onMouseMove(e);
+ };
+ navigator.mouseUpHandler = mouseUpHandler = function (e) {
+ navigator.onMouseUp(e);
+ };
+
+ // Add shades and handles mousedown events
+ eventsToUnbind = navigator.getPartsEvents('mousedown');
+ // Add mouse move and mouseup events. These are bind to doc/container,
+ // because Navigator.grabbedSomething flags are stored in mousedown
+ // events
+ eventsToUnbind.push(
+ addEvent(container, 'mousemove', mouseMoveHandler),
+ addEvent(container.ownerDocument, 'mouseup', mouseUpHandler)
+ );
+
+ // Touch events
+ if (hasTouch) {
+ eventsToUnbind.push(
+ addEvent(container, 'touchmove', mouseMoveHandler),
+ addEvent(container.ownerDocument, 'touchend', mouseUpHandler)
+ );
+ eventsToUnbind.concat(navigator.getPartsEvents('touchstart'));
+ }
+
+ navigator.eventsToUnbind = eventsToUnbind;
+
+ // Data events
+ if (navigator.series && navigator.series[0]) {
+ eventsToUnbind.push(
+ addEvent(
+ navigator.series[0].xAxis,
+ 'foundExtremes',
+ function () {
+ chart.navigator.modifyNavigatorAxisExtremes();
+ }
+ )
+ );
+ }
+ },
+
+ /**
+ * Generate events for handles and masks
+ * @param {String} eventName Event name handler, 'mousedown' or 'touchstart'
+ * @returns {Array} An array of arrays: [DOMElement, eventName, callback].
+ */
+ getPartsEvents: function (eventName) {
+ var navigator = this,
+ events = [];
+ each(['shades', 'handles'], function (name) {
+ each(navigator[name], function (navigatorItem, index) {
+ events.push(
+ addEvent(
+ navigatorItem.element,
+ eventName,
+ function (e) {
+ navigator[name + 'Mousedown'](e, index);
+ }
+ )
+ );
+ });
+ });
+ return events;
+ },
+
+ /**
+ * Mousedown on a shaded mask, either:
+ * - will be stored for future drag&drop
+ * - will directly shift to a new range
+ *
+ * @param {Object} e Mouse event
+ * @param {Number} index Index of a mask in Navigator.shades array
+ */
+ shadesMousedown: function (e, index) {
+ e = this.chart.pointer.normalize(e);
+
+ var navigator = this,
+ chart = navigator.chart,
+ xAxis = navigator.xAxis,
+ zoomedMin = navigator.zoomedMin,
+ navigatorPosition = navigator.left,
+ navigatorSize = navigator.size,
+ range = navigator.range,
+ chartX = e.chartX,
+ fixedMax,
+ ext,
+ left;
+
+ // For inverted chart, swap some options:
+ if (chart.inverted) {
+ chartX = e.chartY;
+ navigatorPosition = navigator.top;
+ }
+
+ if (index === 1) {
+ // Store information for drag&drop
+ navigator.grabbedCenter = chartX;
+ navigator.fixedWidth = range;
+ navigator.dragOffset = chartX - zoomedMin;
+ } else {
+ // Shift the range by clicking on shaded areas
+ left = chartX - navigatorPosition - range / 2;
+ if (index === 0) {
+ left = Math.max(0, left);
+ } else if (index === 2 && left + range >= navigatorSize) {
+ left = navigatorSize - range;
+ fixedMax = navigator.getUnionExtremes().dataMax; // #2293, #3543
+ }
+ if (left !== zoomedMin) { // it has actually moved
+ navigator.fixedWidth = range; // #1370
+
+ ext = xAxis.toFixedRange(left, left + range, null, fixedMax);
+ if (defined(ext.min)) { // #7411
+ chart.xAxis[0].setExtremes(
+ Math.min(ext.min, ext.max),
+ Math.max(ext.min, ext.max),
+ true,
+ null, // auto animation
+ { trigger: 'navigator' }
+ );
+ }
+ }
+ }
+ },
+
+ /**
+ * Mousedown on a handle mask.
+ * Will store necessary information for drag&drop.
+ *
+ * @param {Object} e Mouse event
+ * @param {Number} index Index of a handle in Navigator.handles array
+ */
+ handlesMousedown: function (e, index) {
+ e = this.chart.pointer.normalize(e);
+
+ var navigator = this,
+ chart = navigator.chart,
+ baseXAxis = chart.xAxis[0],
+ // For reversed axes, min and max are chagned,
+ // so the other extreme should be stored
+ reverse = (chart.inverted && !baseXAxis.reversed) ||
+ (!chart.inverted && baseXAxis.reversed);
+
+ if (index === 0) {
+ // Grab the left handle
+ navigator.grabbedLeft = true;
+ navigator.otherHandlePos = navigator.zoomedMax;
+ navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max;
+ } else {
+ // Grab the right handle
+ navigator.grabbedRight = true;
+ navigator.otherHandlePos = navigator.zoomedMin;
+ navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min;
+ }
+
+ chart.fixedRange = null;
+ },
+ /**
+ * Mouse move event based on x/y mouse position.
+ * @param {Object} e Mouse event
+ */
+ onMouseMove: function (e) {
+ var navigator = this,
+ chart = navigator.chart,
+ left = navigator.left,
+ navigatorSize = navigator.navigatorSize,
+ range = navigator.range,
+ dragOffset = navigator.dragOffset,
+ inverted = chart.inverted,
+ chartX;
+
+
+ // In iOS, a mousemove event with e.pageX === 0 is fired when holding
+ // the finger down in the center of the scrollbar. This should be
+ // ignored.
+ if (!e.touches || e.touches[0].pageX !== 0) { // #4696
+
+ e = chart.pointer.normalize(e);
+ chartX = e.chartX;
+
+ // Swap some options for inverted chart
+ if (inverted) {
+ left = navigator.top;
+ chartX = e.chartY;
+ }
+
+ // Drag left handle or top handle
+ if (navigator.grabbedLeft) {
+ navigator.hasDragged = true;
+ navigator.render(
+ 0,
+ 0,
+ chartX - left,
+ navigator.otherHandlePos
+ );
+ // Drag right handle or bottom handle
+ } else if (navigator.grabbedRight) {
+ navigator.hasDragged = true;
+ navigator.render(
+ 0,
+ 0,
+ navigator.otherHandlePos,
+ chartX - left
+ );
+ // Drag scrollbar or open area in navigator
+ } else if (navigator.grabbedCenter) {
+ navigator.hasDragged = true;
+ if (chartX < dragOffset) { // outside left
+ chartX = dragOffset;
+ // outside right
+ } else if (chartX > navigatorSize + dragOffset - range) {
+ chartX = navigatorSize + dragOffset - range;
+ }
+
+ navigator.render(
+ 0,
+ 0,
+ chartX - dragOffset,
+ chartX - dragOffset + range
+ );
+ }
+ if (
+ navigator.hasDragged &&
+ navigator.scrollbar &&
+ navigator.scrollbar.options.liveRedraw
+ ) {
+ e.DOMType = e.type; // DOMType is for IE8
+ setTimeout(function () {
+ navigator.onMouseUp(e);
+ }, 0);
+ }
+ }
+ },
+
+ /**
+ * Mouse up event based on x/y mouse position.
+ * @param {Object} e Mouse event
+ */
+ onMouseUp: function (e) {
+ var navigator = this,
+ chart = navigator.chart,
+ xAxis = navigator.xAxis,
+ reversed = xAxis && xAxis.reversed,
+ scrollbar = navigator.scrollbar,
+ unionExtremes,
+ fixedMin,
+ fixedMax,
+ ext,
+ DOMEvent = e.DOMEvent || e;
+
+ if (
+ // MouseUp is called for both, navigator and scrollbar (that order),
+ // which causes calling afterSetExtremes twice. Prevent first call
+ // by checking if scrollbar is going to set new extremes (#6334)
+ (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) ||
+ e.trigger === 'scrollbar'
+ ) {
+ unionExtremes = navigator.getUnionExtremes();
+
+ // When dragging one handle, make sure the other one doesn't change
+ if (navigator.zoomedMin === navigator.otherHandlePos) {
+ fixedMin = navigator.fixedExtreme;
+ } else if (navigator.zoomedMax === navigator.otherHandlePos) {
+ fixedMax = navigator.fixedExtreme;
+ }
+ // Snap to right edge (#4076)
+ if (navigator.zoomedMax === navigator.size) {
+ fixedMax = reversed ?
+ unionExtremes.dataMin : unionExtremes.dataMax;
+ }
+
+ // Snap to left edge (#7576)
+ if (navigator.zoomedMin === 0) {
+ fixedMin = reversed ?
+ unionExtremes.dataMax : unionExtremes.dataMin;
+ }
+
+ ext = xAxis.toFixedRange(
+ navigator.zoomedMin,
+ navigator.zoomedMax,
+ fixedMin,
+ fixedMax
+ );
+
+ if (defined(ext.min)) {
+ chart.xAxis[0].setExtremes(
+ Math.min(ext.min, ext.max),
+ Math.max(ext.min, ext.max),
+ true,
+ // Run animation when clicking buttons, scrollbar track etc,
+ // but not when dragging handles or scrollbar
+ navigator.hasDragged ? false : null,
+ {
+ trigger: 'navigator',
+ triggerOp: 'navigator-drag',
+ DOMEvent: DOMEvent // #1838
+ }
+ );
+ }
+ }
+
+ if (e.DOMType !== 'mousemove') {
+ navigator.grabbedLeft = navigator.grabbedRight =
+ navigator.grabbedCenter = navigator.fixedWidth =
+ navigator.fixedExtreme = navigator.otherHandlePos =
+ navigator.hasDragged = navigator.dragOffset = null;
+ }
+ },
+
+ /**
+ * Removes the event handlers attached previously with addEvents.
+ */
+ removeEvents: function () {
+ if (this.eventsToUnbind) {
+ each(this.eventsToUnbind, function (unbind) {
+ unbind();
+ });
+ this.eventsToUnbind = undefined;
+ }
+ this.removeBaseSeriesEvents();
+ },
+
+ /**
+ * Remove data events.
+ */
+ removeBaseSeriesEvents: function () {
+ var baseSeries = this.baseSeries || [];
+ if (this.navigatorEnabled && baseSeries[0]) {
+ if (this.navigatorOptions.adaptToUpdatedData !== false) {
+ each(baseSeries, function (series) {
+ removeEvent(series, 'updatedData', this.updatedDataHandler);
+ }, this);
+ }
+
+ // We only listen for extremes-events on the first baseSeries
+ if (baseSeries[0].xAxis) {
+ removeEvent(
+ baseSeries[0].xAxis,
+ 'foundExtremes',
+ this.modifyBaseAxisExtremes
+ );
+ }
+ }
+ },
+
+ /**
+ * Initiate the Navigator object
+ */
+ init: function (chart) {
+ var chartOptions = chart.options,
+ navigatorOptions = chartOptions.navigator,
+ navigatorEnabled = navigatorOptions.enabled,
+ scrollbarOptions = chartOptions.scrollbar,
+ scrollbarEnabled = scrollbarOptions.enabled,
+ height = navigatorEnabled ? navigatorOptions.height : 0,
+ scrollbarHeight = scrollbarEnabled ? scrollbarOptions.height : 0;
+
+ this.handles = [];
+ this.shades = [];
+
+ this.chart = chart;
+ this.setBaseSeries();
+
+ this.height = height;
+ this.scrollbarHeight = scrollbarHeight;
+ this.scrollbarEnabled = scrollbarEnabled;
+ this.navigatorEnabled = navigatorEnabled;
+ this.navigatorOptions = navigatorOptions;
+ this.scrollbarOptions = scrollbarOptions;
+ this.outlineHeight = height + scrollbarHeight;
+
+ this.opposite = pick(
+ navigatorOptions.opposite,
+ !navigatorEnabled && chart.inverted
+ ); // #6262
+
+ var navigator = this,
+ baseSeries = navigator.baseSeries,
+ xAxisIndex = chart.xAxis.length,
+ yAxisIndex = chart.yAxis.length,
+ baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis ||
+ chart.xAxis[0];
+
+ // Make room for the navigator, can be placed around the chart:
+ chart.extraMargin = {
+ type: navigator.opposite ? 'plotTop' : 'marginBottom',
+ value: (
+ navigatorEnabled || !chart.inverted ?
+ navigator.outlineHeight :
+ 0
+ ) + navigatorOptions.margin
+ };
+ if (chart.inverted) {
+ chart.extraMargin.type = navigator.opposite ?
+ 'marginRight' :
+ 'plotLeft';
+ }
+ chart.isDirtyBox = true;
+
+ if (navigator.navigatorEnabled) {
+ // an x axis is required for scrollbar also
+ navigator.xAxis = new Axis(chart, merge({
+ // inherit base xAxis' break and ordinal options
+ breaks: baseXaxis.options.breaks,
+ ordinal: baseXaxis.options.ordinal
+ }, navigatorOptions.xAxis, {
+ id: 'navigator-x-axis',
+ yAxis: 'navigator-y-axis',
+ isX: true,
+ type: 'datetime',
+ index: xAxisIndex,
+ offset: 0,
+ keepOrdinalPadding: true, // #2436
+ startOnTick: false,
+ endOnTick: false,
+ minPadding: 0,
+ maxPadding: 0,
+ zoomEnabled: false
+ }, chart.inverted ? {
+ offsets: [scrollbarHeight, 0, -scrollbarHeight, 0],
+ width: height
+ } : {
+ offsets: [0, -scrollbarHeight, 0, scrollbarHeight],
+ height: height
+ }));
+
+ navigator.yAxis = new Axis(chart, merge(navigatorOptions.yAxis, {
+ id: 'navigator-y-axis',
+ alignTicks: false,
+ offset: 0,
+ index: yAxisIndex,
+ zoomEnabled: false
+ }, chart.inverted ? {
+ width: height
+ } : {
+ height: height
+ }));
+
+ // If we have a base series, initialize the navigator series
+ if (baseSeries || navigatorOptions.series.data) {
+ navigator.updateNavigatorSeries();
+
+ // If not, set up an event to listen for added series
+ } else if (chart.series.length === 0) {
+
+ wrap(chart, 'redraw', function (proceed, animation) {
+ // We've got one, now add it as base and reset chart.redraw
+ if (chart.series.length > 0 && !navigator.series) {
+ navigator.setBaseSeries();
+ chart.redraw = proceed; // reset
+ }
+ proceed.call(chart, animation);
+ });
+ }
+
+ // Render items, so we can bind events to them:
+ navigator.renderElements();
+ // Add mouse events
+ navigator.addMouseEvents();
+
+ // in case of scrollbar only, fake an x axis to get translation
+ } else {
+ navigator.xAxis = {
+ translate: function (value, reverse) {
+ var axis = chart.xAxis[0],
+ ext = axis.getExtremes(),
+ scrollTrackWidth = axis.len - 2 * scrollbarHeight,
+ min = numExt('min', axis.options.min, ext.dataMin),
+ valueRange = numExt(
+ 'max',
+ axis.options.max,
+ ext.dataMax
+ ) - min;
+
+ return reverse ?
+ // from pixel to value
+ (value * valueRange / scrollTrackWidth) + min :
+ // from value to pixel
+ scrollTrackWidth * (value - min) / valueRange;
+ },
+ toPixels: function (value) {
+ return this.translate(value);
+ },
+ toValue: function (value) {
+ return this.translate(value, true);
+ },
+ toFixedRange: Axis.prototype.toFixedRange,
+ fake: true
+ };
+ }
+
+
+ // Initialize the scrollbar
+ if (chart.options.scrollbar.enabled) {
+ chart.scrollbar = navigator.scrollbar = new Scrollbar(
+ chart.renderer,
+ merge(chart.options.scrollbar, {
+ margin: navigator.navigatorEnabled ? 0 : 10,
+ vertical: chart.inverted
+ }),
+ chart
+ );
+ addEvent(navigator.scrollbar, 'changed', function (e) {
+ var range = navigator.size,
+ to = range * this.to,
+ from = range * this.from;
+
+ navigator.hasDragged = navigator.scrollbar.hasDragged;
+ navigator.render(0, 0, from, to);
+
+ if (
+ chart.options.scrollbar.liveRedraw ||
+ (
+ e.DOMType !== 'mousemove' &&
+ e.DOMType !== 'touchmove'
+ )
+ ) {
+ setTimeout(function () {
+ navigator.onMouseUp(e);
+ });
+ }
+ });
+ }
+
+ // Add data events
+ navigator.addBaseSeriesEvents();
+ // Add redraw events
+ navigator.addChartEvents();
+ },
+
+ /**
+ * Get the union data extremes of the chart - the outer data extremes of the
+ * base X axis and the navigator axis.
+ * @param {boolean} returnFalseOnNoBaseSeries - as the param says.
+ */
+ getUnionExtremes: function (returnFalseOnNoBaseSeries) {
+ var baseAxis = this.chart.xAxis[0],
+ navAxis = this.xAxis,
+ navAxisOptions = navAxis.options,
+ baseAxisOptions = baseAxis.options,
+ ret;
+
+ if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) {
+ ret = {
+ dataMin: pick( // #4053
+ navAxisOptions && navAxisOptions.min,
+ numExt(
+ 'min',
+ baseAxisOptions.min,
+ baseAxis.dataMin,
+ navAxis.dataMin,
+ navAxis.min
+ )
+ ),
+ dataMax: pick(
+ navAxisOptions && navAxisOptions.max,
+ numExt(
+ 'max',
+ baseAxisOptions.max,
+ baseAxis.dataMax,
+ navAxis.dataMax,
+ navAxis.max
+ )
+ )
+ };
+ }
+ return ret;
+ },
+
+ /**
+ * Set the base series and update the navigator series from this. With a bit
+ * of modification we should be able to make this an API method to be called
+ * from the outside
+ * @param {Object} baseSeriesOptions
+ * Additional series options for a navigator
+ * @param {Boolean} [redraw]
+ * Whether to redraw after update.
+ */
+ setBaseSeries: function (baseSeriesOptions, redraw) {
+ var chart = this.chart,
+ baseSeries = this.baseSeries = [];
+
+ baseSeriesOptions = (
+ baseSeriesOptions ||
+ chart.options && chart.options.navigator.baseSeries ||
+ 0
+ );
+
+ // Iterate through series and add the ones that should be shown in
+ // navigator.
+ each(chart.series || [], function (series, i) {
+ if (
+ // Don't include existing nav series
+ !series.options.isInternal &&
+ (
+ series.options.showInNavigator ||
+ (
+ i === baseSeriesOptions ||
+ series.options.id === baseSeriesOptions
+ ) &&
+ series.options.showInNavigator !== false
+ )
+ ) {
+ baseSeries.push(series);
+ }
+ });
+
+ // When run after render, this.xAxis already exists
+ if (this.xAxis && !this.xAxis.fake) {
+ this.updateNavigatorSeries(redraw);
+ }
+ },
+
+ /*
+ * Update series in the navigator from baseSeries, adding new if does not
+ * exist.
+ */
+ updateNavigatorSeries: function (redraw) {
+ var navigator = this,
+ chart = navigator.chart,
+ baseSeries = navigator.baseSeries,
+ baseOptions,
+ mergedNavSeriesOptions,
+ chartNavigatorSeriesOptions = navigator.navigatorOptions.series,
+ baseNavigatorOptions,
+ navSeriesMixin = {
+ enableMouseTracking: false,
+ index: null, // #6162
+ linkedTo: null, // #6734
+ group: 'nav', // for columns
+ padXAxis: false,
+ xAxis: 'navigator-x-axis',
+ yAxis: 'navigator-y-axis',
+ showInLegend: false,
+ stacking: false, // #4823
+ isInternal: true,
+ visible: true
+ },
+ // Remove navigator series that are no longer in the baseSeries
+ navigatorSeries = navigator.series = H.grep(
+ navigator.series || [], function (navSeries) {
+ var base = navSeries.baseSeries;
+ if (H.inArray(base, baseSeries) < 0) { // Not in array
+ // If there is still a base series connected to this
+ // series, remove event handler and reference.
+ if (base) {
+ removeEvent(
+ base,
+ 'updatedData',
+ navigator.updatedDataHandler
+ );
+ delete base.navigatorSeries;
+ }
+ // Kill the nav series
+ navSeries.destroy();
+ return false;
+ }
+ return true;
+ }
+ );
+
+ // Go through each base series and merge the options to create new
+ // series
+ if (baseSeries && baseSeries.length) {
+ each(baseSeries, function eachBaseSeries(base) {
+ var linkedNavSeries = base.navigatorSeries,
+ userNavOptions = extend(
+ // Grab color from base as default
+ {
+ color: base.color
+ },
+ !isArray(chartNavigatorSeriesOptions) ?
+ chartNavigatorSeriesOptions :
+ defaultOptions.navigator.series
+ );
+
+ // Don't update if the series exists in nav and we have disabled
+ // adaptToUpdatedData.
+ if (
+ linkedNavSeries &&
+ navigator.navigatorOptions.adaptToUpdatedData === false
+ ) {
+ return;
+ }
+
+ navSeriesMixin.name = 'Navigator ' + baseSeries.length;
+
+ baseOptions = base.options || {};
+ baseNavigatorOptions = baseOptions.navigatorOptions || {};
+ mergedNavSeriesOptions = merge(
+ baseOptions,
+ navSeriesMixin,
+ userNavOptions,
+ baseNavigatorOptions
+ );
+
+ // Merge data separately. Do a slice to avoid mutating the
+ // navigator options from base series (#4923).
+ var navigatorSeriesData =
+ baseNavigatorOptions.data || userNavOptions.data;
+ navigator.hasNavigatorData =
+ navigator.hasNavigatorData || !!navigatorSeriesData;
+ mergedNavSeriesOptions.data =
+ navigatorSeriesData ||
+ baseOptions.data && baseOptions.data.slice(0);
+
+ // Update or add the series
+ if (linkedNavSeries && linkedNavSeries.options) {
+ linkedNavSeries.update(mergedNavSeriesOptions, redraw);
+ } else {
+ base.navigatorSeries = chart.initSeries(
+ mergedNavSeriesOptions
+ );
+ base.navigatorSeries.baseSeries = base; // Store ref
+ navigatorSeries.push(base.navigatorSeries);
+ }
+ });
+ }
+
+ // If user has defined data (and no base series) or explicitly defined
+ // navigator.series as an array, we create these series on top of any
+ // base series.
+ if (
+ chartNavigatorSeriesOptions.data &&
+ !(baseSeries && baseSeries.length) ||
+ isArray(chartNavigatorSeriesOptions)
+ ) {
+ navigator.hasNavigatorData = false;
+ // Allow navigator.series to be an array
+ chartNavigatorSeriesOptions = H.splat(chartNavigatorSeriesOptions);
+ each(chartNavigatorSeriesOptions, function (userSeriesOptions, i) {
+ navSeriesMixin.name =
+ 'Navigator ' + (navigatorSeries.length + 1);
+ mergedNavSeriesOptions = merge(
+ defaultOptions.navigator.series,
+ {
+ // Since we don't have a base series to pull color from,
+ // try to fake it by using color from series with same
+ // index. Otherwise pull from the colors array. We need
+ // an explicit color as otherwise updates will increment
+ // color counter and we'll get a new color for each
+ // update of the nav series.
+ color: chart.series[i] &&
+ !chart.series[i].options.isInternal &&
+ chart.series[i].color ||
+ chart.options.colors[i] ||
+ chart.options.colors[0]
+ },
+ navSeriesMixin,
+ userSeriesOptions
+ );
+ mergedNavSeriesOptions.data = userSeriesOptions.data;
+ if (mergedNavSeriesOptions.data) {
+ navigator.hasNavigatorData = true;
+ navigatorSeries.push(
+ chart.initSeries(mergedNavSeriesOptions)
+ );
+ }
+ });
+ }
+
+ this.addBaseSeriesEvents();
+ },
+
+ /**
+ * Add data events.
+ * For example when main series is updated we need to recalculate extremes
+ */
+ addBaseSeriesEvents: function () {
+ var navigator = this,
+ baseSeries = navigator.baseSeries || [];
+
+ // Bind modified extremes event to first base's xAxis only.
+ // In event of > 1 base-xAxes, the navigator will ignore those.
+ // Adding this multiple times to the same axis is no problem, as
+ // duplicates should be discarded by the browser.
+ if (baseSeries[0] && baseSeries[0].xAxis) {
+ addEvent(
+ baseSeries[0].xAxis,
+ 'foundExtremes',
+ this.modifyBaseAxisExtremes
+ );
+ }
+
+ each(baseSeries, function (base) {
+ // Link base series show/hide to navigator series visibility
+ addEvent(base, 'show', function () {
+ if (this.navigatorSeries) {
+ this.navigatorSeries.setVisible(true, false);
+ }
+ });
+ addEvent(base, 'hide', function () {
+ if (this.navigatorSeries) {
+ this.navigatorSeries.setVisible(false, false);
+ }
+ });
+
+ // Respond to updated data in the base series, unless explicitily
+ // not adapting to data changes.
+ if (this.navigatorOptions.adaptToUpdatedData !== false) {
+ if (base.xAxis) {
+ addEvent(base, 'updatedData', this.updatedDataHandler);
+ }
+ }
+
+ // Handle series removal
+ addEvent(base, 'remove', function () {
+ if (this.navigatorSeries) {
+ erase(navigator.series, this.navigatorSeries);
+ this.navigatorSeries.remove(false);
+ delete this.navigatorSeries;
+ }
+ });
+ }, this);
+ },
+
+ /**
+ * Set the navigator x axis extremes to reflect the total. The navigator
+ * extremes should always be the extremes of the union of all series in the
+ * chart as well as the navigator series.
+ */
+ modifyNavigatorAxisExtremes: function () {
+ var xAxis = this.xAxis,
+ unionExtremes;
+
+ if (xAxis.getExtremes) {
+ unionExtremes = this.getUnionExtremes(true);
+ if (
+ unionExtremes &&
+ (
+ unionExtremes.dataMin !== xAxis.min ||
+ unionExtremes.dataMax !== xAxis.max
+ )
+ ) {
+ xAxis.min = unionExtremes.dataMin;
+ xAxis.max = unionExtremes.dataMax;
+ }
+ }
+ },
+
+ /**
+ * Hook to modify the base axis extremes with information from the Navigator
+ */
+ modifyBaseAxisExtremes: function () {
+ var baseXAxis = this,
+ navigator = baseXAxis.chart.navigator,
+ baseExtremes = baseXAxis.getExtremes(),
+ baseMin = baseExtremes.min,
+ baseMax = baseExtremes.max,
+ baseDataMin = baseExtremes.dataMin,
+ baseDataMax = baseExtremes.dataMax,
+ range = baseMax - baseMin,
+ stickToMin = navigator.stickToMin,
+ stickToMax = navigator.stickToMax,
+ overscroll = baseXAxis.options.overscroll,
+ newMax,
+ newMin,
+ navigatorSeries = navigator.series && navigator.series[0],
+ hasSetExtremes = !!baseXAxis.setExtremes,
+
+ // When the extremes have been set by range selector button, don't
+ // stick to min or max. The range selector buttons will handle the
+ // extremes. (#5489)
+ unmutable = baseXAxis.eventArgs &&
+ baseXAxis.eventArgs.trigger === 'rangeSelectorButton';
+
+ if (!unmutable) {
+
+ // If the zoomed range is already at the min, move it to the right
+ // as new data comes in
+ if (stickToMin) {
+ newMin = baseDataMin;
+ newMax = newMin + range;
+ }
+
+ // If the zoomed range is already at the max, move it to the right
+ // as new data comes in
+ if (stickToMax) {
+ newMax = baseDataMax + overscroll;
+
+ // if stickToMin is true, the new min value is set above
+ if (!stickToMin) {
+ newMin = Math.max(
+ newMax - range,
+ navigatorSeries && navigatorSeries.xData ?
+ navigatorSeries.xData[0] : -Number.MAX_VALUE
+ );
+ }
+ }
+
+ // Update the extremes
+ if (hasSetExtremes && (stickToMin || stickToMax)) {
+ if (isNumber(newMin)) {
+ baseXAxis.min = baseXAxis.userMin = newMin;
+ baseXAxis.max = baseXAxis.userMax = newMax;
+ }
+ }
+ }
+
+ // Reset
+ navigator.stickToMin = navigator.stickToMax = null;
+ },
+
+ /**
+ * Handler for updated data on the base series. When data is modified, the
+ * navigator series must reflect it. This is called from the Chart.redraw
+ * function before axis and series extremes are computed.
+ */
+ updatedDataHandler: function () {
+ var navigator = this.chart.navigator,
+ baseSeries = this,
+ navigatorSeries = this.navigatorSeries;
+
+ // If the scrollbar is scrolled all the way to the right, keep right as
+ // new data comes in.
+ navigator.stickToMax =
+ Math.round(navigator.zoomedMax) >= Math.round(navigator.size);
+
+ // Detect whether the zoomed area should stick to the minimum or
+ // maximum. If the current axis minimum falls outside the new updated
+ // dataset, we must adjust.
+ navigator.stickToMin = isNumber(baseSeries.xAxis.min) &&
+ (baseSeries.xAxis.min <= baseSeries.xData[0]) &&
+ (!this.chart.fixedRange || !navigator.stickToMax);
+
+ // Set the navigator series data to the new data of the base series
+ if (navigatorSeries && !navigator.hasNavigatorData) {
+ navigatorSeries.options.pointStart = baseSeries.xData[0];
+ navigatorSeries.setData(
+ baseSeries.options.data,
+ false,
+ null,
+ false
+ ); // #5414
+ }
+ },
+
+ /**
+ * Add chart events, like redrawing navigator, when chart requires that.
+ */
+ addChartEvents: function () {
+ addEvent(this.chart, 'redraw', function () {
+ // Move the scrollbar after redraw, like after data updata even if
+ // axes don't redraw
+ var navigator = this.navigator,
+ xAxis = navigator && (
+ navigator.baseSeries &&
+ navigator.baseSeries[0] &&
+ navigator.baseSeries[0].xAxis ||
+ navigator.scrollbar && this.xAxis[0]
+ ); // #5709
+
+ if (xAxis) {
+ navigator.render(xAxis.min, xAxis.max);
+ }
+ });
+ },
+
+ /**
+ * Destroys allocated elements.
+ */
+ destroy: function () {
+
+ // Disconnect events added in addEvents
+ this.removeEvents();
+
+ if (this.xAxis) {
+ erase(this.chart.xAxis, this.xAxis);
+ erase(this.chart.axes, this.xAxis);
+ }
+ if (this.yAxis) {
+ erase(this.chart.yAxis, this.yAxis);
+ erase(this.chart.axes, this.yAxis);
+ }
+ // Destroy series
+ each(this.series || [], function (s) {
+ if (s.destroy) {
+ s.destroy();
+ }
+ });
+
+ // Destroy properties
+ each([
+ 'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack',
+ 'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup',
+ 'rendered'
+ ], function (prop) {
+ if (this[prop] && this[prop].destroy) {
+ this[prop].destroy();
+ }
+ this[prop] = null;
+ }, this);
+
+ // Destroy elements in collection
+ each([this.handles], function (coll) {
+ destroyObjectProperties(coll);
+ }, this);
+ }
+};
+
+H.Navigator = Navigator;
+
+/**
+ * For Stock charts, override selection zooming with some special features
+ * because X axis zooming is already allowed by the Navigator and Range
+ * selector.
+ */
+wrap(Axis.prototype, 'zoom', function (proceed, newMin, newMax) {
+ var chart = this.chart,
+ chartOptions = chart.options,
+ zoomType = chartOptions.chart.zoomType,
+ previousZoom,
+ navigator = chartOptions.navigator,
+ rangeSelector = chartOptions.rangeSelector,
+ ret;
+
+ if (this.isXAxis && ((navigator && navigator.enabled) ||
+ (rangeSelector && rangeSelector.enabled))) {
+
+ // For x only zooming, fool the chart.zoom method not to create the zoom
+ // button because the property already exists
+ if (zoomType === 'x') {
+ chart.resetZoomButton = 'blocked';
+
+ // For y only zooming, ignore the X axis completely
+ } else if (zoomType === 'y') {
+ ret = false;
+
+ // For xy zooming, record the state of the zoom before zoom selection,
+ // then when the reset button is pressed, revert to this state. This
+ // should apply only if the chart is initialized with a range (#6612),
+ // otherwise zoom all the way out.
+ } else if (zoomType === 'xy' && this.options.range) {
+
+ previousZoom = this.previousZoom;
+ if (defined(newMin)) {
+ this.previousZoom = [this.min, this.max];
+ } else if (previousZoom) {
+ newMin = previousZoom[0];
+ newMax = previousZoom[1];
+ delete this.previousZoom;
+ }
+ }
+
+ }
+ return ret !== undefined ? ret : proceed.call(this, newMin, newMax);
+});
+
+// Initialize navigator for stock charts
+wrap(Chart.prototype, 'init', function (proceed, options, callback) {
+
+ addEvent(this, 'beforeRender', function () {
+ var options = this.options;
+ if (options.navigator.enabled || options.scrollbar.enabled) {
+ this.scroller = this.navigator = new Navigator(this);
+ }
+ });
+
+ proceed.call(this, options, callback);
+
+});
+
+/**
+ * For stock charts, extend the Chart.setChartSize method so that we can set the
+ * final top position of the navigator once the height of the chart, including
+ * the legend, is determined. #367. We can't use Chart.getMargins, because
+ * labels offsets are not calculated yet.
+ */
+wrap(Chart.prototype, 'setChartSize', function (proceed) {
+
+ var legend = this.legend,
+ navigator = this.navigator,
+ scrollbarHeight,
+ legendOptions,
+ xAxis,
+ yAxis;
+
+ proceed.apply(this, [].slice.call(arguments, 1));
+
+ if (navigator) {
+ legendOptions = legend && legend.options;
+ xAxis = navigator.xAxis;
+ yAxis = navigator.yAxis;
+ scrollbarHeight = navigator.scrollbarHeight;
+
+ // Compute the top position
+ if (this.inverted) {
+ navigator.left = navigator.opposite ?
+ this.chartWidth - scrollbarHeight - navigator.height :
+ this.spacing[3] + scrollbarHeight;
+ navigator.top = this.plotTop + scrollbarHeight;
+ } else {
+ navigator.left = this.plotLeft + scrollbarHeight;
+ navigator.top = navigator.navigatorOptions.top ||
+ this.chartHeight -
+ navigator.height -
+ scrollbarHeight -
+ this.spacing[2] -
+ (
+ this.rangeSelector && this.extraBottomMargin ?
+ this.rangeSelector.getHeight() :
+ 0
+ ) -
+ (
+ (
+ legendOptions &&
+ legendOptions.verticalAlign === 'bottom' &&
+ legendOptions.enabled &&
+ !legendOptions.floating
+ ) ?
+ legend.legendHeight + pick(legendOptions.margin, 10) :
+ 0
+ );
+ }
+
+ if (xAxis && yAxis) { // false if navigator is disabled (#904)
+
+ if (this.inverted) {
+ xAxis.options.left = yAxis.options.left = navigator.left;
+ } else {
+ xAxis.options.top = yAxis.options.top = navigator.top;
+ }
+
+ xAxis.setAxisSize();
+ yAxis.setAxisSize();
+ }
+ }
+});
+
+// Pick up badly formatted point options to addPoint
+wrap(Series.prototype, 'addPoint', function (
+ proceed,
+ options,
+ redraw,
+ shift,
+ animation
+) {
+ var turboThreshold = this.options.turboThreshold;
+ if (
+ turboThreshold &&
+ this.xData.length > turboThreshold &&
+ isObject(options, true) &&
+ this.chart.navigator
+ ) {
+ error(20, true);
+ }
+ proceed.call(this, options, redraw, shift, animation);
+});
+
+// Handle adding new series
+wrap(Chart.prototype, 'addSeries', function (
+ proceed,
+ options,
+ redraw,
+ animation
+) {
+ var series = proceed.call(this, options, false, animation);
+ if (this.navigator) {
+ // Recompute which series should be shown in navigator, and add them
+ this.navigator.setBaseSeries(null, false);
+ }
+ if (pick(redraw, true)) {
+ this.redraw();
+ }
+ return series;
+});
+
+// Handle updating series
+wrap(Series.prototype, 'update', function (proceed, newOptions, redraw) {
+ proceed.call(this, newOptions, false);
+ if (this.chart.navigator && !this.options.isInternal) {
+ this.chart.navigator.setBaseSeries(null, false);
+ }
+ if (pick(redraw, true)) {
+ this.chart.redraw();
+ }
+});
+
+Chart.prototype.callbacks.push(function (chart) {
+ var extremes,
+ navigator = chart.navigator;
+
+ // Initiate the navigator
+ if (navigator) {
+ extremes = chart.xAxis[0].getExtremes();
+ navigator.render(extremes.min, extremes.max);
+ }
+});
+
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var addEvent = H.addEvent,
+ Axis = H.Axis,
+ Chart = H.Chart,
+ css = H.css,
+ createElement = H.createElement,
+ defaultOptions = H.defaultOptions,
+ defined = H.defined,
+ destroyObjectProperties = H.destroyObjectProperties,
+ discardElement = H.discardElement,
+ each = H.each,
+ extend = H.extend,
+ fireEvent = H.fireEvent,
+ isNumber = H.isNumber,
+ merge = H.merge,
+ pick = H.pick,
+ pInt = H.pInt,
+ splat = H.splat,
+ wrap = H.wrap;
+
+/* ****************************************************************************
+ * Start Range Selector code *
+ *****************************************************************************/
+extend(defaultOptions, {
+
+ /**
+ * The range selector is a tool for selecting ranges to display within
+ * the chart. It provides buttons to select preconfigured ranges in
+ * the chart, like 1 day, 1 week, 1 month etc. It also provides input
+ * boxes where min and max dates can be manually input.
+ *
+ * @product highstock
+ * @optionparent rangeSelector
+ */
+ rangeSelector: {
+ // allButtonsEnabled: false,
+ // enabled: true,
+ // buttons: {Object}
+ // buttonSpacing: 0,
+
+ /**
+ * The vertical alignment of the rangeselector box. Allowed properties are `top`,
+ * `middle`, `bottom`.
+ *
+ * @since 6.0.0
+ *
+ * @sample {highstock} stock/rangeselector/vertical-align-middle/ Middle
+ *
+ * @sample {highstock} stock/rangeselector/vertical-align-bottom/ Bottom
+ */
+ verticalAlign: 'top',
+
+ /**
+ * A collection of attributes for the buttons. The object takes SVG
+ * attributes like `fill`, `stroke`, `stroke-width`, as well as `style`,
+ * a collection of CSS properties for the text.
+ *
+ * The object can also be extended with states, so you can set presentational
+ * options for `hover`, `select` or `disabled` button states.
+ *
+ * CSS styles for the text label.
+ *
+ * In styled mode, the buttons are styled by the `.highcharts-
+ * range-selector-buttons .highcharts-button` rule with its different
+ * states.
+ *
+ * @type {Object}
+ * @sample {highstock} stock/rangeselector/styling/ Styling the buttons and inputs
+ * @product highstock
+ */
+ buttonTheme: {
+ 'stroke-width': 0,
+ width: 28,
+ height: 18,
+ padding: 2,
+ zIndex: 7 // #484, #852
+ },
+
+ /**
+ * When the rangeselector is floating, the plot area does not reserve
+ * space for it. This opens for positioning anywhere on the chart.
+ *
+ * @sample {highstock} stock/rangeselector/floating/
+ * Placing the range selector between the plot area and the
+ * navigator
+ * @since 6.0.0
+ * @product highstock
+ */
+ floating: false,
+
+ /**
+ * The x offset of the range selector relative to its horizontal
+ * alignment within `chart.spacingLeft` and `chart.spacingRight`.
+ *
+ * @since 6.0.0
+ * @product highstock
+ */
+ x: 0,
+
+ /**
+ * The y offset of the range selector relative to its horizontal
+ * alignment within `chart.spacingLeft` and `chart.spacingRight`.
+ *
+ * @since 6.0.0
+ * @product highstock
+ */
+ y: 0,
+
+ /**
+ * Deprecated. The height of the range selector. Currently it is
+ * calculated dynamically.
+ *
+ * @type {Number}
+ * @default undefined
+ * @since 2.1.9
+ * @product highstock
+ * @deprecated true
+ */
+ height: undefined, // reserved space for buttons and input
+
+ /**
+ * Positioning for the input boxes. Allowed properties are `align`,
+ * `x` and `y`.
+ *
+ * @type {Object}
+ * @default { align: "right" }
+ * @since 1.2.4
+ * @product highstock
+ */
+ inputPosition: {
+ /**
+ * The alignment of the input box. Allowed properties are `left`,
+ * `center`, `right`.
+ * @validvalue ["left", "center", "right"]
+ * @sample {highstock} stock/rangeselector/input-button-position/
+ * Alignment
+ * @since 6.0.0
+ */
+ align: 'right',
+ x: 0,
+ y: 0
+ },
+
+ /**
+ * Positioning for the button row.
+ *
+ * @since 1.2.4
+ * @product highstock
+ */
+ buttonPosition: {
+ /**
+ * The alignment of the input box. Allowed properties are `left`,
+ * `center`, `right`.
+ *
+ * @validvalue ["left", "center", "right"]
+ * @sample {highstock} stock/rangeselector/input-button-position/
+ * Alignment
+ * @since 6.0.0
+ */
+ align: 'left',
+ /**
+ * X offset of the button row.
+ */
+ x: 0,
+ /**
+ * Y offset of the button row.
+ */
+ y: 0
+ },
+ // inputDateFormat: '%b %e, %Y',
+ // inputEditDateFormat: '%Y-%m-%d',
+ // inputEnabled: true,
+ // selected: undefined,
+
+ // inputStyle: {},
+
+ /**
+ * CSS styles for the labels - the Zoom, From and To texts.
+ *
+ * In styled mode, the labels are styled by the `.highcharts-range-label` class.
+ *
+ * @type {CSSObject}
+ * @sample {highstock} stock/rangeselector/styling/ Styling the buttons and inputs
+ * @product highstock
+ */
+ labelStyle: {
+ color: '#666666'
+ }
+
+ }
+});
+
+defaultOptions.lang = merge(
+ defaultOptions.lang,
+ /**
+ * Language object. The language object is global and it can't be set
+ * on each chart initiation. Instead, use `Highcharts.setOptions` to
+ * set it before any chart is initialized.
+ *
+ * Highcharts.setOptions({
+ * lang: {
+ * months: [
+ * 'Janvier', 'Février', 'Mars', 'Avril',
+ * 'Mai', 'Juin', 'Juillet', 'Août',
+ * 'Septembre', 'Octobre', 'Novembre', 'Décembre'
+ * ],
+ * weekdays: [
+ * 'Dimanche', 'Lundi', 'Mardi', 'Mercredi',
+ * 'Jeudi', 'Vendredi', 'Samedi'
+ * ]
+ * }
+ * });
+ *
+ * @optionparent lang
+ * @product highstock
+ */
+ {
+
+ /**
+ * The text for the label for the range selector buttons.
+ *
+ * @type {String}
+ * @default Zoom
+ * @product highstock
+ */
+ rangeSelectorZoom: 'Zoom',
+
+ /**
+ * The text for the label for the "from" input box in the range
+ * selector.
+ *
+ * @type {String}
+ * @default From
+ * @product highstock
+ */
+ rangeSelectorFrom: 'From',
+
+ /**
+ * The text for the label for the "to" input box in the range selector.
+ *
+ * @type {String}
+ * @default To
+ * @product highstock
+ */
+ rangeSelectorTo: 'To'
+ }
+);
+
+/**
+ * The range selector.
+ * @class
+ * @param {Object} chart
+ */
+function RangeSelector(chart) {
+
+ // Run RangeSelector
+ this.init(chart);
+}
+
+RangeSelector.prototype = {
+ /**
+ * The method to run when one of the buttons in the range selectors is clicked
+ * @param {Number} i The index of the button
+ * @param {Object} rangeOptions
+ * @param {Boolean} redraw
+ */
+ clickButton: function (i, redraw) {
+ var rangeSelector = this,
+ chart = rangeSelector.chart,
+ rangeOptions = rangeSelector.buttonOptions[i],
+ baseAxis = chart.xAxis[0],
+ unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis || {},
+ dataMin = unionExtremes.dataMin,
+ dataMax = unionExtremes.dataMax,
+ newMin,
+ newMax = baseAxis && Math.round(Math.min(baseAxis.max, pick(dataMax, baseAxis.max))), // #1568
+ type = rangeOptions.type,
+ baseXAxisOptions,
+ range = rangeOptions._range,
+ rangeMin,
+ minSetting,
+ rangeSetting,
+ ctx,
+ ytdExtremes,
+ dataGrouping = rangeOptions.dataGrouping;
+
+ if (dataMin === null || dataMax === null) { // chart has no data, base series is removed
+ return;
+ }
- (c) 2009-2014 Torstein Honsi
+ // Set the fixed range before range is altered
+ chart.fixedRange = range;
+
+ // Apply dataGrouping associated to button
+ if (dataGrouping) {
+ this.forcedDataGrouping = true;
+ Axis.prototype.setDataGrouping.call(baseAxis || { chart: this.chart }, dataGrouping, false);
+ }
- License: www.highcharts.com/license
-*/
-(function(){function s(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function y(){var a,b=arguments,c,d={},e=function(a,b){var c,d;typeof a!=="object"&&(a={});for(d in b)b.hasOwnProperty(d)&&(c=b[d],a[d]=c&&typeof c==="object"&&Object.prototype.toString.call(c)!=="[object Array]"&&d!=="renderTo"&&typeof c.nodeType!=="number"?e(a[d]||{},c):b[d]);return a};b[0]===!0&&(d=b[1],b=Array.prototype.slice.call(b,2));c=b.length;for(a=0;a3?k.length%3:0;return e!==Da?e(a,b,c,d):j+(l?k.substr(0,l)+f:"")+k.substr(l).replace(/(\d{3})(?=\d)/g,"$1"+f)+(h?i+P(g-k).toFixed(h).slice(2):"")}function Ta(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function Q(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments);
-a.unshift(d);return c.apply(this,a)}}function Ma(a,b){for(var c="{",d=!1,e,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(":");g=f.shift().split(".");i=g.length;e=b;for(h=0;h-1?h.thousandsSep:""))):e=xa(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?"}":"{"}j.push(a);return j.join("")}function vb(a){return X.pow(10,V(X.log(a)/
-X.LN10))}function wb(a,b,c,d){var e,c=q(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d===!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;dc&&(c=a[b]);return c}
-function Na(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Va(a){kb||(kb=aa(Wa));a&&kb.appendChild(a);kb.innerHTML=""}function ja(a){return parseFloat(a.toPrecision(14))}function ab(a,b){Fa=q(a,b.animation)}function Mb(){var a=F.global.useUTC,b=a?"getUTC":"get",c=a?"setUTC":"set";ca=F.global.Date||window.Date;Oa=(a&&F.global.timezoneOffset||0)*6E4;lb=a?ca.UTC:function(a,b,c,g,h,i){return(new ca(a,b,q(c,1),q(g,0),q(h,0),q(i,0))).getTime()};yb=b+"Minutes";zb=b+
-"Hours";Ab=b+"Day";Xa=b+"Date";mb=b+"Month";nb=b+"FullYear";Nb=c+"Minutes";Ob=c+"Hours";Bb=c+"Date";Pb=c+"Month";Qb=c+"FullYear"}function Z(){}function bb(a,b,c,d){this.axis=a;this.pos=b;this.type=c||"";this.isNew=!0;!c&&!d&&this.addLabel()}function D(){this.init.apply(this,arguments)}function ya(){this.init.apply(this,arguments)}function Rb(a,b,c,d,e){var f=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.alignOptions={align:b.align||
-(f?c?"left":"right":"center"),verticalAlign:b.verticalAlign||(f?"middle":c?"bottom":"top"),y:q(b.y,f?4:c?14:-6),x:q(b.x,f?c?-6:6:0)};this.textAlign=b.textAlign||(f?c?"right":"left":"center")}function Cb(a){var b=a.options,c=b.navigator,d=c.enabled,b=b.scrollbar,e=b.enabled,f=d?c.height:0,g=e?b.height:0;this.handles=[];this.scrollbarButtons=[];this.elementsToDestroy=[];this.chart=a;this.setBaseSeries();this.height=f;this.scrollbarHeight=g;this.scrollbarEnabled=e;this.navigatorEnabled=d;this.navigatorOptions=
-c;this.scrollbarOptions=b;this.outlineHeight=f+g;this.init()}function Db(a){this.init(a)}var r,E=document,S=window,X=Math,t=X.round,V=X.floor,Ya=X.ceil,v=X.max,A=X.min,P=X.abs,fa=X.cos,ka=X.sin,ua=X.PI,Pa=ua*2/360,Ga=navigator.userAgent,Sb=S.opera,La=/msie/i.test(Ga)&&!Sb,ob=E.documentMode===8,Eb=/AppleWebKit/.test(Ga),cb=/Firefox/.test(Ga),fb=/(Mobile|Android|Windows Phone)/.test(Ga),Ha="http://www.w3.org/2000/svg",ea=!!E.createElementNS&&!!E.createElementNS(Ha,"svg").createSVGRect,Yb=cb&&parseInt(Ga.split("Firefox/")[1],
-10)<4,la=!ea&&!La&&!!E.createElement("canvas").getContext,Za,db,Tb={},Fb=0,kb,F,xa,Fa,Gb,G,ma,ra=function(){return r},ba=[],gb=0,Wa="div",$="none",Zb=/^[0-9]+$/,$b="stroke-width",ca,lb,Oa,yb,zb,Ab,Xa,mb,nb,Nb,Ob,Bb,Pb,Qb,B={},N;S.Highcharts?ma(16,!0):N=S.Highcharts={};xa=function(a,b,c){if(!u(b)||isNaN(b))return"Invalid date";var a=q(a,"%Y-%m-%d %H:%M:%S"),d=new ca(b-Oa),e,f=d[zb](),g=d[Ab](),h=d[Xa](),i=d[mb](),j=d[nb](),k=F.lang,l=k.weekdays,d=s({a:l[g].substr(0,3),A:l[g],d:Ta(h),e:h,b:k.shortMonths[i],
-B:k.months[i],m:Ta(i+1),y:j.toString().substr(2,2),Y:j,H:Ta(f),I:Ta(f%12||12),l:f%12||12,M:Ta(d[yb]()),p:f<12?"AM":"PM",P:f<12?"am":"pm",S:Ta(d.getSeconds()),L:Ta(t(b%1E3),3)},N.dateFormats);for(e in d)for(;a.indexOf("%"+e)!==-1;)a=a.replace("%"+e,typeof d[e]==="function"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+a.substr(1):a};ma=function(a,b){var c="Highcharts error #"+a+": www.highcharts.com/errors/"+a;if(b)throw c;S.console&&console.log(c)};G={millisecond:1,second:1E3,minute:6E4,hour:36E5,
-day:864E5,week:6048E5,month:26784E5,year:31556952E3};Gb={init:function(a,b,c){var b=b||"",d=a.shift,e=b.indexOf("C")>-1,f=e?7:3,g,b=b.split(" "),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]==="M"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length{point.key} ',pointFormat:'● {series.name}: {point.y} ',shadow:!0,snap:fb?25:10,style:{color:"#333333",cursor:"default",fontSize:"12px",padding:"8px",whiteSpace:"nowrap"}},credits:{enabled:!0,text:"Highcharts.com",href:"http://www.highcharts.com",position:{align:"right",x:-10,verticalAlign:"bottom",
-y:-5},style:{cursor:"pointer",color:"#909090",fontSize:"9px"}}};var W=F.plotOptions,L=W.line;Mb();var dc=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,ec=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/,fc=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,Ia=function(a){var b=[],c,d;(function(a){a&&a.stops?d=za(a.stops,function(a){return Ia(a[1])}):(c=dc.exec(a))?b=[H(c[1]),H(c[2]),H(c[3]),parseFloat(c[4],10)]:(c=ec.exec(a))?b=[H(c[1],
-16),H(c[2],16),H(c[3],16),1]:(c=fc.exec(a))&&(b=[H(c[1]),H(c[2]),H(c[3]),1])})(a);return{get:function(c){var f;d?(f=y(a),f.stops=[].concat(f.stops),p(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c==="rgb"?"rgb("+b[0]+","+b[1]+","+b[2]+")":c==="a"?b[3]:"rgba("+b.join(",")+")":a;return f},brighten:function(a){if(d)p(d,function(b){b.brighten(a)});else if(pa(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=H(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=
-a;return this}}};Z.prototype={opacity:1,textProps:"fontSize,fontWeight,fontFamily,color,lineHeight,width,textDecoration,textShadow,HcTextStroke".split(","),init:function(a,b){this.element=b==="span"?aa(b):E.createElementNS(Ha,b);this.renderer=a},animate:function(a,b,c){b=q(b,Fa,!0);hb(this);if(b){b=y(b,{});if(c)b.complete=c;rb(this,a,b)}else this.attr(a),c&&c();return this},colorGradient:function(a,b,c){var d=this.renderer,e,f,g,h,i,j,k,l,m,n,o=[];a.linearGradient?f="linearGradient":a.radialGradient&&
-(f="radialGradient");if(f){g=a[f];h=d.gradients;j=a.stops;m=c.radialReference;Ja(g)&&(a[f]=g={x1:g[0],y1:g[1],x2:g[2],y2:g[3],gradientUnits:"userSpaceOnUse"});f==="radialGradient"&&m&&!u(g.gradientUnits)&&(g=y(g,{cx:m[0]-m[2]/2+g.cx*m[2],cy:m[1]-m[2]/2+g.cy*m[2],r:g.r*m[2],gradientUnits:"userSpaceOnUse"}));for(n in g)n!=="id"&&o.push(n,g[n]);for(n in j)o.push(j[n]);o=o.join(",");h[o]?a=h[o].attr("id"):(g.id=a="highcharts-"+Fb++,h[o]=i=d.createElement(f).attr(g).add(d.defs),i.stops=[],p(j,function(a){a[1].indexOf("rgba")===
-0?(e=Ia(a[1]),k=e.get("rgb"),l=e.get("a")):(k=a[1],l=1);a=d.createElement("stop").attr({offset:a[0],"stop-color":k,"stop-opacity":l}).add(i);i.stops.push(a)}));c.setAttribute(b,"url("+d.url+"#"+a+")")}},attr:function(a,b){var c,d,e=this.element,f,g=this,h;typeof a==="string"&&b!==r&&(c=a,a={},a[c]=b);if(typeof a==="string")g=(this[a+"Getter"]||this._defaultGetter).call(this,a,e);else{for(c in a){d=a[c];h=!1;this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(f||(this.symbolAttr(a),
-f=!0),h=!0);if(this.rotation&&(c==="x"||c==="y"))this.doTransform=!0;h||(this[c+"Setter"]||this._defaultSetter).call(this,d,c,e);this.shadows&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c)&&this.updateShadows(c,d)}if(this.doTransform)this.updateTransform(),this.doTransform=!1}return g},updateShadows:function(a,b){for(var c=this.shadows,d=c.length;d--;)c[d].setAttribute(a,a==="height"?v(b-(c[d].cutHeight||0),0):a==="d"?this.d:b)},addClass:function(a){var b=this.element,c=Y(b,"class")||
-"";c.indexOf(a)===-1&&Y(b,"class",c+" "+a);return this},symbolAttr:function(a){var b=this;p("x,y,r,start,end,width,height,innerR,anchorX,anchorY".split(","),function(c){b[c]=q(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,b.y,b.width,b.height,b)})},clip:function(a){return this.attr("clip-path",a?"url("+this.renderer.url+"#"+a.id+")":$)},crisp:function(a){var b,c={},d,e=a.strokeWidth||this.strokeWidth||0;d=t(e)%2/2;a.x=V(a.x||this.x||0)+d;a.y=V(a.y||this.y||0)+d;a.width=V((a.width||this.width||
-0)-2*d);a.height=V((a.height||this.height||0)-2*d);a.strokeWidth=e;for(b in a)this[b]!==a[b]&&(this[b]=c[b]=a[b]);return c},css:function(a){var b=this.styles,c={},d=this.element,e,f,g="";e=!b;if(a&&a.color)a.fill=a.color;if(b)for(f in a)a[f]!==b[f]&&(c[f]=a[f],e=!0);if(e){e=this.textWidth=a&&a.width&&d.nodeName.toLowerCase()==="text"&&H(a.width);b&&(a=s(b,c));this.styles=a;e&&(la||!ea&&this.renderer.forExport)&&delete a.width;if(La&&!ea)K(this.element,a);else{b=function(a,b){return"-"+b.toLowerCase()};
-for(f in a)g+=f.replace(/([A-Z])/g,b)+":"+a[f]+";";Y(d,"style",g)}e&&this.added&&this.renderer.buildText(this)}return this},on:function(a,b){var c=this,d=c.element;db&&a==="click"?(d.ontouchstart=function(a){c.touchEventFired=ca.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(Ga.indexOf("Android")===-1||ca.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d["on"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a,
-translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=this.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation,g=this.element;e&&(a+=this.attr("width"),b+=this.attr("height"));a=["translate("+a+","+b+")"];e?a.push("rotate(90) scale(-1,1)"):f&&a.push("rotate("+f+" "+(g.getAttribute("x")||0)+" "+(g.getAttribute("y")||0)+")");(u(c)||u(d))&&a.push("scale("+q(c,1)+" "+q(d,1)+")");a.length&&g.setAttribute("transform",
-a.join(" "))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=a,this.alignByTranslate=b,!c||Sa(c))this.alignTo=d=c||"renderer",ta(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=q(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d==="right"||d==="center")f+=(c.width-(a.width||0))/{right:1,center:2}[d];
-h[b?"translateX":"x"]=t(f);if(e==="bottom"||e==="middle")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?"translateY":"y"]=t(g);this[this.placed?"animate":"attr"](h);this.placed=!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d,e=this.rotation;c=this.element;var f=this.styles,g=e*Pa;d=this.textStr;var h;if(d===""||Zb.test(d))h="num."+d.toString().length+(f?"|"+f.fontSize+"|"+f.fontFamily:"");h&&(a=b.cache[h]);if(!a){if(c.namespaceURI===Ha||b.forExport){try{a=
-c.getBBox?s({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(i){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){c=a.width;d=a.height;if(La&&f&&f.fontSize==="11px"&&d.toPrecision(3)==="16.9")a.height=d=14;if(e)a.width=P(d*ka(g))+P(c*fa(g)),a.height=P(d*fa(g))+P(c*ka(g))}this.bBox=a;h&&(b.cache[h]=a)}return a},show:function(a){a&&this.element.namespaceURI===Ha?this.element.removeAttribute("visibility"):this.attr({visibility:a?"inherit":"visible"});return this},
-hide:function(){return this.attr({visibility:"hidden"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.attr({y:-9999})}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=this.element,f=this.zIndex,g,h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(f)c.handleZ=!0,f=H(f);if(c.handleZ){a=d.childNodes;for(g=0;gf||!u(f)&&u(c))){d.insertBefore(e,
-b);h=!0;break}}h||d.appendChild(e);this.added=!0;if(this.onAdd)this.onAdd();return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName==="SPAN"&&a.parentGroup,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;hb(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f]*>/g,"")},textSetter:function(a){if(a!==this.textStr)delete this.bBox,this.textStr=a,this.added&&this.renderer.buildText(this)},fillSetter:function(a,b,
-c){typeof a==="string"?c.setAttribute(b,a):a&&this.colorGradient(a,b,c)},zIndexSetter:function(a,b,c){c.setAttribute(b,a);this[b]=a},_defaultSetter:function(a,b,c){c.setAttribute(b,a)}};Z.prototype.yGetter=Z.prototype.xGetter;Z.prototype.translateXSetter=Z.prototype.translateYSetter=Z.prototype.rotationSetter=Z.prototype.verticalAlignSetter=Z.prototype.scaleXSetter=Z.prototype.scaleYSetter=function(a,b){this[b]=a;this.doTransform=!0};Z.prototype["stroke-widthSetter"]=Z.prototype.strokeSetter=function(a,
-b,c){this[b]=a;if(this.stroke&&this["stroke-width"])this.strokeWidth=this["stroke-width"],Z.prototype.fillSetter.call(this,this.stroke,"stroke",c),c.setAttribute("stroke-width",this["stroke-width"]),this.hasStroke=!0;else if(b==="stroke-width"&&a===0&&this.hasStroke)c.removeAttribute("stroke"),this.hasStroke=!1};var na=function(){this.init.apply(this,arguments)};na.prototype={Element:Z,init:function(a,b,c,d,e){var f=location,g,d=this.createElement("svg").attr({version:"1.1"}).css(this.getStyle(d));
-g=d.element;a.appendChild(g);a.innerHTML.indexOf("xmlns")===-1&&Y(g,"xmlns",Ha);this.isSVG=!0;this.box=g;this.boxWrapper=d;this.alignedObjects=[];this.url=(cb||Eb)&&E.getElementsByTagName("base").length?f.href.replace(/#.*?$/,"").replace(/([\('\)])/g,"\\$1").replace(/ /g,"%20"):"";this.createElement("desc").add().element.appendChild(E.createTextNode("Created with Highstock 2.0.4"));this.defs=this.createElement("defs").add();this.forExport=e;this.gradients={};this.cache={};this.setSize(b,c,!1);var h;
-if(cb&&a.getBoundingClientRect)this.subPixelFix=b=function(){K(a,{left:0,top:0});h=a.getBoundingClientRect();K(a,{left:Ya(h.left)-h.left+"px",top:Ya(h.top)-h.top+"px"})},b(),z(S,"resize",b)},getStyle:function(a){return this.style=s({fontFamily:'"Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif',fontSize:"12px"},a)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Na(this.gradients||
-{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&U(S,"resize",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=q(a.textStr,"").toString(),f=e.indexOf("<")!==-1,g=b.childNodes,h,i,j=Y(b,"x"),k=a.styles,l=a.textWidth,m=k&&k.lineHeight,n=k&&k.HcTextStroke,o=g.length,x=function(a){return m?H(m):c.fontMetrics(/(px|em)$/.test(a&&
-a.style.fontSize)?a.style.fontSize:k&&k.fontSize||c.style.fontSize||12,a).h};o--;)b.removeChild(g[o]);!f&&!n&&e.indexOf(" ")===-1?b.appendChild(E.createTextNode(e)):(h=/<.*style="([^"]+)".*>/,i=/<.*href="(http[^"]+)".*>/,l&&!a.added&&this.box.appendChild(b),e=f?e.replace(/<(b|strong)>/g,'').replace(/<(i|em)>/g,'').replace(//g," ").split(//g):[e],e[e.length-1]===""&&e.pop(),p(e,function(e,
-f){var g,m=0,e=e.replace(//g," |||");g=e.split("|||");p(g,function(e){if(e!==""||g.length===1){var n={},o=E.createElementNS(Ha,"tspan"),I;h.test(e)&&(I=e.match(h)[1].replace(/(;| |^)color([ :])/,"$1fill$2"),Y(o,"style",I));i.test(e)&&!d&&(Y(o,"onclick",'location.href="'+e.match(i)[1]+'"'),K(o,{cursor:"pointer"}));e=(e.replace(/<(.|\n)*?>/g,"")||" ").replace(/</g,"<").replace(/>/g,">");if(e!==" "){o.appendChild(E.createTextNode(e));if(m)n.dx=0;else if(f&&
-j!==null)n.x=j;Y(o,n);b.appendChild(o);!m&&f&&(!ea&&d&&K(o,{display:"block"}),Y(o,"dy",x(o)));if(l)for(var e=e.replace(/([^\^])-/g,"$1- ").split(" "),n=g.length>1||e.length>1&&k.whiteSpace!=="nowrap",q,p,r=k.HcHeight,t=[],u=x(o),Ub=1;n&&(e.length||t.length);)delete a.bBox,q=a.getBBox(),p=q.width,!ea&&c.forExport&&(p=c.measureSpanWidth(o.firstChild.data,a.styles)),q=p>l,!q||e.length===1?(e=t,t=[],e.length&&(Ub++,r&&Ub*u>r?(e=["..."],a.attr("title",a.textStr)):(o=E.createElementNS(Ha,"tspan"),Y(o,{dy:u,
-x:j}),I&&Y(o,"style",I),b.appendChild(o))),p>l&&(l=p)):(o.removeChild(o.firstChild),t.unshift(e.pop())),e.length&&o.appendChild(E.createTextNode(e.join(" ").replace(/- /g,"-")));m++}}})}))},button:function(a,b,c,d,e,f,g,h,i){var j=this.label(a,b,c,i,null,null,null,null,"button"),k=0,l,m,n,o,x,I,a={x1:0,y1:0,x2:0,y2:1},e=y({"stroke-width":1,stroke:"#CCCCCC",fill:{linearGradient:a,stops:[[0,"#FEFEFE"],[1,"#F6F6F6"]]},r:2,padding:5,style:{color:"black"}},e);n=e.style;delete e.style;f=y(e,{stroke:"#68A",
-fill:{linearGradient:a,stops:[[0,"#FFF"],[1,"#ACF"]]}},f);o=f.style;delete f.style;g=y(e,{stroke:"#68A",fill:{linearGradient:a,stops:[[0,"#9BD"],[1,"#CDF"]]}},g);x=g.style;delete g.style;h=y(e,{style:{color:"#CCC"}},h);I=h.style;delete h.style;z(j.element,La?"mouseover":"mouseenter",function(){k!==3&&j.attr(f).css(o)});z(j.element,La?"mouseout":"mouseleave",function(){k!==3&&(l=[e,f,g][k],m=[n,o,x][k],j.attr(l).css(m))});j.setState=function(a){(j.state=k=a)?a===2?j.attr(g).css(x):a===3&&j.attr(h).css(I):
-j.attr(e).css(n)};return j.on("click",function(){k!==3&&d.call(j)}).attr(e).css(s({cursor:"default"},n))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=t(a[1])-b%2/2);a[2]===a[5]&&(a[2]=a[5]=t(a[2])+b%2/2);return a},path:function(a){var b={fill:$};Ja(a)?b.d=a:ha(a)&&s(b,a);return this.createElement("path").attr(b)},circle:function(a,b,c){a=ha(a)?a:{x:a,y:b,r:c};b=this.createElement("circle");b.xSetter=function(a){this.element.setAttribute("cx",a)};b.ySetter=function(a){this.element.setAttribute("cy",
-a)};return b.attr(a)},arc:function(a,b,c,d,e,f){if(ha(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol("arc",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){var e=ha(a)?a.r:e,g=this.createElement("rect"),a=ha(a)?a:a===r?{}:{x:a,y:b,width:v(c,0),height:v(d,0)};if(f!==r)a.strokeWidth=f,a=g.crisp(a);if(e)a.r=e;g.rSetter=function(a){Y(this.element,{rx:a,ry:a})};return g.attr(a)},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;
-this.width=a;this.height=b;for(this.boxWrapper[q(c,!0)?"animate":"attr"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement("g");return u(a)?b.attr({"class":"highcharts-"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:$};arguments.length>1&&s(f,{x:b,y:c,width:d,height:e});f=this.createElement("image").attr(f);f.element.setAttributeNS?f.element.setAttributeNS("http://www.w3.org/1999/xlink","href",a):f.element.setAttribute("hc-svg-href",a);return f},symbol:function(a,
-b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(t(b),t(c),d,e,f),i=/^url\((.*?)\)$/,j,k;if(h)g=this.path(h),s(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&s(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(t((d-b[0])/2),t((e-b[1])/2)))},j=a.match(i)[1],a=Tb[j]||f&&f.width&&f.height&&[f.width,f.height],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),aa("img",{onload:function(){k(g,Tb[j]=[this.width,this.height])},
-src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return["M",a+c/2,b,"C",a+c+e,b,a+c+e,b+d,a+c/2,b+d,"C",a-e,b+d,a-e,b,a+c/2,b,"Z"]},square:function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c,b+d,a,b+d,"Z"]},triangle:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d,a,b+d,"Z"]},"triangle-down":function(a,b,c,d){return["M",a,b,"L",a+c,b,a+c/2,b+d,"Z"]},diamond:function(a,b,c,d){return["M",a+c/2,b,"L",a+c,b+d/2,a+c/2,b+d,a,b+d/2,"Z"]},arc:function(a,b,c,d,e){var f=e.start,c=e.r||c||d,g=e.end-
-0.001,d=e.innerR,h=e.open,i=fa(f),j=ka(f),k=fa(g),g=ka(g),e=e.end-fc&&i>b+g&&ib+g&&id&&h>a+g&&ha+g&&h l&&/[ \-]/.test(b.textContent||b.innerText))K(b,{width:l+"px",display:"block",whiteSpace:"normal"}),i=l;this.getSpanCorrection(i,k,h,j,g)}K(b,{left:e+(this.xCorr||0)+"px",top:f+(this.yCorr||0)+"px"});if(Eb)k=b.offsetHeight;this.cTT=m}}else this.alignOnAdd=!0},setSpanRotation:function(a,b,c){var d={},e=La?"-ms-transform":Eb?"-webkit-transform":cb?"MozTransform":Sb?"-o-transform":"";d[e]=d.transform="rotate("+a+"deg)";d[e+(cb?"Origin":"-origin")]=d.transformOrigin=
-b*100+"% "+c+"px";K(this.element,d)},getSpanCorrection:function(a,b,c){this.xCorr=-a*c;this.yCorr=-b}});s(na.prototype,{html:function(a,b,c){var d=this.createElement("span"),e=d.element,f=d.renderer;d.textSetter=function(a){a!==e.innerHTML&&delete this.bBox;e.innerHTML=this.textStr=a};d.xSetter=d.ySetter=d.alignSetter=d.rotationSetter=function(a,b){b==="align"&&(b="textAlign");d[b]=a;d.htmlUpdateTransform()};d.attr({text:a,x:t(b),y:t(c)}).css({position:"absolute",whiteSpace:"nowrap",fontFamily:this.style.fontFamily,
-fontSize:this.style.fontSize});d.css=d.htmlCss;if(f.isSVG)d.add=function(a){var b,c=f.box.parentNode,j=[];if(this.parentGroup=a){if(b=a.div,!b){for(;a;)j.push(a),a=a.parentGroup;p(j.reverse(),function(a){var d;b=a.div=a.div||aa(Wa,{className:Y(a.element,"class")},{position:"absolute",left:(a.translateX||0)+"px",top:(a.translateY||0)+"px"},b||c);d=b.style;s(a,{translateXSetter:function(b,c){d.left=b+"px";a[c]=b;a.doTransform=!0},translateYSetter:function(b,c){d.top=b+"px";a[c]=b;a.doTransform=!0},
-visibilitySetter:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(e);d.added=!0;d.alignOnAdd&&d.htmlUpdateTransform();return d};return d}});var ib,ga;if(!ea&&!la)ga={init:function(a,b){var c=["<",b,' filled="f" stroked="f"'],d=["position: ","absolute",";"],e=b===Wa;(b==="shape"||e)&&d.push("left:0;top:0;width:1px;height:1px;");d.push("visibility: ",e?"hidden":"visible");c.push(' style="',d.join(""),'"/>');if(b)c=e||b==="span"||b==="img"?c.join(""):a.prepVML(c),this.element=aa(c);this.renderer=a},
-add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();if(this.onAdd)this.onAdd();return this},updateTransform:Z.prototype.htmlUpdateTransform,setSpanRotation:function(){var a=this.rotation,b=fa(a*Pa),c=ka(a*Pa);K(this.element,{filter:a?["progid:DXImageTransform.Microsoft.Matrix(M11=",b,", M12=",-c,", M21=",c,", M22=",b,", sizingMethod='auto expand')"].join(""):
-$})},getSpanCorrection:function(a,b,c,d,e){var f=d?fa(d*Pa):1,g=d?ka(d*Pa):0,h=q(this.elemHeight,this.element.offsetHeight),i;this.xCorr=f<0&&-a;this.yCorr=g<0&&-h;i=f*g<0;this.xCorr+=g*b*(i?1-c:c);this.yCorr-=f*b*(d?i?c:1-c:1);e&&e!=="left"&&(this.xCorr-=a*c*(f<0?-1:1),d&&(this.yCorr-=h*c*(g<0?-1:1)),K(this.element,{textAlign:e}))},pathToVML:function(a){for(var b=a.length,c=[];b--;)if(pa(a[b]))c[b]=t(a[b]*10)-5;else if(a[b]==="Z")c[b]="x";else if(c[b]=a[b],a.isArc&&(a[b]==="wa"||a[b]==="at"))c[b+
-5]===c[b+7]&&(c[b+7]+=a[b+7]>a[b+5]?1:-1),c[b+6]===c[b+8]&&(c[b+8]+=a[b+8]>a[b+6]?1:-1);return c.join(" ")||"x"},clip:function(a){var b=this,c;a?(c=a.members,ta(c,b),c.push(b),b.destroyClip=function(){ta(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:ob?"inherit":"rect(auto)"});return b.css(a)},css:Z.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Va(a)},destroy:function(){this.destroyClip&&this.destroyClip();return Z.prototype.destroy.apply(this)},on:function(a,b){this.element["on"+
-a]=function(){var a=S.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,b){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=H(a[c-2])-10*b;return a.join(" ")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,n,o;k&&typeof k.value!=="string"&&(k="x");m=k;if(a){n=q(a.width,3);o=(a.opacity||0.15)/n;for(e=1;e<=3;e++){l=n*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=[' '];h=aa(g.prepVML(j),null,{left:H(i.left)+q(a.offsetX,1),top:H(i.top)+q(a.offsetY,1)});if(c)h.cutOff=l+1;j=[' '];aa(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this},updateShadows:ra,setAttr:function(a,b){ob?this.element[a]=b:this.element.setAttribute(a,b)},classSetter:function(a){this.element.className=a},dashstyleSetter:function(a,
-b,c){(c.getElementsByTagName("stroke")[0]||aa(this.renderer.prepVML([" "]),null,null,c))[b]=a||"solid";this[b]=a},dSetter:function(a,b,c){var d=this.shadows,a=a||[];this.d=a.join&&a.join(" ");c.path=a=this.pathToVML(a);if(d)for(c=d.length;c--;)d[c].path=d[c].cutOff?this.cutOffPath(a,d[c].cutOff):a;this.setAttr(b,a)},fillSetter:function(a,b,c){var d=c.nodeName;if(d==="SPAN")c.style.color=a;else if(d!=="IMG")c.filled=a!==$,this.setAttr("fillcolor",this.renderer.color(a,c,b,this))},opacitySetter:ra,
-rotationSetter:function(a,b,c){c=c.style;this[b]=c[b]=a;c.left=-t(ka(a*Pa)+1)+"px";c.top=t(fa(a*Pa))+"px"},strokeSetter:function(a,b,c){this.setAttr("strokecolor",this.renderer.color(a,c,b))},"stroke-widthSetter":function(a,b,c){c.stroked=!!a;this[b]=a;pa(a)&&(a+="px");this.setAttr("strokeweight",a)},titleSetter:function(a,b){this.setAttr(b,a)},visibilitySetter:function(a,b,c){a==="inherit"&&(a="visible");this.shadows&&p(this.shadows,function(c){c.style[b]=a});c.nodeName==="DIV"&&(a=a==="hidden"?
-"-999em":0,ob||(c.style[b]=a?"visible":"hidden"),b="top");c.style[b]=a},xSetter:function(a,b,c){this[b]=a;b==="x"?b="left":b==="y"&&(b="top");this.updateClipping?(this[b]=a,this.updateClipping()):c.style[b]=a},zIndexSetter:function(a,b,c){c.style[b]=a}},N.VMLElement=ga=ia(Z,ga),ga.prototype.ySetter=ga.prototype.widthSetter=ga.prototype.heightSetter=ga.prototype.xSetter,ga={Element:ga,isIE8:Ga.indexOf("MSIE 8.0")>-1,init:function(a,b,c,d){var e;this.alignedObjects=[];d=this.createElement(Wa).css(s(this.getStyle(d),
-{position:"relative"}));e=d.element;a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=d;this.cache={};this.setSize(b,c,!1);if(!E.namespaces.hcv){E.namespaces.add("hcv","urn:schemas-microsoft-com:vml");try{E.createStyleSheet().cssText="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}catch(f){E.styleSheets[0].cssText+="hcv\\:fill, hcv\\:path, hcv\\:shape, hcv\\:stroke{ behavior:url(#default#VML); display: inline-block; } "}}},
-isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=ha(a);return s(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=a.element,c=b.nodeName,a=a.inverted,d=this.top-(c==="shape"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:"rect("+t(a?e:d)+"px,"+t(a?f:b)+"px,"+t(a?b:f)+"px,"+t(a?d:e)+"px)"};!a&&ob&&c==="DIV"&&s(d,{width:b+"px",height:f+"px"});return d},updateClipping:function(){p(e.members,
-function(a){a.element&&a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=$;a&&a.linearGradient?i="gradient":a&&a.radialGradient&&(i="pattern");if(i){var k,l,m=a.linearGradient||a.radialGradient,n,o,x,I,q,w="",a=a.stops,r,t=[],v=function(){h=[' '];aa(e.prepVML(h),null,null,b)};n=a[0];r=a[a.length-1];n[0]>0&&a.unshift([0,n[1]]);r[0]<1&&a.push([1,r[1]]);p(a,function(a,
-b){g.test(a[1])?(f=Ia(a[1]),k=f.get("rgb"),l=f.get("a")):(k=a[1],l=1);t.push(a[0]*100+"% "+k);b?(x=l,I=k):(o=l,q=k)});if(c==="fill")if(i==="gradient")c=m.x1||m[0]||0,a=m.y1||m[1]||0,n=m.x2||m[2]||0,m=m.y2||m[3]||0,w='angle="'+(90-X.atan((m-a)/(n-c))*180/ua)+'"',v();else{var j=m.r,u=j*2,s=j*2,y=m.cx,C=m.cy,R=b.radialReference,A,j=function(){R&&(A=d.getBBox(),y+=(R[0]-A.x)/A.width-0.5,C+=(R[1]-A.y)/A.height-0.5,u*=R[2]/A.width,s*=R[2]/A.height);w='src="'+F.global.VMLRadialGradientURL+'" size="'+u+","+
-s+'" origin="0.5,0.5" position="'+y+","+C+'" color2="'+q+'" ';v()};d.added?j():d.onAdd=j;j=I}else j=k}else if(g.test(a)&&b.tagName!=="IMG")f=Ia(a),h=["<",c,' opacity="',f.get("a"),'"/>'],aa(this.prepVML(h),null,null,b),j=f.get("rgb");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type="solid";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join("");b?(a=a.replace("/>",' xmlns="urn:schemas-microsoft-com:vml" />'),a=a.indexOf('style="')===-1?a.replace("/>",' style="display:inline-block;behavior:url(#default#VML);" />'):
-a.replace('style="','style="display:inline-block;behavior:url(#default#VML);')):a=a.replace("<","1&&f.attr({x:b,y:c,width:d,height:e});return f},createElement:function(a){return a==="rect"?this.symbol(a):na.prototype.createElement.call(this,a)},invertChild:function(a,b){var c=this,d=b.style,e=a.tagName==="IMG"&&a.style;K(a,{flip:"x",left:H(d.width)-(e?H(e.top):1),top:H(d.height)-(e?H(e.left):1),rotation:-90});p(a.childNodes,function(b){c.invertChild(b,a)})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||
-d,c=e.innerR,d=fa(f),i=ka(f),j=fa(g),k=ka(g);if(g-f===0)return["x"];f=["wa",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push("e","M",a,b);f.push("at",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,"x","e");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);e&&e.isCircle&&(a-=c/2,b-=d/2);return["wa",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,"e"]},rect:function(a,b,c,d,e){return na.prototype.symbols[!u(e)||!e.r?"square":"callout"].call(0,a,b,c,d,e)}}},N.VMLRenderer=ib=function(){this.init.apply(this,
-arguments)},ib.prototype=y(na.prototype,ga),Za=ib;na.prototype.measureSpanWidth=function(a,b){var c=E.createElement("span"),d;d=E.createTextNode(a);c.appendChild(d);K(c,b);this.box.appendChild(c);d=c.offsetWidth;Va(c);return d};var Vb;if(la)N.CanVGRenderer=ga=function(){Ha="http://www.w3.org/1999/xhtml"},ga.prototype.symbols={},Vb=function(){function a(){var a=b.length,d;for(d=0;dm[l]?m[l]=g+j:n||(c=!1);if(n){l=(m=d.justifyToPlot)?d.pos:0;m=m?l+d.len:d.chart.chartWidth;do a+=e?1:-1,n=d.ticks[i[a]];while(i[a]&&(!n||!n.label||n.label.line!==o));d=n&&n.label.xy&&n.label.xy.x+n.getLabelSides()[e?0:1];e&&!h||f&&h?g+kd&&(c=!1)):g+j>m&&(g=m-j,n&&g+k0&&b.height>0){f=y({align:c&&k&&"center",x:c?!k&&4:10,verticalAlign:!c&&k&&"middle",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g){r={align:f.textAlign||f.align,rotation:f.rotation};if(u(q))r.zIndex=q;a.label=g=t.text(f.text,0,0,f.useHTML).attr(r).css(f.style).add()}b=[o[1],o[4],k?o[6]:o[1]];k=[o[2],o[5],k?o[7]:o[2]];o=Ua(b);c=Ua(k);g.align(f,
-!1,{x:o,y:c,width:Ea(b)-o,height:Ea(k)-c});g.show()}else g&&g.hide();return a},destroy:function(){ta(this.axis.plotLinesAndBands,this);delete this.axis;Na(this)}};D.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:"%H:%M:%S.%L",second:"%H:%M:%S",minute:"%H:%M",hour:"%H:%M",day:"%e. %b",week:"%e. %b",month:"%b '%y",year:"%Y"},endOnTick:!1,gridLineColor:"#C0C0C0",labels:T,lineColor:"#C0D0E0",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:"#E0E0E0",minorGridLineWidth:1,minorTickColor:"#A0A0A0",
-minorTickLength:2,minorTickPosition:"outside",startOfWeek:1,startOnTick:!1,tickColor:"#C0D0E0",tickLength:10,tickmarkPlacement:"between",tickPixelInterval:100,tickPosition:"outside",tickWidth:1,title:{align:"middle",style:{color:"#707070"}},type:"linear"},defaultYAxisOptions:{endOnTick:!0,gridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:"Values"},stackLabels:{enabled:!1,formatter:function(){return Da(this.total,
--1)},style:T.style}},defaultLeftAxisOptions:{labels:{x:-15,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:15,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:null},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-15},title:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.coll=(this.isXAxis=c)?"xAxis":"yAxis";this.opposite=b.opposite;this.side=b.side||(this.horiz?this.opposite?0:2:this.opposite?1:3);this.setOptions(b);var d=
-this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e==="category";this.names=[];this.isLog=e==="logarithmic";this.isDatetimeAxis=e==="datetime";this.isLinked=u(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement==="between"&&q(d.tickInterval,1)===1?0.5:0;this.ticks={};this.labelEdge=[];this.minorTicks=
-{};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.min=this.max=null;this.crosshair=q(d.crosshair,qa(a.options.tooltip.crosshairs)[c?0:1],!1);var f,d=this.options.events;Qa(this,a.axes)===-1&&(c&&!this.isColorAxis?a.axes.splice(a.xAxis.length,0,this):a.axes.push(this),a[this.coll].push(this));this.series=this.series||[];if(a.inverted&&c&&this.reversed===
-r)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)z(this,f,d[f]);if(this.isLog)this.val2lin=Ka,this.lin2val=sa},setOptions:function(a){this.options=y(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],y(F[this.coll],a))},defaultLabelFormatter:function(){var a=this.axis,b=this.value,c=a.categories,d=this.dateTimeLabelFormat,
-e=F.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ma(h,this);else if(c)g=b;else if(d)g=xa(d,b);else if(f&&a>=1E3)for(;f--&&g===r;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Da(b/c,-1)+e[f]);g===r&&(g=P(b)>=1E4?Da(b,0):Da(b,-1,r,""));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=a.ignoreMinPadding=a.ignoreMaxPadding=null;a.buildStacks&&a.buildStacks();p(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;
-d=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=A(q(a.dataMin,d[0]),Ua(d)),a.dataMax=v(q(a.dataMax,d[0]),Ea(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(u(c)&&u(e))a.dataMin=A(q(a.dataMin,c),c),a.dataMax=v(q(a.dataMax,e),e);if(u(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMaxg+this.width)m=!0}else if(a=g,c=l-this.right,ih+this.height)m=!0;return m&&!d?null:f.renderer.crispLine(["M",a,i,"L",c,j],b||1)},getLinearTickPositions:function(a,b,c){var d,e=ja(V(b/a)*a),f=ja(Ya(c/
-a)*a),g=[];if(b===c&&pa(b))return[b];for(b=e;b<=f;){g.push(b);b=ja(b+a);if(b===d)break;d=b}return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===r&&!this.isLog)u(a.min)||u(a.max)?this.minRange=null:(p(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-1;g>0;g--)if(h=i[g]-i[g-1],f===r||hc&&(h=0);d=v(d,h);f=v(f,Sa(j)?
-0:h/2);g=v(g,j==="on"?0:h);!a.noSharedTooltip&&u(n)&&(e=u(e)?A(e,n):n)}),h=b.ordinalSlope&&e?b.ordinalSlope/e:1,b.minPointOffset=f*=h,b.pointRangePadding=g*=h,b.pointRange=A(d,c),b.closestPointRange=e;if(a)b.oldTransA=j;b.translationSlope=b.transA=j=b.len/(c+g||1);b.transB=b.horiz?b.left:b.bottom;b.minPixelPadding=j*f},setTickPositions:function(a){var b=this,c=b.chart,d=b.options,e=d.startOnTick,f=d.endOnTick,g=b.isLog,h=b.isDatetimeAxis,i=b.isXAxis,j=b.isLinked,k=b.options.tickPositioner,l=d.maxPadding,
-m=d.minPadding,n=d.tickInterval,o=d.minTickInterval,x=d.tickPixelInterval,I,oa=b.categories;j?(b.linkedParent=c[b.coll][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=q(c.min,c.dataMin),b.max=q(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&ma(11,1)):(b.min=q(b.userMin,d.min,b.dataMin),b.max=q(b.userMax,d.max,b.dataMax));if(g)!a&&A(b.min,q(b.dataMin,b.min))<=0&&ma(10,1),b.min=ja(Ka(b.min)),b.max=ja(Ka(b.max));if(b.range&&u(b.max))b.userMin=b.min=v(b.min,b.max-b.range),b.userMax=b.max,b.range=
-null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!oa&&!b.axisPointRange&&!b.usePercentage&&!j&&u(b.min)&&u(b.max)&&(c=b.max-b.min)){if(!u(d.min)&&!u(b.userMin)&&m&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*m;if(!u(d.max)&&!u(b.userMax)&&l&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*l}if(pa(d.floor))b.min=v(b.min,d.floor);if(pa(d.ceiling))b.max=A(b.max,d.ceiling);b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:j&&!n&&x===b.linkedParent.options.tickPixelInterval?b.tickInterval=
-b.linkedParent.tickInterval:(b.tickInterval=q(n,oa?1:(b.max-b.min)*x/v(b.len,x)),!u(n)&&b.len1&&b.tickInterval<5&&b.max>1E3&&b.max<9999)));b.minorTickInterval=d.minorTickInterval==="auto"&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):k&&k.apply(b,[b.min,b.max]);if(!a)!b.ordinalPositions&&(b.max-b.min)/b.tickInterval>v(2*b.len,200)&&ma(19,!0),a=h?b.getTimeTicks(b.normalizeTimeTickInterval(b.tickInterval,d.units),b.min,
-b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):g?b.getLogTickPositions(b.tickInterval,b.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),I&&a.splice(1,a.length-2),b.tickPositions=a;if(!j)d=a[0],g=a[a.length-1],h=b.minPointOffset||0,e?b.min=d:b.min-h>d&&a.shift(),f?b.max=g:b.max+h1E13?1:0.001,b.min-=e,b.max+=e)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=
-[this.coll,this.pos,this.len].join("-");if(!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1&&this.min!==r){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e=v(d,q(e.max,d))&&(b=r));this.displayBtn=a!==r||b!==r;this.setExtremes(a,b,!1,r,{trigger:"zoom"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=this.horiz,e=q(b.width,a.plotWidth-c+(b.offsetRight||0)),f=q(b.height,a.plotHeight),g=q(b.top,a.plotTop),b=q(b.left,a.plotLeft+c),c=/%$/;c.test(f)&&(f=parseInt(f,10)/
-100*a.plotHeight);c.test(g)&&(g=parseInt(g,10)/100*a.plotHeight+a.plotTop);this.left=b;this.top=g;this.width=e;this.height=f;this.bottom=a.chartHeight-f-g;this.right=a.chartWidth-e-b;this.len=v(d?e:f,0);this.pos=d?b:g},getExtremes:function(){var a=this.isLog;return{min:a?ja(sa(this.min)):this.min,max:a?ja(sa(this.max)):this.max,dataMin:this.dataMin,dataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?sa(this.min):this.min,b=b?sa(this.max):
-this.max;c>a||a===null?a=c:b 15&&a<165?"right":a>195&&a<345?"left":"center"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k,l=0,m,n=0,o=d.title,x=d.labels,I=0,oa=b.axisOffset,b=b.clipOffset,w=[-1,1,1,-1][h],t,s=1,y=q(x.maxStaggerLines,5),O,A,z,C,R;a.hasData=j=a.hasVisibleSeries||u(a.min)&&u(a.max)&&
-!!e;a.showAxis=k=j||q(d.showEmpty,!0);a.staggerLines=a.horiz&&x.staggerLines;if(!a.axisGroup)a.gridGroup=c.g("grid").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g("axis").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g("axis-labels").attr({zIndex:x.zIndex||7}).addClass("highcharts-"+a.coll.toLowerCase()+"-labels").add();if(j||a.isLinked){a.labelAlign=q(x.align||a.autoLabelAlign(x.rotation));p(e,function(b){f[b]?f[b].addLabel():f[b]=new bb(a,b)});if(a.horiz&&!a.staggerLines&&y&&!x.rotation){for(j=
-a.reversed?[].concat(e).reverse():e;s1)a.staggerLines=s}p(e,function(b){if(h===0||h===2||{1:"left",3:"right"}[h]===a.labelAlign)I=v(f[b].getLabelSize(),I)});if(a.staggerLines)I*=a.staggerLines,a.labelOffset=I}else for(t in f)f[t].destroy(),delete f[t];if(o&&o.text&&o.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(o.text,
-0,0,o.useHTML).attr({zIndex:7,rotation:o.rotation||0,align:o.textAlign||{low:"left",middle:"center",high:"right"}[o.align]}).addClass("highcharts-"+this.coll.toLowerCase()+"-title").css(o.style).add(a.axisGroup),a.axisTitle.isNew=!0;if(k)l=a.axisTitle.getBBox()[g?"height":"width"],m=o.offset,n=u(m)?0:q(o.margin,g?5:10);a.axisTitle[k?"show":"hide"]()}a.offset=w*q(d.offset,oa[h]);c=h===2?a.tickBaseline:0;g=I+n+(I&&w*(g?q(x.y,a.tickBaseline+8):x.x)-c);a.axisTitleMargin=q(m,g);oa[h]=v(oa[h],a.axisTitleMargin+
-l+w*a.offset,g);b[i]=v(b[i],V(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine(["M",e?this.left:f,e?d:this.top,"L",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=H(e.style.fontSize||
-12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,b=a.horiz,c=a.reversed,d=a.chart,e=d.renderer,f=a.options,g=a.isLog,h=a.isLinked,i=a.tickPositions,j,k=a.axisTitle,l=a.ticks,m=a.minorTicks,n=a.alternateBands,o=f.stackLabels,x=f.alternateGridColor,q=a.tickmarkOffset,oa=f.lineWidth,w=d.hasRendered&&u(a.oldMin)&&
-!isNaN(a.oldMin),t=a.hasData,v=a.showAxis,s,y=f.labels.overflow,A=a.justifyLabels=b&&y!==!1,z;a.labelEdge.length=0;a.justifyToPlot=y==="justify";p([l,m,n],function(a){for(var b in a)a[b].isActive=!1});if(t||h)if(a.minorTickInterval&&!a.categories&&p(a.getMinorTickPositions(),function(b){m[b]||(m[b]=new bb(a,b,"minor"));w&&m[b].isNew&&m[b].render(null,!0);m[b].render(null,!1,1)}),i.length&&(j=i.slice(),(b&&c||!b&&!c)&&j.reverse(),A&&(j=j.slice(1).concat([j[0]])),p(j,function(b,c){A&&(c=c===j.length-
-1?0:c+1);if(!h||b>=a.min&&b<=a.max)l[b]||(l[b]=new bb(a,b)),w&&l[b].isNew&&l[b].render(c,!0,0.1),l[b].render(c)}),q&&a.min===0&&(l[-1]||(l[-1]=new bb(a,-1,null,!0)),l[-1].render(-1))),x&&p(i,function(b,c){if(c%2===0&&b=G.second&&(i.setMilliseconds(0),i.setSeconds(j>=G.minute?0:k*V(i.getSeconds()/k)));if(j>=G.minute)i[Nb](j>=G.hour?0:k*V(i[yb]()/k));if(j>=G.hour)i[Ob](j>=G.day?0:k*V(i[zb]()/k));if(j>=G.day)i[Bb](j>=G.month?1:k*V(i[Xa]()/k));j>=G.month&&(i[Pb](j>=G.year?0:k*V(i[mb]()/k)),h=i[nb]());j>=G.year&&(h-=h%k,i[Qb](h));if(j===G.week)i[Bb](i[Xa]()-i[Ab]()+q(d,1));b=1;Oa&&(i=new ca(i.getTime()+Oa));h=i[nb]();for(var d=i.getTime(),
-l=i[mb](),m=i[Xa](),n=(G.day+(g?Oa:i.getTimezoneOffset()*6E4))%G.day;d=0.5)a=t(a),g=this.getLinearTickPositions(a,
-b,c);else if(a>=0.08)for(var f=V(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];fb&&(!d||k<=c)&&k!==r&&g.push(k),k>c&&(l=!0),k=j}else if(b=sa(b),c=sa(c),a=e[d?"minorTickInterval":"tickInterval"],a=q(a==="auto"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=wb(a,null,vb(a)),g=za(this.getLinearTickPositions(a,b,c),Ka),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=
-a;return g};var Jb=N.Tooltip=function(){this.init.apply(this,arguments)};Jb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=H(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=a.renderer.label("",0,0,b.shape||"callout",null,null,b.useHTML,null,"tooltip").attr({padding:e,fill:b.backgroundColor,"stroke-width":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-9999});la||this.label.shadow(b.shadow);this.shared=b.shared},
-destroy:function(){if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden&&(P(a-f.x)>1||P(b-f.y)>1),h=e.followPointer||e.len>1;s(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:h?r:g?(2*f.anchorX+c)/3:c,anchorY:h?r:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g)clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(a){var b=
-this,c;clearTimeout(this.hideTimer);if(!this.isHidden)c=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){b.label.fadeOut();b.isHidden=!0},q(a,this.options.hideDelay,500)),c&&p(c,function(a){a.setState()}),this.chart.hoverPoints=null},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=0,i,a=qa(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===r&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(p(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=
-(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:h]);return za(c,t)},getPosition:function(a,b,c){var d=this.chart,e=this.distance,f={},g,h=["y",d.chartHeight,b,c.plotY+d.plotTop],i=["x",d.chartWidth,a,c.plotX+d.plotLeft],j=c.ttBelow||d.inverted&&!c.negative||!d.inverted&&c.negative,k=function(a,b,c,d){var g=cb-e)return!1;else f[a]=db-c/2?b-c-2:d-c/2},m=function(a){var b=h;h=i;i=b;g=a},n=function(){k.apply(0,h)!==!1?l.apply(0,i)===!1&&!g&&(m(!0),n()):g?f.x=f.y=0:(m(!0),n())};(d.inverted||this.len>1)&&m();n();return f},defaultFormatter:function(a){var b=this.points||qa(this),c=b[0].series,d;d=[a.tooltipHeaderFormatter(b[0])];p(b,function(a){c=a.series;d.push(c.tooltipFormatter&&c.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});
-d.push(a.options.footerFormat||"");return d.join("")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=this.shared;clearTimeout(this.hideTimer);this.followPointer=qa(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];l&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&p(h,function(a){a.setState()}),p(a,function(a){a.setState("hover");j.push(a.getLabelConfig())}),h={x:a[0].category,
-y:a[0].y},h.points=j,this.len=j.length,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;this.distance=q(h.tooltipOptions.distance,16);i===!1?this.hide():(this.isHidden&&(hb(d),d.attr("opacity",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||"#606060",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g,negative:a.negative,ttBelow:a.ttBelow}),this.isHidden=!1);J(c,"tooltipRefresh",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart,
-c=this.label,c=(this.options.positioner||this.getPosition).call(this,c.width,c.height,a);this.move(t(c.x),t(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)},tooltipHeaderFormatter:function(a){var b=a.series,c=b.tooltipOptions,d=c.dateTimeLabelFormats,e=c.xDateFormat,f=b.xAxis,g=f&&f.options.type==="datetime"&&pa(a.key),c=c.headerFormat,f=f&&f.closestPointRange,h;if(g&&!e){if(f)for(h in G){if(G[h]>=f||G[h]<=G.day&&a.key%G[h]>0){e=d[h];break}}else e=d.day;e=e||d.year}g&&e&&(c=c.replace("{point.key}","{point.key:"+
-e+"}"));return Ma(c,{point:a,series:b})}};var va;db=E.documentElement.ontouchstart!==r;var $a=N.Pointer=function(a,b){this.init(a,b)};$a.prototype={init:function(a,b){var c=b.chart,d=c.events,e=la?"":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.hasZoom=f||e;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(N.Tooltip&&b.tooltip.enabled)a.tooltip=new Jb(a,b.tooltip),
-this.followTouchMove=b.tooltip.followTouchMove;this.setDOMEvents()},normalize:function(a,b){var c,d,a=a||window.event,a=cc(a);if(!a.target)a.target=a.srcElement;d=a.touches?a.touches.length?a.touches.item(0):a.changedTouches[0]:a;if(!b)this.chartPosition=b=bc(this.chart.container);d.pageX===r?(c=v(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return s(a,{chartX:t(c),chartY:t(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};p(this.chart.axes,function(c){b[c.isXAxis?"xAxis":
-"yAxis"].push({axis:c,value:c.toValue(a[c.horiz?"chartX":"chartY"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?b.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f,g=b.hoverPoint,h=b.hoverSeries,i,j,k=b.chartWidth,l=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!h||!h.noSharedTooltip)){f=[];i=c.length;for(j=0;jk&&f.splice(i,1);if(f.length&&f[0].clientX!==this.hoverX)d.refresh(f,a),this.hoverX=f[0].clientX}c=h&&h.tooltipOptions.followPointer;if(h&&h.tracker&&!c){if((e=h.tooltipPoints[l])&&e!==g)e.onMouseOver(a)}else d&&c&&!d.isHidden&&(h=d.getAnchor([{}],a),d.updatePosition({plotX:h[0],plotY:h[1]}));if(d&&!this._onDocumentMouseMove)this._onDocumentMouseMove=
-function(a){if(ba[va])ba[va].pointer.onDocumentMouseMove(a)},z(E,"mousemove",this._onDocumentMouseMove);p(b.axes,function(b){b.drawCrosshair(a,q(e,g))})},reset:function(a,b){var c=this.chart,d=c.hoverSeries,e=c.hoverPoint,f=c.tooltip,g=f&&f.shared?c.hoverPoints:e;(a=a&&f&&g)&&qa(g)[0].plotX===r&&(a=!1);if(a)f.refresh(g),e&&e.setState(e.state,!0);else{if(e)e.onMouseOut();if(d)d.onMouseOut();f&&f.hide(b);if(this._onDocumentMouseMove)U(E,"mousemove",this._onDocumentMouseMove),this._onDocumentMouseMove=
-null;p(c.axes,function(a){a.hideCrosshair()});this.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;p(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=this.mouseDownY=a.chartY},
-drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,m=this.mouseDownX,n=this.mouseDownY,o=c.panKey&&a[c.panKey+"Key"];dh+j&&(d=h+j);ei+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(m-d,2)+Math.pow(n-e,2));if(this.hasDragged>10){l=b.isInsidePlot(m-h,n-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!o&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,
-f?1:j,g?1:k,0).attr({fill:c.selectionMarkerFill||"rgba(69,114,167,0.25)",zIndex:7}).add();this.selectionMarker&&f&&(d-=m,this.selectionMarker.attr({width:P(d),x:(d>0?0:d)+m}));this.selectionMarker&&g&&(d=e-n,this.selectionMarker.attr({height:P(d),y:(d>0?0:d)+n}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.attr?e.attr("x"):
-e.x,g=e.attr?e.attr("y"):e.y,h=e.attr?e.attr("width"):e.width,i=e.attr?e.attr("height"):e.height,j;if(this.hasDragged||c)p(b.axes,function(b){if(b.zoomEnabled){var c=b.horiz,e=a.type==="touchend"?b.minPixelPadding:0,n=b.toValue((c?f:g)+e),c=b.toValue((c?f+h:g+i)-e);!isNaN(n)&&!isNaN(c)&&(d[b.coll].push({axis:b,min:A(n,c),max:v(n,c)}),j=!0)}}),j&&J(b,"selection",d,function(a){b.zoom(s(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)K(b.container,
-{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=this.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){ba[va]&&ba[va].pointer.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,"highcharts-tracker")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&
-this.reset()},onContainerMouseLeave:function(){var a=ba[va];if(a)a.pointer.reset(),a.pointer.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart;va=b.index;a=this.normalize(a);a.returnValue=!1;b.mouseIsDown==="mousedown"&&this.drag(a);(this.inClass(a.target,"highcharts-tracker")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=Y(a,"class"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf("highcharts-container")!==
--1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries,c=(a=a.relatedTarget||a.toElement)&&a.point&&a.point.series;if(b&&!b.options.stickyTracking&&!this.inClass(a,"highcharts-tooltip")&&c!==b)b.onMouseOut()},onContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,a=this.normalize(a);a.cancelBubble=!0;b.cancelClick||(c&&this.inClass(a.target,"highcharts-tracker")?(J(c.series,"click",s(a,{point:c})),b.hoverPoint&&c.firePointEvent("click",
-a)):(s(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&J(b,"click",a)))},setDOMEvents:function(){var a=this,b=a.chart.container;b.onmousedown=function(b){a.onContainerMouseDown(b)};b.onmousemove=function(b){a.onContainerMouseMove(b)};b.onclick=function(b){a.onContainerClick(b)};z(b,"mouseleave",a.onContainerMouseLeave);gb===1&&z(E,"mouseup",a.onDocumentMouseUp);if(db)b.ontouchstart=function(b){a.onContainerTouchStart(b)},b.ontouchmove=function(b){a.onContainerTouchMove(b)},gb===1&&
-z(E,"touchend",a.onDocumentTouchEnd)},destroy:function(){var a;U(this.chart.container,"mouseleave",this.onContainerMouseLeave);gb||(U(E,"mouseup",this.onDocumentMouseUp),U(E,"touchend",this.onDocumentTouchEnd));clearInterval(this.tooltipTimeout);for(a in this)this[a]=null}};s(N.Pointer.prototype,{pinchTranslate:function(a,b,c,d,e,f){(this.zoomHor||this.pinchHor)&&this.pinchTranslateDirection(!0,a,b,c,d,e,f);(this.zoomVert||this.pinchVert)&&this.pinchTranslateDirection(!1,a,b,c,d,e,f)},pinchTranslateDirection:function(a,
-b,c,d,e,f,g,h){var i=this.chart,j=a?"x":"y",k=a?"X":"Y",l="chart"+k,m=a?"width":"height",n=i["plot"+(a?"Left":"Top")],o,x,q=h||1,p=i.inverted,w=i.bounds[a?"h":"v"],r=b.length===1,t=b[0][l],v=c[0][l],s=!r&&b[1][l],u=!r&&c[1][l],y,c=function(){!r&&P(t-s)>20&&(q=h||P(v-u)/P(t-s));x=(n-v)/q+t;o=i["plot"+(a?"Width":"Height")]/q};c();b=x;bw.max&&(b=w.max-o,y=!0);y?(v-=0.8*(v-g[j][0]),r||(u-=0.8*(u-g[j][1])),c()):g[j]=[v,u];p||(f[j]=x-n,f[m]=o);f=p?1/q:q;e[m]=o;e[j]=b;d[p?a?"scaleY":
-"scaleX":"scale"+k]=q;d["translate"+k]=f*n+(v-f*t)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=b.followTouchMove,f=a.touches,g=f.length,h=b.lastValidTouch,i=b.hasZoom,j=b.selectionMarker,k={},l=g===1&&(b.inClass(a.target,"highcharts-tracker")&&c.runTrackerClick||b.runChartClick),m={};(i||e)&&!l&&a.preventDefault();za(f,function(a){return b.normalize(a)});if(a.type==="touchstart")p(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,
-d[1]&&d[1].chartY],p(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?"h":"v"],d=a.minPixelPadding,e=a.toPixels(q(a.options.min,a.dataMin)),f=a.toPixels(q(a.options.max,a.dataMax)),g=A(e,f),e=v(e,f);b.min=A(a.pos,g-d);b.max=v(a.pos+a.len,e+d)}}),b.res=!0;else if(d.length){if(!j)b.selectionMarker=j=s({destroy:ra},c.plotBox);b.pinchTranslate(d,f,k,j,m,h);b.hasPinched=i;b.scaleGroups(k,m);if(!i&&e&&g===1)this.runPointActions(b.normalize(a));else if(b.res)b.res=!1,this.reset(!1,0)}},onContainerTouchStart:function(a){var b=
-this.chart;va=b.index;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){ba[va]&&ba[va].pointer.drop(a)}});if(S.PointerEvent||S.MSPointerEvent){var Aa={},Kb=!!S.PointerEvent,gc=function(){var a,b=[];b.item=function(a){return this[a]};for(a in Aa)Aa.hasOwnProperty(a)&&
-b.push({pageX:Aa[a].pageX,pageY:Aa[a].pageY,target:Aa[a].target});return b},Lb=function(a,b,c,d){a=a.originalEvent||a;if((a.pointerType==="touch"||a.pointerType===a.MSPOINTER_TYPE_TOUCH)&&ba[va])d(a),d=ba[va].pointer,d[b]({type:c,target:a.currentTarget,preventDefault:ra,touches:gc()})};s($a.prototype,{onContainerPointerDown:function(a){Lb(a,"onContainerTouchStart","touchstart",function(a){Aa[a.pointerId]={pageX:a.pageX,pageY:a.pageY,target:a.currentTarget}})},onContainerPointerMove:function(a){Lb(a,
-"onContainerTouchMove","touchmove",function(a){Aa[a.pointerId]={pageX:a.pageX,pageY:a.pageY};if(!Aa[a.pointerId].target)Aa[a.pointerId].target=a.currentTarget})},onDocumentPointerUp:function(a){Lb(a,"onContainerTouchEnd","touchend",function(a){delete Aa[a.pointerId]})},batchMSEvents:function(a){a(this.chart.container,Kb?"pointerdown":"MSPointerDown",this.onContainerPointerDown);a(this.chart.container,Kb?"pointermove":"MSPointerMove",this.onContainerPointerMove);a(E,Kb?"pointerup":"MSPointerUp",this.onDocumentPointerUp)}});
-Q($a.prototype,"init",function(a,b,c){a.call(this,b,c);(this.hasZoom||this.followTouchMove)&&K(b.container,{"-ms-touch-action":$,"touch-action":$})});Q($a.prototype,"setDOMEvents",function(a){a.apply(this);(this.hasZoom||this.followTouchMove)&&this.batchMSEvents(z)});Q($a.prototype,"destroy",function(a){this.batchMSEvents(U);a.call(this)})}var sb=N.Legend=function(a,b){this.init(a,b)};sb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=q(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.itemStyle=
-d,c.itemHiddenStyle=y(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.symbolWidth=q(b.symbolWidth,16),c.pages=[],c.render(),z(c.chart,"endResize",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,d=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.legendColor||a.color||"#CCC":g,g=a.options&&a.options.marker,i={fill:h},
-j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in i.stroke=h,g=a.convertAttribs(g),g)d=g[j],d!==r&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=e,f.y=d},destroyItem:function(a){var b=a.checkbox;p(["legendItem","legendLine","legendSymbol","legendGroup"],function(b){a[b]&&(a[b]=a[b].destroy())});
-b&&Va(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,p(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,K(f,{left:b.translateX+e.checkboxOffset+f.x-20+"px",top:g+"px",display:g>c-6&&g(m||b.chartWidth-2*j-x-d.x))this.itemX=x,this.itemY+=o+this.lastLineHeight+n,this.lastLineHeight=0;this.maxItemWidth=v(this.maxItemWidth,f);this.lastItemY=o+this.itemY+
-n;this.lastLineHeight=v(g,this.lastLineHeight);a._legendItemPos=[this.itemX,this.itemY];e?this.itemX+=f:(this.itemY+=o+g+n,this.lastLineHeight=g);this.offsetWidth=m||v((e?this.itemX-x-k:f)+j,this.offsetWidth)},getAllItems:function(){var a=[];p(this.chart.series,function(b){var c=b.options;if(q(c.showInLegend,!u(c.linkedTo)?r:!1,!0))a=a.concat(b.legendItems||(c.legendType==="point"?b.data:b))});return a},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,
-l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g("legend").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=a.getAllItems();xb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;p(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;
-h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||m){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?"attr":"animate"](i.crisp({width:g,height:h})),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,"stroke-width":l||0,fill:m||$}).add(d).shadow(j.shadow),i.isNew=!0;i[f?"show":"hide"]()}a.legendWidth=g;a.legendHeight=h;p(e,function(b){a.positionItem(b)});f&&d.align(s({width:g,height:h},j),!0,"spacingBox");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=
-this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign==="top"?-f:f)-this.padding,g=e.maxHeight,h,i=this.clipRect,j=e.navigation,k=q(j.animation,!0),l=j.arrowSize||12,m=this.nav,n=this.pages,o,x=this.allItems;e.layout==="horizontal"&&(f/=2);g&&(f=A(f,g));n.length=0;if(a>f&&!e.useHTML){this.clipHeight=h=v(f-20-this.titleHeight-this.padding,0);this.currentPage=q(this.currentPage,1);this.fullHeight=a;p(x,function(a,b){var c=a._legendItemPos[1],d=t(a.legendItem.getBBox().height),
-e=n.length;if(!e||c-n[e-1]>h&&(o||c)!==n[e-1])n.push(o||c),e++;b===x.length-1&&c+d-n[e-1]>h&&n.push(c);c!==o&&(o=c)});if(!i)i=b.clipRect=d.clipRect(0,this.padding,9999,0),b.contentGroup.clip(i);i.attr({height:h});if(!m)this.nav=m=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol("triangle",0,0,l,l).on("click",function(){b.scroll(-1,k)}).add(m),this.pager=d.text("",15,10).css(j.style).add(m),this.down=d.symbol("triangle-down",0,0,l,l).on("click",function(){b.scroll(1,k)}).add(m);b.scroll(0);
-a=f}else if(m)i.attr({height:c.chartHeight}),m.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pages,d=c.length,e=this.currentPage+a,f=this.clipHeight,g=this.options.navigation,h=g.activeColor,g=g.inactiveColor,i=this.pager,j=this.padding;e>d&&(e=d);if(e>0)b!==r&&ab(b,this.chart),this.nav.attr({translateX:j,translateY:f+this.padding+7+this.titleHeight,visibility:"visible"}),this.up.attr({fill:e===1?g:h}).css({cursor:e===1?"default":"pointer"}),
-i.attr({text:e+"/"+d}),this.down.attr({x:18+this.pager.getBBox().width,fill:e===d?g:h}).css({cursor:e===d?"default":"pointer"}),c=-c[e-1]+this.initialItemY,this.scrollGroup.animate({translateY:c}),this.currentPage=e,this.positionCheckboxes(c)}};T=N.LegendSymbolMixin={drawRectangle:function(a,b){var c=a.options.symbolHeight||12;b.legendSymbol=this.chart.renderer.rect(0,a.baseline-5-c/2,a.symbolWidth,c,a.options.symbolRadius||0).attr({zIndex:3}).add(b.legendGroup)},drawLineMarker:function(a){var b=
-this.options,c=b.marker,d;d=a.symbolWidth;var e=this.chart.renderer,f=this.legendGroup,a=a.baseline-t(e.fontMetrics(a.options.itemStyle.fontSize,this.legendItem).b*0.3),g;if(b.lineWidth){g={"stroke-width":b.lineWidth};if(b.dashStyle)g.dashstyle=b.dashStyle;this.legendLine=e.path(["M",0,a,"L",d,a]).attr(g).add(f)}if(c&&c.enabled!==!1)b=c.radius,this.legendSymbol=d=e.symbol(this.symbol,d/2-b,a-b,2*b,2*b).add(f),d.isMarker=!0}};(/Trident\/7\.0/.test(Ga)||cb)&&Q(sb.prototype,"positionItem",function(a,
-b){var c=this,d=function(){b._legendItemPos&&a.call(c,b)};d();setTimeout(d)});ya.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=y(F,a);c.series=a.series=d;this.userOptions=a;d=c.chart;this.margin=this.splashArray("margin",d);this.spacing=this.splashArray("spacing",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=ba.length;ba.push(f);gb++;d.reflow!==!1&&z(f,
-"load",function(){f.initReflow()});if(e)for(g in e)z(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=la?!1:q(d.animation,!0);f.pointCount=f.colorCounter=f.symbolCounter=0;f.firstRender()},initSeries:function(a){var b=this.options.chart;(b=B[a.type||b.type||b.defaultSeriesType])||ma(17,!0);b=new b;b.init(this,a);return b},isInsidePlot:function(a,b,c){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&p(this.axes,
-function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.hasCartesianSeries,j=this.isDirtyBox,k=c.length,l=k,m=this.renderer,n=m.isHidden(),o=[];ab(a,this);n&&this.cloneRenderTo();for(this.layOutTitles();l--;)if(a=c[l],a.options.stacking&&(g=!0,a.isDirty)){h=!0;break}if(h)for(l=k;l--;)if(a=c[l],a.options.stacking)a.isDirty=!0;p(c,function(a){a.isDirty&&a.options.legendType==="point"&&(f=!0)});
-if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(i){if(!this.isResizing)this.maxTicks=null,p(b,function(a){a.setScale()});this.adjustTickAmounts()}this.getMargins();i&&(p(b,function(a){a.isDirty&&(j=!0)}),p(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,o.push(function(){J(a,"afterSetExtremes",s(a.eventArgs,a.getExtremes()));delete a.eventArgs});(j||g)&&a.redraw()}));j&&this.drawChartBox();p(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});
-d&&d.reset(!0);m.draw();J(this,"redraw");n&&this.cloneRenderTo(!0);p(o,function(a){a.call()})},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Va(b),delete this.renderToClone):(c&&
-c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=b=this.renderTo.cloneNode(0),K(b,{position:"absolute",top:"-9999px",display:"block"}),b.style.setProperty&&b.style.setProperty("display","block","important"),E.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e="highcharts-"+Fb++;if(Sa(a))this.renderTo=a=E.getElementById(a);a||ma(13,!0);c=H(Y(a,"data-highcharts-chart"));!isNaN(c)&&ba[c]&&ba[c].hasRendered&&
-ba[c].destroy();Y(a,"data-highcharts-chart",this.index);a.innerHTML="";!b.skipClone&&!a.offsetWidth&&this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;this.container=a=aa(Wa,{className:"highcharts-container"+(b.className?" "+b.className:""),id:e},s({position:"relative",overflow:"hidden",width:c+"px",height:d+"px",textAlign:"left",lineHeight:"normal",zIndex:0,"-webkit-tap-highlight-color":"rgba(0,0,0,0)"},b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=
-b.forExport?new na(a,c,d,b.style,!0):new Za(a,c,d,b.style);la&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,f=q(e.margin,20),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!u(d[0]))this.plotTop=v(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i==="right"){if(!u(d[1]))this.marginRight=v(this.marginRight,c.legendWidth-g+f+a[1])}else if(i===
-"left"){if(!u(d[3]))this.plotLeft=v(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j==="top"){if(!u(d[0]))this.plotTop=v(this.plotTop,c.legendHeight+h+f+a[0])}else if(j==="bottom"&&!u(d[2]))this.marginBottom=v(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&p(this.axes,function(a){a.getOffset()});u(d[3])||(this.plotLeft+=b[3]);u(d[0])||(this.plotTop+=b[0]);
-u(d[2])||(this.marginBottom+=b[2]);u(d[1])||(this.marginRight+=b[1]);this.setChartSize()},reflow:function(a){var b=this,c=b.options.chart,d=b.renderTo,e=c.width||pb(d,"width"),f=c.height||pb(d,"height"),c=a?a.target:S,d=function(){if(b.container)b.setSize(e,f,!1),b.hasUserSize=null};if(!b.hasUserSize&&e&&f&&(c===S||c===E)){if(e!==b.containerWidth||f!==b.containerHeight)clearTimeout(b.reflowTimeout),a?b.reflowTimeout=setTimeout(d,100):d();b.containerWidth=e;b.containerHeight=f}},initReflow:function(){var a=
-this,b=function(b){a.reflow(b)};z(S,"resize",b);z(a,"destroy",function(){U(S,"resize",b)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&J(d,"endResize",null,function(){d.isResizing-=1})};ab(c,d);d.oldChartHeight=d.chartHeight;d.oldChartWidth=d.chartWidth;if(u(a))d.chartWidth=e=v(0,t(a)),d.hasUserSize=!!e;if(u(b))d.chartHeight=f=v(0,t(b));(Fa?rb:K)(d.container,{width:e+"px",height:f+"px"},Fa);d.setChartSize(!0);d.renderer.setSize(e,f,c);d.maxTicks=null;p(d.axes,function(a){a.isDirty=
-!0;a.setScale()});p(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.layOutTitles();d.getMargins();d.redraw(c);d.oldChartHeight=null;J(d,"resize");Fa===!1?g():setTimeout(g,Fa&&Fa.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,d=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=t(this.plotLeft);this.plotTop=j=t(this.plotTop);this.plotWidth=k=v(0,t(d-i-this.marginRight));this.plotHeight=
-l=v(0,t(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*V(this.plotBorderWidth/2);b=Ya(v(d,h[3])/2);c=Ya(v(d,h[0])/2);this.clipBox={x:b,y:c,width:V(this.plotSizeX-v(d,h[1])/2-b),height:v(0,V(this.plotSizeY-v(d,h[2])/2-c))};a||p(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=
-this.spacing,b=this.margin;this.plotTop=q(b[0],a[0]);this.marginRight=q(b[1],a[1]);this.marginBottom=q(b[2],a[2]);this.plotLeft=q(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,d=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,n,o=this.plotLeft,
-x=this.plotTop,q=this.plotWidth,p=this.plotHeight,w=this.plotBox,r=this.clipRect,t=this.clipBox;n=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp({width:c-n,height:d-n}));else{e={fill:j||$};if(i)e.stroke=a.borderColor,e["stroke-width"]=i;this.chartBackground=b.rect(n/2,n/2,c-n,d-n,a.borderRadius,i).attr(e).addClass("highcharts-background").add().shadow(a.shadow)}if(k)f?f.animate(w):this.plotBackground=b.rect(o,x,q,p,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(w):this.plotBGImage=
-b.image(l,o,x,q,p).add();r?r.animate({width:t.width,height:t.height}):this.clipRect=b.clipRect(t);if(m)g?g.animate(g.crisp({x:o,y:x,width:q,height:p,strokeWidth:-m})):this.plotBorder=b.rect(o,x,q,p,0,-m).attr({stroke:a.plotBorderColor,"stroke-width":m,fill:$,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,c,d=a.options.series,e,f;p(["inverted","angular","polar"],function(g){c=B[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&
-e--;)(c=B[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;p(b,function(a){a.linkedSeries.length=0});p(b,function(b){var d=b.options.linkedTo;if(Sa(d)&&(d=d===":previous"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},renderSeries:function(){p(this.series,function(a){a.translate();a.setTooltipPoints&&a.setTooltipPoints();a.render()})},renderLabels:function(){var a=this,b=a.options.labels;b.items&&p(b.items,function(c){var d=s(b.style,
-c.style),e=H(d.left)+a.plotLeft,f=H(d.top)+a.plotTop+12;delete d.left;delete d.top;a.renderer.text(c.html,e,f).attr({zIndex:2}).css(d).add()})},render:function(){var a=this.axes,b=this.renderer,c=this.options;this.setTitle();this.legend=new sb(this,c.legend);this.getStacks();p(a,function(a){a.setScale()});this.getMargins();this.maxTicks=null;p(a,function(a){a.setTickPositions(!0);a.setMaxTicks()});this.adjustTickAmounts();this.getMargins();this.drawChartBox();this.hasCartesianSeries&&p(a,function(a){a.render()});
-if(!this.seriesGroup)this.seriesGroup=b.g("series-group").attr({zIndex:3}).add();this.renderSeries();this.renderLabels();this.showCredits(c.credits);this.hasRendered=!0},showCredits:function(a){if(a.enabled&&!this.credits)this.credits=this.renderer.text(a.text,0,0).on("click",function(){if(a.href)location.href=a.href}).attr({align:a.position.align,zIndex:8}).css(a.style).add().align(a.position)},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;J(a,"destroy");ba[a.index]=
-r;gb--;a.renderTo.removeAttribute("data-highcharts-chart");U(a);for(e=b.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();p("title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer".split(","),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML="",U(d),f&&Va(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!ea&&S==S.top&&
-E.readyState!=="complete"||la&&!S.canvg?(la?Vb.push(function(){a.firstRender()},a.options.global.canvasToolsURL):E.attachEvent("onreadystatechange",function(){E.detachEvent("onreadystatechange",a.firstRender);E.readyState==="complete"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender()){a.getContainer();J(a,"init");a.resetMargins();a.setChartSize();a.propFromSeries();a.getAxes();p(b.series||[],function(b){a.initSeries(b)});a.linkSeries();J(a,
-"beforeRender");if(N.Pointer)a.pointer=new $a(a,b);a.render();a.renderer.draw();c&&c.apply(a,[a]);p(a.callbacks,function(b){b.apply(a,[a])});a.cloneRenderTo(!0);J(a,"load")}},splashArray:function(a,b){var c=b[a],c=ha(c)?c:[c,c,c,c];return[q(b[a+"Top"],c[0]),q(b[a+"Right"],c[1]),q(b[a+"Bottom"],c[2]),q(b[a+"Left"],c[3])]}};ya.prototype.callbacks=[];ga=N.CenteredSeriesMixin={getCenter:function(){var a=this.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,
-a=[q(b[0],"50%"),q(b[1],"50%"),a.size||"100%",a.innerSize||0],g=A(e,f),h;return za(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*H(a)/100:a)+(d?c:0)})}};var Ba=function(){};Ba.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=0;a.chart.pointCount++;return this},applyOptions:function(a,
-b){var c=this.series,d=c.options.pointValKey||c.pointValKey,a=Ba.prototype.optionsToObject.call(this,a);s(this,a);this.options=this.options?s(this.options,a):a;if(d)this.y=this[d];if(this.x===r&&c)this.x=b===r?c.autoIncrement():b;return this},optionsToObject:function(a){var b={},c=this.series,d=c.pointArrayMap||["y"],e=d.length,f=0,g=0;if(typeof a==="number"||a===null)b[d[0]]=a;else if(Ja(a)){if(a.length>e){c=typeof a[0];if(c==="string")b.name=a[0];else if(c==="number")b.x=a[0];f++}for(;ga+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart,c=b.options.plotOptions,b=b.userOptions||{},d=b.plotOptions||{},e=c[this.type];this.userOptions=a;c=y(e,c.series,a);this.tooltipOptions=y(F.tooltip,F.plotOptions[this.type].tooltip,b.tooltip,d.series&&d.series.tooltip,d[this.type]&&
-d[this.type].tooltip,a.tooltip);e.marker===null&&delete c.marker;return c},getCyclic:function(a,b,c){var d=this.userOptions,e="_"+a+"Index",f=a+"Counter";b||(u(d[e])?b=d[e]:(d[e]=b=this.chart[f]%c.length,this.chart[f]+=1),b=c[b]);this[a]=b},getColor:function(){this.options.colorByPoint||this.getCyclic("color",this.options.color||W[this.type].color,this.chart.options.colors)},getSymbol:function(){var a=this.options.marker;this.getCyclic("symbol",a.symbol,this.chart.options.symbols);if(/^url/.test(this.symbol))a.radius=
-0},drawLegendSymbol:T.drawLineMarker,setData:function(a,b,c,d){var e=this,f=e.points,g=f&&f.length||0,h,i=e.options,j=e.chart,k=null,l=e.xAxis,m=l&&!!l.categories,n=e.tooltipPoints,o=i.turboThreshold,x=this.xData,I=this.yData,t=(h=e.pointArrayMap)&&h.length,a=a||[];h=a.length;b=q(b,!0);if(d!==!1&&h&&g===h&&!e.cropped&&!e.hasGroupedData)p(a,function(a,b){f[b].update(a,!1,null,!1)});else{e.xIncrement=null;e.pointRange=m?1:i.pointRange;e.colorCounter=0;p(this.parallelArrays,function(a){e[a+"Data"].length=
-0});if(o&&h>o){for(c=0;k===null&&ci||this.forceCrop))if(b[d-1]m)b=[],c=[];else if(b[0]m)e=
-this.cropData(this.xData,this.yData,n,m),b=e.xData,c=e.yData,e=e.start,f=!0,k=b.length;for(i=b.length-1;i>=0;i--)d=b[i]-b[i-1],!f&&b[i]>n&&b[i]0&&(g===r||d=c){f=v(0,i-h);break}for(;i<
-e;i++)if(a[i]>d){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,b=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m0),j=this.getExtremesFromAll||this.cropped||(c[l+1]||j)>=g&&(c[l-1]||j)<=h,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=
-k;this.dataMin=q(void 0,Ua(e));this.dataMax=q(void 0,Ea(e))},translate:function(){this.processedXData||this.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i==="between"||pa(i),k=a.threshold,a=0;a0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h,o?n:l).attr(a).add(p)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=q(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=W[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color;f={stroke:g,fill:g};var h=a.points||[],i,j=[],k,l=a.pointAttrToOptions;k=a.hasPointSpecificOptions;var m=b.negativeColor,n=
-c.lineColor,o=c.fillColor;i=b.turboThreshold;var q;b.marker?(e.radius=e.radius||c.radius+e.radiusPlus,e.lineWidth=e.lineWidth||c.lineWidth+e.lineWidthPlus):e.color=e.color||Ia(e.color||g).brighten(e.brightness).get();j[""]=a.convertAttribs(c,f);p(["hover","select"],function(b){j[b]=a.convertAttribs(d[b],j[""])});a.pointAttr=j;g=h.length;if(!i||g1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=
-b},drawGraph:function(){var a=this,b=this.options,c=[["graph",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=b.linecap!=="square",g=this.getGraphPath(),h=b.negativeColor;h&&c.push(["graphNeg",h]);p(c,function(c,h){var k=c[0],l=a[k];if(l)hb(l),l.animate({d:g});else if(d&&g.length)l={stroke:c[1],"stroke-width":d,fill:$,zIndex:1},e?l.dashstyle=e:f&&(l["stroke-linecap"]=l["stroke-linejoin"]="round"),a[k]=a.chart.renderer.path(g).attr(l).add(a.group).shadow(!h&&b.shadow)})},clipNeg:function(){var a=
-this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j=b.chartHeight,k=v(e,j),l=this.yAxis;if(d&&(f||g)){d=t(l.toPixels(a.threshold||0,!0));d<0&&(k-=d);a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?(b=k,e=a):(b=a,e=k);h?
-(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};p(["group","markerGroup"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)z(c,"resize",a),z(b,"destroy",function(){U(c,"resize",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],g=!f;g&&(this[a]=
-f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?"attr":"animate"](this.getPlotBox());return f},getPlotBox:function(){var a=this.chart,b=this.xAxis,c=this.yAxis;if(a.inverted)b=c,c=this.xAxis;return{translateX:b?b.left:a.plotLeft,translateY:c?c.top:a.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this,b=a.chart,c,d=a.options,e=(c=d.animation)&&!!a.animate&&b.renderer.isSVG&&q(c.duration,500)||0,f=a.visible?"visible":"hidden",g=d.zIndex,h=a.hasRendered,i=b.seriesGroup;
-c=a.plotGroup("group","series",f,g,i);a.markerGroup=a.plotGroup("markerGroup","markers",f,g,i);e&&a.animate(!0);a.getAttribs();c.inverted=a.isCartesian?b.inverted:!1;a.drawGraph&&(a.drawGraph(),a.clipNeg());p(a.points,function(a){a.redraw&&a.redraw()});a.drawDataLabels&&a.drawDataLabels();a.visible&&a.drawPoints();a.drawTracker&&a.options.enableMouseTracking!==!1&&a.drawTracker();b.inverted&&a.invertGroups();d.clip!==!1&&!a.sharedClipKey&&!h&&c.clip(b.clipRect);e&&a.animate();if(!h)e?a.animationTimeout=
-setTimeout(function(){a.afterAnimate()},e):a.afterAnimate();a.isDirty=a.isDirtyData=!1;a.hasRendered=!0},redraw:function(){var a=this.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:q(d&&d.left,a.plotLeft),translateY:q(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints&&this.setTooltipPoints(!0);this.render();b&&J(this,"updatedData")}};Rb.prototype={destroy:function(){Na(this,this.axis)},
-render:function(a){var b=this.options,c=b.format,c=c?Ma(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:"hidden"}):this.label=this.axis.chart.renderer.text(c,null,null,b.useHTML).css(b.style).attr({align:this.textAlign,rotation:b.rotation,visibility:"hidden"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,g=c.translate(c.usePercentage?100:this.total,0,0,0,1),c=c.translate(0),c=P(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,
-f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e[this.options.crop===!1||d.isInsidePlot(f.x,f.y)?"show":"hide"](!0)}};D.prototype.buildStacks=function(){var a=this.series,b=q(this.options.reversedStacks,!0),c=a.length;if(!this.isXAxis){for(this.usePercentage=!1;c--;)a[b?c:a.length-c-1].setStackedPoints();if(this.usePercentage)for(c=0;cg;)h--;this.updateParallelArrays(d,"splice",h,0,0);this.updateParallelArrays(d,h);if(j&&d.name)j[g]=d.name;l.splice(h,0,a);m&&(this.data.splice(h,0,null),this.processData());e.legendType==="point"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):
-(f.shift(),this.updateParallelArrays(d,"shift"),l.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},remove:function(a,b){var c=this,d=c.chart,a=q(a,!0);if(!c.isRemoving)c.isRemoving=!0,J(c,"remove",null,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},update:function(a,b){var c=this,d=this.chart,e=this.userOptions,f=this.type,g=B[f].prototype,h=["group","markerGroup","dataLabelsGroup"],i;p(h,function(a){h[a]=c[a];delete c[a]});
-a=y(e,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);for(i in g)g.hasOwnProperty(i)&&(this[i]=r);s(this,B[a.type||f].prototype);p(h,function(a){c[a]=h[a]});this.init(d,a);d.linkSeries();q(b,!0)&&d.redraw(!1)}});s(D.prototype,{update:function(a,b){var c=this.chart,a=c.options[this.coll][this.options.index]=y(this.userOptions,a);this.destroy(!0);this._addedPlotLB=r;this.init(c,s(a,{events:r}));c.isDirtyBox=!0;q(b,!0)&&c.redraw()},remove:function(a){for(var b=
-this.chart,c=this.coll,d=this.series,e=d.length;e--;)d[e]&&d[e].remove(!1);ta(b.axes,this);ta(b[c],this);b.options[c].splice(this.options.index,1);p(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;q(a,!0)&&b.redraw()},setTitle:function(a,b){this.update({title:a},b)},setCategories:function(a,b){this.update({categories:a},b)}});var Ca=ia(M);B.line=Ca;W.area=y(L,{threshold:0});var wa=ia(M,{type:"area",getSegments:function(){var a=this,b=[],c=[],d=[],e=this.xAxis,f=this.yAxis,g=
-f.stacks[this.stackKey],h={},i,j,k=this.points,l=this.options.connectNulls,m,n;if(this.options.stacking&&!this.cropped){for(m=0;m=0;d--)g=q(a[d].yBottom,f),da&&i>e?(i=v(a,e),k=2*e-i):ig&&k>e?(k=v(g,e),i=2*e-k):k0.5*a.xAxis.len?0:1),e=a.yAxis,f=a.translatedThreshold=e.getThreshold(c.threshold),g=q(c.minPointLength,5),h=a.getColumnMetrics(),i=h.width,j=a.barW=v(i,1+2*d),k=a.pointXOffset=h.offset,l=-(d%2?0.5:0),m=d%2?0.5:1;b.renderer.isVML&&b.inverted&&(m+=1);c.pointPadding&&(j=Ya(j));M.prototype.translate.apply(a);p(a.points,function(c){var d=q(c.yBottom,f),h=A(v(-999-
-d,c.plotY),e.len+999+d),p=c.plotX+k,r=j,w=A(h,d),s;s=v(h,d)-w;P(s)g?d-g:f-(e.translate(c.y,0,1,0,1)<=f?g:0)));c.barX=p;c.pointWidth=i;c.tooltipPos=b.inverted?[e.len-h,a.xAxis.len-p-r/2]:[p+r/2,h+e.pos-b.plotTop];r=t(p+r)+l;p=t(p)+l;r-=p;d=P(w)<0.5;s=t(w+s)+m;w=t(w)+m;s-=w;d&&(w-=1,s+=1);c.shapeType="rect";c.shapeArgs={x:p,y:w,width:r,height:s}})},getSymbol:ra,drawLegendSymbol:T.drawRectangle,drawGraph:ra,drawPoints:function(){var a=this,b=this.chart,c=a.options,d=b.renderer,
-e=c.animationLimit||250,f,g;p(a.points,function(h){var i=h.plotY,j=h.graphic;if(i!==r&&!isNaN(i)&&h.y!==null)f=h.shapeArgs,i=u(a.borderWidth)?{"stroke-width":a.borderWidth}:{},g=h.pointAttr[h.selected?"select":""]||a.pointAttr[""],j?(hb(j),j.attr(i)[b.pointCount● {series.name} ',pointFormat:"x: {point.x} y: {point.y} "},stickyTracking:!1});wa=ia(M,{type:"scatter",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:["markerGroup","dataLabelsGroup"],takeOrdinalPosition:!1,singularTooltips:!0,drawGraph:function(){this.options.lineWidth&&M.prototype.drawGraph.call(this)}});B.scatter=wa;W.pie=y(L,{borderColor:"#FFFFFF",borderWidth:1,
-center:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0,legendType:"point",marker:null,size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});L={type:"pie",isCartesian:!1,pointClass:ia(Ba,{init:function(){Ba.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;s(a,{visible:a.visible!==!1,name:q(a.name,"Slice")});b=function(b){a.slice(b.type===
-"select")};z(a,"select",b);z(a,"unselect",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart;b.visible=b.options.visible=a=a===r?!b.visible:a;c.options.data[Qa(b,c.data)]=b.options;p(["graphic","dataLabel","connector","shadowGroup"],function(c){if(b[c])b[c][a?"show":"hide"](!0)});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;ab(c,d.chart);q(b,!0);this.sliced=this.options.sliced=
-a=u(a)?a:!this.sliced;d.options.data[Qa(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)},haloPath:function(a){var b=this.shapeArgs,c=this.series.chart;return this.sliced||!this.visible?[]:this.series.chart.renderer.symbols.arc(c.plotLeft+b.x,c.plotTop+b.y,b.r+a,b.r+a,{innerR:this.shapeArgs.r,start:b.start,end:b.end})}}),requireSorting:!1,noSharedTooltip:!0,trackerGroups:["group","dataLabelsGroup"],
-axisTypes:[],pointAttrToOptions:{stroke:"borderColor","stroke-width":"borderWidth",fill:"color"},singularTooltips:!0,getColor:ra,animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)p(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b,c,d){M.prototype.setData.call(this,a,!1,c,d);this.processData();this.generatePoints();q(b,!0)&&this.chart.redraw(c)},
-generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;M.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a0?e.y/b*100:0,e.total=b},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=ua/180*(i-90),i=(this.endAngleRad=ua/180*(q(c.endAngle,i+360)-90))-j,k=this.points,l=c.dataLabels.distance,
-c=c.ignoreHiddenPoint,m,n=k.length,o;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=X.asin(A((b-a[1])/(a[2]/2+l),1));return a[0]+(c?-1:1)*fa(h)*(a[2]/2+l)};for(m=0;m1.5*ua?h-=2*ua:h<-ua/2&&(h+=2*ua);o.slicedTranslation={translateX:t(fa(h)*d),translateY:t(ka(h)*d)};f=fa(h)*a[2]/2;g=ka(h)*a[2]/2;o.tooltipPos=
-[a[0]+f*0.7,a[1]+g*0.7];o.half=h<-ua/2||h>ua/2?1:0;o.angle=h;e=A(e,l/2);o.labelPos=[a[0]+f+fa(h)*l,a[1]+g+ka(h)*l,a[0]+f+fa(h)*e,a[1]+g+ka(h)*e,a[0]+f,a[1]+g,l<0?"center":o.half?"right":"left",h]}},drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g("shadow").add(a.group);p(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g("shadow").add(a.shadowGroup);c=h.sliced?h.slicedTranslation:
-{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(s(g,c)):h.graphic=d=b[h.shapeType](g).setRadialReference(a.center).attr(h.pointAttr[h.selected?"select":""]).attr({"stroke-linejoin":"round"}).attr(c).add(a.group).shadow(e,f);h.visible!==void 0&&h.setVisible(h.visible)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawLegendSymbol:T.drawRectangle,getCenter:ga.getCenter,getSymbol:ra};L=ia(M,L);B.pie=L;M.prototype.drawDataLabels=function(){var a=
-this,b=a.options,c=b.cursor,d=b.dataLabels,e=a.points,f,g,h=a.hasRendered||0,i,j;if(d.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(d),j=a.plotGroup("dataLabelsGroup","data-labels",d.defer?"hidden":"visible",d.zIndex||6),q(d.defer,!0)&&(j.attr({opacity:+h}),h||z(a,"afterAnimate",function(){a.visible&&j.show();j[b.animation?"animate":"attr"]({opacity:1},{duration:200})})),g=d,p(e,function(b){var e,h=b.dataLabel,n,o,p=b.connector,t=!0;f=b.options&&b.options.dataLabels;e=q(f&&f.enabled,
-g.enabled);if(h&&!e)b.dataLabel=h.destroy();else if(e){d=y(g,f);e=d.rotation;n=b.getLabelConfig();i=d.format?Ma(d.format,n):d.formatter.call(n,d);d.style.color=q(d.color,d.style.color,a.color,"black");if(h)if(u(i))h.attr({text:i}),t=!1;else{if(b.dataLabel=h=h.destroy(),p)b.connector=p.destroy()}else if(u(i)){h={fill:d.backgroundColor,stroke:d.borderColor,"stroke-width":d.borderWidth,r:d.borderRadius||0,rotation:e,padding:d.padding,zIndex:1};for(o in h)h[o]===r&&delete h[o];h=b.dataLabel=a.chart.renderer[e?
-"text":"label"](i,0,-999,null,null,null,d.useHTML).attr(h).css(s(d.style,c&&{cursor:c})).add(j).shadow(d.shadow)}h&&a.alignDataLabel(b,h,d,null,t)}})};M.prototype.alignDataLabel=function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=q(a.plotX,-999),i=q(a.plotY,-999),j=b.getBBox();if(a=this.visible&&(a.series.forceDL||f.isInsidePlot(h,t(i),g)||d&&f.isInsidePlot(h,g?d.x+1:d.y+d.height-1,g)))d=s({x:g?f.plotWidth-i:h,y:t(g?f.plotHeight-h:i),width:0,height:0},d),s(c,{width:j.width,height:j.height}),c.rotation?
-b[e?"attr":"animate"]({x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2}).attr({align:c.align}):(b.align(c,null,d),g=b.alignAttr,q(c.overflow,"justify")==="justify"?this.justifyDataLabel(b,c,g,j,d,e):q(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)));if(!a)b.attr({y:-999}),b.placed=!1};M.prototype.justifyDataLabel=function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h==="right"?b.align="left":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h==="left"?
-b.align="right":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i==="bottom"?b.verticalAlign="top":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i==="top"?b.verticalAlign="bottom":b.y=g.plotHeight-j,k=!0;if(k)a.placed=!f,a.align(b,null,e)};if(B.pie)B.pie.prototype.drawDataLabels=function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=q(e.connectorPadding,10),g=q(e.connectorWidth,1),h=d.plotWidth,i=d.plotHeight,j,k,l=q(e.softConnector,!0),m=e.distance,n=a.center,o=n[2]/2,x=n[1],r=m>0,s,w,u,y=[[],[]],
-z,O,E,G,C,R=[0,0,0,0],H=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){M.prototype.drawDataLabels.apply(a);p(b,function(a){a.dataLabel&&a.visible&&y[a.half].push(a)});for(G=2;G--;){var B=[],K=[],F=y[G],J=F.length,D;if(J){a.sortByAngle(F,G-0.5);for(C=b=0;!b&&F[C];)b=F[C]&&F[C].dataLabel&&(F[C].dataLabel.getBBox().height||21),C++;if(m>0){w=A(x+o+m,d.plotHeight);for(C=v(0,x-o-m);C<=w;C+=b)B.push(C);w=B.length;if(J>w){c=[].concat(F);c.sort(H);for(C=J;C--;)c[C].rank=C;for(C=
-J;C--;)F[C].rank>=w&&F.splice(C,1);J=F.length}for(C=0;C0){if(w=K.pop(),D=w.i,O=w.y,c>O&&B[D+1]!==null||ch-f&&(R[1]=v(t(z+w-h+f),R[1])),O-b/2<0?R[0]=v(t(-O+b/2),R[0]):O+b/2>i&&(R[2]=v(t(O+b/2-i),R[2]))}}}if(Ea(R)===0||this.verifyDataLabelOverflow(R))this.placeDataLabels(),r&&g&&p(this.points,function(b){j=b.connector;u=b.labelPos;if((s=b.dataLabel)&&s._pos)E=s._attr.visibility,z=s.connX,O=s.connY,k=
-l?["M",z+(u[6]==="left"?5:-5),O,"C",z,O,2*u[2]-u[4],2*u[3]-u[5],u[2],u[3],"L",u[4],u[5]]:["M",z+(u[6]==="left"?5:-5),O,"L",u[2],u[3],"L",u[4],u[5]],j?(j.animate({d:k}),j.attr("visibility",E)):b.connector=j=a.chart.renderer.path(k).attr({"stroke-width":g,stroke:e.connectorColor||b.color||"#606060",visibility:E}).add(a.dataLabelsGroup);else if(j)b.connector=j.destroy()})}},B.pie.prototype.placeDataLabels=function(){p(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?
-"animate":"attr"](b),a.moved=!0):a&&a.attr({y:-999})})},B.pie.prototype.alignDataLabel=ra,B.pie.prototype.verifyDataLabelOverflow=function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||80,f;d[0]!==null?e=v(b[2]-v(a[1],a[3]),c):(e=v(b[2]-a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=v(A(e,b[2]-v(a[0],a[2])),c):(e=v(A(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);eq(this.translatedThreshold,f.plotSizeY),j=q(c.inside,!!this.options.stacking);if(h&&(d=y(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=q(c.align,!g||j?"center":i?"right":"left");c.verticalAlign=q(c.verticalAlign,g||j?"middle":
-i?"top":"bottom");M.prototype.alignDataLabel.call(this,a,b,c,d,e)};var jb=N.TrackerMixin={drawTrackerPoint:function(){var a=this,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==r&&e!==b.hoverPoint)e.onMouseOver(c)};p(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)p(a.trackerGroups,function(b){if(a[b]&&
-(a[b].addClass("highcharts-tracker").on("mouseover",f).on("mouseout",function(a){c.onTrackerMouseOut(a)}).css(e),db))a[b].on("touchstart",f)}),a._hasTracking=!0},drawTrackerGraph:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,n=function(){if(f.hoverSeries!==a)a.onMouseOver()},o="rgba(192,192,192,"+(ea?1.0E-4:0.002)+")";if(e&&!c)for(m=
-e+1;m--;)d[m]==="M"&&d.splice(m+1,0,d[m+1]-i,d[m+2],"L"),(m&&d[m]==="M"||m===e)&&d.splice(m,0,"L",d[m-2]+i,d[m-1]);for(m=0;mA(k.dataMin,k.min)&&i=f.min&&c<=f.max){h=b[i+1];c=d===r?0:d+1;for(d=b[i+1]?A(v(0,V((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===r?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;
-J(this,a?"select":"unselect")},drawTracker:jb.drawTrackerGraph});Q(M.prototype,"init",function(a){var b;a.apply(this,Array.prototype.slice.call(arguments,1));(b=this.xAxis)&&b.options.ordinal&&z(this,"updatedData",function(){delete b.ordinalIndex})});Q(D.prototype,"getTimeTicks",function(a,b,c,d,e,f,g,h){var i=0,j=0,k,l={},m,n,o,p=[],q=-Number.MAX_VALUE,t=this.options.tickPixelInterval;if(!this.options.ordinal||!f||f.length<3||c===r)return a.call(this,b,c,d,e);for(n=f.length;j
-d;f[j]g*5||o){if(f[j]>q){for(k=a.call(this,b,f[i],f[j],e);k.length&&k[0]<=q;)k.shift();k.length&&(q=k[k.length-1]);p=p.concat(k)}i=j+1}if(o)break}a=k.info;if(h&&a.unitRange<=G.hour){j=p.length-1;for(i=1;id?a-1:a;for(w=void 0;h--;)i=j[h],d=w-i,w&&d2){d=b[1]-
-b[0];for(g=a-1;g--&&!c;)b[g+1]-b[g]!==d&&(c=!0);if(!this.options.keepOrdinalPadding&&(b[0]-f>d||e-b[b.length-1]>d))c=!0}c?(this.ordinalPositions=b,c=this.val2lin(v(f,b[0]),!0),d=v(this.val2lin(A(e,b[b.length-1]),!0),1),this.ordinalSlope=e=(e-f)/(d-c),this.ordinalOffset=f-c*e):this.ordinalPositions=this.ordinalSlope=this.ordinalOffset=r}this.groupIntervalFactor=null},val2lin:function(a,b){var c=this.ordinalPositions;if(c){var d=c.length,e,f;for(e=d;e--;)if(c[e]===a){f=e;break}for(e=d-1;e--;)if(a>c[e]||
-e===0){c=(a-c[e])/(c[e+1]-c[e]);f=e+c;break}return b?f:this.ordinalSlope*(f||0)+this.ordinalOffset}else return a},lin2val:function(a,b){var c=this.ordinalPositions;if(c){var d=this.ordinalSlope,e=this.ordinalOffset,f=c.length-1,g,h;if(b)a<0?a=c[0]:a>f?a=c[f]:(f=V(a),h=a-f);else for(;f--;)if(g=d*f+e,a>=g){d=d*(f+1)+e;h=(a-g)/(d-g);break}return h!==r&&c[f]!==r?c[f]+(h?h*(c[f+1]-c[f]):0):a}else return a},getExtendedPositions:function(){var a=this.chart,b=this.series[0].currentDataGrouping,c=this.ordinalIndex,
-d=b?b.count+b.unitName:"raw",e=this.getExtremes(),f,g;if(!c)c=this.ordinalIndex={};if(!c[d])f={series:[],getExtremes:function(){return{min:e.dataMin,max:e.dataMax}},options:{ordinal:!0},val2lin:D.prototype.val2lin},p(this.series,function(c){g={xAxis:f,xData:c.xData,chart:a,destroyGroupedData:ra};g.options={dataGrouping:b?{enabled:!0,forced:!0,approximation:"open",units:[[b.unitName,[b.count]]]}:{enabled:!1}};c.processData.apply(g);f.series.push(g)}),this.beforeSetTickPositions.apply(f),c[d]=f.ordinalPositions;
-return c[d]},getGroupIntervalFactor:function(a,b,c){var d=0,c=c.processedXData,e=c.length,f=[],g=this.groupIntervalFactor;if(!g){for(;d1)k&&p(k,function(a){a.setState()}),f<0?(k=m,o=c.ordinalPositions?c:m):(k=c.ordinalPositions?c:m,o=m),m=o.ordinalPositions,h>m[m.length-1]&&m.push(h),this.fixedRange=j-i,f=c.toFixedRange(null,null,l.apply(k,[n.apply(k,[i,!0])+f,!0]),l.apply(o,[n.apply(o,[j,!0])+
-f,!0])),f.min>=A(g.dataMin,i)&&f.max<=v(h,j)&&c.setExtremes(f.min,f.max,!0,!1,{trigger:"pan"}),this.mouseDownX=d,K(this.container,{cursor:"move"})}else e=!0}else e=!0;e&&a.apply(this,Array.prototype.slice.call(arguments,1))});Q(M.prototype,"getSegments",function(a){var b,c=this.options.gapSize,d=this.xAxis;a.apply(this,Array.prototype.slice.call(arguments,1));if(c)b=this.segments,p(b,function(a,f){for(var g=a.length-1;g--;)a[g+1].x-a[g].x>d.closestPointRange*c&&b.splice(f+1,0,a.splice(g+1,a.length-
-g))})});var da=M.prototype,L=Jb.prototype,hc=da.processData,ic=da.generatePoints,jc=da.destroy,kc=L.tooltipHeaderFormatter,lc={approximation:"average",groupPixelWidth:2,dateTimeLabelFormats:{millisecond:["%A, %b %e, %H:%M:%S.%L","%A, %b %e, %H:%M:%S.%L","-%H:%M:%S.%L"],second:["%A, %b %e, %H:%M:%S","%A, %b %e, %H:%M:%S","-%H:%M:%S"],minute:["%A, %b %e, %H:%M","%A, %b %e, %H:%M","-%H:%M"],hour:["%A, %b %e, %H:%M","%A, %b %e, %H:%M","-%H:%M"],day:["%A, %b %e, %Y","%A, %b %e","-%A, %b %e, %Y"],week:["Week from %A, %b %e, %Y",
-"%A, %b %e","-%A, %b %e, %Y"],month:["%B %Y","%B","-%B %Y"],year:["%Y","%Y","-%Y"]}},Wb={line:{},spline:{},area:{},areaspline:{},column:{approximation:"sum",groupPixelWidth:10},arearange:{approximation:"range"},areasplinerange:{approximation:"range"},columnrange:{approximation:"range",groupPixelWidth:10},candlestick:{approximation:"ohlc",groupPixelWidth:10},ohlc:{approximation:"ohlc",groupPixelWidth:5}},Xb=[["millisecond",[1,2,5,10,20,25,50,100,200,500]],["second",[1,2,5,10,15,30]],["minute",[1,2,
-5,10,15,30]],["hour",[1,2,3,4,6,8,12]],["day",[1]],["week",[1]],["month",[1,3,6]],["year",null]],Ra={sum:function(a){var b=a.length,c;if(!b&&a.hasNulls)c=null;else if(b)for(c=0;b--;)c+=a[b];return c},average:function(a){var b=a.length,a=Ra.sum(a);typeof a==="number"&&b&&(a/=b);return a},open:function(a){return a.length?a[0]:a.hasNulls?null:r},high:function(a){return a.length?Ea(a):a.hasNulls?null:r},low:function(a){return a.length?Ua(a):a.hasNulls?null:r},close:function(a){return a.length?a[a.length-
-1]:a.hasNulls?null:r},ohlc:function(a,b,c,d){a=Ra.open(a);b=Ra.high(b);c=Ra.low(c);d=Ra.close(d);if(typeof a==="number"||typeof b==="number"||typeof c==="number"||typeof d==="number")return[a,b,c,d]},range:function(a,b){a=Ra.low(a);b=Ra.high(b);if(typeof a==="number"||typeof b==="number")return[a,b]}};da.groupData=function(a,b,c,d){var e=this.data,f=this.options.data,g=[],h=[],i=a.length,j,k,l=!!b,m=[[],[],[],[]],d=typeof d==="function"?d:Ra[d],n=this.pointArrayMap,o=n&&n.length,p;for(p=0;p<=i;p++)if(a[p]>=
-c[0])break;for(;p<=i;p++){for(;c[1]!==r&&a[p]>=c[1]||p===i;)if(j=c.shift(),k=d.apply(0,m),k!==r&&(g.push(j),h.push(k)),m[0]=[],m[1]=[],m[2]=[],m[3]=[],p===i)break;if(p===i)break;if(n){j=this.cropStart+p;j=e&&e[j]||this.pointClass.prototype.applyOptions.apply({series:this},[f[j]]);var q;for(k=0;k0;)f[c]+=h/2;f[0]=d}this.currentDataGrouping=j.info;if(b.pointRange===null)this.pointRange=j.info.totalRange;this.closestPointRange=j.info.totalRange;if(u(f[0])&&f[0]=g.closestPointRange||G[h]<=G.day&&a.key%G[h]>0){e=d[h][0];break}e=xa(e,a.key);f&&(e+=xa(f,a.key+b.totalRange-1));a=c.headerFormat.replace("{point.key}",e)}else a=kc.call(this,a);return a};da.destroy=function(){for(var a=this.groupedData||[],b=a.length;b--;)a[b]&&a[b].destroy();jc.apply(this)};Q(da,"setOptions",function(a,b){var c=a.call(this,b),d=this.type,e=this.chart.options.plotOptions,
-f=W[d].dataGrouping;if(Wb[d])f||(f=y(lc,Wb[d])),c.dataGrouping=y(f,e.series&&e.series.dataGrouping,e[d].dataGrouping,b.dataGrouping);if(this.chart.options._stock)this.requireSorting=!0;return c});Q(D.prototype,"setScale",function(a){a.call(this);p(this.series,function(a){a.hasProcessed=!1})});D.prototype.getGroupPixelWidth=function(){var a=this.series,b=a.length,c,d=0,e=!1,f;for(c=b;c--;)(f=a[c].options.dataGrouping)&&(d=v(d,f.groupPixelWidth));for(c=b;c--;)if((f=a[c].options.dataGrouping)&&a[c].hasProcessed)if(b=
-(a[c].processedXData||a[c].data).length,a[c].groupPixelWidth||b>this.chart.plotSizeX/d||b&&f.forced)e=!0;return e?d:0};W.ohlc=y(W.column,{lineWidth:1,tooltip:{pointFormat:'● {series.name} Open: {point.open} High: {point.high} Low: {point.low} Close: {point.close} '},states:{hover:{lineWidth:3}},threshold:null});L=ia(B.column,{type:"ohlc",pointArrayMap:["open","high","low","close"],toYData:function(a){return[a.open,a.high,a.low,
-a.close]},pointValKey:"high",pointAttrToOptions:{stroke:"color","stroke-width":"lineWidth"},upColorProp:"stroke",getAttribs:function(){B.column.prototype.getAttribs.apply(this,arguments);var a=this.options,b=a.states,a=a.upColor||this.color,c=y(this.pointAttr),d=this.upColorProp;c[""][d]=a;c.hover[d]=b.hover.upColor||a;c.select[d]=b.select.upColor||a;p(this.points,function(a){if(a.open"},
-threshold:null,y:-30});B.flags=ia(B.column,{type:"flags",sorted:!1,noSharedTooltip:!0,allowDG:!1,takeOrdinalPosition:!1,trackerGroups:["markerGroup"],forceCrop:!0,init:M.prototype.init,pointAttrToOptions:{fill:"fillColor",stroke:"color","stroke-width":"lineWidth",r:"radius"},translate:function(){B.column.prototype.translate.apply(this);var a=this.chart,b=this.points,c=b.length-1,d,e,f=this.options.onSeries,f=(d=f&&a.get(f))&&d.options.step,g=d&&d.points,h=g&&g.length,i=this.xAxis,j=i.getExtremes(),
-k,l,m;if(d&&d.visible&&h){d=d.currentDataGrouping;l=g[h-1].x+(d?d.totalRange:0);for(b.sort(function(a,b){return a.x-b.x});h--&&b[c];)if(d=b[c],k=g[h],k.x<=d.x&&k.plotY!==r){if(d.x<=l)d.plotY=k.plotY,k.x=j.min&&c.x<=j.max?c.plotY=a.chartHeight-i.bottom-(i.opposite?i.height:0)+i.offset-a.plotTop:c.shapeArgs={};if((e=b[d-1])&&e.plotX===c.plotX){if(e.stackIndex===
-r)e.stackIndex=0;c.stackIndex=e.stackIndex+1}})},drawPoints:function(){var a,b=this.pointAttr[""],c=this.points,d=this.chart.renderer,e,f,g=this.options,h=g.y,i,j,k,l,m=g.lineWidth%2/2,n,o;for(j=c.length;j--;)if(k=c[j],a=k.plotX>this.xAxis.len,e=k.plotX+(a?m:-m),l=k.stackIndex,i=k.options.shape||g.shape,f=k.plotY,f!==r&&(f=k.plotY+h+m-(l!==r&&l*g.stackDistance)),n=l?r:k.plotX+m,o=l?r:k.plotY,l=k.graphic,f!==r&&e>=0&&!a)a=k.pointAttr[k.selected?"select":""]||b,l?l.attr({x:e,y:f,r:a.r,anchorX:n,anchorY:o}):
-k.graphic=d.label(k.options.title||g.title||"A",e,f,i,n,o,g.useHTML).css(y(g.style,k.style)).attr(a).attr({align:i==="flag"?"left":"center",width:g.width,height:g.height}).add(this.markerGroup).shadow(g.shadow),k.tooltipPos=[e,f];else if(l)k.graphic=l.destroy()},drawTracker:function(){var a=this.points;jb.drawTrackerPoint.apply(this);p(a,function(b){var c=b.graphic;c&&z(c.element,"mouseover",function(){if(b.stackIndex>0&&!b.raised)b._y=c.y,c.attr({y:b._y-8}),b.raised=!0;p(a,function(a){if(a!==b&&
-a.raised&&a.graphic)a.graphic.attr({y:a._y}),a.raised=!1})})})},animate:ra});tb.flag=function(a,b,c,d,e){var f=e&&e.anchorX||a,e=e&&e.anchorY||b;return["M",f,e,"L",a,b+d,a,b,a+c,b,a+c,b+d,a,b+d,"M",f,e,"Z"]};p(["circle","square"],function(a){tb[a+"pin"]=function(b,c,d,e,f){var g=f&&f.anchorX,f=f&&f.anchorY,b=tb[a](b,c,d,e);g&&f&&b.push("M",g,c>f?c:c+e,"L",g,f);return b}});Za===N.VMLRenderer&&p(["flag","circlepin","squarepin"],function(a){ib.prototype.symbols[a]=tb[a]});var L=[].concat(Xb),ub=function(a){return Math[a].apply(0,
-qb(arguments,function(a){return typeof a==="number"}))};L[4]=["day",[1,2,3,4]];L[5]=["week",[1,2,3]];s(F,{navigator:{handles:{backgroundColor:"#ebe7e8",borderColor:"#b2b1b6"},height:40,margin:25,maskFill:"rgba(128,179,236,0.3)",maskInside:!0,outlineColor:"#b2b1b6",outlineWidth:1,series:{type:B.areaspline===r?"line":"areaspline",color:"#4572A7",compare:null,fillOpacity:0.05,dataGrouping:{approximation:"average",enabled:!0,groupPixelWidth:2,smoothed:!0,units:L},dataLabels:{enabled:!1,zIndex:2},id:"highcharts-navigator-series",
-lineColor:"#4572A7",lineWidth:1,marker:{enabled:!1},pointRange:0,shadow:!1,threshold:null},xAxis:{tickWidth:0,lineWidth:0,gridLineColor:"#EEE",gridLineWidth:1,tickPixelInterval:200,labels:{align:"left",style:{color:"#888"},x:3,y:-4},crosshair:!1},yAxis:{gridLineWidth:0,startOnTick:!1,endOnTick:!1,minPadding:0.1,maxPadding:0.1,labels:{enabled:!1},crosshair:!1,title:{text:null},tickWidth:0}},scrollbar:{height:fb?20:14,barBackgroundColor:"#bfc8d1",barBorderRadius:0,barBorderWidth:1,barBorderColor:"#bfc8d1",
-buttonArrowColor:"#666",buttonBackgroundColor:"#ebe7e8",buttonBorderColor:"#bbb",buttonBorderRadius:0,buttonBorderWidth:1,minWidth:6,rifleColor:"#666",trackBackgroundColor:"#eeeeee",trackBorderColor:"#eeeeee",trackBorderWidth:1,liveRedraw:ea&&!fb}});Cb.prototype={drawHandle:function(a,b){var c=this.chart,d=c.renderer,e=this.elementsToDestroy,f=this.handles,g=this.navigatorOptions.handles,g={fill:g.backgroundColor,stroke:g.borderColor,"stroke-width":1},h;this.rendered||(f[b]=d.g("navigator-handle-"+
-["left","right"][b]).css({cursor:"e-resize"}).attr({zIndex:4-b}).add(),h=d.rect(-4.5,0,9,16,0,1).attr(g).add(f[b]),e.push(h),h=d.path(["M",-1.5,4,"L",-1.5,12,"M",0.5,4,"L",0.5,12]).attr(g).add(f[b]),e.push(h));f[b][c.isResizing?"animate":"attr"]({translateX:this.scrollerLeft+this.scrollbarHeight+parseInt(a,10),translateY:this.top+this.height/2-8})},drawScrollbarButton:function(a){var b=this.chart.renderer,c=this.elementsToDestroy,d=this.scrollbarButtons,e=this.scrollbarHeight,f=this.scrollbarOptions,
-g;this.rendered||(d[a]=b.g().add(this.scrollbarGroup),g=b.rect(-0.5,-0.5,e+1,e+1,f.buttonBorderRadius,f.buttonBorderWidth).attr({stroke:f.buttonBorderColor,"stroke-width":f.buttonBorderWidth,fill:f.buttonBackgroundColor}).add(d[a]),c.push(g),g=b.path(["M",e/2+(a?-1:1),e/2-3,"L",e/2+(a?-1:1),e/2+3,e/2+(a?2:-2),e/2]).attr({fill:f.buttonArrowColor}).add(d[a]),c.push(g));a&&d[a].attr({translateX:this.scrollerWidth-e})},render:function(a,b,c,d){var e=this.chart,f=e.renderer,g,h,i,j,k=this.scrollbarGroup,
-l=this.navigatorGroup,m=this.scrollbar,l=this.xAxis,n=this.scrollbarTrack,o=this.scrollbarHeight,p=this.scrollbarEnabled,r=this.navigatorOptions,s=this.scrollbarOptions,w=s.minWidth,u=this.height,y=this.top,z=this.navigatorEnabled,O=r.outlineWidth,B=O/2,E=0,C=this.outlineHeight,G=s.barBorderRadius,F=s.barBorderWidth,D=y+B,H;if(!isNaN(a)){this.navigatorLeft=g=q(l.left,e.plotLeft+o);this.navigatorWidth=h=q(l.len,e.plotWidth-2*o);this.scrollerLeft=i=g-o;this.scrollerWidth=j=j=h+2*o;l.getExtremes&&(H=
-this.getUnionExtremes(!0))&&(H.dataMin!==l.min||H.dataMax!==l.max)&&l.setExtremes(H.dataMin,H.dataMax,!0,!1);c=q(c,l.translate(a));d=q(d,l.translate(b));if(isNaN(c)||P(c)===Infinity)c=0,d=j;if(!(l.translate(d,!0)-l.translate(c,!0)12?"visible":"hidden"})[e]({d:["M",w-3,o/4,"L",w-3,2*o/3,"M",w,o/4,"L",w,2*o/3,"M",w+3,o/4,"L",w+3,2*o/3]});this.scrollbarPad=E;this.rendered=!0}}},addEvents:function(){var a=this.chart.container,b=this.mouseDownHandler,c=this.mouseMoveHandler,
-d=this.mouseUpHandler,e;e=[[a,"mousedown",b],[a,"mousemove",c],[document,"mouseup",d]];db&&e.push([a,"touchstart",b],[a,"touchmove",c],[document,"touchend",d]);p(e,function(a){z.apply(null,a)});this._events=e},removeEvents:function(){p(this._events,function(a){U.apply(null,a)});this._events=r;this.navigatorEnabled&&this.baseSeries&&U(this.baseSeries,"updatedData",this.updatedDataHandler)},init:function(){var a=this,b=a.chart,c,d,e=a.scrollbarHeight,f=a.navigatorOptions,g=a.height,h=a.top,i,j,k=document.body.style,
-l,m=a.baseSeries;a.mouseDownHandler=function(d){var d=b.pointer.normalize(d),e=a.zoomedMin,f=a.zoomedMax,h=a.top,j=a.scrollbarHeight,m=a.scrollerLeft,n=a.scrollerWidth,o=a.navigatorLeft,p=a.navigatorWidth,q=a.scrollbarPad,r=a.range,s=d.chartX,t=d.chartY,d=b.xAxis[0],u,v=fb?10:7;if(t>h&&to+e-q&&sm&&sm+n-j?e+r*0.2:s=p)f=p-r,u=c.dataMax;if(f!==e)a.fixedWidth=r,e=c.toFixedRange(f,f+r,null,u),d.setExtremes(e.min,e.max,!0,!1,{trigger:"navigator"})}};a.mouseMoveHandler=function(c){var d=a.scrollbarHeight,e=a.navigatorLeft,f=a.navigatorWidth,g=a.scrollerLeft,h=a.scrollerWidth,k=a.range,l;
-if(c.pageX!==0)c=b.pointer.normalize(c),l=c.chartX,lg+h-d&&(l=g+h-d),a.grabbedLeft?(j=!0,a.render(0,0,l-e,a.otherHandlePos)):a.grabbedRight?(j=!0,a.render(0,0,a.otherHandlePos,l-e)):a.grabbedCenter&&(j=!0,lf+i-k&&(l=f+i-k),a.render(0,0,l-i,l-i+k)),j&&a.scrollbarOptions.liveRedraw&&setTimeout(function(){a.mouseUpHandler(c)},0)};a.mouseUpHandler=function(d){var e,f;if(j){if(a.zoomedMin===a.otherHandlePos)e=a.fixedExtreme;else if(a.zoomedMax===a.otherHandlePos)f=a.fixedExtreme;e=c.toFixedRange(a.zoomedMin,
-a.zoomedMax,e,f);b.xAxis[0].setExtremes(e.min,e.max,!0,!1,{trigger:"navigator",triggerOp:"navigator-drag",DOMEvent:d})}if(d.type!=="mousemove")a.grabbedLeft=a.grabbedRight=a.grabbedCenter=a.fixedWidth=a.fixedExtreme=a.otherHandlePos=j=i=null,k.cursor=l||""};var n=b.xAxis.length,o=b.yAxis.length;b.extraBottomMargin=a.outlineHeight+f.margin;a.navigatorEnabled?(a.xAxis=c=new D(b,y({ordinal:m&&m.xAxis.options.ordinal},f.xAxis,{id:"navigator-x-axis",isX:!0,type:"datetime",index:n,height:g,offset:0,offsetLeft:e,
-offsetRight:-e,keepOrdinalPadding:!0,startOnTick:!1,endOnTick:!1,minPadding:0,maxPadding:0,zoomEnabled:!1})),a.yAxis=d=new D(b,y(f.yAxis,{id:"navigator-y-axis",alignTicks:!1,height:g,offset:0,index:o,zoomEnabled:!1})),m||f.series.data?a.addBaseSeries():b.series.length===0&&Q(b,"redraw",function(c,d){if(b.series.length>0&&!a.series)a.setBaseSeries(),b.redraw=c;c.call(b,d)})):a.xAxis=c={translate:function(a,c){var d=b.xAxis[0],f=d.getExtremes(),g=b.plotWidth-2*e,h=ub("min",d.options.min,f.dataMin),
-d=ub("max",d.options.max,f.dataMax)-h;return c?a*d/g+h:g*(a-h)/d},toFixedRange:D.prototype.toFixedRange};Q(b,"getMargins",function(b){var e=this.legend,f=e.options;b.call(this);a.top=h=a.navigatorOptions.top||this.chartHeight-a.height-a.scrollbarHeight-this.spacing[2]-(f.verticalAlign==="bottom"&&f.enabled&&!f.floating?e.legendHeight+q(f.margin,10):0);if(c&&d)c.options.top=d.options.top=h,c.setAxisSize(),d.setAxisSize()});a.addEvents()},getUnionExtremes:function(a){var b=this.chart.xAxis[0],c=this.xAxis,
-d=c.options,e=b.options;if(!a||b.dataMin!==null)return{dataMin:ub("min",d&&d.min,e.min,b.dataMin,c.dataMin),dataMax:ub("max",d&&d.max,e.max,b.dataMax,c.dataMax)}},setBaseSeries:function(a){var b=this.chart,a=a||b.options.navigator.baseSeries;this.series&&this.series.remove();this.baseSeries=b.series[a]||typeof a==="string"&&b.get(a)||b.series[0];this.xAxis&&this.addBaseSeries()},addBaseSeries:function(){var a=this.baseSeries,b=a?a.options:{},c=b.data,d=this.navigatorOptions.series,e;e=d.data;this.hasNavigatorData=
-!!e;b=y(b,d,{enableMouseTracking:!1,group:"nav",padXAxis:!1,xAxis:"navigator-x-axis",yAxis:"navigator-y-axis",name:"Navigator",showInLegend:!1,isInternal:!0,visible:!0});b.data=e||c;this.series=this.chart.initSeries(b);if(a&&this.navigatorOptions.adaptToUpdatedData!==!1)z(a,"updatedData",this.updatedDataHandler),a.userOptions.events=s(a.userOptions.event,{updatedData:this.updatedDataHandler})},updatedDataHandler:function(){var a=this.chart.scroller,b=a.baseSeries,c=b.xAxis,d=c.getExtremes(),e=d.min,
-f=d.max,g=d.dataMin,d=d.dataMax,h=f-e,i,j,k,l,m,n=a.series;i=n.xData;var o=!!c.setExtremes;j=f>=i[i.length-1]-(this.closestPointRange||0);i=e<=g;if(!a.hasNavigatorData)n.options.pointStart=b.xData[0],n.setData(b.options.data,!1),m=!0;i&&(l=g,k=l+h);j&&(k=d,i||(l=v(k-h,n.xData[0])));o&&(i||j)?isNaN(l)||c.setExtremes(l,k,!0,!1,{trigger:"updatedData"}):(m&&this.chart.redraw(!1),a.render(v(e,g),A(f,d)))},destroy:function(){this.removeEvents();p([this.xAxis,this.yAxis,this.leftShade,this.rightShade,this.outline,
-this.scrollbarTrack,this.scrollbarRifles,this.scrollbarGroup,this.scrollbar],function(a){a&&a.destroy&&a.destroy()});this.xAxis=this.yAxis=this.leftShade=this.rightShade=this.outline=this.scrollbarTrack=this.scrollbarRifles=this.scrollbarGroup=this.scrollbar=null;p([this.scrollbarButtons,this.handles,this.elementsToDestroy],function(a){Na(a)})}};N.Scroller=Cb;Q(D.prototype,"zoom",function(a,b,c){var d=this.chart,e=d.options,f=e.chart.zoomType,g=e.navigator,e=e.rangeSelector,h;if(this.isXAxis&&(g&&
-g.enabled||e&&e.enabled))if(f==="x")d.resetZoomButton="blocked";else if(f==="y")h=!1;else if(f==="xy")d=this.previousZoom,u(b)?this.previousZoom=[this.min,this.max]:d&&(b=d[0],c=d[1],delete this.previousZoom);return h!==r?h:a.call(this,b,c)});Q(ya.prototype,"init",function(a,b,c){z(this,"beforeRender",function(){var a=this.options;if(a.navigator.enabled||a.scrollbar.enabled)this.scroller=new Cb(this)});a.call(this,b,c)});Q(M.prototype,"addPoint",function(a,b,c,d,e){var f=this.options.turboThreshold;
-f&&this.xData.length>f&&ha(b)&&!Ja(b)&&this.chart.scroller&&ma(20,!0);a.call(this,b,c,d,e)});s(F,{rangeSelector:{buttonTheme:{width:28,height:18,fill:"#f7f7f7",padding:2,r:0,"stroke-width":0,style:{color:"#444",cursor:"pointer",fontWeight:"normal"},zIndex:7,states:{hover:{fill:"#e7e7e7"},select:{fill:"#e7f0f9",style:{color:"black",fontWeight:"bold"}}}},inputPosition:{align:"right"},labelStyle:{color:"#666"}}});F.lang=y(F.lang,{rangeSelectorZoom:"Zoom",rangeSelectorFrom:"From",rangeSelectorTo:"To"});
-Db.prototype={clickButton:function(a,b){var c=this,d=c.selected,e=c.chart,f=c.buttons,g=c.buttonOptions[a],h=e.xAxis[0],i=e.scroller&&e.scroller.getUnionExtremes()||h||{},j=i.dataMin,k=i.dataMax,l,m=h&&t(A(h.max,q(k,h.max))),n=new ca(m),o=g.type,s=g.count,i=g._range,u;if(!(j===null||k===null||a===c.selected)){if(o==="month"||o==="year")l={month:"Month",year:"FullYear"}[o],n["set"+l](n["get"+l]()-s),l=n.getTime(),j=q(j,Number.MIN_VALUE),isNaN(l)||lg-f,o=e=g-f&&j[c].state!==2,q=a.type==="ytd"&&xa("%Y",f)===xa("%Y",g);e===t(d.max-d.min)&&c!==h?(b.setSelected(c),j[c].setState(2)):!i&&(n||o||p||q)?j[c].setState(3):j[c].state===3&&j[c].setState(0)})},computeButtonRange:function(a){var b=a.type,c=a.count||1,d={millisecond:1,second:1E3,minute:6E4,hour:36E5,day:864E5,week:6048E5};if(d[b])a._range=d[b]*c;else if(b==="month"||
-b==="year")a._range={month:30,year:365}[b]*864E5*c},setInputValue:function(a,b){var c=this.chart.options.rangeSelector;if(u(b))this[a+"Input"].HCTime=b;this[a+"Input"].value=xa(c.inputEditDateFormat||"%Y-%m-%d",this[a+"Input"].HCTime);this[a+"DateBox"].attr({text:xa(c.inputDateFormat||"%b %e, %Y",this[a+"Input"].HCTime)})},drawInput:function(a){var b=this,c=b.chart,d=c.renderer.style,e=c.renderer,f=c.options.rangeSelector,g=b.div,h=a==="min",i,j,k,l=this.inputGroup;this[a+"Label"]=j=e.label(F.lang[h?
-"rangeSelectorFrom":"rangeSelectorTo"],this.inputGroup.offset).attr({padding:2}).css(y(d,f.labelStyle)).add(l);l.offset+=j.width+5;this[a+"DateBox"]=k=e.label("",l.offset).attr({padding:2,width:f.inputBoxWidth||90,height:f.inputBoxHeight||17,stroke:f.inputBoxBorderColor||"silver","stroke-width":1}).css(y({textAlign:"center",color:"#444"},d,f.inputStyle)).on("click",function(){b[a+"Input"].focus()}).add(l);l.offset+=k.width+(h?10:0);this[a+"Input"]=i=aa("input",{name:a,className:"highcharts-range-selector",
-type:"text"},s({position:"absolute",border:0,width:"1px",height:"1px",padding:0,textAlign:"center",fontSize:d.fontSize,fontFamily:d.fontFamily,top:c.plotTop+"px"},f.inputStyle),g);i.onfocus=function(){K(this,{left:l.translateX+k.x+"px",top:l.translateY+"px",width:k.width-2+"px",height:k.height-2+"px",border:"2px solid silver"})};i.onblur=function(){K(this,{border:0,width:"1px",height:"1px"});b.setInputValue(a)};i.onchange=function(){var a=i.value,d=(f.inputDateParser||ca.parse)(a),e=c.xAxis[0],g=
-e.dataMin,j=e.dataMax;isNaN(d)&&(d=a.split("-"),d=ca.UTC(H(d[0]),H(d[1])-1,H(d[2])));isNaN(d)||(F.global.useUTC||(d+=(new ca).getTimezoneOffset()*6E4),h?d>b.maxInput.HCTime?d=r:dj&&(d=j),d!==r&&c.xAxis[0].setExtremes(h?d:e.min,h?e.max:d,r,r,{trigger:"rangeSelectorInput"}))}},render:function(a,b){var c=this,d=c.chart,e=d.renderer,f=d.container,g=d.options,h=g.exporting&&g.navigation&&g.navigation.buttonOptions,i=g.rangeSelector,j=c.buttons,g=F.lang,k=c.div,k=c.inputGroup,
-l=i.buttonTheme,m=i.inputEnabled!==!1,n=l&&l.states,o=d.plotLeft,r;if(!c.rendered&&(c.zoomText=e.text(g.rangeSelectorZoom,o,d.plotTop-20).css(i.labelStyle).add(),r=o+c.zoomText.getBBox().width+5,p(c.buttonOptions,function(a,b){j[b]=e.button(a.text,r,d.plotTop-35,function(){c.clickButton(b);c.isActive=!0},l,n&&n.hover,n&&n.select).css({textAlign:"center"}).add();r+=j[b].width+q(i.buttonSpacing,5);c.selected===b&&j[b].setState(2)}),c.updateButtonStates(),m))c.div=k=aa("div",null,{position:"relative",
-height:0,zIndex:1}),f.parentNode.insertBefore(k,f),c.inputGroup=k=e.g("input-group").add(),k.offset=0,c.drawInput("min"),c.drawInput("max");m&&(f=d.plotTop-45,k.align(s({y:f,width:k.offset,x:h&&f<(h.y||0)+h.height-d.spacing[0]?-40:0},i.inputPosition),!0,d.spacingBox),c.setInputValue("min",a),c.setInputValue("max",b));c.rendered=!0},destroy:function(){var a=this.minInput,b=this.maxInput,c=this.chart,d=this.blurInputs,e;U(c.container,"mousedown",d);U(c,"resize",d);Na(this.buttons);if(a)a.onfocus=a.onblur=
-a.onchange=null;if(b)b.onfocus=b.onblur=b.onchange=null;for(e in this)this[e]&&e!=="chart"&&(this[e].destroy?this[e].destroy():this[e].nodeType&&Va(this[e])),this[e]=null}};D.prototype.toFixedRange=function(a,b,c,d){var e=this.chart&&this.chart.fixedRange,a=q(c,this.translate(a,!0)),b=q(d,this.translate(b,!0)),c=e&&(b-a)/e;c>0.7&&c<1.3&&(d?a=b-e:b=a+e);return{min:a,max:b}};Q(ya.prototype,"init",function(a,b,c){z(this,"init",function(){if(this.options.rangeSelector.enabled)this.rangeSelector=new Db(this)});
-a.call(this,b,c)});N.RangeSelector=Db;ya.prototype.callbacks.push(function(a){function b(){f=a.xAxis[0].getExtremes();g.render(f.min,f.max)}function c(){f=a.xAxis[0].getExtremes();isNaN(f.min)||h.render(f.min,f.max)}function d(a){a.triggerOp!=="navigator-drag"&&g.render(a.min,a.max)}function e(a){h.render(a.min,a.max)}var f,g=a.scroller,h=a.rangeSelector;g&&(z(a.xAxis[0],"afterSetExtremes",d),Q(a,"drawChartBox",function(a){var c=this.isDirtyBox;a.call(this);c&&b()}),b());h&&(z(a.xAxis[0],"afterSetExtremes",
-e),z(a,"resize",c),c());z(a,"destroy",function(){g&&U(a.xAxis[0],"afterSetExtremes",d);h&&(U(a,"resize",c),U(a.xAxis[0],"afterSetExtremes",e))})});N.StockChart=function(a,b){var c=a.series,d,e=q(a.navigator&&a.navigator.enabled,!0)?{startOnTick:!1,endOnTick:!1}:null,f={marker:{enabled:!1,radius:2},states:{hover:{lineWidth:2}}},g={shadow:!1,borderWidth:0};a.xAxis=za(qa(a.xAxis||{}),function(a){return y({minPadding:0,maxPadding:0,ordinal:!0,title:{text:null},labels:{overflow:"justify"},showLastLabel:!0},
-a,{type:"datetime",categories:null},e)});a.yAxis=za(qa(a.yAxis||{}),function(a){d=q(a.opposite,!0);return y({labels:{y:-2},opposite:d,showLastLabel:!1,title:{text:null}},a)});a.series=null;a=y({chart:{panning:!0,pinchType:"x"},navigator:{enabled:!0},scrollbar:{enabled:!0},rangeSelector:{enabled:!0},title:{text:null,style:{fontSize:"16px"}},tooltip:{shared:!0,crosshairs:!0},legend:{enabled:!1},plotOptions:{line:f,spline:f,area:f,areaspline:f,arearange:f,areasplinerange:f,column:g,columnrange:g,candlestick:g,
-ohlc:g}},a,{_stock:!0,chart:{inverted:!1}});a.series=c;return new ya(a,b)};Q($a.prototype,"init",function(a,b,c){var d=c.chart.pinchType||"";a.call(this,b,c);this.pinchX=this.pinchHor=d.indexOf("x")!==-1;this.pinchY=this.pinchVert=d.indexOf("y")!==-1;this.hasZoom=this.hasZoom||this.pinchHor||this.pinchVert});Q(D.prototype,"autoLabelAlign",function(a){var b=this.chart,c=this.options,b=b._labelPanes=b._labelPanes||{},d=this.options.labels;if(this.chart.options._stock&&this.coll==="yAxis"&&(c=c.top+
-","+c.height,!b[c]&&d.enabled)){if(d.x===15)d.x=0;if(d.align===void 0)d.align="right";b[c]=1;return"right"}return a.call(this,[].slice.call(arguments,1))});D.prototype.getPlotLinePath=function(a,b,c,d,e){var f=this,g=this.isLinked&&!this.series?this.linkedParent.series:this.series,h=f.chart,i=h.renderer,j=f.left,k=f.top,l,m,n,o,r=[],s,v;if(f.coll==="xAxis"||f.coll==="yAxis")s=f.isXAxis?u(f.options.yAxis)?[h.yAxis[f.options.yAxis]]:za(g,function(a){return a.yAxis}):u(f.options.xAxis)?[h.xAxis[f.options.xAxis]]:
-za(g,function(a){return a.xAxis});p(f.isXAxis?h.yAxis:h.xAxis,function(a){if(u(a.options.id)?a.options.id.indexOf("navigator")===-1:1){var b=a.isXAxis?"yAxis":"xAxis",b=u(a.options[b])?h[b][a.options[b]]:h[b][0];f===b&&s.push(a)}});v=s.length?[]:[f];p(s,function(a){Qa(a,v)===-1&&v.push(a)});e=q(e,f.translate(a,null,null,c));isNaN(e)||(f.horiz?p(v,function(a){m=a.top;o=m+a.len;l=n=t(e+f.transB);(l>=j&&l<=j+f.width||d)&&r.push("M",l,m,"L",n,o)}):p(v,function(a){l=a.left;n=l+a.width;m=o=t(k+f.height-
-e);(m>=k&&m<=k+f.height||d)&&r.push("M",l,m,"L",n,o)}));if(r.length>0)return i.crispPolyLine(r,b||1)};D.prototype.getPlotBandPath=function(a,b){var c=this.getPlotLinePath(b),d=this.getPlotLinePath(a),e=[],f;if(d&&c)for(f=0;fj+this.height)this.hideCrosshair();else{!n&&!f.formatter&&(this.isDatetimeAxis&&(o="%b %d, %Y"),n="{value"+(o?":"+o:"")+"}");k.attr({text:n?Ma(n,{value:c[g]}):f.formatter.call(this,c[g]),x:l,y:m,visibility:"visible"});c=k.getBBox();if(b){if(this.options.tickPosition==="inside"&&!h||this.options.tickPosition!=="inside"&&h)m=k.y-c.height}else m=k.y-c.height/2;b?(d=i-c.x,e=i+this.width-c.x):(d=this.labelAlign==="left"?i:
-0,e=this.labelAlign==="right"?i+this.width:a.chartWidth);k.translateX=e&&(l-=k.translateX+c.width-e);k.attr({x:l,y:m,visibility:"visible"})}}});var mc=da.init,nc=da.processData,oc=Ba.prototype.tooltipFormatter;da.init=function(){mc.apply(this,arguments);this.setCompare(this.options.compare)};da.setCompare=function(a){this.modifyValue=a==="value"||a==="percent"?function(b,c){var d=this.compareValue;if(b!==r&&(b=a==="value"?b-d:b=100*(b/d)-100,c))c.change=
-b;return b}:null;if(this.chart.hasRendered)this.isDirty=!0};da.processData=function(){var a=0,b,c,d;nc.apply(this,arguments);if(this.xAxis&&this.processedYData){b=this.processedXData;c=this.processedYData;for(d=c.length;a=this.xAxis.min){this.compareValue=c[a];break}}};Q(da,"getExtremes",function(a){a.apply(this,[].slice.call(arguments,1));if(this.modifyValue)this.dataMax=this.modifyValue(this.dataMax),this.dataMin=this.modifyValue(this.dataMin)});D.prototype.setCompare=
-function(a,b){this.isXAxis||(p(this.series,function(b){b.setCompare(a)}),q(b,!0)&&this.chart.redraw())};Ba.prototype.tooltipFormatter=function(a){a=a.replace("{point.change}",(this.change>0?"+":"")+Da(this.change,q(this.series.tooltipOptions.changeDecimals,2)));return oc.apply(this,[a])};Q(M.prototype,"render",function(a){if(this.chart.options._stock)!this.clipBox&&this.animate&&this.animate.toString().indexOf("sharedClip")!==-1?(this.clipBox=y(this.chart.clipBox),this.clipBox.width=this.xAxis.len,
-this.clipBox.height=this.yAxis.len):this.chart[this.sharedClipKey]&&this.chart[this.sharedClipKey].attr({width:this.xAxis.len,height:this.yAxis.len});a.call(this)});s(N,{Axis:D,Chart:ya,Color:Ia,Point:Ba,Tick:bb,Renderer:Za,Series:M,SVGElement:Z,SVGRenderer:na,arrayMin:Ua,arrayMax:Ea,charts:ba,dateFormat:xa,format:Ma,pathAnim:Gb,getOptions:function(){return F},hasBidiBug:Yb,isTouchDevice:fb,numberFormat:Da,seriesTypes:B,setOptions:function(a){F=y(!0,F,a);Mb();return F},addEvent:z,removeEvent:U,createElement:aa,
-discardElement:Va,css:K,each:p,extend:s,map:za,merge:y,pick:q,splat:qa,extendClass:ia,pInt:H,wrap:Q,svg:ea,canvas:la,vml:!ea&&!la,product:"Highstock",version:"2.0.4"})})();
+ // Apply range
+ if (type === 'month' || type === 'year') {
+ if (!baseAxis) {
+ // This is set to the user options and picked up later when the axis is instantiated
+ // so that we know the min and max.
+ range = rangeOptions;
+ } else {
+ ctx = {
+ range: rangeOptions,
+ max: newMax,
+ dataMin: dataMin,
+ dataMax: dataMax
+ };
+ newMin = baseAxis.minFromRange.call(ctx);
+ if (isNumber(ctx.newMax)) {
+ newMax = ctx.newMax;
+ }
+ }
+
+ // Fixed times like minutes, hours, days
+ } else if (range) {
+ newMin = Math.max(newMax - range, dataMin);
+ newMax = Math.min(newMin + range, dataMax);
+
+ } else if (type === 'ytd') {
+
+ // On user clicks on the buttons, or a delayed action running from the beforeRender
+ // event (below), the baseAxis is defined.
+ if (baseAxis) {
+ // When "ytd" is the pre-selected button for the initial view, its calculation
+ // is delayed and rerun in the beforeRender event (below). When the series
+ // are initialized, but before the chart is rendered, we have access to the xData
+ // array (#942).
+ if (dataMax === undefined) {
+ dataMin = Number.MAX_VALUE;
+ dataMax = Number.MIN_VALUE;
+ each(chart.series, function (series) {
+ var xData = series.xData; // reassign it to the last item
+ dataMin = Math.min(xData[0], dataMin);
+ dataMax = Math.max(xData[xData.length - 1], dataMax);
+ });
+ redraw = false;
+ }
+ ytdExtremes = rangeSelector.getYTDExtremes(
+ dataMax,
+ dataMin,
+ chart.time.useUTC
+ );
+ newMin = rangeMin = ytdExtremes.min;
+ newMax = ytdExtremes.max;
+
+ // "ytd" is pre-selected. We don't yet have access to processed point and extremes data
+ // (things like pointStart and pointInterval are missing), so we delay the process (#942)
+ } else {
+ addEvent(chart, 'beforeRender', function () {
+ rangeSelector.clickButton(i);
+ });
+ return;
+ }
+ } else if (type === 'all' && baseAxis) {
+ newMin = dataMin;
+ newMax = dataMax;
+ }
+
+ newMin += rangeOptions._offsetMin;
+ newMax += rangeOptions._offsetMax;
+
+ rangeSelector.setSelected(i);
+
+ // Update the chart
+ if (!baseAxis) {
+ // Axis not yet instanciated. Temporarily set min and range
+ // options and remove them on chart load (#4317).
+ baseXAxisOptions = splat(chart.options.xAxis)[0];
+ rangeSetting = baseXAxisOptions.range;
+ baseXAxisOptions.range = range;
+ minSetting = baseXAxisOptions.min;
+ baseXAxisOptions.min = rangeMin;
+ addEvent(chart, 'load', function resetMinAndRange() {
+ baseXAxisOptions.range = rangeSetting;
+ baseXAxisOptions.min = minSetting;
+ });
+ } else {
+ // Existing axis object. Set extremes after render time.
+ baseAxis.setExtremes(
+ newMin,
+ newMax,
+ pick(redraw, 1),
+ null, // auto animation
+ {
+ trigger: 'rangeSelectorButton',
+ rangeSelectorButton: rangeOptions
+ }
+ );
+ }
+ },
+
+ /**
+ * Set the selected option. This method only sets the internal flag, it
+ * doesn't update the buttons or the actual zoomed range.
+ */
+ setSelected: function (selected) {
+ this.selected = this.options.selected = selected;
+ },
+
+ /**
+ * The default buttons for pre-selecting time frames
+ */
+ defaultButtons: [{
+ type: 'month',
+ count: 1,
+ text: '1m'
+ }, {
+ type: 'month',
+ count: 3,
+ text: '3m'
+ }, {
+ type: 'month',
+ count: 6,
+ text: '6m'
+ }, {
+ type: 'ytd',
+ text: 'YTD'
+ }, {
+ type: 'year',
+ count: 1,
+ text: '1y'
+ }, {
+ type: 'all',
+ text: 'All'
+ }],
+
+ /**
+ * Initialize the range selector
+ */
+ init: function (chart) {
+ var rangeSelector = this,
+ options = chart.options.rangeSelector,
+ buttonOptions = options.buttons ||
+ [].concat(rangeSelector.defaultButtons),
+ selectedOption = options.selected,
+ blurInputs = function () {
+ var minInput = rangeSelector.minInput,
+ maxInput = rangeSelector.maxInput;
+
+ // #3274 in some case blur is not defined
+ if (minInput && minInput.blur) {
+ fireEvent(minInput, 'blur');
+ }
+ if (maxInput && maxInput.blur) {
+ fireEvent(maxInput, 'blur');
+ }
+ };
+
+ rangeSelector.chart = chart;
+ rangeSelector.options = options;
+ rangeSelector.buttons = [];
+
+ chart.extraTopMargin = options.height;
+ rangeSelector.buttonOptions = buttonOptions;
+
+ this.unMouseDown = addEvent(chart.container, 'mousedown', blurInputs);
+ this.unResize = addEvent(chart, 'resize', blurInputs);
+
+ // Extend the buttonOptions with actual range
+ each(buttonOptions, rangeSelector.computeButtonRange);
+
+ // zoomed range based on a pre-selected button index
+ if (selectedOption !== undefined && buttonOptions[selectedOption]) {
+ this.clickButton(selectedOption, false);
+ }
+
+
+ addEvent(chart, 'load', function () {
+ // If a data grouping is applied to the current button, release it
+ // when extremes change
+ if (chart.xAxis && chart.xAxis[0]) {
+ addEvent(chart.xAxis[0], 'setExtremes', function (e) {
+ if (
+ this.max - this.min !== chart.fixedRange &&
+ e.trigger !== 'rangeSelectorButton' &&
+ e.trigger !== 'updatedData' &&
+ rangeSelector.forcedDataGrouping
+ ) {
+ this.setDataGrouping(false, false);
+ }
+ });
+ }
+ });
+ },
+
+ /**
+ * Dynamically update the range selector buttons after a new range has been
+ * set
+ */
+ updateButtonStates: function () {
+ var rangeSelector = this,
+ chart = this.chart,
+ baseAxis = chart.xAxis[0],
+ actualRange = Math.round(baseAxis.max - baseAxis.min),
+ hasNoData = !baseAxis.hasVisibleSeries,
+ day = 24 * 36e5, // A single day in milliseconds
+ unionExtremes = (
+ chart.scroller &&
+ chart.scroller.getUnionExtremes()
+ ) || baseAxis,
+ dataMin = unionExtremes.dataMin,
+ dataMax = unionExtremes.dataMax,
+ ytdExtremes = rangeSelector.getYTDExtremes(
+ dataMax,
+ dataMin,
+ chart.time.useUTC
+ ),
+ ytdMin = ytdExtremes.min,
+ ytdMax = ytdExtremes.max,
+ selected = rangeSelector.selected,
+ selectedExists = isNumber(selected),
+ allButtonsEnabled = rangeSelector.options.allButtonsEnabled,
+ buttons = rangeSelector.buttons;
+
+ each(rangeSelector.buttonOptions, function (rangeOptions, i) {
+ var range = rangeOptions._range,
+ type = rangeOptions.type,
+ count = rangeOptions.count || 1,
+ button = buttons[i],
+ state = 0,
+ disable,
+ select,
+ offsetRange = rangeOptions._offsetMax - rangeOptions._offsetMin,
+ isSelected = i === selected,
+ // Disable buttons where the range exceeds what is allowed in
+ // the current view
+ isTooGreatRange = range > dataMax - dataMin,
+ // Disable buttons where the range is smaller than the minimum
+ // range
+ isTooSmallRange = range < baseAxis.minRange,
+ // Do not select the YTD button if not explicitly told so
+ isYTDButNotSelected = false,
+ // Disable the All button if we're already showing all
+ isAllButAlreadyShowingAll = false,
+ isSameRange = range === actualRange;
+ // Months and years have a variable range so we check the extremes
+ if (
+ (type === 'month' || type === 'year') &&
+ (
+ actualRange + 36e5 >=
+ { month: 28, year: 365 }[type] * day * count - offsetRange
+ ) &&
+ (
+ actualRange - 36e5 <=
+ { month: 31, year: 366 }[type] * day * count + offsetRange
+ )
+ ) {
+ isSameRange = true;
+ } else if (type === 'ytd') {
+ isSameRange = (ytdMax - ytdMin + offsetRange) === actualRange;
+ isYTDButNotSelected = !isSelected;
+ } else if (type === 'all') {
+ isSameRange = baseAxis.max - baseAxis.min >= dataMax - dataMin;
+ isAllButAlreadyShowingAll = (
+ !isSelected &&
+ selectedExists &&
+ isSameRange
+ );
+ }
+
+ // The new zoom area happens to match the range for a button - mark
+ // it selected. This happens when scrolling across an ordinal gap.
+ // It can be seen in the intraday demos when selecting 1h and scroll
+ // across the night gap.
+ disable = (
+ !allButtonsEnabled &&
+ (
+ isTooGreatRange ||
+ isTooSmallRange ||
+ isAllButAlreadyShowingAll ||
+ hasNoData
+ )
+ );
+ select = (
+ (isSelected && isSameRange) ||
+ (isSameRange && !selectedExists && !isYTDButNotSelected)
+ );
+
+ if (disable) {
+ state = 3;
+ } else if (select) {
+ selectedExists = true; // Only one button can be selected
+ state = 2;
+ }
+
+ // If state has changed, update the button
+ if (button.state !== state) {
+ button.setState(state);
+ }
+ });
+ },
+
+ /**
+ * Compute and cache the range for an individual button
+ */
+ computeButtonRange: function (rangeOptions) {
+ var type = rangeOptions.type,
+ count = rangeOptions.count || 1,
+
+ // these time intervals have a fixed number of milliseconds, as
+ // opposed to month, ytd and year
+ fixedTimes = {
+ millisecond: 1,
+ second: 1000,
+ minute: 60 * 1000,
+ hour: 3600 * 1000,
+ day: 24 * 3600 * 1000,
+ week: 7 * 24 * 3600 * 1000
+ };
+
+ // Store the range on the button object
+ if (fixedTimes[type]) {
+ rangeOptions._range = fixedTimes[type] * count;
+ } else if (type === 'month' || type === 'year') {
+ rangeOptions._range =
+ { month: 30, year: 365 }[type] * 24 * 36e5 * count;
+ }
+
+ rangeOptions._offsetMin = pick(rangeOptions.offsetMin, 0);
+ rangeOptions._offsetMax = pick(rangeOptions.offsetMax, 0);
+ rangeOptions._range +=
+ rangeOptions._offsetMax - rangeOptions._offsetMin;
+ },
+
+ /**
+ * Set the internal and displayed value of a HTML input for the dates
+ * @param {String} name
+ * @param {Number} inputTime
+ */
+ setInputValue: function (name, inputTime) {
+ var options = this.chart.options.rangeSelector,
+ time = this.chart.time,
+ input = this[name + 'Input'];
+
+ if (defined(inputTime)) {
+ input.previousValue = input.HCTime;
+ input.HCTime = inputTime;
+ }
+
+ input.value = time.dateFormat(
+ options.inputEditDateFormat || '%Y-%m-%d',
+ input.HCTime
+ );
+ this[name + 'DateBox'].attr({
+ text: time.dateFormat(
+ options.inputDateFormat || '%b %e, %Y',
+ input.HCTime
+ )
+ });
+ },
+
+ showInput: function (name) {
+ var inputGroup = this.inputGroup,
+ dateBox = this[name + 'DateBox'];
+
+ css(this[name + 'Input'], {
+ left: (inputGroup.translateX + dateBox.x) + 'px',
+ top: inputGroup.translateY + 'px',
+ width: (dateBox.width - 2) + 'px',
+ height: (dateBox.height - 2) + 'px',
+ border: '2px solid silver'
+ });
+ },
+
+ hideInput: function (name) {
+ css(this[name + 'Input'], {
+ border: 0,
+ width: '1px',
+ height: '1px'
+ });
+ this.setInputValue(name);
+ },
+
+ /**
+ * Draw either the 'from' or the 'to' HTML input box of the range selector
+ * @param {Object} name
+ */
+ drawInput: function (name) {
+ var rangeSelector = this,
+ chart = rangeSelector.chart,
+ chartStyle = chart.renderer.style || {},
+ renderer = chart.renderer,
+ options = chart.options.rangeSelector,
+ lang = defaultOptions.lang,
+ div = rangeSelector.div,
+ isMin = name === 'min',
+ input,
+ label,
+ dateBox,
+ inputGroup = this.inputGroup;
+
+ function updateExtremes() {
+ var inputValue = input.value,
+ value = (options.inputDateParser || Date.parse)(inputValue),
+ chartAxis = chart.xAxis[0],
+ dataAxis = chart.scroller && chart.scroller.xAxis ? chart.scroller.xAxis : chartAxis,
+ dataMin = dataAxis.dataMin,
+ dataMax = dataAxis.dataMax;
+ if (value !== input.previousValue) {
+ input.previousValue = value;
+ // If the value isn't parsed directly to a value by the browser's Date.parse method,
+ // like YYYY-MM-DD in IE, try parsing it a different way
+ if (!isNumber(value)) {
+ value = inputValue.split('-');
+ value = Date.UTC(pInt(value[0]), pInt(value[1]) - 1, pInt(value[2]));
+ }
+
+ if (isNumber(value)) {
+
+ // Correct for timezone offset (#433)
+ if (!chart.time.useUTC) {
+ value = value + new Date().getTimezoneOffset() * 60 * 1000;
+ }
+
+ // Validate the extremes. If it goes beyound the data min or max, use the
+ // actual data extreme (#2438).
+ if (isMin) {
+ if (value > rangeSelector.maxInput.HCTime) {
+ value = undefined;
+ } else if (value < dataMin) {
+ value = dataMin;
+ }
+ } else {
+ if (value < rangeSelector.minInput.HCTime) {
+ value = undefined;
+ } else if (value > dataMax) {
+ value = dataMax;
+ }
+ }
+
+ // Set the extremes
+ if (value !== undefined) {
+ chartAxis.setExtremes(
+ isMin ? value : chartAxis.min,
+ isMin ? chartAxis.max : value,
+ undefined,
+ undefined,
+ { trigger: 'rangeSelectorInput' }
+ );
+ }
+ }
+ }
+ }
+
+ // Create the text label
+ this[name + 'Label'] = label = renderer.label(lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo'], this.inputGroup.offset)
+ .addClass('highcharts-range-label')
+ .attr({
+ padding: 2
+ })
+ .add(inputGroup);
+ inputGroup.offset += label.width + 5;
+
+ // Create an SVG label that shows updated date ranges and and records click events that
+ // bring in the HTML input.
+ this[name + 'DateBox'] = dateBox = renderer.label('', inputGroup.offset)
+ .addClass('highcharts-range-input')
+ .attr({
+ padding: 2,
+ width: options.inputBoxWidth || 90,
+ height: options.inputBoxHeight || 17,
+ stroke: options.inputBoxBorderColor || '#cccccc',
+ 'stroke-width': 1,
+ 'text-align': 'center'
+ })
+ .on('click', function () {
+ rangeSelector.showInput(name); // If it is already focused, the onfocus event doesn't fire (#3713)
+ rangeSelector[name + 'Input'].focus();
+ })
+ .add(inputGroup);
+ inputGroup.offset += dateBox.width + (isMin ? 10 : 0);
+
+
+ // Create the HTML input element. This is rendered as 1x1 pixel then set to the right size
+ // when focused.
+ this[name + 'Input'] = input = createElement('input', {
+ name: name,
+ className: 'highcharts-range-selector',
+ type: 'text'
+ }, {
+ top: chart.plotTop + 'px' // prevent jump on focus in Firefox
+ }, div);
+
+
+ // Styles
+ label.css(merge(chartStyle, options.labelStyle));
+
+ dateBox.css(merge({
+ color: '#333333'
+ }, chartStyle, options.inputStyle));
+
+ css(input, extend({
+ position: 'absolute',
+ border: 0,
+ width: '1px', // Chrome needs a pixel to see it
+ height: '1px',
+ padding: 0,
+ textAlign: 'center',
+ fontSize: chartStyle.fontSize,
+ fontFamily: chartStyle.fontFamily,
+ top: '-9999em' // #4798
+ }, options.inputStyle));
+
+
+ // Blow up the input box
+ input.onfocus = function () {
+ rangeSelector.showInput(name);
+ };
+ // Hide away the input box
+ input.onblur = function () {
+ rangeSelector.hideInput(name);
+ };
+
+ // handle changes in the input boxes
+ input.onchange = updateExtremes;
+
+ input.onkeypress = function (event) {
+ // IE does not fire onchange on enter
+ if (event.keyCode === 13) {
+ updateExtremes();
+ }
+ };
+ },
+
+ /**
+ * Get the position of the range selector buttons and inputs. This can be overridden from outside for custom positioning.
+ */
+ getPosition: function () {
+ var chart = this.chart,
+ options = chart.options.rangeSelector,
+ top = (options.verticalAlign) === 'top' ? chart.plotTop - chart.axisOffset[0] : 0; // set offset only for varticalAlign top
+
+ return {
+ buttonTop: top + options.buttonPosition.y,
+ inputTop: top + options.inputPosition.y - 10
+ };
+ },
+ /**
+ * Get the extremes of YTD.
+ * Will choose dataMax if its value is lower than the current timestamp.
+ * Will choose dataMin if its value is higher than the timestamp for
+ * the start of current year.
+ * @param {number} dataMax
+ * @param {number} dataMin
+ * @return {object} Returns min and max for the YTD
+ */
+ getYTDExtremes: function (dataMax, dataMin, useUTC) {
+ var time = this.chart.time,
+ min,
+ now = new time.Date(dataMax),
+ year = now[time.getFullYear](),
+ startOfYear = useUTC ? time.Date.UTC(year, 0, 1) : +new time.Date(year, 0, 1); // eslint-disable-line new-cap
+ min = Math.max(dataMin || 0, startOfYear);
+ now = now.getTime();
+ return {
+ max: Math.min(dataMax || now, now),
+ min: min
+ };
+ },
+
+ /**
+ * Render the range selector including the buttons and the inputs. The first time render
+ * is called, the elements are created and positioned. On subsequent calls, they are
+ * moved and updated.
+ * @param {Number} min X axis minimum
+ * @param {Number} max X axis maximum
+ */
+ render: function (min, max) {
+
+ var rangeSelector = this,
+ chart = rangeSelector.chart,
+ renderer = chart.renderer,
+ container = chart.container,
+ chartOptions = chart.options,
+ navButtonOptions = chartOptions.exporting && chartOptions.exporting.enabled !== false &&
+ chartOptions.navigation && chartOptions.navigation.buttonOptions,
+ lang = defaultOptions.lang,
+ div = rangeSelector.div,
+ options = chartOptions.rangeSelector,
+ floating = options.floating,
+ buttons = rangeSelector.buttons,
+ inputGroup = rangeSelector.inputGroup,
+ buttonTheme = options.buttonTheme,
+ buttonPosition = options.buttonPosition,
+ inputPosition = options.inputPosition,
+ inputEnabled = options.inputEnabled,
+ states = buttonTheme && buttonTheme.states,
+ plotLeft = chart.plotLeft,
+ buttonLeft,
+ buttonGroup = rangeSelector.buttonGroup,
+ group,
+ groupHeight,
+ rendered = rangeSelector.rendered,
+ verticalAlign = rangeSelector.options.verticalAlign,
+ legend = chart.legend,
+ legendOptions = legend && legend.options,
+ buttonPositionY = buttonPosition.y,
+ inputPositionY = inputPosition.y,
+ animate = rendered || false,
+ exportingX = 0,
+ alignTranslateY,
+ legendHeight,
+ minPosition,
+ translateY = 0,
+ translateX;
+
+ if (options.enabled === false) {
+ return;
+ }
+
+ // create the elements
+ if (!rendered) {
+
+ rangeSelector.group = group = renderer.g('range-selector-group')
+ .attr({
+ zIndex: 7
+ })
+ .add();
+
+ rangeSelector.buttonGroup = buttonGroup = renderer.g('range-selector-buttons').add(group);
+
+ rangeSelector.zoomText = renderer.text(lang.rangeSelectorZoom, pick(plotLeft + buttonPosition.x, plotLeft), 15)
+ .css(options.labelStyle)
+ .add(buttonGroup);
+
+ // button start position
+ buttonLeft = pick(plotLeft + buttonPosition.x, plotLeft) + rangeSelector.zoomText.getBBox().width + 5;
+
+ each(rangeSelector.buttonOptions, function (rangeOptions, i) {
+
+ buttons[i] = renderer.button(
+ rangeOptions.text,
+ buttonLeft,
+ 0,
+ function () {
+
+ // extract events from button object and call
+ var buttonEvents = rangeOptions.events && rangeOptions.events.click,
+ callDefaultEvent;
+
+ if (buttonEvents) {
+ callDefaultEvent = buttonEvents.call(rangeOptions);
+ }
+
+ if (callDefaultEvent !== false) {
+ rangeSelector.clickButton(i);
+ }
+
+ rangeSelector.isActive = true;
+ },
+ buttonTheme,
+ states && states.hover,
+ states && states.select,
+ states && states.disabled
+ )
+ .attr({
+ 'text-align': 'center'
+ })
+ .add(buttonGroup);
+
+ // increase button position for the next button
+ buttonLeft += buttons[i].width + pick(options.buttonSpacing, 5);
+ });
+
+ // first create a wrapper outside the container in order to make
+ // the inputs work and make export correct
+ if (inputEnabled !== false) {
+ rangeSelector.div = div = createElement('div', null, {
+ position: 'relative',
+ height: 0,
+ zIndex: 1 // above container
+ });
+
+ container.parentNode.insertBefore(div, container);
+
+ // Create the group to keep the inputs
+ rangeSelector.inputGroup = inputGroup = renderer.g('input-group')
+ .add(group);
+ inputGroup.offset = 0;
+
+ rangeSelector.drawInput('min');
+ rangeSelector.drawInput('max');
+ }
+ }
+
+ plotLeft = chart.plotLeft - chart.spacing[3];
+ rangeSelector.updateButtonStates();
+
+ // detect collisiton with exporting
+ if
+ (
+ navButtonOptions &&
+ this.titleCollision(chart) &&
+ verticalAlign === 'top' &&
+ buttonPosition.align === 'right' &&
+ (
+ (buttonPosition.y + buttonGroup.getBBox().height - 12) <
+ ((navButtonOptions.y || 0) + navButtonOptions.height)
+ )
+ ) {
+ exportingX = -40;
+ }
+
+ if (buttonPosition.align === 'left') {
+ translateX = buttonPosition.x - chart.spacing[3];
+ } else if (buttonPosition.align === 'right') {
+ translateX = buttonPosition.x + exportingX - chart.spacing[1];
+ }
+
+ // align button group
+ buttonGroup.align({
+ y: buttonPosition.y,
+ width: buttonGroup.getBBox().width,
+ align: buttonPosition.align,
+ x: translateX
+ }, true, chart.spacingBox);
+
+ // skip animation
+ rangeSelector.group.placed = animate;
+ rangeSelector.buttonGroup.placed = animate;
+
+ if (inputEnabled !== false) {
+
+ var inputGroupX,
+ inputGroupWidth,
+ buttonGroupX,
+ buttonGroupWidth;
+
+ // detect collision with exporting
+ if
+ (
+ navButtonOptions &&
+ this.titleCollision(chart) &&
+ verticalAlign === 'top' &&
+ inputPosition.align === 'right' &&
+ (
+ (inputPosition.y - inputGroup.getBBox().height - 12) <
+ ((navButtonOptions.y || 0) + navButtonOptions.height + chart.spacing[0])
+ )
+ ) {
+ exportingX = -40;
+ } else {
+ exportingX = 0;
+ }
+
+ if (inputPosition.align === 'left') {
+ translateX = plotLeft;
+ } else if (inputPosition.align === 'right') {
+ translateX = -Math.max(chart.axisOffset[1], -exportingX); // yAxis offset
+ }
+
+ // Update the alignment to the updated spacing box
+ inputGroup.align({
+ y: inputPosition.y,
+ width: inputGroup.getBBox().width,
+ align: inputPosition.align,
+ x: inputPosition.x + translateX - 2 // fix wrong getBBox() value on right align
+ }, true, chart.spacingBox);
+
+ // detect collision
+ inputGroupX = inputGroup.alignAttr.translateX + inputGroup.alignOptions.x -
+ exportingX + inputGroup.getBBox().x + 2; // getBBox for detecing left margin, 2px padding to not overlap input and label
+
+ inputGroupWidth = inputGroup.alignOptions.width;
+
+ buttonGroupX = buttonGroup.alignAttr.translateX + buttonGroup.getBBox().x;
+ buttonGroupWidth = buttonGroup.getBBox().width + 20; // 20 is minimal spacing between elements
+
+ if (
+ (inputPosition.align === buttonPosition.align) ||
+ (
+ (buttonGroupX + buttonGroupWidth > inputGroupX) &&
+ (inputGroupX + inputGroupWidth > buttonGroupX) &&
+ (buttonPositionY < (inputPositionY + inputGroup.getBBox().height))
+ )
+ ) {
+
+ inputGroup.attr({
+ translateX: inputGroup.alignAttr.translateX + (chart.axisOffset[1] >= -exportingX ? 0 : -exportingX),
+ translateY: inputGroup.alignAttr.translateY + buttonGroup.getBBox().height + 10
+ });
+
+ }
+
+ // Set or reset the input values
+ rangeSelector.setInputValue('min', min);
+ rangeSelector.setInputValue('max', max);
+
+ // skip animation
+ rangeSelector.inputGroup.placed = animate;
+ }
+
+ // vertical align
+ rangeSelector.group.align({
+ verticalAlign: verticalAlign
+ }, true, chart.spacingBox);
+
+ // set position
+ groupHeight = rangeSelector.group.getBBox().height + 20; // # 20 padding
+ alignTranslateY = rangeSelector.group.alignAttr.translateY;
+
+ // calculate bottom position
+ if (verticalAlign === 'bottom') {
+ legendHeight = legendOptions && legendOptions.verticalAlign === 'bottom' && legendOptions.enabled &&
+ !legendOptions.floating ? legend.legendHeight + pick(legendOptions.margin, 10) : 0;
+
+ groupHeight = groupHeight + legendHeight - 20;
+ translateY = alignTranslateY - groupHeight - (floating ? 0 : options.y) - 10; // 10 spacing
+
+ }
+
+ if (verticalAlign === 'top') {
+ if (floating) {
+ translateY = 0;
+ }
+
+ if (chart.titleOffset) {
+ translateY = chart.titleOffset + chart.options.title.margin;
+ }
+
+ translateY += ((chart.margin[0] - chart.spacing[0]) || 0);
+
+ } else if (verticalAlign === 'middle') {
+ if (inputPositionY === buttonPositionY) {
+ if (inputPositionY < 0) {
+ translateY = alignTranslateY + minPosition;
+ } else {
+ translateY = alignTranslateY;
+ }
+ } else if (inputPositionY || buttonPositionY) {
+ if (inputPositionY < 0 || buttonPositionY < 0) {
+ translateY -= Math.min(inputPositionY, buttonPositionY);
+ } else {
+ translateY = alignTranslateY - groupHeight + minPosition;
+ }
+ }
+ }
+
+ rangeSelector.group.translate(
+ options.x,
+ options.y + Math.floor(translateY)
+ );
+
+ // translate HTML inputs
+ if (inputEnabled !== false) {
+ rangeSelector.minInput.style.marginTop = rangeSelector.group.translateY + 'px';
+ rangeSelector.maxInput.style.marginTop = rangeSelector.group.translateY + 'px';
+ }
+
+ rangeSelector.rendered = true;
+ },
+
+ /**
+ * Extracts height of range selector
+ * @return {Number} Returns rangeSelector height
+ */
+ getHeight: function () {
+ var rangeSelector = this,
+ options = rangeSelector.options,
+ rangeSelectorGroup = rangeSelector.group,
+ inputPosition = options.inputPosition,
+ buttonPosition = options.buttonPosition,
+ yPosition = options.y,
+ buttonPositionY = buttonPosition.y,
+ inputPositionY = inputPosition.y,
+ rangeSelectorHeight = 0,
+ minPosition;
+
+ rangeSelectorHeight = rangeSelectorGroup ? (rangeSelectorGroup.getBBox(true).height) + 13 + yPosition : 0; // 13px to keep back compatibility
+
+ minPosition = Math.min(inputPositionY, buttonPositionY);
+
+ if (
+ (inputPositionY < 0 && buttonPositionY < 0) ||
+ (inputPositionY > 0 && buttonPositionY > 0)
+ ) {
+ rangeSelectorHeight += Math.abs(minPosition);
+ }
+
+ return rangeSelectorHeight;
+ },
+
+ /**
+ * Detect collision with title or subtitle
+ * @param {object} chart
+ * @return {Boolean} Returns collision status
+ */
+ titleCollision: function (chart) {
+ return !(chart.options.title.text || chart.options.subtitle.text);
+ },
+
+ /**
+ * Update the range selector with new options
+ * @param {object} options
+ */
+ update: function (options) {
+ var chart = this.chart;
+
+ merge(true, chart.options.rangeSelector, options);
+ this.destroy();
+ this.init(chart);
+ chart.rangeSelector.render();
+ },
+
+ /**
+ * Destroys allocated elements.
+ */
+ destroy: function () {
+ var rSelector = this,
+ minInput = rSelector.minInput,
+ maxInput = rSelector.maxInput;
+
+ rSelector.unMouseDown();
+ rSelector.unResize();
+
+ // Destroy elements in collections
+ destroyObjectProperties(rSelector.buttons);
+
+ // Clear input element events
+ if (minInput) {
+ minInput.onfocus = minInput.onblur = minInput.onchange = null;
+ }
+ if (maxInput) {
+ maxInput.onfocus = maxInput.onblur = maxInput.onchange = null;
+ }
+
+ // Destroy HTML and SVG elements
+ H.objectEach(rSelector, function (val, key) {
+ if (val && key !== 'chart') {
+ if (val.destroy) { // SVGElement
+ val.destroy();
+ } else if (val.nodeType) { // HTML element
+ discardElement(this[key]);
+ }
+ }
+ if (val !== RangeSelector.prototype[key]) {
+ rSelector[key] = null;
+ }
+ }, this);
+ }
+};
+
+/**
+ * Add logic to normalize the zoomed range in order to preserve the pressed state of range selector buttons
+ */
+Axis.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) {
+ var fixedRange = this.chart && this.chart.fixedRange,
+ newMin = pick(fixedMin, this.translate(pxMin, true, !this.horiz)),
+ newMax = pick(fixedMax, this.translate(pxMax, true, !this.horiz)),
+ changeRatio = fixedRange && (newMax - newMin) / fixedRange;
+
+ // If the difference between the fixed range and the actual requested range is
+ // too great, the user is dragging across an ordinal gap, and we need to release
+ // the range selector button.
+ if (changeRatio > 0.7 && changeRatio < 1.3) {
+ if (fixedMax) {
+ newMin = newMax - fixedRange;
+ } else {
+ newMax = newMin + fixedRange;
+ }
+ }
+ if (!isNumber(newMin) || !isNumber(newMax)) { // #1195, #7411
+ newMin = newMax = undefined;
+ }
+
+ return {
+ min: newMin,
+ max: newMax
+ };
+};
+
+/**
+ * Get the axis min value based on the range option and the current max. For
+ * stock charts this is extended via the {@link RangeSelector} so that if the
+ * selected range is a multiple of months or years, it is compensated for
+ * various month lengths.
+ *
+ * @return {number} The new minimum value.
+ */
+Axis.prototype.minFromRange = function () {
+ var rangeOptions = this.range,
+ type = rangeOptions.type,
+ timeName = { month: 'Month', year: 'FullYear' }[type],
+ min,
+ max = this.max,
+ dataMin,
+ range,
+ // Get the true range from a start date
+ getTrueRange = function (base, count) {
+ var date = new Date(base),
+ basePeriod = date['get' + timeName]();
+
+ date['set' + timeName](basePeriod + count);
+
+ if (basePeriod === date['get' + timeName]()) {
+ date.setDate(0); // #6537
+ }
+
+ return date.getTime() - base;
+ };
+
+ if (isNumber(rangeOptions)) {
+ min = max - rangeOptions;
+ range = rangeOptions;
+ } else {
+ min = max + getTrueRange(max, -rangeOptions.count);
+
+ // Let the fixedRange reflect initial settings (#5930)
+ if (this.chart) {
+ this.chart.fixedRange = max - min;
+ }
+ }
+
+ dataMin = pick(this.dataMin, Number.MIN_VALUE);
+ if (!isNumber(min)) {
+ min = dataMin;
+ }
+ if (min <= dataMin) {
+ min = dataMin;
+ if (range === undefined) { // #4501
+ range = getTrueRange(min, rangeOptions.count);
+ }
+ this.newMax = Math.min(min + range, this.dataMax);
+ }
+ if (!isNumber(max)) {
+ min = undefined;
+ }
+ return min;
+
+};
+
+// Initialize rangeselector for stock charts
+wrap(Chart.prototype, 'init', function (proceed, options, callback) {
+
+ addEvent(this, 'init', function () {
+ if (this.options.rangeSelector.enabled) {
+ this.rangeSelector = new RangeSelector(this);
+ }
+ });
+
+ proceed.call(this, options, callback);
+
+});
+
+wrap(Chart.prototype, 'render', function (proceed, options, callback) {
+
+ var chart = this,
+ axes = chart.axes,
+ rangeSelector = chart.rangeSelector,
+ verticalAlign;
+
+ if (rangeSelector) {
+
+ each(axes, function (axis) {
+ axis.updateNames();
+ axis.setScale();
+ });
+
+ chart.getAxisMargins();
+
+ rangeSelector.render();
+ verticalAlign = rangeSelector.options.verticalAlign;
+
+ if (!rangeSelector.options.floating) {
+ if (verticalAlign === 'bottom') {
+ this.extraBottomMargin = true;
+ } else if (verticalAlign !== 'middle') {
+ this.extraTopMargin = true;
+ }
+ }
+ }
+
+ proceed.call(this, options, callback);
+
+});
+
+wrap(Chart.prototype, 'update', function (proceed, options, redraw, oneToOne) {
+
+ var chart = this,
+ rangeSelector = chart.rangeSelector,
+ verticalAlign;
+
+ this.extraBottomMargin = false;
+ this.extraTopMargin = false;
+
+ if (rangeSelector) {
+
+ rangeSelector.render();
+
+ verticalAlign = (options.rangeSelector && options.rangeSelector.verticalAlign) ||
+ (rangeSelector.options && rangeSelector.options.verticalAlign);
+
+ if (!rangeSelector.options.floating) {
+ if (verticalAlign === 'bottom') {
+ this.extraBottomMargin = true;
+ } else if (verticalAlign !== 'middle') {
+ this.extraTopMargin = true;
+ }
+ }
+ }
+
+ proceed.call(this, H.merge(true, options, {
+ chart: {
+ marginBottom: pick(options.chart && options.chart.marginBottom, chart.margin.bottom),
+ spacingBottom: pick(options.chart && options.chart.spacingBottom, chart.spacing.bottom)
+ }
+ }), redraw, oneToOne);
+
+});
+
+wrap(Chart.prototype, 'redraw', function (proceed, options, callback) {
+ var chart = this,
+ rangeSelector = chart.rangeSelector,
+ verticalAlign;
+
+ if (rangeSelector && !rangeSelector.options.floating) {
+
+ rangeSelector.render();
+ verticalAlign = rangeSelector.options.verticalAlign;
+
+ if (verticalAlign === 'bottom') {
+ this.extraBottomMargin = true;
+ } else if (verticalAlign !== 'middle') {
+ this.extraTopMargin = true;
+ }
+ }
+
+ proceed.call(this, options, callback);
+});
+
+Chart.prototype.adjustPlotArea = function () {
+ var chart = this,
+ rangeSelector = chart.rangeSelector,
+ rangeSelectorHeight;
+
+ if (this.rangeSelector) {
+
+ rangeSelectorHeight = rangeSelector.getHeight();
+
+ if (this.extraTopMargin) {
+ this.plotTop += rangeSelectorHeight;
+ }
+
+ if (this.extraBottomMargin) {
+ this.marginBottom += rangeSelectorHeight;
+ }
+ }
+};
+
+Chart.prototype.callbacks.push(function (chart) {
+ var extremes,
+ rangeSelector = chart.rangeSelector,
+ unbindRender,
+ unbindSetExtremes;
+
+ function renderRangeSelector() {
+ extremes = chart.xAxis[0].getExtremes();
+ if (isNumber(extremes.min)) {
+ rangeSelector.render(extremes.min, extremes.max);
+ }
+ }
+
+ if (rangeSelector) {
+ // redraw the scroller on setExtremes
+ unbindSetExtremes = addEvent(
+ chart.xAxis[0],
+ 'afterSetExtremes',
+ function (e) {
+ rangeSelector.render(e.min, e.max);
+ }
+ );
+
+ // redraw the scroller chart resize
+ unbindRender = addEvent(chart, 'redraw', renderRangeSelector);
+
+ // do it now
+ renderRangeSelector();
+ }
+
+ // Remove resize/afterSetExtremes at chart destroy
+ addEvent(chart, 'destroy', function destroyEvents() {
+ if (rangeSelector) {
+ unbindRender();
+ unbindSetExtremes();
+ }
+ });
+});
+
+
+H.RangeSelector = RangeSelector;
+
+/* ****************************************************************************
+ * End Range Selector code *
+ *****************************************************************************/
+
+}(Highcharts));
+(function (H) {
+/**
+ * (c) 2010-2017 Torstein Honsi
+ *
+ * License: www.highcharts.com/license
+ */
+var arrayMax = H.arrayMax,
+ arrayMin = H.arrayMin,
+ Axis = H.Axis,
+ Chart = H.Chart,
+ defined = H.defined,
+ each = H.each,
+ extend = H.extend,
+ format = H.format,
+ grep = H.grep,
+ inArray = H.inArray,
+ isNumber = H.isNumber,
+ isString = H.isString,
+ map = H.map,
+ merge = H.merge,
+ pick = H.pick,
+ Point = H.Point,
+ Renderer = H.Renderer,
+ Series = H.Series,
+ splat = H.splat,
+ SVGRenderer = H.SVGRenderer,
+ VMLRenderer = H.VMLRenderer,
+ wrap = H.wrap,
+
+
+ seriesProto = Series.prototype,
+ seriesInit = seriesProto.init,
+ seriesProcessData = seriesProto.processData,
+ pointTooltipFormatter = Point.prototype.tooltipFormatter;
+
+
+/**
+ * Compare the values of the series against the first non-null, non-
+ * zero value in the visible range. The y axis will show percentage
+ * or absolute change depending on whether `compare` is set to `"percent"`
+ * or `"value"`. When this is applied to multiple series, it allows
+ * comparing the development of the series against each other.
+ *
+ * @type {String}
+ * @see [compareBase](#plotOptions.series.compareBase), [Axis.setCompare()](#Axis.
+ * setCompare())
+ * @sample {highstock} stock/plotoptions/series-compare-percent/ Percent
+ * @sample {highstock} stock/plotoptions/series-compare-value/ Value
+ * @default undefined
+ * @since 1.0.1
+ * @product highstock
+ * @apioption plotOptions.series.compare
+ */
+
+/**
+ * Defines if comparisson should start from the first point within the visible
+ * range or should start from the first point before the range.
+ * In other words, this flag determines if first point within the visible range
+ * will have 0% (`compareStart=true`) or should have been already calculated
+ * according to the previous point (`compareStart=false`).
+ *
+ * @type {Boolean}
+ * @sample {highstock} stock/plotoptions/series-comparestart/ Calculate compare within visible range
+ * @default false
+ * @since 6.0.0
+ * @product highstock
+ * @apioption plotOptions.series.compareStart
+ */
+
+/**
+ * When [compare](#plotOptions.series.compare) is `percent`, this option
+ * dictates whether to use 0 or 100 as the base of comparison.
+ *
+ * @validvalue [0, 100]
+ * @type {Number}
+ * @sample {highstock} / Compare base is 100
+ * @default 0
+ * @since 5.0.6
+ * @product highstock
+ * @apioption plotOptions.series.compareBase
+ */
+
+/**
+ * Factory function for creating new stock charts. Creates a new {@link Chart|
+ * Chart} object with different default options than the basic Chart.
+ *
+ * @function #stockChart
+ * @memberOf Highcharts
+ *
+ * @param {String|HTMLDOMElement} renderTo
+ * The DOM element to render to, or its id.
+ * @param {Options} options
+ * The chart options structure as described in the {@link
+ * https://api.highcharts.com/highstock|options reference}.
+ * @param {Function} callback
+ * A function to execute when the chart object is finished loading and
+ * rendering. In most cases the chart is built in one thread, but in
+ * Internet Explorer version 8 or less the chart is sometimes initialized
+ * before the document is ready, and in these cases the chart object
+ * will not be finished synchronously. As a consequence, code that
+ * relies on the newly built Chart object should always run in the
+ * callback. Defining a {@link https://api.highcharts.com/highstock/chart.events.load|
+ * chart.event.load} handler is equivalent.
+ *
+ * @return {Chart}
+ * The chart object.
+ *
+ * @example
+ * var chart = Highcharts.stockChart('container', {
+ * series: [{
+ * data: [1, 2, 3, 4, 5, 6, 7, 8, 9],
+ * pointInterval: 24 * 60 * 60 * 1000
+ * }]
+ * });
+ */
+H.StockChart = H.stockChart = function (a, b, c) {
+ var hasRenderToArg = isString(a) || a.nodeName,
+ options = arguments[hasRenderToArg ? 1 : 0],
+ seriesOptions = options.series, // to increase performance, don't merge the data
+ defaultOptions = H.getOptions(),
+ opposite,
+
+ // Always disable startOnTick:true on the main axis when the navigator
+ // is enabled (#1090)
+ navigatorEnabled = pick(
+ options.navigator && options.navigator.enabled,
+ defaultOptions.navigator.enabled,
+ true
+ ),
+ disableStartOnTick = navigatorEnabled ? {
+ startOnTick: false,
+ endOnTick: false
+ } : null,
+
+ lineOptions = {
+
+ marker: {
+ enabled: false,
+ radius: 2
+ }
+ // gapSize: 0
+ },
+ columnOptions = {
+ shadow: false,
+ borderWidth: 0
+ };
+
+ // apply X axis options to both single and multi y axes
+ options.xAxis = map(splat(options.xAxis || {}), function (xAxisOptions) {
+ return merge(
+ { // defaults
+ minPadding: 0,
+ maxPadding: 0,
+ overscroll: 0,
+ ordinal: true,
+ title: {
+ text: null
+ },
+ labels: {
+ overflow: 'justify'
+ },
+ showLastLabel: true
+ },
+ defaultOptions.xAxis, // #3802
+ xAxisOptions, // user options
+ { // forced options
+ type: 'datetime',
+ categories: null
+ },
+ disableStartOnTick
+ );
+ });
+
+ // apply Y axis options to both single and multi y axes
+ options.yAxis = map(splat(options.yAxis || {}), function (yAxisOptions) {
+ opposite = pick(yAxisOptions.opposite, true);
+ return merge({ // defaults
+ labels: {
+ y: -2
+ },
+ opposite: opposite,
+
+ /**
+ * @default {highcharts} true
+ * @default {highstock} false
+ * @apioption yAxis.showLastLabel
+ */
+ showLastLabel: !!(
+ // #6104, show last label by default for category axes
+ yAxisOptions.categories ||
+ yAxisOptions.type === 'category'
+ ),
+
+ title: {
+ text: null
+ }
+ },
+ defaultOptions.yAxis, // #3802
+ yAxisOptions // user options
+ );
+ });
+
+ options.series = null;
+
+ options = merge(
+ {
+ chart: {
+ panning: true,
+ pinchType: 'x'
+ },
+ navigator: {
+ enabled: navigatorEnabled
+ },
+ scrollbar: {
+ // #4988 - check if setOptions was called
+ enabled: pick(defaultOptions.scrollbar.enabled, true)
+ },
+ rangeSelector: {
+ // #4988 - check if setOptions was called
+ enabled: pick(defaultOptions.rangeSelector.enabled, true)
+ },
+ title: {
+ text: null
+ },
+ tooltip: {
+ split: pick(defaultOptions.tooltip.split, true),
+ crosshairs: true
+ },
+ legend: {
+ enabled: false
+ },
+
+ plotOptions: {
+ line: lineOptions,
+ spline: lineOptions,
+ area: lineOptions,
+ areaspline: lineOptions,
+ arearange: lineOptions,
+ areasplinerange: lineOptions,
+ column: columnOptions,
+ columnrange: columnOptions,
+ candlestick: columnOptions,
+ ohlc: columnOptions
+ }
+
+ },
+
+ options, // user's options
+
+ { // forced options
+ isStock: true // internal flag
+ }
+ );
+
+ options.series = seriesOptions;
+
+ return hasRenderToArg ?
+ new Chart(a, options, c) :
+ new Chart(options, b);
+};
+
+// Override the automatic label alignment so that the first Y axis' labels
+// are drawn on top of the grid line, and subsequent axes are drawn outside
+wrap(Axis.prototype, 'autoLabelAlign', function (proceed) {
+ var chart = this.chart,
+ options = this.options,
+ panes = chart._labelPanes = chart._labelPanes || {},
+ key,
+ labelOptions = this.options.labels;
+ if (this.chart.options.isStock && this.coll === 'yAxis') {
+ key = options.top + ',' + options.height;
+ if (!panes[key] && labelOptions.enabled) { // do it only for the first Y axis of each pane
+ if (labelOptions.x === 15) { // default
+ labelOptions.x = 0;
+ }
+ if (labelOptions.align === undefined) {
+ labelOptions.align = 'right';
+ }
+ panes[key] = this;
+ return 'right';
+ }
+ }
+ return proceed.apply(this, [].slice.call(arguments, 1));
+});
+
+// Clear axis from label panes (#6071)
+wrap(Axis.prototype, 'destroy', function (proceed) {
+ var chart = this.chart,
+ key = this.options && (this.options.top + ',' + this.options.height);
+
+ if (key && chart._labelPanes && chart._labelPanes[key] === this) {
+ delete chart._labelPanes[key];
+ }
+
+ return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
+});
+
+// Override getPlotLinePath to allow for multipane charts
+wrap(Axis.prototype, 'getPlotLinePath', function (proceed, value, lineWidth, old, force, translatedValue) {
+ var axis = this,
+ series = (this.isLinked && !this.series ? this.linkedParent.series : this.series),
+ chart = axis.chart,
+ renderer = chart.renderer,
+ axisLeft = axis.left,
+ axisTop = axis.top,
+ x1,
+ y1,
+ x2,
+ y2,
+ result = [],
+ axes = [], // #3416 need a default array
+ axes2,
+ uniqueAxes,
+ transVal;
+
+ /**
+ * Return the other axis based on either the axis option or on related series.
+ */
+ function getAxis(coll) {
+ var otherColl = coll === 'xAxis' ? 'yAxis' : 'xAxis',
+ opt = axis.options[otherColl];
+
+ // Other axis indexed by number
+ if (isNumber(opt)) {
+ return [chart[otherColl][opt]];
+ }
+
+ // Other axis indexed by id (like navigator)
+ if (isString(opt)) {
+ return [chart.get(opt)];
+ }
+
+ // Auto detect based on existing series
+ return map(series, function (s) {
+ return s[otherColl];
+ });
+ }
+
+ // Ignore in case of colorAxis or zAxis. #3360, #3524, #6720
+ if (axis.coll !== 'xAxis' && axis.coll !== 'yAxis') {
+ return proceed.apply(this, [].slice.call(arguments, 1));
+ }
+
+ // Get the related axes based on series
+ axes = getAxis(axis.coll);
+
+ // Get the related axes based options.*Axis setting #2810
+ axes2 = (axis.isXAxis ? chart.yAxis : chart.xAxis);
+ each(axes2, function (A) {
+ if (defined(A.options.id) ? A.options.id.indexOf('navigator') === -1 : true) {
+ var a = (A.isXAxis ? 'yAxis' : 'xAxis'),
+ rax = (defined(A.options[a]) ? chart[a][A.options[a]] : chart[a][0]);
+
+ if (axis === rax) {
+ axes.push(A);
+ }
+ }
+ });
+
+
+ // Remove duplicates in the axes array. If there are no axes in the axes array,
+ // we are adding an axis without data, so we need to populate this with grid
+ // lines (#2796).
+ uniqueAxes = axes.length ? [] : [axis.isXAxis ? chart.yAxis[0] : chart.xAxis[0]]; // #3742
+ each(axes, function (axis2) {
+ if (
+ inArray(axis2, uniqueAxes) === -1 &&
+ // Do not draw on axis which overlap completely. #5424
+ !H.find(uniqueAxes, function (unique) {
+ return unique.pos === axis2.pos && unique.len && axis2.len;
+ })
+ ) {
+ uniqueAxes.push(axis2);
+ }
+ });
+
+ transVal = pick(translatedValue, axis.translate(value, null, null, old));
+ if (isNumber(transVal)) {
+ if (axis.horiz) {
+ each(uniqueAxes, function (axis2) {
+ var skip;
+
+ y1 = axis2.pos;
+ y2 = y1 + axis2.len;
+ x1 = x2 = Math.round(transVal + axis.transB);
+
+ if (x1 < axisLeft || x1 > axisLeft + axis.width) { // outside plot area
+ if (force) {
+ x1 = x2 = Math.min(Math.max(axisLeft, x1), axisLeft + axis.width);
+ } else {
+ skip = true;
+ }
+ }
+ if (!skip) {
+ result.push('M', x1, y1, 'L', x2, y2);
+ }
+ });
+ } else {
+ each(uniqueAxes, function (axis2) {
+ var skip;
+
+ x1 = axis2.pos;
+ x2 = x1 + axis2.len;
+ y1 = y2 = Math.round(axisTop + axis.height - transVal);
+
+ if (y1 < axisTop || y1 > axisTop + axis.height) { // outside plot area
+ if (force) {
+ y1 = y2 = Math.min(Math.max(axisTop, y1), axis.top + axis.height);
+ } else {
+ skip = true;
+ }
+ }
+ if (!skip) {
+ result.push('M', x1, y1, 'L', x2, y2);
+ }
+ });
+ }
+ }
+ return result.length > 0 ?
+ renderer.crispPolyLine(result, lineWidth || 1) :
+ null; // #3557 getPlotLinePath in regular Highcharts also returns null
+});
+
+// Function to crisp a line with multiple segments
+SVGRenderer.prototype.crispPolyLine = function (points, width) {
+ // points format: ['M', 0, 0, 'L', 100, 0]
+ // normalize to a crisp line
+ var i;
+ for (i = 0; i < points.length; i = i + 6) {
+ if (points[i + 1] === points[i + 4]) {
+ // Substract due to #1129. Now bottom and left axis gridlines behave the same.
+ points[i + 1] = points[i + 4] = Math.round(points[i + 1]) - (width % 2 / 2);
+ }
+ if (points[i + 2] === points[i + 5]) {
+ points[i + 2] = points[i + 5] = Math.round(points[i + 2]) + (width % 2 / 2);
+ }
+ }
+ return points;
+};
+
+if (Renderer === VMLRenderer) {
+ VMLRenderer.prototype.crispPolyLine = SVGRenderer.prototype.crispPolyLine;
+}
+
+
+// Wrapper to hide the label
+wrap(Axis.prototype, 'hideCrosshair', function (proceed, i) {
+
+ proceed.call(this, i);
+
+ if (this.crossLabel) {
+ this.crossLabel = this.crossLabel.hide();
+ }
+});
+
+// Wrapper to draw the label
+wrap(Axis.prototype, 'drawCrosshair', function (proceed, e, point) {
+
+ // Draw the crosshair
+ proceed.call(this, e, point);
+
+ // Check if the label has to be drawn
+ if (
+ !defined(this.crosshair.label) ||
+ !this.crosshair.label.enabled ||
+ !this.cross
+ ) {
+ return;
+ }
+
+ var chart = this.chart,
+ options = this.options.crosshair.label, // the label's options
+ horiz = this.horiz, // axis orientation
+ opposite = this.opposite, // axis position
+ left = this.left, // left position
+ top = this.top, // top position
+ crossLabel = this.crossLabel, // reference to the svgElement
+ posx,
+ posy,
+ crossBox,
+ formatOption = options.format,
+ formatFormat = '',
+ limit,
+ align,
+ tickInside = this.options.tickPosition === 'inside',
+ snap = this.crosshair.snap !== false,
+ value,
+ offset = 0;
+
+ // Use last available event (#5287)
+ if (!e) {
+ e = this.cross && this.cross.e;
+ }
+
+ align = (horiz ? 'center' : opposite ?
+ (this.labelAlign === 'right' ? 'right' : 'left') :
+ (this.labelAlign === 'left' ? 'left' : 'center'));
+
+ // If the label does not exist yet, create it.
+ if (!crossLabel) {
+ crossLabel = this.crossLabel = chart.renderer.label(
+ null,
+ null,
+ null,
+ options.shape || 'callout'
+ )
+ .addClass('highcharts-crosshair-label' +
+ (this.series[0] && ' highcharts-color-' + this.series[0].colorIndex))
+ .attr({
+ align: options.align || align,
+ padding: pick(options.padding, 8),
+ r: pick(options.borderRadius, 3),
+ zIndex: 2
+ })
+ .add(this.labelGroup);
+
+
+ // Presentational
+ crossLabel
+ .attr({
+ fill: options.backgroundColor ||
+ (this.series[0] && this.series[0].color) ||
+ '#666666',
+ stroke: options.borderColor || '',
+ 'stroke-width': options.borderWidth || 0
+ })
+ .css(extend({
+ color: '#ffffff',
+ fontWeight: 'normal',
+ fontSize: '11px',
+ textAlign: 'center'
+ }, options.style));
+
+ }
+
+ if (horiz) {
+ posx = snap ? point.plotX + left : e.chartX;
+ posy = top + (opposite ? 0 : this.height);
+ } else {
+ posx = opposite ? this.width + left : 0;
+ posy = snap ? point.plotY + top : e.chartY;
+ }
+
+ if (!formatOption && !options.formatter) {
+ if (this.isDatetimeAxis) {
+ formatFormat = '%b %d, %Y';
+ }
+ formatOption = '{value' + (formatFormat ? ':' + formatFormat : '') + '}';
+ }
+
+ // Show the label
+ value = snap ?
+ point[this.isXAxis ? 'x' : 'y'] :
+ this.toValue(horiz ? e.chartX : e.chartY);
+
+ crossLabel.attr({
+ text: formatOption ?
+ format(formatOption, { value: value }, chart.time) :
+ options.formatter.call(this, value),
+ x: posx,
+ y: posy,
+ // Crosshair should be rendered within Axis range (#7219)
+ visibility: value < this.min || value > this.max ? 'hidden' : 'visible'
+ });
+
+ crossBox = crossLabel.getBBox();
+
+ // now it is placed we can correct its position
+ if (horiz) {
+ if ((tickInside && !opposite) || (!tickInside && opposite)) {
+ posy = crossLabel.y - crossBox.height;
+ }
+ } else {
+ posy = crossLabel.y - (crossBox.height / 2);
+ }
+
+ // check the edges
+ if (horiz) {
+ limit = {
+ left: left - crossBox.x,
+ right: left + this.width - crossBox.x
+ };
+ } else {
+ limit = {
+ left: this.labelAlign === 'left' ? left : 0,
+ right: this.labelAlign === 'right' ? left + this.width : chart.chartWidth
+ };
+ }
+
+ // left edge
+ if (crossLabel.translateX < limit.left) {
+ offset = limit.left - crossLabel.translateX;
+ }
+ // right edge
+ if (crossLabel.translateX + crossBox.width >= limit.right) {
+ offset = -(crossLabel.translateX + crossBox.width - limit.right);
+ }
+
+ // show the crosslabel
+ crossLabel.attr({
+ x: posx + offset,
+ y: posy,
+ // First set x and y, then anchorX and anchorY, when box is actually calculated, #5702
+ anchorX: horiz ? posx : (this.opposite ? 0 : chart.chartWidth),
+ anchorY: horiz ? (this.opposite ? chart.chartHeight : 0) : posy + crossBox.height / 2
+ });
+});
+
+/* ****************************************************************************
+ * Start value compare logic *
+ *****************************************************************************/
+
+/**
+ * Extend series.init by adding a method to modify the y value used for plotting
+ * on the y axis. This method is called both from the axis when finding dataMin
+ * and dataMax, and from the series.translate method.
+ */
+seriesProto.init = function () {
+
+ // Call base method
+ seriesInit.apply(this, arguments);
+
+ // Set comparison mode
+ this.setCompare(this.options.compare);
+};
+
+/**
+ * Highstock only. Set the {@link
+ * http://api.highcharts.com/highstock/plotOptions.series.compare|
+ * compare} mode of the series after render time. In most cases it is more
+ * useful running {@link Axis#setCompare} on the X axis to update all its
+ * series.
+ *
+ * @function setCompare
+ * @memberOf Series.prototype
+ *
+ * @param {String} compare
+ * Can be one of `null`, `"percent"` or `"value"`.
+ */
+seriesProto.setCompare = function (compare) {
+
+ // Set or unset the modifyValue method
+ this.modifyValue = (compare === 'value' || compare === 'percent') ? function (value, point) {
+ var compareValue = this.compareValue;
+
+ if (value !== undefined && compareValue !== undefined) { // #2601, #5814
+
+ // Get the modified value
+ if (compare === 'value') {
+ value -= compareValue;
+
+ // Compare percent
+ } else {
+ value = 100 * (value / compareValue) -
+ (this.options.compareBase === 100 ? 0 : 100);
+ }
+
+ // record for tooltip etc.
+ if (point) {
+ point.change = value;
+ }
+
+ return value;
+ }
+ } : null;
+
+ // Survive to export, #5485
+ this.userOptions.compare = compare;
+
+ // Mark dirty
+ if (this.chart.hasRendered) {
+ this.isDirty = true;
+ }
+
+};
+
+/**
+ * Extend series.processData by finding the first y value in the plot area,
+ * used for comparing the following values
+ */
+seriesProto.processData = function () {
+ var series = this,
+ i,
+ keyIndex = -1,
+ processedXData,
+ processedYData,
+ compareStart = series.options.compareStart === true ? 0 : 1,
+ length,
+ compareValue;
+
+ // call base method
+ seriesProcessData.apply(this, arguments);
+
+ if (series.xAxis && series.processedYData) { // not pies
+
+ // local variables
+ processedXData = series.processedXData;
+ processedYData = series.processedYData;
+ length = processedYData.length;
+
+ // For series with more than one value (range, OHLC etc), compare against
+ // close or the pointValKey (#4922, #3112)
+ if (series.pointArrayMap) {
+ // Use close if present (#3112)
+ keyIndex = inArray('close', series.pointArrayMap);
+ if (keyIndex === -1) {
+ keyIndex = inArray(series.pointValKey || 'y', series.pointArrayMap);
+ }
+ }
+
+ // find the first value for comparison
+ for (i = 0; i < length - compareStart; i++) {
+ compareValue = processedYData[i] && keyIndex > -1 ?
+ processedYData[i][keyIndex] :
+ processedYData[i];
+ if (
+ isNumber(compareValue) &&
+ processedXData[i + compareStart] >= series.xAxis.min &&
+ compareValue !== 0
+ ) {
+ series.compareValue = compareValue;
+ break;
+ }
+ }
+ }
+};
+
+/**
+ * Modify series extremes
+ */
+wrap(seriesProto, 'getExtremes', function (proceed) {
+ var extremes;
+
+ proceed.apply(this, [].slice.call(arguments, 1));
+
+ if (this.modifyValue) {
+ extremes = [this.modifyValue(this.dataMin), this.modifyValue(this.dataMax)];
+ this.dataMin = arrayMin(extremes);
+ this.dataMax = arrayMax(extremes);
+ }
+});
+
+/**
+ * Highstock only. Set the compare mode on all series belonging to an Y axis
+ * after render time.
+ *
+ * @param {String} compare
+ * The compare mode. Can be one of `null`, `"value"` or `"percent"`.
+ * @param {Boolean} [redraw=true]
+ * Whether to redraw the chart or to wait for a later call to {@link
+ * Chart#redraw},
+ *
+ * @function setCompare
+ * @memberOf Axis.prototype
+ *
+ * @see {@link https://api.highcharts.com/highstock/series.plotOptions.compare|
+ * series.plotOptions.compare}
+ *
+ * @sample stock/members/axis-setcompare/
+ * Set compoare
+ */
+Axis.prototype.setCompare = function (compare, redraw) {
+ if (!this.isXAxis) {
+ each(this.series, function (series) {
+ series.setCompare(compare);
+ });
+ if (pick(redraw, true)) {
+ this.chart.redraw();
+ }
+ }
+};
+
+/**
+ * Extend the tooltip formatter by adding support for the point.change variable
+ * as well as the changeDecimals option
+ */
+Point.prototype.tooltipFormatter = function (pointFormat) {
+ var point = this;
+
+ pointFormat = pointFormat.replace(
+ '{point.change}',
+ (point.change > 0 ? '+' : '') +
+ H.numberFormat(point.change, pick(point.series.tooltipOptions.changeDecimals, 2))
+ );
+
+ return pointTooltipFormatter.apply(this, [pointFormat]);
+};
+
+/* ****************************************************************************
+ * End value compare logic *
+ *****************************************************************************/
+
+
+/**
+ * Extend the Series prototype to create a separate series clip box. This is
+ * related to using multiple panes, and a future pane logic should incorporate
+ * this feature (#2754).
+ */
+wrap(Series.prototype, 'render', function (proceed) {
+ // Only do this on not 3d (#2939, #5904) nor polar (#6057) charts, and only
+ // if the series type handles clipping in the animate method (#2975).
+ if (
+ !(this.chart.is3d && this.chart.is3d()) &&
+ !this.chart.polar &&
+ this.xAxis &&
+ !this.xAxis.isRadial // Gauge, #6192
+ ) {
+
+ // First render, initial clip box
+ if (!this.clipBox && this.animate) {
+ this.clipBox = merge(this.chart.clipBox);
+ this.clipBox.width = this.xAxis.len;
+ this.clipBox.height = this.yAxis.len;
+
+ // On redrawing, resizing etc, update the clip rectangle
+ } else if (this.chart[this.sharedClipKey]) {
+ this.chart[this.sharedClipKey].attr({
+ width: this.xAxis.len,
+ height: this.yAxis.len
+ });
+ // #3111
+ } else if (this.clipBox) {
+ this.clipBox.width = this.xAxis.len;
+ this.clipBox.height = this.yAxis.len;
+ }
+ }
+ proceed.call(this);
+});
+
+wrap(Chart.prototype, 'getSelectedPoints', function (proceed) {
+ var points = proceed.call(this);
+
+ each(this.series, function (serie) {
+ // series.points - for grouped points (#6445)
+ if (serie.hasGroupedData) {
+ points = points.concat(grep(serie.points || [], function (point) {
+ return point.selected;
+ }));
+ }
+ });
+ return points;
+});
+
+wrap(Chart.prototype, 'update', function (proceed, options) {
+ // Use case: enabling scrollbar from a disabled state.
+ // Scrollbar needs to be initialized from a controller, Navigator in this
+ // case (#6615)
+ if ('scrollbar' in options && this.navigator) {
+ merge(true, this.options.scrollbar, options.scrollbar);
+ this.navigator.update({}, false);
+ delete options.scrollbar;
+ }
+
+ return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
+});
+
+}(Highcharts));
+(function () {
+
+
+}());
+return Highcharts
+}));
\ No newline at end of file