diff --git a/notemyprogress/ajax.php b/notemyprogress/ajax.php index 1bd5ed4b377c9e9d30eb521c0dd39c70ad71774a..a2fb52b92b808a54f6ad7d8ab3c516d0fcb4dd41 100644 --- a/notemyprogress/ajax.php +++ b/notemyprogress/ajax.php @@ -73,6 +73,7 @@ $settings= optional_param('settings', false ,PARAM_TEXT); $url= optional_param('url', false ,PARAM_TEXT); //$courseid = optional_param('modulename', false ,PARAM_TEXT); $enable = optional_param('enable', false ,PARAM_BOOL); +$section = optional_param('section',false,PARAM_TEXT); /* */ @@ -193,17 +194,35 @@ if($action == 'saveconfigweek') {//Exemple: if the action passed is saveconfigwe array_push($params, $levels); array_push($params, $settings); array_push($params, $url); - array_push($params, $enable); + array_push($params, $section); if($courseid && $userid && $rules && $levels && $settings && $url) { $func = "local_notemyprogress_save_gamification_config"; } }elseif($action =='rankable'){ array_push($params, $courseid); array_push($params, $userid); + array_push($params, $url); if($courseid && $userid) { $func = "local_notemyprogress_set_rankable_player"; } -} +}elseif($action =='saveEnable'){ + array_push($params, $courseid); + array_push($params, $userid); + array_push($params,$enable); + array_push($params,$url); + if($courseid && $userid) { + $func = "local_notemyprogress_save_enable"; + } +}elseif($action =='studentGamificationViewed'){ + array_push($params, $courseid); + array_push($params, $userid); + array_push($params,$url); + if($courseid && $userid) { + $func = "local_notemyprogress_viewed_student_lvl"; + } +} + + if(isset($params) && isset($func)){ call_user_func_array($func, $params); @@ -337,7 +356,6 @@ function local_notemyprogress_get_student_sessions($weekcode, $courseid, $userid function local_notemyprogress_downloadMoodleLogs($beginDate, $lastDate, $courseid, $userid, $currentUrl) { $logs = new \local_notemyprogress\logs($courseid, $userid); - $logs->addLogsNMP("downloaded", "logfile", "LOGFILES", "moodle", $currentUrl, "File that contains all the activities performed on the Note My Progress plugin in a time interval"); $jsonData = $logs->searchLogsMoodle($beginDate, $lastDate); local_notemyprogress_ajax_response(array("jsonData"=>$jsonData)); @@ -348,31 +366,42 @@ function local_notemyprogress_addLogs($sectionname, $actiontype, $courseid, $use $logs->addLogsNMP($actiontype, $objectType, $sectionname, $objectName, $currentUrl, $objectDescription); local_notemyprogress_ajax_response(array("ok"=>"ok")); } -function local_notemyprogress_save_gamification_config($courseid, $userid, $rules, $levels, $settings, $url,$enable){ - // \local_notemyprogress\logs::create( - // "setgamification", - // "configweeks", - // "saved", - // "weeks_settings", - // $url, - // 4, - // $userid, - // $courseid - // ); - +function local_notemyprogress_save_gamification_config($courseid, $userid, $rules, $levels, $settings, $url,$section){ + $logs = new \local_notemyprogress\logs($courseid, $userid); + $logs->addLogsNMP("Saved", $section, "CONFIGURATION_GAMIFICATION", "configuration_gamification", $url, "GamificationSaved"); + $configLevels = new \local_notemyprogress\configgamification($courseid, $userid); $configLevels->save_levels($levels, $settings, $rules); - $configLevels->save_enable($enable); + //$configLevels->save_enable($enable); $message = get_string('fml_api_save_successful', 'local_notemyprogress'); local_notemyprogress_ajax_response($message); } -function local_notemyprogress_set_rankable_player($courseid, $userid){ +function local_notemyprogress_set_rankable_player($courseid, $userid,$url){ + $logs = new \local_notemyprogress\logs($courseid, $userid); + $logs->addLogsNMP("Saved", "section", "STUDENT_GAMIFICATION", "student_gamification", $url, "GamificationSaved"); GLobal $DB; - // $sql = "update {notemyprogress_xp} set rankable = ? where courseid = ? and userid = ?"; - // $DB->execute($sql, array(1,$courseid, $userid)); - $sql = "UPDATE {notemyprogress_xp} SET rankable = 1 WHERE courseid = 2 AND userid = 3"; - $DB->execute($sql); + $sql = "update {notemyprogress_xp} set rankable = ? where courseid = ? and userid = ?"; + $DB->execute($sql, array(1,$courseid, $userid)); + // $sql = "UPDATE {notemyprogress_xp} SET rankable = 1 WHERE courseid = 2 AND userid = 3"; + // $DB->execute($sql); $message = get_string('fml_api_save_successful', 'local_notemyprogress'); local_notemyprogress_ajax_response($message); -} \ No newline at end of file +} + +function local_notemyprogress_save_enable($courseid, $userid, $enable,$url){ + $logs = new \local_notemyprogress\logs($courseid, $userid); + $logs->addLogsNMP("Saved", "section", "CONFIGURATION_GAMIFICATION", "configuration_gamification", $url, "GamificationSaved"); + $configLevels = new \local_notemyprogress\configgamification($courseid, $userid); + $configLevels->save_enable($enable); + $message = get_string('fml_api_save_successful', 'local_notemyprogress'); + local_notemyprogress_ajax_response($message); + } + function local_notemyprogress_viewed_student_lvl($courseid, $userid,$url){ + $logs = new \local_notemyprogress\logs($courseid, $userid); + $logs->addLogsNMP("Viewed", "section", "STUDENT_GAMIFICATION", "student_gamification", $url, "GamificationSaved");; + // $configLevels = new \local_notemyprogress\configgamification($courseid, $userid); + // $configLevels->save_enable($enable); + // $message = get_string('fml_api_save_successful', 'local_notemyprogress'); + // local_notemyprogress_ajax_response($message); + } \ No newline at end of file diff --git a/notemyprogress/amd/build/gamification.js b/notemyprogress/amd/build/gamification.js index e83c2b5d5dcc9c72007e142993c3987a557cc6d6..5ba1ae7061ea5405269a932cd210be99e41a35be 100644 --- a/notemyprogress/amd/build/gamification.js +++ b/notemyprogress/amd/build/gamification.js @@ -4,13 +4,15 @@ define([ "local_notemyprogress/axios", "local_notemyprogress/alertify", "local_notemyprogress/pageheader", -], function (Vue, Vuetify, Axios, Alertify, PageHeader) { + "local_notemyprogress/chartdynamic", +], function (Vue, Vuetify, Axios, Alertify, PageHeader, ChartDynamic) { "use strict"; function init(content) { - //console.log(content); + console.log(content); Vue.use(Vuetify); Vue.component("pageheader", PageHeader); + Vue.component("chart", ChartDynamic); const app = new Vue({ delimiters: ["[[", "]]"], el: "#gamification", @@ -33,7 +35,13 @@ define([ setPointsOption: "calculated", pointsBase: 0, pointsBaseOri: 0, - swDisableEnable: "", + swDisableEnable: content.strings.swValue, + spreadData: [], + week_resources_categories: [], + week_resources_data: [], + week_resources_colors: "#FA4641", + indicators: content.indicators, + chartdata: content.chart_data, }, beforeMount() {}, @@ -113,10 +121,11 @@ define([ } return parseInt(x); }, - save_changes() { + save_changes(logParam) { this.notifications = ["Do you want to save the changes"]; Alertify.confirm(this.strings.save_warning_content, () => { - this.saveGamificationConfig(); + this.saveGamificationConfig(logParam); + console.log(logParam); }) // ON CONFIRM .set({ title: this.strings.save_warning_title }) .set({ @@ -126,7 +135,7 @@ define([ }, }); }, - saveGamificationConfig() { + saveGamificationConfig(logParam) { this.loading = true; let settings = { tit: this.settings.tit, @@ -142,10 +151,10 @@ define([ rules: JSON.stringify(this.rulesData), token: this.token, enable: this.enable, + section: logParam, }; let url = { url: window.location.href }; Axios({ - ///! try resolve that with a listener method: "post", url: M.cfg.wwwroot + @@ -162,8 +171,10 @@ define([ "&url=" + url.url + "&enable=" + - data.enable, - data: data, + data.enable + + "§ion=" + + data.section, + params: data, }) .then((response) => { // console.log(response); @@ -213,7 +224,204 @@ define([ disableEnable(swDisableEnable) { //traitement this.enable = swDisableEnable; + let data = { + courseid: this.courseid, + userid: this.userid, + action: "saveEnable", + enable: this.enable, + }; + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + params: data, + }) + .then((response) => { + if (response.status == 200 && response.data.ok) { + this.showNotifications(response.data.data, "success"); + } else { + let message = + response.data.error || this.strings.api_error_network; + this.showNotifications(message, "error"); //in this line "error" define the kind of message send + } + }) + .catch((e) => { + let message = e.response.data || this.strings.api_error_network; + this.showNotifications(message, "error"); + }) + .finally(() => { + this.loading = false; + }); + }, + //? ////////////////////////////////////////////////////////////////////// ?// + //? //////////////////////////// ChartPart /////////////////////////////// ?// + //? ////////////////////////////////////////////////////////////////////// ?// + chart_spread() { + let chart = new Object(); + chart.chart = { + type: "column", + backgroundColor: null, + }; + chart.title = { + text: this.strings.chartTitle, + }; + chart.colors = ["#118AB2"]; + (chart.xAxis = { + type: "category", + labels: { + rotation: -45, + style: { + fontSize: "13px", + fontFamily: "Verdana, sans-serif", + }, + }, + }), + (chart.yAxis = { + min: 0, + title: { + text: this.strings.chartYaxis, + }, + }); + chart.legend = { + enabled: false, + }; + (chart.series = [ + { + name: null, + data: this.chartdata, //[["level : 1", 1]], + dataLabels: { + enabled: true, + rotation: -90, + color: "#FFFFFF", + align: "right", + format: "{point.y:.1f}", // one decimal + y: 10, // 10 pixels down from the top + style: { + fontSize: "13px", + fontFamily: "Verdana, sans-serif", + }, + }, + }, + ]), + console.log("series: "); + console.log(chart.series); + return chart; }, + /* + build_inverted_time_chart() { + console.log("enter build_inverted_time_chart "); + //console.log(this.students_planification); + //console.log("this.data_report_meta_hours = "); + //console.log(this.data_report_meta_hours); + let chart = new Object(); + let meta = new Object(); + meta = this.chartdata_hours_week_dedication(); + console.log("meta = "); + console.log(meta); + let invest = [ + { + name: meta.labels[0], + y: meta.datasets[0].data[0], + }, + { + name: meta.labels[1], + y: meta.datasets[0].data[1], + }, + { + name: meta.labels[2], + y: meta.datasets[0].data[2], + }, + ]; + console.log("invest = "); + console.log(invest); + chart.chart = { + type: "bar", + backgroundColor: null, + style: { fontFamily: "poppins" }, + }; + chart.title = { text: null }; + chart.colors = this.inverted_time_colors; + chart.xAxis = { + type: "category", + crosshair: true, + }; + chart.yAxis = { + title: { + text: "Framboise", + }, + }; + chart.tooltip = { + shared: true, + useHTML: true, + formatter: function () { + let category_name = this.points[0].key; + let time = vue.convert_time(this.y); + return `<b>${category_name}: </b>${time}`; + }, + }; + chart.legend = { + enabled: false, + }; + chart.series = [ + { + colorByPoint: true, + data: invest, + }, + ]; + //console.log("this.inverted_time.data = "); + //console.log(this.inverted_time.data); + + // console.log("invest = "); + // console.log(invest); + return chart; + }, + chartdata_hours_week_dedication() { + var data = new Object(); + data.datasets = []; + + let inverted = + this.render_has == "teacher" + ? this.strings.inverted_time + : `${this.strings.myself} ${this.strings.inverted_time}`; + let planified = + this.render_has == "teacher" + ? this.strings.planified_time + : `${this.strings.myself} ${this.strings.planified_time}`; + data.labels = [inverted, planified]; + var dataset = new Object(); + dataset.label = "Horas"; + //console.log("data_report_meta_hours in chartdata_hours_week_dedication = "); + //console.log(this.data_report_meta_hours); + dataset.data = [ + parseFloat(this.data_report_meta_hours.horas_trabajadas), + parseInt(this.data_report_meta_hours.horas_planificadas), + ]; + dataset.backgroundColor = ["#ffa700", "#a0c2fa"]; + dataset.borderWidth = 0; + data.datasets.push(dataset); + + //if (this.render_has == "student" && this.compare_with_course) { + data.labels.splice(1, 0, this.strings.inverted_time_course); + //data.labels.splice(3, 0, this.strings.planified_time_course); + dataset.data.splice( + 1, + 0, + parseFloat(this.course_report_hours.horas_trabajadas) + ); + // dataset.data.splice( + // 3, + // 0, + // parseFloat(this.course_report_hours.horas_planificadas) + // ); + dataset.backgroundColor.splice(1, 0, "#ffa700"); + //dataset.backgroundColor.splice(3, 0, "#a0c2fa"); + //} + //console.log("data_report_meta_hours = "); + //console.log(this.data_report_meta_hours); + + //console.log("data = "); + //console.log(data); + return data; + },*/ }, }); } diff --git a/notemyprogress/amd/build/listener.min.js b/notemyprogress/amd/build/listener.min.js index f7412851521e015dd04ea00d52bd076ea76c2e2e..577b33cb6aedc1b3b10838f0ef40f60696806187 100644 --- a/notemyprogress/amd/build/listener.min.js +++ b/notemyprogress/amd/build/listener.min.js @@ -1 +1,11 @@ -define([],function(){return{init:function(){document.querySelector("#downloadButton").addEventListener("click",function(){console.log("clicked !")})}}}); \ No newline at end of file +define([], function () { + return { + init: function () { + document + .querySelector("#downloadButton") + .addEventListener("click", function () { + console.log("clicked !"); + }); + }, + }; +}); diff --git a/notemyprogress/amd/build/student_gamification.js b/notemyprogress/amd/build/student_gamification.js index a77d864883406dae90330837bdfab7cd94e991d2..d7fb47847300386cc9b36ae99c5382f37b04e6d9 100644 --- a/notemyprogress/amd/build/student_gamification.js +++ b/notemyprogress/amd/build/student_gamification.js @@ -31,7 +31,18 @@ define([ error_messages: [], save_successful: false, }, - beforeMount() {}, + beforeMount() { + let data = { + courseid: this.courseid, + userid: this.userid, + action: "studentGamificationViewed", + }; + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + params: data, + }); + }, computed: {}, methods: { get_help_content() { diff --git a/notemyprogress/amd/build/teacher.js b/notemyprogress/amd/build/teacher.js new file mode 100644 index 0000000000000000000000000000000000000000..59e8e40d2f52b57d58a94dc6da977a237f792f03 --- /dev/null +++ b/notemyprogress/amd/build/teacher.js @@ -0,0 +1,305 @@ +define([ + "local_notemyprogress/vue", + "local_notemyprogress/vuetify", + "local_notemyprogress/axios", + "local_notemyprogress/pagination", + "local_notemyprogress/chartstatic", + "local_notemyprogress/pageheader", + "local_notemyprogress/helpdialog", +], function ( + Vue, + Vuetify, + Axios, + Pagination, + ChartStatic, + PageHeader, + HelpDialog +) { + "use strict"; + + function init(content) { + // console.log(content); + Vue.use(Vuetify); + Vue.component("pagination", Pagination); + Vue.component("chart", ChartStatic); + Vue.component("pageheader", PageHeader); + Vue.component("helpdialog", HelpDialog); + let vue = new Vue({ + delimiters: ["[[", "]]"], + el: "#teacher", + vuetify: new Vuetify(), + data() { + return { + strings: content.strings, + groups: content.groups, + userid: content.userid, + courseid: content.courseid, + timezone: content.timezone, + render_has: content.profile_render, + + indicators: content.indicators, + week_resources_colors: content.week_resources_colors, + search: null, + week_resources_categories: [], + week_resources_data: [], + + help_dialog: false, + help_contents: [], + }; + }, + beforeMount() { + this.calculate_week_resources(); + }, + mounted() { + document.querySelector("#sessions-loader").style.display = "none"; + document.querySelector("#teacher").style.display = "block"; + }, + methods: { + get_help_content() { + let contents = []; + contents.push({ + title: this.strings.section_help_title, + description: this.strings.section_help_description, + }); + return contents; + }, + + get_course_grade() { + let grade = Number(this.indicators.course.grademax); + return this.isInt(grade) ? grade : grade.toFixed(2); + }, + + calculate_week_resources() { + let categories = [], + data = []; + let week_name; + this.indicators.weeks.forEach((week) => { + week_name = `${week.name} ${week.position + 1}`; + categories.push(week_name); + data.push(week.cms); + }); + let name = this.capitalizeFirstLetter( + this.strings.teacher_indicators_modules + ); + this.week_resources_categories = categories; + this.week_resources_data = [{ name, data }]; + }, + + build_week_resources_chart() { + let chart = new Object(); + chart.chart = { + type: "bar", + backgroundColor: null, + style: { fontFamily: "poppins" }, + }; + chart.title = { + text: null, + }; + chart.colors = this.week_resources_colors; + chart.xAxis = { + categories: this.week_resources_categories, + }; + chart.yAxis = { + min: 0, + title: { + text: this.strings.teacher_indicators_week_resources_yaxis_title, + }, + }; + chart.legend = { + enabled: false, + }; + chart.series = this.week_resources_data; + console.log(chart.series); + return chart; + }, + + build_weeks_sessions_chart() { + let chart = new Object(); + chart.chart = { + type: "heatmap", + backgroundColor: null, + style: { fontFamily: "poppins" }, + }; + chart.title = { + text: null, + }; + chart.xAxis = { + categories: this.strings.weeks, + }; + chart.yAxis = { + categories: this.indicators.sessions.categories, + title: null, + reversed: true, + }; + chart.colorAxis = { + min: 0, + minColor: "#E0E0E0", + maxColor: "#118AB2", + }; + chart.legend = { + layout: "horizontal", + verticalAlign: "bottom", + }; + chart.tooltip = { + formatter: function () { + let days = + vue.indicators.sessions.weeks[this.point.y][this.point.x] || ""; + let xCategoryName = vue.get_point_category_name(this.point, "x"); + let yCategoryName = vue.get_point_category_name(this.point, "y"); + let label = vue.strings.teacher_indicators_sessions; + if (this.point.value == 1) { + label = vue.strings.teacher_indicators_session; + } + return ( + "<b>" + + yCategoryName + + " " + + xCategoryName + + "</b>: " + + this.point.value + + " " + + label + + "<br/>" + + days + ); + }, + }; + chart.series = [ + { + borderWidth: 2, + borderColor: "#FAFAFA", + data: this.indicators.sessions.data, + }, + ]; + return chart; + }, + + table_headers() { + let headers = [ + { text: "", value: "id", align: "center", sortable: false }, + { text: this.strings.thead_name, value: "firstname" }, + { text: this.strings.thead_lastname, value: "lastname" }, + { text: this.strings.thead_email, value: "email" }, + { + text: this.strings.thead_progress, + value: "progress_percentage", + align: "center", + }, + { + text: this.strings.thead_sessions, + value: "sessions_number", + align: "center", + }, + { + text: this.strings.thead_time, + value: "inverted_time", + align: "center", + }, + ]; + return headers; + }, + + get_picture_url(userid) { + let url = `${M.cfg.wwwroot}/user/pix.php?file=/${userid}/f1.jpg`; + return url; + }, + + get_percentage_progress(value) { + return `${value} %`; + }, + + get_progress_tooltip(item) { + let module_label = this.strings.teacher_indicators_modules; + let finished_label = this.strings.teacher_indicators_finished; + if (item.cms.complete == 1) { + module_label = this.strings.teacher_indicators_module; + finished_label = this.strings.teacher_indicators_finalized; + } + return `${item.cms.complete} ${module_label} ${finished_label} ${this.strings.of_conector} ${item.cms.total}`; + }, + + get_point_category_name(point, dimension) { + let series = point.series, + isY = dimension === "y", + axis = series[isY ? "yAxis" : "xAxis"]; + return axis.categories[point[isY ? "y" : "x"]]; + }, + + capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); + }, + + isInt(n) { + return n % 1 === 0; + }, + + open_chart_help(chart) { + let contents = []; + if (chart == "week_resources") { + contents.push({ + title: this.strings.week_resources_help_title, + description: this.strings.week_resources_help_description_p1, + }); + contents.push({ + description: this.strings.week_resources_help_description_p2, + }); + } else if (chart == "weeks_sessions") { + contents.push({ + title: this.strings.weeks_sessions_help_title, + description: this.strings.week_sessions_help_description_p1, + }); + contents.push({ + description: this.strings.week_sessions_help_description_p2, + }); + } else if (chart == "progress_table") { + contents.push({ + title: this.strings.progress_table_help_title, + description: this.strings.progress_table_help_description, + }); + } + this.help_contents = contents; + if (this.help_contents.length) { + this.help_dialog = true; + } + }, + + update_help_dialog(value) { + this.help_dialog = value; + }, + + get_timezone() { + let information = `${this.strings.ss_change_timezone} ${this.timezone}`; + return information; + }, + + addLogsIntoDB(action, objectName, objectType, objectDescription) { + let data = { + courseid: content.courseid, + userid: content.userid, + action: "addLogs", + sectionname: "TEACHER_GENERAL_INDICATORS", + actiontype: action, + objectType: objectType, + objectName: objectName, + currentUrl: document.location.href, + objectDescription: objectDescription, + }; + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + params: data, + }) + .then((response) => { + if (response.status == 200 && response.data.ok) { + } + }) + .catch((e) => {}); + }, + }, + }); + } + + return { + init: init, + }; +}); diff --git a/notemyprogress/amd/build/teacher.min.js b/notemyprogress/amd/build/teacher.min.js deleted file mode 100644 index 7b49e25001efbe51976e1ee2eb2274b2f88de329..0000000000000000000000000000000000000000 --- a/notemyprogress/amd/build/teacher.min.js +++ /dev/null @@ -1,2 +0,0 @@ -define(["local_notemyprogress/vue","local_notemyprogress/vuetify","local_notemyprogress/axios","local_notemyprogress/pagination","local_notemyprogress/chartstatic","local_notemyprogress/pageheader","local_notemyprogress/helpdialog"],function(e,t,s,i,r,o,n){"use strict";return{init:function(a){e.use(t),e.component("pagination",i),e.component("chart",r),e.component("pageheader",o),e.component("helpdialog",n);let l=new e({delimiters:["[[","]]"],el:"#teacher",vuetify:new t,data:()=>({strings:a.strings,groups:a.groups,userid:a.userid,courseid:a.courseid,timezone:a.timezone,render_has:a.profile_render,indicators:a.indicators,week_resources_colors:a.week_resources_colors,search:null,week_resources_categories:[],week_resources_data:[],help_dialog:!1,help_contents:[]}),beforeMount(){this.calculate_week_resources()},mounted(){document.querySelector("#sessions-loader").style.display="none",document.querySelector("#teacher").style.display="block"},methods:{get_help_content(){let e=[];return e.push({title:this.strings.section_help_title,description:this.strings.section_help_description}),e},get_course_grade(){let e=Number(this.indicators.course.grademax);return this.isInt(e)?e:e.toFixed(2)},calculate_week_resources(){let e,t=[],s=[];this.indicators.weeks.forEach(i=>{e=`${i.name} ${i.position+1}`,t.push(e),s.push(i.cms)});let i=this.capitalizeFirstLetter(this.strings.teacher_indicators_modules);this.week_resources_categories=t,this.week_resources_data=[{name:i,data:s}]},build_week_resources_chart(){let e=new Object;return e.chart={type:"bar",backgroundColor:null,style:{fontFamily:"poppins"}},e.title={text:null},e.colors=this.week_resources_colors,e.xAxis={categories:this.week_resources_categories},e.yAxis={min:0,title:{text:this.strings.teacher_indicators_week_resources_yaxis_title}},e.legend={enabled:!1},e.series=this.week_resources_data,e},build_weeks_sessions_chart(){let e=new Object;return e.chart={type:"heatmap",backgroundColor:null,style:{fontFamily:"poppins"}},e.title={text:null},e.xAxis={categories:this.strings.weeks},e.yAxis={categories:this.indicators.sessions.categories,title:null,reversed:!0},e.colorAxis={min:0,minColor:"#E0E0E0",maxColor:"#118AB2"},e.legend={layout:"horizontal",verticalAlign:"bottom"},e.tooltip={formatter:function(){let e=l.indicators.sessions.weeks[this.point.y][this.point.x]||"",t=l.get_point_category_name(this.point,"x"),s=l.get_point_category_name(this.point,"y"),i=l.strings.teacher_indicators_sessions;return 1==this.point.value&&(i=l.strings.teacher_indicators_session),"<b>"+s+" "+t+"</b>: "+this.point.value+" "+i+"<br/>"+e}},e.series=[{borderWidth:2,borderColor:"#FAFAFA",data:this.indicators.sessions.data}],e},table_headers(){return[{text:"",value:"id",align:"center",sortable:!1},{text:this.strings.thead_name,value:"firstname"},{text:this.strings.thead_lastname,value:"lastname"},{text:this.strings.thead_email,value:"email"},{text:this.strings.thead_progress,value:"progress_percentage",align:"center"},{text:this.strings.thead_sessions,value:"sessions_number",align:"center"},{text:this.strings.thead_time,value:"inverted_time",align:"center"}]},get_picture_url:e=>`${M.cfg.wwwroot}/user/pix.php?file=/${e}/f1.jpg`,get_percentage_progress:e=>`${e} %`,get_progress_tooltip(e){let t=this.strings.teacher_indicators_modules,s=this.strings.teacher_indicators_finished;return 1==e.cms.complete&&(t=this.strings.teacher_indicators_module,s=this.strings.teacher_indicators_finalized),`${e.cms.complete} ${t} ${s} ${this.strings.of_conector} ${e.cms.total}`},get_point_category_name(e,t){let s="y"===t;return e.series[s?"yAxis":"xAxis"].categories[e[s?"y":"x"]]},capitalizeFirstLetter:e=>e.charAt(0).toUpperCase()+e.slice(1),isInt:e=>e%1==0,open_chart_help(e){let t=[];"week_resources"==e?(t.push({title:this.strings.week_resources_help_title,description:this.strings.week_resources_help_description_p1}),t.push({description:this.strings.week_resources_help_description_p2})):"weeks_sessions"==e?(t.push({title:this.strings.weeks_sessions_help_title,description:this.strings.week_sessions_help_description_p1}),t.push({description:this.strings.week_sessions_help_description_p2})):"progress_table"==e&&t.push({title:this.strings.progress_table_help_title,description:this.strings.progress_table_help_description}),this.help_contents=t,this.help_contents.length&&(this.help_dialog=!0)},update_help_dialog(e){this.help_dialog=e},get_timezone(){return`${this.strings.ss_change_timezone} ${this.timezone}`},addLogsIntoDB(e,t,i,r){let o={courseid:a.courseid,userid:a.userid,action:"addLogs",sectionname:"TEACHER_GENERAL_INDICATORS",actiontype:e,objectType:i,objectName:t,currentUrl:document.location.href,objectDescription:r};s({method:"get",url:M.cfg.wwwroot+"/local/notemyprogress/ajax.php",params:o}).then(e=>{200==e.status&&e.data.ok}).catch(e=>{})}}})}}}); -//# sourceMappingURL=teacher.min.js.map diff --git a/notemyprogress/amd/build/teacher.min.js.map b/notemyprogress/amd/build/teacher.min.js.map deleted file mode 100644 index 6579b63acd185dff389ab267b839dbee24635552..0000000000000000000000000000000000000000 --- a/notemyprogress/amd/build/teacher.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["../src/teacher.js"],"names":["define","Vue","Vuetify","Axios","Pagination","ChartStatic","PageHeader","HelpDialog","init","content","use","component","vue","delimiters","el","vuetify","data","strings","groups","userid","courseid","timezone","render_has","profile_render","indicators","week_resources_colors","search","week_resources_categories","week_resources_data","help_dialog","help_contents","beforeMount","calculate_week_resources","mounted","document","querySelector","style","display","methods","get_help_content","contents","title","section_help_title","description","section_help_description","get_course_grade","grade","course","grademax","isInt","toFixed","categories","week_name","weeks","forEach","week","name","position","push","cms","capitalizeFirstLetter","teacher_indicators_modules","build_week_resources_chart","chart","type","backgroundColor","fontFamily","text","colors","xAxis","yAxis","min","teacher_indicators_week_resources_yaxis_title","legend","enabled","series","build_weeks_sessions_chart","sessions","reversed","colorAxis","minColor","maxColor","layout","verticalAlign","tooltip","formatter","days","point","y","x","xCategoryName","get_point_category_name","yCategoryName","label","teacher_indicators_sessions","value","teacher_indicators_session","borderWidth","borderColor","table_headers","headers","align","sortable","thead_name","thead_lastname","thead_email","thead_progress","thead_sessions","thead_time","get_picture_url","url","M","cfg","wwwroot","get_percentage_progress","get_progress_tooltip","item","module_label","finished_label","teacher_indicators_finished","complete","teacher_indicators_module","teacher_indicators_finalized","of_conector","total","dimension","isY","axis","string","charAt","toUpperCase","slice","n","open_chart_help","week_resources_help_title","week_resources_help_description_p1","week_resources_help_description_p2","weeks_sessions_help_title","week_sessions_help_description_p1","week_sessions_help_description_p2","progress_table_help_title","progress_table_help_description","length","update_help_dialog","get_timezone","information","ss_change_timezone"],"mappings":"AAAAA,OAAM,8BAAC,CAAC,wBAAD,CACC,4BADD,CAEC,0BAFD,CAGC,+BAHD,CAIC,gCAJD,CAKC,+BALD,CAMC,+BAND,CAAD,CAQF,SAASC,CAAT,CAAcC,CAAd,CAAuBC,CAAvB,CAA8BC,CAA9B,CAA0CC,CAA1C,CAAuDC,CAAvD,CAAmEC,CAAnE,CAA+E,CAC3E,aAwOA,MAAO,CACHC,IAAI,CAvOR,SAAcC,CAAd,CAAuB,CAEnBR,CAAG,CAACS,GAAJ,CAAQR,CAAR,EACAD,CAAG,CAACU,SAAJ,CAAc,YAAd,CAA4BP,CAA5B,EACAH,CAAG,CAACU,SAAJ,CAAc,OAAd,CAAuBN,CAAvB,EACAJ,CAAG,CAACU,SAAJ,CAAc,YAAd,CAA4BL,CAA5B,EACAL,CAAG,CAACU,SAAJ,CAAc,YAAd,CAA4BJ,CAA5B,EACA,GAAIK,CAAAA,CAAG,CAAG,GAAIX,CAAAA,CAAJ,CAAQ,CACdY,UAAU,CAAE,CAAC,IAAD,CAAO,IAAP,CADE,CAEdC,EAAE,CAAE,UAFU,CAGdC,OAAO,CAAE,GAAIb,CAAAA,CAHC,CAIdc,IAJc,gBAIP,CACH,MAAO,CACHC,OAAO,CAAGR,CAAO,CAACQ,OADf,CAEHC,MAAM,CAAGT,CAAO,CAACS,MAFd,CAGHC,MAAM,CAAGV,CAAO,CAACU,MAHd,CAIHC,QAAQ,CAAGX,CAAO,CAACW,QAJhB,CAKHC,QAAQ,CAAGZ,CAAO,CAACY,QALhB,CAMHC,UAAU,CAAGb,CAAO,CAACc,cANlB,CAQHC,UAAU,CAAEf,CAAO,CAACe,UARjB,CASHC,qBAAqB,CAAEhB,CAAO,CAACgB,qBAT5B,CAUHC,MAAM,CAAE,IAVL,CAWHC,yBAAyB,CAAE,EAXxB,CAYHC,mBAAmB,CAAE,EAZlB,CAcHC,WAAW,GAdR,CAeHC,aAAa,CAAE,EAfZ,CAiBV,CAtBa,CAuBdC,WAvBc,uBAuBD,CACT,KAAKC,wBAAL,EACH,CAzBa,CA0BdC,OA1Bc,mBA0BL,CACLC,QAAQ,CAACC,aAAT,CAAuB,kBAAvB,EAA2CC,KAA3C,CAAiDC,OAAjD,CAA2D,MAA3D,CACAH,QAAQ,CAACC,aAAT,CAAuB,UAAvB,EAAmCC,KAAnC,CAAyCC,OAAzC,CAAmD,OACtD,CA7Ba,CA8BdC,OAAO,CAAG,CACNC,gBADM,4BACY,CACd,GAAIC,CAAAA,CAAQ,CAAG,CACD,CACVC,KAAK,CAAE,KAAKxB,OAAL,CAAayB,kBADV,CAEVC,WAAW,CAAE,KAAK1B,OAAL,CAAa2B,wBAFhB,CADC,CAAf,CAKA,MAAOJ,CAAAA,CACV,CARK,CAUNK,gBAVM,4BAUY,CACd,GAAIC,CAAAA,CAAK,EAAU,KAAKtB,UAAL,CAAgBuB,MAAhB,CAAuBC,QAA1C,CACA,MAAQ,MAAKC,KAAL,CAAWH,CAAX,CAAD,CAAsBA,CAAtB,CAA8BA,CAAK,CAACI,OAAN,CAAc,CAAd,CACxC,CAbK,CAeNlB,wBAfM,oCAeqB,IACnBmB,CAAAA,CAAU,CAAG,EADM,CACFnC,CAAI,CAAG,EADL,CAEnBoC,CAFmB,CAGvB,KAAK5B,UAAL,CAAgB6B,KAAhB,CAAsBC,OAAtB,CAA8B,SAAAC,CAAI,CAAI,CAClCH,CAAS,WAAMG,CAAI,CAACC,IAAX,aAAoBD,CAAI,CAACE,QAAL,CAAc,CAAlC,CAAT,CACAN,CAAU,CAACO,IAAX,CAAgBN,CAAhB,EACApC,CAAI,CAAC0C,IAAL,CAAUH,CAAI,CAACI,GAAf,CACH,CAJD,EAKA,GAAIH,CAAAA,CAAI,CAAG,KAAKI,qBAAL,CAA2B,KAAK3C,OAAL,CAAa4C,0BAAxC,CAAX,CACA,KAAKlC,yBAAL,CAAiCwB,CAAjC,CACA,KAAKvB,mBAAL,CAA2B,CAAC,CAAE4B,IAAI,CAAJA,CAAF,CAAQxC,IAAI,CAAJA,CAAR,CAAD,CAC9B,CA1BK,CA4BN8C,0BA5BM,sCA4BuB,CACzB,GAAIC,CAAAA,CAAK,GAAT,CACAA,CAAK,CAACA,KAAN,CAAc,CACVC,IAAI,CAAE,KADI,CAEVC,eAAe,CAAE,IAFP,CAGV7B,KAAK,CAAE,CAAC8B,UAAU,CAAE,SAAb,CAHG,CAAd,CAKAH,CAAK,CAACtB,KAAN,CAAc,CACV0B,IAAI,CAAE,IADI,CAAd,CAGAJ,CAAK,CAACK,MAAN,CAAe,KAAK3C,qBAApB,CACAsC,CAAK,CAACM,KAAN,CAAc,CACVlB,UAAU,CAAE,KAAKxB,yBADP,CAAd,CAGAoC,CAAK,CAACO,KAAN,CAAc,CACVC,GAAG,CAAE,CADK,CAEN9B,KAAK,CAAE,CACP0B,IAAI,CAAE,KAAKlD,OAAL,CAAauD,6CADZ,CAFD,CAAd,CAMAT,CAAK,CAACU,MAAN,CAAe,CACXC,OAAO,GADI,CAAf,CAGAX,CAAK,CAACY,MAAN,CAAe,KAAK/C,mBAApB,CACA,MAAOmC,CAAAA,CACV,CArDK,CAuDNa,0BAvDM,sCAuDuB,CACzB,GAAIb,CAAAA,CAAK,GAAT,CACAA,CAAK,CAACA,KAAN,CAAc,CACVC,IAAI,CAAE,SADI,CAEVC,eAAe,CAAE,IAFP,CAGV7B,KAAK,CAAE,CAAC8B,UAAU,CAAE,SAAb,CAHG,CAAd,CAKAH,CAAK,CAACtB,KAAN,CAAc,CACV0B,IAAI,CAAE,IADI,CAAd,CAGAJ,CAAK,CAACM,KAAN,CAAc,CACVlB,UAAU,CAAE,KAAKlC,OAAL,CAAaoC,KADf,CAAd,CAGAU,CAAK,CAACO,KAAN,CAAc,CACVnB,UAAU,CAAE,KAAK3B,UAAL,CAAgBqD,QAAhB,CAAyB1B,UAD3B,CAEVV,KAAK,CAAE,IAFG,CAGVqC,QAAQ,GAHE,CAAd,CAKAf,CAAK,CAACgB,SAAN,CAAkB,CACdR,GAAG,CAAE,CADS,CAEdS,QAAQ,CAAE,SAFI,CAGdC,QAAQ,CAAE,SAHI,CAAlB,CAKAlB,CAAK,CAACU,MAAN,CAAe,CACXS,MAAM,CAAE,YADG,CAEXC,aAAa,CAAE,QAFJ,CAAf,CAIApB,CAAK,CAACqB,OAAN,CAAgB,CACZC,SAAS,CAAE,oBAAY,IACfC,CAAAA,CAAI,CAAG1E,CAAG,CAACY,UAAJ,CAAeqD,QAAf,CAAwBxB,KAAxB,CAA8B,KAAKkC,KAAL,CAAWC,CAAzC,EAA4C,KAAKD,KAAL,CAAWE,CAAvD,GAA6D,EADrD,CAEfC,CAAa,CAAG9E,CAAG,CAAC+E,uBAAJ,CAA4B,KAAKJ,KAAjC,CAAwC,GAAxC,CAFD,CAGfK,CAAa,CAAGhF,CAAG,CAAC+E,uBAAJ,CAA4B,KAAKJ,KAAjC,CAAwC,GAAxC,CAHD,CAIfM,CAAK,CAAGjF,CAAG,CAACK,OAAJ,CAAY6E,2BAJL,CAKnB,GAAwB,CAApB,OAAKP,KAAL,CAAWQ,KAAf,CAA2B,CACvBF,CAAK,CAAGjF,CAAG,CAACK,OAAJ,CAAY+E,0BACvB,CACD,MAAO,MAAQJ,CAAR,CAAwB,GAAxB,CAA8BF,CAA9B,CAA8C,QAA9C,CACD,KAAKH,KAAL,CAAWQ,KADV,CACiB,GADjB,CACuBF,CADvB,CAC+B,OAD/B,CACyCP,CACnD,CAXW,CAAhB,CAaAvB,CAAK,CAACY,MAAN,CAAe,CAAC,CACZsB,WAAW,CAAE,CADD,CAEZC,WAAW,CAAE,SAFD,CAGZlF,IAAI,CAAE,KAAKQ,UAAL,CAAgBqD,QAAhB,CAAyB7D,IAHnB,CAAD,CAAf,CAKA,MAAO+C,CAAAA,CACV,CArGK,CAuGNoC,aAvGM,yBAuGS,CACX,GAAIC,CAAAA,CAAO,CAAG,CACV,CAAEjC,IAAI,CAAE,EAAR,CAAY4B,KAAK,CAAG,IAApB,CAA0BM,KAAK,CAAG,QAAlC,CAA4CC,QAAQ,GAApD,CADU,CAEV,CAAEnC,IAAI,CAAE,KAAKlD,OAAL,CAAasF,UAArB,CAAkCR,KAAK,CAAG,WAA1C,CAFU,CAGV,CAAE5B,IAAI,CAAE,KAAKlD,OAAL,CAAauF,cAArB,CAAsCT,KAAK,CAAG,UAA9C,CAHU,CAIV,CAAE5B,IAAI,CAAE,KAAKlD,OAAL,CAAawF,WAArB,CAAmCV,KAAK,CAAG,OAA3C,CAJU,CAKV,CAAE5B,IAAI,CAAE,KAAKlD,OAAL,CAAayF,cAArB,CAAsCX,KAAK,CAAG,qBAA9C,CAAsEM,KAAK,CAAG,QAA9E,CALU,CAMV,CAAElC,IAAI,CAAE,KAAKlD,OAAL,CAAa0F,cAArB,CAAsCZ,KAAK,CAAG,iBAA9C,CAAkEM,KAAK,CAAG,QAA1E,CANU,CAOV,CAAElC,IAAI,CAAE,KAAKlD,OAAL,CAAa2F,UAArB,CAAkCb,KAAK,CAAG,eAA1C,CAA2DM,KAAK,CAAG,QAAnE,CAPU,CAAd,CASA,MAAOD,CAAAA,CACV,CAlHK,CAoHNS,eApHM,0BAoHU1F,CApHV,CAoHiB,CACnB,GAAI2F,CAAAA,CAAG,WAAMC,CAAC,CAACC,GAAF,CAAMC,OAAZ,gCAA0C9F,CAA1C,WAAP,CACA,MAAO2F,CAAAA,CACV,CAvHK,CAyHNI,uBAzHM,kCAyHkBnB,CAzHlB,CAyHwB,CAC1B,gBAAUA,CAAV,MACH,CA3HK,CA6HNoB,oBA7HM,+BA6HeC,CA7Hf,CA6HoB,IAClBC,CAAAA,CAAY,CAAG,KAAKpG,OAAL,CAAa4C,0BADV,CAElByD,CAAc,CAAG,KAAKrG,OAAL,CAAasG,2BAFZ,CAGtB,GAAyB,CAArB,EAAAH,CAAI,CAACzD,GAAL,CAAS6D,QAAb,CAA4B,CACxBH,CAAY,CAAG,KAAKpG,OAAL,CAAawG,yBAA5B,CACAH,CAAc,CAAG,KAAKrG,OAAL,CAAayG,4BACjC,CACD,gBAAUN,CAAI,CAACzD,GAAL,CAAS6D,QAAnB,aAA+BH,CAA/B,aAA+CC,CAA/C,aAAiE,KAAKrG,OAAL,CAAa0G,WAA9E,aAA6FP,CAAI,CAACzD,GAAL,CAASiE,KAAtG,CACH,CArIK,CAuINjC,uBAvIM,kCAuIkBJ,CAvIlB,CAuIyBsC,CAvIzB,CAuIoC,CACtC,GAAIlD,CAAAA,CAAM,CAAGY,CAAK,CAACZ,MAAnB,CACImD,CAAG,CAAiB,GAAd,GAAAD,CADV,CAEIE,CAAI,CAAGpD,CAAM,CAACmD,CAAG,CAAG,OAAH,CAAa,OAAjB,CAFjB,CAGA,MAAOC,CAAAA,CAAI,CAAC5E,UAAL,CAAgBoC,CAAK,CAACuC,CAAG,CAAG,GAAH,CAAS,GAAb,CAArB,CACV,CA5IK,CA8INlE,qBA9IM,gCA8IgBoE,CA9IhB,CA8IwB,CAC1B,MAAOA,CAAAA,CAAM,CAACC,MAAP,CAAc,CAAd,EAAiBC,WAAjB,GAAiCF,CAAM,CAACG,KAAP,CAAa,CAAb,CAC3C,CAhJK,CAkJNlF,KAlJM,gBAkJAmF,CAlJA,CAkJG,CACL,MAAiB,EAAV,EAAAA,CAAC,CAAG,CACd,CApJK,CAsJNC,eAtJM,0BAsJUtE,CAtJV,CAsJiB,CACnB,GAAIvB,CAAAA,CAAQ,CAAG,EAAf,CACA,GAAa,gBAAT,EAAAuB,CAAJ,CAA+B,CAC3BvB,CAAQ,CAACkB,IAAT,CAAc,CACVjB,KAAK,CAAE,KAAKxB,OAAL,CAAaqH,yBADV,CAEV3F,WAAW,CAAE,KAAK1B,OAAL,CAAasH,kCAFhB,CAAd,EAIA/F,CAAQ,CAACkB,IAAT,CAAc,CACVf,WAAW,CAAE,KAAK1B,OAAL,CAAauH,kCADhB,CAAd,CAGH,CARD,IAQO,IAAa,gBAAT,EAAAzE,CAAJ,CAA+B,CAClCvB,CAAQ,CAACkB,IAAT,CAAc,CACVjB,KAAK,CAAE,KAAKxB,OAAL,CAAawH,yBADV,CAEV9F,WAAW,CAAE,KAAK1B,OAAL,CAAayH,iCAFhB,CAAd,EAIAlG,CAAQ,CAACkB,IAAT,CAAc,CACVf,WAAW,CAAE,KAAK1B,OAAL,CAAa0H,iCADhB,CAAd,CAGH,CARM,IAQA,IAAa,gBAAT,EAAA5E,CAAJ,CAA+B,CAClCvB,CAAQ,CAACkB,IAAT,CAAc,CACVjB,KAAK,CAAE,KAAKxB,OAAL,CAAa2H,yBADV,CAEVjG,WAAW,CAAE,KAAK1B,OAAL,CAAa4H,+BAFhB,CAAd,CAIH,CACD,KAAK/G,aAAL,CAAqBU,CAArB,CACA,GAAI,KAAKV,aAAL,CAAmBgH,MAAvB,CAA+B,CAC3B,KAAKjH,WAAL,GACH,CACJ,CAlLK,CAoLNkH,kBApLM,6BAoLchD,CApLd,CAoLqB,CACvB,KAAKlE,WAAL,CAAmBkE,CACtB,CAtLK,CAwLNiD,YAxLM,wBAwLQ,CACV,GAAIC,CAAAA,CAAW,WAAM,KAAKhI,OAAL,CAAaiI,kBAAnB,aAAyC,KAAK7H,QAA9C,CAAf,CACA,MAAO4H,CAAAA,CACV,CA3LK,CA9BI,CAAR,CA6Nb,CAEM,CAGV,CApPC,CAAN","sourcesContent":["define([\"local_notemyprogress/vue\",\r\n \"local_notemyprogress/vuetify\",\r\n \"local_notemyprogress/axios\",\r\n \"local_notemyprogress/pagination\",\r\n \"local_notemyprogress/chartstatic\",\r\n \"local_notemyprogress/pageheader\",\r\n \"local_notemyprogress/helpdialog\",\r\n ],\r\n function(Vue, Vuetify, Axios, Pagination, ChartStatic, PageHeader, HelpDialog) {\r\n \"use strict\";\r\n\r\n function init(content) {\r\n // console.log(content);\r\n Vue.use(Vuetify);\r\n Vue.component('pagination', Pagination);\r\n Vue.component('chart', ChartStatic);\r\n Vue.component('pageheader', PageHeader);\r\n Vue.component('helpdialog', HelpDialog);\r\n let vue = new Vue({\r\n delimiters: [\"[[\", \"]]\"],\r\n el: \"#teacher\",\r\n vuetify: new Vuetify(),\r\n data() {\r\n return {\r\n strings : content.strings,\r\n groups : content.groups,\r\n userid : content.userid,\r\n courseid : content.courseid,\r\n timezone : content.timezone,\r\n render_has : content.profile_render,\r\n\r\n indicators: content.indicators,\r\n week_resources_colors: content.week_resources_colors,\r\n search: null,\r\n week_resources_categories: [],\r\n week_resources_data: [],\r\n\r\n help_dialog: false,\r\n help_contents: [],\r\n }\r\n },\r\n beforeMount(){\r\n this.calculate_week_resources();\r\n },\r\n mounted(){\r\n document.querySelector(\"#sessions-loader\").style.display = \"none\";\r\n document.querySelector(\"#teacher\").style.display = \"block\";\r\n },\r\n methods : {\r\n get_help_content(){\r\n let contents = [];\r\n contents.push({\r\n title: this.strings.section_help_title,\r\n description: this.strings.section_help_description,\r\n });\r\n return contents;\r\n },\r\n\r\n get_course_grade(){\r\n let grade = Number(this.indicators.course.grademax);\r\n return (this.isInt(grade)) ? grade : grade.toFixed(2);\r\n },\r\n\r\n calculate_week_resources() {\r\n let categories = [], data = [];\r\n let week_name;\r\n this.indicators.weeks.forEach(week => {\r\n week_name = `${week.name} ${(week.position+1)}`;\r\n categories.push(week_name);\r\n data.push(week.cms);\r\n });\r\n let name = this.capitalizeFirstLetter(this.strings.teacher_indicators_modules);\r\n this.week_resources_categories = categories;\r\n this.week_resources_data = [{ name, data}];\r\n },\r\n\r\n build_week_resources_chart() {\r\n let chart = new Object();\r\n chart.chart = {\r\n type: 'bar',\r\n backgroundColor: null,\r\n style: {fontFamily: 'poppins'},\r\n };\r\n chart.title = {\r\n text: null,\r\n };\r\n chart.colors = this.week_resources_colors;\r\n chart.xAxis = {\r\n categories: this.week_resources_categories\r\n };\r\n chart.yAxis = {\r\n min: 0,\r\n title: {\r\n text: this.strings.teacher_indicators_week_resources_yaxis_title\r\n }\r\n };\r\n chart.legend = {\r\n enabled: false\r\n };\r\n chart.series = this.week_resources_data;\r\n return chart;\r\n },\r\n\r\n build_weeks_sessions_chart() {\r\n let chart = new Object();\r\n chart.chart = {\r\n type: 'heatmap',\r\n backgroundColor: null,\r\n style: {fontFamily: 'poppins'},\r\n };\r\n chart.title = {\r\n text: null,\r\n };\r\n chart.xAxis = {\r\n categories: this.strings.weeks,\r\n };\r\n chart.yAxis = {\r\n categories: this.indicators.sessions.categories,\r\n title: null,\r\n reversed: true,\r\n };\r\n chart.colorAxis = {\r\n min: 0,\r\n minColor: '#E0E0E0',\r\n maxColor: '#118AB2'\r\n };\r\n chart.legend = {\r\n layout: 'horizontal',\r\n verticalAlign: 'bottom',\r\n };\r\n chart.tooltip = {\r\n formatter: function () {\r\n let days = vue.indicators.sessions.weeks[this.point.y][this.point.x] || '';\r\n let xCategoryName = vue.get_point_category_name(this.point, 'x');\r\n let yCategoryName = vue.get_point_category_name(this.point, 'y');\r\n let label = vue.strings.teacher_indicators_sessions;\r\n if (this.point.value == 1) {\r\n label = vue.strings.teacher_indicators_session;\r\n }\r\n return '<b>' + yCategoryName + ' ' + xCategoryName + '</b>: '\r\n + this.point.value +' ' + label + '<br/>' + days;\r\n }\r\n };\r\n chart.series = [{\r\n borderWidth: 2,\r\n borderColor: '#FAFAFA',\r\n data: this.indicators.sessions.data,\r\n }];\r\n return chart;\r\n },\r\n\r\n table_headers(){\r\n let headers = [\r\n { text: '', value : 'id', align : 'center', sortable : false},\r\n { text: this.strings.thead_name , value : 'firstname'},\r\n { text: this.strings.thead_lastname , value : 'lastname'},\r\n { text: this.strings.thead_email , value : 'email'},\r\n { text: this.strings.thead_progress , value : 'progress_percentage', align : 'center'},\r\n { text: this.strings.thead_sessions , value : 'sessions_number', align : 'center'},\r\n { text: this.strings.thead_time , value : 'inverted_time', align : 'center'},\r\n ];\r\n return headers;\r\n },\r\n\r\n get_picture_url(userid){\r\n let url = `${M.cfg.wwwroot}/user/pix.php?file=/${userid}/f1.jpg`;\r\n return url;\r\n },\r\n\r\n get_percentage_progress(value){\r\n return `${value} %`;\r\n },\r\n\r\n get_progress_tooltip(item){\r\n let module_label = this.strings.teacher_indicators_modules;\r\n let finished_label = this.strings.teacher_indicators_finished;\r\n if (item.cms.complete == 1) {\r\n module_label = this.strings.teacher_indicators_module;\r\n finished_label = this.strings.teacher_indicators_finalized;\r\n }\r\n return `${item.cms.complete} ${module_label} ${finished_label} ${this.strings.of_conector} ${item.cms.total}`;\r\n },\r\n\r\n get_point_category_name(point, dimension) {\r\n let series = point.series,\r\n isY = dimension === 'y',\r\n axis = series[isY ? 'yAxis' : 'xAxis'];\r\n return axis.categories[point[isY ? 'y' : 'x']];\r\n },\r\n\r\n capitalizeFirstLetter(string) {\r\n return string.charAt(0).toUpperCase() + string.slice(1);\r\n },\r\n\r\n isInt(n) {\r\n return n % 1 === 0;\r\n },\r\n\r\n open_chart_help(chart) {\r\n let contents = [];\r\n if (chart == \"week_resources\") {\r\n contents.push({\r\n title: this.strings.week_resources_help_title,\r\n description: this.strings.week_resources_help_description_p1,\r\n });\r\n contents.push({\r\n description: this.strings.week_resources_help_description_p2,\r\n });\r\n } else if (chart == \"weeks_sessions\") {\r\n contents.push({\r\n title: this.strings.weeks_sessions_help_title,\r\n description: this.strings.week_sessions_help_description_p1,\r\n });\r\n contents.push({\r\n description: this.strings.week_sessions_help_description_p2,\r\n });\r\n } else if (chart == \"progress_table\") {\r\n contents.push({\r\n title: this.strings.progress_table_help_title,\r\n description: this.strings.progress_table_help_description,\r\n });\r\n }\r\n this.help_contents = contents;\r\n if (this.help_contents.length) {\r\n this.help_dialog = true;\r\n }\r\n },\r\n\r\n update_help_dialog (value) {\r\n this.help_dialog = value;\r\n },\r\n\r\n get_timezone(){\r\n let information = `${this.strings.ss_change_timezone} ${this.timezone}`\r\n return information;\r\n },\r\n\r\n }\r\n })\r\n }\r\n\r\n return {\r\n init : init\r\n };\r\n });"],"file":"teacher.min.js"} \ No newline at end of file diff --git a/notemyprogress/amd/src/logs.js b/notemyprogress/amd/src/logs.js index 11f3883b60293bf7ae8fb7f7e14bb168c131588f..edc81115e83e526f5c3d83be4a402cbbe5afa8ca 100644 --- a/notemyprogress/amd/src/logs.js +++ b/notemyprogress/amd/src/logs.js @@ -2,305 +2,379 @@ @author 2021 Éric Bart <bart.eric@hotmail.com> */ -define(["local_notemyprogress/vue", - "local_notemyprogress/vuetify", - "local_notemyprogress/axios", - "local_notemyprogress/moment", - "local_notemyprogress/pagination", - "local_notemyprogress/pageheader", - "local_notemyprogress/helpdialog", - "local_notemyprogress/alertify", - -], - function (Vue, Vuetify, Axios, Moment, Pagination, Pageheader, HelpDialog, Alertify) { - "use strict"; - - function init(content) { - const timeout = 60 * 120 * 1000 - Axios.defaults.timeout = timeout - Vue.use(Vuetify); - Vue.component('pagination', Pagination); - Vue.component('pageheader', Pageheader); - Vue.component('helpdialog', HelpDialog); - let vue = new Vue({ - delimiters: ["[[", "]]"], - el: "#logs", - vuetify: new Vuetify(), - data() { - return { - calendarData: {}, - strings: content.strings, - groups: content.groups, - userid: content.userid, - courseid: content.courseid, - timezone: content.timezone, - render_has: content.profile_render, - courseRole: content.courseRole, - loading: false, - errors: [], - pages: content.pages, - help_dialog: false, - help_contents: [], - dateRules: [ - v => !!v || this.strings.logs_invalid_date - ] +define([ + "local_notemyprogress/vue", + "local_notemyprogress/vuetify", + "local_notemyprogress/axios", + "local_notemyprogress/moment", + "local_notemyprogress/pagination", + "local_notemyprogress/pageheader", + "local_notemyprogress/helpdialog", + "local_notemyprogress/alertify", +], function ( + Vue, + Vuetify, + Axios, + Moment, + Pagination, + Pageheader, + HelpDialog, + Alertify +) { + "use strict"; + function init(content) { + const timeout = 60 * 120 * 1000; + Axios.defaults.timeout = timeout; + Vue.use(Vuetify); + Vue.component("pagination", Pagination); + Vue.component("pageheader", Pageheader); + Vue.component("helpdialog", HelpDialog); + let vue = new Vue({ + delimiters: ["[[", "]]"], + el: "#logs", + vuetify: new Vuetify(), + data() { + return { + calendarData: {}, + strings: content.strings, + groups: content.groups, + userid: content.userid, + courseid: content.courseid, + timezone: content.timezone, + render_has: content.profile_render, + courseRole: content.courseRole, + loading: false, + errors: [], + pages: content.pages, + help_dialog: false, + help_contents: [], + dateRules: [(v) => !!v || this.strings.logs_invalid_date], + }; + }, + beforeMount() { + document.querySelector("#downloadButtonMoodle").style.display = "none"; + document.querySelector("#downloadButtonNMP").style.display = "none"; + }, + mounted() { + document.querySelector(".v-application--wrap").style.minHeight = "60vh"; + document.querySelector("#sessions-loader").style.display = "none"; + document.querySelector("#helpMoodle").style.display = "block"; + document.querySelector("#helpNMP").style.display = "block"; + document.querySelector("#downloadButtonMoodle").style.display = "block"; + document.querySelector("#downloadButtonNMP").style.display = "block"; + }, + methods: { + get_Moodlefile() { + let lastDate = document.querySelector("#lastDateMoodle"); + let beginDate = document.querySelector("#beginDateMoodle"); + let timestampBeginDate = 0; + let timestampLastDate = 0; + let parsedBeginDate = []; + let parsedLastDate = []; + this.url = false; + this.loading = true; + var data = { + action: "downloadMOODLElogs", + courseid: this.courseid, + userid: this.userid, + beginDate: beginDate.value, + lastDate: lastDate.value, + currentUrl: window.location.href, + }; + if (beginDate.value != "" && lastDate.value != "") { + parsedBeginDate = beginDate.value.split("-"); + timestampBeginDate = new Date( + parsedBeginDate[0], + parsedBeginDate[1] - 1, + parsedBeginDate[2] + ); + parsedLastDate = lastDate.value.split("-"); + timestampLastDate = new Date( + parsedLastDate[0], + parsedLastDate[1] - 1, + parsedLastDate[2] + ); + if (timestampBeginDate.getTime() <= timestampLastDate.getTime()) { + if (timestampBeginDate.getTime() <= Date.now()) { + document.querySelector("#downloadButtonMoodle").innerHTML = + this.strings.logs_download_btn; + document.getElementById("downloadButtonMoodle").disabled = true; + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + timeout: timeout, + params: data, + }) + .then((response) => { + this.loading = false; + if (response.status == 200 && response.data.ok) { + let jsonData = response.data.data.jsonData; + jsonData = jsonData.map((row) => ({ + Role: row.user.role, + Email: row.user.email, + Fullname: row.user.firstname + " " + row.user.lastname, + Date: row.time.date, + Hour: row.time.hour, + ACTION_VERB: row.action.actionverb, + CourseID: row.course.courseid, + CourseName: row.course.coursename, + OBJECT_ID: row.action.objectid, + OBJECT_NAME: row.action.objectname, + OBJECT_TYPE: row.action.objecttype, + })); + let csvData = vue.objectToCSV(jsonData); + vue.downloadCSV(csvData, "Activity_Moodle"); + document.querySelector( + "#downloadButtonMoodle" + ).innerHTML = this.strings.logs_valid_Moodlebtn; + document.getElementById( + "downloadButtonMoodle" + ).disabled = false; + Alertify.success( + this.strings.logs_success_file_downloaded + ); + } else { + Alertify.error( + this.strings.logs_error_problem_encountered + ); + document.querySelector( + "#downloadButtonMoodle" + ).innerHTML = this.strings.logs_valid_Moodlebtn; + document.getElementById( + "downloadButtonMoodle" + ).disabled = false; } - }, - beforeMount() { - document.querySelector("#downloadButtonMoodle").style.display = "none"; - document.querySelector("#downloadButtonNMP").style.display = "none"; - }, - mounted() { - document.querySelector(".v-application--wrap").style.minHeight = "60vh"; - document.querySelector("#sessions-loader").style.display = "none"; - document.querySelector("#helpMoodle").style.display = "block"; - document.querySelector("#helpNMP").style.display = "block"; - document.querySelector("#downloadButtonMoodle").style.display = "block"; - document.querySelector("#downloadButtonNMP").style.display = "block"; - }, - methods: { - get_Moodlefile() { - let lastDate = document.querySelector("#lastDateMoodle"); - let beginDate = document.querySelector("#beginDateMoodle"); - let timestampBeginDate = 0; - let timestampLastDate = 0; - let parsedBeginDate = []; - let parsedLastDate = []; - this.url = false; - this.loading = true; - var data = { - action: "downloadMOODLElogs", - courseid: this.courseid, - userid: this.userid, - beginDate: beginDate.value, - lastDate: lastDate.value, - currentUrl: window.location.href, - } - if (beginDate.value != "" && lastDate.value != "") { - parsedBeginDate = beginDate.value.split("-"); - timestampBeginDate = new Date(parsedBeginDate[0], parsedBeginDate[1] - 1, parsedBeginDate[2]); - parsedLastDate = lastDate.value.split("-"); - timestampLastDate = new Date(parsedLastDate[0], parsedLastDate[1] - 1, parsedLastDate[2]); - if (timestampBeginDate.getTime() <= timestampLastDate.getTime()) { - if (timestampBeginDate.getTime() <= Date.now()) { - document.querySelector('#downloadButtonMoodle').innerHTML = this.strings.logs_download_btn; - document.getElementById('downloadButtonMoodle').disabled = true; - Axios({ - method: 'get', - url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", - timeout: timeout, - params: data, - }).then((response) => { - this.loading = false - if (response.status == 200 && response.data.ok) { - let jsonData = response.data.data.jsonData; - jsonData = jsonData.map(row => ({ - Role: row.user.role, - Email: row.user.email, - Fullname: row.user.firstname + ' ' + row.user.lastname, - Date: row.time.date, - Hour: row.time.hour, - ACTION_VERB: row.action.actionverb, - CourseID: row.course.courseid, - CourseName: row.course.coursename, - OBJECT_ID: row.action.objectid, - OBJECT_NAME: row.action.objectname, - OBJECT_TYPE: row.action.objecttype - })); - let csvData = vue.objectToCSV(jsonData); - vue.downloadCSV(csvData, "Activity_Moodle"); - document.querySelector('#downloadButtonMoodle').innerHTML = this.strings.logs_valid_Moodlebtn; - document.getElementById('downloadButtonMoodle').disabled = false; - Alertify.success(this.strings.logs_success_file_downloaded); - } else { - Alertify.error(this.strings.logs_error_problem_encountered); - document.querySelector('#downloadButtonMoodle').innerHTML = this.strings.logs_valid_Moodlebtn; - document.getElementById('downloadButtonMoodle').disabled = false; - } - }).catch((e) => { - Alertify.error(this.strings.logs_error_problem_encountered); - this.loading = false; - document.querySelector('#downloadButtonMoodle').innerHTML = this.strings.logs_valid_Moodlebtn; - document.getElementById('downloadButtonMoodle').disabled = false; - }).finally(() => { - this.loading = false; - document.querySelector('#downloadButtonMoodle').innerHTML = this.strings.logs_valid_Moodlebtn; - document.getElementById('downloadButtonMoodle').disabled = false; - }); - } else { - Alertify.error(this.strings.logs_error_begin_date_superior); - } - } else { - Alertify.error(this.strings.logs_error_begin_date_inferior); - } - } else { - Alertify.error(this.strings.logs_error_empty_dates); - } - }, - - objectToCSV(jsonData) { - let csvRows = []; + }) + .catch((e) => { + Alertify.error(this.strings.logs_error_problem_encountered); + this.loading = false; + document.querySelector("#downloadButtonMoodle").innerHTML = + this.strings.logs_valid_Moodlebtn; + document.getElementById( + "downloadButtonMoodle" + ).disabled = false; + }) + .finally(() => { + this.loading = false; + document.querySelector("#downloadButtonMoodle").innerHTML = + this.strings.logs_valid_Moodlebtn; + document.getElementById( + "downloadButtonMoodle" + ).disabled = false; + }); + } else { + Alertify.error(this.strings.logs_error_begin_date_superior); + } + } else { + Alertify.error(this.strings.logs_error_begin_date_inferior); + } + } else { + Alertify.error(this.strings.logs_error_empty_dates); + } + }, - let headers = Object.keys(jsonData[0]); - csvRows.push(headers.join(';')); + objectToCSV(jsonData) { + let csvRows = []; - for (const row of jsonData) { - const values = headers.map(header => { - const escaped = ('' + row[header]).replace(/"/g, '\\"'); - return `"${escaped}"`; - }) - csvRows.push(values.join(';')); - } + let headers = Object.keys(jsonData[0]); + csvRows.push(headers.join(";")); - return csvRows.join('\n'); - }, + for (const row of jsonData) { + const values = headers.map((header) => { + const escaped = ("" + row[header]).replace(/"/g, '\\"'); + return `"${escaped}"`; + }); + csvRows.push(values.join(";")); + } - downloadCSV(data, documentName) { - let blob = new Blob([data], { type: 'text/csv' }); - let url = window.URL.createObjectURL(blob); - const a = document.createElement('a'); - a.setAttribute('hidden', ''); - a.setAttribute('href', url); - a.setAttribute('download', documentName + '.csv'); - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - }, + return csvRows.join("\n"); + }, - getRapport() { - Alertify.confirm(this.strings.logs_download_details_description, - () => { - let path = M.cfg.wwwroot + "/local/notemyprogress/downloads/Details_Informations_LogsNMP.pdf"; - var link = document.createElement('a'); - link.href = path; - link.download = "Details_Informations_LogsNMP.pdf"; - link.click(); - Alertify.success(this.strings.logs_download_details_validation); - }).set({ title: this.strings.logs_download_details_title }) - .set({ labels: { cancel: this.strings.logs_download_details_cancel, ok: this.strings.logs_download_details_ok } }); - }, + downloadCSV(data, documentName) { + let blob = new Blob([data], { type: "text/csv" }); + let url = window.URL.createObjectURL(blob); + const a = document.createElement("a"); + a.setAttribute("hidden", ""); + a.setAttribute("href", url); + a.setAttribute("download", documentName + ".csv"); + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + }, - get_NMPfile() { - let lastDate = document.querySelector("#lastDateNMP"); - let beginDate = document.querySelector("#beginDateNMP"); - let timestampBeginDate = 0; - let timestampLastDate = 0; - let parsedBeginDate = []; - let parsedLastDate = []; - this.url = false; - this.loading = true; - var data = { - action: "downloadNMPlogs", - courseid: this.courseid, - userid: this.userid, - beginDate: beginDate.value, - lastDate: lastDate.value, - currentUrl: window.location.href, - } - if (beginDate.value != "" && lastDate.value != "") { - parsedBeginDate = beginDate.value.split("-"); - timestampBeginDate = new Date(parsedBeginDate[0], parsedBeginDate[1] - 1, parsedBeginDate[2]); - parsedLastDate = lastDate.value.split("-"); - timestampLastDate = new Date(parsedLastDate[0], parsedLastDate[1] - 1, parsedLastDate[2]); - if (timestampBeginDate.getTime() <= timestampLastDate.getTime()) { - if (timestampBeginDate.getTime() <= Date.now()) { - document.querySelector('#downloadButtonNMP').innerHTML = this.strings.logs_download_btn; - document.getElementById('downloadButtonNMP').disabled = true; - Axios({ - method: 'get', - url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", - timeout: timeout, - params: data, - }).then((response) => { - this.loading = false - if (response.status == 200 && response.data.ok) { - if (beginDate.value != "" || lastDate.value != "") { - let jsonData = response.data.data.jsonData; - jsonData = jsonData.map(row => ({ - Role: row.user.role, - Email: row.user.email, - Username: row.user.username, - Fullname: row.user.fullname, - Date: row.time.date, - Hour: row.time.hour, - CourseID: row.course.courseid, - SECTION_NAME: row.action.sectionname, - ACTION_TYPE: row.action.actiontype - })); - let csvData = vue.objectToCSV(jsonData); - vue.downloadCSV(csvData, "Activity_NoteMyProgress"); - document.querySelector('#downloadButtonNMP').innerHTML = this.strings.logs_valid_NMPbtn; - document.getElementById('downloadButtonNMP').disabled = false; - Alertify.success(this.strings.logs_success_file_downloaded); - } - } else { - Alertify.error(this.strings.logs_error_problem_encountered); - document.querySelector('#downloadButtonNMP').innerHTML = this.strings.logs_valid_NMPbtn; - document.getElementById('downloadButtonNMP').disabled = false; - } - }).catch((e) => { - Alertify.error(this.strings.logs_error_problem_encountered); - this.loading = false; - document.querySelector('#downloadButtonNMP').innerHTML = this.strings.logs_valid_NMPbtn; - document.getElementById('downloadButtonNMP').disabled = false; - }).finally(() => { - this.loading = false; - document.querySelector('#downloadButtonNMP').innerHTML = this.strings.logs_valid_NMPbtn; - document.getElementById('downloadButtonNMP').disabled = false; - }); - } else { //Si la date de début est supérieure à la date de fin - Alertify.error(this.strings.logs_error_begin_date_superior); - } - } else { //Si la date de début est inférieure à la date du jour - Alertify.error(this.strings.logs_error_begin_date_inferior); - } - } else { //Si les dates ne sont pas remplies - Alertify.error(this.strings.logs_error_empty_dates); - } - }, + getRapport() { + Alertify.confirm( + this.strings.logs_download_details_description, + () => { + let path = + M.cfg.wwwroot + + "/local/notemyprogress/downloads/Details_Informations_LogsNMP.pdf"; + var link = document.createElement("a"); + link.href = path; + link.download = "Details_Informations_LogsNMP.pdf"; + link.click(); + Alertify.success(this.strings.logs_download_details_validation); + } + ) + .set({ title: this.strings.logs_download_details_title }) + .set({ + labels: { + cancel: this.strings.logs_download_details_cancel, + ok: this.strings.logs_download_details_ok, + }, + }); + }, - get_help_content() { - var help_contents = []; - var help = new Object(); - help.title = this.strings.title; - help.description = this.strings.description; - help_contents.push(help); - return help_contents; - }, - - open_chart_help(chart) { - let contents = []; - if (chart == 'download_moodle') { - contents.push({ - title: this.strings.logs_download_moodle_help_title, - description: this.strings.logs_download_moodle_help_description, - }); - } else if (chart == "download_nmp") { - contents.push({ - title: this.strings.logs_download_nmp_help_title, - description: this.strings.logs_download_nmp_help_description, - }); - } - this.help_contents = contents; - if (this.help_contents.length) { - this.help_dialog = true; - } - }, + get_NMPfile() { + let lastDate = document.querySelector("#lastDateNMP"); + let beginDate = document.querySelector("#beginDateNMP"); + let timestampBeginDate = 0; + let timestampLastDate = 0; + let parsedBeginDate = []; + let parsedLastDate = []; + this.url = false; + this.loading = true; + var data = { + action: "downloadNMPlogs", + courseid: this.courseid, + userid: this.userid, + beginDate: beginDate.value, + lastDate: lastDate.value, + currentUrl: window.location.href, + }; + if (beginDate.value != "" && lastDate.value != "") { + parsedBeginDate = beginDate.value.split("-"); + timestampBeginDate = new Date( + parsedBeginDate[0], + parsedBeginDate[1] - 1, + parsedBeginDate[2] + ); + parsedLastDate = lastDate.value.split("-"); + timestampLastDate = new Date( + parsedLastDate[0], + parsedLastDate[1] - 1, + parsedLastDate[2] + ); + if (timestampBeginDate.getTime() <= timestampLastDate.getTime()) { + if (timestampBeginDate.getTime() <= Date.now()) { + document.querySelector("#downloadButtonNMP").innerHTML = + this.strings.logs_download_btn; + document.getElementById("downloadButtonNMP").disabled = true; + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + timeout: timeout, + params: data, + }) + .then((response) => { + this.loading = false; + if (response.status == 200 && response.data.ok) { + if (beginDate.value != "" || lastDate.value != "") { + let jsonData = response.data.data.jsonData; + jsonData = jsonData.map((row) => ({ + Role: row.user.role, + Email: row.user.email, + Username: row.user.username, + Fullname: row.user.fullname, + Date: row.time.date, + Hour: row.time.hour, + CourseID: row.course.courseid, + SECTION_NAME: row.action.sectionname, + ACTION_TYPE: row.action.actiontype, + })); + let csvData = vue.objectToCSV(jsonData); + vue.downloadCSV(csvData, "Activity_NoteMyProgress"); + document.querySelector("#downloadButtonNMP").innerHTML = + this.strings.logs_valid_NMPbtn; + document.getElementById( + "downloadButtonNMP" + ).disabled = false; + Alertify.success( + this.strings.logs_success_file_downloaded + ); + } + } else { + Alertify.error( + this.strings.logs_error_problem_encountered + ); + document.querySelector("#downloadButtonNMP").innerHTML = + this.strings.logs_valid_NMPbtn; + document.getElementById( + "downloadButtonNMP" + ).disabled = false; + } + }) + .catch((e) => { + Alertify.error(this.strings.logs_error_problem_encountered); + this.loading = false; + document.querySelector("#downloadButtonNMP").innerHTML = + this.strings.logs_valid_NMPbtn; + document.getElementById( + "downloadButtonNMP" + ).disabled = false; + }) + .finally(() => { + this.loading = false; + document.querySelector("#downloadButtonNMP").innerHTML = + this.strings.logs_valid_NMPbtn; + document.getElementById( + "downloadButtonNMP" + ).disabled = false; + }); + } else { + //Si la date de début est supérieure à la date de fin + Alertify.error(this.strings.logs_error_begin_date_superior); + } + } else { + //Si la date de début est inférieure à la date du jour + Alertify.error(this.strings.logs_error_begin_date_inferior); + } + } else { + //Si les dates ne sont pas remplies + Alertify.error(this.strings.logs_error_empty_dates); + } + }, - update_help_dialog(value) { - this.help_dialog = value; - }, + get_help_content() { + var help_contents = []; + var help = new Object(); + help.title = this.strings.title; + help.description = this.strings.description; + help_contents.push(help); + return help_contents; + }, - get_timezone() { - let information = `${this.strings.ss_change_timezone} ${this.timezone}` - return information; - }, - } - }) - } + open_chart_help(chart) { + let contents = []; + if (chart == "download_moodle") { + contents.push({ + title: this.strings.logs_download_moodle_help_title, + description: this.strings.logs_download_moodle_help_description, + }); + } else if (chart == "download_nmp") { + contents.push({ + title: this.strings.logs_download_nmp_help_title, + description: this.strings.logs_download_nmp_help_description, + }); + } + this.help_contents = contents; + if (this.help_contents.length) { + this.help_dialog = true; + } + }, - return { - init: init - }; - }); \ No newline at end of file + update_help_dialog(value) { + this.help_dialog = value; + }, + get_timezone() { + let information = `${this.strings.ss_change_timezone} ${this.timezone}`; + return information; + }, + }, + }); + } + return { + init: init, + }; +}); diff --git a/notemyprogress/amd/src/sessions.js b/notemyprogress/amd/src/sessions.js index 14d0d7990d3de6af94f026f4cfbbcb9dda6c3931..8d9df751b9905c8312ec27ab41c406ebc347749a 100644 --- a/notemyprogress/amd/src/sessions.js +++ b/notemyprogress/amd/src/sessions.js @@ -1,388 +1,438 @@ -define(["local_notemyprogress/vue", - "local_notemyprogress/vuetify", - "local_notemyprogress/axios", - "local_notemyprogress/moment", - "local_notemyprogress/pagination", - "local_notemyprogress/chartstatic", - "local_notemyprogress/pageheader", - "local_notemyprogress/helpdialog", - ], - function(Vue, Vuetify, Axios, Moment, Pagination, ChartStatic, PageHeader, HelpDialog) { - "use strict"; +define([ + "local_notemyprogress/vue", + "local_notemyprogress/vuetify", + "local_notemyprogress/axios", + "local_notemyprogress/moment", + "local_notemyprogress/pagination", + "local_notemyprogress/chartstatic", + "local_notemyprogress/pageheader", + "local_notemyprogress/helpdialog", +], function ( + Vue, + Vuetify, + Axios, + Moment, + Pagination, + ChartStatic, + PageHeader, + HelpDialog +) { + "use strict"; - function init(content) { - // console.log(content); - Vue.use(Vuetify); - Vue.component('pagination', Pagination); - Vue.component('chart', ChartStatic); - Vue.component('pageheader', PageHeader); - Vue.component('helpdialog', HelpDialog); - let vue = new Vue({ - delimiters: ["[[", "]]"], - el: "#work_sessions", - vuetify: new Vuetify(), - data() { - return { - strings : content.strings, - groups : content.groups, - userid : content.userid, - courseid : content.courseid, - timezone : content.timezone, - render_has : content.profile_render, - loading : false, - errors : [], + function init(content) { + // console.log(content); + Vue.use(Vuetify); + Vue.component("pagination", Pagination); + Vue.component("chart", ChartStatic); + Vue.component("pageheader", PageHeader); + Vue.component("helpdialog", HelpDialog); + let vue = new Vue({ + delimiters: ["[[", "]]"], + el: "#work_sessions", + vuetify: new Vuetify(), + data() { + return { + strings: content.strings, + groups: content.groups, + userid: content.userid, + courseid: content.courseid, + timezone: content.timezone, + render_has: content.profile_render, + loading: false, + errors: [], - pages : content.pages, - hours_sessions: content.indicators.sessions, - session_count: content.indicators.count, - inverted_time: content.indicators.time, - inverted_time_colors: content.inverted_time_colors, - sessions_count_colors: content.sessions_count_colors, + pages: content.pages, + hours_sessions: content.indicators.sessions, + session_count: content.indicators.count, + inverted_time: content.indicators.time, + inverted_time_colors: content.inverted_time_colors, + sessions_count_colors: content.sessions_count_colors, - search: null, + search: null, - help_dialog: false, - help_contents: [], - } - }, - mounted(){ - document.querySelector("#sessions-loader").style.display = "none"; - document.querySelector("#work_sessions").style.display = "block"; - setTimeout(function() { - vue.setGraphicsEventListeners(); - }, 500); - }, - methods : { - get_help_content(){ - let contents = []; - contents.push({ - title: this.strings.section_help_title, - description: this.strings.section_help_description, - }); - return contents; - }, + help_dialog: false, + help_contents: [], + }; + }, + mounted() { + document.querySelector("#sessions-loader").style.display = "none"; + document.querySelector("#work_sessions").style.display = "block"; + setTimeout(function () { + vue.setGraphicsEventListeners(); + }, 500); + }, + methods: { + get_help_content() { + let contents = []; + contents.push({ + title: this.strings.section_help_title, + description: this.strings.section_help_description, + }); + return contents; + }, - update_interactions(week){ - this.loading = true; - this.errors = []; - let data = { - action : "worksessions", - userid : this.userid, - courseid : this.courseid, - weekcode : week.weekcode, - profile : this.render_has, - } - Axios({ - method:'get', - url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", - params : data, - }).then((response) => { - if (response.status == 200 && response.data.ok) { - this.hours_sessions = response.data.data.indicators.sessions; - this.session_count = response.data.data.indicators.count; - this.inverted_time = response.data.data.indicators.time; - } else { - this.error_messages.push(this.strings.error_network); - } - }).catch((e) => { - this.errors.push(this.strings.api_error_network); - }).finally(() => { - this.loading = false; - //Ici, la page a fini de charger - vue.addLogsIntoDB("viewed", "week_"+week.weekcode, "week_section", "Week section that allows you to obtain information on a specific week"); - vue.setGraphicsEventListeners(); - }); - return this.data; - }, + update_interactions(week) { + this.loading = true; + this.errors = []; + let data = { + action: "worksessions", + userid: this.userid, + courseid: this.courseid, + weekcode: week.weekcode, + profile: this.render_has, + }; + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + params: data, + }) + .then((response) => { + if (response.status == 200 && response.data.ok) { + this.hours_sessions = response.data.data.indicators.sessions; + this.session_count = response.data.data.indicators.count; + this.inverted_time = response.data.data.indicators.time; + } else { + this.error_messages.push(this.strings.error_network); + } + }) + .catch((e) => { + this.errors.push(this.strings.api_error_network); + }) + .finally(() => { + this.loading = false; + //Ici, la page a fini de charger + vue.addLogsIntoDB( + "viewed", + "week_" + week.weekcode, + "week_section", + "Week section that allows you to obtain information on a specific week" + ); + vue.setGraphicsEventListeners(); + }); + return this.data; + }, - get_point_category_name(point, dimension) { - let series = point.series, - isY = dimension === 'y', - axis = series[isY ? 'yAxis' : 'xAxis']; - return axis.categories[point[isY ? 'y' : 'x']]; - }, + get_point_category_name(point, dimension) { + let series = point.series, + isY = dimension === "y", + axis = series[isY ? "yAxis" : "xAxis"]; + return axis.categories[point[isY ? "y" : "x"]]; + }, - build_hours_sessions_chart() { - let chart = new Object(); - chart.title = { - text: null, - }; - chart.chart = { - type: 'heatmap', - backgroundColor: null, - style: {fontFamily: 'poppins'}, - }; - chart.xAxis = { - categories: this.strings.days, - }; - chart.yAxis = { - categories: this.strings.hours, - title: null, - reversed: true, - }; - chart.colorAxis = { - min: 0, - minColor: '#E0E0E0', - maxColor: '#118AB2' - }; - chart.legend = { - layout: 'horizontal', - verticalAlign: 'bottom', - }; - chart.tooltip = { - formatter: function () { - let xCategoryName = vue.get_point_category_name(this.point, 'x'); - let yCategoryName = vue.get_point_category_name(this.point, 'y'); - let label = vue.strings.sessions_text; - if (this.point.value == 1) { - label = vue.strings.session_text; - } - return '<b>' + xCategoryName + ' ' + yCategoryName + '</b>: ' - + this.point.value +' ' + label; - } - }; - chart.series = [{ - borderWidth: 2, - borderColor: '#FAFAFA', - data: this.hours_sessions, - }]; - return chart; - }, + build_hours_sessions_chart() { + let chart = new Object(); + chart.title = { + text: null, + }; + chart.chart = { + type: "heatmap", + backgroundColor: null, + style: { fontFamily: "poppins" }, + }; + chart.xAxis = { + categories: this.strings.days, + }; + chart.yAxis = { + categories: this.strings.hours, + title: null, + reversed: true, + }; + chart.colorAxis = { + min: 0, + minColor: "#E0E0E0", + maxColor: "#118AB2", + }; + chart.legend = { + layout: "horizontal", + verticalAlign: "bottom", + }; + chart.tooltip = { + formatter: function () { + let xCategoryName = vue.get_point_category_name(this.point, "x"); + let yCategoryName = vue.get_point_category_name(this.point, "y"); + let label = vue.strings.sessions_text; + if (this.point.value == 1) { + label = vue.strings.session_text; + } + return ( + "<b>" + + xCategoryName + + " " + + yCategoryName + + "</b>: " + + this.point.value + + " " + + label + ); + }, + }; + chart.series = [ + { + borderWidth: 2, + borderColor: "#FAFAFA", + data: this.hours_sessions, + }, + ]; + return chart; + }, - build_inverted_time_chart() { - let chart = new Object(); - chart.chart = { - type: 'bar', - backgroundColor: null, - style: {fontFamily: 'poppins'}, - }; - chart.title = { - text: null, - }; - chart.colors = this.inverted_time_colors; - chart.xAxis = { - type: 'category', - crosshair: true, - }; - chart.yAxis = { - title: { - text: this.strings.time_inverted_x_axis, - } - }; - chart.tooltip = { - shared:true, - useHTML:true, - formatter: function () { - let category_name = this.points[0].key; - let time = vue.convert_time(this.y); - return `<b>${category_name}: </b>${time}`; - } - }; - chart.legend = { - enabled: false - }; - chart.series = [{ - colorByPoint: true, - data: this.inverted_time.data - }]; - return chart; - }, + build_inverted_time_chart() { + let chart = new Object(); + chart.chart = { + type: "bar", + backgroundColor: null, + style: { fontFamily: "poppins" }, + }; + chart.title = { + text: null, + }; + chart.colors = this.inverted_time_colors; + chart.xAxis = { + type: "category", + crosshair: true, + }; + chart.yAxis = { + title: { + text: this.strings.time_inverted_x_axis, + }, + }; + chart.tooltip = { + shared: true, + useHTML: true, + formatter: function () { + let category_name = this.points[0].key; + let time = vue.convert_time(this.y); + return `<b>${category_name}: </b>${time}`; + }, + }; + chart.legend = { + enabled: false, + }; + chart.series = [ + { + colorByPoint: true, + data: this.inverted_time.data, + }, + ]; + return chart; + }, - build_sessions_count_chart() { - let chart = new Object(); - chart.chart = { - backgroundColor: null, - style: {fontFamily: 'poppins'}, - }; - chart.title = { - text: null, - }; - chart.colors = this.sessions_count_colors; - chart.yAxis = { - title: { - text: this.strings.session_count_yaxis_title, - }, - allowDecimals: false - }; - chart.xAxis = { - categories: this.session_count.categories, - }; - chart.tooltip = { - valueSuffix: this.strings.session_count_tooltip_suffix, - }; - chart.legend = { - layout: 'horizontal', - verticalAlign: 'bottom', - }; - chart.series = this.session_count.data - return chart; - }, + build_sessions_count_chart() { + let chart = new Object(); + chart.chart = { + backgroundColor: null, + style: { fontFamily: "poppins" }, + }; + chart.title = { + text: null, + }; + chart.colors = this.sessions_count_colors; + chart.yAxis = { + title: { + text: this.strings.session_count_yaxis_title, + }, + allowDecimals: false, + }; + chart.xAxis = { + categories: this.session_count.categories, + }; + chart.tooltip = { + valueSuffix: this.strings.session_count_tooltip_suffix, + }; + chart.legend = { + layout: "horizontal", + verticalAlign: "bottom", + }; + chart.series = this.session_count.data; + return chart; + }, - convert_time(time) { - time *= 3600; // pasar las horas a segundos - let h = this.strings.hours_short; - let m = this.strings.minutes_short; - let s = this.strings.seconds_short; - let hours = Math.floor(time / 3600); - let minutes = Math.floor((time % 3600) / 60); - let seconds = Math.floor(time % 60); - let text; - if (hours >= 1) { - if (minutes >= 1) { - text = `${hours}${h} ${minutes}${m}`; - } else { - text = `${hours}${h}`; - } - } else if ((minutes >= 1)) { - if (seconds >= 1) { - text = `${minutes}${m} ${seconds}${s}`; - } else { - text = `${minutes}${m}`; - } - } else { - text = `${seconds}${s}`; - } - return text; - }, + convert_time(time) { + time *= 3600; // pasar las horas a segundos + let h = this.strings.hours_short; + let m = this.strings.minutes_short; + let s = this.strings.seconds_short; + let hours = Math.floor(time / 3600); + let minutes = Math.floor((time % 3600) / 60); + let seconds = Math.floor(time % 60); + let text; + if (hours >= 1) { + if (minutes >= 1) { + text = `${hours}${h} ${minutes}${m}`; + } else { + text = `${hours}${h}`; + } + } else if (minutes >= 1) { + if (seconds >= 1) { + text = `${minutes}${m} ${seconds}${s}`; + } else { + text = `${minutes}${m}`; + } + } else { + text = `${seconds}${s}`; + } + return text; + }, - open_chart_help(chart) { - let contents = []; - var action = ""; - var objectName = ""; - var objectType = ""; - var objectDescription = ""; - if (chart == "inverted_time") { - contents.push({ - title: this.strings.inverted_time_help_title, - description: this.strings.inverted_time_help_description_p1, - }); - contents.push({ - description: this.strings.inverted_time_help_description_p2, - }); - action = "viewed"; - objectType = "help"; - objectName = "invested_time"; - objectDescription = "Help section that provides information about the sessions per week chart"; - vue.addLogsIntoDB(action, objectName, objectType, objectDescription); - } else if (chart == "hours_sessions") { - contents.push({ - title: this.strings.hours_sessions_help_title, - description: this.strings.hours_sessions_help_description_p1, - }); - contents.push({ - description: this.strings.hours_sessions_help_description_p2, - }); - action = "viewed"; - objectType = "help"; - objectName = "hours_sessions"; - objectDescription = "Help section that provides information about the sessions per hour chart"; - vue.addLogsIntoDB(action, objectName, objectType, objectDescription); - } else if (chart == "sessions_count") { - contents.push({ - title: this.strings.sessions_count_help_title, - description: this.strings.sessions_count_help_description_p1, - }); - contents.push({ - description: this.strings.sessions_count_help_description_p2, - }); - action = "viewed"; - objectType = "help"; - objectName = "sessions_count"; - objectDescription = "Help section that provides information about the invested time chart"; - vue.addLogsIntoDB(action, objectName, objectType, objectDescription); - } - this.help_contents = contents; - if (this.help_contents.length) { - this.help_dialog = true; - } - }, + open_chart_help(chart) { + let contents = []; + var action = ""; + var objectName = ""; + var objectType = ""; + var objectDescription = ""; + if (chart == "inverted_time") { + contents.push({ + title: this.strings.inverted_time_help_title, + description: this.strings.inverted_time_help_description_p1, + }); + contents.push({ + description: this.strings.inverted_time_help_description_p2, + }); + action = "viewed"; + objectType = "help"; + objectName = "invested_time"; + objectDescription = + "Help section that provides information about the sessions per week chart"; + vue.addLogsIntoDB( + action, + objectName, + objectType, + objectDescription + ); + } else if (chart == "hours_sessions") { + contents.push({ + title: this.strings.hours_sessions_help_title, + description: this.strings.hours_sessions_help_description_p1, + }); + contents.push({ + description: this.strings.hours_sessions_help_description_p2, + }); + action = "viewed"; + objectType = "help"; + objectName = "hours_sessions"; + objectDescription = + "Help section that provides information about the sessions per hour chart"; + vue.addLogsIntoDB( + action, + objectName, + objectType, + objectDescription + ); + } else if (chart == "sessions_count") { + contents.push({ + title: this.strings.sessions_count_help_title, + description: this.strings.sessions_count_help_description_p1, + }); + contents.push({ + description: this.strings.sessions_count_help_description_p2, + }); + action = "viewed"; + objectType = "help"; + objectName = "sessions_count"; + objectDescription = + "Help section that provides information about the invested time chart"; + vue.addLogsIntoDB( + action, + objectName, + objectType, + objectDescription + ); + } + this.help_contents = contents; + if (this.help_contents.length) { + this.help_dialog = true; + } + }, - update_help_dialog (value) { - this.help_dialog = value; - }, + update_help_dialog(value) { + this.help_dialog = value; + }, - get_timezone(){ - let information = `${this.strings.ss_change_timezone} ${this.timezone}` - return information; - }, + get_timezone() { + let information = `${this.strings.ss_change_timezone} ${this.timezone}`; + return information; + }, - setGraphicsEventListeners() { - let graphics = document.querySelectorAll('.highcharts-container'); - if(graphics.length<1) { - setTimeout(vue.setGraphicsEventListeners,500); - } else { - graphics[0].id="investedTime"; - graphics[1].id="sessionsPerHour"; - graphics[2].id="sessionsPerWeek"; - graphics.forEach((graph) => { - graph.addEventListener('mouseenter', vue.addLogsViewGraphic); - }) - } - }, + setGraphicsEventListeners() { + let graphics = document.querySelectorAll(".highcharts-container"); + if (graphics.length < 1) { + setTimeout(vue.setGraphicsEventListeners, 500); + } else { + graphics[0].id = "investedTime"; + graphics[1].id = "sessionsPerHour"; + graphics[2].id = "sessionsPerWeek"; + graphics.forEach((graph) => { + graph.addEventListener("mouseenter", vue.addLogsViewGraphic); + }); + } + }, - addLogsViewGraphic(e) { - event.stopPropagation(); - var action = ""; - var objectName = ""; - var objectType = ""; - var objectDescription = ""; - switch(e.target.id) { - case "investedTime": - action = "viewed"; - objectName = "invested_time"; - objectType = "chart"; - objectDescription = "Bar chart that shows the average time invested by students as a function of the expected invested time"; - break; - case "sessionsPerHour": - action = "viewed"; - objectName = "hours_sessions"; - objectType = "chart"; - objectDescription = "Chart showing the number of sessions performed according to the time of day"; - break; - case "sessionsPerWeek": - action = "viewed"; - objectName = "sessions_count"; - objectType = "chart"; - objectDescription = "Chart showing the number of sessions performed per week"; - break; - default: - action = "viewed"; - objectName = ""; - objectType = "chart"; - objectDescription = "A chart"; - break; - } - vue.addLogsIntoDB(action, objectName, objectType, objectDescription); - }, + addLogsViewGraphic(e) { + event.stopPropagation(); + var action = ""; + var objectName = ""; + var objectType = ""; + var objectDescription = ""; + switch (e.target.id) { + case "investedTime": + action = "viewed"; + objectName = "invested_time"; + objectType = "chart"; + objectDescription = + "Bar chart that shows the average time invested by students as a function of the expected invested time"; + break; + case "sessionsPerHour": + action = "viewed"; + objectName = "hours_sessions"; + objectType = "chart"; + objectDescription = + "Chart showing the number of sessions performed according to the time of day"; + break; + case "sessionsPerWeek": + action = "viewed"; + objectName = "sessions_count"; + objectType = "chart"; + objectDescription = + "Chart showing the number of sessions performed per week"; + break; + default: + action = "viewed"; + objectName = ""; + objectType = "chart"; + objectDescription = "A chart"; + break; + } + vue.addLogsIntoDB(action, objectName, objectType, objectDescription); + }, - addLogsIntoDB(action, objectName, objectType, objectDescription) { - let data = { - courseid: content.courseid, - userid: content.userid, - action: "addLogs", - sectionname: "TEACHER_STUDY_SESSIONS", - actiontype: action, - objectType: objectType, - objectName: objectName, - currentUrl: document.location.href, - objectDescription: objectDescription, - }; - Axios({ - method:'get', - url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", - params : data, - }).then((response) => { - if (response.status == 200 && response.data.ok) { - } - }).catch((e) => { - }); - }, - } + addLogsIntoDB(action, objectName, objectType, objectDescription) { + let data = { + courseid: content.courseid, + userid: content.userid, + action: "addLogs", + sectionname: "TEACHER_STUDY_SESSIONS", + actiontype: action, + objectType: objectType, + objectName: objectName, + currentUrl: document.location.href, + objectDescription: objectDescription, + }; + Axios({ + method: "get", + url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", + params: data, + }) + .then((response) => { + if (response.status == 200 && response.data.ok) { + } }) + .catch((e) => {}); + }, + }, + }); + } - } - - return { - init : init - }; - }); \ No newline at end of file + return { + init: init, + }; +}); diff --git a/notemyprogress/amd/src/teacher.js b/notemyprogress/amd/src/teacher.js deleted file mode 100644 index 516f045979325e9d354104572559ed77df141938..0000000000000000000000000000000000000000 --- a/notemyprogress/amd/src/teacher.js +++ /dev/null @@ -1,268 +0,0 @@ -define(["local_notemyprogress/vue", - "local_notemyprogress/vuetify", - "local_notemyprogress/axios", - "local_notemyprogress/pagination", - "local_notemyprogress/chartstatic", - "local_notemyprogress/pageheader", - "local_notemyprogress/helpdialog", - ], - function(Vue, Vuetify, Axios, Pagination, ChartStatic, PageHeader, HelpDialog) { - "use strict"; - - function init(content) { - // console.log(content); - Vue.use(Vuetify); - Vue.component('pagination', Pagination); - Vue.component('chart', ChartStatic); - Vue.component('pageheader', PageHeader); - Vue.component('helpdialog', HelpDialog); - let vue = new Vue({ - delimiters: ["[[", "]]"], - el: "#teacher", - vuetify: new Vuetify(), - data() { - return { - strings : content.strings, - groups : content.groups, - userid : content.userid, - courseid : content.courseid, - timezone : content.timezone, - render_has : content.profile_render, - - indicators: content.indicators, - week_resources_colors: content.week_resources_colors, - search: null, - week_resources_categories: [], - week_resources_data: [], - - help_dialog: false, - help_contents: [], - } - }, - beforeMount(){ - this.calculate_week_resources(); - }, - mounted(){ - document.querySelector("#sessions-loader").style.display = "none"; - document.querySelector("#teacher").style.display = "block"; - }, - methods : { - get_help_content(){ - let contents = []; - contents.push({ - title: this.strings.section_help_title, - description: this.strings.section_help_description, - }); - return contents; - }, - - get_course_grade(){ - let grade = Number(this.indicators.course.grademax); - return (this.isInt(grade)) ? grade : grade.toFixed(2); - }, - - calculate_week_resources() { - let categories = [], data = []; - let week_name; - this.indicators.weeks.forEach(week => { - week_name = `${week.name} ${(week.position+1)}`; - categories.push(week_name); - data.push(week.cms); - }); - let name = this.capitalizeFirstLetter(this.strings.teacher_indicators_modules); - this.week_resources_categories = categories; - this.week_resources_data = [{ name, data}]; - }, - - build_week_resources_chart() { - let chart = new Object(); - chart.chart = { - type: 'bar', - backgroundColor: null, - style: {fontFamily: 'poppins'}, - }; - chart.title = { - text: null, - }; - chart.colors = this.week_resources_colors; - chart.xAxis = { - categories: this.week_resources_categories - }; - chart.yAxis = { - min: 0, - title: { - text: this.strings.teacher_indicators_week_resources_yaxis_title - } - }; - chart.legend = { - enabled: false - }; - chart.series = this.week_resources_data; - return chart; - }, - - build_weeks_sessions_chart() { - let chart = new Object(); - chart.chart = { - type: 'heatmap', - backgroundColor: null, - style: {fontFamily: 'poppins'}, - }; - chart.title = { - text: null, - }; - chart.xAxis = { - categories: this.strings.weeks, - }; - chart.yAxis = { - categories: this.indicators.sessions.categories, - title: null, - reversed: true, - }; - chart.colorAxis = { - min: 0, - minColor: '#E0E0E0', - maxColor: '#118AB2' - }; - chart.legend = { - layout: 'horizontal', - verticalAlign: 'bottom', - }; - chart.tooltip = { - formatter: function () { - let days = vue.indicators.sessions.weeks[this.point.y][this.point.x] || ''; - let xCategoryName = vue.get_point_category_name(this.point, 'x'); - let yCategoryName = vue.get_point_category_name(this.point, 'y'); - let label = vue.strings.teacher_indicators_sessions; - if (this.point.value == 1) { - label = vue.strings.teacher_indicators_session; - } - return '<b>' + yCategoryName + ' ' + xCategoryName + '</b>: ' - + this.point.value +' ' + label + '<br/>' + days; - } - }; - chart.series = [{ - borderWidth: 2, - borderColor: '#FAFAFA', - data: this.indicators.sessions.data, - }]; - return chart; - }, - - table_headers(){ - let headers = [ - { text: '', value : 'id', align : 'center', sortable : false}, - { text: this.strings.thead_name , value : 'firstname'}, - { text: this.strings.thead_lastname , value : 'lastname'}, - { text: this.strings.thead_email , value : 'email'}, - { text: this.strings.thead_progress , value : 'progress_percentage', align : 'center'}, - { text: this.strings.thead_sessions , value : 'sessions_number', align : 'center'}, - { text: this.strings.thead_time , value : 'inverted_time', align : 'center'}, - ]; - return headers; - }, - - get_picture_url(userid){ - let url = `${M.cfg.wwwroot}/user/pix.php?file=/${userid}/f1.jpg`; - return url; - }, - - get_percentage_progress(value){ - return `${value} %`; - }, - - get_progress_tooltip(item){ - let module_label = this.strings.teacher_indicators_modules; - let finished_label = this.strings.teacher_indicators_finished; - if (item.cms.complete == 1) { - module_label = this.strings.teacher_indicators_module; - finished_label = this.strings.teacher_indicators_finalized; - } - return `${item.cms.complete} ${module_label} ${finished_label} ${this.strings.of_conector} ${item.cms.total}`; - }, - - get_point_category_name(point, dimension) { - let series = point.series, - isY = dimension === 'y', - axis = series[isY ? 'yAxis' : 'xAxis']; - return axis.categories[point[isY ? 'y' : 'x']]; - }, - - capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); - }, - - isInt(n) { - return n % 1 === 0; - }, - - open_chart_help(chart) { - let contents = []; - if (chart == "week_resources") { - contents.push({ - title: this.strings.week_resources_help_title, - description: this.strings.week_resources_help_description_p1, - }); - contents.push({ - description: this.strings.week_resources_help_description_p2, - }); - } else if (chart == "weeks_sessions") { - contents.push({ - title: this.strings.weeks_sessions_help_title, - description: this.strings.week_sessions_help_description_p1, - }); - contents.push({ - description: this.strings.week_sessions_help_description_p2, - }); - } else if (chart == "progress_table") { - contents.push({ - title: this.strings.progress_table_help_title, - description: this.strings.progress_table_help_description, - }); - } - this.help_contents = contents; - if (this.help_contents.length) { - this.help_dialog = true; - } - }, - - update_help_dialog (value) { - this.help_dialog = value; - }, - - get_timezone(){ - let information = `${this.strings.ss_change_timezone} ${this.timezone}` - return information; - }, - - addLogsIntoDB(action, objectName, objectType, objectDescription) { - let data = { - courseid: content.courseid, - userid: content.userid, - action: "addLogs", - sectionname: "TEACHER_GENERAL_INDICATORS", - actiontype: action, - objectType: objectType, - objectName: objectName, - currentUrl: document.location.href, - objectDescription: objectDescription, - }; - Axios({ - method:'get', - url: M.cfg.wwwroot + "/local/notemyprogress/ajax.php", - params : data, - }).then((response) => { - if (response.status == 200 && response.data.ok) { - } - }).catch((e) => { - }); - }, - - } - }) - } - - return { - init : init - }; - }); \ No newline at end of file diff --git a/notemyprogress/classes/configgamification.php b/notemyprogress/classes/configgamification.php index 0a4f947f7ea560db8d1b18b4322ca10f727f5ffc..3fb9d502f2dcd001c6e220e6eddce06380f743c5 100644 --- a/notemyprogress/classes/configgamification.php +++ b/notemyprogress/classes/configgamification.php @@ -115,7 +115,12 @@ class configgamification { $levelsData->levelsdata = json_decode($levelsData->levelsdata); $levelsData->settings = json_decode($levelsData->settings); $levelsData->rules = json_decode($levelsData->rules); - } + + + + } + $sql = "SELECT enablegamification from {notemyprogress_gamification} where courseid=? AND timecreated=? "; + $value = $DB->get_record_sql($sql, array("courseid="=>($courseid),"timecreated="=>$timecrated->maximum)); return $levelsData; } @@ -193,19 +198,6 @@ class configgamification { $list = []; $coreevents = $this->get_core_events(); -//! $list[] = [get_string('coresystem') => array_reduce(array_keys($coreevents), function($carry, $prefix) use ($coreevents) { -//! return array_merge($carry, array_reduce($coreevents[$prefix], function($carry, $eventclass) use ($prefix) { -//! $infos = self::get_event_infos($eventclass); -//! if ($infos) { -//! $carry[$infos['eventname']] = get_string('tg_colon', 'local_notemyprogress', (object) [ -//! 'a' => $prefix, -// ! 'b' => $infos['name'] -// ! ]); -// ! } -// ! return $carry; -// ! }, [])); -// ! }, [])]; - $coreevents = [get_string('coresystem') => array_reduce(array_keys($coreevents), function($carry, $prefix) use ($coreevents) { return array_merge($carry, array_reduce($coreevents[$prefix], function($carry, $eventclass) use ($prefix) { $infos = self::get_event_infos($eventclass); @@ -393,4 +385,25 @@ class configgamification { } return $eventclasses; } + /** + * Get the data needed to complete the graph. + * + * @param int $courseid the course id. + * @return array an array containing the apropriate data well formated + */ + public static function get_spread_chart($courseid){ + Global $DB; + $resultat = []; + $levelsData = "SELECT MAX(level) as lvl from {notemyprogress_xp} where courseid=?"; + $lvl = $DB->get_record_sql($levelsData, array("courseid="=>($courseid))); + $i = 1; + while($i<=$lvl->lvl){ + $levelCount = "SELECT COUNT(*) as cpt from {notemyprogress_xp} where courseid=? and level=?"; + $cpt = $DB->get_record_sql($levelCount, array("courseid="=>($courseid),"level="=>($i))); + $string = "level {$i} :"; + array_push( $resultat,[$string,intval($cpt->cpt)]); + $i = $i+1; + } + return $resultat; + } } \ No newline at end of file diff --git a/notemyprogress/classes/event_strategy.php b/notemyprogress/classes/event_strategy.php index d7ef122e8bac722b3ad5bce3cc7544881c679880..134b9aee5171eefbb2ab689e68ed484daf01eec2 100644 --- a/notemyprogress/classes/event_strategy.php +++ b/notemyprogress/classes/event_strategy.php @@ -358,11 +358,16 @@ class event_strategy { * * @return users */ - public function get_ranking() { + public function get_ranking($int) { global $DB; $users = array(); - $sql = "SELECT * FROM {{$this->table_xp}} WHERE courseid = {$this->course->id} and rankable = 1 ORDER BY points DESC"; + if ($int==1){ + $sql = "SELECT * FROM {{$this->table_xp}} WHERE courseid = {$this->course->id} and rankable = 1 ORDER BY points DESC"; + }else{ + $sql = "SELECT * FROM {{$this->table_xp}} WHERE courseid = {$this->course->id} ORDER BY points DESC"; + } + $rank = $DB->get_recordset_sql($sql); $index = 1; diff --git a/notemyprogress/gamification.php b/notemyprogress/gamification.php index 4a32d7751800de10ad1b1cceaafc055206835866..392dac98236677db5eaaea684b5bde729fa11283 100644 --- a/notemyprogress/gamification.php +++ b/notemyprogress/gamification.php @@ -23,7 +23,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once('locallib.php'); -global $COURSE, $USER; +global $COURSE, $USER , $DB; $courseid = required_param('courseid', PARAM_INT); $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); @@ -36,14 +36,27 @@ require_capability('local/notemyprogress:usepluggin', $context); require_capability('local/notemyprogress:teacher_gamification', $context); require_capability('local/notemyprogress:setweeks', $context); +$maxTimeSql = "SELECT MAX(timecreated) as maximum from {notemyprogress_gamification} where courseid=? "; +$timecrated = $DB->get_record_sql($maxTimeSql, array("courseid="=>($courseid))); + +$sql = "SELECT enablegamification from {notemyprogress_gamification} where courseid=? AND timecreated=? "; +$value = $DB->get_record_sql($sql, array("courseid="=>($courseid),"timecreated="=>$timecrated->maximum)); +if($value->enablegamification==1){ + $swValue = true; +}else{ + $swValue = false; +} + $actualLink = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]"; $logs = new \local_notemyprogress\logs($COURSE->id, $USER->id); -$logs->addLogsNMP("viewed", "section", "CONFIGURATION_GAMIFICATION", "configuration_gamification", $actualLink, "Section where teacher can configure gamification"); - +$logs->addLogsNMP("viewed", "section", "CONFIGURATION_GAMIFICATION", "configuration_gamification", $actualLink, "Section where teacher can configure gamification"); $configgamification = new \local_notemyprogress\configgamification($COURSE->id, $USER->id); $es = new \local_notemyprogress\event_strategy($COURSE, $USER); +$reports = new \local_notemyprogress\teacher($COURSE->id, $USER->id); + + $content = [ 'strings' =>[ @@ -87,15 +100,26 @@ $content = [ 'add_rule' => get_string("tg_section_settings_add_rule","local_notemyprogress"), 'earn' => get_string("tg_section_settings_earn","local_notemyprogress"), 'select_event' => get_string("tg_section_settings_select_event","local_notemyprogress"), - + 'enable'=>get_string("EnableGame","local_notemyprogress"), + 'swValue'=>$swValue, + 'chart' => $reports->get_chart_langs(), + 'pointError'=>get_string("game_point_error","local_notemyprogress"), + 'eventError'=>get_string("game_event_error","local_notemyprogress"), + 'nameError'=>get_string("game_name_error","local_notemyprogress"), + 'chartTitle'=>get_string("gm_Chart_Title","local_notemyprogress"), + 'chartYaxis'=>get_string("gm_Chart_Y","local_notemyprogress"), ], 'token' => local_notemyprogress_generate_token($COURSE->id, $USER->id, "teacher"), -// 'userid' => $USER->id, -// 'courseid' => $courseid, + 'userid' => $USER->id, + 'courseid' => $courseid, 'levels_data' => $configgamification->get_levels_data(), - 'ranking' => $es->get_ranking(), + 'ranking' => $es->get_ranking(0), 'events' => $configgamification->events_list(), - 'image' => $OUTPUT->image_url('badge', 'local_notemyprogress') + 'image' => $OUTPUT->image_url('badge', 'local_notemyprogress'), + 'indicators' => $reports->get_general_indicators(), + 'profile_render' => $reports->render_has(), + 'timezone' => $reports->timezone, + 'chart_data' => $configgamification->get_spread_chart($COURSE->id), ]; $templatecontext = [ diff --git a/notemyprogress/js/highcharts/highcharts.src.js b/notemyprogress/js/highcharts/highcharts.src.js index d8256d90570499e39c82e91ad80cb7294ee247c8..eab1ec4f806f46453de3965511828507d970445f 100644 --- a/notemyprogress/js/highcharts/highcharts.src.js +++ b/notemyprogress/js/highcharts/highcharts.src.js @@ -5,45166 +5,48241 @@ * * License: www.highcharts.com/license */ -'use strict'; +"use strict"; (function (root, factory) { - if (typeof module === 'object' && module.exports) { - factory['default'] = factory; - module.exports = root.document ? - factory(root) : - factory; - } else if (typeof define === 'function' && define.amd) { - define('highcharts/highcharts', function () { - return factory(root); + if (typeof module === "object" && module.exports) { + factory["default"] = factory; + module.exports = root.document ? factory(root) : factory; + } else if (typeof define === "function" && define.amd) { + define("highcharts/highcharts", function () { + return factory(root); + }); + } else { + if (root.Highcharts) { + root.Highcharts.error(16, true); + } + root.Highcharts = factory(root); + } +})(typeof window !== "undefined" ? window : this, function (win) { + var _modules = {}; + function _registerModule(obj, path, args, fn) { + if (!obj.hasOwnProperty(path)) { + obj[path] = fn.apply(null, args); + } + } + _registerModule(_modules, "Core/Globals.js", [], function () { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /* globals Image, window */ + /** + * Reference to the global SVGElement class as a workaround for a name conflict + * in the Highcharts namespace. + * + * @global + * @typedef {global.SVGElement} GlobalSVGElement + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement + */ + // glob is a temporary fix to allow our es-modules to work. + var glob = // @todo UMD variable named `window`, and glob named `win` + typeof win !== "undefined" + ? win + : typeof window !== "undefined" + ? window + : {}, + 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 = userAgent.indexOf("Firefox") !== -1, + isChrome = userAgent.indexOf("Chrome") !== -1, + hasBidiBug = + isFirefox && parseInt(userAgent.split("Firefox/")[1], 10) < 4; // issue #38 + var H = { + product: "Highcharts", + version: "8.2.2", + deg2rad: (Math.PI * 2) / 360, + doc: doc, + hasBidiBug: hasBidiBug, + hasTouch: !!glob.TouchEvent, + isMS: isMS, + isWebKit: userAgent.indexOf("AppleWebKit") !== -1, + isFirefox: isFirefox, + isChrome: isChrome, + isSafari: !isChrome && userAgent.indexOf("Safari") !== -1, + 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 () {}, + /** + * Theme options that should get applied to the chart. In module mode it + * might not be possible to change this property because of read-only + * restrictions, instead use {@link Highcharts.setOptions}. + * + * @name Highcharts.theme + * @type {Highcharts.Options} + */ + /** + * 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`. + * + * @name Highcharts.charts + * @type {Array<Highcharts.Chart|undefined>} + */ + charts: [], + /** + * 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. + * + * @sample highcharts/global/dateformats/ + * Adding support for week number + * + * @name Highcharts.dateFormats + * @type {Highcharts.Dictionary<Highcharts.TimeFormatCallbackFunction>} + */ + dateFormats: {}, + }; + + return H; + }); + _registerModule( + _modules, + "Core/Utilities.js", + [_modules["Core/Globals.js"]], + function (H) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /** + * An animation configuration. Animation configurations can also be defined as + * booleans, where `false` turns off animation and `true` defaults to a duration + * of 500ms and defer of 0ms. + * + * @interface Highcharts.AnimationOptionsObject + */ /** + * A callback function to exectute when the animation finishes. + * @name Highcharts.AnimationOptionsObject#complete + * @type {Function|undefined} + */ /** + * The animation defer in milliseconds. + * @name Highcharts.AnimationOptionsObject#defer + * @type {number|undefined} + */ /** + * The animation duration in milliseconds. + * @name Highcharts.AnimationOptionsObject#duration + * @type {number|undefined} + */ /** + * The name of an easing function as defined on the `Math` object. + * @name Highcharts.AnimationOptionsObject#easing + * @type {string|Function|undefined} + */ /** + * 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. + * @name Highcharts.AnimationOptionsObject#step + * @type {Function|undefined} + */ + /** + * Creates a frame for the animated SVG element. + * + * @callback Highcharts.AnimationStepCallbackFunction + * + * @param {Highcharts.SVGElement} this + * The SVG element to animate. + * + * @return {void} + */ + /** + * Interface description for a class. + * + * @interface Highcharts.Class<T> + * @extends Function + */ /** + * Class costructor. + * @function Highcharts.Class<T>#new + * @param {...Array<*>} args + * Constructor arguments. + * @return {T} + * Class instance. + */ + /** + * A style object with camel case property names to define visual appearance of + * a SVG element or HTML element. The properties can be whatever styles are + * supported on the given SVG or HTML element. + * + * @example + * { + * fontFamily: 'monospace', + * fontSize: '1.2em' + * } + * + * @interface Highcharts.CSSObject + */ /** + * @name Highcharts.CSSObject#[key:string] + * @type {boolean|number|string|undefined} + */ /** + * Background style for the element. + * @name Highcharts.CSSObject#background + * @type {string|undefined} + */ /** + * Background color of the element. + * @name Highcharts.CSSObject#backgroundColor + * @type {Highcharts.ColorString|undefined} + */ /** + * Border style for the element. + * @name Highcharts.CSSObject#border + * @type {string|undefined} + */ /** + * Radius of the element border. + * @name Highcharts.CSSObject#borderRadius + * @type {number|undefined} + */ /** + * Color used in the element. The 'contrast' option is a Highcharts custom + * property that results in black or white, depending on the background of the + * element. + * @name Highcharts.CSSObject#color + * @type {'contrast'|Highcharts.ColorString|undefined} + */ /** + * Style of the mouse cursor when resting over the element. + * @name Highcharts.CSSObject#cursor + * @type {Highcharts.CursorValue|undefined} + */ /** + * Font family of the element text. Multiple values have to be in decreasing + * preference order and separated by comma. + * @name Highcharts.CSSObject#fontFamily + * @type {string|undefined} + */ /** + * Font size of the element text. + * @name Highcharts.CSSObject#fontSize + * @type {string|undefined} + */ /** + * Font weight of the element text. + * @name Highcharts.CSSObject#fontWeight + * @type {string|undefined} + */ /** + * Height of the element. + * @name Highcharts.CSSObject#height + * @type {number|undefined} + */ /** + * Width of the element border. + * @name Highcharts.CSSObject#lineWidth + * @type {number|undefined} + */ /** + * Opacity of the element. + * @name Highcharts.CSSObject#opacity + * @type {number|undefined} + */ /** + * Space around the element content. + * @name Highcharts.CSSObject#padding + * @type {string|undefined} + */ /** + * Behaviour of the element when the mouse cursor rests over it. + * @name Highcharts.CSSObject#pointerEvents + * @type {string|undefined} + */ /** + * Positioning of the element. + * @name Highcharts.CSSObject#position + * @type {string|undefined} + */ /** + * Alignment of the element text. + * @name Highcharts.CSSObject#textAlign + * @type {string|undefined} + */ /** + * Additional decoration of the element text. + * @name Highcharts.CSSObject#textDecoration + * @type {string|undefined} + */ /** + * Outline style of the element text. + * @name Highcharts.CSSObject#textOutline + * @type {string|undefined} + */ /** + * Line break style of the element text. Highcharts SVG elements support + * `ellipsis` when a `width` is set. + * @name Highcharts.CSSObject#textOverflow + * @type {string|undefined} + */ /** + * Top spacing of the element relative to the parent element. + * @name Highcharts.CSSObject#top + * @type {string|undefined} + */ /** + * Animated transition of selected element properties. + * @name Highcharts.CSSObject#transition + * @type {string|undefined} + */ /** + * Line break style of the element text. + * @name Highcharts.CSSObject#whiteSpace + * @type {string|undefined} + */ /** + * Width of the element. + * @name Highcharts.CSSObject#width + * @type {number|undefined} + */ + /** + * All possible cursor styles. + * + * @typedef {'alias'|'all-scroll'|'auto'|'cell'|'col-resize'|'context-menu'|'copy'|'crosshair'|'default'|'e-resize'|'ew-resize'|'grab'|'grabbing'|'help'|'move'|'n-resize'|'ne-resize'|'nesw-resize'|'no-drop'|'none'|'not-allowed'|'ns-resize'|'nw-resize'|'nwse-resize'|'pointer'|'progress'|'row-resize'|'s-resize'|'se-resize'|'sw-resize'|'text'|'vertical-text'|'w-resize'|'wait'|'zoom-in'|'zoom-out'} Highcharts.CursorValue + */ + /** + * All possible dash styles. + * + * @typedef {'Dash'|'DashDot'|'Dot'|'LongDash'|'LongDashDot'|'LongDashDotDot'|'ShortDash'|'ShortDashDot'|'ShortDashDotDot'|'ShortDot'|'Solid'} Highcharts.DashStyleValue + */ + /** + * Generic dictionary in TypeScript notation. + * Use the native `Record<string, any>` instead. + * + * @deprecated + * @interface Highcharts.Dictionary<T> + */ /** + * @name Highcharts.Dictionary<T>#[key:string] + * @type {T} + */ + /** + * The function callback to execute when the event is fired. The `this` context + * contains the instance, that fired the event. + * + * @callback Highcharts.EventCallbackFunction<T> + * + * @param {T} this + * + * @param {Highcharts.Dictionary<*>|Event} [eventArguments] + * Event arguments. + * + * @return {boolean|void} + */ + /** + * The event options for adding function callback. + * + * @interface Highcharts.EventOptionsObject + */ /** + * The order the event handler should be called. This opens for having one + * handler be called before another, independent of in which order they were + * added. + * @name Highcharts.EventOptionsObject#order + * @type {number} + */ + /** + * Formats data as a string. Usually the data is accessible throught the `this` + * keyword. + * + * @callback Highcharts.FormatterCallbackFunction<T> + * + * @param {T} this + * Context to format + * + * @return {string} + * Formatted text + */ + /** + * An object of key-value pairs for HTML attributes. + * + * @typedef {Highcharts.Dictionary<boolean|number|string|Function>} Highcharts.HTMLAttributes + */ + /** + * An HTML DOM element. The type is a reference to the regular HTMLElement in + * the global scope. + * + * @typedef {global.HTMLElement} Highcharts.HTMLDOMElement + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement + */ + /** + * The iterator callback. + * + * @callback Highcharts.ObjectEachCallbackFunction<T> + * + * @param {T} this + * The context. + * + * @param {*} value + * The property value. + * + * @param {string} key + * The property key. + * + * @param {*} obj + * The object that objectEach is being applied to. + */ + /** + * An object containing `left` and `top` properties for the position in the + * page. + * + * @interface Highcharts.OffsetObject + */ /** + * Left distance to the page border. + * @name Highcharts.OffsetObject#left + * @type {number} + */ /** + * Top distance to the page border. + * @name Highcharts.OffsetObject#top + * @type {number} + */ + /** + * Describes a range. + * + * @interface Highcharts.RangeObject + */ /** + * Maximum number of the range. + * @name Highcharts.RangeObject#max + * @type {number} + */ /** + * Minimum number of the range. + * @name Highcharts.RangeObject#min + * @type {number} + */ + /** + * 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. + * + * @typedef {number|string} Highcharts.RelativeSize + */ + /** + * Proceed function to call original (wrapped) function. + * + * @callback Highcharts.WrapProceedFunction + * + * @param {*} [arg1] + * Optional argument. Without any arguments defaults to first argument of + * the wrapping function. + * + * @param {*} [arg2] + * Optional argument. Without any arguments defaults to second argument + * of the wrapping function. + * + * @param {*} [arg3] + * Optional argument. Without any arguments defaults to third argument of + * the wrapping function. + * + * @return {*} + * Return value of the original function. + */ + /** + * 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. + * + * @sample highcharts/chart/highcharts-error/ + * Custom error handler + * + * @function Highcharts.error + * + * @param {number|string} code + * The error code. See + * [errors.xml](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. + * + * @param {Highcharts.Chart} [chart] + * Reference to the chart that causes the error. Used in 'debugger' + * module to display errors directly on the chart. + * Important note: This argument is undefined for errors that lack + * access to the Chart instance. + * + * @param {Highcharts.Dictionary<string>} [params] + * Additional parameters for the generated message. + * + * @return {void} + */ + function error(code, stop, chart, params) { + var severity = stop ? "Highcharts error" : "Highcharts warning"; + if (code === 32) { + code = severity + ": Deprecated member"; + } + var isCode = isNumber(code), + message = isCode + ? severity + + " #" + + code + + ": www.highcharts.com/errors/" + + code + + "/" + : code.toString(), + defaultHandler = function () { + if (stop) { + throw new Error(message); + } + // else ... + if ( + win.console && + error.messages.indexOf(message) === -1 // prevent console flooting + ) { + console.log(message); // eslint-disable-line no-console + } + }; + if (typeof params !== "undefined") { + var additionalMessages_1 = ""; + if (isCode) { + message += "?"; + } + objectEach(params, function (value, key) { + additionalMessages_1 += "\n - " + key + ": " + value; + if (isCode) { + message += encodeURI(key) + "=" + encodeURI(value); + } + }); + message += additionalMessages_1; + } + if (chart) { + fireEvent( + chart, + "displayError", + { code: code, message: message, params: params }, + defaultHandler + ); + } else { + defaultHandler(); + } + error.messages.push(message); + } + (function (error) { + error.messages = []; + })(error || (error = {})); + H.error = error; + /* eslint-disable valid-jsdoc */ + /** + * 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 Highcharts.merge<T> + * + * @param {boolean} extend + * Whether to extend the left-side object (a) or return a whole new + * object. + * + * @param {T|undefined} a + * The first object to extend. When only this is given, the function + * returns a deep copy. + * + * @param {...Array<object|undefined>} [n] + * An object to merge into the previous one. + * + * @return {T} + * The merged object. If the first argument is true, the return is the + * same as the second argument. + */ /** + * Utility function to deep merge two or more objects and return a third object. + * The merge function can also be used with a single object argument to create a + * deep copy of an object. + * + * @function Highcharts.merge<T> + * + * @param {T|undefined} a + * The first object to extend. When only this is given, the function + * returns a deep copy. + * + * @param {...Array<object|undefined>} [n] + * An object to merge into the previous one. + * + * @return {T} + * The merged object. If the first argument is true, the return is the + * same as the second argument. + */ + function merge() { + /* eslint-enable valid-jsdoc */ + var i, + args = arguments, + len, + ret = {}, + doCopy = function (copy, original) { + // An object is replacing a primitive + if (typeof copy !== "object") { + copy = {}; + } + objectEach(original, function (value, key) { + // Copy the contents of objects, but not arrays or DOM nodes + if ( + isObject(value, true) && + !isClass(value) && + !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; + } + H.merge = merge; + /** + * Constrain a value to within a lower and upper threshold. + * + * @private + * @param {number} value The initial value + * @param {number} min The lower threshold + * @param {number} max The upper threshold + * @return {number} Returns a number value within min and max. + */ + function clamp(value, min, max) { + return value > min ? (value < max ? value : max) : min; + } + /** + * Shortcut for parseInt + * + * @private + * @function Highcharts.pInt + * + * @param {*} s + * any + * + * @param {number} [mag] + * Magnitude + * + * @return {number} + * number + */ + var pInt = (H.pInt = function pInt(s, mag) { + return parseInt(s, mag || 10); + }); + /** + * Utility function to check for string type. + * + * @function Highcharts.isString + * + * @param {*} s + * The item to check. + * + * @return {boolean} + * True if the argument is a string. + */ + var isString = (H.isString = function isString(s) { + return typeof s === "string"; + }); + /** + * Utility function to check if an item is an array. + * + * @function Highcharts.isArray + * + * @param {*} obj + * The item to check. + * + * @return {boolean} + * True if the argument is an array. + */ + var isArray = (H.isArray = function isArray(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 Highcharts.isObject + * + * @param {*} obj + * The item to check. + * + * @param {boolean} [strict=false] + * Also checks that the object is not an array. + * + * @return {boolean} + * True if the argument is an object. + */ + function isObject(obj, strict) { + return !!obj && typeof obj === "object" && (!strict || !isArray(obj)); // eslint-disable-line @typescript-eslint/no-explicit-any + } + H.isObject = isObject; + /** + * Utility function to check if an Object is a HTML Element. + * + * @function Highcharts.isDOMElement + * + * @param {*} obj + * The item to check. + * + * @return {boolean} + * True if the argument is a HTML Element. + */ + var isDOMElement = (H.isDOMElement = function isDOMElement(obj) { + return isObject(obj) && typeof obj.nodeType === "number"; + }); + /** + * Utility function to check if an Object is a class. + * + * @function Highcharts.isClass + * + * @param {object|undefined} obj + * The item to check. + * + * @return {boolean} + * True if the argument is a class. + */ + var isClass = (H.isClass = function isClass(obj) { + var c = obj && obj.constructor; + return !!( + isObject(obj, true) && + !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 Highcharts.isNumber + * + * @param {*} n + * The item to check. + * + * @return {boolean} + * True if the item is a finite number + */ + var isNumber = (H.isNumber = function isNumber(n) { + return ( + typeof n === "number" && !isNaN(n) && n < Infinity && n > -Infinity + ); + }); + /** + * Remove the last occurence of an item from an array. + * + * @function Highcharts.erase + * + * @param {Array<*>} arr + * The array. + * + * @param {*} item + * The item to remove. + * + * @return {void} + */ + var erase = (H.erase = function erase(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 Highcharts.defined + * + * @param {*} obj + * The object to check. + * + * @return {boolean} + * False if the object is null or undefined, otherwise true. + */ + var defined = (H.defined = function defined(obj) { + return typeof 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 Highcharts.attr + * + * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} elem + * The DOM element to receive the attribute(s). + * + * @param {string|Highcharts.HTMLAttributes|Highcharts.SVGAttributes} [prop] + * The property or an object of key-value pairs. + * + * @param {number|string} [value] + * The value if a single property is set. + * + * @return {string|null|undefined} + * When used as a getter, return the value. + */ + function attr(elem, prop, value) { + var ret; + // if the prop is a string + if (isString(prop)) { + // set the value + if (defined(value)) { + elem.setAttribute(prop, value); + // get the value + } else if (elem && elem.getAttribute) { + ret = elem.getAttribute(prop); + // IE7 and below cannot get class through getAttribute (#7850) + if (!ret && prop === "class") { + ret = elem.getAttribute(prop + "Name"); + } + } + // else if prop is defined, it is a hash of key/value pairs + } else { + objectEach(prop, function (val, key) { + elem.setAttribute(key, val); + }); + } + return ret; + } + H.attr = attr; + /** + * Check if an element is an array, and if not, make it into an array. + * + * @function Highcharts.splat + * + * @param {*} obj + * The object to splat. + * + * @return {Array} + * The produced or original array. + */ + var splat = (H.splat = function splat(obj) { + return isArray(obj) ? obj : [obj]; + }); + /** + * Set a timeout if the delay is given, otherwise perform the function + * synchronously. + * + * @function Highcharts.syncTimeout + * + * @param {Function} fn + * The function callback. + * + * @param {number} delay + * Delay in milliseconds. + * + * @param {*} [context] + * An optional context to send to the function callback. + * + * @return {number} + * An identifier for the timeout that can later be cleared with + * Highcharts.clearTimeout. Returns -1 if there is no timeout. + */ + var syncTimeout = (H.syncTimeout = function syncTimeout( + fn, + delay, + context + ) { + if (delay > 0) { + return setTimeout(fn, delay, context); + } + fn.call(0, context); + return -1; + }); + /** + * Internal clear timeout. The function checks that the `id` was not removed + * (e.g. by `chart.destroy()`). For the details see + * [issue #7901](https://github.com/highcharts/highcharts/issues/7901). + * + * @function Highcharts.clearTimeout + * + * @param {number} id + * Id of a timeout. + * + * @return {void} + */ + var internalClearTimeout = (H.clearTimeout = function (id) { + if (defined(id)) { + clearTimeout(id); + } + }); + /* eslint-disable valid-jsdoc */ + /** + * Utility function to extend an object with the members of another. + * + * @function Highcharts.extend<T> + * + * @param {T|undefined} a + * The object to be extended. + * + * @param {object} b + * The object to add to the first one. + * + * @return {T} + * Object a, the original object. + */ + var extend = (H.extend = function extend(a, b) { + /* eslint-enable valid-jsdoc */ + var n; + if (!a) { + a = {}; + } + for (n in b) { + // eslint-disable-line guard-for-in + a[n] = b[n]; + } + return a; + }); + /* eslint-disable valid-jsdoc */ + /** + * Return the first value that is not null or undefined. + * + * @function Highcharts.pick<T> + * + * @param {...Array<T|null|undefined>} items + * Variable number of arguments to inspect. + * + * @return {T} + * The value of the first argument that is not null or undefined. + */ + function pick() { + var args = arguments; + var length = args.length; + for (var i = 0; i < length; i++) { + var arg = args[i]; + if (typeof arg !== "undefined" && arg !== null) { + return arg; + } + } + } + H.pick = pick; + /** + * Set CSS on a given element. + * + * @function Highcharts.css + * + * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} el + * An HTML DOM element. + * + * @param {Highcharts.CSSObject} styles + * Style object with camel case property names. + * + * @return {void} + */ + var css = (H.css = function css(el, styles) { + if (H.isMS && !H.svg) { + // #2686 + if (styles && typeof styles.opacity !== "undefined") { + styles.filter = "alpha(opacity=" + styles.opacity * 100 + ")"; + } + } + extend(el.style, styles); + }); + /** + * Utility function to create an HTML element with attributes and styles. + * + * @function Highcharts.createElement + * + * @param {string} tag + * The HTML tag. + * + * @param {Highcharts.HTMLAttributes} [attribs] + * Attributes as an object of key-value pairs. + * + * @param {Highcharts.CSSObject} [styles] + * Styles as an object of key-value pairs. + * + * @param {Highcharts.HTMLDOMElement} [parent] + * The parent HTML object. + * + * @param {boolean} [nopad=false] + * If true, remove all padding, border and margin. + * + * @return {Highcharts.HTMLDOMElement} + * The created DOM element. + */ + var createElement = (H.createElement = function createElement( + tag, + attribs, + styles, + parent, + nopad + ) { + var el = doc.createElement(tag); + if (attribs) { + 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; + }); + // eslint-disable-next-line valid-jsdoc + /** + * Extend a prototyped class by new members. + * + * @function Highcharts.extendClass<T> + * + * @param {Highcharts.Class<T>} parent + * The parent prototype to inherit. + * + * @param {Highcharts.Dictionary<*>} members + * A collection of prototype members to add or override compared to the + * parent prototype. + * + * @return {Highcharts.Class<T>} + * A new prototype. + */ + var extendClass = (H.extendClass = function extendClass(parent, members) { + var obj = function () {}; + obj.prototype = new parent(); // eslint-disable-line new-cap + extend(obj.prototype, members); + return obj; + }); + /** + * Left-pad a string to a given length by adding a character repetetively. + * + * @function Highcharts.pad + * + * @param {number} number + * The input string or number. + * + * @param {number} [length] + * The desired string length. + * + * @param {string} [padder=0] + * The character to pad with. + * + * @return {string} + * The padded string. + */ + var pad = (H.pad = function pad(number, length, padder) { + return ( + new Array( + (length || 2) + 1 - String(number).replace("-", "").length + ).join(padder || "0") + number + ); + }); + /** + * Return a length based on either the integer value, or a percentage of a base. + * + * @function Highcharts.relativeLength + * + * @param {Highcharts.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. + */ + var relativeLength = (H.relativeLength = function relativeLength( + 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 Highcharts.wrap + * + * @param {*} 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 {Highcharts.WrapProceedFunction} 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. + */ + var wrap = (H.wrap = function wrap(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 string according to a subset of the rules of Python's String.format + * method. + * + * @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 + * + * @function Highcharts.format + * + * @param {string} str + * The string to format. + * + * @param {Record<string, *>} ctx + * The context, a collection of key-value pairs where each key is + * replaced by its value. + * + * @param {Highcharts.Chart} [chart] + * A `Chart` instance used to get numberFormatter and time. + * + * @return {string} + * The formatted string. + */ + var format = (H.format = function (str, ctx, chart) { + var splitter = "{", + isInside = false, + segment, + valueAndFormat, + ret = [], + val, + index; + var floatRegex = /f$/; + var decRegex = /\.([0-9])/; + var lang = H.defaultOptions.lang; + var time = (chart && chart.time) || H.time; + var numberFormatter = (chart && chart.numberFormatter) || numberFormat; + 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(":"); + val = getNestedProperty(valueAndFormat.shift() || "", ctx); + // Format the replacement + if (valueAndFormat.length && typeof val === "number") { + segment = valueAndFormat.join(":"); + if (floatRegex.test(segment)) { + // float + var decimals = parseInt( + (segment.match(decRegex) || ["", "-1"])[1], + 10 + ); + if (val !== null) { + val = numberFormatter( + val, + decimals, + lang.decimalPoint, + segment.indexOf(",") > -1 ? lang.thousandsSep : "" + ); + } + } else { + val = time.dateFormat(segment, val); + } + } + // 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 Highcharts.getMagnitude + * + * @param {number} num + * The number. + * + * @return {number} + * The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2 etc. + */ + var getMagnitude = (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. + * + * @deprecated + * @function Highcharts.normalizeTickInterval + * + * @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. + * + * @return {number} + * The normalized interval. + * + * @todo + * Move this function to the Axis prototype. It is here only for historical + * reasons. + */ + var normalizeTickInterval = (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 = 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 = multiples.filter(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 = 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 Highcharts.stableSort + * + * @param {Array<*>} arr + * The array to sort. + * + * @param {Function} sortFunction + * The function to sort it with, like with regular Array.prototype.sort. + * + * @return {void} + */ + var stableSort = (H.stableSort = function stableSort(arr, sortFunction) { + // @todo It seems like Chrome since v70 sorts in a stable way internally, + // plus all other browsers do it, so over time we may be able to remove this + // function + 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; }); - } else { - if (root.Highcharts) { - root.Highcharts.error(16, true); + // Remove index from items + for (i = 0; i < length; i++) { + delete arr[i].safeI; // stable sort index } - root.Highcharts = factory(root); - } -}(typeof window !== 'undefined' ? window : this, function (win) { - var _modules = {}; - function _registerModule(obj, path, args, fn) { - if (!obj.hasOwnProperty(path)) { - obj[path] = fn.apply(null, args); + }); + /** + * 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 Highcharts.arrayMin + * + * @param {Array<*>} data + * An array of numbers. + * + * @return {number} + * The lowest number. + */ + var arrayMin = (H.arrayMin = function arrayMin(data) { + var i = data.length, + min = data[0]; + while (i--) { + if (data[i] < min) { + min = data[i]; + } } - } - _registerModule(_modules, 'Core/Globals.js', [], function () { - /* * + 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 Highcharts.arrayMax + * + * @param {Array<*>} data + * An array of numbers. + * + * @return {number} + * The highest number. + */ + var arrayMax = (H.arrayMax = function arrayMax(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 Highcharts.destroyObjectProperties + * + * @param {*} obj + * The object to destroy properties on. + * + * @param {*} [except] + * Exception, do not destroy this property, only delete it. + */ + var destroyObjectProperties = (H.destroyObjectProperties = + function destroyObjectProperties(obj, except) { + 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 Highcharts.discardElement + * + * @param {Highcharts.HTMLDOMElement} element + * The HTML node to discard. + */ + var discardElement = (H.discardElement = function discardElement( + element + ) { + var garbageBin = H.garbageBin; + // create a garbage bin element, not part of the DOM + if (!garbageBin) { + garbageBin = createElement("div"); + } + // move the node and empty bin + if (element) { + garbageBin.appendChild(element); + } + garbageBin.innerHTML = ""; + }); + /** + * Fix JS round off float errors. + * + * @function Highcharts.correctFloat + * + * @param {number} num + * A float number to fix. + * + * @param {number} [prec=14] + * The precision. + * + * @return {number} + * The corrected float number. + */ + var correctFloat = (H.correctFloat = function correctFloat(num, prec) { + return parseFloat(num.toPrecision(prec || 14)); + }); + /** + * The time unit lookup + * + * @ignore + */ + var timeUnits = (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. + * + * @sample highcharts/members/highcharts-numberformat/ + * Custom number format + * + * @function Highcharts.numberFormat + * + * @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. + * + * @return {string} + * The formatted number. + */ + var numberFormat = (H.numberFormat = function numberFormat( + 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 (!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(pInt(roundedNumber)); + // Leftover after grouping into thousands. Can be 0, 1 or 2. + thousands = strinteger.length > 3 ? strinteger.length % 3 : 0; + // Language + decimalPoint = pick(decimalPoint, lang.decimalPoint); + thousandsSep = 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 + * + * @private + * @function Math.easeInOutSine + * + * @param {number} pos + * Current position, ranging from 0 to 1. + * + * @return {number} + * Ease result + */ + Math.easeInOutSine = function (pos) { + return -0.5 * (Math.cos(Math.PI * pos) - 1); + }; + /** + * Returns the value of a property path on a given object. + * + * @private + * @function getNestedProperty + * + * @param {string} path + * Path to the property, for example `custom.myValue`. + * + * @param {unknown} obj + * Instance containing the property on the specific path. + * + * @return {unknown} + * The unknown property value. + */ + function getNestedProperty(path, obj) { + if (!path) { + return obj; + } + var pathElements = path.split(".").reverse(); + var subProperty = obj; + if (pathElements.length === 1) { + return subProperty[path]; + } + var pathElement = pathElements.pop(); + while ( + typeof pathElement !== "undefined" && + typeof subProperty !== "undefined" && + subProperty !== null + ) { + subProperty = subProperty[pathElement]; + pathElement = pathElements.pop(); + } + return subProperty; + } + /** + * 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 Highcharts.getStyle + * + * @param {Highcharts.HTMLDOMElement} el + * An HTML element. + * + * @param {string} prop + * The property name. + * + * @param {boolean} [toInt=true] + * Parse to integer. + * + * @return {number|string} + * The numeric value. + */ + var getStyle = (H.getStyle = function (el, prop, toInt) { + var style; + // For width and height, return the actual inner pixel size (#4913) + if (prop === "width") { + var offsetWidth = Math.min(el.offsetWidth, el.scrollWidth); + // In flex boxes, we need to use getBoundingClientRect and floor it, + // because scrollWidth doesn't support subpixel precision (#6427) ... + var boundingClientRectWidth = + el.getBoundingClientRect && el.getBoundingClientRect().width; + // ...unless if the containing div or its parents are transform-scaled + // down, in which case the boundingClientRect can't be used as it is + // also scaled down (#9871, #10498). + if ( + boundingClientRectWidth < offsetWidth && + boundingClientRectWidth >= offsetWidth - 1 + ) { + offsetWidth = Math.floor(boundingClientRectWidth); + } + return Math.max( + 0, // #8377 + offsetWidth - + H.getStyle(el, "padding-left") - + H.getStyle(el, "padding-right") + ); + } + if (prop === "height") { + return Math.max( + 0, // #8377 + 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? + error(27, true); + } + // Otherwise, get the computed style + style = win.getComputedStyle(el, undefined); // eslint-disable-line no-undefined + if (style) { + style = style.getPropertyValue(prop); + if (pick(toInt, prop !== "opacity")) { + style = pInt(style); + } + } + return style; + }); + /** + * Search for an item in an array. + * + * @function Highcharts.inArray + * + * @deprecated + * + * @param {*} item + * The item to search for. + * + * @param {Array<*>} arr + * The array or node collection to search in. + * + * @param {number} [fromIndex=0] + * The index to start searching from. + * + * @return {number} + * The index within the array, or -1 if not found. + */ + var inArray = (H.inArray = function (item, arr, fromIndex) { + error(32, false, void 0, { "Highcharts.inArray": "use Array.indexOf" }); + return arr.indexOf(item, fromIndex); + }); + /* eslint-disable valid-jsdoc */ + /** + * Return the value of the first element in the array that satisfies the + * provided testing function. + * + * @function Highcharts.find<T> + * + * @param {Array<T>} 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. + * + * @return {T|undefined} + * The value of the element. + */ + var find = (H.find = Array.prototype.find + ? /* eslint-enable valid-jsdoc */ + function (arr, callback) { + return arr.find(callback); + } + : // Legacy implementation. PhantomJS, IE <= 11 etc. #7223. + function (arr, callback) { + var i, + length = arr.length; + for (i = 0; i < length; i++) { + if (callback(arr[i], i)) { + // eslint-disable-line callback-return + return arr[i]; + } + } + }); + /** + * Returns an array of a given object's own properties. + * + * @function Highcharts.keys + * @deprecated + * + * @param {*} obj + * The object of which the properties are to be returned. + * + * @return {Array<string>} + * An array of strings that represents all the properties. + */ + H.keys = function (obj) { + error(32, false, void 0, { "Highcharts.keys": "use Object.keys" }); + return Object.keys(obj); + }; + /** + * Get the element's offset position, corrected for `overflow: auto`. + * + * @function Highcharts.offset + * + * @param {global.Element} el + * The DOM element. + * + * @return {Highcharts.OffsetObject} + * An object containing `left` and `top` properties for the position in + * the page. + */ + var offset = (H.offset = function offset(el) { + var docElem = doc.documentElement, + box = + el.parentElement || el.parentNode + ? 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), + }; + }); + /* eslint-disable valid-jsdoc */ + /** + * Iterate over object key pairs in an object. + * + * @function Highcharts.objectEach<T> + * + * @param {*} obj + * The object to iterate over. + * + * @param {Highcharts.ObjectEachCallbackFunction<T>} 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 {T} [ctx] + * The context. + * + * @return {void} + */ + var objectEach = (H.objectEach = function objectEach(obj, fn, ctx) { + /* eslint-enable valid-jsdoc */ + for (var key in obj) { + if (Object.hasOwnProperty.call(obj, key)) { + fn.call(ctx || obj[key], obj[key], key, obj); + } + } + }); + /** + * Iterate over an array. + * + * @deprecated + * @function Highcharts.each + * + * @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 {*} [ctx] + * The context. + * + * @return {void} + */ + /** + * Filter an array by a callback. + * + * @deprecated + * @function Highcharts.grep + * + * @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. + * + * @return {Array<*>} + * A new, filtered array. + */ + /** + * Map an array by a callback. + * + * @deprecated + * @function Highcharts.map + * + * @param {Array<*>} arr + * The array to map. + * + * @param {Function} fn + * The callback function. Return the new value for the new array. + * + * @return {Array<*>} + * A new array item with modified items. + */ + /** + * Reduce an array to a single value. + * + * @deprecated + * @function Highcharts.reduce + * + * @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 {*} initialValue + * The initial value of the accumulator. + * + * @return {*} + * The reduced value. + */ + /** + * Test whether at least one element in the array passes the test implemented by + * the provided function. + * + * @deprecated + * @function Highcharts.some + * + * @param {Array<*>} arr + * The array to test + * + * @param {Function} fn + * The function to run on each item. Return truty to pass the test. + * Receives arguments `currentValue`, `index` and `array`. + * + * @param {*} ctx + * The context. + * + * @return {boolean} + */ + objectEach( + { + map: "map", + each: "forEach", + grep: "filter", + reduce: "reduce", + some: "some", + }, + function (val, key) { + H[key] = function (arr) { + var _a; + error( + 32, + false, + void 0, + ((_a = {}), (_a["Highcharts." + key] = "use Array." + val), _a) + ); + return Array.prototype[val].apply(arr, [].slice.call(arguments, 1)); + }; + } + ); + /* eslint-disable valid-jsdoc */ + /** + * Add an event listener. + * + * @function Highcharts.addEvent<T> + * + * @param {Highcharts.Class<T>|T} 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 {Highcharts.EventCallbackFunction<T>|Function} fn + * The function callback to execute when the event is fired. + * + * @param {Highcharts.EventOptionsObject} [options] + * Options for adding the event. + * + * @return {Function} + * A callback function to remove the added event. + */ + var addEvent = (H.addEvent = function (el, type, fn, options) { + if (options === void 0) { + options = {}; + } + /* eslint-enable valid-jsdoc */ + var events, + addEventListener = el.addEventListener || H.addEventListenerPolyfill; + // If we're setting events directly on the constructor, use a separate + // collection, `protoEvents` to distinguish it from the item events in + // `hcEvents`. + if (typeof el === "function" && el.prototype) { + events = el.prototype.protoEvents = el.prototype.protoEvents || {}; + } else { + events = el.hcEvents = el.hcEvents || {}; + } + // Allow click events added to points, otherwise they will be prevented by + // the TouchPointer.pinch function after a pinch zoom operation (#7091). + if (H.Point && el instanceof H.Point && el.series && el.series.chart) { + el.series.chart.runTrackerClick = true; + } + // Handle DOM events + if (addEventListener) { + addEventListener.call(el, type, fn, false); + } + if (!events[type]) { + events[type] = []; + } + var eventObject = { + fn: fn, + order: typeof options.order === "number" ? options.order : Infinity, + }; + events[type].push(eventObject); + // Order the calls + events[type].sort(function (a, b) { + return a.order - b.order; + }); + // Return a function that can be called to remove this event. + return function () { + removeEvent(el, type, fn); + }; + }); + /* eslint-disable valid-jsdoc */ + /** + * Remove an event that was added with {@link Highcharts#addEvent}. + * + * @function Highcharts.removeEvent<T> + * + * @param {Highcharts.Class<T>|T} 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 {Highcharts.EventCallbackFunction<T>} [fn] + * The specific callback to remove. If undefined, all events that match + * the element and optionally the type are removed. + * + * @return {void} + */ + var removeEvent = (H.removeEvent = function removeEvent(el, type, fn) { + /* eslint-enable valid-jsdoc */ + var events; + /** + * @private + * @param {string} type - event type + * @param {Highcharts.EventCallbackFunction<T>} fn - callback + * @return {void} + */ + function removeOneEvent(type, fn) { + var removeEventListener = + el.removeEventListener || H.removeEventListenerPolyfill; + if (removeEventListener) { + removeEventListener.call(el, type, fn, false); + } + } + /** + * @private + * @param {any} eventCollection - collection + * @return {void} + */ + function removeAllEvents(eventCollection) { + var types, len; + if (!el.nodeName) { + return; // break on non-DOM events + } + if (type) { + types = {}; + types[type] = true; + } else { + types = eventCollection; + } + objectEach(types, function (_val, n) { + if (eventCollection[n]) { + len = eventCollection[n].length; + while (len--) { + removeOneEvent(n, eventCollection[n][len].fn); + } + } + }); + } + ["protoEvents", "hcEvents"].forEach(function (coll, i) { + var eventElem = i ? el : el.prototype; + var eventCollection = eventElem && eventElem[coll]; + if (eventCollection) { + if (type) { + events = eventCollection[type] || []; + if (fn) { + eventCollection[type] = events.filter(function (obj) { + return fn !== obj.fn; + }); + removeOneEvent(type, fn); + } else { + removeAllEvents(eventCollection); + eventCollection[type] = []; + } + } else { + removeAllEvents(eventCollection); + eventElem[coll] = {}; + } + } + }); + }); + /* eslint-disable valid-jsdoc */ + /** + * Fire an event that was registered with {@link Highcharts#addEvent}. + * + * @function Highcharts.fireEvent<T> + * + * @param {T} 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 {Highcharts.Dictionary<*>|Event} [eventArguments] + * Custom event arguments that are passed on as an argument to the event + * handler. + * + * @param {Highcharts.EventCallbackFunction<T>|Function} [defaultFunction] + * The default function to execute if the other listeners haven't + * returned false. + * + * @return {void} + */ + var fireEvent = (H.fireEvent = function ( + el, + type, + eventArguments, + defaultFunction + ) { + /* eslint-enable valid-jsdoc */ + var e, i; + eventArguments = eventArguments || {}; + if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) { + e = doc.createEvent("Events"); + e.initEvent(type, true, true); + extend(e, eventArguments); + if (el.dispatchEvent) { + el.dispatchEvent(e); + } else { + el.fireEvent(type, e); + } + } else { + if (!eventArguments.target) { + // We're running a custom event + 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, + }); + } + var fireInOrder = function (protoEvents, hcEvents) { + if (protoEvents === void 0) { + protoEvents = []; + } + if (hcEvents === void 0) { + hcEvents = []; + } + var iA = 0; + var iB = 0; + var length = protoEvents.length + hcEvents.length; + for (i = 0; i < length; i++) { + var obj = !protoEvents[iA] + ? hcEvents[iB++] + : !hcEvents[iB] + ? protoEvents[iA++] + : protoEvents[iA].order <= hcEvents[iB].order + ? protoEvents[iA++] + : hcEvents[iB++]; + // If the event handler return false, prevent the default + // handler from executing + if (obj.fn.call(el, eventArguments) === false) { + eventArguments.preventDefault(); + } + } + }; + fireInOrder( + el.protoEvents && el.protoEvents[type], + el.hcEvents && el.hcEvents[type] + ); + } + // Run the default if not prevented + if (defaultFunction && !eventArguments.defaultPrevented) { + defaultFunction.call(el, eventArguments); + } + }); + var serialMode; + /** + * 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. + * + * @example + * var id = uniqueKey(); // => 'highcharts-x45f6hp-0' + * + * @function Highcharts.uniqueKey + * + * @return {string} + * A unique key. + */ + var uniqueKey = (H.uniqueKey = (function () { + var hash = Math.random().toString(36).substring(2, 9) + "-"; + var id = 0; + return function () { + return "highcharts-" + (serialMode ? "" : hash) + id++; + }; + })()); + /** + * Activates a serial mode for element IDs provided by + * {@link Highcharts.uniqueKey}. This mode can be used in automated tests, where + * a simple comparison of two rendered SVG graphics is needed. + * + * **Note:** This is only for testing purposes and will break functionality in + * webpages with multiple charts. + * + * @example + * if ( + * process && + * process.env.NODE_ENV === 'development' + * ) { + * Highcharts.useSerialIds(true); + * } + * + * @function Highcharts.useSerialIds + * + * @param {boolean} [mode] + * Changes the state of serial mode. + * + * @return {boolean|undefined} + * State of the serial mode. + */ + var useSerialIds = (H.useSerialIds = function (mode) { + return (serialMode = pick(mode, serialMode)); + }); + var isFunction = (H.isFunction = function (obj) { + return typeof obj === "function"; + }); + /** + * 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. + * + * @function Highcharts.getOptions + * + * @return {Highcharts.Options} + */ + var getOptions = (H.getOptions = function () { + return H.defaultOptions; + }); + /** + * Merge the default options with custom options and return the new options + * structure. Commonly used for defining reusable templates. + * + * @sample highcharts/global/useutc-false Setting a global option + * @sample highcharts/members/setoptions Applying a global theme + * + * @function Highcharts.setOptions + * + * @param {Highcharts.Options} options + * The new custom chart options. + * + * @return {Highcharts.Options} + * Updated options. + */ + var setOptions = (H.setOptions = function (options) { + // Copy in the default options + H.defaultOptions = merge(true, H.defaultOptions, options); + // Update the time object + if (options.time || options.global) { + H.time.update( + merge( + H.defaultOptions.global, + H.defaultOptions.time, + options.global, + options.time + ) + ); + } + return H.defaultOptions; + }); + // Register Highcharts as a plugin in jQuery + if (win.jQuery) { + /** + * Highcharts-extended JQuery. * - * (c) 2010-2020 Torstein Honsi + * @external JQuery + */ + /** + * Helper function to return the chart of the current JQuery selector + * element. * - * License: www.highcharts.com/license + * @function external:JQuery#highcharts * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @return {Highcharts.Chart} + * The chart that is linked to the JQuery selector element. + */ /** + * Factory function to create a chart in the current JQuery selector + * element. * - * */ - /* globals Image, window */ - /** - * Reference to the global SVGElement class as a workaround for a name conflict - * in the Highcharts namespace. - * - * @global - * @typedef {global.SVGElement} GlobalSVGElement - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement - */ - // glob is a temporary fix to allow our es-modules to work. - var glob = ( // @todo UMD variable named `window`, and glob named `win` - typeof win !== 'undefined' ? - win : - typeof window !== 'undefined' ? - window : - {}), 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 = userAgent.indexOf('Firefox') !== -1, isChrome = userAgent.indexOf('Chrome') !== -1, hasBidiBug = (isFirefox && - parseInt(userAgent.split('Firefox/')[1], 10) < 4 // issue #38 - ); - var H = { - product: 'Highcharts', - version: '8.2.2', - deg2rad: Math.PI * 2 / 360, - doc: doc, - hasBidiBug: hasBidiBug, - hasTouch: !!glob.TouchEvent, - isMS: isMS, - isWebKit: userAgent.indexOf('AppleWebKit') !== -1, - isFirefox: isFirefox, - isChrome: isChrome, - isSafari: !isChrome && userAgent.indexOf('Safari') !== -1, - 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 () { }, - /** - * Theme options that should get applied to the chart. In module mode it - * might not be possible to change this property because of read-only - * restrictions, instead use {@link Highcharts.setOptions}. - * - * @name Highcharts.theme - * @type {Highcharts.Options} - */ - /** - * 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`. - * - * @name Highcharts.charts - * @type {Array<Highcharts.Chart|undefined>} - */ - charts: [], - /** - * 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. - * - * @sample highcharts/global/dateformats/ - * Adding support for week number - * - * @name Highcharts.dateFormats - * @type {Highcharts.Dictionary<Highcharts.TimeFormatCallbackFunction>} - */ - dateFormats: {} - }; + * @function external:JQuery#highcharts + * + * @param {'Chart'|'Map'|'StockChart'|string} [className] + * Name of the factory class in the Highcharts namespace. + * + * @param {Highcharts.Options} [options] + * The chart options structure. + * + * @param {Highcharts.ChartCallbackFunction} [callback] + * Function to run when the chart has loaded and and all external + * images are loaded. Defining a + * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) + * handler is equivalent. + * + * @return {JQuery} + * The current JQuery selector. + */ + 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 computed-property-spacing, no-new + // Constructor defaults to Chart + 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[attr(this[0], "data-highcharts-chart")]; + } + }; + } + // TODO use named exports when supported. + var utilitiesModule = { + addEvent: addEvent, + arrayMax: arrayMax, + arrayMin: arrayMin, + attr: attr, + clamp: clamp, + clearTimeout: internalClearTimeout, + correctFloat: correctFloat, + createElement: createElement, + css: css, + defined: defined, + destroyObjectProperties: destroyObjectProperties, + discardElement: discardElement, + erase: erase, + error: error, + extend: extend, + extendClass: extendClass, + find: find, + fireEvent: fireEvent, + format: format, + getMagnitude: getMagnitude, + getNestedProperty: getNestedProperty, + getOptions: getOptions, + getStyle: getStyle, + inArray: inArray, + isArray: isArray, + isClass: isClass, + isDOMElement: isDOMElement, + isFunction: isFunction, + isNumber: isNumber, + isObject: isObject, + isString: isString, + merge: merge, + normalizeTickInterval: normalizeTickInterval, + numberFormat: numberFormat, + objectEach: objectEach, + offset: offset, + pad: pad, + pick: pick, + pInt: pInt, + relativeLength: relativeLength, + removeEvent: removeEvent, + setOptions: setOptions, + splat: splat, + stableSort: stableSort, + syncTimeout: syncTimeout, + timeUnits: timeUnits, + uniqueKey: uniqueKey, + useSerialIds: useSerialIds, + wrap: wrap, + }; - return H; - }); - _registerModule(_modules, 'Core/Utilities.js', [_modules['Core/Globals.js']], function (H) { + return utilitiesModule; + } + ); + _registerModule( + _modules, + "Core/Color/Color.js", + [_modules["Core/Globals.js"], _modules["Core/Utilities.js"]], + function (H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var isNumber = U.isNumber, + merge = U.merge, + pInt = U.pInt; + /** + * 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. + * + * @typedef {string} Highcharts.ColorString + */ + /** + * A valid color type than can be parsed and handled by Highcharts. It can be a + * color string, a gradient object, or a pattern object. + * + * @typedef {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} Highcharts.ColorType + */ + /** + * Gradient options instead of a solid color. + * + * @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 + * ] + * } + * + * @interface Highcharts.GradientColorObject + */ /** + * Holds an object that defines the start position and the end position relative + * to the shape. + * @name Highcharts.GradientColorObject#linearGradient + * @type {Highcharts.LinearGradientColorObject|undefined} + */ /** + * Holds an object that defines the center position and the radius. + * @name Highcharts.GradientColorObject#radialGradient + * @type {Highcharts.RadialGradientColorObject|undefined} + */ /** + * 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. + * @name Highcharts.GradientColorObject#stops + * @type {Array<Highcharts.GradientColorStopObject>} + */ + /** + * Color stop tuple. + * + * @see Highcharts.GradientColorObject + * + * @interface Highcharts.GradientColorStopObject + */ /** + * @name Highcharts.GradientColorStopObject#0 + * @type {number} + */ /** + * @name Highcharts.GradientColorStopObject#1 + * @type {Highcharts.ColorString} + */ /** + * @name Highcharts.GradientColorStopObject#color + * @type {Highcharts.Color|undefined} + */ + /** + * Defines the start position and the end position for a gradient relative + * to the shape. Start position (x1, y1) and end position (x2, y2) are relative + * to the shape, where 0 means top/left and 1 is bottom/right. + * + * @interface Highcharts.LinearGradientColorObject + */ /** + * Start horizontal position of the gradient. Float ranges 0-1. + * @name Highcharts.LinearGradientColorObject#x1 + * @type {number} + */ /** + * End horizontal position of the gradient. Float ranges 0-1. + * @name Highcharts.LinearGradientColorObject#x2 + * @type {number} + */ /** + * Start vertical position of the gradient. Float ranges 0-1. + * @name Highcharts.LinearGradientColorObject#y1 + * @type {number} + */ /** + * End vertical position of the gradient. Float ranges 0-1. + * @name Highcharts.LinearGradientColorObject#y2 + * @type {number} + */ + /** + * Defines the center position and the radius for a gradient. + * + * @interface Highcharts.RadialGradientColorObject + */ /** + * Center horizontal position relative to the shape. Float ranges 0-1. + * @name Highcharts.RadialGradientColorObject#cx + * @type {number} + */ /** + * Center vertical position relative to the shape. Float ranges 0-1. + * @name Highcharts.RadialGradientColorObject#cy + * @type {number} + */ /** + * Radius relative to the shape. Float ranges 0-1. + * @name Highcharts.RadialGradientColorObject#r + * @type {number} + */ + (""); // detach doclets above + /* * + * + * Class + * + * */ + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * Handle color operations. Some object methods are chainable. + * + * @class + * @name Highcharts.Color + * + * @param {Highcharts.ColorType} input + * The input color in either rbga or hex format + */ + var Color = /** @class */ (function () { /* * * - * (c) 2010-2020 Torstein Honsi + * Constructors * - * License: www.highcharts.com/license + * */ + function Color(input) { + // Collection of parsers. This can be extended from the outside by pushing + // parsers to Highcharts.Color.prototype.parsers. + this.parsers = [ + { + // RGBA color + // eslint-disable-next-line max-len + 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]; + }, + }, + ]; + this.rgba = []; + // Backwards compatibility, allow class overwrite + if (H.Color !== Color) { + return new H.Color(input); + } + // Backwards compatibility, allow instanciation without new (#13053) + if (!(this instanceof Color)) { + return new Color(input); + } + this.init(input); + } + /* * * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * Static Functions * * */ /** - * An animation configuration. Animation configurations can also be defined as - * booleans, where `false` turns off animation and `true` defaults to a duration - * of 500ms and defer of 0ms. + * Creates a color instance out of a color string or object. * - * @interface Highcharts.AnimationOptionsObject - */ /** - * A callback function to exectute when the animation finishes. - * @name Highcharts.AnimationOptionsObject#complete - * @type {Function|undefined} - */ /** - * The animation defer in milliseconds. - * @name Highcharts.AnimationOptionsObject#defer - * @type {number|undefined} - */ /** - * The animation duration in milliseconds. - * @name Highcharts.AnimationOptionsObject#duration - * @type {number|undefined} - */ /** - * The name of an easing function as defined on the `Math` object. - * @name Highcharts.AnimationOptionsObject#easing - * @type {string|Function|undefined} - */ /** - * 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. - * @name Highcharts.AnimationOptionsObject#step - * @type {Function|undefined} - */ - /** - * Creates a frame for the animated SVG element. - * - * @callback Highcharts.AnimationStepCallbackFunction - * - * @param {Highcharts.SVGElement} this - * The SVG element to animate. - * - * @return {void} - */ - /** - * Interface description for a class. + * @function Highcharts.Color.parse * - * @interface Highcharts.Class<T> - * @extends Function - */ /** - * Class costructor. - * @function Highcharts.Class<T>#new - * @param {...Array<*>} args - * Constructor arguments. - * @return {T} - * Class instance. - */ - /** - * A style object with camel case property names to define visual appearance of - * a SVG element or HTML element. The properties can be whatever styles are - * supported on the given SVG or HTML element. + * @param {Highcharts.ColorType} input + * The input color in either rbga or hex format. * - * @example - * { - * fontFamily: 'monospace', - * fontSize: '1.2em' - * } + * @return {Highcharts.Color} + * Color instance. + */ + Color.parse = function (input) { + return new Color(input); + }; + /* * * - * @interface Highcharts.CSSObject - */ /** - * @name Highcharts.CSSObject#[key:string] - * @type {boolean|number|string|undefined} - */ /** - * Background style for the element. - * @name Highcharts.CSSObject#background - * @type {string|undefined} - */ /** - * Background color of the element. - * @name Highcharts.CSSObject#backgroundColor - * @type {Highcharts.ColorString|undefined} - */ /** - * Border style for the element. - * @name Highcharts.CSSObject#border - * @type {string|undefined} - */ /** - * Radius of the element border. - * @name Highcharts.CSSObject#borderRadius - * @type {number|undefined} - */ /** - * Color used in the element. The 'contrast' option is a Highcharts custom - * property that results in black or white, depending on the background of the - * element. - * @name Highcharts.CSSObject#color - * @type {'contrast'|Highcharts.ColorString|undefined} - */ /** - * Style of the mouse cursor when resting over the element. - * @name Highcharts.CSSObject#cursor - * @type {Highcharts.CursorValue|undefined} - */ /** - * Font family of the element text. Multiple values have to be in decreasing - * preference order and separated by comma. - * @name Highcharts.CSSObject#fontFamily - * @type {string|undefined} - */ /** - * Font size of the element text. - * @name Highcharts.CSSObject#fontSize - * @type {string|undefined} - */ /** - * Font weight of the element text. - * @name Highcharts.CSSObject#fontWeight - * @type {string|undefined} - */ /** - * Height of the element. - * @name Highcharts.CSSObject#height - * @type {number|undefined} - */ /** - * Width of the element border. - * @name Highcharts.CSSObject#lineWidth - * @type {number|undefined} - */ /** - * Opacity of the element. - * @name Highcharts.CSSObject#opacity - * @type {number|undefined} - */ /** - * Space around the element content. - * @name Highcharts.CSSObject#padding - * @type {string|undefined} - */ /** - * Behaviour of the element when the mouse cursor rests over it. - * @name Highcharts.CSSObject#pointerEvents - * @type {string|undefined} - */ /** - * Positioning of the element. - * @name Highcharts.CSSObject#position - * @type {string|undefined} - */ /** - * Alignment of the element text. - * @name Highcharts.CSSObject#textAlign - * @type {string|undefined} - */ /** - * Additional decoration of the element text. - * @name Highcharts.CSSObject#textDecoration - * @type {string|undefined} - */ /** - * Outline style of the element text. - * @name Highcharts.CSSObject#textOutline - * @type {string|undefined} - */ /** - * Line break style of the element text. Highcharts SVG elements support - * `ellipsis` when a `width` is set. - * @name Highcharts.CSSObject#textOverflow - * @type {string|undefined} - */ /** - * Top spacing of the element relative to the parent element. - * @name Highcharts.CSSObject#top - * @type {string|undefined} - */ /** - * Animated transition of selected element properties. - * @name Highcharts.CSSObject#transition - * @type {string|undefined} - */ /** - * Line break style of the element text. - * @name Highcharts.CSSObject#whiteSpace - * @type {string|undefined} - */ /** - * Width of the element. - * @name Highcharts.CSSObject#width - * @type {number|undefined} - */ - /** - * All possible cursor styles. - * - * @typedef {'alias'|'all-scroll'|'auto'|'cell'|'col-resize'|'context-menu'|'copy'|'crosshair'|'default'|'e-resize'|'ew-resize'|'grab'|'grabbing'|'help'|'move'|'n-resize'|'ne-resize'|'nesw-resize'|'no-drop'|'none'|'not-allowed'|'ns-resize'|'nw-resize'|'nwse-resize'|'pointer'|'progress'|'row-resize'|'s-resize'|'se-resize'|'sw-resize'|'text'|'vertical-text'|'w-resize'|'wait'|'zoom-in'|'zoom-out'} Highcharts.CursorValue - */ - /** - * All possible dash styles. - * - * @typedef {'Dash'|'DashDot'|'Dot'|'LongDash'|'LongDashDot'|'LongDashDotDot'|'ShortDash'|'ShortDashDot'|'ShortDashDotDot'|'ShortDot'|'Solid'} Highcharts.DashStyleValue - */ - /** - * Generic dictionary in TypeScript notation. - * Use the native `Record<string, any>` instead. + * Functions * - * @deprecated - * @interface Highcharts.Dictionary<T> - */ /** - * @name Highcharts.Dictionary<T>#[key:string] - * @type {T} - */ + * */ /** - * The function callback to execute when the event is fired. The `this` context - * contains the instance, that fired the event. + * Parse the input color to rgba array * - * @callback Highcharts.EventCallbackFunction<T> - * - * @param {T} this + * @private + * @function Highcharts.Color#init * - * @param {Highcharts.Dictionary<*>|Event} [eventArguments] - * Event arguments. + * @param {Highcharts.ColorType} input + * The input color in either rbga or hex format * - * @return {boolean|void} + * @return {void} */ + Color.prototype.init = function (input) { + var result, rgba, i, parser, len; + this.input = input = + Color.names[ + input && input.toLowerCase ? input.toLowerCase() : "" + ] || input; + // Gradients + if (input && input.stops) { + this.stops = input.stops.map(function (stop) { + return new 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 || []; + }; /** - * The event options for adding function callback. - * - * @interface Highcharts.EventOptionsObject - */ /** - * The order the event handler should be called. This opens for having one - * handler be called before another, independent of in which order they were - * added. - * @name Highcharts.EventOptionsObject#order - * @type {number} - */ - /** - * Formats data as a string. Usually the data is accessible throught the `this` - * keyword. + * Return the color or gradient stops in the specified format * - * @callback Highcharts.FormatterCallbackFunction<T> + * @function Highcharts.Color#get * - * @param {T} this - * Context to format + * @param {string} [format] + * Possible values are 'a', 'rgb', 'rgba' (default). * - * @return {string} - * Formatted text + * @return {Highcharts.ColorType} + * This color as a string or gradient stops. */ + Color.prototype.get = function (format) { + var input = this.input, + rgba = this.rgba, + ret; + if (typeof this.stops !== "undefined") { + ret = merge(input); + ret.stops = [].concat(ret.stops); + this.stops.forEach(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; + }; /** - * An object of key-value pairs for HTML attributes. + * Brighten the color instance. * - * @typedef {Highcharts.Dictionary<boolean|number|string|Function>} Highcharts.HTMLAttributes - */ + * @function Highcharts.Color#brighten + * + * @param {number} alpha + * The alpha value. + * + * @return {Highcharts.Color} + * This color with modifications. + */ + Color.prototype.brighten = function (alpha) { + var i, + rgba = this.rgba; + if (this.stops) { + this.stops.forEach(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; + }; /** - * An HTML DOM element. The type is a reference to the regular HTMLElement in - * the global scope. + * Set the color's opacity to a given alpha value. * - * @typedef {global.HTMLElement} Highcharts.HTMLDOMElement + * @function Highcharts.Color#setOpacity * - * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement + * @param {number} alpha + * Opacity between 0 and 1. + * + * @return {Highcharts.Color} + * Color with modifications. */ + Color.prototype.setOpacity = function (alpha) { + this.rgba[3] = alpha; + return this; + }; /** - * The iterator callback. + * Return an intermediate color between two colors. * - * @callback Highcharts.ObjectEachCallbackFunction<T> + * @function Highcharts.Color#tweenTo * - * @param {T} this - * The context. + * @param {Highcharts.Color} to + * The color object to tween to. * - * @param {*} value - * The property value. + * @param {number} pos + * The intermediate position, where 0 is the from color (current + * color item), and 1 is the `to` color. + * + * @return {Highcharts.ColorString} + * The intermediate color in rgba notation. + */ + Color.prototype.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; + }; + /* * * - * @param {string} key - * The property key. + * Static Properties * - * @param {*} obj - * The object that objectEach is being applied to. - */ - /** - * An object containing `left` and `top` properties for the position in the - * page. + * */ + // Collection of named colors. Can be extended from the outside by adding + // colors to Highcharts.Color.names. + Color.names = { + white: "#ffffff", + black: "#000000", + }; + return Color; + })(); + H.Color = Color; + /** + * Creates a color instance out of a color string. + * + * @function Highcharts.color + * + * @param {Highcharts.ColorType} input + * The input color in either rbga or hex format + * + * @return {Highcharts.Color} + * Color instance + */ + H.color = Color.parse; + /* * + * + * Export + * + * */ + + return Color; + } + ); + _registerModule( + _modules, + "Core/Animation/Fx.js", + [_modules["Core/Globals.js"], _modules["Core/Utilities.js"]], + function (H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var win = H.win; + var isNumber = U.isNumber, + objectEach = U.objectEach; + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * 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}. + * + * @example + * var rect = renderer.rect(0, 0, 10, 10).add(); + * rect.animate({ width: 100 }); + * + * @private + * @class + * @name Highcharts.Fx + */ + var Fx = /** @class */ (function () { + /* * * - * @interface Highcharts.OffsetObject - */ /** - * Left distance to the page border. - * @name Highcharts.OffsetObject#left - * @type {number} - */ /** - * Top distance to the page border. - * @name Highcharts.OffsetObject#top - * @type {number} - */ - /** - * Describes a range. - * - * @interface Highcharts.RangeObject - */ /** - * Maximum number of the range. - * @name Highcharts.RangeObject#max - * @type {number} - */ /** - * Minimum number of the range. - * @name Highcharts.RangeObject#min - * @type {number} - */ - /** - * 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. + * Constructors * - * @typedef {number|string} Highcharts.RelativeSize - */ + * */ /** - * Proceed function to call original (wrapped) function. * - * @callback Highcharts.WrapProceedFunction + * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} elem + * The element to animate. * - * @param {*} [arg1] - * Optional argument. Without any arguments defaults to first argument of - * the wrapping function. + * @param {Partial<Highcharts.AnimationOptionsObject>} options + * Animation options. * - * @param {*} [arg2] - * Optional argument. Without any arguments defaults to second argument - * of the wrapping function. + * @param {string} prop + * The single attribute or CSS property to animate. + */ + function Fx(elem, options, prop) { + this.pos = NaN; + this.options = options; + this.elem = elem; + this.prop = prop; + } + /* * * - * @param {*} [arg3] - * Optional argument. Without any arguments defaults to third argument of - * the wrapping function. + * Functions * - * @return {*} - * Return value of the original function. - */ + * */ /** - * 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. + * Set the current step of a path definition on SVGElement. * - * @example - * var chart = Highcharts.chart('container', { ... }); + * @function Highcharts.Fx#dSetter * - * @namespace Highcharts + * @return {void} */ - H.timers = []; - var charts = H.charts, - doc = H.doc, - win = H.win; + Fx.prototype.dSetter = function () { + var paths = this.paths, + start = paths && paths[0], + end = paths && paths[1], + path = [], + now = this.now || 0; + // Land on the final path without adjustment points appended in the ends + if (now === 1 || !start || !end) { + path = this.toD || []; + } else if (start.length === end.length && now < 1) { + for (var i = 0; i < end.length; i++) { + // Tween between the start segment and the end segment. Start + // with a copy of the end segment and tween the appropriate + // numerics + var startSeg = start[i]; + var endSeg = end[i]; + var tweenSeg = []; + for (var j = 0; j < endSeg.length; j++) { + var startItem = startSeg[j]; + var endItem = endSeg[j]; + // Tween numbers + if ( + typeof startItem === "number" && + typeof endItem === "number" && + // Arc boolean flags + !(endSeg[0] === "A" && (j === 4 || j === 5)) + ) { + tweenSeg[j] = startItem + now * (endItem - startItem); + // Strings, take directly from the end segment + } else { + tweenSeg[j] = endItem; + } + } + path.push(tweenSeg); + } + // If animation is finished or length not matching, land on right value + } else { + path = end; + } + this.elem.attr("d", path, void 0, true); + }; /** - * Provide error messages for debugging, with links to online explanation. This - * function can be overridden to provide custom error handling. + * Update the element with the current animation step. * - * @sample highcharts/chart/highcharts-error/ - * Custom error handler + * @function Highcharts.Fx#update * - * @function Highcharts.error + * @return {void} + */ + Fx.prototype.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. * - * @param {number|string} code - * The error code. See - * [errors.xml](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. + * @function Highcharts.Fx#run * - * @param {boolean} [stop=false] - * Whether to throw an error or just log a warning in the console. + * @param {number} from + * The current value, value to start from. * - * @param {Highcharts.Chart} [chart] - * Reference to the chart that causes the error. Used in 'debugger' - * module to display errors directly on the chart. - * Important note: This argument is undefined for errors that lack - * access to the Chart instance. + * @param {number} to + * The end value, value to land on. * - * @param {Highcharts.Dictionary<string>} [params] - * Additional parameters for the generated message. + * @param {string} unit + * The property unit, for example `px`. * * @return {void} */ - function error(code, stop, chart, params) { - var severity = stop ? 'Highcharts error' : 'Highcharts warning'; - if (code === 32) { - code = severity + ": Deprecated member"; - } - var isCode = isNumber(code), - message = isCode ? - severity + " #" + code + ": www.highcharts.com/errors/" + code + "/" : - code.toString(), - defaultHandler = function () { - if (stop) { - throw new Error(message); - } - // else ... - if (win.console && - error.messages.indexOf(message) === -1 // prevent console flooting - ) { - console.log(message); // eslint-disable-line no-console - } - }; - if (typeof params !== 'undefined') { - var additionalMessages_1 = ''; - if (isCode) { - message += '?'; - } - objectEach(params, function (value, key) { - additionalMessages_1 += "\n - " + key + ": " + value; - if (isCode) { - message += encodeURI(key) + '=' + encodeURI(value); - } - }); - message += additionalMessages_1; - } - if (chart) { - fireEvent(chart, 'displayError', { code: code, message: message, params: params }, defaultHandler); + Fx.prototype.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 && !this.elem["forceAnimate:" + this.prop]) { + delete options.curAnim[this.prop]; + if (options.complete && Object.keys(options.curAnim).length === 0) { + options.complete.call(this.elem); } - else { - defaultHandler(); + } 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); } - error.messages.push(message); - } - (function (error) { - error.messages = []; - })(error || (error = {})); - H.error = error; - /* eslint-disable valid-jsdoc */ + } + }; /** - * 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. + * Run a single step in the animation. * - * @function Highcharts.merge<T> + * @function Highcharts.Fx#step * - * @param {boolean} extend - * Whether to extend the left-side object (a) or return a whole new - * object. + * @param {boolean} [gotoEnd] + * Whether to go to the endpoint of the animation after abort. * - * @param {T|undefined} a - * The first object to extend. When only this is given, the function - * returns a deep copy. - * - * @param {...Array<object|undefined>} [n] - * An object to merge into the previous one. - * - * @return {T} - * The merged object. If the first argument is true, the return is the - * same as the second argument. - */ /** - * Utility function to deep merge two or more objects and return a third object. - * The merge function can also be used with a single object argument to create a - * deep copy of an object. - * - * @function Highcharts.merge<T> - * - * @param {T|undefined} a - * The first object to extend. When only this is given, the function - * returns a deep copy. - * - * @param {...Array<object|undefined>} [n] - * An object to merge into the previous one. - * - * @return {T} - * The merged object. If the first argument is true, the return is the - * same as the second argument. - */ - function merge() { - /* eslint-enable valid-jsdoc */ - var i, - args = arguments, - len, - ret = {}, - doCopy = function (copy, - original) { - // An object is replacing a primitive - if (typeof copy !== 'object') { - copy = {}; - } - objectEach(original, function (value, key) { - // Copy the contents of objects, but not arrays or DOM nodes - if (isObject(value, true) && - !isClass(value) && - !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 {boolean} + * Returns `true` if animation continues. + */ + Fx.prototype.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; + objectEach(curAnim, function (val) { + if (val !== true) { + done = false; + } + }); + if (done && complete) { + complete.call(elem); } - return ret; - } - H.merge = merge; + 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; + }; /** - * Constrain a value to within a lower and upper threshold. + * Prepare start and end values so that the path can be animated one to one. * - * @private - * @param {number} value The initial value - * @param {number} min The lower threshold - * @param {number} max The upper threshold - * @return {number} Returns a number value within min and max. - */ - function clamp(value, min, max) { - return value > min ? value < max ? value : max : min; - } - /** - * Shortcut for parseInt + * @function Highcharts.Fx#initPath * - * @private - * @function Highcharts.pInt + * @param {Highcharts.SVGElement} elem + * The SVGElement item. * - * @param {*} s - * any + * @param {Highcharts.SVGPathArray|undefined} fromD + * Starting path definition. * - * @param {number} [mag] - * Magnitude + * @param {Highcharts.SVGPathArray} toD + * Ending path definition. * - * @return {number} - * number + * @return {Array<Highcharts.SVGPathArray,Highcharts.SVGPathArray>} + * An array containing start and end paths in array form so that + * they can be animated in parallel. */ - var pInt = H.pInt = function pInt(s, - mag) { - return parseInt(s, - mag || 10); + Fx.prototype.initPath = function (elem, fromD, toD) { + var shift, + startX = elem.startX, + endX = elem.endX, + fullLength, + i, + start = fromD && fromD.slice(), // copy + end = toD.slice(), // copy + isArea = elem.isArea, + positionFactor = isArea ? 2 : 1, + reverse; + if (!start) { + return [end, end]; + } + /** + * If shifting points, prepend a dummy point to the end path. + * @private + * @param {Highcharts.SVGPathArray} arr - array + * @param {Highcharts.SVGPathArray} other - array + * @return {void} + */ + function prepend(arr, other) { + while (arr.length < fullLength) { + // Move to, line to or curve to? + var moveSegment = arr[0], + otherSegment = other[fullLength - arr.length]; + if (otherSegment && moveSegment[0] === "M") { + if (otherSegment[0] === "C") { + arr[0] = [ + "C", + moveSegment[1], + moveSegment[2], + moveSegment[1], + moveSegment[2], + moveSegment[1], + moveSegment[2], + ]; + } else { + arr[0] = ["L", moveSegment[1], moveSegment[2]]; + } + } + // Prepend a copy of the first point + arr.unshift(moveSegment); + // For areas, the bottom path goes back again to the left, so we + // need to append a copy of the last point. + if (isArea) { + arr.push(arr[arr.length - 1]); + } + } + } + /** + * Copy and append last point until the length matches the end length. + * @private + * @param {Highcharts.SVGPathArray} arr - array + * @param {Highcharts.SVGPathArray} other - array + * @return {void} + */ + function append(arr, other) { + while (arr.length < fullLength) { + // 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. + var segmentToAdd = arr[arr.length / positionFactor - 1].slice(); + // Disable the first control point of curve segments + if (segmentToAdd[0] === "C") { + segmentToAdd[1] = segmentToAdd[5]; + segmentToAdd[2] = segmentToAdd[6]; + } + if (!isArea) { + arr.push(segmentToAdd); + } else { + var lowerSegmentToAdd = + arr[arr.length / positionFactor].slice(); + arr.splice(arr.length / 2, 0, segmentToAdd, lowerSegmentToAdd); + } + } + } + // 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; + // Fixed from the right side, "scaling" left + } else if ( + startX[startX.length - 1] === + endX[endX.length - startX.length + i] + ) { + shift = startX.length - i; + break; + } + } + if (typeof shift === "undefined") { + start = []; + } + } + if (start.length && 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; + if (!reverse) { + prepend(end, start); + append(start, end); + } else { + prepend(start, end); + append(end, start); + } + } + return [start, end]; }; /** - * Utility function to check for string type. + * Handle animation of the color attributes directly. * - * @function Highcharts.isString + * @function Highcharts.Fx#fillSetter * - * @param {*} s - * The item to check. - * - * @return {boolean} - * True if the argument is a string. + * @return {void} */ - var isString = H.isString = function isString(s) { - return typeof s === 'string'; + Fx.prototype.fillSetter = function () { + Fx.prototype.strokeSetter.apply(this, arguments); }; /** - * Utility function to check if an item is an array. - * - * @function Highcharts.isArray + * Handle animation of the color attributes directly. * - * @param {*} obj - * The item to check. + * @function Highcharts.Fx#strokeSetter * - * @return {boolean} - * True if the argument is an array. + * @return {void} */ - var isArray = H.isArray = function isArray(obj) { - var str = Object.prototype.toString.call(obj); - return str === '[object Array]' || str === '[object Array Iterator]'; + 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 check if an item is of type object. - * - * @function Highcharts.isObject - * - * @param {*} obj - * The item to check. + return Fx; + })(); + H.Fx = Fx; + + return Fx; + } + ); + _registerModule( + _modules, + "Core/Animation/AnimationUtilities.js", + [ + _modules["Core/Animation/Fx.js"], + _modules["Core/Globals.js"], + _modules["Core/Utilities.js"], + ], + function (Fx, H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var defined = U.defined, + getStyle = U.getStyle, + isArray = U.isArray, + isNumber = U.isNumber, + isObject = U.isObject, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick; + /** + * Set the global animation to either a given value, or fall back to the given + * chart's animation option. + * + * @function Highcharts.setAnimation + * + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>|undefined} animation + * The animation object. + * + * @param {Highcharts.Chart} 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. + */ + var setAnimation = (H.setAnimation = function setAnimation( + animation, + chart + ) { + chart.renderer.globalAnimation = pick( + animation, + chart.options.chart.animation, + true + ); + }); + /** + * Get the animation in object form, where a disabled animation is always + * returned as `{ duration: 0 }`. + * + * @function Highcharts.animObject + * + * @param {boolean|Highcharts.AnimationOptionsObject} [animation=0] + * An animation setting. Can be an object with duration, complete and + * easing properties, or a boolean to enable or disable. + * + * @return {Highcharts.AnimationOptionsObject} + * An object with at least a duration property. + */ + var animObject = (H.animObject = function animObject(animation) { + return isObject(animation) + ? H.merge({ duration: 500, defer: 0 }, animation) + : { duration: animation ? 500 : 0, defer: 0 }; + }); + /** + * Get the defer as a number value from series animation options. + * + * @function Highcharts.getDeferredAnimation + * + * @param {Highcharts.Chart} chart + * The chart instance. + * + * @param {boolean|Highcharts.AnimationOptionsObject} animation + * An animation setting. Can be an object with duration, complete and + * easing properties, or a boolean to enable or disable. + * + * @param {Highcharts.Series} [series] + * Series to defer animation. + * + * @return {number} + * The numeric value. + */ + var getDeferredAnimation = (H.getDeferredAnimation = function ( + chart, + animation, + series + ) { + var labelAnimation = animObject(animation); + var s = series ? [series] : chart.series; + var defer = 0; + var duration = 0; + s.forEach(function (series) { + var seriesAnim = animObject(series.options.animation); + defer = + animation && defined(animation.defer) + ? labelAnimation.defer + : Math.max(defer, seriesAnim.duration + seriesAnim.defer); + duration = Math.min(labelAnimation.duration, seriesAnim.duration); + }); + // Disable defer for exporting + if (chart.renderer.forExport) { + defer = 0; + } + var anim = { + defer: Math.max(0, defer - duration), + duration: Math.min(defer, duration), + }; + return anim; + }); + /** + * The global animate method, which uses Fx to create individual animators. + * + * @function Highcharts.animate + * + * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} el + * The element to animate. + * + * @param {Highcharts.CSSObject|Highcharts.SVGAttributes} 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 {Partial<Highcharts.AnimationOptionsObject>} [opt] + * Animation options. + * + * @return {void} + */ + var animate = (H.animate = function (el, params, opt) { + var start, + unit = "", + end, + fx, + args; + if (!isObject(opt)) { + // Number or undefined/null + args = arguments; + opt = { + duration: args[2], + easing: args[3], + complete: args[4], + }; + } + if (!isNumber(opt.duration)) { + opt.duration = 400; + } + opt.easing = + typeof opt.easing === "function" + ? opt.easing + : Math[opt.easing] || Math.easeInOutSine; + opt.curAnim = merge(params); + objectEach(params, function (val, prop) { + // Stop current running animation of this property + stop(el, prop); + fx = new Fx(el, opt, prop); + end = null; + if (prop === "d" && isArray(params.d)) { + fx.paths = fx.initPath(el, el.pathArray, params.d); + fx.toD = params.d; + start = 0; + end = 1; + } else if (el.attr) { + start = el.attr(prop); + } else { + start = parseFloat(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); + }); + }); + /** + * Stop running animation. + * + * @function Highcharts.stop + * + * @param {Highcharts.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. + * + * @return {void} + * + * @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. + */ + var stop = (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 + } + } + }); + var animationExports = { + animate: animate, + animObject: animObject, + getDeferredAnimation: getDeferredAnimation, + setAnimation: setAnimation, + stop: stop, + }; + + return animationExports; + } + ); + _registerModule( + _modules, + "Core/Renderer/SVG/SVGElement.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Color/Color.js"], + _modules["Core/Globals.js"], + _modules["Core/Utilities.js"], + ], + function (A, Color, H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var animate = A.animate, + animObject = A.animObject, + stop = A.stop; + var deg2rad = H.deg2rad, + doc = H.doc, + hasTouch = H.hasTouch, + isFirefox = H.isFirefox, + noop = H.noop, + svg = H.svg, + SVG_NS = H.SVG_NS, + win = H.win; + var attr = U.attr, + createElement = U.createElement, + css = U.css, + defined = U.defined, + erase = U.erase, + extend = U.extend, + fireEvent = U.fireEvent, + isArray = U.isArray, + isFunction = U.isFunction, + isNumber = U.isNumber, + isString = U.isString, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick, + pInt = U.pInt, + syncTimeout = U.syncTimeout, + uniqueKey = U.uniqueKey; + /** + * The horizontal alignment of an element. + * + * @typedef {"center"|"left"|"right"} Highcharts.AlignValue + */ + /** + * Options to align the element relative to the chart or another box. + * + * @interface Highcharts.AlignObject + */ /** + * Horizontal alignment. Can be one of `left`, `center` and `right`. + * + * @name Highcharts.AlignObject#align + * @type {Highcharts.AlignValue|undefined} + * + * @default left + */ /** + * Vertical alignment. Can be one of `top`, `middle` and `bottom`. + * + * @name Highcharts.AlignObject#verticalAlign + * @type {Highcharts.VerticalAlignValue|undefined} + * + * @default top + */ /** + * Horizontal pixel offset from alignment. + * + * @name Highcharts.AlignObject#x + * @type {number|undefined} + * + * @default 0 + */ /** + * Vertical pixel offset from alignment. + * + * @name Highcharts.AlignObject#y + * @type {number|undefined} + * + * @default 0 + */ /** + * Use the `transform` attribute with translateX and translateY custom + * attributes to align this elements rather than `x` and `y` attributes. + * + * @name Highcharts.AlignObject#alignByTranslate + * @type {boolean|undefined} + * + * @default false + */ + /** + * Bounding box of an element. + * + * @interface Highcharts.BBoxObject + * @extends Highcharts.PositionObject + */ /** + * Height of the bounding box. + * + * @name Highcharts.BBoxObject#height + * @type {number} + */ /** + * Width of the bounding box. + * + * @name Highcharts.BBoxObject#width + * @type {number} + */ /** + * Horizontal position of the bounding box. + * + * @name Highcharts.BBoxObject#x + * @type {number} + */ /** + * Vertical position of the bounding box. + * + * @name Highcharts.BBoxObject#y + * @type {number} + */ + /** + * 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 + * } + * + * @interface Highcharts.SVGAttributes + */ /** + * @name Highcharts.SVGAttributes#[key:string] + * @type {*} + */ /** + * @name Highcharts.SVGAttributes#d + * @type {string|Highcharts.SVGPathArray|undefined} + */ /** + * @name Highcharts.SVGAttributes#fill + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} + */ /** + * @name Highcharts.SVGAttributes#inverted + * @type {boolean|undefined} + */ /** + * @name Highcharts.SVGAttributes#matrix + * @type {Array<number>|undefined} + */ /** + * @name Highcharts.SVGAttributes#rotation + * @type {number|undefined} + */ /** + * @name Highcharts.SVGAttributes#rotationOriginX + * @type {number|undefined} + */ /** + * @name Highcharts.SVGAttributes#rotationOriginY + * @type {number|undefined} + */ /** + * @name Highcharts.SVGAttributes#scaleX + * @type {number|undefined} + */ /** + * @name Highcharts.SVGAttributes#scaleY + * @type {number|undefined} + */ /** + * @name Highcharts.SVGAttributes#stroke + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} + */ /** + * @name Highcharts.SVGAttributes#style + * @type {string|Highcharts.CSSObject|undefined} + */ /** + * @name Highcharts.SVGAttributes#translateX + * @type {number|undefined} + */ /** + * @name Highcharts.SVGAttributes#translateY + * @type {number|undefined} + */ /** + * @name Highcharts.SVGAttributes#zIndex + * @type {number|undefined} + */ + /** + * An SVG DOM element. The type is a reference to the regular SVGElement in the + * global scope. + * + * @typedef {globals.GlobalSVGElement} Highcharts.SVGDOMElement + * + * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement + */ + /** + * The vertical alignment of an element. + * + * @typedef {"bottom"|"middle"|"top"} Highcharts.VerticalAlignValue + */ + (""); // detach doclets above + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * 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 + * {@link Highcharts.SVGRenderer#rect|rect}, + * {@link Highcharts.SVGRenderer#path|path}, + * {@link Highcharts.SVGRenderer#text|text}, + * {@link Highcharts.SVGRenderer#label|label}, + * {@link Highcharts.SVGRenderer#g|g} + * and more. + * + * @class + * @name Highcharts.SVGElement + */ + var SVGElement = /** @class */ (function () { + function SVGElement() { + /* * + * + * Properties + * + * */ + this.element = void 0; + this.height = void 0; + this.opacity = 1; // Default base for animation + this.renderer = void 0; + this.SVG_NS = SVG_NS; + // Custom attributes used for symbols, these should be filtered out when + // setting SVGElement attributes (#9375). + this.symbolCustomAttribs = [ + "x", + "y", + "width", + "height", + "r", + "start", + "end", + "innerR", + "anchorX", + "anchorY", + "rounded", + ]; + this.width = void 0; + } + /* * * - * @param {boolean} [strict=false] - * Also checks that the object is not an array. + * Functions * - * @return {boolean} - * True if the argument is an object. - */ - function isObject(obj, strict) { - return (!!obj && - typeof obj === 'object' && - (!strict || !isArray(obj))); // eslint-disable-line @typescript-eslint/no-explicit-any - } - H.isObject = isObject; + * */ /** - * Utility function to check if an Object is a HTML Element. + * Get the current value of an attribute or pseudo attribute, + * used mainly for animation. Called internally from + * the {@link Highcharts.SVGRenderer#attr} function. * - * @function Highcharts.isDOMElement + * @private + * @function Highcharts.SVGElement#_defaultGetter * - * @param {*} obj - * The item to check. + * @param {string} key + * Property key. * - * @return {boolean} - * True if the argument is a HTML Element. - */ - var isDOMElement = H.isDOMElement = function isDOMElement(obj) { - return isObject(obj) && typeof obj.nodeType === 'number'; + * @return {number|string} + * Property value. + */ + SVGElement.prototype._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; }; /** - * Utility function to check if an Object is a class. + * @private + * @function Highcharts.SVGElement#_defaultSetter + * + * @param {string} value * - * @function Highcharts.isClass + * @param {string} key * - * @param {object|undefined} obj - * The item to check. + * @param {Highcharts.SVGDOMElement} element * - * @return {boolean} - * True if the argument is a class. + * @return {void} */ - var isClass = H.isClass = function isClass(obj) { - var c = obj && obj.constructor; - return !!(isObject(obj, true) && - !isDOMElement(obj) && - (c && c.name && c.name !== 'Object')); + SVGElement.prototype._defaultSetter = function (value, key, element) { + element.setAttribute(key, value); + }; + /** + * Add the element to the DOM. All elements must be added this way. + * + * @sample highcharts/members/renderer-g + * Elements added to a group + * + * @function Highcharts.SVGElement#add + * + * @param {Highcharts.SVGElement} [parent] + * The parent item to add it to. If undefined, the element is added + * to the {@link Highcharts.SVGRenderer.box}. + * + * @return {Highcharts.SVGElement} + * Returns the SVGElement for chaining. + */ + SVGElement.prototype.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 ( + typeof this.textStr !== "undefined" && + this.element.nodeName === "text" // Not for SVGLabel instances + ) { + 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; + }; + /** + * Add a class name to an element. + * + * @function Highcharts.SVGElement#addClass + * + * @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. + * + * @return {Highcharts.SVGElement} + * Return the SVG element for chainability. + */ + SVGElement.prototype.addClass = function (className, replace) { + var currentClassName = replace ? "" : this.attr("class") || ""; + // Trim the string and remove duplicates + className = (className || "") + .split(/ /g) + .reduce( + function (newClassName, name) { + if (currentClassName.indexOf(name) === -1) { + newClassName.push(name); + } + return newClassName; + }, + currentClassName ? [currentClassName] : [] + ) + .join(" "); + if (className !== currentClassName) { + this.attr("class", className); + } + return this; }; /** - * Utility function to check if an item is a number and it is finite (not NaN, - * Infinity or -Infinity). + * 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. * - * @function Highcharts.isNumber + * @private + * @function Highcharts.SVGElement#afterSetters + */ + SVGElement.prototype.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; + } + }; + /** + * Align the element relative to the chart or another box. * - * @param {*} n - * The item to check. + * @function Highcharts.SVGElement#align * - * @return {boolean} - * True if the item is a finite number - */ - var isNumber = H.isNumber = function isNumber(n) { - return typeof n === 'number' && !isNaN(n) && n < Infinity && n > -Infinity; + * @param {Highcharts.AlignObject} [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 {boolean} [alignByTranslate] + * Align element by translation. + * + * @param {string|Highcharts.BBoxObject} [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. + * + * @return {Highcharts.SVGElement} Returns the SVGElement for chaining. + */ + SVGElement.prototype.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 = void 0; // 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; + // default: left align + x = (box.x || 0) + (alignOptions.x || 0); + // default: top align + y = (box.y || 0) + (alignOptions.y || 0); + // 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; + }; + /** + * @private + * @function Highcharts.SVGElement#alignSetter + * @param {"left"|"center"|"right"} value + */ + SVGElement.prototype.alignSetter = function (value) { + var convert = { + left: "start", + center: "middle", + right: "end", + }; + if (convert[value]) { + this.alignValue = value; + this.element.setAttribute("text-anchor", convert[value]); + } }; /** - * Remove the last occurence of an item from an array. + * Animate to given attributes or CSS properties. * - * @function Highcharts.erase + * @sample highcharts/members/element-on/ + * Setting some attributes by animation * - * @param {Array<*>} arr - * The array. + * @function Highcharts.SVGElement#animate * - * @param {*} item - * The item to remove. + * @param {Highcharts.SVGAttributes} params + * SVG attributes or CSS to animate. * - * @return {void} - */ - var erase = H.erase = function erase(arr, - item) { - var i = arr.length; - while (i--) { - if (arr[i] === item) { - arr.splice(i, 1); - break; - } + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [options] + * Animation options. + * + * @param {Function} [complete] + * Function to perform at the end of animation. + * + * @return {Highcharts.SVGElement} + * Returns the SVGElement for chaining. + */ + SVGElement.prototype.animate = function (params, options, complete) { + var _this = this; + var animOptions = animObject( + pick(options, this.renderer.globalAnimation, true) + ), + deferTime = animOptions.defer; + // When the page is hidden save resources in the background by not + // running animation at all (#9749). + if (pick(doc.hidden, doc.msHidden, doc.webkitHidden, false)) { + animOptions.duration = 0; + } + if (animOptions.duration !== 0) { + // allows using a callback with the global animation without + // overwriting it + if (complete) { + animOptions.complete = complete; } + // If defer option is defined delay the animation #12901 + syncTimeout(function () { + if (_this.element) { + animate(_this, params, animOptions); + } + }, deferTime); + } else { + this.attr(params, void 0, complete); + // Call the end step synchronously + objectEach( + params, + function (val, prop) { + if (animOptions.step) { + animOptions.step.call(this, val, { prop: prop, pos: 1 }); + } + }, + this + ); + } + return this; }; /** - * Check if an object is null or undefined. - * - * @function Highcharts.defined + * 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 + * [example](https://jsfiddle.net/highcharts/43soe9m1/2/). * - * @param {*} obj - * The object to check. + * @example + * // Specific color + * text.css({ + * textOutline: '1px black' + * }); + * // Automatic contrast + * text.css({ + * color: '#000000', // black text + * textOutline: '1px contrast' // => white outline + * }); * - * @return {boolean} - * False if the object is null or undefined, otherwise true. - */ - var defined = H.defined = function defined(obj) { - return typeof obj !== 'undefined' && obj !== null; + * @private + * @function Highcharts.SVGElement#applyTextOutline + * + * @param {string} textOutline + * A custom CSS `text-outline` setting, defined by `width color`. + */ + SVGElement.prototype.applyTextOutline = function (textOutline) { + var elem = this.element, + tspans, + hasContrast = textOutline.indexOf("contrast") !== -1, + styles = {}, + color, + strokeWidth, + firstRealChild; + // 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. + this.removeTextOutline(tspans); + // Check if the element contains RTL characters. + // Comparing against Hebrew and Arabic characters, + // excluding Arabic digits. Source: + // https://www.unicode.org/Public/UNIDATA/extracted/DerivedBidiClass.txt + var isRTL_1 = elem.textContent + ? /^[\u0591-\u065F\u066A-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/.test( + elem.textContent + ) + : false; + // For each of the tspans, create a stroked copy behind it. + firstRealChild = elem.firstChild; + tspans.forEach(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. + // For RTL elements apply outline properties for orginal element + // to prevent outline from overlapping the text. + // For RTL in Firefox keep the orginal order (#10162). + clone = tspan.cloneNode(true); + attr(isRTL_1 && !isFirefox ? tspan : clone, { + class: "highcharts-text-outline", + fill: color, + stroke: color, + "stroke-width": strokeWidth, + "stroke-linejoin": "round", + }); + elem.insertBefore(clone, firstRealChild); + }); + // Create a whitespace between tspan and clone, + // to fix the display of Arabic characters in Firefox. + if (isRTL_1 && isFirefox && tspans[0]) { + var whitespace = tspans[0].cloneNode(true); + whitespace.textContent = " "; + elem.insertBefore(whitespace, firstRealChild); + } + } }; /** - * 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 Highcharts.SVGElement#attr + * @param {string} key + * @return {number|string} + */ /** + * Apply native and custom attributes to the SVG elements. * - * @function Highcharts.attr + * 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. * - * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} elem - * The DOM element to receive the attribute(s). + * Attributes frequently used in Highcharts are `fill`, `stroke`, + * `stroke-width`. * - * @param {string|Highcharts.HTMLAttributes|Highcharts.SVGAttributes} [prop] - * The property or an object of key-value pairs. + * @sample highcharts/members/renderer-rect/ + * Setting some attributes * - * @param {number|string} [value] - * The value if a single property is set. + * @example + * // Set multiple attributes + * element.attr({ + * stroke: 'red', + * fill: 'blue', + * x: 10, + * y: 10 + * }); * - * @return {string|null|undefined} - * When used as a getter, return the value. - */ - function attr(elem, prop, value) { - var ret; - // if the prop is a string - if (isString(prop)) { - // set the value - if (defined(value)) { - elem.setAttribute(prop, value); - // get the value - } - else if (elem && elem.getAttribute) { - ret = elem.getAttribute(prop); - // IE7 and below cannot get class through getAttribute (#7850) - if (!ret && prop === 'class') { - ret = elem.getAttribute(prop + 'Name'); - } + * // Set a single attribute + * element.attr('stroke', 'red'); + * + * // Get an attribute + * element.attr('stroke'); // => 'red' + * + * @function Highcharts.SVGElement#attr + * + * @param {string|Highcharts.SVGAttributes} [hash] + * The native and custom SVG attributes. + * + * @param {number|string|Highcharts.SVGPathArray} [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. + * + * @return {Highcharts.SVGElement} + * If used as a setter, it returns the current + * {@link Highcharts.SVGElement} so the calls can be chained. If + * used as a getter, the current value of the attribute is returned. + */ + SVGElement.prototype.attr = function ( + hash, + val, + complete, + continueAnimation + ) { + var key, + element = this.element, + hasSetSymbolSize, + ret = this, + skipAttr, + setter, + symbolCustomAttribs = this.symbolCustomAttribs; + // single key-value pair + if (typeof hash === "string" && typeof 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 && + symbolCustomAttribs.indexOf(key) !== -1 + ) { + 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.styledMode && + this.shadows && + /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test( + key + ) + ) { + this.updateShadows(key, val, setter); + } } - // else if prop is defined, it is a hash of key/value pairs - } - else { - objectEach(prop, function (val, key) { - elem.setAttribute(key, val); - }); - } - return ret; - } - H.attr = attr; + }, + this + ); + this.afterSetters(); + } + // In accordance with animate, run a complete callback + if (complete) { + complete.call(this); + } + return ret; + }; /** - * Check if an element is an array, and if not, make it into an array. + * Apply a clipping rectangle to this element. * - * @function Highcharts.splat + * @function Highcharts.SVGElement#clip * - * @param {*} obj - * The object to splat. + * @param {Highcharts.ClipRectElement} [clipRect] + * The clipping rectangle. If skipped, the current clip is removed. * - * @return {Array} - * The produced or original array. + * @return {Highcharts.SVGElement} + * Returns the SVG element to allow chaining. */ - var splat = H.splat = function splat(obj) { - return isArray(obj) ? obj : [obj]; + SVGElement.prototype.clip = function (clipRect) { + return this.attr( + "clip-path", + clipRect + ? "url(" + this.renderer.url + "#" + clipRect.id + ")" + : "none" + ); }; /** - * Set a timeout if the delay is given, otherwise perform the function - * synchronously. - * - * @function Highcharts.syncTimeout - * - * @param {Function} fn - * The function callback. - * - * @param {number} delay - * Delay in milliseconds. - * - * @param {*} [context] - * An optional context to send to the function callback. - * - * @return {number} - * An identifier for the timeout that can later be cleared with - * Highcharts.clearTimeout. Returns -1 if there is no timeout. - */ - var syncTimeout = H.syncTimeout = function syncTimeout(fn, - delay, - context) { - if (delay > 0) { - return setTimeout(fn, - delay, - context); - } - fn.call(0, context); - return -1; + * Calculate the coordinates needed for drawing a rectangle crisply and + * return the calculated attributes. + * + * @function Highcharts.SVGElement#crisp + * + * @param {Highcharts.RectangleObject} rect + * Rectangle to crisp. + * + * @param {number} [strokeWidth] + * The stroke width to consider when computing crisp positioning. It can + * also be set directly on the rect parameter. + * + * @return {Highcharts.RectangleObject} + * The modified rectangle arguments. + */ + SVGElement.prototype.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; }; /** - * Internal clear timeout. The function checks that the `id` was not removed - * (e.g. by `chart.destroy()`). For the details see - * [issue #7901](https://github.com/highcharts/highcharts/issues/7901). + * Build and apply an SVG gradient out of a common JavaScript configuration + * object. This function is called from the attribute setters. An event + * hook is added for supporting other complex color types. * - * @function Highcharts.clearTimeout + * @private + * @function Highcharts.SVGElement#complexColor * - * @param {number} id - * Id of a timeout. + * @param {Highcharts.GradientColorObject|Highcharts.PatternObject} colorOptions + * The gradient or pattern options structure. * - * @return {void} - */ - var internalClearTimeout = H.clearTimeout = function (id) { - if (defined(id)) { - clearTimeout(id); + * @param {string} prop + * The property to apply, can either be `fill` or `stroke`. + * + * @param {Highcharts.SVGDOMElement} elem + * SVG element to apply the gradient on. + */ + SVGElement.prototype.complexColor = function ( + colorOptions, + prop, + elem + ) { + var renderer = this.renderer, + colorObject, + gradName, + gradAttr, + radAttr, + gradients, + stops, + stopColor, + stopOpacity, + radialReference, + id, + key = [], + value; + fireEvent( + this.renderer, + "complexColor", + { + args: arguments, + }, + function () { + // Apply linear or radial gradients + if (colorOptions.radialGradient) { + gradName = "radialGradient"; + } else if (colorOptions.linearGradient) { + gradName = "linearGradient"; + } + if (gradName) { + gradAttr = colorOptions[gradName]; + gradients = renderer.gradients; + stops = colorOptions.stops; + radialReference = elem.radialReference; + // Keep < 2.2 kompatibility + if (isArray(gradAttr)) { + colorOptions[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) + ) { + // Save the radial attributes for updating + radAttr = gradAttr; + 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 = uniqueKey(); + var gradientObject_1 = (gradients[key] = renderer + .createElement(gradName) + .attr(gradAttr) + .add(renderer.defs)); + gradientObject_1.radAttr = radAttr; + // The gradient needs to keep a list of stops to be able to + // destroy them + gradientObject_1.stops = []; + stops.forEach(function (stop) { + var stopObject; + if (stop[1].indexOf("rgba") === 0) { + colorObject = Color.parse(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_1); + // Add the stop element to the gradient + gradientObject_1.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) + colorOptions.toString = function () { + return value; + }; + } } + ); }; - /* eslint-disable valid-jsdoc */ /** - * Utility function to extend an object with the members of another. + * 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. * - * @function Highcharts.extend<T> + * @sample highcharts/members/renderer-text-on-chart/ + * Styled text * - * @param {T|undefined} a - * The object to be extended. + * @function Highcharts.SVGElement#css * - * @param {object} b - * The object to add to the first one. - * - * @return {T} - * Object a, the original object. - */ - var extend = H.extend = function extend(a, - b) { - /* eslint-enable valid-jsdoc */ - var n; - if (!a) { - a = {}; + * @param {Highcharts.CSSObject} styles + * The new CSS styles. + * + * @return {Highcharts.SVGElement} + * Return the SVG element for chaining. + */ + SVGElement.prototype.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 (oldStyles && oldStyles[n] !== style) { + 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 + if (styles) { + // Previously set, unset it (#8234) + if (styles.width === null || styles.width === "auto") { + delete this.textWidth; + // Apply new + } else if ( + elem.nodeName.toLowerCase() === "text" && + styles.width + ) { + textWidth = this.textWidth = pInt(styles.width); + } + } + // store object + this.styles = styles; + if (textWidth && !svg && this.renderer.forExport) { + delete styles.width; } - for (n in b) { // eslint-disable-line guard-for-in - a[n] = b[n]; + // Serialize and set style attribute + if (elem.namespaceURI === this.SVG_NS) { + // #7633 + hyphenate = function (a, b) { + return "-" + b.toLowerCase(); + }; + objectEach(styles, function (style, n) { + if (svgPseudoProps.indexOf(n) === -1) { + serializedCss += + n.replace(/([A-Z])/g, hyphenate) + ":" + style + ";"; + } + }); + if (serializedCss) { + attr(elem, "style", serializedCss); // #1881 + } + } else { + css(elem, styles); } - return a; + 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; }; - /* eslint-disable valid-jsdoc */ /** - * Return the first value that is not null or undefined. - * - * @function Highcharts.pick<T> - * - * @param {...Array<T|null|undefined>} items - * Variable number of arguments to inspect. - * - * @return {T} - * The value of the first argument that is not null or undefined. - */ - function pick() { - var args = arguments; - var length = args.length; - for (var i = 0; i < length; i++) { - var arg = args[i]; - if (typeof arg !== 'undefined' && arg !== null) { - return arg; - } + * @private + * @function Highcharts.SVGElement#dashstyleSetter + * @param {string} value + */ + SVGElement.prototype.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) { + var v = 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 = v.length; + while (i--) { + v[i] = "" + pInt(v[i]) * pick(strokeWidth, NaN); } - } - H.pick = pick; + value = v.join(",").replace(/NaN/g, "none"); // #3226 + this.element.setAttribute("stroke-dasharray", value); + } + }; /** - * Set CSS on a given element. - * - * @function Highcharts.css - * - * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} el - * An HTML DOM element. + * Destroy the element and element wrapper and clear up the DOM and event + * hooks. + * + * @function Highcharts.SVGElement#destroy + */ + SVGElement.prototype.destroy = function () { + var wrapper = this, + element = wrapper.element || {}, + renderer = wrapper.renderer, + parentToClean = + (renderer.isSVG && + element.nodeName === "SPAN" && + wrapper.parentGroup) || + void 0, + 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) { + var clipPath_1 = wrapper.clipPath; + // Look for existing references to this clipPath and remove them + // before destroying the element (#6196). + // The upper case version is for Edge + [].forEach.call( + ownerSVGElement.querySelectorAll("[clip-path],[CLIP-PATH]"), + function (el) { + var clipPathAttr = el.getAttribute("clip-path"); + if (clipPathAttr.indexOf(clipPath_1.element.id) > -1) { + el.removeAttribute("clip-path"); + } + } + ); + wrapper.clipPath = clipPath_1.destroy(); + } + // Destroy stops in case this is a gradient object @todo old code? + if (wrapper.stops) { + for (i = 0; i < wrapper.stops.length; i++) { + wrapper.stops[i].destroy(); + } + wrapper.stops.length = 0; + wrapper.stops = void 0; + } + // remove element + wrapper.safeRemoveChild(element); + if (!renderer.styledMode) { + 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(renderer.alignedObjects, wrapper); + } + objectEach(wrapper, function (val, key) { + // Destroy child elements of a group + if ( + wrapper[key] && + wrapper[key].parentGroup === wrapper && + wrapper[key].destroy + ) { + wrapper[key].destroy(); + } + // Delete all properties + delete wrapper[key]; + }); + return; + }; + /** + * Destroy shadows on the element. * - * @param {Highcharts.CSSObject} styles - * Style object with camel case property names. + * @private + * @function Highcharts.SVGElement#destroyShadows * * @return {void} */ - var css = H.css = function css(el, - styles) { - if (H.isMS && !H.svg) { // #2686 - if (styles && typeof styles.opacity !== 'undefined') { - styles.filter = - 'alpha(opacity=' + (styles.opacity * 100) + ')'; - } + SVGElement.prototype.destroyShadows = function () { + (this.shadows || []).forEach(function (shadow) { + this.safeRemoveChild(shadow); + }, this); + this.shadows = void 0; + }; + /** + * @private + */ + SVGElement.prototype.destroyTextPath = function (elem, path) { + var textElement = elem.getElementsByTagName("text")[0]; + var tspans; + if (textElement) { + // Remove textPath attributes + textElement.removeAttribute("dx"); + textElement.removeAttribute("dy"); + // Remove ID's: + path.element.setAttribute("id", ""); + // Check if textElement includes textPath, + if ( + this.textPathWrapper && + textElement.getElementsByTagName("textPath").length + ) { + // Move nodes to <text> + tspans = this.textPathWrapper.element.childNodes; + // Now move all <tspan>'s to the <textPath> node + while (tspans.length) { + textElement.appendChild(tspans[0]); + } + // Remove <textPath> from the DOM + textElement.removeChild(this.textPathWrapper.element); } - extend(el.style, styles); + } else if (elem.getAttribute("dx") || elem.getAttribute("dy")) { + // Remove textPath attributes from elem + // to get correct text-outline position + elem.removeAttribute("dx"); + elem.removeAttribute("dy"); + } + if (this.textPathWrapper) { + // Set textPathWrapper to undefined and destroy it + this.textPathWrapper = this.textPathWrapper.destroy(); + } }; /** - * Utility function to create an HTML element with attributes and styles. - * - * @function Highcharts.createElement - * - * @param {string} tag - * The HTML tag. - * - * @param {Highcharts.HTMLAttributes} [attribs] - * Attributes as an object of key-value pairs. - * - * @param {Highcharts.CSSObject} [styles] - * Styles as an object of key-value pairs. - * - * @param {Highcharts.HTMLDOMElement} [parent] - * The parent HTML object. + * @private + * @function Highcharts.SVGElement#dSettter + * @param {number|string|Highcharts.SVGPathArray} value + * @param {string} key + * @param {Highcharts.SVGDOMElement} element + */ + SVGElement.prototype.dSetter = function (value, key, element) { + if (isArray(value)) { + // Backwards compatibility, convert one-dimensional array into an + // array of segments + if (typeof value[0] === "string") { + value = this.renderer.pathToSegments(value); + } + this.pathArray = value; + value = value.reduce(function (acc, seg, i) { + if (!seg || !seg.join) { + return (seg || "").toString(); + } + return (i ? acc + " " : "") + seg.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; + } + }; + /** + * Fade out an element by animating its opacity down to 0, and hide it on + * complete. Used internally for the tooltip. * - * @param {boolean} [nopad=false] - * If true, remove all padding, border and margin. + * @function Highcharts.SVGElement#fadeOut * - * @return {Highcharts.HTMLDOMElement} - * The created DOM element. + * @param {number} [duration=150] + * The fade duration in milliseconds. */ - var createElement = H.createElement = function createElement(tag, - attribs, - styles, - parent, - nopad) { - var el = doc.createElement(tag); - if (attribs) { - extend(el, attribs); + SVGElement.prototype.fadeOut = function (duration) { + var elemWrapper = this; + elemWrapper.animate( + { + opacity: 0, + }, + { + duration: pick(duration, 150), + complete: function () { + // #3088, assuming we're only using this for tooltips + elemWrapper.attr({ y: -9999 }).hide(); + }, } - if (nopad) { - css(el, { padding: '0', border: 'none', margin: '0' }); + ); + }; + /** + * @private + * @function Highcharts.SVGElement#fillSetter + * @param {Highcharts.ColorType} value + * @param {string} key + * @param {Highcharts.SVGDOMElement} element + */ + SVGElement.prototype.fillSetter = function (value, key, element) { + if (typeof value === "string") { + element.setAttribute(key, value); + } else if (value) { + this.complexColor(value, key, element); + } + }; + /** + * 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. + * + * @sample highcharts/members/renderer-on-chart/ + * Draw a rectangle based on a text's bounding box + * + * @function Highcharts.SVGElement#getBBox + * + * @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. + * + * @return {Highcharts.BBoxObject} + * The bounding box with `x`, `y`, `width` and `height` properties. + */ + SVGElement.prototype.getBBox = function (reload, rot) { + var wrapper = this, + bBox, // = wrapper.bBox, + renderer = wrapper.renderer, + width, + height, + element = wrapper.element, + styles = wrapper.styles, + fontSize, + textStr = wrapper.textStr, + toggleTextShadowShim, + cache = renderer.cache, + cacheKeys = renderer.cacheKeys, + isSVG = element.namespaceURI === wrapper.SVG_NS, + cacheKey; + var rotation = pick(rot, wrapper.rotation, 0); + fontSize = renderer.styledMode + ? element && + SVGElement.prototype.getStyle.call(element, "font-size") + : 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"); } - if (styles) { - css(el, styles); + // Properties that affect bounding box + cacheKey += [ + "", + rotation, + fontSize, + wrapper.textWidth, + styles && styles.textOverflow, + styles && styles.fontWeight, // #12163 + ].join(","); + } + if (cacheKey && !reload) { + bBox = cache[cacheKey]; + } + // No cache found + if (!bBox) { + // SVG elements + if (isSVG || 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) { + [].forEach.call( + element.querySelectorAll(".highcharts-text-outline"), + function (tspan) { + tspan.style.display = display; + } + ); + }; + // Workaround for #3842, Firefox reporting wrong bounding + // box for shadows + if (isFunction(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 (isFunction(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. + // https://jsfiddle.net/highcharts/em37nvuj/ + // (#1101, #1505, #1669, #2568, #6213). + if (isSVG) { + bBox.height = height = + { + "11px,17": 14, + "13px,20": 16, + }[styles && styles.fontSize + "," + Math.round(height)] || + height; + } + // Adjust for rotated text + if (rotation) { + var rad = rotation * deg2rad; + 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)); + } } - if (parent) { - parent.appendChild(el); + // 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 el; + } + return bBox; }; - // eslint-disable-next-line valid-jsdoc /** - * Extend a prototyped class by new members. + * Get the computed style. Only in styled mode. * - * @function Highcharts.extendClass<T> + * @example + * chart.series[0].points[0].graphic.getStyle('stroke-width'); // => '1px' * - * @param {Highcharts.Class<T>} parent - * The parent prototype to inherit. + * @function Highcharts.SVGElement#getStyle * - * @param {Highcharts.Dictionary<*>} members - * A collection of prototype members to add or override compared to the - * parent prototype. + * @param {string} prop + * The property name to check for. * - * @return {Highcharts.Class<T>} - * A new prototype. + * @return {string} + * The current computed value. */ - var extendClass = H.extendClass = function extendClass(parent, - members) { - var obj = (function () { }); - obj.prototype = new parent(); // eslint-disable-line new-cap - extend(obj.prototype, members); - return obj; + SVGElement.prototype.getStyle = function (prop) { + return win + .getComputedStyle(this.element || this, "") + .getPropertyValue(prop); }; /** - * Left-pad a string to a given length by adding a character repetetively. - * - * @function Highcharts.pad + * Check if an element has the given class name. * - * @param {number} number - * The input string or number. + * @function Highcharts.SVGElement#hasClass * - * @param {number} [length] - * The desired string length. + * @param {string} className + * The class name to check for. * - * @param {string} [padder=0] - * The character to pad with. - * - * @return {string} - * The padded string. + * @return {boolean} + * Whether the class name is found. */ - var pad = H.pad = function pad(number, length, padder) { - return new Array((length || 2) + - 1 - - String(number) - .replace('-', '') - .length).join(padder || '0') + number; + SVGElement.prototype.hasClass = function (className) { + return ("" + this.attr("class")).split(" ").indexOf(className) !== -1; }; /** - * Return a length based on either the integer value, or a percentage of a base. - * - * @function Highcharts.relativeLength + * Hide the element, similar to setting the `visibility` attribute to + * `hidden`. * - * @param {Highcharts.RelativeSize} value - * A percentage string or a number. + * @function Highcharts.SVGElement#hide * - * @param {number} base - * The full length that represents 100%. + * @param {boolean} [hideByTranslation=false] + * The flag to determine if element should be hidden by moving out + * of the viewport. Used for example for dataLabels. * - * @param {number} [offset=0] - * A pixel offset to apply for percentage values. Used internally in - * axis positioning. - * - * @return {number} - * The computed length. - */ - var relativeLength = H.relativeLength = function relativeLength(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 Highcharts.wrap - * - * @param {*} 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 {Highcharts.WrapProceedFunction} 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. - */ - var wrap = H.wrap = function wrap(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; - }; + * @return {Highcharts.SVGElement} + * Returns the SVGElement for chaining. + */ + SVGElement.prototype.hide = function (hideByTranslation) { + if (hideByTranslation) { + this.attr({ y: -9999 }); + } else { + this.attr({ visibility: "hidden" }); + } + return this; }; /** - * Format a string according to a subset of the rules of Python's String.format - * method. - * - * @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 - * - * @function Highcharts.format - * - * @param {string} str - * The string to format. - * - * @param {Record<string, *>} ctx - * The context, a collection of key-value pairs where each key is - * replaced by its value. - * - * @param {Highcharts.Chart} [chart] - * A `Chart` instance used to get numberFormatter and time. - * - * @return {string} - * The formatted string. - */ - var format = H.format = function (str, - ctx, - chart) { - var splitter = '{', - isInside = false, - segment, - valueAndFormat, - ret = [], - val, - index; - var floatRegex = /f$/; - var decRegex = /\.([0-9])/; - var lang = H.defaultOptions.lang; - var time = chart && chart.time || H.time; - var numberFormatter = chart && chart.numberFormatter || numberFormat; - 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(':'); - val = getNestedProperty(valueAndFormat.shift() || '', ctx); - // Format the replacement - if (valueAndFormat.length && typeof val === 'number') { - segment = valueAndFormat.join(':'); - if (floatRegex.test(segment)) { // float - var decimals = parseInt((segment.match(decRegex) || ['', '-1'])[1], 10); - if (val !== null) { - val = numberFormatter(val, decimals, lang.decimalPoint, segment.indexOf(',') > -1 ? lang.thousandsSep : ''); - } - } - else { - val = time.dateFormat(segment, val); - } - } - // 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(''); + * @private + */ + SVGElement.prototype.htmlGetBBox = function () { + return { height: 0, width: 0, x: 0, y: 0 }; }; /** - * Get the magnitude of a number. + * Initialize the SVG element. This function only exists to make the + * initialization process overridable. It should not be called directly. + * + * @function Highcharts.SVGElement#init + * + * @param {Highcharts.SVGRenderer} renderer + * The SVGRenderer instance to initialize to. + * + * @param {string} nodeName + * The SVG node name. + */ + SVGElement.prototype.init = function (renderer, nodeName) { + /** + * The primary DOM node. Each `SVGElement` instance wraps a main DOM + * node, but may also represent more nodes. + * + * @name Highcharts.SVGElement#element + * @type {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} + */ + this.element = + nodeName === "span" + ? createElement(nodeName) + : doc.createElementNS(this.SVG_NS, nodeName); + /** + * The renderer that the SVGElement belongs to. + * + * @name Highcharts.SVGElement#renderer + * @type {Highcharts.SVGRenderer} + */ + this.renderer = renderer; + fireEvent(this, "afterInit"); + }; + /** + * 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. * - * @function Highcharts.getMagnitude + * @function Highcharts.SVGElement#invert * - * @param {number} num - * The number. + * @param {boolean} inverted + * Whether to invert or not. An inverted shape can be un-inverted by + * setting it to false. * - * @return {number} - * The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2 etc. + * @return {Highcharts.SVGElement} + * Return the SVGElement for chaining. */ - var getMagnitude = H.getMagnitude = function (num) { - return Math.pow(10, - Math.floor(Math.log(num) / Math.LN10)); + SVGElement.prototype.invert = function (inverted) { + var wrapper = this; + wrapper.inverted = inverted; + wrapper.updateTransform(); + return wrapper; }; /** - * Take an interval and normalize it to multiples of round numbers. - * - * @deprecated - * @function Highcharts.normalizeTickInterval - * - * @param {number} interval - * The raw, un-rounded interval. - * - * @param {Array<*>} [multiples] - * Allowed multiples. - * - * @param {number} [magnitude] - * The magnitude of the number. + * 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 {boolean} [allowDecimals] - * Whether to allow decimals. + * @sample highcharts/members/element-on/ + * A clickable rectangle + * + * @function Highcharts.SVGElement#on + * + * @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. + * + * @return {Highcharts.SVGElement} + * The SVGElement for chaining. + */ + SVGElement.prototype.on = function (eventType, handler) { + var svgElement = this, + element = svgElement.element, + touchStartPos, + touchEventFired; + // touch + if (hasTouch && eventType === "click") { + element.ontouchstart = function (e) { + // save touch position for later calculation + touchStartPos = { + clientX: e.touches[0].clientX, + clientY: e.touches[0].clientY, + }; + }; + // Instead of ontouchstart, event handlers should be called + // on touchend - similar to how current mouseup events are called + element.ontouchend = function (e) { + // hasMoved is a boolean variable containing logic if page + // was scrolled, so if touch position changed more than + // ~4px (value borrowed from general touch handler) + var hasMoved = touchStartPos.clientX + ? Math.sqrt( + Math.pow( + touchStartPos.clientX - e.changedTouches[0].clientX, + 2 + ) + + Math.pow( + touchStartPos.clientY - e.changedTouches[0].clientY, + 2 + ) + ) >= 4 + : false; + if (!hasMoved) { + // only call handlers if page was not scrolled + handler.call(element, e); + } + touchEventFired = true; + if (e.cancelable !== false) { + // prevent other events from being fired. #9682 + e.preventDefault(); + } + }; + element.onclick = function (e) { + // Do not call onclick handler if touch event was fired already. + if (!touchEventFired) { + handler.call(element, e); + } + }; + } else { + // simplest possible event model for internal use + element["on" + eventType] = handler; + } + return this; + }; + /** + * @private + * @function Highcharts.SVGElement#opacitySetter + * @param {string} value + * @param {string} key + * @param {Highcharts.SVGDOMElement} element + */ + SVGElement.prototype.opacitySetter = function (value, key, element) { + // Round off to avoid float errors, like tests where opacity lands on + // 9.86957e-06 instead of 0 + var opacity = Number(Number(value).toFixed(3)); + this.opacity = opacity; + element.setAttribute(key, opacity); + }; + /** + * Remove a class name from the element. * - * @param {boolean} [hasTickAmount] - * If it has tickAmount, avoid landing on tick intervals lower than - * original. + * @function Highcharts.SVGElement#removeClass * - * @return {number} - * The normalized interval. + * @param {string|RegExp} className + * The class name to remove. * - * @todo - * Move this function to the Axis prototype. It is here only for historical - * reasons. + * @return {Highcharts.SVGElement} Returns the SVG element for chainability. */ - var normalizeTickInterval = 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 = 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 = multiples.filter(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; - } + SVGElement.prototype.removeClass = function (className) { + return this.attr( + "class", + ("" + this.attr("class")) + .replace( + isString(className) + ? new RegExp("(^| )" + className + "( |$)") // #12064, #13590 + : className, + " " + ) + .replace(/ +/g, " ") + .trim() + ); + }; + /** + * @private + * @param {Array<Highcharts.SVGDOMElement>} tspans + * Text spans. + */ + SVGElement.prototype.removeTextOutline = function (tspans) { + // Iterate from the end to + // support removing items inside the cycle (#6472). + var i = tspans.length, + tspan; + while (i--) { + tspan = tspans[i]; + if (tspan.getAttribute("class") === "highcharts-text-outline") { + // Remove then erase + erase(tspans, this.element.removeChild(tspan)); } - // Multiply back to the correct magnitude. Correct floats to appropriate - // precision (#6085). - retInterval = 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 Highcharts.stableSort + * Removes an element from the DOM. * - * @param {Array<*>} arr - * The array to sort. - * - * @param {Function} sortFunction - * The function to sort it with, like with regular Array.prototype.sort. + * @private + * @function Highcharts.SVGElement#safeRemoveChild * - * @return {void} + * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element + * The DOM node to remove. */ - var stableSort = H.stableSort = function stableSort(arr, - sortFunction) { - // @todo It seems like Chrome since v70 sorts in a stable way internally, - // plus all other browsers do it, so over time we may be able to remove this - // function - var length = arr.length, - sortValue, - i; - // Add index to each item - for (i = 0; i < length; i++) { - arr[i].safeI = i; // stable sort index + SVGElement.prototype.safeRemoveChild = function (element) { + var parentNode = element.parentNode; + if (parentNode) { + parentNode.removeChild(element); + } + }; + /** + * 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. + * + * @function Highcharts.SVGElement#setRadialReference + * + * @param {Array<number>} coordinates + * The center reference. The format is `[centerX, centerY, diameter]` in + * pixels. + * + * @return {Highcharts.SVGElement} + * Returns the SVGElement for chaining. + */ + SVGElement.prototype.setRadialReference = function (coordinates) { + var existingGradient = + this.element.gradient && + 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; + }; + /** + * @private + * @function Highcharts.SVGElement#setTextPath + * @param {Highcharts.SVGElement} path + * Path to follow. + * @param {Highcharts.DataLabelsTextPathOptionsObject} textPathOptions + * Options. + * @return {Highcharts.SVGElement} + * Returns the SVGElement for chaining. + */ + SVGElement.prototype.setTextPath = function (path, textPathOptions) { + var elem = this.element, + attribsMap = { + textAnchor: "text-anchor", + }, + attrs, + adder = false, + textPathElement, + textPathId, + textPathWrapper = this.textPathWrapper, + tspans, + firstTime = !textPathWrapper; + // Defaults + textPathOptions = merge( + true, + { + enabled: true, + attributes: { + dy: -5, + startOffset: "50%", + textAnchor: "middle", + }, + }, + textPathOptions + ); + attrs = textPathOptions.attributes; + if (path && textPathOptions && textPathOptions.enabled) { + // In case of fixed width for a text, string is rebuilt + // (e.g. ellipsis is applied), so we need to rebuild textPath too + if ( + textPathWrapper && + textPathWrapper.element.parentNode === null + ) { + // When buildText functionality was triggered again + // and deletes textPathWrapper parentNode + firstTime = true; + textPathWrapper = textPathWrapper.destroy(); + } else if (textPathWrapper) { + // Case after drillup when spans were added into + // the DOM outside the textPathWrapper parentGroup + this.removeTextOutline.call( + textPathWrapper.parentGroup, + [].slice.call(elem.getElementsByTagName("tspan")) + ); + } + // label() has padding, text() doesn't + if (this.options && this.options.padding) { + attrs.dx = -this.options.padding; + } + if (!textPathWrapper) { + // Create <textPath>, defer the DOM adder + this.textPathWrapper = textPathWrapper = + this.renderer.createElement("textPath"); + adder = true; + } + textPathElement = textPathWrapper.element; + // Set ID for the path + textPathId = path.element.getAttribute("id"); + if (!textPathId) { + path.element.setAttribute("id", (textPathId = uniqueKey())); + } + // Change DOM structure, by placing <textPath> tag in <text> + if (firstTime) { + tspans = elem.getElementsByTagName("tspan"); + // Now move all <tspan>'s to the <textPath> node + while (tspans.length) { + // Remove "y" from tspans, as Firefox translates them + tspans[0].setAttribute("y", 0); + // Remove "x" from tspans + if (isNumber(attrs.dx)) { + tspans[0].setAttribute("x", -attrs.dx); + } + textPathElement.appendChild(tspans[0]); + } + } + // Add <textPath> to the DOM + if (adder && textPathWrapper) { + textPathWrapper.add({ + // label() is placed in a group, text() is standalone + element: this.text ? this.text.element : elem, + }); + } + // Set basic options: + // Use `setAttributeNS` because Safari needs this.. + textPathElement.setAttributeNS( + "http://www.w3.org/1999/xlink", + "href", + this.renderer.url + "#" + textPathId + ); + // Presentation attributes: + // dx/dy options must by set on <text> (parent), + // the rest should be set on <textPath> + if (defined(attrs.dy)) { + textPathElement.parentNode.setAttribute("dy", attrs.dy); + delete attrs.dy; } - arr.sort(function (a, b) { - sortValue = sortFunction(a, b); - return sortValue === 0 ? a.safeI - b.safeI : sortValue; + if (defined(attrs.dx)) { + textPathElement.parentNode.setAttribute("dx", attrs.dx); + delete attrs.dx; + } + // Additional attributes + objectEach(attrs, function (val, key) { + textPathElement.setAttribute(attribsMap[key] || key, val); }); - // Remove index from items - for (i = 0; i < length; i++) { - delete arr[i].safeI; // stable sort index + // Remove translation, text that follows path does not need that + elem.removeAttribute("transform"); + // Remove shadows and text outlines + this.removeTextOutline.call( + textPathWrapper, + [].slice.call(elem.getElementsByTagName("tspan")) + ); + // Remove background and border for label(), see #10545 + // Alternatively, we can disable setting background rects in + // series.drawDataLabels() + if (this.text && !this.renderer.styledMode) { + this.attr({ + fill: "none", + "stroke-width": 0, + }); + } + // Disable some functions + this.updateTransform = noop; + this.applyTextOutline = noop; + } else if (textPathWrapper) { + // Reset to prototype + delete this.updateTransform; + delete this.applyTextOutline; + // Restore DOM structure: + this.destroyTextPath(elem, path); + // Bring attributes back + this.updateTransform(); + // Set textOutline back for text() + if (this.options && this.options.rotation) { + this.applyTextOutline(this.options.style.textOutline); } + } + return this; }; /** - * 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. + * 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. * - * @function Highcharts.arrayMin + * @example + * renderer.rect(10, 100, 100, 100) + * .attr({ fill: 'red' }) + * .shadow(true); * - * @param {Array<*>} data - * An array of numbers. + * @function Highcharts.SVGElement#shadow * - * @return {number} - * The lowest number. - */ - var arrayMin = H.arrayMin = function arrayMin(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. + * @param {boolean|Highcharts.ShadowOptionsObject} [shadowOptions] + * The shadow options. If `true`, the default options are applied. If + * `false`, the current shadow will be removed. * - * @function Highcharts.arrayMax + * @param {Highcharts.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 {Array<*>} data - * An array of numbers. + * @param {boolean} [cutOff] + * Used internally for column shadows. * - * @return {number} - * The highest number. + * @return {Highcharts.SVGElement} + * Returns the SVGElement for chaining. */ - var arrayMax = H.arrayMax = function arrayMax(data) { - var i = data.length, - max = data[0]; - while (i--) { - if (data[i] > max) { - max = data[i]; - } + SVGElement.prototype.shadow = function (shadowOptions, group, cutOff) { + var shadows = [], + i, + shadow, + element = this.element, + strokeWidth, + shadowElementOpacity, + update = false, + oldShadowOptions = this.oldShadowOptions, + // compensate for inverted plot area + transform; + var defaultShadowOptions = { + color: "#000000", + offsetX: 1, + offsetY: 1, + opacity: 0.15, + width: 3, + }; + var options; + if (shadowOptions === true) { + options = defaultShadowOptions; + } else if (typeof shadowOptions === "object") { + options = extend(defaultShadowOptions, shadowOptions); + } + // Update shadow when options change (#12091). + if (options) { + // Go over each key to look for change + if (options && oldShadowOptions) { + objectEach(options, function (value, key) { + if (value !== oldShadowOptions[key]) { + update = true; + } + }); + } + if (update) { + this.destroyShadows(); + } + this.oldShadowOptions = options; + } + if (!options) { + this.destroyShadows(); + } else if (!this.shadows) { + shadowElementOpacity = options.opacity / options.width; + transform = this.parentInverted + ? "translate(-1,-1)" + : "translate(" + options.offsetX + ", " + options.offsetY + ")"; + for (i = 1; i <= options.width; i++) { + shadow = element.cloneNode(false); + strokeWidth = options.width * 2 + 1 - 2 * i; + attr(shadow, { + stroke: shadowOptions.color || "#000000", + "stroke-opacity": shadowElementOpacity * i, + "stroke-width": strokeWidth, + transform: transform, + fill: "none", + }); + shadow.setAttribute( + "class", + (shadow.getAttribute("class") || "") + " highcharts-shadow" + ); + 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); } - return max; + this.shadows = shadows; + } + return this; }; /** - * 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. + * Show the element after it has been hidden. * - * @function Highcharts.destroyObjectProperties + * @function Highcharts.SVGElement#show * - * @param {*} obj - * The object to destroy properties on. + * @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. * - * @param {*} [except] - * Exception, do not destroy this property, only delete it. + * @return {Highcharts.SVGElement} + * Returns the SVGElement for chaining. */ - var destroyObjectProperties = H.destroyObjectProperties = - function destroyObjectProperties(obj, - except) { - 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]; - }); - }; + SVGElement.prototype.show = function (inherit) { + return this.attr({ visibility: inherit ? "inherit" : "visible" }); + }; /** - * Discard a HTML element by moving it to the bin and delete. + * 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. * - * @function Highcharts.discardElement - * - * @param {Highcharts.HTMLDOMElement} element - * The HTML node to discard. - */ - var discardElement = H.discardElement = function discardElement(element) { - var garbageBin = H.garbageBin; - // create a garbage bin element, not part of the DOM - if (!garbageBin) { - garbageBin = createElement('div'); - } - // move the node and empty bin - if (element) { - garbageBin.appendChild(element); - } - garbageBin.innerHTML = ''; + * @private + * @function Highcharts.SVGElement#strokeSetter + * @param {number|string} value + * @param {string} key + * @param {Highcharts.SVGDOMElement} element + */ + 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; + } else if (this.renderer.styledMode && this["stroke-width"]) { + element.setAttribute("stroke-width", this["stroke-width"]); + this.hasStroke = true; + } }; /** - * Fix JS round off float errors. + * Get the computed stroke width in pixel values. This is used extensively + * when drawing shapes to ensure the shapes are rendered crisp and + * positioned correctly relative to each other. Using + * `shape-rendering: crispEdges` leaves us less control over positioning, + * for example when we want to stack columns next to each other, or position + * things pixel-perfectly within the plot box. * - * @function Highcharts.correctFloat + * The common pattern when placing a shape is: + * - Create the SVGElement and add it to the DOM. In styled mode, it will + * now receive a stroke width from the style sheet. In classic mode we + * will add the `stroke-width` attribute. + * - Read the computed `elem.strokeWidth()`. + * - Place it based on the stroke width. * - * @param {number} num - * A float number to fix. - * - * @param {number} [prec=14] - * The precision. + * @function Highcharts.SVGElement#strokeWidth * * @return {number} - * The corrected float number. - */ - var correctFloat = H.correctFloat = function correctFloat(num, - prec) { - return parseFloat(num.toPrecision(prec || 14)); + * The stroke width in pixels. Even if the given stroke widtch (in CSS or by + * attributes) is based on `em` or other units, the pixel size is returned. + */ + SVGElement.prototype.strokeWidth = function () { + // In non-styled mode, read the stroke width as set by .attr + if (!this.renderer.styledMode) { + return this["stroke-width"] || 0; + } + // In styled mode, read computed stroke width + var val = this.getStyle("stroke-width"), + ret = 0, + dummy; + // Read pixel values directly + if (val.indexOf("px") === val.length - 2) { + ret = pInt(val); + // Other values like em, pt etc need to be measured + } else if (val !== "") { + dummy = doc.createElementNS(SVG_NS, "rect"); + attr(dummy, { + width: val, + "stroke-width": 0, + }); + this.element.parentNode.appendChild(dummy); + ret = dummy.getBBox().width; + dummy.parentNode.removeChild(dummy); + } + return ret; }; /** - * The time unit lookup + * 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 * - * @ignore - */ - var timeUnits = 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 - }; + * @private + * @function Highcharts.SVGElement#symbolAttr + * + * @param {Highcharts.SVGAttributes} hash + * The attributes to set. + */ + SVGElement.prototype.symbolAttr = function (hash) { + var wrapper = this; + [ + "x", + "y", + "r", + "start", + "end", + "width", + "height", + "innerR", + "anchorX", + "anchorY", + "clockwise", + ].forEach(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 + ), + }); + }; /** - * Format a number and return a string based on input settings. - * - * @sample highcharts/members/highcharts-numberformat/ - * Custom number format - * - * @function Highcharts.numberFormat - * - * @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. - * - * @return {string} - * The formatted number. - */ - var numberFormat = H.numberFormat = function numberFormat(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 (!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(pInt(roundedNumber)); - // Leftover after grouping into thousands. Can be 0, 1 or 2. - thousands = strinteger.length > 3 ? strinteger.length % 3 : 0; - // Language - decimalPoint = pick(decimalPoint, lang.decimalPoint); - thousandsSep = 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]; + * @private + * @function Highcharts.SVGElement#textSetter + * @param {string} value + */ + SVGElement.prototype.textSetter = function (value) { + if (value !== this.textStr) { + // Delete size caches when the text changes + // delete this.bBox; // old code in series-label + delete this.textPxLength; + this.textStr = value; + if (this.added) { + this.renderer.buildText(this); } - return ret; + } }; /** - * Easing definition - * * @private - * @function Math.easeInOutSine + * @function Highcharts.SVGElement#titleSetter + * @param {string} value + */ + SVGElement.prototype.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, "") + .replace(/</g, "<") + .replace(/>/g, ">") + ) + ); + }; + /** + * Bring the element to the front. Alternatively, a new zIndex can be set. * - * @param {number} pos - * Current position, ranging from 0 to 1. + * @sample highcharts/members/element-tofront/ + * Click an element to bring it to front * - * @return {number} - * Ease result + * @function Highcharts.SVGElement#toFront + * + * @return {Highcharts.SVGElement} + * Returns the SVGElement for chaining. */ - Math.easeInOutSine = function (pos) { - return -0.5 * (Math.cos(Math.PI * pos) - 1); + SVGElement.prototype.toFront = function () { + var element = this.element; + element.parentNode.appendChild(element); + return this; }; /** - * Returns the value of a property path on a given object. + * Move an object and its children by x and y values. * - * @private - * @function getNestedProperty + * @function Highcharts.SVGElement#translate * - * @param {string} path - * Path to the property, for example `custom.myValue`. + * @param {number} x + * The x value. * - * @param {unknown} obj - * Instance containing the property on the specific path. + * @param {number} y + * The y value. * - * @return {unknown} - * The unknown property value. + * @return {Highcharts.SVGElement} */ - function getNestedProperty(path, obj) { - if (!path) { - return obj; - } - var pathElements = path.split('.').reverse(); - var subProperty = obj; - if (pathElements.length === 1) { - return subProperty[path]; - } - var pathElement = pathElements.pop(); - while (typeof pathElement !== 'undefined' && - typeof subProperty !== 'undefined' && - subProperty !== null) { - subProperty = subProperty[pathElement]; - pathElement = pathElements.pop(); - } - return subProperty; - } + SVGElement.prototype.translate = function (x, y) { + return this.attr({ + translateX: x, + translateY: y, + }); + }; /** - * 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. + * Update the shadow elements with new attributes. * - * @function Highcharts.getStyle - * - * @param {Highcharts.HTMLDOMElement} el - * An HTML element. + * @private + * @function Highcharts.SVGElement#updateShadows * - * @param {string} prop - * The property name. + * @param {string} key + * The attribute name. * - * @param {boolean} [toInt=true] - * Parse to integer. + * @param {number} value + * The value of the attribute. * - * @return {number|string} - * The numeric value. + * @param {Function} setter + * The setter function, inherited from the parent wrapper. */ - var getStyle = H.getStyle = function (el, - prop, - toInt) { - var style; - // For width and height, return the actual inner pixel size (#4913) - if (prop === 'width') { - var offsetWidth = Math.min(el.offsetWidth, - el.scrollWidth); - // In flex boxes, we need to use getBoundingClientRect and floor it, - // because scrollWidth doesn't support subpixel precision (#6427) ... - var boundingClientRectWidth = el.getBoundingClientRect && - el.getBoundingClientRect().width; - // ...unless if the containing div or its parents are transform-scaled - // down, in which case the boundingClientRect can't be used as it is - // also scaled down (#9871, #10498). - if (boundingClientRectWidth < offsetWidth && - boundingClientRectWidth >= offsetWidth - 1) { - offsetWidth = Math.floor(boundingClientRectWidth); - } - return Math.max(0, // #8377 - (offsetWidth - - H.getStyle(el, 'padding-left') - - H.getStyle(el, 'padding-right'))); - } - if (prop === 'height') { - return Math.max(0, // #8377 - 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? - error(27, true); - } - // Otherwise, get the computed style - style = win.getComputedStyle(el, undefined); // eslint-disable-line no-undefined - if (style) { - style = style.getPropertyValue(prop); - if (pick(toInt, prop !== 'opacity')) { - style = pInt(style); - } + SVGElement.prototype.updateShadows = function (key, value, setter) { + var shadows = this.shadows; + if (shadows) { + var 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] + ); } - return style; + } }; /** - * Search for an item in an array. + * 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. * - * @function Highcharts.inArray - * - * @deprecated + * @private + * @function Highcharts.SVGElement#updateTransform + */ + SVGElement.prototype.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(" ")); + } + }; + /** + * @private + * @function Highcharts.SVGElement#visibilitySetter * - * @param {*} item - * The item to search for. + * @param {string} value * - * @param {Array<*>} arr - * The array or node collection to search in. + * @param {string} key * - * @param {number} [fromIndex=0] - * The index to start searching from. + * @param {Highcharts.SVGDOMElement} element * - * @return {number} - * The index within the array, or -1 if not found. + * @return {void} */ - var inArray = H.inArray = function (item, - arr, - fromIndex) { - error(32, - false, - void 0, { 'Highcharts.inArray': 'use Array.indexOf' }); - return arr.indexOf(item, fromIndex); + SVGElement.prototype.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; }; - /* eslint-disable valid-jsdoc */ - /** - * Return the value of the first element in the array that satisfies the - * provided testing function. - * - * @function Highcharts.find<T> - * - * @param {Array<T>} 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. - * - * @return {T|undefined} - * The value of the element. - */ - var find = H.find = Array.prototype.find ? - /* eslint-enable valid-jsdoc */ - function (arr, - callback) { - return arr.find(callback); - } : - // Legacy implementation. PhantomJS, IE <= 11 etc. #7223. - function (arr, callback) { - var i, - length = arr.length; - for (i = 0; i < length; i++) { - if (callback(arr[i], i)) { // eslint-disable-line callback-return - return arr[i]; - } - } - }; /** - * Returns an array of a given object's own properties. - * - * @function Highcharts.keys - * @deprecated + * @private + * @function Highcharts.SVGElement#xGetter * - * @param {*} obj - * The object of which the properties are to be returned. + * @param {string} key * - * @return {Array<string>} - * An array of strings that represents all the properties. + * @return {number|string|null} */ - H.keys = function (obj) { - error(32, false, void 0, { 'Highcharts.keys': 'use Object.keys' }); - return Object.keys(obj); + SVGElement.prototype.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 element's offset position, corrected for `overflow: auto`. - * - * @function Highcharts.offset - * - * @param {global.Element} el - * The DOM element. - * - * @return {Highcharts.OffsetObject} - * An object containing `left` and `top` properties for the position in - * the page. - */ - var offset = H.offset = function offset(el) { - var docElem = doc.documentElement, - box = (el.parentElement || el.parentNode) ? - 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) - }; + * @private + * @function Highcharts.SVGElement#zIndexSetter + * @param {number} [value] + * @param {string} [key] + * @return {boolean} + */ + SVGElement.prototype.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 = false, + undefinedOtherZIndex, + svgParent = parentNode === renderer.box, + run = this.added, + i; + if (defined(value)) { + // So we can read it for other elements in the group + element.setAttribute("data-z-index", value); + value = +value; + if (this[key] === value) { + // Only update when needed (#3865) + run = false; + } + } else if (defined(this[key])) { + element.removeAttribute("data-z-index"); + } + 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.getAttribute("data-z-index"); + undefinedOtherZIndex = !defined(otherZIndex); + if (otherElement !== element) { + if ( + // Negative zIndex versus no zIndex: + // On all levels except the highest. If the parent is + // <svg>, then we don't want to put items before <desc> + // or <defs> + 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; }; - /* eslint-disable valid-jsdoc */ - /** - * Iterate over object key pairs in an object. - * - * @function Highcharts.objectEach<T> + return SVGElement; + })(); + // Some shared setters and getters + SVGElement.prototype["stroke-widthSetter"] = + SVGElement.prototype.strokeSetter; + SVGElement.prototype.yGetter = SVGElement.prototype.xGetter; + SVGElement.prototype.matrixSetter = + SVGElement.prototype.rotationOriginXSetter = + SVGElement.prototype.rotationOriginYSetter = + SVGElement.prototype.rotationSetter = + SVGElement.prototype.scaleXSetter = + SVGElement.prototype.scaleYSetter = + SVGElement.prototype.translateXSetter = + SVGElement.prototype.translateYSetter = + SVGElement.prototype.verticalAlignSetter = + function (value, key) { + this[key] = value; + this.doTransform = true; + }; + H.SVGElement = SVGElement; + + return H.SVGElement; + } + ); + _registerModule( + _modules, + "Core/Renderer/SVG/SVGLabel.js", + [ + _modules["Core/Renderer/SVG/SVGElement.js"], + _modules["Core/Utilities.js"], + ], + function (SVGElement, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var __extends = + (this && this.__extends) || + (function () { + var extendStatics = function (d, b) { + extendStatics = + Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && + function (d, b) { + d.__proto__ = b; + }) || + function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { + this.constructor = d; + } + d.prototype = + b === null + ? Object.create(b) + : ((__.prototype = b.prototype), new __()); + }; + })(); + var defined = U.defined, + extend = U.extend, + isNumber = U.isNumber, + merge = U.merge, + removeEvent = U.removeEvent; + /** + * SVG label to render text. + * @private + * @class + * @name Highcharts.SVGLabel + * @augments Highcharts.SVGElement + */ + var SVGLabel = /** @class */ (function (_super) { + __extends(SVGLabel, _super); + /* * * - * @param {*} obj - * The object to iterate over. + * Constructors * - * @param {Highcharts.ObjectEachCallbackFunction<T>} 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. + * */ + function SVGLabel( + renderer, + str, + x, + y, + shape, + anchorX, + anchorY, + useHTML, + baseline, + className + ) { + var _this = _super.call(this) || this; + _this.init(renderer, "g"); + _this.textStr = str; + _this.x = x; + _this.y = y; + _this.anchorX = anchorX; + _this.anchorY = anchorY; + _this.baseline = baseline; + _this.className = className; + if (className !== "button") { + _this.addClass("highcharts-label"); + } + if (className) { + _this.addClass("highcharts-" + className); + } + _this.text = renderer.text("", 0, 0, useHTML).attr({ + zIndex: 1, + }); + // Validate the shape argument + var hasBGImage; + if (typeof shape === "string") { + hasBGImage = /^url\((.*?)\)$/.test(shape); + if (_this.renderer.symbols[shape] || hasBGImage) { + _this.symbolKey = shape; + } + } + _this.bBox = SVGLabel.emptyBBox; + _this.padding = 3; + _this.paddingLeft = 0; + _this.baselineOffset = 0; + _this.needsBox = renderer.styledMode || hasBGImage; + _this.deferredAttr = {}; + _this.alignFactor = 0; + return _this; + } + /* * * - * @param {T} [ctx] - * The context. + * Functions * - * @return {void} - */ - var objectEach = H.objectEach = function objectEach(obj, - fn, - ctx) { - /* eslint-enable valid-jsdoc */ - for (var key in obj) { - if (Object.hasOwnProperty.call(obj, - key)) { - fn.call(ctx || obj[key], - obj[key], + * */ + SVGLabel.prototype.alignSetter = function (value) { + var alignFactor = { + left: 0, + center: 0.5, + right: 1, + }[value]; + if (alignFactor !== this.alignFactor) { + this.alignFactor = alignFactor; + // Bounding box exists, means we're dynamically changing + if (this.bBox && isNumber(this.xSetting)) { + this.attr({ x: this.xSetting }); // #5134 + } + } + }; + SVGLabel.prototype.anchorXSetter = function (value, key) { + this.anchorX = value; + this.boxAttr( key, - obj); - } + Math.round(value) - this.getCrispAdjust() - this.xSetting + ); + }; + SVGLabel.prototype.anchorYSetter = function (value, key) { + this.anchorY = value; + this.boxAttr(key, value - this.ySetting); + }; + /* + * Set a box attribute, or defer it if the box is not yet created + */ + SVGLabel.prototype.boxAttr = function (key, value) { + if (this.box) { + this.box.attr(key, value); + } else { + this.deferredAttr[key] = value; + } + }; + /* + * Pick up some properties and apply them to the text instead of the + * wrapper. + */ + SVGLabel.prototype.css = function (styles) { + if (styles) { + var textStyles = {}, + isWidth, + isFontStyle; + // Create a copy to avoid altering the original object + // (#537) + styles = merge(styles); + SVGLabel.textProps.forEach(function (prop) { + if (typeof styles[prop] !== "undefined") { + textStyles[prop] = styles[prop]; + delete styles[prop]; + } + }); + this.text.css(textStyles); + isWidth = "width" in textStyles; + isFontStyle = + "fontSize" in textStyles || "fontWeight" in textStyles; + // Update existing text, box (#9400, #12163) + if (isWidth || isFontStyle) { + this.updateBoxSize(); + // Keep updated (#9400, #12163) + if (isFontStyle) { + this.updateTextPadding(); + } } + } + return SVGElement.prototype.css.call(this, styles); }; - /** - * Iterate over an array. - * - * @deprecated - * @function Highcharts.each + /* + * Destroy and release memory. + */ + SVGLabel.prototype.destroy = function () { + // Added by button implementation + removeEvent(this.element, "mouseenter"); + removeEvent(this.element, "mouseleave"); + if (this.text) { + this.text.destroy(); + } + if (this.box) { + this.box = this.box.destroy(); + } + // Call base implementation to destroy the rest + SVGElement.prototype.destroy.call(this); + return void 0; + }; + SVGLabel.prototype.fillSetter = function (value, key) { + if (value) { + this.needsBox = true; + } + // for animation getter (#6776) + this.fill = value; + this.boxAttr(key, value); + }; + /* + * Return the bounding box of the box, not the group. + */ + SVGLabel.prototype.getBBox = function () { + var bBox = this.bBox; + var padding = this.padding; + return { + width: bBox.width + 2 * padding, + height: bBox.height + 2 * padding, + x: bBox.x - padding, + y: bBox.y - padding, + }; + }; + SVGLabel.prototype.getCrispAdjust = function () { + return this.renderer.styledMode && this.box + ? (this.box.strokeWidth() % 2) / 2 + : ((this["stroke-width"] ? parseInt(this["stroke-width"], 10) : 0) % + 2) / + 2; + }; + SVGLabel.prototype.heightSetter = function (value) { + this.heightSetting = value; + }; + // Event handling. In case of useHTML, we need to make sure that events + // are captured on the span as well, and that mouseenter/mouseleave + // between the SVG group and the HTML span are not treated as real + // enter/leave events. #13310. + SVGLabel.prototype.on = function (eventType, handler) { + var label = this; + var text = label.text; + var span = text && text.element.tagName === "SPAN" ? text : void 0; + var selectiveHandler; + if (span) { + selectiveHandler = function (e) { + if ( + (eventType === "mouseenter" || eventType === "mouseleave") && + e.relatedTarget instanceof Element && + (label.element.contains(e.relatedTarget) || + span.element.contains(e.relatedTarget)) + ) { + return; + } + handler.call(label.element, e); + }; + span.on(eventType, selectiveHandler); + } + SVGElement.prototype.on.call( + label, + eventType, + selectiveHandler || handler + ); + return label; + }; + /* + * After the text element is added, get the desired size of the border + * box and add it before the text in the DOM. + */ + SVGLabel.prototype.onAdd = function () { + var str = this.textStr; + this.text.add(this); + this.attr({ + // Alignment is available now (#3295, 0 not rendered if given + // as a value) + text: defined(str) ? str : "", + x: this.x, + y: this.y, + }); + if (this.box && defined(this.anchorX)) { + this.attr({ + anchorX: this.anchorX, + anchorY: this.anchorY, + }); + } + }; + SVGLabel.prototype.paddingSetter = function (value) { + if (defined(value) && value !== this.padding) { + this.padding = value; + this.updateTextPadding(); + } + }; + SVGLabel.prototype.paddingLeftSetter = function (value) { + if (defined(value) && value !== this.paddingLeft) { + this.paddingLeft = value; + this.updateTextPadding(); + } + }; + SVGLabel.prototype.rSetter = function (value, key) { + this.boxAttr(key, value); + }; + SVGLabel.prototype.shadow = function (b) { + if (b && !this.renderer.styledMode) { + this.updateBoxSize(); + if (this.box) { + this.box.shadow(b); + } + } + return this; + }; + SVGLabel.prototype.strokeSetter = function (value, key) { + // for animation getter (#6776) + this.stroke = value; + this.boxAttr(key, value); + }; + SVGLabel.prototype["stroke-widthSetter"] = function (value, key) { + if (value) { + this.needsBox = true; + } + this["stroke-width"] = value; + this.boxAttr(key, value); + }; + SVGLabel.prototype["text-alignSetter"] = function (value) { + this.textAlign = value; + }; + SVGLabel.prototype.textSetter = function (text) { + if (typeof text !== "undefined") { + // Must use .attr to ensure transforms are done (#10009) + this.text.attr({ text: text }); + } + this.updateBoxSize(); + this.updateTextPadding(); + }; + /* + * 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. + */ + SVGLabel.prototype.updateBoxSize = function () { + var style = this.text.element.style, + crispAdjust, + attribs = {}; + var padding = this.padding; + var paddingLeft = this.paddingLeft; + // #12165 error when width is null (auto) + // #12163 when fontweight: bold, recalculate bBox withot cache + // #3295 && 3514 box failure when string equals 0 + var bBox = + (!isNumber(this.widthSetting) || + !isNumber(this.heightSetting) || + this.textAlign) && + defined(this.text.textStr) + ? this.text.getBBox() + : SVGLabel.emptyBBox; + this.width = + (this.widthSetting || bBox.width || 0) + 2 * padding + paddingLeft; + this.height = (this.heightSetting || bBox.height || 0) + 2 * padding; + // Update the label-scoped y offset. Math.min because of inline + // style (#9400) + this.baselineOffset = + padding + + Math.min( + this.renderer.fontMetrics(style && style.fontSize, this.text).b, + // When the height is 0, there is no bBox, so go with the font + // metrics. Highmaps CSS demos. + bBox.height || Infinity + ); + if (this.needsBox) { + // Create the border box if it is not already present + if (!this.box) { + // Symbol definition exists (#5324) + var box = (this.box = this.symbolKey + ? this.renderer.symbol(this.symbolKey) + : this.renderer.rect()); + box.addClass( + // Don't use label className for buttons + (this.className === "button" ? "" : "highcharts-label-box") + + (this.className + ? " highcharts-" + this.className + "-box" + : "") + ); + box.add(this); + crispAdjust = this.getCrispAdjust(); + attribs.x = crispAdjust; + attribs.y = + (this.baseline ? -this.baselineOffset : 0) + crispAdjust; + } + // Apply the box attributes + attribs.width = Math.round(this.width); + attribs.height = Math.round(this.height); + this.box.attr(extend(attribs, this.deferredAttr)); + this.deferredAttr = {}; + } + this.bBox = bBox; + }; + /* + * This function runs after setting text or padding, but only if padding + * is changed. + */ + SVGLabel.prototype.updateTextPadding = function () { + var text = this.text; + // Determine y based on the baseline + var textY = this.baseline ? 0 : this.baselineOffset; + var textX = this.paddingLeft + this.padding; + // compensate for alignment + if ( + defined(this.widthSetting) && + this.bBox && + (this.textAlign === "center" || this.textAlign === "right") + ) { + textX += + { center: 0.5, right: 1 }[this.textAlign] * + (this.widthSetting - this.bBox.width); + } + // update if anything changed + if (textX !== text.x || textY !== text.y) { + text.attr("x", textX); + // #8159 - prevent misplaced data labels in treemap + // (useHTML: true) + if (text.hasBoxWidthChanged) { + this.bBox = text.getBBox(true); + this.updateBoxSize(); + } + if (typeof textY !== "undefined") { + text.attr("y", textY); + } + } + // record current values + text.x = textX; + text.y = textY; + }; + SVGLabel.prototype.widthSetter = function (value) { + // width:auto => null + this.widthSetting = isNumber(value) ? value : void 0; + }; + SVGLabel.prototype.xSetter = function (value) { + this.x = value; // for animation getter + if (this.alignFactor) { + value -= + this.alignFactor * + ((this.widthSetting || this.bBox.width) + 2 * this.padding); + // Force animation even when setting to the same value (#7898) + this["forceAnimate:x"] = true; + } + this.xSetting = Math.round(value); + this.attr("translateX", this.xSetting); + }; + SVGLabel.prototype.ySetter = function (value) { + this.ySetting = this.y = Math.round(value); + this.attr("translateY", this.ySetting); + }; + /* * * - * @param {Array<*>} arr - * The array to iterate over. + * Static Properties * - * @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. + * */ + SVGLabel.emptyBBox = { width: 0, height: 0, x: 0, y: 0 }; + /* * * - * @param {*} [ctx] - * The context. + * Properties * - * @return {void} - */ + * */ /** - * Filter an array by a callback. + * For labels, these CSS properties are applied to the `text` node directly. * - * @deprecated - * @function Highcharts.grep + * @private + * @name Highcharts.SVGLabel#textProps + * @type {Array<string>} + */ + SVGLabel.textProps = [ + "color", + "cursor", + "direction", + "fontFamily", + "fontSize", + "fontStyle", + "fontWeight", + "lineHeight", + "textAlign", + "textDecoration", + "textOutline", + "textOverflow", + "width", + ]; + return SVGLabel; + })(SVGElement); + + return SVGLabel; + } + ); + _registerModule( + _modules, + "Core/Renderer/SVG/SVGRenderer.js", + [ + _modules["Core/Color/Color.js"], + _modules["Core/Globals.js"], + _modules["Core/Renderer/SVG/SVGElement.js"], + _modules["Core/Renderer/SVG/SVGLabel.js"], + _modules["Core/Utilities.js"], + ], + function (Color, H, SVGElement, SVGLabel, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var addEvent = U.addEvent, + attr = U.attr, + createElement = U.createElement, + css = U.css, + defined = U.defined, + destroyObjectProperties = U.destroyObjectProperties, + extend = U.extend, + isArray = U.isArray, + isNumber = U.isNumber, + isObject = U.isObject, + isString = U.isString, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick, + pInt = U.pInt, + splat = U.splat, + uniqueKey = U.uniqueKey; + /** + * 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); + * + * @typedef {Highcharts.SVGElement} Highcharts.ClipRectElement + */ + /** + * The font metrics. + * + * @interface Highcharts.FontMetricsObject + */ /** + * The baseline relative to the top of the box. + * + * @name Highcharts.FontMetricsObject#b + * @type {number} + */ /** + * The font size. + * + * @name Highcharts.FontMetricsObject#f + * @type {number} + */ /** + * The line height. + * + * @name Highcharts.FontMetricsObject#h + * @type {number} + */ + /** + * An object containing `x` and `y` properties for the position of an element. + * + * @interface Highcharts.PositionObject + */ /** + * X position of the element. + * @name Highcharts.PositionObject#x + * @type {number} + */ /** + * Y position of the element. + * @name Highcharts.PositionObject#y + * @type {number} + */ + /** + * A rectangle. + * + * @interface Highcharts.RectangleObject + */ /** + * Height of the rectangle. + * @name Highcharts.RectangleObject#height + * @type {number} + */ /** + * Width of the rectangle. + * @name Highcharts.RectangleObject#width + * @type {number} + */ /** + * Horizontal position of the rectangle. + * @name Highcharts.RectangleObject#x + * @type {number} + */ /** + * Vertical position of the rectangle. + * @name Highcharts.RectangleObject#y + * @type {number} + */ + /** + * The shadow options. + * + * @interface Highcharts.ShadowOptionsObject + */ /** + * The shadow color. + * @name Highcharts.ShadowOptionsObject#color + * @type {Highcharts.ColorString|undefined} + * @default #000000 + */ /** + * The horizontal offset from the element. + * + * @name Highcharts.ShadowOptionsObject#offsetX + * @type {number|undefined} + * @default 1 + */ /** + * The vertical offset from the element. + * @name Highcharts.ShadowOptionsObject#offsetY + * @type {number|undefined} + * @default 1 + */ /** + * The shadow opacity. + * + * @name Highcharts.ShadowOptionsObject#opacity + * @type {number|undefined} + * @default 0.15 + */ /** + * The shadow width or distance from the element. + * @name Highcharts.ShadowOptionsObject#width + * @type {number|undefined} + * @default 3 + */ + /** + * @interface Highcharts.SizeObject + */ /** + * @name Highcharts.SizeObject#height + * @type {number} + */ /** + * @name Highcharts.SizeObject#width + * @type {number} + */ + /** + * Serialized form of an SVG definition, including children. Some key + * property names are reserved: tagName, textContent, and children. + * + * @interface Highcharts.SVGDefinitionObject + */ /** + * @name Highcharts.SVGDefinitionObject#[key:string] + * @type {boolean|number|string|Array<Highcharts.SVGDefinitionObject>|undefined} + */ /** + * @name Highcharts.SVGDefinitionObject#children + * @type {Array<Highcharts.SVGDefinitionObject>|undefined} + */ /** + * @name Highcharts.SVGDefinitionObject#tagName + * @type {string|undefined} + */ /** + * @name Highcharts.SVGDefinitionObject#textContent + * @type {string|undefined} + */ + /** + * Array of path commands, that will go into the `d` attribute of an SVG + * element. + * + * @typedef {Array<(Array<Highcharts.SVGPathCommand>|Array<Highcharts.SVGPathCommand,number>|Array<Highcharts.SVGPathCommand,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number,number,number,number>)>} Highcharts.SVGPathArray + */ + /** + * Possible path commands in an SVG path array. Valid values are `A`, `C`, `H`, + * `L`, `M`, `Q`, `S`, `T`, `V`, `Z`. + * + * @typedef {string} Highcharts.SVGPathCommand + * @validvalue ["a","c","h","l","m","q","s","t","v","z","A","C","H","L","M","Q","S","T","V","Z"] + */ + /** + * An extendable collection of functions for defining symbol paths. Symbols are + * used internally for point markers, button and label borders and backgrounds, + * or custom shapes. Extendable by adding to {@link SVGRenderer#symbols}. + * + * @interface Highcharts.SymbolDictionary + */ /** + * @name Highcharts.SymbolDictionary#[key:string] + * @type {Function|undefined} + */ /** + * @name Highcharts.SymbolDictionary#arc + * @type {Function|undefined} + */ /** + * @name Highcharts.SymbolDictionary#callout + * @type {Function|undefined} + */ /** + * @name Highcharts.SymbolDictionary#circle + * @type {Function|undefined} + */ /** + * @name Highcharts.SymbolDictionary#diamond + * @type {Function|undefined} + */ /** + * @name Highcharts.SymbolDictionary#square + * @type {Function|undefined} + */ /** + * @name Highcharts.SymbolDictionary#triangle + * @type {Function|undefined} + */ + /** + * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`, `triangle`, + * and `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}. + * + * @typedef {"arc"|"callout"|"circle"|"diamond"|"square"|"triangle"|"triangle-down"} Highcharts.SymbolKeyValue + */ + /** + * Additional options, depending on the actual symbol drawn. + * + * @interface Highcharts.SymbolOptionsObject + */ /** + * The anchor X position for the `callout` symbol. This is where the chevron + * points to. + * + * @name Highcharts.SymbolOptionsObject#anchorX + * @type {number|undefined} + */ /** + * The anchor Y position for the `callout` symbol. This is where the chevron + * points to. + * + * @name Highcharts.SymbolOptionsObject#anchorY + * @type {number|undefined} + */ /** + * The end angle of an `arc` symbol. + * + * @name Highcharts.SymbolOptionsObject#end + * @type {number|undefined} + */ /** + * Whether to draw `arc` symbol open or closed. + * + * @name Highcharts.SymbolOptionsObject#open + * @type {boolean|undefined} + */ /** + * The radius of an `arc` symbol, or the border radius for the `callout` symbol. + * + * @name Highcharts.SymbolOptionsObject#r + * @type {number|undefined} + */ /** + * The start angle of an `arc` symbol. + * + * @name Highcharts.SymbolOptionsObject#start + * @type {number|undefined} + */ + /* eslint-disable no-invalid-this, valid-jsdoc */ + var charts = H.charts, + deg2rad = H.deg2rad, + doc = H.doc, + isFirefox = H.isFirefox, + isMS = H.isMS, + isWebKit = H.isWebKit, + noop = H.noop, + svg = H.svg, + SVG_NS = H.SVG_NS, + symbolSizes = H.symbolSizes, + win = H.win; + /** + * 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. + * + * @sample highcharts/members/renderer-on-chart + * Annotating a chart programmatically. + * @sample highcharts/members/renderer-basic + * Independent SVG drawing. + * + * @example + * // Use directly without a chart object. + * var renderer = new Highcharts.Renderer(parentNode, 600, 400); + * + * @class + * @name Highcharts.SVGRenderer + * + * @param {Highcharts.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 {Highcharts.CSSObject} [style] + * The box style, if not in styleMode + * + * @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. + * + * @param {boolean} [styledMode=false] + * Whether the renderer belongs to a chart that is in styled mode. + * If it does, it will avoid setting presentational attributes in + * some cases, but not when set explicitly through `.attr` and `.css` + * etc. + */ + var SVGRenderer = /** @class */ (function () { + /* * * - * @param {Array<*>} arr - * The array to filter. + * Constructors + * + * */ + function SVGRenderer( + container, + width, + height, + style, + forExport, + allowHTML, + styledMode + ) { + /* * + * + * Properties + * + * */ + this.alignedObjects = void 0; + /** + * The root `svg` node of the renderer. + * + * @name Highcharts.SVGRenderer#box + * @type {Highcharts.SVGDOMElement} + */ + this.box = void 0; + /** + * The wrapper for the root `svg` node of the renderer. + * + * @name Highcharts.SVGRenderer#boxWrapper + * @type {Highcharts.SVGElement} + */ + this.boxWrapper = void 0; + this.cache = void 0; + this.cacheKeys = void 0; + this.chartIndex = void 0; + /** + * A pointer to the `defs` node of the root SVG. + * + * @name Highcharts.SVGRenderer#defs + * @type {Highcharts.SVGElement} + */ + this.defs = void 0; + this.globalAnimation = void 0; + this.gradients = void 0; + this.height = void 0; + this.imgCount = void 0; + this.isSVG = void 0; + this.style = void 0; + /** + * Page url used for internal references. + * + * @private + * @name Highcharts.SVGRenderer#url + * @type {string} + */ + this.url = void 0; + this.width = void 0; + this.init( + container, + width, + height, + style, + forExport, + allowHTML, + styledMode + ); + } + /* * * - * @param {Function} callback - * The callback function. The function receives the item as the first - * argument. Return `true` if the item is to be preserved. + * Functions * - * @return {Array<*>} - * A new, filtered array. - */ + * */ /** - * Map an array by a callback. + * Initialize the SVGRenderer. Overridable initializer function that takes + * the same parameters as the constructor. * - * @deprecated - * @function Highcharts.map - * - * @param {Array<*>} arr - * The array to map. + * @function Highcharts.SVGRenderer#init * - * @param {Function} fn - * The callback function. Return the new value for the new array. + * @param {Highcharts.HTMLDOMElement} container + * Where to put the SVG in the web page. * - * @return {Array<*>} - * A new array item with modified items. - */ - /** - * Reduce an array to a single value. + * @param {number} width + * The width of the SVG. * - * @deprecated - * @function Highcharts.reduce + * @param {number} height + * The height of the SVG. * - * @param {Array<*>} arr - * The array to reduce. + * @param {Highcharts.CSSObject} [style] + * The box style, if not in styleMode * - * @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 {boolean} [forExport=false] + * Whether the rendered content is intended for export. * - * @param {*} initialValue - * The initial value of the accumulator. + * @param {boolean} [allowHTML=true] + * Whether the renderer is allowed to include HTML text, which will be + * projected on top of the SVG. * - * @return {*} - * The reduced value. - */ + * @param {boolean} [styledMode=false] + * Whether the renderer belongs to a chart that is in styled mode. If it + * does, it will avoid setting presentational attributes in some cases, but + * not when set explicitly through `.attr` and `.css` etc. + */ + SVGRenderer.prototype.init = function ( + container, + width, + height, + style, + forExport, + allowHTML, + styledMode + ) { + var renderer = this, + boxWrapper, + element, + desc; + boxWrapper = renderer.createElement("svg").attr({ + version: "1.1", + class: "highcharts-root", + }); + if (!styledMode) { + boxWrapper.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; + this.box = element; + this.boxWrapper = boxWrapper; + renderer.alignedObjects = []; + // #24, #672, #1070 + this.url = + (isFirefox || isWebKit) && doc.getElementsByTagName("base").length + ? win.location.href + .split("#")[0] // 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 8.2.2") + ); + renderer.defs = this.createElement("defs").add(); + renderer.allowHTML = allowHTML; + renderer.forExport = forExport; + renderer.styledMode = styledMode; + 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); + } + }; + /** + * General method for adding a definition to the SVG `defs` tag. Can be used + * for gradients, fills, filters etc. Styled mode only. A hook for adding + * general definitions to the SVG's defs tag. Definitions can be referenced + * from the CSS by its `id`. Read more in + * [gradients, shadows and patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns). + * Styled mode only. + * + * @function Highcharts.SVGRenderer#definition + * + * @param {Highcharts.SVGDefinitionObject} def + * A serialized form of an SVG definition, including children. + * + * @return {Highcharts.SVGElement} + * The inserted node. + */ + SVGRenderer.prototype.definition = function (def) { + var ren = this; + /** + * @private + * @param {Highcharts.SVGDefinitionObject} config - SVG definition + * @param {Highcharts.SVGElement} [parent] - parent node + */ + function recurse(config, parent) { + var ret; + splat(config).forEach(function (item) { + var node = ren.createElement(item.tagName), + attr = {}; + // Set attributes + objectEach(item, function (val, key) { + if ( + key !== "tagName" && + key !== "children" && + key !== "textContent" + ) { + attr[key] = val; + } + }); + node.attr(attr); + // Add to the tree + node.add(parent || ren.defs); + // Add text content + if (item.textContent) { + node.element.appendChild(doc.createTextNode(item.textContent)); + } + // Recurse + recurse(item.children || [], node); + ret = node; + }); + // Return last node added (on top level it's the only one) + return ret; + } + return recurse(def); + }; /** - * Test whether at least one element in the array passes the test implemented by - * the provided function. + * Get the global style setting for the renderer. * - * @deprecated - * @function Highcharts.some + * @private + * @function Highcharts.SVGRenderer#getStyle * - * @param {Array<*>} arr - * The array to test + * @param {Highcharts.CSSObject} style + * Style settings. * - * @param {Function} fn - * The function to run on each item. Return truty to pass the test. - * Receives arguments `currentValue`, `index` and `array`. + * @return {Highcharts.CSSObject} + * The style settings mixed with defaults. + */ + SVGRenderer.prototype.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 {*} ctx - * The context. + * @function Highcharts.SVGRenderer#setStyle * - * @return {boolean} + * @param {Highcharts.CSSObject} style + * CSS to apply. */ - objectEach({ - map: 'map', - each: 'forEach', - grep: 'filter', - reduce: 'reduce', - some: 'some' - }, function (val, key) { - H[key] = function (arr) { - var _a; - error(32, false, void 0, (_a = {}, _a["Highcharts." + key] = "use Array." + val, _a)); - return Array.prototype[val].apply(arr, [].slice.call(arguments, 1)); - }; - }); - /* eslint-disable valid-jsdoc */ + SVGRenderer.prototype.setStyle = function (style) { + this.boxWrapper.css(this.getStyle(style)); + }; /** - * Add an event listener. + * 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. * - * @function Highcharts.addEvent<T> + * @function Highcharts.SVGRenderer#isHidden * - * @param {Highcharts.Class<T>|T} 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. + * @return {boolean} + * True if it is hidden. + */ + SVGRenderer.prototype.isHidden = function () { + return !this.boxWrapper.getBBox().width; + }; + /** + * Destroys the renderer and its allocated members. + * + * @function Highcharts.SVGRenderer#destroy + * + * @return {null} + */ + SVGRenderer.prototype.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 {Highcharts.EventCallbackFunction<T>|Function} fn - * The function callback to execute when the event is fired. + * @function Highcharts.SVGRenderer#createElement * - * @param {Highcharts.EventOptionsObject} [options] - * Options for adding the event. + * @param {string} nodeName + * The node name, for example `rect`, `g` etc. * - * @return {Function} - * A callback function to remove the added event. + * @return {Highcharts.SVGElement} + * The generated SVGElement. */ - var addEvent = H.addEvent = function (el, - type, - fn, - options) { - if (options === void 0) { options = {}; } - /* eslint-enable valid-jsdoc */ - var events, - addEventListener = (el.addEventListener || H.addEventListenerPolyfill); - // If we're setting events directly on the constructor, use a separate - // collection, `protoEvents` to distinguish it from the item events in - // `hcEvents`. - if (typeof el === 'function' && el.prototype) { - events = el.prototype.protoEvents = el.prototype.protoEvents || {}; - } - else { - events = el.hcEvents = el.hcEvents || {}; - } - // Allow click events added to points, otherwise they will be prevented by - // the TouchPointer.pinch function after a pinch zoom operation (#7091). - if (H.Point && - el instanceof H.Point && - el.series && - el.series.chart) { - el.series.chart.runTrackerClick = true; - } - // Handle DOM events - if (addEventListener) { - addEventListener.call(el, type, fn, false); - } - if (!events[type]) { - events[type] = []; - } - var eventObject = { - fn: fn, - order: typeof options.order === 'number' ? options.order : Infinity - }; - events[type].push(eventObject); - // Order the calls - events[type].sort(function (a, b) { - return a.order - b.order; - }); - // Return a function that can be called to remove this event. - return function () { - removeEvent(el, type, fn); - }; + SVGRenderer.prototype.createElement = function (nodeName) { + var wrapper = new this.Element(); + wrapper.init(this, nodeName); + return wrapper; }; - /* eslint-disable valid-jsdoc */ /** - * Remove an event that was added with {@link Highcharts#addEvent}. - * - * @function Highcharts.removeEvent<T> + * Get converted radial gradient attributes according to the radial + * reference. Used internally from the {@link SVGElement#colorGradient} + * function. * - * @param {Highcharts.Class<T>|T} el - * The element to remove events on. + * @private + * @function Highcharts.SVGRenderer#getRadialAttr + */ + SVGRenderer.prototype.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], + }; + }; + /** + * Truncate the text node contents to a given length. Used when the css + * width is set. If the `textOverflow` is `ellipsis`, the text is truncated + * character by character to the given length. If not, the text is + * word-wrapped line by line. * - * @param {string} [type] - * The type of events to remove. If undefined, all events are removed - * from the element. + * @private + * @function Highcharts.SVGRenderer#truncate * - * @param {Highcharts.EventCallbackFunction<T>} [fn] - * The specific callback to remove. If undefined, all events that match - * the element and optionally the type are removed. + * @return {boolean} + * True if tspan is too long. + */ + SVGRenderer.prototype.truncate = function ( + wrapper, + tspan, + text, + words, + startAt, + width, + getString + ) { + var renderer = this, + rotation = wrapper.rotation, + str, + // Word wrap can not be truncated to shorter than one word, ellipsis + // text can be completely blank. + minIndex = words ? 1 : 0, + maxIndex = (text || words).length, + currentIndex = maxIndex, + // Cache the lengths to avoid checking the same twice + lengths = [], + updateTSpan = function (s) { + if (tspan.firstChild) { + tspan.removeChild(tspan.firstChild); + } + if (s) { + tspan.appendChild(doc.createTextNode(s)); + } + }, + getSubStringLength = function (charEnd, concatenatedEnd) { + // charEnd is useed when finding the character-by-character + // break for ellipsis, concatenatedEnd is used for word-by-word + // break for word wrapping. + var end = concatenatedEnd || charEnd; + if (typeof lengths[end] === "undefined") { + // Modern browsers + if (tspan.getSubStringLength) { + // Fails with DOM exception on unit-tests/legend/members + // of unknown reason. Desired width is 0, text content + // is "5" and end is 1. + try { + lengths[end] = + startAt + + tspan.getSubStringLength(0, words ? end + 1 : end); + } catch (e) { + (""); + } + // Legacy + } else if (renderer.getSpanWidth) { + // #9058 jsdom + updateTSpan(getString(text || words, charEnd)); + lengths[end] = + startAt + renderer.getSpanWidth(wrapper, tspan); + } + } + return lengths[end]; + }, + actualWidth, + truncated; + wrapper.rotation = 0; // discard rotation when computing box + actualWidth = getSubStringLength(tspan.textContent.length); + truncated = startAt + actualWidth > width; + if (truncated) { + // Do a binary search for the index where to truncate the text + while (minIndex <= maxIndex) { + currentIndex = Math.ceil((minIndex + maxIndex) / 2); + // When checking words for word-wrap, we need to build the + // string and measure the subStringLength at the concatenated + // word length. + if (words) { + str = getString(words, currentIndex); + } + actualWidth = getSubStringLength( + currentIndex, + str && str.length - 1 + ); + 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 the shortest possible text was also + // too large. For ellipsis that means only the ellipsis, while for + // word wrap it means the whole first word. + if (maxIndex === 0) { + // Remove ellipsis + updateTSpan(""); + // If the new text length is one less than the original, we don't + // need the ellipsis + } else if (!(text && maxIndex === text.length - 1)) { + updateTSpan(str || getString(text || words, currentIndex)); + } + } + // When doing line wrapping, prepare for the next line by removing the + // items from this line. + if (words) { + words.splice(0, currentIndex); + } + wrapper.actualWidth = actualWidth; + wrapper.rotation = rotation; // Apply rotation again. + return truncated; + }; + /** + * 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`. * - * @return {void} - */ - var removeEvent = H.removeEvent = function removeEvent(el, - type, - fn) { - /* eslint-enable valid-jsdoc */ - var events; - /** - * @private - * @param {string} type - event type - * @param {Highcharts.EventCallbackFunction<T>} fn - callback - * @return {void} - */ - function removeOneEvent(type, fn) { - var removeEventListener = (el.removeEventListener || H.removeEventListenerPolyfill); - if (removeEventListener) { - removeEventListener.call(el, type, fn, false); - } + * @private + * @function Highcharts.SVGRenderer#buildText + * + * @param {Highcharts.SVGElement} wrapper + * The parent SVGElement. + */ + SVGRenderer.prototype.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, + truncated, + 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; + if (!renderer.styledMode) { + 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, except) { + objectEach(renderer.escapes, function (value, key) { + if (!except || except.indexOf(value) === -1) { + inputStr = inputStr + .toString() + .replace(new RegExp(value, "g"), key); + } + }); + return inputStr; + }, + parseAttribute = function (s, attr) { + var start, delimiter; + start = s.indexOf("<"); + s = s.substring(start, s.indexOf(">") - start); + start = s.indexOf(attr + "="); + if (start !== -1) { + start = start + attr.length + 1; + delimiter = s.charAt(start); + if (delimiter === '"' || delimiter === "'") { + // eslint-disable-line quotes + s = s.substring(start + 1); + return s.substring(0, s.indexOf(delimiter)); + } + } + }; + var regexMatchBreaks = /<br.*?>/g; + // 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 || + (noWrap && !regexMatchBreaks.test(textStr))) + ) { + textNode.appendChild(doc.createTextNode(unescapeEntities(textStr))); + // Complex strings, add more logic + } else { + if (tempParent) { + // attach it to the DOM to read offset width + tempParent.appendChild(textNode); } - /** - * @private - * @param {any} eventCollection - collection - * @return {void} - */ - function removeAllEvents(eventCollection) { - var types, - len; - if (!el.nodeName) { - return; // break on non-DOM events - } - if (type) { - types = {}; - types[type] = true; - } - else { - types = eventCollection; - } - objectEach(types, function (_val, n) { - if (eventCollection[n]) { - len = eventCollection[n].length; - while (len--) { - removeOneEvent(n, eventCollection[n][len].fn); - } - } - }); + if (hasMarkup) { + lines = renderer.styledMode + ? textStr + .replace( + /<(b|strong)>/g, + '<span class="highcharts-strong">' + ) + .replace( + /<(i|em)>/g, + '<span class="highcharts-emphasized">' + ) + : textStr + .replace(/<(b|strong)>/g, '<span style="font-weight:bold">') + .replace(/<(i|em)>/g, '<span style="font-style:italic">'); + lines = lines + .replace(/<a/g, "<span") + .replace(/<\/(b|strong|i|em|a)>/g, "</span>") + .split(regexMatchBreaks); + } else { + lines = [textStr]; } - ['protoEvents', 'hcEvents'].forEach(function (coll, i) { - var eventElem = i ? el : el.prototype; - var eventCollection = eventElem && eventElem[coll]; - if (eventCollection) { - if (type) { - events = (eventCollection[type] || []); - if (fn) { - eventCollection[type] = events.filter(function (obj) { - return fn !== obj.fn; + // Trim empty lines (#5261) + lines = lines.filter(function (line) { + return line !== ""; + }); + // build the lines + lines.forEach(function (line, lineNo) { + var spans, + spanNo = 0, + lineLength = 0; + line = line + // Trim to prevent useless/costly process on the spaces + // (#5258) + .replace(/^\s+|\s+$/g, "") + .replace(/<span/g, "|||<span") + .replace(/<\/span>/g, "</span>|||"); + spans = line.split("|||"); + spans.forEach(function buildTextSpans(span) { + if (span !== "" || spans.length === 1) { + var attributes = {}, + tspan = doc.createElementNS(renderer.SVG_NS, "tspan"), + a, + classAttribute, + styleAttribute, // #390 + hrefAttribute; + classAttribute = parseAttribute(span, "class"); + if (classAttribute) { + attr(tspan, "class", classAttribute); + } + styleAttribute = parseAttribute(span, "style"); + if (styleAttribute) { + styleAttribute = styleAttribute.replace( + /(;| |^)color([ :])/, + "$1fill$2" + ); + attr(tspan, "style", styleAttribute); + } + // For anchors, wrap the tspan in an <a> tag and apply + // the href attribute as is (#13559). Not for export + // (#1529) + hrefAttribute = parseAttribute(span, "href"); + if (hrefAttribute && !forExport) { + if ( + // Stop JavaScript links, vulnerable to XSS + hrefAttribute + .split(":")[0] + .toLowerCase() + .indexOf("javascript") === -1 + ) { + a = doc.createElementNS(renderer.SVG_NS, "a"); + attr(a, "href", hrefAttribute); + attr(tspan, "class", "highcharts-anchor"); + a.appendChild(tspan); + if (!renderer.styledMode) { + 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(a || 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)); + } + // Check width and apply soft breaks or ellipsis + if (width) { + var words = span.replace(/([^\^])-/g, "$1- ").split(" "), // #1273 + hasWhiteSpace = + !noWrap && + (spans.length > 1 || lineNo || words.length > 1), + wrapLineNo = 0, + dy = getLineHeight(tspan); + if (ellipsis) { + truncated = renderer.truncate( + wrapper, + tspan, + span, + void 0, + 0, + // Target width + Math.max( + 0, + // Substract the font face to make + // room for the ellipsis itself + width - parseInt(fontSize || 12, 10) + ), + // Build the text to test for + function (text, currentIndex) { + return text.substring(0, currentIndex) + "\u2026"; + } + ); + } else if (hasWhiteSpace) { + while (words.length) { + // For subsequent lines, create tspans + // with the same style attributes as the + // parent text node. + if (words.length && !noWrap && wrapLineNo > 0) { + tspan = doc.createElementNS(SVG_NS, "tspan"); + attr(tspan, { + dy: dy, + x: parentX, }); - removeOneEvent(type, fn); - } - else { - removeAllEvents(eventCollection); - eventCollection[type] = []; + if (styleAttribute) { + // #390 + attr(tspan, "style", styleAttribute); + } + // Start by appending the full + // remaining text + tspan.appendChild( + doc.createTextNode( + words.join(" ").replace(/- /g, "-") + ) + ); + textNode.appendChild(tspan); + } + // For each line, truncate the remaining + // words into the line length. + renderer.truncate( + wrapper, + tspan, + null, + words, + wrapLineNo === 0 ? lineLength : 0, + width, + // Build the text to test for + function (text, currentIndex) { + return words + .slice(0, currentIndex) + .join(" ") + .replace(/- /g, "-"); + } + ); + lineLength = wrapper.actualWidth; + wrapLineNo++; } + } } - else { - removeAllEvents(eventCollection); - eventElem[coll] = {}; - } + spanNo++; + } } + }); + // To avoid beginning lines that doesn't add to the textNode + // (#6144) + isSubsequentLine = isSubsequentLine || textNode.childNodes.length; }); + if (ellipsis && truncated) { + wrapper.attr( + "title", + unescapeEntities(wrapper.textStr || "", ["<", ">"]) // #7179 + ); + } + if (tempParent) { + tempParent.removeChild(textNode); + } + // Apply the text outline + if (isString(textOutline) && wrapper.applyTextOutline) { + wrapper.applyTextOutline(textOutline); + } + } }; - /* eslint-disable valid-jsdoc */ /** - * Fire an event that was registered with {@link Highcharts#addEvent}. + * Returns white for dark colors and black for bright colors. * - * @function Highcharts.fireEvent<T> + * @function Highcharts.SVGRenderer#getContrast * - * @param {T} el - * The object to fire the event on. It can be a {@link HTMLDOMElement}, - * an {@link SVGElement} or any other object. + * @param {Highcharts.ColorString} rgba + * The color to get the contrast for. * - * @param {string} type - * The type of event. + * @return {Highcharts.ColorString} + * The contrast color, either `#000000` or `#FFFFFF`. + */ + SVGRenderer.prototype.getContrast = function (rgba) { + rgba = Color.parse(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.5; // blue + return rgba[0] + rgba[1] + rgba[2] > 1.8 * 255 + ? "#000000" + : "#FFFFFF"; + }; + /** + * Create a button with preset states. * - * @param {Highcharts.Dictionary<*>|Event} [eventArguments] - * Custom event arguments that are passed on as an argument to the event - * handler. + * @function Highcharts.SVGRenderer#button * - * @param {Highcharts.EventCallbackFunction<T>|Function} [defaultFunction] - * The default function to execute if the other listeners haven't - * returned false. + * @param {string} text + * The text or HTML to draw. * - * @return {void} - */ - var fireEvent = H.fireEvent = function (el, - type, - eventArguments, - defaultFunction) { - /* eslint-enable valid-jsdoc */ - var e, - i; - eventArguments = eventArguments || {}; - if (doc.createEvent && - (el.dispatchEvent || el.fireEvent)) { - e = doc.createEvent('Events'); - e.initEvent(type, true, true); - extend(e, eventArguments); - if (el.dispatchEvent) { - el.dispatchEvent(e); - } - else { - el.fireEvent(type, e); - } + * @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 {Highcharts.EventCallbackFunction<Highcharts.SVGElement>} callback + * The function to execute on button click or touch. + * + * @param {Highcharts.SVGAttributes} [normalState] + * SVG attributes for the normal state. + * + * @param {Highcharts.SVGAttributes} [hoverState] + * SVG attributes for the hover state. + * + * @param {Highcharts.SVGAttributes} [pressedState] + * SVG attributes for the pressed state. + * + * @param {Highcharts.SVGAttributes} [disabledState] + * SVG attributes for the disabled state. + * + * @param {Highcharts.SymbolKeyValue} [shape=rect] + * The shape type. + * + * @param {boolean} [useHTML=false] + * Wether to use HTML to render the label. + * + * @return {Highcharts.SVGElement} + * The button element. + */ + SVGRenderer.prototype.button = function ( + text, + x, + y, + callback, + normalState, + hoverState, + pressedState, + disabledState, + shape, + useHTML + ) { + var label = this.label( + text, + x, + y, + shape, + void 0, + void 0, + useHTML, + void 0, + "button" + ), + curState = 0, + styledMode = this.styledMode, + // Make a copy of normalState (#13798) + // (reference to options.rangeSelector.buttonTheme) + normalState = normalState ? merge(normalState) : normalState, + userNormalStyle = (normalState && normalState.style) || {}; + // Remove stylable attributes + if (normalState && normalState.style) { + delete normalState.style; + } + // Default, non-stylable attributes + label.attr(merge({ padding: 8, r: 2 }, normalState)); + if (!styledMode) { + // 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", + }, + }, + { + style: userNormalStyle, + }, + 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); + } } - else { - if (!eventArguments.target) { - // We're running a custom event - 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 - }); - } - var fireInOrder = function (protoEvents, - hcEvents) { - if (protoEvents === void 0) { protoEvents = []; } - if (hcEvents === void 0) { hcEvents = []; } - var iA = 0; - var iB = 0; - var length = protoEvents.length + hcEvents.length; - for (i = 0; i < length; i++) { - var obj = (!protoEvents[iA] ? - hcEvents[iB++] : - !hcEvents[iB] ? - protoEvents[iA++] : - protoEvents[iA].order <= hcEvents[iB].order ? - protoEvents[iA++] : - hcEvents[iB++]); - // If the event handler return false, prevent the default - // handler from executing - if (obj.fn.call(el, eventArguments) === false) { - eventArguments.preventDefault(); - } - } - }; - fireInOrder(el.protoEvents && el.protoEvents[type], el.hcEvents && el.hcEvents[type]); + ); + 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] + ); + if (!styledMode) { + label + .attr( + [normalState, hoverState, pressedState, disabledState][ + state || 0 + ] + ) + .css( + [normalStyle, hoverStyle, pressedStyle, disabledStyle][ + state || 0 + ] + ); } - // Run the default if not prevented - if (defaultFunction && !eventArguments.defaultPrevented) { - defaultFunction.call(el, eventArguments); + }; + // Presentational attributes + if (!styledMode) { + label + .attr(normalState) + .css(extend({ cursor: "default" }, normalStyle)); + } + return label.on("click", function (e) { + if (curState !== 3) { + callback.call(label, e); } + }); }; - var serialMode; /** - * 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. + * Make a straight line crisper by not spilling out to neighbour pixels. * - * @example - * var id = uniqueKey(); // => 'highcharts-x45f6hp-0' + * @function Highcharts.SVGRenderer#crispLine * - * @function Highcharts.uniqueKey + * @param {Highcharts.SVGPathArray} points + * The original points on the format `[['M', 0, 0], ['L', 100, 0]]`. * - * @return {string} - * A unique key. - */ - var uniqueKey = H.uniqueKey = (function () { - var hash = Math.random().toString(36).substring(2, 9) + '-'; - var id = 0; - return function () { - return 'highcharts-' + (serialMode ? '' : hash) + id++; - }; - }()); + * @param {number} width + * The width of the line. + * + * @param {string} roundingFunction + * The rounding function name on the `Math` object, can be one of + * `round`, `floor` or `ceil`. + * + * @return {Highcharts.SVGPathArray} + * The original points array, but modified to render crisply. + */ + SVGRenderer.prototype.crispLine = function ( + points, + width, + roundingFunction + ) { + if (roundingFunction === void 0) { + roundingFunction = "round"; + } + var start = points[0]; + var end = points[1]; + // Normalize to a crisp line + if (start[1] === end[1]) { + // Substract due to #1129. Now bottom and left axis gridlines behave + // the same. + start[1] = end[1] = + Math[roundingFunction](start[1]) - (width % 2) / 2; + } + if (start[2] === end[2]) { + start[2] = end[2] = + Math[roundingFunction](start[2]) + (width % 2) / 2; + } + return points; + }; /** - * Activates a serial mode for element IDs provided by - * {@link Highcharts.uniqueKey}. This mode can be used in automated tests, where - * a simple comparison of two rendered SVG graphics is needed. + * Draw a path, wraps the SVG `path` element. * - * **Note:** This is only for testing purposes and will break functionality in - * webpages with multiple charts. + * @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 * * @example - * if ( - * process && - * process.env.NODE_ENV === 'development' - * ) { - * Highcharts.useSerialIds(true); - * } + * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z']) + * .attr({ stroke: '#ff00ff' }) + * .add(); * - * @function Highcharts.useSerialIds + * @function Highcharts.SVGRenderer#path * - * @param {boolean} [mode] - * Changes the state of serial mode. + * @param {Highcharts.SVGPathArray} [path] + * An SVG path definition in array form. * - * @return {boolean|undefined} - * State of the serial mode. - */ - var useSerialIds = H.useSerialIds = function (mode) { - return (serialMode = pick(mode, - serialMode)); - }; - var isFunction = H.isFunction = function (obj) { - return typeof obj === 'function'; + * @return {Highcharts.SVGElement} + * The generated wrapper element. + * + */ /** + * Draw a path, wraps the SVG `path` element. + * + * @function Highcharts.SVGRenderer#path + * + * @param {Highcharts.SVGAttributes} [attribs] + * The initial attributes. + * + * @return {Highcharts.SVGElement} + * The generated wrapper element. + */ + SVGRenderer.prototype.path = function (path) { + var attribs = this.styledMode + ? {} + : { + fill: "none", + }; + if (isArray(path)) { + attribs.d = path; + } else if (isObject(path)) { + // attributes + extend(attribs, path); + } + return this.createElement("path").attr(attribs); }; /** - * 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. + * Draw a circle, wraps the SVG `circle` element. * - * @function Highcharts.getOptions + * @sample highcharts/members/renderer-circle/ + * Drawing a circle * - * @return {Highcharts.Options} - */ - var getOptions = H.getOptions = function () { - return H.defaultOptions; - }; - /** - * Merge the default options with custom options and return the new options - * structure. Commonly used for defining reusable templates. + * @function Highcharts.SVGRenderer#circle * - * @sample highcharts/global/useutc-false Setting a global option - * @sample highcharts/members/setoptions Applying a global theme + * @param {number} [x] + * The center x position. * - * @function Highcharts.setOptions + * @param {number} [y] + * The center y position. * - * @param {Highcharts.Options} options - * The new custom chart options. + * @param {number} [r] + * The radius. * - * @return {Highcharts.Options} - * Updated options. - */ - var setOptions = H.setOptions = function (options) { - // Copy in the default options - H.defaultOptions = merge(true, - H.defaultOptions, - options); - // Update the time object - if (options.time || options.global) { - H.time.update(merge(H.defaultOptions.global, H.defaultOptions.time, options.global, options.time)); - } - return H.defaultOptions; + * @return {Highcharts.SVGElement} + * The generated wrapper element. + */ /** + * Draw a circle, wraps the SVG `circle` element. + * + * @function Highcharts.SVGRenderer#circle + * + * @param {Highcharts.SVGAttributes} [attribs] + * The initial attributes. + * + * @return {Highcharts.SVGElement} + * The generated wrapper element. + */ + SVGRenderer.prototype.circle = function (x, y, r) { + var attribs = isObject(x) + ? x + : typeof x === "undefined" + ? {} + : { 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); }; - // Register Highcharts as a plugin in jQuery - if (win.jQuery) { - /** - * Highcharts-extended JQuery. - * - * @external JQuery - */ - /** - * Helper function to return the chart of the current JQuery selector - * element. - * - * @function external:JQuery#highcharts - * - * @return {Highcharts.Chart} - * The chart that is linked to the JQuery selector element. - */ /** - * Factory function to create a chart in the current JQuery selector - * element. - * - * @function external:JQuery#highcharts - * - * @param {'Chart'|'Map'|'StockChart'|string} [className] - * Name of the factory class in the Highcharts namespace. - * - * @param {Highcharts.Options} [options] - * The chart options structure. - * - * @param {Highcharts.ChartCallbackFunction} [callback] - * Function to run when the chart has loaded and and all external - * images are loaded. Defining a - * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) - * handler is equivalent. - * - * @return {JQuery} - * The current JQuery selector. - */ - 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 computed-property-spacing, no-new - // Constructor defaults to Chart - 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[attr(this[0], 'data-highcharts-chart')]; - } - }; - } - // TODO use named exports when supported. - var utilitiesModule = { - addEvent: addEvent, - arrayMax: arrayMax, - arrayMin: arrayMin, - attr: attr, - clamp: clamp, - clearTimeout: internalClearTimeout, - correctFloat: correctFloat, - createElement: createElement, - css: css, - defined: defined, - destroyObjectProperties: destroyObjectProperties, - discardElement: discardElement, - erase: erase, - error: error, - extend: extend, - extendClass: extendClass, - find: find, - fireEvent: fireEvent, - format: format, - getMagnitude: getMagnitude, - getNestedProperty: getNestedProperty, - getOptions: getOptions, - getStyle: getStyle, - inArray: inArray, - isArray: isArray, - isClass: isClass, - isDOMElement: isDOMElement, - isFunction: isFunction, - isNumber: isNumber, - isObject: isObject, - isString: isString, - merge: merge, - normalizeTickInterval: normalizeTickInterval, - numberFormat: numberFormat, - objectEach: objectEach, - offset: offset, - pad: pad, - pick: pick, - pInt: pInt, - relativeLength: relativeLength, - removeEvent: removeEvent, - setOptions: setOptions, - splat: splat, - stableSort: stableSort, - syncTimeout: syncTimeout, - timeUnits: timeUnits, - uniqueKey: uniqueKey, - useSerialIds: useSerialIds, - wrap: wrap - }; - - return utilitiesModule; - }); - _registerModule(_modules, 'Core/Color/Color.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { - /* * + /** + * Draw and return an arc. * - * (c) 2010-2020 Torstein Honsi + * @sample highcharts/members/renderer-arc/ + * Drawing an arc * - * License: www.highcharts.com/license + * @function Highcharts.SVGRenderer#arc * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @param {number} [x=0] + * Center X position. * - * */ - var isNumber = U.isNumber, - merge = U.merge, - pInt = U.pInt; - /** - * 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. + * @param {number} [y=0] + * Center Y position. * - * @typedef {string} Highcharts.ColorString - */ - /** - * A valid color type than can be parsed and handled by Highcharts. It can be a - * color string, a gradient object, or a pattern object. + * @param {number} [r=0] + * The outer radius' of the arc. * - * @typedef {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} Highcharts.ColorType - */ - /** - * Gradient options instead of a solid color. + * @param {number} [innerR=0] + * Inner radius like used in donut charts. * - * @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 - * ] - * } - * - * @interface Highcharts.GradientColorObject - */ /** - * Holds an object that defines the start position and the end position relative - * to the shape. - * @name Highcharts.GradientColorObject#linearGradient - * @type {Highcharts.LinearGradientColorObject|undefined} - */ /** - * Holds an object that defines the center position and the radius. - * @name Highcharts.GradientColorObject#radialGradient - * @type {Highcharts.RadialGradientColorObject|undefined} - */ /** - * 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. - * @name Highcharts.GradientColorObject#stops - * @type {Array<Highcharts.GradientColorStopObject>} - */ - /** - * Color stop tuple. - * - * @see Highcharts.GradientColorObject - * - * @interface Highcharts.GradientColorStopObject - */ /** - * @name Highcharts.GradientColorStopObject#0 - * @type {number} - */ /** - * @name Highcharts.GradientColorStopObject#1 - * @type {Highcharts.ColorString} - */ /** - * @name Highcharts.GradientColorStopObject#color - * @type {Highcharts.Color|undefined} - */ - /** - * Defines the start position and the end position for a gradient relative - * to the shape. Start position (x1, y1) and end position (x2, y2) are relative - * to the shape, where 0 means top/left and 1 is bottom/right. - * - * @interface Highcharts.LinearGradientColorObject - */ /** - * Start horizontal position of the gradient. Float ranges 0-1. - * @name Highcharts.LinearGradientColorObject#x1 - * @type {number} - */ /** - * End horizontal position of the gradient. Float ranges 0-1. - * @name Highcharts.LinearGradientColorObject#x2 - * @type {number} - */ /** - * Start vertical position of the gradient. Float ranges 0-1. - * @name Highcharts.LinearGradientColorObject#y1 - * @type {number} - */ /** - * End vertical position of the gradient. Float ranges 0-1. - * @name Highcharts.LinearGradientColorObject#y2 - * @type {number} - */ - /** - * Defines the center position and the radius for a gradient. - * - * @interface Highcharts.RadialGradientColorObject - */ /** - * Center horizontal position relative to the shape. Float ranges 0-1. - * @name Highcharts.RadialGradientColorObject#cx - * @type {number} - */ /** - * Center vertical position relative to the shape. Float ranges 0-1. - * @name Highcharts.RadialGradientColorObject#cy - * @type {number} - */ /** - * Radius relative to the shape. Float ranges 0-1. - * @name Highcharts.RadialGradientColorObject#r - * @type {number} - */ - ''; // detach doclets above - /* * + * @param {number} [start=0] + * The starting angle of the arc in radians, where 0 is to the right and + * `-Math.PI/2` is up. * - * Class + * @param {number} [end=0] + * The ending angle of the arc in radians, where 0 is to the right and + * `-Math.PI/2` is up. * - * */ - /* eslint-disable no-invalid-this, valid-jsdoc */ + * @return {Highcharts.SVGElement} + * The generated wrapper element. + */ /** + * Draw and return an arc. Overloaded function that takes arguments object. + * + * @function Highcharts.SVGRenderer#arc + * + * @param {Highcharts.SVGAttributes} attribs + * Initial SVG attributes. + * + * @return {Highcharts.SVGElement} + * The generated wrapper element. + */ + SVGRenderer.prototype.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; + }; /** - * Handle color operations. Some object methods are chainable. + * Draw and return a rectangle. * - * @class - * @name Highcharts.Color + * @function Highcharts.SVGRenderer#rect * - * @param {Highcharts.ColorType} input - * The input color in either rbga or hex format - */ - var Color = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function Color(input) { - // Collection of parsers. This can be extended from the outside by pushing - // parsers to Highcharts.Color.prototype.parsers. - this.parsers = [{ - // RGBA color - // eslint-disable-next-line max-len - 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]; - } - }]; - this.rgba = []; - // Backwards compatibility, allow class overwrite - if (H.Color !== Color) { - return new H.Color(input); - } - // Backwards compatibility, allow instanciation without new (#13053) - if (!(this instanceof Color)) { - return new Color(input); - } - this.init(input); - } - /* * - * - * Static Functions - * - * */ - /** - * Creates a color instance out of a color string or object. - * - * @function Highcharts.Color.parse - * - * @param {Highcharts.ColorType} input - * The input color in either rbga or hex format. - * - * @return {Highcharts.Color} - * Color instance. - */ - Color.parse = function (input) { - return new Color(input); - }; - /* * - * - * Functions - * - * */ - /** - * Parse the input color to rgba array - * - * @private - * @function Highcharts.Color#init - * - * @param {Highcharts.ColorType} input - * The input color in either rbga or hex format - * - * @return {void} - */ - Color.prototype.init = function (input) { - var result, - rgba, - i, - parser, - len; - this.input = input = Color.names[input && input.toLowerCase ? - input.toLowerCase() : - ''] || input; - // Gradients - if (input && input.stops) { - this.stops = input.stops.map(function (stop) { - return new 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 or gradient stops in the specified format - * - * @function Highcharts.Color#get - * - * @param {string} [format] - * Possible values are 'a', 'rgb', 'rgba' (default). - * - * @return {Highcharts.ColorType} - * This color as a string or gradient stops. - */ - Color.prototype.get = function (format) { - var input = this.input, - rgba = this.rgba, - ret; - if (typeof this.stops !== 'undefined') { - ret = merge(input); - ret.stops = [].concat(ret.stops); - this.stops.forEach(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 instance. - * - * @function Highcharts.Color#brighten - * - * @param {number} alpha - * The alpha value. - * - * @return {Highcharts.Color} - * This color with modifications. - */ - Color.prototype.brighten = function (alpha) { - var i, - rgba = this.rgba; - if (this.stops) { - this.stops.forEach(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. - * - * @function Highcharts.Color#setOpacity - * - * @param {number} alpha - * Opacity between 0 and 1. - * - * @return {Highcharts.Color} - * Color with modifications. - */ - Color.prototype.setOpacity = function (alpha) { - this.rgba[3] = alpha; - return this; - }; - /** - * Return an intermediate color between two colors. - * - * @function Highcharts.Color#tweenTo - * - * @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 {Highcharts.ColorString} - * The intermediate color in rgba notation. - */ - Color.prototype.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; - }; - /* * - * - * Static Properties - * - * */ - // Collection of named colors. Can be extended from the outside by adding - // colors to Highcharts.Color.names. - Color.names = { - white: '#ffffff', - black: '#000000' - }; - return Color; - }()); - H.Color = Color; - /** - * Creates a color instance out of a color string. + * @param {number} [x] + * Left position. * - * @function Highcharts.color + * @param {number} [y] + * Top position. * - * @param {Highcharts.ColorType} input - * The input color in either rbga or hex format + * @param {number} [width] + * Width of the rectangle. * - * @return {Highcharts.Color} - * Color instance - */ - H.color = Color.parse; - /* * + * @param {number} [height] + * Height of the rectangle. * - * Export + * @param {number} [r] + * Border corner radius. * - * */ - - return Color; - }); - _registerModule(_modules, 'Core/Animation/Fx.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { - /* * + * @param {number} [strokeWidth] + * A stroke width can be supplied to allow crisp drawing. * - * (c) 2010-2020 Torstein Honsi + * @return {Highcharts.SVGElement} + * The generated wrapper element. + */ /** + * Draw and return a rectangle. + * + * @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 + * + * @function Highcharts.SVGRenderer#rect + * + * @param {Highcharts.SVGAttributes} [attributes] + * General SVG attributes for the rectangle. + * + * @return {Highcharts.SVGElement} + * The generated wrapper element. + */ + SVGRenderer.prototype.rect = function ( + x, + y, + width, + height, + r, + strokeWidth + ) { + r = isObject(x) ? x.r : r; + var wrapper = this.createElement("rect"), + attribs = isObject(x) + ? x + : typeof x === "undefined" + ? {} + : { + x: x, + y: y, + width: Math.max(width, 0), + height: Math.max(height, 0), + }; + if (!this.styledMode) { + if (typeof strokeWidth !== "undefined") { + attribs.strokeWidth = strokeWidth; + attribs = wrapper.crisp(attribs); + } + attribs.fill = "none"; + } + if (r) { + attribs.r = r; + } + wrapper.rSetter = function (value, key, element) { + wrapper.r = value; + attr(element, { + rx: value, + ry: value, + }); + }; + wrapper.rGetter = function () { + return wrapper.r; + }; + return wrapper.attr(attribs); + }; + /** + * Resize the {@link SVGRenderer#box} and re-align all aligned child + * elements. * - * License: www.highcharts.com/license + * @sample highcharts/members/renderer-g/ + * Show and hide grouped objects * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @function Highcharts.SVGRenderer#setSize * - * */ - var win = H.win; - var isNumber = U.isNumber, - objectEach = U.objectEach; - /* eslint-disable no-invalid-this, valid-jsdoc */ + * @param {number} width + * The new pixel width. + * + * @param {number} height + * The new pixel height. + * + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animate=true] + * Whether and how to animate. + */ + SVGRenderer.prototype.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) ? void 0 : 0, + } + ); + while (i--) { + alignedObjects[i].align(); + } + }; /** - * 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}. + * 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()}. * - * @example - * var rect = renderer.rect(0, 0, 10, 10).add(); - * rect.animate({ width: 100 }); + * @function Highcharts.SVGRenderer#g * - * @private - * @class - * @name Highcharts.Fx + * @param {string} [name] + * The group will be given a class name of `highcharts-{name}`. This + * can be used for styling and scripting. + * + * @return {Highcharts.SVGElement} + * The generated wrapper element. */ - var Fx = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - /** - * - * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} elem - * The element to animate. - * - * @param {Partial<Highcharts.AnimationOptionsObject>} options - * Animation options. - * - * @param {string} prop - * The single attribute or CSS property to animate. - */ - function Fx(elem, options, prop) { - this.pos = NaN; - this.options = options; - this.elem = elem; - this.prop = prop; + SVGRenderer.prototype.g = function (name) { + var elem = this.createElement("g"); + return name ? elem.attr({ class: "highcharts-" + name }) : elem; + }; + /** + * Display an image. + * + * @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 + * + * @function Highcharts.SVGRenderer#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. + * + * @param {Function} [onload] + * Event handler for image load. + * + * @return {Highcharts.SVGElement} + * The generated wrapper element. + */ + SVGRenderer.prototype.image = function ( + src, + x, + y, + width, + height, + onload + ) { + var attribs = { preserveAspectRatio: "none" }, + elemWrapper, + dummy, + setSVGImageSource = function (el, src) { + // Set the href in the xlink namespace + if (el.setAttributeNS) { + el.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 + el.setAttribute("hc-svg-href", src); + } + }, + onDummyLoad = function (e) { + setSVGImageSource(elemWrapper.element, src); + onload.call(elemWrapper, e); + }; + // optional properties + if (arguments.length > 1) { + extend(attribs, { + x: x, + y: y, + width: width, + height: height, + }); + } + elemWrapper = this.createElement("image").attr(attribs); + // Add load event if supplied + if (onload) { + // We have to use a dummy HTML image since IE support for SVG image + // load events is very buggy. First set a transparent src, wait for + // dummy to load, and then add the real src to the SVG image. + setSVGImageSource( + elemWrapper.element, + "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" /* eslint-disable-line */ + ); + dummy = new win.Image(); + addEvent(dummy, "load", onDummyLoad); + dummy.src = src; + if (dummy.complete) { + onDummyLoad({}); } - /* * - * - * Functions - * - * */ - /** - * Set the current step of a path definition on SVGElement. - * - * @function Highcharts.Fx#dSetter - * - * @return {void} - */ - Fx.prototype.dSetter = function () { - var paths = this.paths, - start = paths && paths[0], - end = paths && paths[1], - path = [], - now = this.now || 0; - // Land on the final path without adjustment points appended in the ends - if (now === 1 || !start || !end) { - path = this.toD || []; - } - else if (start.length === end.length && now < 1) { - for (var i = 0; i < end.length; i++) { - // Tween between the start segment and the end segment. Start - // with a copy of the end segment and tween the appropriate - // numerics - var startSeg = start[i]; - var endSeg = end[i]; - var tweenSeg = []; - for (var j = 0; j < endSeg.length; j++) { - var startItem = startSeg[j]; - var endItem = endSeg[j]; - // Tween numbers - if (typeof startItem === 'number' && - typeof endItem === 'number' && - // Arc boolean flags - !(endSeg[0] === 'A' && (j === 4 || j === 5))) { - tweenSeg[j] = startItem + now * (endItem - startItem); - // Strings, take directly from the end segment - } - else { - tweenSeg[j] = endItem; - } - } - path.push(tweenSeg); - } - // If animation is finished or length not matching, land on right value - } - else { - path = end; - } - this.elem.attr('d', path, void 0, true); - }; - /** - * Update the element with the current animation step. - * - * @function Highcharts.Fx#update - * - * @return {void} - */ - Fx.prototype.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); - } - }; + } else { + setSVGImageSource(elemWrapper.element, 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. + * + * @function Highcharts.SVGRenderer#symbol + * + * @param {string} 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 {Highcharts.SymbolOptionsObject} [options] + * Additional options, depending on the actual symbol drawn. + * + * @return {Highcharts.SVGElement} + */ + SVGRenderer.prototype.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], + path, + imageSrc, + centerImage; + if (symbolFn) { + // Check if there's a path defined for this symbol + if (typeof x === "number") { + path = symbolFn.call( + this.symbols, + Math.round(x || 0), + Math.round(y || 0), + width || 0, + height || 0, + options + ); + } + obj = this.path(path); + if (!ren.styledMode) { + 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 + ); /** - * Run an animation. - * - * @function Highcharts.Fx#run - * - * @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`. - * - * @return {void} + * Set the size and position */ - Fx.prototype.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 && !this.elem['forceAnimate:' + this.prop]) { - delete options.curAnim[this.prop]; - if (options.complete && Object.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); - } - } + centerImage = function () { + obj.attr({ + width: obj.width, + height: obj.height, + }); }; /** - * Run a single step in the animation. - * - * @function Highcharts.Fx#step - * - * @param {boolean} [gotoEnd] - * Whether to go to the endpoint of the animation after abort. - * - * @return {boolean} - * Returns `true` if animation continues. + * 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. */ - Fx.prototype.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; - objectEach(curAnim, function (val) { - if (val !== true) { - done = false; - } + ["width", "height"].forEach(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)) { + // Scale and center the image within its container. + // The name `backgroundSize` is taken from the CSS spec, + // but the value `within` is made up. Other possible + // values in the spec, `cover` and `contain`, can be + // implemented if needed. + if ( + options && + options.backgroundSize === "within" && + this.width && + this.height + ) { + imgSize = Math.round( + imgSize * + Math.min( + this.width / this.imgwidth, + this.height / this.imgheight + ) + ); + } + 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", }); - 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 Highcharts.Fx#initPath - * - * @param {Highcharts.SVGElement} elem - * The SVGElement item. - * - * @param {Highcharts.SVGPathArray|undefined} fromD - * Starting path definition. - * - * @param {Highcharts.SVGPathArray} toD - * Ending path definition. - * - * @return {Array<Highcharts.SVGPathArray,Highcharts.SVGPathArray>} - * An array containing start and end paths in array form so that - * they can be animated in parallel. - */ - Fx.prototype.initPath = function (elem, fromD, toD) { - var shift, - startX = elem.startX, - endX = elem.endX, - fullLength, - i, - start = fromD && fromD.slice(), // copy - end = toD.slice(), // copy - isArea = elem.isArea, - positionFactor = isArea ? 2 : 1, - reverse; - if (!start) { - return [end, end]; - } - /** - * If shifting points, prepend a dummy point to the end path. - * @private - * @param {Highcharts.SVGPathArray} arr - array - * @param {Highcharts.SVGPathArray} other - array - * @return {void} - */ - function prepend(arr, other) { - while (arr.length < fullLength) { - // Move to, line to or curve to? - var moveSegment = arr[0], - otherSegment = other[fullLength - arr.length]; - if (otherSegment && moveSegment[0] === 'M') { - if (otherSegment[0] === 'C') { - arr[0] = [ - 'C', - moveSegment[1], - moveSegment[2], - moveSegment[1], - moveSegment[2], - moveSegment[1], - moveSegment[2] - ]; - } - else { - arr[0] = ['L', moveSegment[1], moveSegment[2]]; - } - } - // Prepend a copy of the first point - arr.unshift(moveSegment); - // For areas, the bottom path goes back again to the left, so we - // need to append a copy of the last point. - if (isArea) { - arr.push(arr[arr.length - 1]); - } - } - } - /** - * Copy and append last point until the length matches the end length. - * @private - * @param {Highcharts.SVGPathArray} arr - array - * @param {Highcharts.SVGPathArray} other - array - * @return {void} - */ - function append(arr, other) { - while (arr.length < fullLength) { - // 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. - var segmentToAdd = arr[arr.length / positionFactor - 1].slice(); - // Disable the first control point of curve segments - if (segmentToAdd[0] === 'C') { - segmentToAdd[1] = segmentToAdd[5]; - segmentToAdd[2] = segmentToAdd[6]; - } - if (!isArea) { - arr.push(segmentToAdd); - } - else { - var lowerSegmentToAdd = arr[arr.length / positionFactor].slice(); - arr.splice(arr.length / 2, 0, segmentToAdd, lowerSegmentToAdd); - } - } - } - // 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; - // Fixed from the right side, "scaling" left - } - else if (startX[startX.length - 1] === - endX[endX.length - startX.length + i]) { - shift = startX.length - i; - break; - } - } - if (typeof shift === 'undefined') { - start = []; - } - } - if (start.length && 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; - if (!reverse) { - prepend(end, start); - append(start, end); - } - else { - prepend(start, end); - append(end, start); - } - } - return [start, end]; - }; - /** - * Handle animation of the color attributes directly. - * - * @function Highcharts.Fx#fillSetter - * - * @return {void} - */ - Fx.prototype.fillSetter = function () { - Fx.prototype.strokeSetter.apply(this, arguments); - }; - /** - * Handle animation of the color attributes directly. - * - * @function Highcharts.Fx#strokeSetter - * - * @return {void} - */ - Fx.prototype.strokeSetter = function () { - this.elem.attr(this.prop, H.color(this.start).tweenTo(H.color(this.end), this.pos), null, true); - }; - return Fx; - }()); - H.Fx = Fx; - - return Fx; - }); - _registerModule(_modules, 'Core/Animation/AnimationUtilities.js', [_modules['Core/Animation/Fx.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (Fx, H, U) { - /* * + doc.body.appendChild(this); + } + // Center the image + symbolSizes[imageSrc] = { + 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.hasLoaded) { + chart.onload(); + } + }, + src: imageSrc, + }); + this.imgCount++; + } + } + return obj; + }; + /** + * Define a clipping rectangle. The clipping rectangle is later applied + * to {@link SVGElement} objects through the {@link SVGElement#clip} + * function. * - * (c) 2010-2020 Torstein Honsi + * @example + * var circle = renderer.circle(100, 100, 100) + * .attr({ fill: 'red' }) + * .add(); + * var clipRect = renderer.clipRect(100, 100, 100, 100); * - * License: www.highcharts.com/license + * // Leave only the lower right quarter visible + * circle.clip(clipRect); * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @function Highcharts.SVGRenderer#clipRect * - * */ - var defined = U.defined, - getStyle = U.getStyle, - isArray = U.isArray, - isNumber = U.isNumber, - isObject = U.isObject, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick; - /** - * Set the global animation to either a given value, or fall back to the given - * chart's animation option. + * @param {number} [x] * - * @function Highcharts.setAnimation + * @param {number} [y] * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>|undefined} animation - * The animation object. + * @param {number} [width] * - * @param {Highcharts.Chart} chart - * The chart instance. + * @param {number} [height] * - * @todo - * This function always relates to a chart, and sets a property on the renderer, - * so it should be moved to the SVGRenderer. + * @return {Highcharts.ClipRectElement} + * A clipping rectangle. */ - var setAnimation = H.setAnimation = function setAnimation(animation, - chart) { - chart.renderer.globalAnimation = pick(animation, - chart.options.chart.animation, - true); + SVGRenderer.prototype.clipRect = function (x, y, width, height) { + var wrapper, + // Add a hyphen at the end to avoid confusion in testing indexes + // -1 and -10, -11 etc (#6550) + id = 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; }; /** - * Get the animation in object form, where a disabled animation is always - * returned as `{ duration: 0 }`. + * 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' })`. * - * @function Highcharts.animObject - * - * @param {boolean|Highcharts.AnimationOptionsObject} [animation=0] - * An animation setting. Can be an object with duration, complete and - * easing properties, or a boolean to enable or disable. + * @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 * - * @return {Highcharts.AnimationOptionsObject} - * An object with at least a duration property. - */ - var animObject = H.animObject = function animObject(animation) { - return isObject(animation) ? - H.merge({ duration: 500, - defer: 0 }, - animation) : - { duration: animation ? 500 : 0, - defer: 0 }; + * @function Highcharts.SVGRenderer#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 {Highcharts.SVGElement} + * The text object. + */ + SVGRenderer.prototype.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 (defined(str)) { + 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; }; /** - * Get the defer as a number value from series animation options. - * - * @function Highcharts.getDeferredAnimation - * - * @param {Highcharts.Chart} chart - * The chart instance. - * - * @param {boolean|Highcharts.AnimationOptionsObject} animation - * An animation setting. Can be an object with duration, complete and - * easing properties, or a boolean to enable or disable. - * - * @param {Highcharts.Series} [series] - * Series to defer animation. - * - * @return {number} - * The numeric value. - */ - var getDeferredAnimation = H.getDeferredAnimation = function (chart, - animation, - series) { - var labelAnimation = animObject(animation); - var s = series ? [series] : chart.series; - var defer = 0; - var duration = 0; - s.forEach(function (series) { - var seriesAnim = animObject(series.options.animation); - defer = animation && defined(animation.defer) ? - labelAnimation.defer : - Math.max(defer, seriesAnim.duration + seriesAnim.defer); - duration = Math.min(labelAnimation.duration, seriesAnim.duration); - }); - // Disable defer for exporting - if (chart.renderer.forExport) { - defer = 0; - } - var anim = { - defer: Math.max(0, - defer - duration), - duration: Math.min(defer, - duration) - }; - return anim; + * Utility to return the baseline offset and total line height from the font + * size. + * + * @function Highcharts.SVGRenderer#fontMetrics + * + * @param {number|string} [fontSize] + * The current font size to inspect. If not given, the font size + * will be found from the DOM element. + * + * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement} [elem] + * The element to inspect for a current font size. + * + * @return {Highcharts.FontMetricsObject} + * The font metrics. + */ + SVGRenderer.prototype.fontMetrics = function (fontSize, elem) { + var lineHeight, baseline; + if ( + (this.styledMode || !/px/.test(fontSize)) && + win.getComputedStyle // old IE doesn't support it + ) { + fontSize = + elem && SVGElement.prototype.getStyle.call(elem, "font-size"); + } else { + 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 { + fontSize = 12; + } + // Empirical values found by comparing font size and bounding box + // height. Applies to the default font family. + // https://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, + }; }; /** - * The global animate method, which uses Fx to create individual animators. + * Correct X and Y positioning of a label for rotation (#1764). * - * @function Highcharts.animate + * @private + * @function Highcharts.SVGRenderer#rotCorr * - * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} el - * The element to animate. + * @param {number} baseline * - * @param {Highcharts.CSSObject|Highcharts.SVGAttributes} 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 {number} rotation * - * @param {Partial<Highcharts.AnimationOptionsObject>} [opt] - * Animation options. + * @param {boolean} [alterY] * - * @return {void} + * @param {Highcharts.PositionObject} */ - var animate = H.animate = function (el, - params, - opt) { - var start, - unit = '', - end, - fx, - args; - if (!isObject(opt)) { // Number or undefined/null - args = arguments; - opt = { - duration: args[2], - easing: args[3], - complete: args[4] - }; - } - if (!isNumber(opt.duration)) { - opt.duration = 400; - } - opt.easing = typeof opt.easing === 'function' ? - opt.easing : - (Math[opt.easing] || Math.easeInOutSine); - opt.curAnim = merge(params); - objectEach(params, function (val, prop) { - // Stop current running animation of this property - stop(el, prop); - fx = new Fx(el, opt, prop); - end = null; - if (prop === 'd' && isArray(params.d)) { - fx.paths = fx.initPath(el, el.pathArray, params.d); - fx.toD = params.d; - start = 0; - end = 1; - } - else if (el.attr) { - start = el.attr(prop); - } - else { - start = parseFloat(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); - }); + SVGRenderer.prototype.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, + }; }; /** - * Stop running animation. + * Compatibility function to convert the legacy one-dimensional path array + * into an array of segments. * - * @function Highcharts.stop - * - * @param {Highcharts.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. - * - * @return {void} + * It is used in maps to parse the `path` option, and in SVGRenderer.dSetter + * to support legacy paths from demos. * - * @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. - */ - var stop = 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 - } + * @private + * @function Highcharts.SVGRenderer#pathToSegments + */ + SVGRenderer.prototype.pathToSegments = function (path) { + var ret = []; + var segment = []; + var commandLength = { + A: 8, + C: 7, + H: 2, + L: 3, + M: 3, + Q: 5, + S: 5, + T: 3, + V: 2, + }; + // Short, non-typesafe parsing of the one-dimensional array. It splits + // the path on any string. This is not type checked against the tuple + // types, but is shorter, and doesn't require specific checks for any + // command type in SVG. + for (var i = 0; i < path.length; i++) { + // Command skipped, repeat previous or insert L/l for M/m + if ( + isString(segment[0]) && + isNumber(path[i]) && + segment.length === commandLength[segment[0].toUpperCase()] + ) { + path.splice(i, 0, segment[0].replace("M", "L").replace("m", "l")); } - }; - var animationExports = { - animate: animate, - animObject: animObject, - getDeferredAnimation: getDeferredAnimation, - setAnimation: setAnimation, - stop: stop - }; - - return animationExports; - }); - _registerModule(_modules, 'Core/Renderer/SVG/SVGElement.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (A, Color, H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var animate = A.animate, - animObject = A.animObject, - stop = A.stop; - var deg2rad = H.deg2rad, - doc = H.doc, - hasTouch = H.hasTouch, - isFirefox = H.isFirefox, - noop = H.noop, - svg = H.svg, - SVG_NS = H.SVG_NS, - win = H.win; - var attr = U.attr, - createElement = U.createElement, - css = U.css, - defined = U.defined, - erase = U.erase, - extend = U.extend, - fireEvent = U.fireEvent, - isArray = U.isArray, - isFunction = U.isFunction, - isNumber = U.isNumber, - isString = U.isString, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick, - pInt = U.pInt, - syncTimeout = U.syncTimeout, - uniqueKey = U.uniqueKey; - /** - * The horizontal alignment of an element. - * - * @typedef {"center"|"left"|"right"} Highcharts.AlignValue - */ - /** - * Options to align the element relative to the chart or another box. - * - * @interface Highcharts.AlignObject - */ /** - * Horizontal alignment. Can be one of `left`, `center` and `right`. - * - * @name Highcharts.AlignObject#align - * @type {Highcharts.AlignValue|undefined} - * - * @default left - */ /** - * Vertical alignment. Can be one of `top`, `middle` and `bottom`. - * - * @name Highcharts.AlignObject#verticalAlign - * @type {Highcharts.VerticalAlignValue|undefined} - * - * @default top - */ /** - * Horizontal pixel offset from alignment. - * - * @name Highcharts.AlignObject#x - * @type {number|undefined} - * - * @default 0 - */ /** - * Vertical pixel offset from alignment. - * - * @name Highcharts.AlignObject#y - * @type {number|undefined} - * - * @default 0 - */ /** - * Use the `transform` attribute with translateX and translateY custom - * attributes to align this elements rather than `x` and `y` attributes. - * - * @name Highcharts.AlignObject#alignByTranslate - * @type {boolean|undefined} - * - * @default false - */ - /** - * Bounding box of an element. - * - * @interface Highcharts.BBoxObject - * @extends Highcharts.PositionObject - */ /** - * Height of the bounding box. - * - * @name Highcharts.BBoxObject#height - * @type {number} - */ /** - * Width of the bounding box. - * - * @name Highcharts.BBoxObject#width - * @type {number} - */ /** - * Horizontal position of the bounding box. - * - * @name Highcharts.BBoxObject#x - * @type {number} - */ /** - * Vertical position of the bounding box. - * - * @name Highcharts.BBoxObject#y - * @type {number} - */ - /** - * 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 - * } - * - * @interface Highcharts.SVGAttributes - */ /** - * @name Highcharts.SVGAttributes#[key:string] - * @type {*} - */ /** - * @name Highcharts.SVGAttributes#d - * @type {string|Highcharts.SVGPathArray|undefined} - */ /** - * @name Highcharts.SVGAttributes#fill - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} - */ /** - * @name Highcharts.SVGAttributes#inverted - * @type {boolean|undefined} - */ /** - * @name Highcharts.SVGAttributes#matrix - * @type {Array<number>|undefined} - */ /** - * @name Highcharts.SVGAttributes#rotation - * @type {number|undefined} - */ /** - * @name Highcharts.SVGAttributes#rotationOriginX - * @type {number|undefined} - */ /** - * @name Highcharts.SVGAttributes#rotationOriginY - * @type {number|undefined} - */ /** - * @name Highcharts.SVGAttributes#scaleX - * @type {number|undefined} - */ /** - * @name Highcharts.SVGAttributes#scaleY - * @type {number|undefined} - */ /** - * @name Highcharts.SVGAttributes#stroke - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} - */ /** - * @name Highcharts.SVGAttributes#style - * @type {string|Highcharts.CSSObject|undefined} - */ /** - * @name Highcharts.SVGAttributes#translateX - * @type {number|undefined} - */ /** - * @name Highcharts.SVGAttributes#translateY - * @type {number|undefined} - */ /** - * @name Highcharts.SVGAttributes#zIndex - * @type {number|undefined} - */ - /** - * An SVG DOM element. The type is a reference to the regular SVGElement in the - * global scope. - * - * @typedef {globals.GlobalSVGElement} Highcharts.SVGDOMElement - * - * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement - */ - /** - * The vertical alignment of an element. - * - * @typedef {"bottom"|"middle"|"top"} Highcharts.VerticalAlignValue - */ - ''; // detach doclets above - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * 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 - * {@link Highcharts.SVGRenderer#rect|rect}, - * {@link Highcharts.SVGRenderer#path|path}, - * {@link Highcharts.SVGRenderer#text|text}, - * {@link Highcharts.SVGRenderer#label|label}, - * {@link Highcharts.SVGRenderer#g|g} - * and more. - * - * @class - * @name Highcharts.SVGElement - */ - var SVGElement = /** @class */ (function () { - function SVGElement() { - /* * - * - * Properties - * - * */ - this.element = void 0; - this.height = void 0; - this.opacity = 1; // Default base for animation - this.renderer = void 0; - this.SVG_NS = SVG_NS; - // Custom attributes used for symbols, these should be filtered out when - // setting SVGElement attributes (#9375). - this.symbolCustomAttribs = [ - 'x', - 'y', - 'width', - 'height', - 'r', - 'start', - 'end', - 'innerR', - 'anchorX', - 'anchorY', - 'rounded' - ]; - this.width = void 0; + // Split on string + if (typeof path[i] === "string") { + if (segment.length) { + ret.push(segment.slice(0)); + } + segment.length = 0; } - /* * - * - * Functions - * - * */ - /** - * Get the current value of an attribute or pseudo attribute, - * used mainly for animation. Called internally from - * the {@link Highcharts.SVGRenderer#attr} function. - * - * @private - * @function Highcharts.SVGElement#_defaultGetter - * - * @param {string} key - * Property key. - * - * @return {number|string} - * Property value. - */ - SVGElement.prototype._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; - }; - /** - * @private - * @function Highcharts.SVGElement#_defaultSetter - * - * @param {string} value - * - * @param {string} key - * - * @param {Highcharts.SVGDOMElement} element - * - * @return {void} - */ - SVGElement.prototype._defaultSetter = function (value, key, element) { - element.setAttribute(key, value); - }; - /** - * Add the element to the DOM. All elements must be added this way. - * - * @sample highcharts/members/renderer-g - * Elements added to a group - * - * @function Highcharts.SVGElement#add - * - * @param {Highcharts.SVGElement} [parent] - * The parent item to add it to. If undefined, the element is added - * to the {@link Highcharts.SVGRenderer.box}. - * - * @return {Highcharts.SVGElement} - * Returns the SVGElement for chaining. - */ - SVGElement.prototype.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 (typeof this.textStr !== 'undefined' && - this.element.nodeName === 'text' // Not for SVGLabel instances - ) { - 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; - }; - /** - * Add a class name to an element. - * - * @function Highcharts.SVGElement#addClass - * - * @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. - * - * @return {Highcharts.SVGElement} - * Return the SVG element for chainability. - */ - SVGElement.prototype.addClass = function (className, replace) { - var currentClassName = replace ? '' : (this.attr('class') || ''); - // Trim the string and remove duplicates - className = (className || '') - .split(/ /g) - .reduce(function (newClassName, name) { - if (currentClassName.indexOf(name) === -1) { - newClassName.push(name); - } - return newClassName; - }, (currentClassName ? - [currentClassName] : - [])) - .join(' '); - if (className !== currentClassName) { - this.attr('class', className); - } - return this; - }; - /** - * 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 - * @function Highcharts.SVGElement#afterSetters - */ - SVGElement.prototype.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; - } - }; - /** - * Align the element relative to the chart or another box. - * - * @function Highcharts.SVGElement#align - * - * @param {Highcharts.AlignObject} [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 {boolean} [alignByTranslate] - * Align element by translation. - * - * @param {string|Highcharts.BBoxObject} [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. - * - * @return {Highcharts.SVGElement} Returns the SVGElement for chaining. - */ - SVGElement.prototype.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 = void 0; // reassign it below + segment.push(path[i]); + } + ret.push(segment.slice(0)); + return ret; + /* + // Fully type-safe version where each tuple type is checked. The + // downside is filesize and a lack of flexibility for unsupported + // commands + const ret: SVGPath = [], + commands = { + A: 7, + C: 6, + H: 1, + L: 2, + M: 2, + Q: 4, + S: 4, + T: 2, + V: 1, + Z: 0 + }; + + let i = 0, + lastI = 0, + lastCommand; + + while (i < path.length) { + const item = path[i]; + + let command; + + if (typeof item === 'string') { + command = item; + i += 1; + } else { + command = lastCommand || 'M'; } - // 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; - // default: left align - x = (box.x || 0) + (alignOptions.x || 0); - // default: top align - y = (box.y || 0) + (alignOptions.y || 0); - // 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; + + // Upper case + const commandUC = command.toUpperCase(); + + if (commandUC in commands) { + + // No numeric parameters + if (command === 'Z' || command === 'z') { + ret.push([command]); + + // One numeric parameter + } else { + const val0 = path[i]; + if (typeof val0 === 'number') { + + // Horizontal line to + if (command === 'H' || command === 'h') { + ret.push([command, val0]); + i += 1; + + // Vertical line to + } else if (command === 'V' || command === 'v') { + ret.push([command, val0]); + i += 1; + + // Two numeric parameters + } else { + const val1 = path[i + 1]; + if (typeof val1 === 'number') { + // lineTo + if (command === 'L' || command === 'l') { + ret.push([command, val0, val1]); + i += 2; + + // moveTo + } else if (command === 'M' || command === 'm') { + ret.push([command, val0, val1]); + i += 2; + + // Smooth quadratic bezier + } else if (command === 'T' || command === 't') { + ret.push([command, val0, val1]); + i += 2; + + // Four numeric parameters + } else { + const val2 = path[i + 2], + val3 = path[i + 3]; + if ( + typeof val2 === 'number' && + typeof val3 === 'number' + ) { + // Quadratic bezier to + if ( + command === 'Q' || + command === 'q' + ) { + ret.push([ + command, + val0, + val1, + val2, + val3 + ]); + i += 4; + + // Smooth cubic bezier to + } else if ( + command === 'S' || + command === 's' + ) { + ret.push([ + command, + val0, + val1, + val2, + val3 + ]); + i += 4; + + // Six numeric parameters + } else { + const val4 = path[i + 4], + val5 = path[i + 5]; + + if ( + typeof val4 === 'number' && + typeof val5 === 'number' + ) { + // Curve to + if ( + command === 'C' || + command === 'c' + ) { + ret.push([ + command, + val0, + val1, + val2, + val3, + val4, + val5 + ]); + i += 6; + + // Seven numeric parameters + } else { + const val6 = path[i + 6]; + + // Arc to + if ( + typeof val6 === + 'number' && + ( + command === 'A' || + command === 'a' + ) + ) { + ret.push([ + command, + val0, + val1, + val2, + val3, + val4, + val5, + val6 + ]); + i += 7; + + } + + } + } + } + } + } + } + + } + } + } + } + + // An unmarked command following a moveTo is a lineTo + lastCommand = command === 'M' ? 'L' : command; + + if (i === lastI) { + break; + } + lastI = i; } - 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; - }; + return ret; + */ + }; + /** + * 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' })`. + * + * @sample highcharts/members/renderer-label-on-chart/ + * A label on the chart + * + * @function Highcharts.SVGRenderer#label + * + * @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='rect'] + * 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} [useHTML=false] + * Wether to use HTML to render the label. + * + * @param {boolean} [baseline=false] + * 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 {Highcharts.SVGElement} + * The generated label. + */ + SVGRenderer.prototype.label = function ( + str, + x, + y, + shape, + anchorX, + anchorY, + useHTML, + baseline, + className + ) { + return new SVGLabel( + this, + str, + x, + y, + shape, + anchorX, + anchorY, + useHTML, + baseline, + className + ); + }; + return SVGRenderer; + })(); + /** + * A pointer to the renderer's associated Element class. The VMLRenderer + * will have a pointer to VMLElement here. + * + * @name Highcharts.SVGRenderer#Element + * @type {Highcharts.SVGElement} + */ + SVGRenderer.prototype.Element = SVGElement; + /** + * @private + */ + SVGRenderer.prototype.SVG_NS = SVG_NS; + /** + * Dummy function for plugins, called every time the renderer is updated. + * Prior to Highcharts 5, this was used for the canvg renderer. + * + * @deprecated + * @function Highcharts.SVGRenderer#draw + */ + SVGRenderer.prototype.draw = noop; + /** + * 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['"'] = '"'; + * + * @name Highcharts.SVGRenderer#escapes + * @type {Highcharts.Dictionary<string>} + */ + SVGRenderer.prototype.escapes = { + "&": "&", + "<": "<", + ">": ">", + "'": "'", + '"': """, + }; + /** + * An extendable collection of functions for defining symbol paths. + * + * @name Highcharts.SVGRenderer#symbols + * @type {Highcharts.SymbolDictionary} + */ + SVGRenderer.prototype.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: Math.PI * 0.5, + end: Math.PI * 2.5, + open: false, + }); + }, + square: function (x, y, w, h) { + return [ + ["M", x, y], + ["L", x + w, y], + ["L", x + w, y + h], + ["L", x, y + h], + ["Z"], + ]; + }, + triangle: function (x, y, w, h) { + return [ + ["M", x + w / 2, y], + ["L", x + w, y + h], + ["L", x, y + h], + ["Z"], + ]; + }, + "triangle-down": function (x, y, w, h) { + return [["M", x, y], ["L", x + w, y], ["L", x + w / 2, y + h], ["Z"]]; + }, + diamond: function (x, y, w, h) { + return [ + ["M", x + w / 2, y], + ["L", x + w, y + h / 2], + ["L", x + w / 2, y + h], + ["L", x, y + h / 2], + ["Z"], + ]; + }, + arc: function (x, y, w, h, options) { + var arc = []; + if (options) { + var start = options.start || 0, + end = options.end || 0, + rx = options.r || w, + ry = options.r || h || w, + proximity = 0.001, + fullCircle = Math.abs(end - 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 = 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 = pick( + options.longArc, + end - start - Math.PI < proximity ? 0 : 1 + ); + arc.push( + ["M", x + rx * cosStart, y + ry * sinStart], + [ + "A", + rx, + ry, + 0, + longArc, + pick(options.clockwise, 1), + x + rx * cosEnd, + y + ry * sinEnd, + ] + ); + if (defined(innerRadius)) { + arc.push( + open + ? ["M", x + innerRadius * cosEnd, y + innerRadius * sinEnd] + : ["L", x + innerRadius * cosEnd, y + innerRadius * sinEnd], + [ + "A", + innerRadius, + innerRadius, + 0, + longArc, + // Clockwise - opposite to the outer arc clockwise + defined(options.clockwise) ? 1 - options.clockwise : 0, + x + innerRadius * cosStart, + y + innerRadius * sinStart, + ] + ); + } + if (!open) { + arc.push(["Z"]); + } + } + 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) || 0, + anchorY = (options && options.anchorY) || 0, + path; + path = [ + ["M", x + r, y], + ["L", x + w - r, y], + ["C", x + w, y, x + w, y, x + w, y + r], + ["L", x + w, y + h - r], + ["C", x + w, y + h, x + w, y + h, x + w - r, y + h], + ["L", x + r, y + h], + ["C", x, y + h, x, y + h, x, y + h - r], + ["L", x, y + r], + ["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( + 3, + 1, + ["L", x + w, anchorY - halfDistance], + ["L", x + w + arrowLength, anchorY], + ["L", x + w, anchorY + halfDistance], + ["L", x + w, y + h - r] + ); + // Simple connector + } else { + path.splice( + 3, + 1, + ["L", x + w, h / 2], + ["L", anchorX, anchorY], + ["L", x + w, h / 2], + ["L", 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( + 7, + 1, + ["L", x, anchorY + halfDistance], + ["L", x - arrowLength, anchorY], + ["L", x, anchorY - halfDistance], + ["L", x, y + r] + ); + // Simple connector + } else { + path.splice( + 7, + 1, + ["L", x, h / 2], + ["L", anchorX, anchorY], + ["L", x, h / 2], + ["L", x, y + r] + ); + } + } else if ( + // replace bottom + anchorY && + anchorY > h && + anchorX > x + safeDistance && + anchorX < x + w - safeDistance + ) { + path.splice( + 5, + 1, + ["L", anchorX + halfDistance, y + h], + ["L", anchorX, y + h + arrowLength], + ["L", anchorX - halfDistance, y + h], + ["L", x + r, y + h] + ); + } else if ( + // replace top + anchorY && + anchorY < 0 && + anchorX > x + safeDistance && + anchorX < x + w - safeDistance + ) { + path.splice( + 1, + 1, + ["L", anchorX - halfDistance, y], + ["L", anchorX, y - arrowLength], + ["L", anchorX + halfDistance, y], + ["L", w - r, y] + ); + } + return path; + }, + }; + H.SVGRenderer = SVGRenderer; + H.Renderer = H.SVGRenderer; + + return H.Renderer; + } + ); + _registerModule( + _modules, + "Core/Renderer/HTML/HTMLElement.js", + [ + _modules["Core/Globals.js"], + _modules["Core/Renderer/SVG/SVGElement.js"], + _modules["Core/Utilities.js"], + ], + function (H, SVGElement, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var css = U.css, + defined = U.defined, + extend = U.extend, + pick = U.pick, + pInt = U.pInt; + /** + * Element placebo + * @private + */ + var HTMLElement = SVGElement; + var isFirefox = H.isFirefox; + /* eslint-disable valid-jsdoc */ + // Extend SvgElement for useHTML option. + extend( + HTMLElement.prototype, + /** @lends SVGElement.prototype */ { + /** + * Apply CSS to HTML elements. This is used in text within SVG rendering and + * by the VML renderer + * + * @private + * @function Highcharts.SVGElement#htmlCss + * + * @param {Highcharts.CSSObject} styles + * + * @return {Highcharts.SVGElement} + */ + htmlCss: function (styles) { + var wrapper = this, + element = wrapper.element, + // When setting or unsetting the width style, we need to update + // transform (#8809) + isSettingWidth = + element.tagName === "SPAN" && styles && "width" in styles, + textWidth = pick(isSettingWidth && styles.width, void 0), + doTransform; + if (isSettingWidth) { + delete styles.width; + wrapper.textWidth = textWidth; + doTransform = true; + } + if (styles && styles.textOverflow === "ellipsis") { + styles.whiteSpace = "nowrap"; + styles.overflow = "hidden"; + } + wrapper.styles = extend(wrapper.styles, styles); + css(wrapper.element, styles); + // Now that all styles are applied, to the transform + if (doTransform) { + wrapper.htmlUpdateTransform(); + } + return wrapper; + }, + /** + * VML and useHTML method for calculating the bounding box based on offsets. + * + * @private + * @function Highcharts.SVGElement#htmlGetBBox + * + * @param {boolean} refresh + * Whether to force a fresh value from the DOM or to use the cached + * value. + * + * @return {Highcharts.BBoxObject} + * 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. + * + * @private + * @function Highcharts.SVGElement#htmlUpdateTransform + * @return {void} + */ + 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, + whiteSpace = styles && styles.whiteSpace; /** * @private - * @function Highcharts.SVGElement#alignSetter - * @param {"left"|"center"|"right"} value - */ - SVGElement.prototype.alignSetter = function (value) { - var convert = { - left: 'start', - center: 'middle', - right: 'end' - }; - if (convert[value]) { - this.alignValue = value; - this.element.setAttribute('text-anchor', convert[value]); - } - }; - /** - * Animate to given attributes or CSS properties. - * - * @sample highcharts/members/element-on/ - * Setting some attributes by animation - * - * @function Highcharts.SVGElement#animate - * - * @param {Highcharts.SVGAttributes} params - * SVG attributes or CSS to animate. - * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [options] - * Animation options. - * - * @param {Function} [complete] - * Function to perform at the end of animation. - * - * @return {Highcharts.SVGElement} - * Returns the SVGElement for chaining. + * @return {number} */ - SVGElement.prototype.animate = function (params, options, complete) { - var _this = this; - var animOptions = animObject(pick(options, - this.renderer.globalAnimation, - true)), - deferTime = animOptions.defer; - // When the page is hidden save resources in the background by not - // running animation at all (#9749). - if (pick(doc.hidden, doc.msHidden, doc.webkitHidden, false)) { - animOptions.duration = 0; - } - if (animOptions.duration !== 0) { - // allows using a callback with the global animation without - // overwriting it - if (complete) { - animOptions.complete = complete; + function getTextPxLength() { + // Reset multiline/ellipsis in order to read width (#4928, + // #5417) + css(elem, { + width: "", + whiteSpace: whiteSpace || "nowrap", + }); + return elem.offsetWidth; + } + // apply translate + css(elem, { + marginLeft: translateX, + marginTop: translateY, + }); + if (!renderer.styledMode && wrapper.shadows) { + // used in labels/tooltip + wrapper.shadows.forEach(function (shadow) { + css(shadow, { + marginLeft: translateX + 1, + marginTop: translateY + 1, + }); + }); + } + // apply inversion + if (wrapper.inverted) { + // wrapper is a group + [].forEach.call(elem.childNodes, function (child) { + renderer.invertChild(child, elem); + }); + } + if (elem.tagName === "SPAN") { + var rotation = wrapper.rotation, + baseline, + textWidth = wrapper.textWidth && pInt(wrapper.textWidth), + currentTextTransform = [ + rotation, + align, + elem.innerHTML, + wrapper.textWidth, + wrapper.textAlign, + ].join(","); + // Update textWidth. Use the memoized textPxLength if possible, to + // avoid the getTextPxLength function using elem.offsetWidth. + // Calling offsetWidth affects rendering time as it forces layout + // (#7656). + if ( + textWidth !== wrapper.oldTextWidth && + (textWidth > wrapper.oldTextWidth || + (wrapper.textPxLength || getTextPxLength()) > textWidth) && + // Only set the width if the text is able to word-wrap, or + // text-overflow is ellipsis (#9537) + (/[ \-]/.test(elem.textContent || elem.innerText) || + elem.style.textOverflow === "ellipsis") + ) { + // #983, #1254 + css(elem, { + width: textWidth + "px", + display: "block", + whiteSpace: whiteSpace || "normal", // #3331 + }); + wrapper.oldTextWidth = textWidth; + wrapper.hasBoxWidthChanged = true; // #8159 + } else { + wrapper.hasBoxWidthChanged = false; // #8159 + } + // Do the calculations and DOM access only if properties changed + if (currentTextTransform !== wrapper.cTT) { + baseline = renderer.fontMetrics(elem.style.fontSize, elem).b; + // Renderer specific handling of span rotation, but only if we + // have something to update. + if ( + defined(rotation) && + (rotation !== (wrapper.oldRotation || 0) || + align !== wrapper.oldAlign) + ) { + wrapper.setSpanRotation(rotation, alignCorrection, baseline); + } + wrapper.getSpanCorrection( + // Avoid elem.offsetWidth if we can, it affects rendering + // time heavily (#7656) + (!defined(rotation) && wrapper.textPxLength) || // #7920 + elem.offsetWidth, + baseline, + alignCorrection, + rotation, + align + ); + } + // apply position with correction + css(elem, { + left: x + (wrapper.xCorr || 0) + "px", + top: y + (wrapper.yCorr || 0) + "px", + }); + // record current text transform + wrapper.cTT = currentTextTransform; + wrapper.oldRotation = rotation; + wrapper.oldAlign = align; + } + }, + /** + * Set the rotation of an individual HTML span. + * + * @private + * @function Highcharts.SVGElement#setSpanRotation + * @param {number} rotation + * @param {number} alignCorrection + * @param {number} baseline + * @return {void} + */ + 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. + * + * @private + * @function Highcharts.SVGElement#getSpanCorrection + * @param {number} width + * @param {number} baseline + * @param {number} alignCorrection + * @return {void} + */ + getSpanCorrection: function (width, baseline, alignCorrection) { + this.xCorr = -width * alignCorrection; + this.yCorr = -baseline; + }, + } + ); + + return HTMLElement; + } + ); + _registerModule( + _modules, + "Core/Renderer/HTML/HTMLRenderer.js", + [ + _modules["Core/Globals.js"], + _modules["Core/Renderer/SVG/SVGElement.js"], + _modules["Core/Renderer/SVG/SVGRenderer.js"], + _modules["Core/Utilities.js"], + ], + function (H, SVGElement, SVGRenderer, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var isFirefox = H.isFirefox, + isMS = H.isMS, + isWebKit = H.isWebKit, + win = H.win; + var attr = U.attr, + createElement = U.createElement, + extend = U.extend, + pick = U.pick; + /** + * Renderer placebo + * @private + */ + var HTMLRenderer = SVGRenderer; + /* eslint-disable valid-jsdoc */ + // Extend SvgRenderer for useHTML option. + extend( + SVGRenderer.prototype, + /** @lends SVGRenderer.prototype */ { + /** + * @private + * @function Highcharts.SVGRenderer#getTransformKey + * + * @return {string} + */ + 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. + * + * @private + * @function Highcharts.SVGRenderer#html + * + * @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. + * + * @return {Highcharts.HTMLDOMElement} + */ + html: function (str, x, y) { + var wrapper = this.createElement("span"), + element = wrapper.element, + renderer = wrapper.renderer, + isSVG = renderer.isSVG, + addSetters = function (gWrapper, style) { + // These properties are set as attributes on the SVG group, and + // as identical CSS properties on the div. (#3542) + ["opacity", "visibility"].forEach(function (prop) { + gWrapper[prop + "Setter"] = function (value, key, elem) { + var styleObject = gWrapper.div ? gWrapper.div.style : style; + SVGElement.prototype[prop + "Setter"].call( + this, + value, + key, + elem + ); + if (styleObject) { + styleObject[key] = value; } - // If defer option is defined delay the animation #12901 - syncTimeout(function () { - if (_this.element) { - animate(_this, params, animOptions); - } - }, deferTime); + }; + }); + gWrapper.addedSetters = true; + }; + // Text setter + wrapper.textSetter = function (value) { + if (value !== element.innerHTML) { + delete this.bBox; + delete this.oldTextWidth; + } + this.textStr = value; + element.innerHTML = pick(value, ""); + wrapper.doTransform = true; + }; + // 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. + wrapper.alignValue = wrapper.textAlign = value; + } else { + wrapper[key] = value; + } + wrapper.doTransform = true; + }; + // Runs at the end of .attr() + wrapper.afterSetters = function () { + // Update transform. Do this outside the loop to prevent redundant + // updating for batch setting of attributes. + if (this.doTransform) { + this.htmlUpdateTransform(); + this.doTransform = false; + } + }; + // Set the default attributes + wrapper + .attr({ + text: str, + x: Math.round(x), + y: Math.round(y), + }) + .css({ + position: "absolute", + }); + if (!renderer.styledMode) { + wrapper.css({ + fontFamily: this.style.fontFamily, + fontSize: this.style.fontSize, + }); + } + // 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 + parents.reverse().forEach(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) + * @private + * @param {*} value + * @param {string} key + * @return {void} + */ + function translateSetter(value, key) { + parentGroup[key] = value; + if (key === "translateX") { + htmlGroupStyle.left = value + "px"; + } else { + htmlGroupStyle.top = value + "px"; + } + parentGroup.doTransform = true; + } + // 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 ? { className: cls } : void 0, + { + position: "absolute", + left: (parentGroup.translateX || 0) + "px", + top: (parentGroup.translateY || 0) + "px", + display: parentGroup.display, + opacity: parentGroup.opacity, + 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, + }); + if (!parentGroup.addedSetters) { + addSetters(parentGroup); + } + }); + } + } else { + htmlGroup = container; } - else { - this.attr(params, void 0, complete); - // Call the end step synchronously - objectEach(params, function (val, prop) { - if (animOptions.step) { - animOptions.step.call(this, val, { prop: prop, pos: 1 }); - } - }, this); + htmlGroup.appendChild(element); + // Shared with VML: + wrapper.added = true; + if (wrapper.alignOnAdd) { + wrapper.htmlUpdateTransform(); } - return this; - }; - /** - * 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 - * [example](https://jsfiddle.net/highcharts/43soe9m1/2/). - * - * @example - * // Specific color - * text.css({ - * textOutline: '1px black' - * }); - * // Automatic contrast - * text.css({ - * color: '#000000', // black text - * textOutline: '1px contrast' // => white outline - * }); - * - * @private - * @function Highcharts.SVGElement#applyTextOutline - * - * @param {string} textOutline - * A custom CSS `text-outline` setting, defined by `width color`. - */ - SVGElement.prototype.applyTextOutline = function (textOutline) { - var elem = this.element, - tspans, - hasContrast = textOutline.indexOf('contrast') !== -1, - styles = {}, - color, - strokeWidth, - firstRealChild; - // 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. - this.removeTextOutline(tspans); - // Check if the element contains RTL characters. - // Comparing against Hebrew and Arabic characters, - // excluding Arabic digits. Source: - // https://www.unicode.org/Public/UNIDATA/extracted/DerivedBidiClass.txt - var isRTL_1 = elem.textContent ? - /^[\u0591-\u065F\u066A-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/ - .test(elem.textContent) : false; - // For each of the tspans, create a stroked copy behind it. - firstRealChild = elem.firstChild; - tspans.forEach(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. - // For RTL elements apply outline properties for orginal element - // to prevent outline from overlapping the text. - // For RTL in Firefox keep the orginal order (#10162). - clone = tspan.cloneNode(true); - attr((isRTL_1 && !isFirefox) ? tspan : clone, { - 'class': 'highcharts-text-outline', - fill: color, - stroke: color, - 'stroke-width': strokeWidth, - 'stroke-linejoin': 'round' - }); - elem.insertBefore(clone, firstRealChild); - }); - // Create a whitespace between tspan and clone, - // to fix the display of Arabic characters in Firefox. - if (isRTL_1 && isFirefox && tspans[0]) { - var whitespace = tspans[0].cloneNode(true); - whitespace.textContent = ' '; - elem.insertBefore(whitespace, firstRealChild); - } - } - }; - /** - * @function Highcharts.SVGElement#attr - * @param {string} key - * @return {number|string} - */ /** - * 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`. - * - * @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' - * - * @function Highcharts.SVGElement#attr - * - * @param {string|Highcharts.SVGAttributes} [hash] - * The native and custom SVG attributes. - * - * @param {number|string|Highcharts.SVGPathArray} [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. - * - * @return {Highcharts.SVGElement} - * If used as a setter, it returns the current - * {@link Highcharts.SVGElement} so the calls can be chained. If - * used as a getter, the current value of the attribute is returned. - */ - SVGElement.prototype.attr = function (hash, val, complete, continueAnimation) { - var key, - element = this.element, - hasSetSymbolSize, - ret = this, - skipAttr, - setter, - symbolCustomAttribs = this.symbolCustomAttribs; - // single key-value pair - if (typeof hash === 'string' && typeof 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 && - symbolCustomAttribs.indexOf(key) !== -1) { - 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.styledMode && - 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.call(this); - } - return ret; - }; - /** - * Apply a clipping rectangle to this element. - * - * @function Highcharts.SVGElement#clip - * - * @param {Highcharts.ClipRectElement} [clipRect] - * The clipping rectangle. If skipped, the current clip is removed. - * - * @return {Highcharts.SVGElement} - * Returns the SVG element to allow chaining. - */ - SVGElement.prototype.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. - * - * @function Highcharts.SVGElement#crisp - * - * @param {Highcharts.RectangleObject} rect - * Rectangle to crisp. - * - * @param {number} [strokeWidth] - * The stroke width to consider when computing crisp positioning. It can - * also be set directly on the rect parameter. - * - * @return {Highcharts.RectangleObject} - * The modified rectangle arguments. - */ - SVGElement.prototype.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; - }; - /** - * Build and apply an SVG gradient out of a common JavaScript configuration - * object. This function is called from the attribute setters. An event - * hook is added for supporting other complex color types. - * - * @private - * @function Highcharts.SVGElement#complexColor - * - * @param {Highcharts.GradientColorObject|Highcharts.PatternObject} colorOptions - * The gradient or pattern options structure. - * - * @param {string} prop - * The property to apply, can either be `fill` or `stroke`. - * - * @param {Highcharts.SVGDOMElement} elem - * SVG element to apply the gradient on. - */ - SVGElement.prototype.complexColor = function (colorOptions, prop, elem) { - var renderer = this.renderer, - colorObject, - gradName, - gradAttr, - radAttr, - gradients, - stops, - stopColor, - stopOpacity, - radialReference, - id, - key = [], - value; - fireEvent(this.renderer, 'complexColor', { - args: arguments - }, function () { - // Apply linear or radial gradients - if (colorOptions.radialGradient) { - gradName = 'radialGradient'; - } - else if (colorOptions.linearGradient) { - gradName = 'linearGradient'; - } - if (gradName) { - gradAttr = colorOptions[gradName]; - gradients = renderer.gradients; - stops = colorOptions.stops; - radialReference = elem.radialReference; - // Keep < 2.2 kompatibility - if (isArray(gradAttr)) { - colorOptions[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)) { - // Save the radial attributes for updating - radAttr = gradAttr; - 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 = uniqueKey(); - var gradientObject_1 = gradients[key] = - renderer.createElement(gradName) - .attr(gradAttr) - .add(renderer.defs); - gradientObject_1.radAttr = radAttr; - // The gradient needs to keep a list of stops to be able to - // destroy them - gradientObject_1.stops = []; - stops.forEach(function (stop) { - var stopObject; - if (stop[1].indexOf('rgba') === 0) { - colorObject = Color.parse(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_1); - // Add the stop element to the gradient - gradientObject_1.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) - colorOptions.toString = function () { - return value; - }; - } - }); - }; - /** - * 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. - * - * @sample highcharts/members/renderer-text-on-chart/ - * Styled text - * - * @function Highcharts.SVGElement#css - * - * @param {Highcharts.CSSObject} styles - * The new CSS styles. - * - * @return {Highcharts.SVGElement} - * Return the SVG element for chaining. - */ - SVGElement.prototype.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 (oldStyles && oldStyles[n] !== style) { - 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 - if (styles) { - // Previously set, unset it (#8234) - if (styles.width === null || styles.width === 'auto') { - delete this.textWidth; - // Apply new - } - else if (elem.nodeName.toLowerCase() === 'text' && - styles.width) { - textWidth = this.textWidth = pInt(styles.width); - } - } - // store object - this.styles = styles; - if (textWidth && (!svg && this.renderer.forExport)) { - delete styles.width; - } - // Serialize and set style attribute - if (elem.namespaceURI === this.SVG_NS) { // #7633 - hyphenate = function (a, b) { - return '-' + b.toLowerCase(); - }; - objectEach(styles, function (style, n) { - if (svgPseudoProps.indexOf(n) === -1) { - serializedCss += - n.replace(/([A-Z])/g, hyphenate) + ':' + - style + ';'; - } - }); - if (serializedCss) { - attr(elem, 'style', serializedCss); // #1881 - } - } - else { - css(elem, styles); - } - 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; - }; - /** - * @private - * @function Highcharts.SVGElement#dashstyleSetter - * @param {string} value - */ - SVGElement.prototype.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) { - var v = 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 = v.length; - while (i--) { - v[i] = '' + (pInt(v[i]) * pick(strokeWidth, NaN)); - } - value = v.join(',').replace(/NaN/g, 'none'); // #3226 - this.element.setAttribute('stroke-dasharray', value); - } - }; - /** - * Destroy the element and element wrapper and clear up the DOM and event - * hooks. - * - * @function Highcharts.SVGElement#destroy - */ - SVGElement.prototype.destroy = function () { - var wrapper = this, - element = wrapper.element || {}, - renderer = wrapper.renderer, - parentToClean = (renderer.isSVG && - element.nodeName === 'SPAN' && - wrapper.parentGroup || - void 0), - 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) { - var clipPath_1 = wrapper.clipPath; - // Look for existing references to this clipPath and remove them - // before destroying the element (#6196). - // The upper case version is for Edge - [].forEach.call(ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'), function (el) { - var clipPathAttr = el.getAttribute('clip-path'); - if (clipPathAttr.indexOf(clipPath_1.element.id) > -1) { - el.removeAttribute('clip-path'); - } - }); - wrapper.clipPath = clipPath_1.destroy(); - } - // Destroy stops in case this is a gradient object @todo old code? - if (wrapper.stops) { - for (i = 0; i < wrapper.stops.length; i++) { - wrapper.stops[i].destroy(); - } - wrapper.stops.length = 0; - wrapper.stops = void 0; - } - // remove element - wrapper.safeRemoveChild(element); - if (!renderer.styledMode) { - 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(renderer.alignedObjects, wrapper); - } - objectEach(wrapper, function (val, key) { - // Destroy child elements of a group - if (wrapper[key] && - wrapper[key].parentGroup === wrapper && - wrapper[key].destroy) { - wrapper[key].destroy(); - } - // Delete all properties - delete wrapper[key]; + return wrapper; + }; + } + return wrapper; + }, + } + ); + + return HTMLRenderer; + } + ); + _registerModule( + _modules, + "Core/Axis/Tick.js", + [_modules["Core/Globals.js"], _modules["Core/Utilities.js"]], + function (H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /** + * Optional parameters for the tick. + * @private + * @interface Highcharts.TickParametersObject + */ /** + * Set category for the tick. + * @name Highcharts.TickParametersObject#category + * @type {string|undefined} + */ /** + * @name Highcharts.TickParametersObject#options + * @type {Highcharts.Dictionary<any>|undefined} + */ /** + * Set tickmarkOffset for the tick. + * @name Highcharts.TickParametersObject#tickmarkOffset + * @type {number|undefined} + */ + var clamp = U.clamp, + correctFloat = U.correctFloat, + defined = U.defined, + destroyObjectProperties = U.destroyObjectProperties, + extend = U.extend, + fireEvent = U.fireEvent, + isNumber = U.isNumber, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick; + var deg2rad = H.deg2rad; + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * The Tick class. + * + * @class + * @name Highcharts.Tick + * + * @param {Highcharts.Axis} axis + * The axis of the tick. + * + * @param {number} pos + * The position of the tick on the axis in terms of axis values. + * + * @param {string} [type] + * The type of tick, either 'minor' or an empty string + * + * @param {boolean} [noLabel=false] + * Whether to disable the label or not. Defaults to false. + * + * @param {object} [parameters] + * Optional parameters for the tick. + */ + var Tick = /** @class */ (function () { + /* * + * + * Constructors + * + * */ + function Tick(axis, pos, type, noLabel, parameters) { + this.isNew = true; + this.isNewLabel = true; + /** + * The related axis of the tick. + * @name Highcharts.Tick#axis + * @type {Highcharts.Axis} + */ + this.axis = axis; + /** + * The logical position of the tick on the axis in terms of axis values. + * @name Highcharts.Tick#pos + * @type {number} + */ + this.pos = pos; + /** + * The tick type, which can be `"minor"`, or an empty string. + * @name Highcharts.Tick#type + * @type {string} + */ + this.type = type || ""; + this.parameters = parameters || {}; + /** + * The mark offset of the tick on the axis. Usually `undefined`, numeric + * for grid axes. + * @name Highcharts.Tick#tickmarkOffset + * @type {number|undefined} + */ + this.tickmarkOffset = this.parameters.tickmarkOffset; + this.options = this.parameters.options; + fireEvent(this, "init"); + if (!type && !noLabel) { + this.addLabel(); + } + } + /* * + * + * Functions + * + * */ + /** + * Write the tick label. + * + * @private + * @function Highcharts.Tick#addLabel + * @return {void} + */ + Tick.prototype.addLabel = function () { + var tick = this, + axis = tick.axis, + options = axis.options, + chart = axis.chart, + categories = axis.categories, + log = axis.logarithmic, + names = axis.names, + pos = tick.pos, + labelOptions = pick( + tick.options && tick.options.labels, + options.labels + ), + str, + tickPositions = axis.tickPositions, + isFirst = pos === tickPositions[0], + isLast = pos === tickPositions[tickPositions.length - 1], + value = + this.parameters.category || + (categories ? pick(categories[pos], names[pos], pos) : pos), + label = tick.label, + animateLabels = + (!labelOptions.step || labelOptions.step === 1) && + axis.tickInterval === 1, + tickPositionInfo = tickPositions.info, + dateTimeLabelFormat, + dateTimeLabelFormats, + i, + list; + // Set the datetime label format. If a higher rank is set for this + // position, use that. If not, use the general format. + if (axis.dateTime && tickPositionInfo) { + dateTimeLabelFormats = chart.time.resolveDTLFormat( + options.dateTimeLabelFormats[ + (!options.grid && tickPositionInfo.higherRanks[pos]) || + tickPositionInfo.unitName + ] + ); + dateTimeLabelFormat = dateTimeLabelFormats.main; + } + // set properties for access in render method + /** + * True if the tick is the first one on the axis. + * @name Highcharts.Tick#isFirst + * @readonly + * @type {boolean|undefined} + */ + tick.isFirst = isFirst; + /** + * True if the tick is the last one on the axis. + * @name Highcharts.Tick#isLast + * @readonly + * @type {boolean|undefined} + */ + tick.isLast = isLast; + // Get the string + tick.formatCtx = { + axis: axis, + chart: chart, + isFirst: isFirst, + isLast: isLast, + dateTimeLabelFormat: dateTimeLabelFormat, + tickPositionInfo: tickPositionInfo, + value: log ? correctFloat(log.lin2log(value)) : value, + pos: pos, + }; + str = axis.labelFormatter.call(tick.formatCtx, this.formatCtx); + // Set up conditional formatting based on the format list if existing. + list = dateTimeLabelFormats && dateTimeLabelFormats.list; + if (list) { + tick.shortenLabel = function () { + for (i = 0; i < list.length; i++) { + label.attr({ + text: axis.labelFormatter.call( + extend(tick.formatCtx, { dateTimeLabelFormat: list[i] }) + ), }); - return; - }; - /** - * Destroy shadows on the element. - * - * @private - * @function Highcharts.SVGElement#destroyShadows - * - * @return {void} - */ - SVGElement.prototype.destroyShadows = function () { - (this.shadows || []).forEach(function (shadow) { - this.safeRemoveChild(shadow); - }, this); - this.shadows = void 0; - }; - /** - * @private - */ - SVGElement.prototype.destroyTextPath = function (elem, path) { - var textElement = elem.getElementsByTagName('text')[0]; - var tspans; - if (textElement) { - // Remove textPath attributes - textElement.removeAttribute('dx'); - textElement.removeAttribute('dy'); - // Remove ID's: - path.element.setAttribute('id', ''); - // Check if textElement includes textPath, - if (this.textPathWrapper && - textElement.getElementsByTagName('textPath').length) { - // Move nodes to <text> - tspans = this.textPathWrapper.element.childNodes; - // Now move all <tspan>'s to the <textPath> node - while (tspans.length) { - textElement.appendChild(tspans[0]); - } - // Remove <textPath> from the DOM - textElement.removeChild(this.textPathWrapper.element); - } - } - else if (elem.getAttribute('dx') || elem.getAttribute('dy')) { - // Remove textPath attributes from elem - // to get correct text-outline position - elem.removeAttribute('dx'); - elem.removeAttribute('dy'); - } - if (this.textPathWrapper) { - // Set textPathWrapper to undefined and destroy it - this.textPathWrapper = this.textPathWrapper.destroy(); - } - }; - /** - * @private - * @function Highcharts.SVGElement#dSettter - * @param {number|string|Highcharts.SVGPathArray} value - * @param {string} key - * @param {Highcharts.SVGDOMElement} element - */ - SVGElement.prototype.dSetter = function (value, key, element) { - if (isArray(value)) { - // Backwards compatibility, convert one-dimensional array into an - // array of segments - if (typeof value[0] === 'string') { - value = this.renderer.pathToSegments(value); - } - this.pathArray = value; - value = value.reduce(function (acc, seg, i) { - if (!seg || !seg.join) { - return (seg || '').toString(); - } - return (i ? acc + ' ' : '') + seg.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; - } - }; + if ( + label.getBBox().width < + axis.getSlotWidth(tick) - 2 * pick(labelOptions.padding, 5) + ) { + return; + } + } + label.attr({ + text: "", + }); + }; + } + // Call only after first render + if (animateLabels && axis._addedPlotLB) { + tick.moveLabel(str, labelOptions); + } + // First call + if (!defined(label) && !tick.movedLabel) { + /** + * The rendered text label of the tick. + * @name Highcharts.Tick#label + * @type {Highcharts.SVGElement|undefined} + */ + tick.label = label = tick.createLabel( + { x: 0, y: 0 }, + str, + labelOptions + ); + // Base value to detect change for new calls to getBBox + tick.rotation = 0; + // update + } else if (label && label.textStr !== str && !animateLabels) { + // When resetting text, also reset the width if dynamically set + // (#8809) + if ( + label.textWidth && + !(labelOptions.style && labelOptions.style.width) && + !label.styles.width + ) { + label.css({ width: null }); + } + label.attr({ text: str }); + label.textPxLength = label.getBBox().width; + } + }; + /** + * Render and return the label of the tick. + * + * @private + * @function Highcharts.Tick#createLabel + * @param {Highcharts.PositionObject} xy + * @param {string} str + * @param {Highcharts.XAxisLabelsOptions} labelOptions + * @return {Highcharts.SVGElement|undefined} + */ + Tick.prototype.createLabel = function (xy, str, labelOptions) { + var axis = this.axis, + chart = axis.chart, + label = + defined(str) && labelOptions.enabled + ? chart.renderer + .text(str, xy.x, xy.y, labelOptions.useHTML) + .add(axis.labelGroup) + : null; + // Un-rotated length + if (label) { + // Without position absolute, IE export sometimes is wrong + if (!chart.styledMode) { + label.css(merge(labelOptions.style)); + } + label.textPxLength = label.getBBox().width; + } + return label; + }; + /** + * Destructor for the tick prototype + * + * @private + * @function Highcharts.Tick#destroy + * @return {void} + */ + Tick.prototype.destroy = function () { + destroyObjectProperties(this, this.axis); + }; + /** + * Gets the x and y positions for ticks in terms of pixels. + * + * @private + * @function Highcharts.Tick#getPosition + * + * @param {boolean} horiz + * Whether the tick is on an horizontal axis or not. + * + * @param {number} tickPos + * Position of the tick. + * + * @param {number} tickmarkOffset + * Tickmark offset for all ticks. + * + * @param {boolean} [old] + * Whether the axis has changed or not. + * + * @return {Highcharts.PositionObject} + * The tick position. + * + * @fires Highcharts.Tick#event:afterGetPosition + */ + Tick.prototype.getPosition = function ( + horiz, + tickPos, + tickmarkOffset, + old + ) { + var axis = this.axis, + chart = axis.chart, + cHeight = (old && chart.oldChartHeight) || chart.chartHeight, + pos; + pos = { + x: horiz + ? correctFloat( + axis.translate(tickPos + 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) + : correctFloat( + cHeight - + axis.translate(tickPos + tickmarkOffset, null, null, old) - + axis.transB + ), + }; + // Chrome workaround for #10516 + pos.y = clamp(pos.y, -1e5, 1e5); + fireEvent(this, "afterGetPosition", { pos: pos }); + return pos; + }; + /** + * Get the x, y position of the tick label + * + * @private + * @return {Highcharts.PositionObject} + */ + Tick.prototype.getLabelPosition = function ( + x, + y, + label, + horiz, + labelOptions, + tickmarkOffset, + index, + step + ) { + var axis = this.axis, + transA = axis.transA, + reversed = // #7911 + axis.isLinked && axis.linkedParent + ? axis.linkedParent.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, + pos = {}; + 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); + } + pos.x = x; + pos.y = Math.round(y); + fireEvent(this, "afterGetLabelPosition", { + pos: pos, + tickmarkOffset: tickmarkOffset, + index: index, + }); + return pos; + }; + /** + * Get the offset height or width of the label + * + * @private + * @function Highcharts.Tick#getLabelSize + * @return {number} + */ + Tick.prototype.getLabelSize = function () { + return this.label + ? this.label.getBBox()[this.axis.horiz ? "height" : "width"] + : 0; + }; + /** + * Extendible method to return the path of the marker + * + * @private + * + */ + Tick.prototype.getMarkPath = function ( + x, + y, + tickLength, + tickWidth, + horiz, + renderer + ) { + return renderer.crispLine( + [ + ["M", x, y], + [ + "L", + x + (horiz ? 0 : -tickLength), + y + (horiz ? tickLength : 0), + ], + ], + tickWidth + ); + }; + /** + * Handle the label overflow by adjusting the labels to the left and right + * edge, or hide them if they collide into the neighbour label. + * + * @private + * @function Highcharts.Tick#handleOverflow + * @param {Highcharts.PositionObject} xy + * @return {void} + */ + Tick.prototype.handleOverflow = function (xy) { + var tick = this, + 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(tick), + 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 && + pick(labelOptions.overflow, "justify") === "justify" + ) { + 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) { + if (tick.shortenLabel) { + tick.shortenLabel(); + } else { + css.width = Math.floor(textWidth) + "px"; + if (!(labelOptions.style || {}).textOverflow) { + css.textOverflow = "ellipsis"; + } + label.css(css); + } + } + }; + /** + * Try to replace the label if the same one already exists. + * + * @private + * @function Highcharts.Tick#moveLabel + * @param {string} str + * @param {Highcharts.XAxisLabelsOptions} labelOptions + * + * @return {void} + */ + Tick.prototype.moveLabel = function (str, labelOptions) { + var tick = this, + label = tick.label, + moved = false, + axis = tick.axis, + labelPos, + reversed = axis.reversed, + xPos, + yPos; + if (label && label.textStr === str) { + tick.movedLabel = label; + moved = true; + delete tick.label; + } else { + // Find a label with the same string + objectEach(axis.ticks, function (currentTick) { + if ( + !moved && + !currentTick.isNew && + currentTick !== tick && + currentTick.label && + currentTick.label.textStr === str + ) { + tick.movedLabel = currentTick.label; + moved = true; + currentTick.labelPos = tick.movedLabel.xy; + delete currentTick.label; + } + }); + } + // Create new label if the actual one is moved + if (!moved && (tick.labelPos || label)) { + labelPos = tick.labelPos || label.xy; + xPos = axis.horiz + ? reversed + ? 0 + : axis.width + axis.left + : labelPos.x; + yPos = axis.horiz + ? labelPos.y + : reversed + ? axis.width + axis.left + : 0; + tick.movedLabel = tick.createLabel( + { x: xPos, y: yPos }, + str, + labelOptions + ); + if (tick.movedLabel) { + tick.movedLabel.attr({ opacity: 0 }); + } + } + }; + /** + * Put everything in place + * + * @private + * @param {number} index + * @param {boolean} [old] + * Use old coordinates to prepare an animation into new position + * @param {number} [opacity] + * @return {voids} + */ + Tick.prototype.render = function (index, old, opacity) { + var tick = this, + axis = tick.axis, + horiz = axis.horiz, + pos = tick.pos, + tickmarkOffset = pick(tick.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; + fireEvent(this, "afterRender"); + }; + /** + * Renders the gridLine. + * + * @private + * @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 {void} + */ + Tick.prototype.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 = pick(tick.tickmarkOffset, axis.tickmarkOffset), + renderer = axis.chart.renderer, + gridPrefix = type ? type + "Grid" : "grid", + gridLineWidth = options[gridPrefix + "LineWidth"], + gridLineColor = options[gridPrefix + "LineColor"], + dashStyle = options[gridPrefix + "LineDashStyle"]; + if (!gridLine) { + if (!axis.chart.styledMode) { + attribs.stroke = gridLineColor; + attribs["stroke-width"] = gridLineWidth; + if (dashStyle) { + attribs.dashstyle = dashStyle; + } + } + if (!type) { + attribs.zIndex = 1; + } + if (old) { + opacity = 0; + } /** - * Fade out an element by animating its opacity down to 0, and hide it on - * complete. Used internally for the tooltip. - * - * @function Highcharts.SVGElement#fadeOut - * - * @param {number} [duration=150] - * The fade duration in milliseconds. - */ - SVGElement.prototype.fadeOut = function (duration) { - var elemWrapper = this; - elemWrapper.animate({ - opacity: 0 - }, { - duration: pick(duration, 150), - complete: function () { - // #3088, assuming we're only using this for tooltips - elemWrapper.attr({ y: -9999 }).hide(); - } + * The rendered grid line of the tick. + * @name Highcharts.Tick#gridLine + * @type {Highcharts.SVGElement|undefined} + */ + tick.gridLine = gridLine = renderer + .path() + .attr(attribs) + .addClass("highcharts-" + (type ? type + "-" : "") + "grid-line") + .add(axis.gridGroup); + } + if (gridLine) { + gridLinePath = axis.getPlotLinePath({ + value: pos + tickmarkOffset, + lineWidth: gridLine.strokeWidth() * reverseCrisp, + force: "pass", + old: old, + }); + // If the parameter 'old' is set, the current call will be followed + // by another call, therefore do not do any animations this time + if (gridLinePath) { + gridLine[old || tick.isNew ? "attr" : "animate"]({ + d: gridLinePath, + opacity: opacity, + }); + } + } + }; + /** + * Renders the tick mark. + * + * @private + * @param {Highcharts.PositionObject} xy The position vector of the mark + * @param {number} opacity The opacity of the mark + * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1 + * @return {void} + */ + Tick.prototype.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, + 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) { + /** + * The rendered mark of the tick. + * @name Highcharts.Tick#mark + * @type {Highcharts.SVGElement|undefined} + */ + tick.mark = mark = renderer + .path() + .addClass("highcharts-" + (type ? type + "-" : "") + "tick") + .add(axis.axisGroup); + if (!axis.chart.styledMode) { + 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. + * + * @private + * @param {Highcharts.PositionObject} xy The position vector 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 {void} + */ + Tick.prototype.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 = pick(tick.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; + } + } + }; + /** + * Replace labels with the moved ones to perform animation. Additionally + * destroy unused labels. + * + * @private + * @function Highcharts.Tick#replaceMovedLabel + * @return {void} + */ + Tick.prototype.replaceMovedLabel = function () { + var tick = this, + label = tick.label, + axis = tick.axis, + reversed = axis.reversed, + x, + y; + // Animate and destroy + if (label && !tick.isNew) { + x = axis.horiz + ? reversed + ? axis.left + : axis.width + axis.left + : label.xy.x; + y = axis.horiz + ? label.xy.y + : reversed + ? axis.width + axis.top + : axis.top; + label.animate({ x: x, y: y, opacity: 0 }, void 0, label.destroy); + delete tick.label; + } + axis.isDirty = true; + tick.label = tick.movedLabel; + delete tick.movedLabel; + }; + return Tick; + })(); + H.Tick = Tick; + + return H.Tick; + } + ); + _registerModule( + _modules, + "Core/Time.js", + [_modules["Core/Globals.js"], _modules["Core/Utilities.js"]], + function (Highcharts, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /** + * Normalized interval. + * + * @interface Highcharts.TimeNormalizedObject + */ /** + * The count. + * + * @name Highcharts.TimeNormalizedObject#count + * @type {number} + */ /** + * The interval in axis values (ms). + * + * @name Highcharts.TimeNormalizedObject#unitRange + * @type {number} + */ + /** + * Function of an additional date format specifier. + * + * @callback Highcharts.TimeFormatCallbackFunction + * + * @param {number} timestamp + * The time to format. + * + * @return {string} + * The formatted portion of the date. + */ + /** + * Additonal time tick information. + * + * @interface Highcharts.TimeTicksInfoObject + * @extends Highcharts.TimeNormalizedObject + */ /** + * @name Highcharts.TimeTicksInfoObject#higherRanks + * @type {Array<string>} + */ /** + * @name Highcharts.TimeTicksInfoObject#totalRange + * @type {number} + */ + /** + * Time ticks. + * + * @interface Highcharts.AxisTickPositionsArray + * @extends global.Array<number> + */ /** + * @name Highcharts.AxisTickPositionsArray#info + * @type {Highcharts.TimeTicksInfoObject|undefined} + */ + /** + * 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. + * + * @callback Highcharts.TimezoneOffsetCallbackFunction + * + * @param {number} timestamp + * Timestamp in terms of milliseconds since January 1 1970. + * + * @return {number} + * Timezone offset in minutes. + */ + /** + * Allows to manually load the `moment.js` library from Highcharts options + * instead of the `window`. + * In case of loading the library from a `script` tag, + * this option is not needed, it will be loaded from there by default. + * + * @type {function} + * @since 8.2.0 + * @apioption time.moment + */ + var defined = U.defined, + error = U.error, + extend = U.extend, + isObject = U.isObject, + merge = U.merge, + objectEach = U.objectEach, + pad = U.pad, + pick = U.pick, + splat = U.splat, + timeUnits = U.timeUnits; + var H = Highcharts, + win = H.win; + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * 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 {@link 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()) + * ); + * + * @since 6.0.5 + * + * @class + * @name Highcharts.Time + * + * @param {Highcharts.TimeOptions} options + * Time options as defined in [chart.options.time](/highcharts/time). + */ + var Time = /** @class */ (function () { + /* * + * + * Constructors + * + * */ + function Time(options) { + /* * + * + * Properties + * + * */ + this.options = {}; + this.useUTC = false; + this.variableTimezone = false; + this.Date = win.Date; + /** + * Get the time zone offset based on the current timezone information as + * set in the global options. + * + * @function Highcharts.Time#getTimezoneOffset + * + * @param {number} timestamp + * The JavaScript timestamp to inspect. + * + * @return {number} + * The timezone offset in minutes compared to UTC. + */ + this.getTimezoneOffset = this.timezoneOffsetFunction(); + this.update(options); + } + /* * + * + * Functions + * + * */ + /** + * Time units used in `Time.get` and `Time.set` + * + * @typedef {"Date"|"Day"|"FullYear"|"Hours"|"Milliseconds"|"Minutes"|"Month"|"Seconds"} Highcharts.TimeUnitValue + */ + /** + * Get the value of a date object in given units, and subject to the Time + * object's current timezone settings. This function corresponds directly to + * JavaScripts `Date.getXXX / Date.getUTCXXX`, so instead of calling + * `date.getHours()` or `date.getUTCHours()` we will call + * `time.get('Hours')`. + * + * @function Highcharts.Time#get + * + * @param {Highcharts.TimeUnitValue} unit + * @param {Date} date + * + * @return {number} + * The given time unit + */ + Time.prototype.get = function (unit, date) { + if (this.variableTimezone || this.timezoneOffset) { + var realMs = date.getTime(); + var ms = realMs - this.getTimezoneOffset(date); + date.setTime(ms); // Temporary adjust to timezone + var ret = date["getUTC" + unit](); + date.setTime(realMs); // Reset + return ret; + } + // UTC time with no timezone handling + if (this.useUTC) { + return date["getUTC" + unit](); + } + // Else, local time + return date["get" + unit](); + }; + /** + * Set the value of a date object in given units, and subject to the Time + * object's current timezone settings. This function corresponds directly to + * JavaScripts `Date.setXXX / Date.setUTCXXX`, so instead of calling + * `date.setHours(0)` or `date.setUTCHours(0)` we will call + * `time.set('Hours', 0)`. + * + * @function Highcharts.Time#set + * + * @param {Highcharts.TimeUnitValue} unit + * @param {Date} date + * @param {number} value + * + * @return {number} + * The epoch milliseconds of the updated date + */ + Time.prototype.set = function (unit, date, value) { + // UTC time with timezone handling + if (this.variableTimezone || this.timezoneOffset) { + // For lower order time units, just set it directly using UTC + // time + if ( + unit === "Milliseconds" || + unit === "Seconds" || + unit === "Minutes" + ) { + return date["setUTC" + unit](value); + } + // Higher order time units need to take the time zone into + // account + // Adjust by timezone + var offset = this.getTimezoneOffset(date); + var ms = date.getTime() - offset; + date.setTime(ms); + date["setUTC" + unit](value); + var newOffset = this.getTimezoneOffset(date); + ms = date.getTime() + newOffset; + return date.setTime(ms); + } + // UTC time with no timezone handling + if (this.useUTC) { + return date["setUTC" + unit](value); + } + // Else, local time + return date["set" + unit](value); + }; + /** + * Update the Time object with current options. It is called internally on + * initializing Highcharts, after running `Highcharts.setOptions` and on + * `Chart.update`. + * + * @private + * @function Highcharts.Time#update + * + * @param {Highcharts.TimeOptions} options + * + * @return {void} + */ + Time.prototype.update = function (options) { + var useUTC = pick(options && options.useUTC, true), + time = this; + this.options = options = merge(true, this.options || {}, options); + // Allow using a different Date class + this.Date = options.Date || win.Date || Date; + this.useUTC = useUTC; + this.timezoneOffset = useUTC && options.timezoneOffset; + 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 = !!( + !useUTC || + options.getTimezoneOffset || + options.timezone + ); + }; + /** + * 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. + * + * @function Highcharts.Time#makeTime + * + * @param {number} year + * The year + * + * @param {number} month + * The month. Zero-based, so January is 0. + * + * @param {number} [date=1] + * The day of the month + * + * @param {number} [hours=0] + * The hour of the day, 0-23. + * + * @param {number} [minutes=0] + * The minutes + * + * @param {number} [seconds=0] + * The seconds + * + * @return {number} + * The time in milliseconds since January 1st 1970. + */ + Time.prototype.makeTime = function ( + year, + month, + date, + hours, + minutes, + seconds + ) { + var d, offset, newOffset; + if (this.useUTC) { + d = this.Date.UTC.apply(0, arguments); + offset = this.getTimezoneOffset(d); + d += offset; + newOffset = this.getTimezoneOffset(d); + if (offset !== newOffset) { + d += newOffset - offset; + // A special case for transitioning from summer time to winter time. + // When the clock is set back, the same time is repeated twice, i.e. + // 02:30 am is repeated since the clock is set back from 3 am to + // 2 am. We need to make the same time as local Date does. + } else if ( + offset - 36e5 === this.getTimezoneOffset(d - 36e5) && + !H.isSafari + ) { + d -= 36e5; + } + } 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 + * @function Highcharts.Time#timezoneOffsetFunction + * + * @return {Function} + * A getTimezoneOffset function + */ + Time.prototype.timezoneOffsetFunction = function () { + var time = this, + options = this.options, + moment = options.moment || win.moment; + if (!this.useUTC) { + return function (timestamp) { + return new Date(timestamp.toString()).getTimezoneOffset() * 60000; + }; + } + if (options.timezone) { + if (!moment) { + // getTimezoneOffset-function stays undefined because it depends + // on Moment.js + 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.valueOf()) * 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 available format keys are listed + * below. Additional formats can be given in the + * {@link Highcharts.dateFormats} hook. + * + * Supported format keys: + * - `%a`: Short weekday, like 'Mon' + * - `%A`: Long weekday, like 'Monday' + * - `%d`: Two digit day of the month, 01 to 31 + * - `%e`: Day of the month, 1 through 31 + * - `%w`: Day of the week, 0 through 6 + * - `%b`: Short month, like 'Jan' + * - `%B`: Long month, like 'January' + * - `%m`: Two digit month number, 01 through 12 + * - `%y`: Two digits year, like 09 for 2009 + * - `%Y`: Four digits year, like 2009 + * - `%H`: Two digits hours in 24h format, 00 through 23 + * - `%k`: Hours in 24h format, 0 through 23 + * - `%I`: Two digits hours in 12h format, 00 through 11 + * - `%l`: Hours in 12h format, 1 through 12 + * - `%M`: Two digits minutes, 00 through 59 + * - `%p`: Upper case AM or PM + * - `%P`: Lower case AM or PM + * - `%S`: Two digits seconds, 00 through 59 + * - `%L`: Milliseconds (naming from Ruby) + * + * @example + * const time = new Highcharts.Time(); + * const s = time.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2020, 0, 1)); + * console.log(s); // => 2020-01-01 00:00:00 + * + * @function Highcharts.Time#dateFormat + * + * @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. + * + * @return {string} + * The formatted date. + */ + Time.prototype.dateFormat = function (format, timestamp, capitalize) { + var _a; + if (!defined(timestamp) || isNaN(timestamp)) { + return ( + ((_a = H.defaultOptions.lang) === null || _a === void 0 + ? void 0 + : _a.invalidDate) || "" + ); + } + format = pick(format, "%Y-%m-%d %H:%M:%S"); + var time = this, + date = new this.Date(timestamp), + // get the basic time values + hours = this.get("Hours", date), + day = this.get("Day", date), + dayOfMonth = this.get("Date", date), + month = this.get("Month", date), + fullYear = this.get("FullYear", date), + lang = H.defaultOptions.lang, + langWeekdays = + lang === null || lang === void 0 ? void 0 : lang.weekdays, + shortWeekdays = + lang === null || lang === void 0 ? void 0 : lang.shortWeekdays, + // List all format keys. Custom formats can be added from the + // outside. + replacements = 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, " "), + // Day of the week, 0 through 6 + 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), + // Month number, 1 through 12 (#8150) + o: 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(this.get("Minutes", date)), + // 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.floor(timestamp % 1000), 3), + }, + H.dateFormats + ); + // Do the replaces + 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; + }; + /** + * Resolve legacy formats of dateTimeLabelFormats (strings and arrays) into + * an object. + * @private + * @param {string|Array<T>|Highcharts.Dictionary<T>} f - General format description + * @return {Highcharts.Dictionary<T>} - The object definition + */ + Time.prototype.resolveDTLFormat = function (f) { + if (!isObject(f, true)) { + // check for string or array + f = splat(f); + return { + main: f[0], + from: f[1], + to: f[2], }; - /** - * @private - * @function Highcharts.SVGElement#fillSetter - * @param {Highcharts.ColorType} value - * @param {string} key - * @param {Highcharts.SVGDOMElement} element - */ - SVGElement.prototype.fillSetter = function (value, key, element) { - if (typeof value === 'string') { - element.setAttribute(key, value); - } - else if (value) { - this.complexColor(value, key, element); - } - }; - /** - * 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. - * - * @sample highcharts/members/renderer-on-chart/ - * Draw a rectangle based on a text's bounding box - * - * @function Highcharts.SVGElement#getBBox - * - * @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. - * - * @return {Highcharts.BBoxObject} - * The bounding box with `x`, `y`, `width` and `height` properties. - */ - SVGElement.prototype.getBBox = function (reload, rot) { - var wrapper = this, - bBox, // = wrapper.bBox, - renderer = wrapper.renderer, - width, - height, - element = wrapper.element, - styles = wrapper.styles, - fontSize, - textStr = wrapper.textStr, - toggleTextShadowShim, - cache = renderer.cache, - cacheKeys = renderer.cacheKeys, - isSVG = element.namespaceURI === wrapper.SVG_NS, - cacheKey; - var rotation = pick(rot, - wrapper.rotation, 0); - fontSize = renderer.styledMode ? (element && - SVGElement.prototype.getStyle.call(element, 'font-size')) : (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, - fontSize, - wrapper.textWidth, - styles && styles.textOverflow, - styles && styles.fontWeight // #12163 - ].join(','); - } - if (cacheKey && !reload) { - bBox = cache[cacheKey]; - } - // No cache found - if (!bBox) { - // SVG elements - if (isSVG || 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) { - [].forEach.call(element.querySelectorAll('.highcharts-text-outline'), function (tspan) { - tspan.style.display = display; - }); - }; - // Workaround for #3842, Firefox reporting wrong bounding - // box for shadows - if (isFunction(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 (isFunction(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. - // https://jsfiddle.net/highcharts/em37nvuj/ - // (#1101, #1505, #1669, #2568, #6213). - if (isSVG) { - bBox.height = height = ({ - '11px,17': 14, - '13px,20': 16 - }[styles && - styles.fontSize + ',' + Math.round(height)] || - height); - } - // Adjust for rotated text - if (rotation) { - var rad = rotation * deg2rad; - 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; - }; - /** - * Get the computed style. Only in styled mode. - * - * @example - * chart.series[0].points[0].graphic.getStyle('stroke-width'); // => '1px' - * - * @function Highcharts.SVGElement#getStyle - * - * @param {string} prop - * The property name to check for. - * - * @return {string} - * The current computed value. - */ - SVGElement.prototype.getStyle = function (prop) { - return win - .getComputedStyle(this.element || this, '') - .getPropertyValue(prop); - }; - /** - * Check if an element has the given class name. - * - * @function Highcharts.SVGElement#hasClass - * - * @param {string} className - * The class name to check for. - * - * @return {boolean} - * Whether the class name is found. - */ - SVGElement.prototype.hasClass = function (className) { - return ('' + this.attr('class')) - .split(' ') - .indexOf(className) !== -1; - }; - /** - * Hide the element, similar to setting the `visibility` attribute to - * `hidden`. - * - * @function Highcharts.SVGElement#hide - * - * @param {boolean} [hideByTranslation=false] - * The flag to determine if element should be hidden by moving out - * of the viewport. Used for example for dataLabels. - * - * @return {Highcharts.SVGElement} - * Returns the SVGElement for chaining. - */ - SVGElement.prototype.hide = function (hideByTranslation) { - if (hideByTranslation) { - this.attr({ y: -9999 }); - } - else { - this.attr({ visibility: 'hidden' }); - } - return this; - }; - /** - * @private - */ - SVGElement.prototype.htmlGetBBox = function () { - return { height: 0, width: 0, x: 0, y: 0 }; - }; - /** - * Initialize the SVG element. This function only exists to make the - * initialization process overridable. It should not be called directly. - * - * @function Highcharts.SVGElement#init - * - * @param {Highcharts.SVGRenderer} renderer - * The SVGRenderer instance to initialize to. - * - * @param {string} nodeName - * The SVG node name. - */ - SVGElement.prototype.init = function (renderer, nodeName) { - /** - * The primary DOM node. Each `SVGElement` instance wraps a main DOM - * node, but may also represent more nodes. - * - * @name Highcharts.SVGElement#element - * @type {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} - */ - this.element = nodeName === 'span' ? - createElement(nodeName) : - doc.createElementNS(this.SVG_NS, nodeName); - /** - * The renderer that the SVGElement belongs to. - * - * @name Highcharts.SVGElement#renderer - * @type {Highcharts.SVGRenderer} - */ - this.renderer = renderer; - fireEvent(this, 'afterInit'); - }; - /** - * 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. - * - * @function Highcharts.SVGElement#invert - * - * @param {boolean} inverted - * Whether to invert or not. An inverted shape can be un-inverted by - * setting it to false. - * - * @return {Highcharts.SVGElement} - * Return the SVGElement for chaining. - */ - SVGElement.prototype.invert = function (inverted) { - var wrapper = this; - wrapper.inverted = inverted; - wrapper.updateTransform(); - return wrapper; - }; - /** - * 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. - * - * @sample highcharts/members/element-on/ - * A clickable rectangle - * - * @function Highcharts.SVGElement#on - * - * @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. - * - * @return {Highcharts.SVGElement} - * The SVGElement for chaining. - */ - SVGElement.prototype.on = function (eventType, handler) { - var svgElement = this, - element = svgElement.element, - touchStartPos, - touchEventFired; - // touch - if (hasTouch && eventType === 'click') { - element.ontouchstart = function (e) { - // save touch position for later calculation - touchStartPos = { - clientX: e.touches[0].clientX, - clientY: e.touches[0].clientY - }; - }; - // Instead of ontouchstart, event handlers should be called - // on touchend - similar to how current mouseup events are called - element.ontouchend = function (e) { - // hasMoved is a boolean variable containing logic if page - // was scrolled, so if touch position changed more than - // ~4px (value borrowed from general touch handler) - var hasMoved = touchStartPos.clientX ? Math.sqrt(Math.pow(touchStartPos.clientX - e.changedTouches[0].clientX, 2) + - Math.pow(touchStartPos.clientY - e.changedTouches[0].clientY, 2)) >= 4 : false; - if (!hasMoved) { // only call handlers if page was not scrolled - handler.call(element, e); - } - touchEventFired = true; - if (e.cancelable !== false) { - // prevent other events from being fired. #9682 - e.preventDefault(); - } - }; - element.onclick = function (e) { - // Do not call onclick handler if touch event was fired already. - if (!touchEventFired) { - handler.call(element, e); - } - }; - } - else { - // simplest possible event model for internal use - element['on' + eventType] = handler; - } - return this; - }; - /** - * @private - * @function Highcharts.SVGElement#opacitySetter - * @param {string} value - * @param {string} key - * @param {Highcharts.SVGDOMElement} element - */ - SVGElement.prototype.opacitySetter = function (value, key, element) { - // Round off to avoid float errors, like tests where opacity lands on - // 9.86957e-06 instead of 0 - var opacity = Number(Number(value).toFixed(3)); - this.opacity = opacity; - element.setAttribute(key, opacity); - }; - /** - * Remove a class name from the element. - * - * @function Highcharts.SVGElement#removeClass - * - * @param {string|RegExp} className - * The class name to remove. - * - * @return {Highcharts.SVGElement} Returns the SVG element for chainability. - */ - SVGElement.prototype.removeClass = function (className) { - return this.attr('class', ('' + this.attr('class')) - .replace(isString(className) ? - new RegExp("(^| )" + className + "( |$)") : // #12064, #13590 - className, ' ') - .replace(/ +/g, ' ') - .trim()); - }; - /** - * @private - * @param {Array<Highcharts.SVGDOMElement>} tspans - * Text spans. - */ - SVGElement.prototype.removeTextOutline = function (tspans) { - // Iterate from the end to - // support removing items inside the cycle (#6472). - var i = tspans.length, - tspan; - while (i--) { - tspan = tspans[i]; - if (tspan.getAttribute('class') === 'highcharts-text-outline') { - // Remove then erase - erase(tspans, this.element.removeChild(tspan)); - } + } + return f; + }; + /** + * Return an array with time positions distributed on round time values + * right and right after min and max. Used in datetime axes as well as for + * grouping data on a datetime axis. + * + * @function Highcharts.Time#getTimeTicks + * + * @param {Highcharts.TimeNormalizedObject} 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=1] + * + * @return {Highcharts.AxisTickPositionsArray} + */ + Time.prototype.getTimeTicks = function ( + normalizedInterval, + min, + max, + startOfWeek + ) { + var time = this, + 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), + interval = normalizedInterval.unitRange, + count = normalizedInterval.count || 1, + variableDayLength, + minDay; + startOfWeek = pick(startOfWeek, 1); + if (defined(min)) { + // #1300 + time.set( + "Milliseconds", + minDate, + interval >= timeUnits.second + ? 0 // #3935 + : count * Math.floor(time.get("Milliseconds", minDate) / count) + ); // #3652, #3654 + if (interval >= timeUnits.second) { + // second + time.set( + "Seconds", + minDate, + interval >= timeUnits.minute + ? 0 // #3935 + : count * Math.floor(time.get("Seconds", minDate) / count) + ); + } + if (interval >= timeUnits.minute) { + // minute + time.set( + "Minutes", + minDate, + interval >= timeUnits.hour + ? 0 + : count * Math.floor(time.get("Minutes", minDate) / count) + ); + } + if (interval >= timeUnits.hour) { + // hour + time.set( + "Hours", + minDate, + interval >= timeUnits.day + ? 0 + : count * Math.floor(time.get("Hours", minDate) / count) + ); + } + if (interval >= timeUnits.day) { + // day + time.set( + "Date", + minDate, + interval >= timeUnits.month + ? 1 + : Math.max( + 1, + count * Math.floor(time.get("Date", minDate) / count) + ) + ); + } + if (interval >= timeUnits.month) { + // month + time.set( + "Month", + minDate, + interval >= timeUnits.year + ? 0 + : count * Math.floor(time.get("Month", minDate) / count) + ); + minYear = time.get("FullYear", minDate); + } + if (interval >= timeUnits.year) { + // year + minYear -= minYear % count; + time.set("FullYear", minDate, minYear); + } + // week is a special case that runs outside the hierarchy + if (interval === timeUnits.week) { + // get start of current week, independent of count + minDay = time.get("Day", minDate); + time.set( + "Date", + minDate, + time.get("Date", minDate) - + minDay + + startOfWeek + + // We don't want to skip days that are before + // startOfWeek (#7051) + (minDay < startOfWeek ? -7 : 0) + ); + } + // Get basics for variable time spans + minYear = time.get("FullYear", minDate); + var minMonth = time.get("Month", minDate), + minDateDate = time.get("Date", minDate), + minHours = time.get("Hours", minDate); + // 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); + } + // 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 && + count > 1 + ) { + // make sure higher ranks are preserved across DST (#6797, + // #7621) + t = time.makeTime( + minYear, + minMonth, + minDateDate, + minHours + i * count + ); + // 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) { + tickPositions.forEach(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"; } - }; - /** - * Removes an element from the DOM. - * - * @private - * @function Highcharts.SVGElement#safeRemoveChild - * - * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element - * The DOM node to remove. - */ - SVGElement.prototype.safeRemoveChild = function (element) { - var parentNode = element.parentNode; - if (parentNode) { - parentNode.removeChild(element); - } - }; - /** - * 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. - * - * @function Highcharts.SVGElement#setRadialReference - * - * @param {Array<number>} coordinates - * The center reference. The format is `[centerX, centerY, diameter]` in - * pixels. - * - * @return {Highcharts.SVGElement} - * Returns the SVGElement for chaining. - */ - SVGElement.prototype.setRadialReference = function (coordinates) { - var existingGradient = (this.element.gradient && - 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; - }; - /** - * @private - * @function Highcharts.SVGElement#setTextPath - * @param {Highcharts.SVGElement} path - * Path to follow. - * @param {Highcharts.DataLabelsTextPathOptionsObject} textPathOptions - * Options. - * @return {Highcharts.SVGElement} - * Returns the SVGElement for chaining. - */ - SVGElement.prototype.setTextPath = function (path, textPathOptions) { - var elem = this.element, - attribsMap = { - textAnchor: 'text-anchor' - }, - attrs, - adder = false, - textPathElement, - textPathId, - textPathWrapper = this.textPathWrapper, - tspans, - firstTime = !textPathWrapper; - // Defaults - textPathOptions = merge(true, { - enabled: true, - attributes: { - dy: -5, - startOffset: '50%', - textAnchor: 'middle' - } - }, textPathOptions); - attrs = textPathOptions.attributes; - if (path && textPathOptions && textPathOptions.enabled) { - // In case of fixed width for a text, string is rebuilt - // (e.g. ellipsis is applied), so we need to rebuild textPath too - if (textPathWrapper && - textPathWrapper.element.parentNode === null) { - // When buildText functionality was triggered again - // and deletes textPathWrapper parentNode - firstTime = true; - textPathWrapper = textPathWrapper.destroy(); - } - else if (textPathWrapper) { - // Case after drillup when spans were added into - // the DOM outside the textPathWrapper parentGroup - this.removeTextOutline.call(textPathWrapper.parentGroup, [].slice.call(elem.getElementsByTagName('tspan'))); - } - // label() has padding, text() doesn't - if (this.options && this.options.padding) { - attrs.dx = -this.options.padding; - } - if (!textPathWrapper) { - // Create <textPath>, defer the DOM adder - this.textPathWrapper = textPathWrapper = - this.renderer.createElement('textPath'); - adder = true; - } - textPathElement = textPathWrapper.element; - // Set ID for the path - textPathId = path.element.getAttribute('id'); - if (!textPathId) { - path.element.setAttribute('id', textPathId = uniqueKey()); - } - // Change DOM structure, by placing <textPath> tag in <text> - if (firstTime) { - tspans = elem.getElementsByTagName('tspan'); - // Now move all <tspan>'s to the <textPath> node - while (tspans.length) { - // Remove "y" from tspans, as Firefox translates them - tspans[0].setAttribute('y', 0); - // Remove "x" from tspans - if (isNumber(attrs.dx)) { - tspans[0].setAttribute('x', -attrs.dx); - } - textPathElement.appendChild(tspans[0]); - } - } - // Add <textPath> to the DOM - if (adder && - textPathWrapper) { - textPathWrapper.add({ - // label() is placed in a group, text() is standalone - element: this.text ? this.text.element : elem - }); - } - // Set basic options: - // Use `setAttributeNS` because Safari needs this.. - textPathElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', this.renderer.url + '#' + textPathId); - // Presentation attributes: - // dx/dy options must by set on <text> (parent), - // the rest should be set on <textPath> - if (defined(attrs.dy)) { - textPathElement.parentNode - .setAttribute('dy', attrs.dy); - delete attrs.dy; - } - if (defined(attrs.dx)) { - textPathElement.parentNode - .setAttribute('dx', attrs.dx); - delete attrs.dx; - } - // Additional attributes - objectEach(attrs, function (val, key) { - textPathElement.setAttribute(attribsMap[key] || key, val); - }); - // Remove translation, text that follows path does not need that - elem.removeAttribute('transform'); - // Remove shadows and text outlines - this.removeTextOutline.call(textPathWrapper, [].slice.call(elem.getElementsByTagName('tspan'))); - // Remove background and border for label(), see #10545 - // Alternatively, we can disable setting background rects in - // series.drawDataLabels() - if (this.text && !this.renderer.styledMode) { - this.attr({ - fill: 'none', - 'stroke-width': 0 - }); - } - // Disable some functions - this.updateTransform = noop; - this.applyTextOutline = noop; - } - else if (textPathWrapper) { - // Reset to prototype - delete this.updateTransform; - delete this.applyTextOutline; - // Restore DOM structure: - this.destroyTextPath(elem, path); - // Bring attributes back - this.updateTransform(); - // Set textOutline back for text() - if (this.options && this.options.rotation) { - this.applyTextOutline(this.options.style.textOutline); - } - } - return this; - }; - /** - * 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. - * - * @example - * renderer.rect(10, 100, 100, 100) - * .attr({ fill: 'red' }) - * .shadow(true); - * - * @function Highcharts.SVGElement#shadow - * - * @param {boolean|Highcharts.ShadowOptionsObject} [shadowOptions] - * The shadow options. If `true`, the default options are applied. If - * `false`, the current shadow will be removed. - * - * @param {Highcharts.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. - * - * @return {Highcharts.SVGElement} - * Returns the SVGElement for chaining. - */ - SVGElement.prototype.shadow = function (shadowOptions, group, cutOff) { - var shadows = [], - i, - shadow, - element = this.element, - strokeWidth, - shadowElementOpacity, - update = false, - oldShadowOptions = this.oldShadowOptions, - // compensate for inverted plot area - transform; - var defaultShadowOptions = { - color: '#000000', - offsetX: 1, - offsetY: 1, - opacity: 0.15, - width: 3 - }; - var options; - if (shadowOptions === true) { - options = defaultShadowOptions; - } - else if (typeof shadowOptions === 'object') { - options = extend(defaultShadowOptions, shadowOptions); - } - // Update shadow when options change (#12091). - if (options) { - // Go over each key to look for change - if (options && oldShadowOptions) { - objectEach(options, function (value, key) { - if (value !== oldShadowOptions[key]) { - update = true; - } - }); - } - if (update) { - this.destroyShadows(); - } - this.oldShadowOptions = options; - } - if (!options) { - this.destroyShadows(); - } - else if (!this.shadows) { - shadowElementOpacity = options.opacity / options.width; - transform = this.parentInverted ? - 'translate(-1,-1)' : - "translate(" + options.offsetX + ", " + options.offsetY + ")"; - for (i = 1; i <= options.width; i++) { - shadow = element.cloneNode(false); - strokeWidth = (options.width * 2) + 1 - (2 * i); - attr(shadow, { - stroke: (shadowOptions.color || - '#000000'), - 'stroke-opacity': shadowElementOpacity * i, - 'stroke-width': strokeWidth, - transform: transform, - fill: 'none' - }); - shadow.setAttribute('class', (shadow.getAttribute('class') || '') + ' highcharts-shadow'); - 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; - }; - /** - * Show the element after it has been hidden. - * - * @function Highcharts.SVGElement#show - * - * @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. - * - * @return {Highcharts.SVGElement} - * Returns the SVGElement for chaining. - */ - SVGElement.prototype.show = function (inherit) { - return this.attr({ visibility: inherit ? 'inherit' : 'visible' }); - }; - /** - * 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. - * - * @private - * @function Highcharts.SVGElement#strokeSetter - * @param {number|string} value - * @param {string} key - * @param {Highcharts.SVGDOMElement} element - */ - 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; - } - else if (this.renderer.styledMode && this['stroke-width']) { - element.setAttribute('stroke-width', this['stroke-width']); - this.hasStroke = true; - } - }; - /** - * Get the computed stroke width in pixel values. This is used extensively - * when drawing shapes to ensure the shapes are rendered crisp and - * positioned correctly relative to each other. Using - * `shape-rendering: crispEdges` leaves us less control over positioning, - * for example when we want to stack columns next to each other, or position - * things pixel-perfectly within the plot box. - * - * The common pattern when placing a shape is: - * - Create the SVGElement and add it to the DOM. In styled mode, it will - * now receive a stroke width from the style sheet. In classic mode we - * will add the `stroke-width` attribute. - * - Read the computed `elem.strokeWidth()`. - * - Place it based on the stroke width. - * - * @function Highcharts.SVGElement#strokeWidth - * - * @return {number} - * The stroke width in pixels. Even if the given stroke widtch (in CSS or by - * attributes) is based on `em` or other units, the pixel size is returned. - */ - SVGElement.prototype.strokeWidth = function () { - // In non-styled mode, read the stroke width as set by .attr - if (!this.renderer.styledMode) { - return this['stroke-width'] || 0; - } - // In styled mode, read computed stroke width - var val = this.getStyle('stroke-width'), - ret = 0, - dummy; - // Read pixel values directly - if (val.indexOf('px') === val.length - 2) { - ret = pInt(val); - // Other values like em, pt etc need to be measured - } - else if (val !== '') { - dummy = doc.createElementNS(SVG_NS, 'rect'); - attr(dummy, { - width: val, - 'stroke-width': 0 - }); - this.element.parentNode.appendChild(dummy); - ret = dummy.getBBox().width; - dummy.parentNode.removeChild(dummy); - } - return ret; - }; + }); + } + } + // record information on the chosen unit - for dynamic label formatter + tickPositions.info = extend(normalizedInterval, { + higherRanks: higherRanks, + totalRange: interval * count, + }); + return tickPositions; + }; + return Time; + })(); + H.Time = Time; + + return H.Time; + } + ); + _registerModule( + _modules, + "Core/Options.js", + [ + _modules["Core/Globals.js"], + _modules["Core/Color/Color.js"], + _modules["Core/Time.js"], + _modules["Core/Utilities.js"], + ], + function (H, Color, Time, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var isTouchDevice = H.isTouchDevice, + svg = H.svg; + var color = Color.parse; + var merge = U.merge; + /** + * @typedef {"plotBox"|"spacingBox"} Highcharts.ButtonRelativeToValue + */ + /** + * Gets fired when a series is added to the chart after load time, using the + * `addSeries` method. Returning `false` prevents the series from being added. + * + * @callback Highcharts.ChartAddSeriesCallbackFunction + * + * @param {Highcharts.Chart} this + * The chart on which the event occured. + * + * @param {Highcharts.ChartAddSeriesEventObject} event + * The event that occured. + */ + /** + * Contains common event information. Through the `options` property you can + * access the series options that were passed to the `addSeries` method. + * + * @interface Highcharts.ChartAddSeriesEventObject + */ /** + * The series options that were passed to the `addSeries` method. + * @name Highcharts.ChartAddSeriesEventObject#options + * @type {Highcharts.SeriesOptionsType} + */ /** + * Prevents the default behaviour of the event. + * @name Highcharts.ChartAddSeriesEventObject#preventDefault + * @type {Function} + */ /** + * The event target. + * @name Highcharts.ChartAddSeriesEventObject#target + * @type {Highcharts.Chart} + */ /** + * The event type. + * @name Highcharts.ChartAddSeriesEventObject#type + * @type {"addSeries"} + */ + /** + * Gets fired when clicking on the plot background. + * + * @callback Highcharts.ChartClickCallbackFunction + * + * @param {Highcharts.Chart} this + * The chart on which the event occured. + * + * @param {Highcharts.PointerEventObject} event + * The event that occured. + */ + /** + * Contains an axes of the clicked spot. + * + * @interface Highcharts.ChartClickEventAxisObject + */ /** + * Axis at the clicked spot. + * @name Highcharts.ChartClickEventAxisObject#axis + * @type {Highcharts.Axis} + */ /** + * Axis value at the clicked spot. + * @name Highcharts.ChartClickEventAxisObject#value + * @type {number} + */ + /** + * Contains information about the clicked spot on the chart. Remember the unit + * of a datetime axis is milliseconds since 1970-01-01 00:00:00. + * + * @interface Highcharts.ChartClickEventObject + * @extends Highcharts.PointerEventObject + */ /** + * Information about the x-axis on the clicked spot. + * @name Highcharts.ChartClickEventObject#xAxis + * @type {Array<Highcharts.ChartClickEventAxisObject>} + */ /** + * Information about the y-axis on the clicked spot. + * @name Highcharts.ChartClickEventObject#yAxis + * @type {Array<Highcharts.ChartClickEventAxisObject>} + */ /** + * Information about the z-axis on the clicked spot. + * @name Highcharts.ChartClickEventObject#zAxis + * @type {Array<Highcharts.ChartClickEventAxisObject>|undefined} + */ + /** + * Gets fired when the chart is finished loading. + * + * @callback Highcharts.ChartLoadCallbackFunction + * + * @param {Highcharts.Chart} this + * The chart on which the event occured. + * + * @param {global.Event} event + * The event that occured. + */ + /** + * 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`. + * + * @callback Highcharts.ChartRedrawCallbackFunction + * + * @param {Highcharts.Chart} this + * The chart on which the event occured. + * + * @param {global.Event} event + * The event that occured. + */ + /** + * Gets fired after initial load of the chart (directly after the `load` event), + * and after each redraw (directly after the `redraw` event). + * + * @callback Highcharts.ChartRenderCallbackFunction + * + * @param {Highcharts.Chart} this + * The chart on which the event occured. + * + * @param {global.Event} event + * The event that occured. + */ + /** + * Gets fired when an area of the chart has been selected. The default action + * for the selection event is to zoom the chart to the selected area. It can be + * prevented by calling `event.preventDefault()` or return false. + * + * @callback Highcharts.ChartSelectionCallbackFunction + * + * @param {Highcharts.Chart} this + * The chart on which the event occured. + * + * @param {global.ChartSelectionContextObject} event + * Event informations + * + * @return {boolean|undefined} + * Return false to prevent the default action, usually zoom. + */ + /** + * The primary axes are `xAxis[0]` and `yAxis[0]`. Remember the unit of a + * datetime axis is milliseconds since 1970-01-01 00:00:00. + * + * @interface Highcharts.ChartSelectionContextObject + * @extends global.Event + */ /** + * Arrays containing the axes of each dimension and each axis' min and max + * values. + * @name Highcharts.ChartSelectionContextObject#xAxis + * @type {Array<Highcharts.ChartSelectionAxisContextObject>} + */ /** + * Arrays containing the axes of each dimension and each axis' min and max + * values. + * @name Highcharts.ChartSelectionContextObject#yAxis + * @type {Array<Highcharts.ChartSelectionAxisContextObject>} + */ + /** + * Axis context of the selection. + * + * @interface Highcharts.ChartSelectionAxisContextObject + */ /** + * The selected Axis. + * @name Highcharts.ChartSelectionAxisContextObject#axis + * @type {Highcharts.Axis} + */ /** + * The maximum axis value, either automatic or set manually. + * @name Highcharts.ChartSelectionAxisContextObject#max + * @type {number} + */ /** + * The minimum axis value, either automatic or set manually. + * @name Highcharts.ChartSelectionAxisContextObject#min + * @type {number} + */ + (""); // detach doclets above + /* ************************************************************************** * + * Handle the options * + * ************************************************************************** */ + /** + * Global default settings. + * + * @name Highcharts.defaultOptions + * @type {Highcharts.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: + * ```js + * colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', + * '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a'] + * ``` + * + * In Highcharts 2.x, the default colors were: + * ```js + * colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', + * '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'] + * ``` + * + * @sample {highcharts} highcharts/chart/colors/ + * Assign a global color theme + * + * @type {Array<Highcharts.ColorString>} + * @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](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns) + * for more information and code examples. + * + * @type {*} + * @since 5.0.0 + * @apioption defs + */ + /** + * @ignore-option + */ + symbols: ["circle", "diamond", "square", "triangle", "triangle-down"], + /** + * The language object is global and it can't be set on each chart + * initialization. Instead, use `Highcharts.setOptions` to set it before any + * chart is initialized. + * + * ```js + * 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' + * ] + * } + * }); + * ``` + */ + lang: { + /** + * The loading text that appears when the chart is set into the loading + * state following a call to `chart.showLoading`. + */ + loading: "Loading...", + /** + * An array containing the months names. Corresponds to the `%B` format + * in `Highcharts.dateFormat()`. + * + * @type {Array<string>} + * @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<string>} + * @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<string>} + * @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. + * + * @sample highcharts/lang/shortweekdays/ + * Finnish two-letter abbreviations + * + * @type {Array<string>} + * @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 title appearing on hovering the zoom in button. The text itself + * defaults to "+" and can be changed in the button options. + * + * @type {string} + * @default Zoom in + * @product highmaps + * @apioption lang.zoomIn + */ + /** + * The title appearing on hovering the zoom out button. The text itself + * defaults to "-" and can be changed in the button options. + * + * @type {string} + * @default Zoom out + * @product highmaps + * @apioption lang.zoomOut + */ + /** + * The default decimal point used in the `Highcharts.numberFormat` + * method unless otherwise specified in the function arguments. + * + * @since 1.2.2 + */ + decimalPoint: ".", + /** + * [Metric prefixes](https://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. + * + * @sample {highcharts} highcharts/lang/numericsymbols/ + * Replacing the symbols with text + * @sample {highstock} highcharts/lang/numericsymbols/ + * Replacing the symbols with text + * + * @type {Array<string>} + * @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. + * + * @sample highcharts/lang/numericsymbolmagnitude/ + * 10000 magnitude for Japanese + * + * @type {number} + * @default 1000 + * @since 5.0.3 + * @apioption lang.numericSymbolMagnitude + */ + /** + * The text for the label appearing when a chart is zoomed. + * + * @since 1.2.4 + */ + resetZoom: "Reset zoom", + /** + * The tooltip title for the label appearing when a chart is zoomed. + * + * @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. Defaults + * to a single space character, which is recommended in + * [ISO 31-0](https://en.wikipedia.org/wiki/ISO_31-0#Numbers) and works + * across Anglo-American and continental European languages. + * + * @default \u0020 + * @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. + * + * ```js + * Highcharts.setOptions({ + * global: { + * useUTC: false + * } + * }); + * ``` + */ + /** + * _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](https://github.com/canvg/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. + * + * @deprecated + * + * @type {string} + * @default https://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 + * + * @type {boolean} + * @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 + * + * @type {Function} + * @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 + * + * @type {Function} + * @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 + * + * @type {string} + * @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 + * + * @type {number} + * @product highcharts highstock + * @apioption global.timezoneOffset + */ + global: {}, + /** + * 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 + * @optionparent time + */ + time: { + /** + * A custom `Date` class for advanced date handling. For example, + * [JDate](https://github.com/tahajahangir/jdate) can be hooked in to + * handle Jalali dates. + * + * @type {*} + * @since 4.0.4 + * @product highcharts highstock gantt + */ + Date: void 0, + /** + * 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. + * + * @see [global.timezoneOffset](#global.timezoneOffset) + * + * @sample {highcharts|highstock} highcharts/time/gettimezoneoffset/ + * Use moment.js to draw Oslo time regardless of browser locale + * + * @type {Highcharts.TimezoneOffsetCallbackFunction} + * @since 4.1.0 + * @product highcharts highstock gantt + */ + getTimezoneOffset: void 0, + /** + * Requires [moment.js](https://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. + * + * @see [getTimezoneOffset](#time.getTimezoneOffset) + * + * @sample {highcharts|highstock} highcharts/time/timezone/ + * Europe/Oslo + * + * @type {string} + * @since 5.0.7 + * @product highcharts highstock gantt + */ + timezone: void 0, + /** + * 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. + * + * @see [time.getTimezoneOffset](#time.getTimezoneOffset) + * + * @sample {highcharts|highstock} highcharts/time/timezoneoffset/ + * Timezone offset + * + * @since 3.0.8 + * @product highcharts highstock gantt + */ + timezoneOffset: 0, + /** + * 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. + * + * @sample {highcharts} highcharts/time/useutc-true/ + * True by default + * @sample {highcharts} highcharts/time/useutc-false/ + * False + */ + useUTC: true, + }, + /** + * General options for the chart. + */ + chart: { + /** + * Default `mapData` for all series. If set to a string, it functions + * as an index into the `Highcharts.maps` array. Otherwise it is + * interpreted as map data. + * + * @see [mapData](#series.map.mapData) + * + * @sample maps/demo/geojson + * Loading geoJSON data + * @sample maps/chart/topojson + * Loading topoJSON converted to geoJSON + * + * @type {string|Array<*>|Highcharts.GeoJSON} + * @since 5.0.0 + * @product highmaps + * @apioption chart.map + */ + /** + * Set lat/lon transformation definitions for the chart. If not defined, + * these are extracted from the map data. + * + * @type {*} + * @since 5.0.0 + * @product highmaps + * @apioption chart.mapTransforms + */ + /** + * 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. + * + * If `startOnTick` or `endOnTick` in an Axis options are set to false, + * then the `alignTicks ` will be disabled for the Axis. + * + * Disabled for logarithmic axes. + * + * @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 + * + * @type {boolean} + * @default true + * @product highcharts highstock gantt + * @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: + * + * - `defer`: The animation delay time in milliseconds. + * + * - `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](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-animation-easing/). + * + * When zooming on a series with less than 100 points, the chart redraw + * will be done with animation, but in case of more data points, it is + * necessary to set this option to ensure animation on zoom. + * + * @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 + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + * @default undefined + * @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 were passed to + * the `addSeries` method. Returning false prevents the series from + * being added. + * + * @sample {highcharts} highcharts/chart/events-addseries/ + * Alert on add series + * @sample {highstock} stock/chart/events-addseries/ + * Alert on add series + * + * @type {Highcharts.ChartAddSeriesCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Chart + * @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. + * + * ```js + * click: function(e) { + * console.log( + * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value), + * e.yAxis[0].value + * ) + * } + * ``` + * + * @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 + * + * @type {Highcharts.ChartClickCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Chart + * @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. + * + * @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 + * + * @type {Highcharts.ChartLoadCallbackFunction} + * @context Highcharts.Chart + * @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. + * + * @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 + * + * @type {Highcharts.ChartRedrawCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Chart + * @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 {Highcharts.ChartRenderCallbackFunction} + * @since 5.0.7 + * @context Highcharts.Chart + * @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()` or return false. + * + * 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. + * + * ```js + * 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); + * } + * ``` + * + * @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) + * + * @type {Highcharts.ChartSelectionCallbackFunction} + * @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. + * + * @sample {highcharts} highcharts/chart/margins-zero/ + * Zero margins + * @sample {highstock} stock/chart/margin-zero/ + * Zero margins + * + * @type {number|Array<number>} + * @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`. + * + * @sample {highcharts} highcharts/chart/marginbottom/ + * 100px bottom margin + * @sample {highstock} stock/chart/marginbottom/ + * 100px bottom margin + * @sample {highmaps} maps/chart/margin/ + * 100px margins + * + * @type {number} + * @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`. + * + * @sample {highcharts} highcharts/chart/marginleft/ + * 150px left margin + * @sample {highstock} stock/chart/marginleft/ + * 150px left margin + * @sample {highmaps} maps/chart/margin/ + * 100px margins + * + * @type {number} + * @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`. + * + * @sample {highcharts} highcharts/chart/marginright/ + * 100px right margin + * @sample {highstock} stock/chart/marginright/ + * 100px right margin + * @sample {highmaps} maps/chart/margin/ + * 100px margins + * + * @type {number} + * @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`. + * + * @sample {highcharts} highcharts/chart/margintop/ 100px top margin + * @sample {highstock} stock/chart/margintop/ + * 100px top margin + * @sample {highmaps} maps/chart/margin/ + * 100px margins + * + * @type {number} + * @since 2.0 + * @apioption chart.marginTop + */ + /** + * Callback function to override the default function that formats all + * the numbers in the chart. Returns a string with the formatted number. + * + * @sample highcharts/members/highcharts-numberformat + * Arabic digits in Highcharts + * @type {Highcharts.NumberFormatterCallbackFunction} + * @since 8.0.0 + * @apioption chart.numberFormatter + */ + /** + * 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`). + * + * @type {string} + * @since 4.0.3 + * @product highcharts gantt + * @validvalue ["alt", "ctrl", "meta", "shift"] + * @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`. + * + * @sample {highcharts} highcharts/chart/pankey/ Zooming and panning + * @sample {highstock} stock/chart/panning/ Zooming and xy panning + * + * @product highcharts highstock gantt + * @apioption chart.panning + */ + /** + * Enable or disable chart panning. + * + * @type {boolean} + * @default {highcharts} false + * @default {highstock} true + * @apioption chart.panning.enabled + */ + /** + * Decides in what dimensions the user can pan the chart. Can be + * one of `x`, `y`, or `xy`. + * + * @sample {highcharts} highcharts/chart/panning-type + * Zooming and xy panning + * + * @type {string} + * @validvalue ["x", "y", "xy"] + * @default x + * @apioption chart.panning.type + */ + /** + * 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. + * + * @type {string} + * @default {highcharts} undefined + * @default {highstock} x + * @since 3.0 + * @product highcharts highstock gantt + * @validvalue ["x", "y", "xy"] + * @apioption chart.pinchType + */ + /** + * Whether to apply styled mode. When in styled mode, no presentational + * attributes or CSS are applied to the chart SVG. Instead, CSS rules + * are required to style the chart. The default style sheet is + * available from `https://code.highcharts.com/css/highcharts.css`. + * + * @type {boolean} + * @default false + * @since 7.0 + * @apioption chart.styledMode + */ + styledMode: false, + /** + * The corner radius of the outer chart border. + * + * @sample {highcharts} highcharts/chart/borderradius/ + * 20px radius + * @sample {highstock} stock/chart/border/ + * 10px radius + * @sample {highmaps} maps/chart/border/ + * Border options + * + */ + borderRadius: 0, + /** + * In styled mode, this sets how many colors the class names + * should rotate between. With ten colors, series (or points) are + * given class names like `highcharts-color-0`, `highcharts-color-0` + * [...] `highcharts-color-9`. The equivalent in non-styled mode + * is to set colors using the [colors](#colors) setting. + * + * @since 5.0.0 + */ + colorCount: 10, + /** + * Alias of `type`. + * + * @sample {highcharts} highcharts/chart/defaultseriestype/ + * Bar + * + * @deprecated + * + * @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. + * + * @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 + * + * @since 1.2.0 + * @product highcharts highstock gantt + */ + 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). + * + * @sample {highcharts} highcharts/chart/inverted/ + * Inverted line + * @sample {highstock} stock/navigator/inverted/ + * Inverted stock chart + * + * @type {boolean} + * @default false + * @product highcharts highstock gantt + * @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<number>} + * @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: { + /** + * What frame the button placement should be related to. Can be + * either `plotBox` or `spacingBox`. + * + * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/ + * Relative to the chart + * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/ + * Relative to the chart + * + * @type {Highcharts.ButtonRelativeToValue} + * @default plot + * @since 2.2 + * @apioption chart.resetZoomButton.relativeTo + */ + /** + * 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`. + * + * @sample {highcharts} highcharts/chart/resetzoombutton-theme/ + * Theming the button + * @sample {highstock} highcharts/chart/resetzoombutton-theme/ + * Theming the button + * + * @type {Highcharts.SVGAttributes} + * @since 2.2 + */ + theme: { + /** @internal */ + zIndex: 6, + }, /** - * 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 - * - * @private - * @function Highcharts.SVGElement#symbolAttr - * - * @param {Highcharts.SVGAttributes} hash - * The attributes to set. - */ - SVGElement.prototype.symbolAttr = function (hash) { - var wrapper = this; - [ - 'x', - 'y', - 'r', - 'start', - 'end', - 'width', - 'height', - 'innerR', - 'anchorX', - 'anchorY', - 'clockwise' - ].forEach(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) - }); - }; + * The position of the button. + * + * @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 + * + * @type {Highcharts.AlignObject} + * @since 2.2 + */ + position: { + /** + * The horizontal alignment of the button. + */ + align: "right", + /** + * The horizontal offset of the button. + */ + x: -10, + /** + * The vertical alignment of the button. + * + * @type {Highcharts.VerticalAlignValue} + * @default top + * @apioption chart.resetZoomButton.position.verticalAlign + */ + /** + * The vertical offset of the button. + */ + y: 10, + }, + }, + /** + * The pixel width of the plot area border. + * + * @sample {highcharts} highcharts/chart/plotborderwidth/ + * 1px border + * @sample {highstock} stock/chart/plotborder/ + * 2px border + * @sample {highmaps} maps/chart/plotborder/ + * Plot border options + * + * @type {number} + * @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`. + * + * @sample {highcharts} highcharts/chart/plotshadow/ + * Plot shadow + * @sample {highstock} stock/chart/plotshadow/ + * Plot shadow + * @sample {highmaps} maps/chart/plotborder/ + * Plot border options + * + * @type {boolean|Highcharts.CSSObject} + * @default false + * @apioption chart.plotShadow + */ + /** + * When true, cartesian charts like line, spline, area and column are + * transformed into the polar coordinate system. This produces _polar + * charts_, also known as _radar charts_. + * + * @sample {highcharts} highcharts/demo/polar/ + * Polar chart + * @sample {highcharts} highcharts/demo/polar-wind-rose/ + * Wind rose, stacked polar column chart + * @sample {highcharts} highcharts/demo/polar-spider/ + * Spider web chart + * @sample {highcharts} highcharts/parallel-coordinates/polar/ + * Star plot, multivariate data in a polar chart + * + * @type {boolean} + * @default false + * @since 2.3.0 + * @product highcharts + * @requires highcharts-more + * @apioption chart.polar + */ + /** + * Whether to reflow the chart to fit the width of the container div + * on resizing the window. + * + * @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 + * + * @type {boolean} + * @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. + * + * @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 + * + * @type {string|Highcharts.HTMLDOMElement} + * @apioption chart.renderTo + */ + /** + * The background color of the marker square when selecting (zooming + * in on) an area of the chart. + * + * @see In styled mode, the selection marker fill is set with the + * `.highcharts-selection-marker` class. + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @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`. + * + * @sample {highcharts} highcharts/chart/shadow/ + * Shadow + * @sample {highstock} stock/chart/shadow/ + * Shadow + * @sample {highmaps} maps/chart/border/ + * Chart border and shadow + * + * @type {boolean|Highcharts.CSSObject} + * @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. + * + * @sample {highcharts} highcharts/chart/showaxes-false/ + * False by default + * @sample {highcharts} highcharts/chart/showaxes-true/ + * True + * + * @type {boolean} + * @since 1.2.5 + * @product highcharts gantt + * @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). + * + * @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 + * + * @type {number} + * @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). + * + * @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 + * + * @type {number} + * @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). + * + * @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 + * + * @type {number} + * @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). + * + * @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 + * + * @type {number} + * @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. + * + * @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 + * + * @type {Highcharts.CSSObject} + * @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) and [series](#series) or can + * be a series provided by an additional module. + * + * In TypeScript this option has no effect in sense of typing and + * instead the `type` option must always be set in the series. + * + * @sample {highcharts} highcharts/chart/type-bar/ + * Bar + * @sample {highstock} stock/chart/type/ + * Areaspline + * @sample {highmaps} maps/chart/type-mapline/ + * Mapline + * + * @type {string} + * @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`. + * + * @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 + * + * @type {string} + * @product highcharts highstock gantt + * @validvalue ["x", "y", "xy"] + * @apioption chart.zoomType + */ + /** + * An explicit width for the chart. By default (when `null`) the width + * is calculated from the offset width of the containing element. + * + * @sample {highcharts} highcharts/chart/width/ + * 800px wide + * @sample {highstock} stock/chart/width/ + * 800px wide + * @sample {highmaps} maps/chart/size/ + * Chart with explicit size + * + * @type {null|number|string} + */ + 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. + * + * @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 + * + * @type {null|number|string} + */ + height: null, + /** + * The color of the outer chart border. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + */ + borderColor: "#335cad", + /** + * The pixel width of the outer chart border. + * + * @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 + * + * @type {number} + * @default 0 + * @apioption chart.borderWidth + */ + /** + * The background color or gradient for the outer chart area. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + */ + backgroundColor: "#ffffff", + /** + * The background color or gradient for the plot area. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @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. + * + * @see In styled mode, a plot background image can be set with the + * `.highcharts-plot-background` class and a [custom pattern]( + * https://www.highcharts.com/docs/chart-design-and-style/ + * gradients-shadows-and-patterns). + * + * @sample {highcharts} highcharts/chart/plotbackgroundimage/ + * Skies + * @sample {highstock} stock/chart/plotbackgroundimage/ + * Skies + * + * @type {string} + * @apioption chart.plotBackgroundImage + */ + /** + * The color of the inner chart or plot area border. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + */ + plotBorderColor: "#cccccc", + }, + /** + * The chart's main title. + * + * @sample {highmaps} maps/title/title/ + * Title options demonstrated + */ + title: { + /** + * When the title is floating, the plot area will not move to make space + * for it. + * + * @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 + * + * @type {boolean} + * @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. + * + * @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 + * + * @type {Highcharts.CSSObject} + * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" } + * @default {highstock} { "color": "#333333", "fontSize": "16px" } + * @apioption title.style + */ + /** + * Whether to + * [use HTML](https://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`. + * + * @sample {highcharts} highcharts/title/verticalalign/ + * Chart title in bottom right corner + * @sample {highstock} stock/chart/title-verticalalign/ + * Chart title in bottom right corner + * + * @type {Highcharts.VerticalAlignValue} + * @since 2.1 + * @apioption title.verticalAlign + */ + /** + * The x position of the title relative to the alignment within + * `chart.spacingLeft` and `chart.spacingRight`. + * + * @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) + * + * @type {number} + * @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. + * + * @sample {highcharts} highcharts/title/y/ + * Title inside the plot area + * @sample {highstock} stock/chart/title-verticalalign/ + * Chart title in bottom right corner + * + * @type {number} + * @since 2.0 + * @apioption title.y + */ + /** + * The title of the chart. To disable the title, set the `text` to + * `undefined`. + * + * @sample {highcharts} highcharts/title/text/ + * Custom title + * @sample {highstock} stock/chart/title-text/ + * Custom title + * + * @default {highcharts|highmaps} Chart title + * @default {highstock} undefined + */ + text: "Chart title", + /** + * The horizontal alignment of the title. Can be one of "left", "center" + * and "right". + * + * @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) + * + * @type {Highcharts.AlignValue} + * @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. + * + * @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 + * + * @since 2.1 + */ + margin: 15, + /** + * Adjustment made to the title width, normally to reserve space for + * the exporting burger menu. + * + * @sample highcharts/title/widthadjust/ + * Wider menu, greater padding + * + * @since 4.2.5 + */ + widthAdjust: -44, + }, + /** + * 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: { + /** + * When the subtitle is floating, the plot area will not move to make + * space for it. + * + * @sample {highcharts} highcharts/subtitle/floating/ + * Floating title and subtitle + * @sample {highstock} stock/chart/subtitle-footnote + * Footnote floating at bottom right of plot area + * + * @type {boolean} + * @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. + * + * @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 + * + * @type {Highcharts.CSSObject} + * @default {"color": "#666666"} + * @apioption subtitle.style + */ + /** + * Whether to + * [use HTML](https://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 middle, the subtitle behaves as + * floating. + * + * @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 + * + * @type {Highcharts.VerticalAlignValue} + * @since 2.1 + * @apioption subtitle.verticalAlign + */ + /** + * The x position of the subtitle relative to the alignment within + * `chart.spacingLeft` and `chart.spacingRight`. + * + * @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 + * + * @type {number} + * @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. + * + * @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 + * + * @type {number} + * @since 2.0 + * @apioption subtitle.y + */ + /** + * The subtitle of the chart. + * + * @sample {highcharts|highstock} highcharts/subtitle/text/ + * Custom subtitle + * @sample {highcharts|highstock} highcharts/subtitle/text-formatted/ + * Formatted and linked text. + */ + text: "", + /** + * The horizontal alignment of the subtitle. Can be one of "left", + * "center" and "right". + * + * @sample {highcharts} highcharts/subtitle/align/ + * Footnote at right of plot area + * @sample {highstock} stock/chart/subtitle-footnote + * Footnote at bottom right of plot area + * + * @type {Highcharts.AlignValue} + * @since 2.0 + */ + align: "center", + /** + * Adjustment made to the subtitle width, normally to reserve space + * for the exporting burger menu. + * + * @see [title.widthAdjust](#title.widthAdjust) + * + * @sample highcharts/title/widthadjust/ + * Wider menu, greater padding + * + * @since 4.2.5 + */ + widthAdjust: -44, + }, + /** + * The chart's caption, which will render below the chart and will be part + * of exported charts. The caption can be updated after chart initialization + * through the `Chart.update` or `Chart.caption.update` methods. + * + * @sample highcharts/caption/text/ + * A chart with a caption + * @since 7.2.0 + */ + caption: { + /** + * When the caption is floating, the plot area will not move to make + * space for it. + * + * @type {boolean} + * @default false + * @apioption caption.floating + */ + /** + * The margin between the caption and the plot area. + */ + margin: 15, + /** + * CSS styles for the caption. + * + * In styled mode, the caption style is given in the + * `.highcharts-caption` class. + * + * @sample {highcharts} highcharts/css/titles/ + * Styled mode + * + * @type {Highcharts.CSSObject} + * @default {"color": "#666666"} + * @apioption caption.style + */ + /** + * Whether to + * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) + * to render the text. + * + * @type {boolean} + * @default false + * @apioption caption.useHTML + */ + /** + * The x position of the caption relative to the alignment within + * `chart.spacingLeft` and `chart.spacingRight`. + * + * @type {number} + * @default 0 + * @apioption caption.x + */ + /** + * The y position of the caption relative to the alignment within + * `chart.spacingTop` and `chart.spacingBottom`. + * + * @type {number} + * @apioption caption.y + */ + /** + * The caption text of the chart. + * + * @sample {highcharts} highcharts/caption/text/ + * Custom caption + */ + text: "", + /** + * The horizontal alignment of the caption. Can be one of "left", + * "center" and "right". + * + * @type {Highcharts.AlignValue} + */ + align: "left", + /** + * The vertical alignment of the caption. Can be one of `"top"`, + * `"middle"` and `"bottom"`. When middle, the caption behaves as + * floating. + * + * @type {Highcharts.VerticalAlignValue} + */ + verticalAlign: "bottom", + }, + /** + * 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. + * + * This option is deprecated since v7.1.2. Instead, use + * [annotations](#annotations) that support labels. + * + * @deprecated + * @product highcharts highstock + */ + labels: { + /** + * An HTML label that can be positioned anywhere in the chart area. + * + * @deprecated + * @type {Array<*>} + * @apioption labels.items + */ + /** + * Inner HTML or text for the label. + * + * @deprecated + * @type {string} + * @apioption labels.items.html + */ + /** + * CSS styles for each label. To position the label, use left and top + * like this: + * ```js + * style: { + * left: '100px', + * top: '100px' + * } + * ``` + * + * @deprecated + * @type {Highcharts.CSSObject} + * @apioption labels.items.style + */ + /** + * Shared CSS styles for all labels. + * + * @deprecated + * @type {Highcharts.CSSObject} + * @default {"color": "#333333", "position": "absolute"} + */ + style: { /** - * @private - * @function Highcharts.SVGElement#textSetter - * @param {string} value + * @ignore-option */ - SVGElement.prototype.textSetter = function (value) { - if (value !== this.textStr) { - // Delete size caches when the text changes - // delete this.bBox; // old code in series-label - delete this.textPxLength; - this.textStr = value; - if (this.added) { - this.renderer.buildText(this); - } - } - }; + position: "absolute", /** - * @private - * @function Highcharts.SVGElement#titleSetter - * @param {string} value + * @ignore-option */ - SVGElement.prototype.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, '') - .replace(/</g, '<') - .replace(/>/g, '>'))); - }; - /** - * Bring the element to the front. Alternatively, a new zIndex can be set. - * - * @sample highcharts/members/element-tofront/ - * Click an element to bring it to front - * - * @function Highcharts.SVGElement#toFront - * - * @return {Highcharts.SVGElement} - * Returns the SVGElement for chaining. + 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](https://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. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @apioption legend.backgroundColor + */ + /** + * The width of the drawn border around the legend. + * + * @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 + * + * @type {number} + * @default 0 + * @apioption legend.borderWidth + */ + /** + * Enable or disable the legend. There is also a series-specific option, + * [showInLegend](#plotOptions.series.showInLegend), that can hide the + * series from the legend. In some series types this is `false` by + * default, so it must set to `true` in order to show the legend for the + * series. + * + * @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 + * @default {gantt} false + */ + 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. + * + * @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 + * + * @type {Highcharts.AlignValue} + * @since 2.0 + */ + align: "center", + /** + * If the [layout](legend.layout) is `horizontal` and the legend items + * span over two lines or more, whether to align the items into vertical + * columns. Setting this to `false` makes room for more items, but will + * look more messy. + * + * @since 6.1.0 + */ + alignColumns: true, + /** + * When the legend is floating, the plot area ignores it and is allowed + * to be placed below it. + * + * @sample {highcharts} highcharts/legend/floating-false/ + * False by default + * @sample {highcharts} highcharts/legend/floating-true/ + * True + * @sample {highmaps} maps/legend/alignment/ + * Floating legend + * + * @type {boolean} + * @default false + * @since 2.1 + * @apioption legend.floating + */ + /** + * The layout of the legend items. Can be one of `horizontal` or + * `vertical` or `proximate`. When `proximate`, the legend items will be + * placed as close as possible to the graphs they're representing, + * except in inverted charts or when the legend position doesn't allow + * it. + * + * @sample {highcharts} highcharts/legend/layout-horizontal/ + * Horizontal by default + * @sample {highcharts} highcharts/legend/layout-vertical/ + * Vertical + * @sample highcharts/legend/layout-proximate + * Labels proximate to the data + * @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 + * + * @validvalue ["horizontal", "vertical", "proximate"] + */ + layout: "horizontal", + /** + * In a legend with horizontal layout, the itemDistance defines the + * pixel distance between each item. + * + * @sample {highcharts} highcharts/legend/layout-horizontal/ + * 50px item distance + * @sample {highstock} highcharts/legend/layout-horizontal/ + * 50px item distance + * + * @type {number} + * @default {highcharts} 20 + * @default {highstock} 20 + * @default {highmaps} 8 + * @since 3.0.3 + * @apioption legend.itemDistance + */ + /** + * The pixel bottom margin for each legend item. + * + * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ + * Padding and item margins demonstrated + * @sample {highmaps} maps/legend/padding-itemmargin/ + * Padding and item margins demonstrated + * + * @type {number} + * @default 0 + * @since 2.2.0 + * @apioption legend.itemMarginBottom + */ + /** + * The pixel top margin for each legend item. + * + * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ + * Padding and item margins demonstrated + * @sample {highmaps} maps/legend/padding-itemmargin/ + * Padding and item margins demonstrated + * + * @type {number} + * @default 0 + * @since 2.2.0 + * @apioption legend.itemMarginTop + */ + /** + * The width for each legend item. By default the items are laid out + * successively. In a [horizontal layout](legend.layout), if the items + * are laid out across two rows or more, they will be vertically aligned + * depending on the [legend.alignColumns](legend.alignColumns) option. + * + * @sample {highcharts} highcharts/legend/itemwidth-default/ + * Undefined by default + * @sample {highcharts} highcharts/legend/itemwidth-80/ + * 80 for aligned legend items + * + * @type {number} + * @since 2.0 + * @apioption legend.itemWidth + */ + /** + * A [format string](https://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 + */ + /* eslint-disable valid-jsdoc */ + /** + * 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`. + * + * @sample {highcharts} highcharts/legend/labelformatter/ + * Add text + * @sample {highmaps} maps/legend/labelformatter/ + * Data classes with label formatter + * + * @type {Highcharts.FormatterCallbackFunction<Point|Series>} + */ + labelFormatter: function () { + /** eslint-enable valid-jsdoc */ + 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`. + * + * @sample {highcharts} highcharts/legend/lineheight/ + * Setting padding + * + * @deprecated + * + * @type {number} + * @default 16 + * @since 2.0 + * @product highcharts gantt + * @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. + * + * @sample {highcharts} highcharts/legend/margin-default/ + * 12 pixels by default + * @sample {highcharts} highcharts/legend/margin-30/ + * 30 pixels + * + * @type {number} + * @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} + * @since 2.3.0 + * @apioption legend.maxHeight + */ + /** + * The color of the drawn border around the legend. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + */ + borderColor: "#999999", + /** + * The border corner radius of the legend. + * + * @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 + */ + 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](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-enabled-false/). + */ + navigation: { + /** + * 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. + * + * @sample {highcharts} highcharts/legend/navigation/ + * Legend page navigation demonstrated + * @sample {highstock} highcharts/legend/navigation/ + * Legend page navigation demonstrated + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + * @default true + * @since 2.2.4 + * @apioption legend.navigation.animation */ - SVGElement.prototype.toFront = function () { - var element = this.element; - element.parentNode.appendChild(element); - return this; - }; /** - * Move an object and its children by x and y values. - * - * @function Highcharts.SVGElement#translate + * The pixel size of the up and down arrows in the legend paging + * navigation. * - * @param {number} x - * The x value. + * @sample {highcharts} highcharts/legend/navigation/ + * Legend page navigation demonstrated + * @sample {highstock} highcharts/legend/navigation/ + * Legend page navigation demonstrated * - * @param {number} y - * The y value. - * - * @return {Highcharts.SVGElement} + * @type {number} + * @default 12 + * @since 2.2.4 + * @apioption legend.navigation.arrowSize */ - SVGElement.prototype.translate = function (x, y) { - return this.attr({ - translateX: x, - translateY: y - }); - }; /** - * Update the shadow elements with new attributes. - * - * @private - * @function Highcharts.SVGElement#updateShadows - * - * @param {string} key - * The attribute name. - * - * @param {number} value - * The value of the attribute. + * Whether to enable the legend navigation. In most cases, disabling + * the navigation results in an unwanted overflow. * - * @param {Function} setter - * The setter function, inherited from the parent wrapper. - */ - SVGElement.prototype.updateShadows = function (key, value, setter) { - var shadows = this.shadows; - if (shadows) { - var 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]); - } - } - }; - /** - * 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. + * See also the [adapt chart to legend]( + * https://www.highcharts.com/products/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. * - * @private - * @function Highcharts.SVGElement#updateTransform + * @type {boolean} + * @default true + * @since 4.2.4 + * @apioption legend.navigation.enabled */ - SVGElement.prototype.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(' ')); - } - }; /** - * @private - * @function Highcharts.SVGElement#visibilitySetter + * Text styles for the legend page navigation. * - * @param {string} value + * @see In styled mode, the navigation items are styled with the + * `.highcharts-legend-navigation` class. * - * @param {string} key + * @sample {highcharts} highcharts/legend/navigation/ + * Legend page navigation demonstrated + * @sample {highstock} highcharts/legend/navigation/ + * Legend page navigation demonstrated * - * @param {Highcharts.SVGDOMElement} element + * @type {Highcharts.CSSObject} + * @since 2.2.4 + * @apioption legend.navigation.style + */ + /** + * The color for the active up or down arrow in the legend page + * navigation. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @since 2.2.4 + */ + activeColor: "#003399", + /** + * The color of the inactive up or down arrow in the legend page + * navigation. . + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @since 2.2.4 + */ + inactiveColor: "#cccccc", + }, + /** + * The inner padding of the legend box. + * + * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ + * Padding and item margins demonstrated + * @sample {highmaps} maps/legend/padding-itemmargin/ + * Padding and item margins demonstrated + * + * @type {number} + * @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. + * + * @see [yAxis.reversedStacks](#yAxis.reversedStacks), + * [series.legendIndex](#series.legendIndex) + * + * @sample {highcharts} highcharts/legend/reversed/ + * Stacked bar with reversed legend + * + * @type {boolean} + * @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. + * + * @sample {highcharts} highcharts/legend/rtl/ + * Symbol to the right + * + * @type {boolean} + * @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. + * + * @deprecated + * + * @type {Highcharts.CSSObject} + * @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 `undefined` to wrap + * text instead. A `width` property can be added to control the text + * width. + * + * @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 + * + * @type {Highcharts.CSSObject} + * @default {"color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis"} + */ + itemStyle: { + /** + * @ignore + */ + color: "#333333", + /** + * @ignore + */ + cursor: "pointer", + /** + * @ignore + */ + fontSize: "12px", + /** + * @ignore + */ + fontWeight: "bold", + /** + * @ignore + */ + 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. + * + * @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 + * + * @type {Highcharts.CSSObject} + * @default {"color": "#000000"} + */ + itemHoverStyle: { + /** + * @ignore + */ + 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. + * + * @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 + * + * @type {Highcharts.CSSObject} + * @default {"color": "#cccccc"} + */ + itemHiddenStyle: { + /** + * @ignore + */ + 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`. + * + * @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 + * + * @type {boolean|Highcharts.CSSObject} + */ + shadow: false, + /** + * Default styling for the checkbox next to a legend item when + * `showCheckbox` is true. + * + * @type {Highcharts.CSSObject} + * @default {"width": "13px", "height": "13px", "position":"absolute"} + */ + itemCheckboxStyle: { + /** + * @ignore + */ + position: "absolute", + /** + * @ignore + */ + width: "13px", + /** + * @ignore + */ + 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. + * + * @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. + * + * @sample {highmaps} maps/legend/layout-vertical-sized/ + * Sized vertical gradient + * @sample {highmaps} maps/legend/padding-itemmargin/ + * No distance between data classes + * + * @type {number} + * @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`. + * + * @sample {highcharts} highcharts/legend/symbolradius/ + * Round symbols + * @sample {highstock} highcharts/legend/symbolradius/ + * Round symbols + * @sample {highmaps} highcharts/legend/symbolradius/ + * Round symbols + * + * @type {number} + * @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. + * + * @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 + * + * @type {number} + * @apioption legend.symbolWidth + */ + /** + * Whether to [use HTML](https://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. If a number is set, it translates to + * pixels. Since v7.0.2 it allows setting a percent string of the full + * chart width, for example `40%`. + * + * Defaults to the full chart width for legends below or above the + * chart, half the chart width for legends to the left and right. + * + * @sample {highcharts} highcharts/legend/width/ + * Aligned to the plot area + * @sample {highcharts} highcharts/legend/width-percent/ + * A percent of the chart width + * + * @type {number|string} + * @since 2.0 + * @apioption legend.width + */ + /** + * The pixel padding between the legend item symbol and the legend + * item text. + * + * @sample {highcharts} highcharts/legend/symbolpadding/ + * Greater symbol width and padding + */ + 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. + * + * When the [layout](#legend.layout) option is `proximate`, the + * `verticalAlign` option doesn't apply. + * + * @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 + * + * @type {Highcharts.VerticalAlignValue} + * @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. + * + * @sample {highcharts} highcharts/legend/width/ + * Aligned to the plot area + * + * @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. + * + * @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 + * + * @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. * - * @return {void} + * @type {string} + * @since 3.0 + * @apioption legend.title.text */ - SVGElement.prototype.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; - }; /** - * @private - * @function Highcharts.SVGElement#xGetter + * Generic CSS styles for the legend title. * - * @param {string} key + * @see In styled mode, the legend title is styled with the + * `.highcharts-legend-title` class. * - * @return {number|string|null} + * @type {Highcharts.CSSObject} + * @default {"fontWeight": "bold"} + * @since 3.0 */ - SVGElement.prototype.xGetter = function (key) { - if (this.element.nodeName === 'circle') { - if (key === 'x') { - key = 'cx'; - } - else if (key === 'y') { - key = 'cy'; - } - } - return this._defaultGetter(key); - }; - /** - * @private - * @function Highcharts.SVGElement#zIndexSetter - * @param {number} [value] - * @param {string} [key] - * @return {boolean} - */ - SVGElement.prototype.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 = false, - undefinedOtherZIndex, - svgParent = parentNode === renderer.box, - run = this.added, - i; - if (defined(value)) { - // So we can read it for other elements in the group - element.setAttribute('data-z-index', value); - value = +value; - if (this[key] === value) { - // Only update when needed (#3865) - run = false; - } - } - else if (defined(this[key])) { - element.removeAttribute('data-z-index'); - } - 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.getAttribute('data-z-index'); - undefinedOtherZIndex = !defined(otherZIndex); - if (otherElement !== element) { - if ( - // Negative zIndex versus no zIndex: - // On all levels except the highest. If the parent is - // <svg>, then we don't want to put items before <desc> - // or <defs> - 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; - }; - return SVGElement; - }()); - // Some shared setters and getters - SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter; - SVGElement.prototype.yGetter = SVGElement.prototype.xGetter; - SVGElement.prototype.matrixSetter = - SVGElement.prototype.rotationOriginXSetter = - SVGElement.prototype.rotationOriginYSetter = - SVGElement.prototype.rotationSetter = - SVGElement.prototype.scaleXSetter = - SVGElement.prototype.scaleYSetter = - SVGElement.prototype.translateXSetter = - SVGElement.prototype.translateYSetter = - SVGElement.prototype.verticalAlignSetter = function (value, key) { - this[key] = value; - this.doTransform = true; - }; - H.SVGElement = SVGElement; + style: { + /** + * @ignore + */ + 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. + * + * @sample highcharts/loading/hideduration/ + * Fade in and out over a second + * + * @type {number} + * @default 100 + * @since 1.2.0 + * @apioption loading.hideDuration + */ + /** + * The duration in milliseconds of the fade in effect. + * + * @sample highcharts/loading/hideduration/ + * Fade in and out over a second + * + * @type {number} + * @default 100 + * @since 1.2.0 + * @apioption loading.showDuration + */ + /** + * CSS styles for the loading label `span`. + * + * @see In styled mode, the loading label is styled with the + * `.highcharts-loading-inner` class. + * + * @sample {highcharts|highmaps} highcharts/loading/labelstyle/ + * Vertically centered + * @sample {highstock} stock/loading/general/ + * Label styles + * + * @type {Highcharts.CSSObject} + * @default {"fontWeight": "bold", "position": "relative", "top": "45%"} + * @since 1.2.0 + */ + labelStyle: { + /** + * @ignore + */ + fontWeight: "bold", + /** + * @ignore + */ + position: "relative", + /** + * @ignore + */ + top: "45%", + }, + /** + * CSS styles for the loading screen that covers the plot area. + * + * In styled mode, the loading label is styled with the + * `.highcharts-loading` class. + * + * @sample {highcharts|highmaps} highcharts/loading/style/ + * Gray plot area, white text + * @sample {highstock} stock/loading/general/ + * Gray plot area, white text + * + * @type {Highcharts.CSSObject} + * @default {"position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center"} + * @since 1.2.0 + */ + style: { + /** + * @ignore + */ + position: "absolute", + /** + * @ignore + */ + backgroundColor: "#ffffff", + /** + * @ignore + */ + opacity: 0.5, + /** + * @ignore + */ + textAlign: "center", + }, + }, + /** + * Options for the tooltip that appears when the user hovers over a + * series or point. + * + * @declare Highcharts.TooltipOptions + */ + tooltip: { + /** + * The color of the tooltip border. When `undefined`, the border takes + * the color of the corresponding series or point. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @apioption tooltip.borderColor + */ + /** + * A CSS class name to apply to the tooltip's container div, + * allowing unique CSS styling for each chart. + * + * @type {string} + * @apioption tooltip.className + */ + /** + * 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). + * + * @sample {highcharts} highcharts/tooltip/crosshairs-x/ + * Enable a crosshair for the x value + * + * @deprecated + * + * @type {*} + * @default true + * @apioption tooltip.crosshairs + */ + /** + * Distance from point to tooltip in pixels. + * + * @type {number} + * @default 16 + * @apioption tooltip.distance + */ + /** + * 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 pie, polygon, map, sankey + * and wordcloud 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 update as the finger 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. + * + * Note the difference to [followPointer](#tooltip.followPointer) that + * only defines the _position_ of the tooltip. If `followPointer` is + * false in for example a column series, the tooltip will show above or + * below the column, but as `followTouchMove` is true, the tooltip will + * jump from column to column as the user swipes across the plot area. + * + * @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. In + * case of single or [shared](#tooltip.shared) tooltips, a string should + * be returned. In case of [split](#tooltip.split) tooltips, it should + * return an array where the first item is the header, and subsequent + * items are mapped to the points. 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 HTML tags are supported: `b`, + * `br`, `em`, `i`, `span`, `strong`. Spans can be styled with a `style` + * attribute, but only text-related CSS, that is shared with SVG, is + * handled. + * + * The available data in the formatter differ a bit depending on whether + * the tooltip is shared or split, or belongs to a single point. In a + * shared/split 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. + * + * @sample {highcharts} highcharts/tooltip/formatter-simple/ + * Simple string formatting + * @sample {highcharts} highcharts/tooltip/formatter-shared/ + * Formatting with shared tooltip + * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/ + * Formatting with split tooltip + * @sample highcharts/tooltip/formatter-conditional-default/ + * Extending default formatter + * @sample {highstock} stock/tooltip/formatter/ + * Formatting with shared tooltip + * @sample {highmaps} maps/tooltip/formatter/ + * String formatting + * + * @type {Highcharts.TooltipFormatterCallbackFunction} + * @apioption tooltip.formatter + */ + /** + * Callback function to format the text of the tooltip for + * visible null points. + * Works analogously to [formatter](#tooltip.formatter). + * + * @sample highcharts/plotoptions/series-nullformat + * Format data label and tooltip for null point. + * + * @type {Highcharts.TooltipFormatterCallbackFunction} + * @apioption tooltip.nullFormatter + */ + /** + * 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 + */ + /** + * Whether to allow the tooltip to render outside the chart's SVG + * element box. By default (`false`), the tooltip is rendered within the + * chart's SVG element, which results in the tooltip being aligned + * inside the chart area. For small charts, this may result in clipping + * or overlapping. When `true`, a separate SVG element is created and + * overlaid on the page, allowing the tooltip to be aligned inside the + * page itself. + * + * Defaults to `true` if `chart.scrollablePlotArea` is activated, + * otherwise `false`. + * + * @sample highcharts/tooltip/outside + * Small charts with tooltips outside + * + * @type {boolean|undefined} + * @default undefined + * @since 6.1.1 + * @apioption tooltip.outside + */ + /** + * A callback function for formatting the HTML output for a single point + * in the tooltip. Like the `pointFormat` string, but with more + * flexibility. + * + * @type {Highcharts.FormatterCallbackFunction<Highcharts.Point>} + * @since 4.1.0 + * @context Highcharts.Point + * @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. + * + * Since v7, when [tooltip.split](#tooltip.split) option is enabled, + * positioner is called for each of the boxes separately, including + * xAxis header. xAxis header is not a point, instead `point` argument + * contains info: + * `{ plotX: Number, plotY: Number, isHeader: Boolean }` + * + * + * The return should be an object containing x and y values, for example + * `{ x: 100, y: 100 }`. + * + * @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 + * @sample {highstock} stock/tooltip/split-positioner/ + * Split tooltip with fixed positions + * @sample {highstock} stock/tooltip/positioner-scrollable-plotarea/ + * Scrollable plot area combined with tooltip positioner + * + * @type {Highcharts.TooltipPositionerCallbackFunction} + * @since 2.2.4 + * @apioption tooltip.positioner + */ + /** + * The name of a symbol to use for the border around the tooltip. Can + * be one of: `"callout"`, `"circle"`, or `"square"`. When + * [tooltip.split](#tooltip.split) + * option is enabled, shape is applied to all boxes except header, which + * is controlled by + * [tooltip.headerShape](#tooltip.headerShape). + * + * Custom callbacks for symbol path generation can also be added to + * `Highcharts.SVGRenderer.prototype.symbols` the same way as for + * [series.marker.symbol](plotOptions.line.marker.symbol). + * + * @type {Highcharts.TooltipShapeValue} + * @default callout + * @since 4.0 + * @apioption tooltip.shape + */ + /** + * The name of a symbol to use for the border around the tooltip + * header. Applies only when [tooltip.split](#tooltip.split) is + * enabled. + * + * Custom callbacks for symbol path generation can also be added to + * `Highcharts.SVGRenderer.prototype.symbols` the same way as for + * [series.marker.symbol](plotOptions.line.marker.symbol). + * + * @see [tooltip.shape](#tooltip.shape) + * + * @sample {highstock} stock/tooltip/split-positioner/ + * Different shapes for header and split boxes + * + * @type {Highcharts.TooltipShapeValue} + * @default callout + * @validvalue ["callout", "square"] + * @since 7.0 + * @apioption tooltip.headerShape + */ + /** + * 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. The + * `tooltip.split` option takes precedence over `tooltip.shared`. + * + * @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 + * + * @type {boolean} + * @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. This option takes precedence over `tooltip.shared`. + * + * @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. + * + * @sample highcharts/tooltip/split/ + * Split tooltip + * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/ + * Split tooltip and custom formatter callback + * + * @type {boolean} + * @default {highcharts} false + * @default {highstock} true + * @since 5.0.0 + * @product highcharts highstock + * @apioption tooltip.split + */ + /** + * Prevents the tooltip from switching or closing, when touched or + * pointed. + * + * @sample highcharts/tooltip/stickoncontact/ + * Tooltip sticks on pointer contact + * + * @type {boolean} + * @since 8.0.1 + * @apioption tooltip.stickOnContact + */ + /** + * 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. + * + * @sample {highcharts|highstock} highcharts/tooltip/footerformat/ + * A table for value alignment + * @sample {highcharts|highstock} highcharts/tooltip/fullhtml/ + * Full HTML tooltip + * @sample {highmaps} maps/tooltip/usehtml/ + * Pure HTML tooltip + * + * @type {boolean} + * @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. + * + * @sample {highcharts|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 + * + * @type {number} + * @since 2.2 + * @apioption tooltip.valueDecimals + */ + /** + * A string to prepend to each series' y value. Overridable in each + * series' tooltip options object. + * + * @sample {highcharts|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 + * + * @type {string} + * @since 2.2 + * @apioption tooltip.valuePrefix + */ + /** + * A string to append to each series' y value. Overridable in each + * series' tooltip options object. + * + * @sample {highcharts|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 + * + * @type {string} + * @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. + * + * @sample {highcharts} highcharts/tooltip/xdateformat/ + * A different format + * + * @type {string} + * @product highcharts highstock gantt + * @apioption tooltip.xDateFormat + */ + /** + * How many decimals to show for the `point.change` value when the + * `series.compare` option is set. This is overridable in each series' + * tooltip options object. The default is to preserve all decimals. + * + * @type {number} + * @since 1.0.1 + * @product highstock + * @apioption tooltip.changeDecimals + */ + /** + * Enable or disable the tooltip. + * + * @sample {highcharts} highcharts/tooltip/enabled/ + * Disabled + * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ + * Disable tooltip and show values on chart instead + */ + enabled: true, + /** + * Enable or disable animation of the tooltip. + * + * @type {boolean} + * @default true + * @since 2.3.0 + */ + animation: svg, + /** + * The radius of the rounded border corners. + * + * @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 + */ + 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](/class-reference/Highcharts#dateFormat). + * + * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats) + * + * @type {Highcharts.Dictionary<string>} + * @product highcharts highstock gantt + */ + dateTimeLabelFormats: { + /** @internal */ + millisecond: "%A, %b %e, %H:%M:%S.%L", + /** @internal */ + second: "%A, %b %e, %H:%M:%S", + /** @internal */ + minute: "%A, %b %e, %H:%M", + /** @internal */ + hour: "%A, %b %e, %H:%M", + /** @internal */ + day: "%A, %b %e, %Y", + /** @internal */ + week: "Week from %A, %b %e, %Y", + /** @internal */ + month: "%B %Y", + /** @internal */ + 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. + * + * @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). + * + * @sample {highcharts} highcharts/tooltip/bordercolor-default/ + * 10 px by default + * @sample {highcharts} highcharts/tooltip/snap-50/ + * 50 px on graph + * + * @type {number} + * @default 10/25 + * @since 1.2.0 + * @product highcharts highstock + */ + snap: isTouchDevice ? 25 : 10, + /** + * 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`. + * + * @sample {highcharts} highcharts/tooltip/footerformat/ + * An HTML table in the tooltip + * @sample {highstock} highcharts/tooltip/footerformat/ + * An HTML table in the tooltip + * @sample {highmaps} maps/tooltip/format/ + * Format demo + * + * @type {string} + * @apioption tooltip.headerFormat + */ + headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>', + /** + * The HTML of the null point's line in the tooltip. Works analogously + * to [pointFormat](#tooltip.pointFormat). + * + * @sample {highcharts} highcharts/plotoptions/series-nullformat + * Format data label and tooltip for null point. + * + * @type {string} + * @apioption tooltip.nullFormat + */ + /** + * 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. + * + * @sample {highcharts} highcharts/tooltip/pointformat/ + * A different point format with value suffix + * @sample {highmaps} maps/tooltip/format/ + * Format demo + * + * @type {string} + * @since 2.2 + * @apioption tooltip.pointFormat + */ + pointFormat: + '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>', + /** + * The background color or gradient for the tooltip. + * + * In styled mode, the stroke width is set in the + * `.highcharts-tooltip-box` class. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + */ + 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. + * + * @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 + */ + borderWidth: 1, + /** + * Whether to apply a drop shadow to the tooltip. + * + * @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 + * + * @type {boolean|Highcharts.ShadowOptionsObject} + */ + shadow: true, + /** + * CSS styles for the tooltip. The tooltip can also be styled through + * the CSS class `.highcharts-tooltip`. + * + * Note that the default `pointerEvents` style makes the tooltip ignore + * mouse events, so in order to use clickable tooltips, this value must + * be set to `auto`. + * + * @sample {highcharts} highcharts/tooltip/style/ + * Greater padding, bold text + * + * @type {Highcharts.CSSObject} + */ + style: { + /** @internal */ + color: "#333333", + /** @internal */ + cursor: "default", + /** @internal */ + fontSize: "12px", + /** @internal */ + whiteSpace: "nowrap", + }, + }, + /** + * Highchart by default puts a credits label in the lower right corner + * of the chart. This can be changed using these options. + */ + credits: { + /** + * Credits for map source to be concatenated with conventional credit + * text. By default this is a format string that collects copyright + * information from the map if available. + * + * @see [mapTextFull](#credits.mapTextFull) + * @see [text](#credits.text) + * + * @type {string} + * @default \u00a9 <a href="{geojson.copyrightUrl}">{geojson.copyrightShort}</a> + * @since 4.2.2 + * @product highmaps + * @apioption credits.mapText + */ + /** + * Detailed credits for map source to be displayed on hover of credits + * text. By default this is a format string that collects copyright + * information from the map if available. + * + * @see [mapText](#credits.mapText) + * @see [text](#credits.text) + * + * @type {string} + * @default {geojson.copyright} + * @since 4.2.2 + * @product highmaps + * @apioption credits.mapTextFull + */ + /** + * Whether to show the credits text. + * + * @sample {highcharts} highcharts/credits/enabled-false/ + * Credits disabled + * @sample {highstock} stock/credits/enabled/ + * Credits disabled + * @sample {highmaps} maps/credits/enabled-false/ + * Credits disabled + */ + enabled: true, + /** + * The URL for the credits label. + * + * @sample {highcharts} highcharts/credits/href/ + * Custom URL and text + * @sample {highmaps} maps/credits/customized/ + * Custom URL and text + */ + href: "https://www.highcharts.com?credits", + /** + * Position configuration for the credits label. + * + * @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 + * + * @type {Highcharts.AlignObject} + * @since 2.1 + */ + position: { + /** @internal */ + align: "right", + /** @internal */ + x: -10, + /** @internal */ + verticalAlign: "bottom", + /** @internal */ + y: -5, + }, + /** + * CSS styles for the credits label. + * + * @see In styled mode, credits styles can be set with the + * `.highcharts-credits` class. + * + * @type {Highcharts.CSSObject} + */ + style: { + /** @internal */ + cursor: "pointer", + /** @internal */ + color: "#999999", + /** @internal */ + 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`. + * + * @sample {highcharts} highcharts/credits/href/ + * Custom URL and text + * @sample {highmaps} maps/credits/customized/ + * Custom URL and text + */ + text: "Highcharts.com", + }, + }; + /* eslint-disable spaced-comment */ - return H.SVGElement; - }); - _registerModule(_modules, 'Core/Renderer/SVG/SVGLabel.js', [_modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (SVGElement, U) { + (""); + /** + * Global `Time` object with default options. Since v6.0.5, time settings can be + * applied individually for each chart. If no individual settings apply, this + * `Time` object is shared by all instances. + * + * @name Highcharts.time + * @type {Highcharts.Time} + */ + H.time = new Time(merge(H.defaultOptions.global, H.defaultOptions.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](https://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 + * {@link Highcharts.Chart#time} instance to respect chart-level time settings. + * The `Highcharts.dateFormat` function only reflects global time settings set + * with `setOptions`. + * + * Supported format keys: + * - `%a`: Short weekday, like 'Mon' + * - `%A`: Long weekday, like 'Monday' + * - `%d`: Two digit day of the month, 01 to 31 + * - `%e`: Day of the month, 1 through 31 + * - `%w`: Day of the week, 0 through 6 + * - `%b`: Short month, like 'Jan' + * - `%B`: Long month, like 'January' + * - `%m`: Two digit month number, 01 through 12 + * - `%y`: Two digits year, like 09 for 2009 + * - `%Y`: Four digits year, like 2009 + * - `%H`: Two digits hours in 24h format, 00 through 23 + * - `%k`: Hours in 24h format, 0 through 23 + * - `%I`: Two digits hours in 12h format, 00 through 11 + * - `%l`: Hours in 12h format, 1 through 12 + * - `%M`: Two digits minutes, 00 through 59 + * - `%p`: Upper case AM or PM + * - `%P`: Lower case AM or PM + * - `%S`: Two digits seconds, 00 through 59 + * - `%L`: Milliseconds (naming from Ruby) + * + * @function Highcharts.dateFormat + * + * @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. + * + * @return {string} + * The formatted date. + */ + H.dateFormat = function (format, timestamp, capitalize) { + return H.time.dateFormat(format, timestamp, capitalize); + }; + var optionsModule = { + dateFormat: H.dateFormat, + defaultOptions: H.defaultOptions, + time: H.time, + }; + + return optionsModule; + } + ); + _registerModule( + _modules, + "Core/Axis/Axis.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Color/Color.js"], + _modules["Core/Globals.js"], + _modules["Core/Axis/Tick.js"], + _modules["Core/Utilities.js"], + _modules["Core/Options.js"], + ], + function (A, Color, H, Tick, U, O) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var animObject = A.animObject; + var addEvent = U.addEvent, + arrayMax = U.arrayMax, + arrayMin = U.arrayMin, + clamp = U.clamp, + correctFloat = U.correctFloat, + defined = U.defined, + destroyObjectProperties = U.destroyObjectProperties, + error = U.error, + extend = U.extend, + fireEvent = U.fireEvent, + format = U.format, + getMagnitude = U.getMagnitude, + isArray = U.isArray, + isFunction = U.isFunction, + isNumber = U.isNumber, + isString = U.isString, + merge = U.merge, + normalizeTickInterval = U.normalizeTickInterval, + objectEach = U.objectEach, + pick = U.pick, + relativeLength = U.relativeLength, + removeEvent = U.removeEvent, + splat = U.splat, + syncTimeout = U.syncTimeout; + /** + * Options for the path on the Axis to be calculated. + * @interface Highcharts.AxisPlotLinePathOptionsObject + */ /** + * Axis value. + * @name Highcharts.AxisPlotLinePathOptionsObject#value + * @type {number|undefined} + */ /** + * Line width used for calculation crisp line coordinates. Defaults to 1. + * @name Highcharts.AxisPlotLinePathOptionsObject#lineWidth + * @type {number|undefined} + */ /** + * If `false`, the function will return null when it falls outside the axis + * bounds. If `true`, the function will return a path aligned to the plot area + * sides if it falls outside. If `pass`, it will return a path outside. + * @name Highcharts.AxisPlotLinePathOptionsObject#force + * @type {string|boolean|undefined} + */ /** + * Used in Highstock. When `true`, plot paths (crosshair, plotLines, gridLines) + * will be rendered on all axes when defined on the first axis. + * @name Highcharts.AxisPlotLinePathOptionsObject#acrossPanes + * @type {boolean|undefined} + */ /** + * Use old coordinates (for resizing and rescaling). + * If not set, defaults to `false`. + * @name Highcharts.AxisPlotLinePathOptionsObject#old + * @type {boolean|undefined} + */ /** + * If given, return the plot line path of a pixel position on the axis. + * @name Highcharts.AxisPlotLinePathOptionsObject#translatedValue + * @type {number|undefined} + */ /** + * Used in Polar axes. Reverse the positions for concatenation of polygonal + * plot bands + * @name Highcharts.AxisPlotLinePathOptionsObject#reverse + * @type {boolean|undefined} + */ + /** + * Options for crosshairs on axes. + * + * @product highstock + * + * @typedef {Highcharts.XAxisCrosshairOptions|Highcharts.YAxisCrosshairOptions} Highcharts.AxisCrosshairOptions + */ + /** + * @typedef {"navigator"|"pan"|"rangeSelectorButton"|"rangeSelectorInput"|"scrollbar"|"traverseUpButton"|"zoom"} Highcharts.AxisExtremesTriggerValue + */ + /** + * @callback Highcharts.AxisEventCallbackFunction + * + * @param {Highcharts.Axis} this + */ + /** + * @callback Highcharts.AxisLabelsFormatterCallbackFunction + * + * @param {Highcharts.AxisLabelsFormatterContextObject<number>} this + * + * @param {Highcharts.AxisLabelsFormatterContextObject<string>} that + * + * @return {string} + */ + /** + * @interface Highcharts.AxisLabelsFormatterContextObject<T> + */ /** + * @name Highcharts.AxisLabelsFormatterContextObject<T>#axis + * @type {Highcharts.Axis} + */ /** + * @name Highcharts.AxisLabelsFormatterContextObject<T>#chart + * @type {Highcharts.Chart} + */ /** + * @name Highcharts.AxisLabelsFormatterContextObject<T>#isFirst + * @type {boolean} + */ /** + * @name Highcharts.AxisLabelsFormatterContextObject<T>#isLast + * @type {boolean} + */ /** + * @name Highcharts.AxisLabelsFormatterContextObject<T>#pos + * @type {number} + */ /** + * This can be either a numeric value or a category string. + * @name Highcharts.AxisLabelsFormatterContextObject<T>#value + * @type {T} + */ + /** + * Options for axes. + * + * @typedef {Highcharts.XAxisOptions|Highcharts.YAxisOptions|Highcharts.ZAxisOptions} Highcharts.AxisOptions + */ + /** + * @callback Highcharts.AxisPointBreakEventCallbackFunction + * + * @param {Highcharts.Axis} this + * + * @param {Highcharts.AxisPointBreakEventObject} evt + */ + /** + * @interface Highcharts.AxisPointBreakEventObject + */ /** + * @name Highcharts.AxisPointBreakEventObject#brk + * @type {Highcharts.Dictionary<number>} + */ /** + * @name Highcharts.AxisPointBreakEventObject#point + * @type {Highcharts.Point} + */ /** + * @name Highcharts.AxisPointBreakEventObject#preventDefault + * @type {Function} + */ /** + * @name Highcharts.AxisPointBreakEventObject#target + * @type {Highcharts.SVGElement} + */ /** + * @name Highcharts.AxisPointBreakEventObject#type + * @type {"pointBreak"|"pointInBreak"} + */ + /** + * @callback Highcharts.AxisSetExtremesEventCallbackFunction + * + * @param {Highcharts.Axis} this + * + * @param {Highcharts.AxisSetExtremesEventObject} evt + */ + /** + * @interface Highcharts.AxisSetExtremesEventObject + * @extends Highcharts.ExtremesObject + */ /** + * @name Highcharts.AxisSetExtremesEventObject#preventDefault + * @type {Function} + */ /** + * @name Highcharts.AxisSetExtremesEventObject#target + * @type {Highcharts.SVGElement} + */ /** + * @name Highcharts.AxisSetExtremesEventObject#trigger + * @type {Highcharts.AxisExtremesTriggerValue|string} + */ /** + * @name Highcharts.AxisSetExtremesEventObject#type + * @type {"setExtremes"} + */ + /** + * @callback Highcharts.AxisTickPositionerCallbackFunction + * + * @param {Highcharts.Axis} this + * + * @return {Highcharts.AxisTickPositionsArray} + */ + /** + * @interface Highcharts.AxisTickPositionsArray + * @augments Array<number> + */ + /** + * @typedef {"high"|"low"|"middle"} Highcharts.AxisTitleAlignValue + */ + /** + * @typedef {Highcharts.XAxisTitleOptions|Highcharts.YAxisTitleOptions|Highcharts.ZAxisTitleOptions} Highcharts.AxisTitleOptions + */ + /** + * @typedef {"linear"|"logarithmic"|"datetime"|"category"|"treegrid"} Highcharts.AxisTypeValue + */ + /** + * The returned object literal from the {@link Highcharts.Axis#getExtremes} + * function. + * + * @interface Highcharts.ExtremesObject + */ /** + * The maximum value of the axis' associated series. + * @name Highcharts.ExtremesObject#dataMax + * @type {number} + */ /** + * The minimum value of the axis' associated series. + * @name Highcharts.ExtremesObject#dataMin + * @type {number} + */ /** + * 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`. + * @name Highcharts.ExtremesObject#max + * @type {number} + */ /** + * 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`. + * @name Highcharts.ExtremesObject#min + * @type {number} + */ /** + * The user defined maximum, either from the `max` option or from a zoom or + * `setExtremes` action. + * @name Highcharts.ExtremesObject#userMax + * @type {number} + */ /** + * The user defined minimum, either from the `min` option or from a zoom or + * `setExtremes` action. + * @name Highcharts.ExtremesObject#userMin + * @type {number} + */ + /** + * Formatter function for the text of a crosshair label. + * + * @callback Highcharts.XAxisCrosshairLabelFormatterCallbackFunction + * + * @param {Highcharts.Axis} this + * Axis context + * + * @param {number} value + * Y value of the data point + * + * @return {string} + */ + var defaultOptions = O.defaultOptions; + var deg2rad = H.deg2rad; + /** + * 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 + * @name Highcharts.Axis + * + * @param {Highcharts.Chart} chart + * The Chart instance to apply the axis on. + * + * @param {Highcharts.AxisOptions} userOptions + * Axis options. + */ + var Axis = /** @class */ (function () { /* * * - * (c) 2010-2020 Torstein Honsi + * Constructors * - * License: www.highcharts.com/license + * */ + function Axis(chart, userOptions) { + this.alternateBands = void 0; + this.bottom = void 0; + this.categories = void 0; + this.chart = void 0; + this.closestPointRange = void 0; + this.coll = void 0; + this.hasNames = void 0; + this.hasVisibleSeries = void 0; + this.height = void 0; + this.isLinked = void 0; + this.labelEdge = void 0; // @todo + this.labelFormatter = void 0; + this.left = void 0; + this.len = void 0; + this.max = void 0; + this.maxLabelLength = void 0; + this.min = void 0; + this.minorTickInterval = void 0; + this.minorTicks = void 0; + this.minPixelPadding = void 0; + this.names = void 0; + this.offset = void 0; + this.oldMax = void 0; + this.oldMin = void 0; + this.options = void 0; + this.overlap = void 0; + this.paddedTicks = void 0; + this.plotLinesAndBands = void 0; + this.plotLinesAndBandsGroups = void 0; + this.pointRange = void 0; + this.pointRangePadding = void 0; + this.pos = void 0; + this.positiveValuesOnly = void 0; + this.right = void 0; + this.series = void 0; + this.side = void 0; + this.tickAmount = void 0; + this.tickInterval = void 0; + this.tickmarkOffset = void 0; + this.tickPositions = void 0; + this.tickRotCorr = void 0; + this.ticks = void 0; + this.top = void 0; + this.transA = void 0; + this.transB = void 0; + this.translationSlope = void 0; + this.userOptions = void 0; + this.visible = void 0; + this.width = void 0; + this.zoomEnabled = void 0; + this.init(chart, userOptions); + } + /* * * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * Functions * * */ - var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, - b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, - b) { d.__proto__ = b; }) || - function (d, - b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; - })(); - var defined = U.defined, - extend = U.extend, - isNumber = U.isNumber, - merge = U.merge, - removeEvent = U.removeEvent; /** - * SVG label to render text. - * @private - * @class - * @name Highcharts.SVGLabel - * @augments Highcharts.SVGElement - */ - var SVGLabel = /** @class */ (function (_super) { - __extends(SVGLabel, _super); - /* * - * - * Constructors - * - * */ - function SVGLabel(renderer, str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { - var _this = _super.call(this) || this; - _this.init(renderer, 'g'); - _this.textStr = str; - _this.x = x; - _this.y = y; - _this.anchorX = anchorX; - _this.anchorY = anchorY; - _this.baseline = baseline; - _this.className = className; - if (className !== 'button') { - _this.addClass('highcharts-label'); - } - if (className) { - _this.addClass('highcharts-' + className); - } - _this.text = renderer.text('', 0, 0, useHTML) - .attr({ - zIndex: 1 - }); - // Validate the shape argument - var hasBGImage; - if (typeof shape === 'string') { - hasBGImage = /^url\((.*?)\)$/.test(shape); - if (_this.renderer.symbols[shape] || hasBGImage) { - _this.symbolKey = shape; - } - } - _this.bBox = SVGLabel.emptyBBox; - _this.padding = 3; - _this.paddingLeft = 0; - _this.baselineOffset = 0; - _this.needsBox = renderer.styledMode || hasBGImage; - _this.deferredAttr = {}; - _this.alignFactor = 0; - return _this; - } - /* * - * - * Functions - * - * */ - SVGLabel.prototype.alignSetter = function (value) { - var alignFactor = { - left: 0, - center: 0.5, - right: 1 - }[value]; - if (alignFactor !== this.alignFactor) { - this.alignFactor = alignFactor; - // Bounding box exists, means we're dynamically changing - if (this.bBox && isNumber(this.xSetting)) { - this.attr({ x: this.xSetting }); // #5134 - } - } - }; - SVGLabel.prototype.anchorXSetter = function (value, key) { - this.anchorX = value; - this.boxAttr(key, Math.round(value) - this.getCrispAdjust() - this.xSetting); - }; - SVGLabel.prototype.anchorYSetter = function (value, key) { - this.anchorY = value; - this.boxAttr(key, value - this.ySetting); - }; - /* - * Set a box attribute, or defer it if the box is not yet created - */ - SVGLabel.prototype.boxAttr = function (key, value) { - if (this.box) { - this.box.attr(key, value); - } - else { - this.deferredAttr[key] = value; - } - }; - /* - * Pick up some properties and apply them to the text instead of the - * wrapper. - */ - SVGLabel.prototype.css = function (styles) { - if (styles) { - var textStyles = {}, - isWidth, - isFontStyle; - // Create a copy to avoid altering the original object - // (#537) - styles = merge(styles); - SVGLabel.textProps.forEach(function (prop) { - if (typeof styles[prop] !== 'undefined') { - textStyles[prop] = styles[prop]; - delete styles[prop]; - } - }); - this.text.css(textStyles); - isWidth = 'width' in textStyles; - isFontStyle = 'fontSize' in textStyles || - 'fontWeight' in textStyles; - // Update existing text, box (#9400, #12163) - if (isWidth || isFontStyle) { - this.updateBoxSize(); - // Keep updated (#9400, #12163) - if (isFontStyle) { - this.updateTextPadding(); - } - } - } - return SVGElement.prototype.css.call(this, styles); - }; - /* - * Destroy and release memory. - */ - SVGLabel.prototype.destroy = function () { - // Added by button implementation - removeEvent(this.element, 'mouseenter'); - removeEvent(this.element, 'mouseleave'); - if (this.text) { - this.text.destroy(); - } - if (this.box) { - this.box = this.box.destroy(); - } - // Call base implementation to destroy the rest - SVGElement.prototype.destroy.call(this); - return void 0; - }; - SVGLabel.prototype.fillSetter = function (value, key) { - if (value) { - this.needsBox = true; - } - // for animation getter (#6776) - this.fill = value; - this.boxAttr(key, value); - }; - /* - * Return the bounding box of the box, not the group. - */ - SVGLabel.prototype.getBBox = function () { - var bBox = this.bBox; - var padding = this.padding; - return { - width: bBox.width + 2 * padding, - height: bBox.height + 2 * padding, - x: bBox.x - padding, - y: bBox.y - padding - }; - }; - SVGLabel.prototype.getCrispAdjust = function () { - return this.renderer.styledMode && this.box ? - this.box.strokeWidth() % 2 / 2 : - (this['stroke-width'] ? parseInt(this['stroke-width'], 10) : 0) % 2 / 2; - }; - SVGLabel.prototype.heightSetter = function (value) { - this.heightSetting = value; - }; - // Event handling. In case of useHTML, we need to make sure that events - // are captured on the span as well, and that mouseenter/mouseleave - // between the SVG group and the HTML span are not treated as real - // enter/leave events. #13310. - SVGLabel.prototype.on = function (eventType, handler) { - var label = this; - var text = label.text; - var span = text && text.element.tagName === 'SPAN' ? text : void 0; - var selectiveHandler; - if (span) { - selectiveHandler = function (e) { - if ((eventType === 'mouseenter' || - eventType === 'mouseleave') && - e.relatedTarget instanceof Element && - (label.element.contains(e.relatedTarget) || - span.element.contains(e.relatedTarget))) { - return; - } - handler.call(label.element, e); - }; - span.on(eventType, selectiveHandler); - } - SVGElement.prototype.on.call(label, eventType, selectiveHandler || handler); - return label; - }; - /* - * After the text element is added, get the desired size of the border - * box and add it before the text in the DOM. - */ - SVGLabel.prototype.onAdd = function () { - var str = this.textStr; - this.text.add(this); - this.attr({ - // Alignment is available now (#3295, 0 not rendered if given - // as a value) - text: (defined(str) ? str : ''), - x: this.x, - y: this.y - }); - if (this.box && defined(this.anchorX)) { - this.attr({ - anchorX: this.anchorX, - anchorY: this.anchorY - }); - } - }; - SVGLabel.prototype.paddingSetter = function (value) { - if (defined(value) && value !== this.padding) { - this.padding = value; - this.updateTextPadding(); - } - }; - SVGLabel.prototype.paddingLeftSetter = function (value) { - if (defined(value) && value !== this.paddingLeft) { - this.paddingLeft = value; - this.updateTextPadding(); - } - }; - SVGLabel.prototype.rSetter = function (value, key) { - this.boxAttr(key, value); - }; - SVGLabel.prototype.shadow = function (b) { - if (b && !this.renderer.styledMode) { - this.updateBoxSize(); - if (this.box) { - this.box.shadow(b); - } - } - return this; - }; - SVGLabel.prototype.strokeSetter = function (value, key) { - // for animation getter (#6776) - this.stroke = value; - this.boxAttr(key, value); - }; - SVGLabel.prototype['stroke-widthSetter'] = function (value, key) { - if (value) { - this.needsBox = true; - } - this['stroke-width'] = value; - this.boxAttr(key, value); - }; - SVGLabel.prototype['text-alignSetter'] = function (value) { - this.textAlign = value; - }; - SVGLabel.prototype.textSetter = function (text) { - if (typeof text !== 'undefined') { - // Must use .attr to ensure transforms are done (#10009) - this.text.attr({ text: text }); - } - this.updateBoxSize(); - this.updateTextPadding(); - }; - /* - * 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. - */ - SVGLabel.prototype.updateBoxSize = function () { - var style = this.text.element.style, - crispAdjust, - attribs = {}; - var padding = this.padding; - var paddingLeft = this.paddingLeft; - // #12165 error when width is null (auto) - // #12163 when fontweight: bold, recalculate bBox withot cache - // #3295 && 3514 box failure when string equals 0 - var bBox = ((!isNumber(this.widthSetting) || !isNumber(this.heightSetting) || this.textAlign) && - defined(this.text.textStr)) ? - this.text.getBBox() : SVGLabel.emptyBBox; - this.width = ((this.widthSetting || bBox.width || 0) + - 2 * padding + - paddingLeft); - this.height = (this.heightSetting || bBox.height || 0) + 2 * padding; - // Update the label-scoped y offset. Math.min because of inline - // style (#9400) - this.baselineOffset = padding + Math.min(this.renderer.fontMetrics(style && style.fontSize, this.text).b, - // When the height is 0, there is no bBox, so go with the font - // metrics. Highmaps CSS demos. - bBox.height || Infinity); - if (this.needsBox) { - // Create the border box if it is not already present - if (!this.box) { - // Symbol definition exists (#5324) - var box = this.box = this.symbolKey ? - this.renderer.symbol(this.symbolKey) : - this.renderer.rect(); - box.addClass(// Don't use label className for buttons - (this.className === 'button' ? '' : 'highcharts-label-box') + - (this.className ? ' highcharts-' + this.className + '-box' : '')); - box.add(this); - crispAdjust = this.getCrispAdjust(); - attribs.x = crispAdjust; - attribs.y = (this.baseline ? -this.baselineOffset : 0) + crispAdjust; - } - // Apply the box attributes - attribs.width = Math.round(this.width); - attribs.height = Math.round(this.height); - this.box.attr(extend(attribs, this.deferredAttr)); - this.deferredAttr = {}; - } - this.bBox = bBox; - }; - /* - * This function runs after setting text or padding, but only if padding - * is changed. - */ - SVGLabel.prototype.updateTextPadding = function () { - var text = this.text; - // Determine y based on the baseline - var textY = this.baseline ? 0 : this.baselineOffset; - var textX = this.paddingLeft + this.padding; - // compensate for alignment - if (defined(this.widthSetting) && - this.bBox && - (this.textAlign === 'center' || this.textAlign === 'right')) { - textX += { center: 0.5, right: 1 }[this.textAlign] * - (this.widthSetting - this.bBox.width); - } - // update if anything changed - if (textX !== text.x || textY !== text.y) { - text.attr('x', textX); - // #8159 - prevent misplaced data labels in treemap - // (useHTML: true) - if (text.hasBoxWidthChanged) { - this.bBox = text.getBBox(true); - this.updateBoxSize(); - } - if (typeof textY !== 'undefined') { - text.attr('y', textY); - } - } - // record current values - text.x = textX; - text.y = textY; - }; - SVGLabel.prototype.widthSetter = function (value) { - // width:auto => null - this.widthSetting = isNumber(value) ? value : void 0; - }; - SVGLabel.prototype.xSetter = function (value) { - this.x = value; // for animation getter - if (this.alignFactor) { - value -= this.alignFactor * ((this.widthSetting || this.bBox.width) + - 2 * this.padding); - // Force animation even when setting to the same value (#7898) - this['forceAnimate:x'] = true; - } - this.xSetting = Math.round(value); - this.attr('translateX', this.xSetting); - }; - SVGLabel.prototype.ySetter = function (value) { - this.ySetting = this.y = Math.round(value); - this.attr('translateY', this.ySetting); - }; - /* * - * - * Static Properties - * - * */ - SVGLabel.emptyBBox = { width: 0, height: 0, x: 0, y: 0 }; - /* * - * - * Properties - * - * */ - /** - * For labels, these CSS properties are applied to the `text` node directly. - * - * @private - * @name Highcharts.SVGLabel#textProps - * @type {Array<string>} - */ - SVGLabel.textProps = [ - 'color', 'cursor', 'direction', 'fontFamily', 'fontSize', 'fontStyle', - 'fontWeight', 'lineHeight', 'textAlign', 'textDecoration', - 'textOutline', 'textOverflow', 'width' - ]; - return SVGLabel; - }(SVGElement)); - - return SVGLabel; - }); - _registerModule(_modules, 'Core/Renderer/SVG/SVGRenderer.js', [_modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Renderer/SVG/SVGLabel.js'], _modules['Core/Utilities.js']], function (Color, H, SVGElement, SVGLabel, U) { - /* * + * Overrideable function to initialize the axis. * - * (c) 2010-2020 Torstein Honsi + * @see {@link Axis} * - * License: www.highcharts.com/license + * @function Highcharts.Axis#init * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @param {Highcharts.Chart} chart + * The Chart instance to apply the axis on. * - * */ - var addEvent = U.addEvent, - attr = U.attr, - createElement = U.createElement, - css = U.css, - defined = U.defined, - destroyObjectProperties = U.destroyObjectProperties, - extend = U.extend, - isArray = U.isArray, - isNumber = U.isNumber, - isObject = U.isObject, - isString = U.isString, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick, - pInt = U.pInt, - splat = U.splat, - uniqueKey = U.uniqueKey; - /** - * 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. + * @param {Highcharts.AxisOptions} userOptions + * Axis options. * - * @example - * var circle = renderer.circle(100, 100, 100) - * .attr({ fill: 'red' }) - * .add(); - * var clipRect = renderer.clipRect(100, 100, 100, 100); + * @fires Highcharts.Axis#event:afterInit + * @fires Highcharts.Axis#event:init + */ + Axis.prototype.init = function (chart, userOptions) { + var isXAxis = userOptions.isX, + axis = this; + /** + * The Chart that the axis belongs to. + * + * @name Highcharts.Axis#chart + * @type {Highcharts.Chart} + */ + axis.chart = chart; + /** + * Whether the axis is horizontal. + * + * @name Highcharts.Axis#horiz + * @type {boolean|undefined} + */ + axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis; + /** + * Whether the axis is the x-axis. + * + * @name Highcharts.Axis#isXAxis + * @type {boolean|undefined} + */ + 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 Highcharts.Axis#coll + * @type {string} + */ + axis.coll = axis.coll || (isXAxis ? "xAxis" : "yAxis"); + fireEvent(this, "init", { userOptions: userOptions }); + 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 Highcharts.Axis#side + * @type {number} + */ + axis.side = + userOptions.side || + (axis.horiz + ? axis.opposite + ? 0 + : 2 // top : bottom + : axis.opposite + ? 1 + : 3); // right : left + /** + * Current options for the axis after merge of defaults and user's + * options. + * + * @name Highcharts.Axis#options + * @type {Highcharts.AxisOptions} + */ + axis.setOptions(userOptions); + var options = this.options, + type = options.type; + axis.labelFormatter = + options.labels.formatter || + // can be overwritten by dynamic format + axis.defaultLabelFormatter; + /** + * User's options for this axis without defaults. + * + * @name Highcharts.Axis#userOptions + * @type {Highcharts.AxisOptions} + */ + 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 Highcharts.Axis#reversed + * @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; + /** + * If categories are present for the axis, names are used instead of + * numbers for that axis. + * + * Since Highcharts 3.0, categories can also be extracted by giving each + * point a name and setting axis type to `category`. However, if you + * have multiple series, best practice remains defining the `categories` + * array. + * + * @see [xAxis.categories](/highcharts/xAxis.categories) + * + * @name Highcharts.Axis#categories + * @type {Array<string>} + * @readonly + */ + axis.categories = options.categories || axis.hasNames; + if (!axis.names) { + // Preserve on update (#3830) + axis.names = []; + axis.names.keys = {}; + } + // Placeholder for plotlines and plotbands groups + axis.plotLinesAndBandsGroups = {}; + // Shorthand types + axis.positiveValuesOnly = !!axis.logarithmic; + // Flag, if axis is linked to another axis + axis.isLinked = defined(options.linkedTo); + /** + * List of major ticks mapped by postition on axis. + * + * @see {@link Highcharts.Tick} + * + * @name Highcharts.Axis#ticks + * @type {Highcharts.Dictionary<Highcharts.Tick>} + */ + axis.ticks = {}; + axis.labelEdge = []; + /** + * List of minor ticks mapped by position on the axis. + * + * @see {@link Highcharts.Tick} + * + * @name Highcharts.Axis#minorTicks + * @type {Highcharts.Dictionary<Highcharts.Tick>} + */ + 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; + /** + * 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 Highcharts.Axis#max + * @type {number|null} + */ + 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 Highcharts.Axis#min + * @type {number|null} + */ + axis.min = null; + /** + * The processed crosshair options. + * + * @name Highcharts.Axis#crosshair + * @type {boolean|Highcharts.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 (chart.axes.indexOf(axis) === -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 Highcharts.Axis#series + * @type {Array<Highcharts.Series>} + */ + axis.series = axis.series || []; // populated by Series + // Reversed axis + if ( + chart.inverted && + !axis.isZAxis && + isXAxis && + typeof axis.reversed === "undefined" + ) { + axis.reversed = true; + } + axis.labelRotation = axis.options.labels.rotation; + // register event listeners + objectEach(events, function (event, eventType) { + if (isFunction(event)) { + addEvent(axis, eventType, event); + } + }); + fireEvent(this, "afterInit"); + }; + /** + * Merge and set options. * - * // Leave only the lower right quarter visible - * circle.clip(clipRect); + * @private + * @function Highcharts.Axis#setOptions * - * @typedef {Highcharts.SVGElement} Highcharts.ClipRectElement - */ - /** - * The font metrics. + * @param {Highcharts.AxisOptions} userOptions + * Axis options. * - * @interface Highcharts.FontMetricsObject - */ /** - * The baseline relative to the top of the box. - * - * @name Highcharts.FontMetricsObject#b - * @type {number} - */ /** - * The font size. - * - * @name Highcharts.FontMetricsObject#f - * @type {number} - */ /** - * The line height. - * - * @name Highcharts.FontMetricsObject#h - * @type {number} - */ - /** - * An object containing `x` and `y` properties for the position of an element. - * - * @interface Highcharts.PositionObject - */ /** - * X position of the element. - * @name Highcharts.PositionObject#x - * @type {number} - */ /** - * Y position of the element. - * @name Highcharts.PositionObject#y - * @type {number} - */ - /** - * A rectangle. - * - * @interface Highcharts.RectangleObject - */ /** - * Height of the rectangle. - * @name Highcharts.RectangleObject#height - * @type {number} - */ /** - * Width of the rectangle. - * @name Highcharts.RectangleObject#width - * @type {number} - */ /** - * Horizontal position of the rectangle. - * @name Highcharts.RectangleObject#x - * @type {number} - */ /** - * Vertical position of the rectangle. - * @name Highcharts.RectangleObject#y - * @type {number} - */ - /** - * The shadow options. - * - * @interface Highcharts.ShadowOptionsObject - */ /** - * The shadow color. - * @name Highcharts.ShadowOptionsObject#color - * @type {Highcharts.ColorString|undefined} - * @default #000000 - */ /** - * The horizontal offset from the element. - * - * @name Highcharts.ShadowOptionsObject#offsetX - * @type {number|undefined} - * @default 1 - */ /** - * The vertical offset from the element. - * @name Highcharts.ShadowOptionsObject#offsetY - * @type {number|undefined} - * @default 1 - */ /** - * The shadow opacity. - * - * @name Highcharts.ShadowOptionsObject#opacity - * @type {number|undefined} - * @default 0.15 - */ /** - * The shadow width or distance from the element. - * @name Highcharts.ShadowOptionsObject#width - * @type {number|undefined} - * @default 3 - */ - /** - * @interface Highcharts.SizeObject - */ /** - * @name Highcharts.SizeObject#height - * @type {number} - */ /** - * @name Highcharts.SizeObject#width - * @type {number} - */ + * @fires Highcharts.Axis#event:afterSetOptions + */ + Axis.prototype.setOptions = function (userOptions) { + this.options = merge( + Axis.defaultOptions, + this.coll === "yAxis" && Axis.defaultYAxisOptions, + [ + Axis.defaultTopAxisOptions, + Axis.defaultRightAxisOptions, + Axis.defaultBottomAxisOptions, + Axis.defaultLeftAxisOptions, + ][this.side], + merge( + // if set in setOptions (#1053): + defaultOptions[this.coll], + userOptions + ) + ); + fireEvent(this, "afterSetOptions", { userOptions: userOptions }); + }; /** - * Serialized form of an SVG definition, including children. Some key - * property names are reserved: tagName, textContent, and children. + * The default label formatter. The context is a special config object for + * the label. In apps, use the + * [labels.formatter](https://api.highcharts.com/highcharts/xAxis.labels.formatter) + * instead, except when a modification is needed. * - * @interface Highcharts.SVGDefinitionObject - */ /** - * @name Highcharts.SVGDefinitionObject#[key:string] - * @type {boolean|number|string|Array<Highcharts.SVGDefinitionObject>|undefined} - */ /** - * @name Highcharts.SVGDefinitionObject#children - * @type {Array<Highcharts.SVGDefinitionObject>|undefined} - */ /** - * @name Highcharts.SVGDefinitionObject#tagName - * @type {string|undefined} - */ /** - * @name Highcharts.SVGDefinitionObject#textContent - * @type {string|undefined} - */ - /** - * Array of path commands, that will go into the `d` attribute of an SVG - * element. + * @function Highcharts.Axis#defaultLabelFormatter * - * @typedef {Array<(Array<Highcharts.SVGPathCommand>|Array<Highcharts.SVGPathCommand,number>|Array<Highcharts.SVGPathCommand,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number,number,number,number>)>} Highcharts.SVGPathArray - */ + * @param {Highcharts.AxisLabelsFormatterContextObject<number>|Highcharts.AxisLabelsFormatterContextObject<string>} this + * Formatter context of axis label. + * + * @return {string} + * The formatted label content. + */ + Axis.prototype.defaultLabelFormatter = function () { + var axis = this.axis, + value = isNumber(this.value) ? this.value : NaN, + 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.logarithmic + ? Math.abs(value) + : axis.tickInterval; + var chart = this.chart; + var numberFormatter = chart.numberFormatter; + if (formatOption) { + ret = format(formatOption, this, chart); + } else if (categories) { + ret = "" + this.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-- && typeof 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 = numberFormatter(value / multi, -1) + numericSymbols[i]; + } + } + } + if (typeof ret === "undefined") { + if (Math.abs(value) >= 10000) { + // add thousands separators + ret = numberFormatter(value, -1); + } else { + // small numbers + ret = numberFormatter(value, -1, void 0, ""); // #2466 + } + } + return ret; + }; /** - * Possible path commands in an SVG path array. Valid values are `A`, `C`, `H`, - * `L`, `M`, `Q`, `S`, `T`, `V`, `Z`. + * Get the minimum and maximum for the series of each axis. The function + * analyzes the axis series and updates `this.dataMin` and `this.dataMax`. * - * @typedef {string} Highcharts.SVGPathCommand - * @validvalue ["a","c","h","l","m","q","s","t","v","z","A","C","H","L","M","Q","S","T","V","Z"] - */ + * @private + * @function Highcharts.Axis#getSeriesExtremes + * + * @fires Highcharts.Axis#event:afterGetSeriesExtremes + * @fires Highcharts.Axis#event:getSeriesExtremes + */ + Axis.prototype.getSeriesExtremes = function () { + var axis = this, + chart = axis.chart, + xExtremes; + fireEvent(this, "getSeriesExtremes", null, function () { + axis.hasVisibleSeries = false; + // Reset properties in case we're redrawing (#3353) + axis.dataMin = axis.dataMax = axis.threshold = null; + axis.softThreshold = !axis.isXAxis; + if (axis.stacking) { + axis.stacking.buildStacks(); + } + // loop through this axis' series + axis.series.forEach(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) { + var isPositive = function (number) { + return number > 0; + }; + xData = axis.logarithmic + ? xData.filter(axis.validatePositiveValue) + : xData; + xExtremes = series.getXExtremes(xData); + // 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 = xExtremes.min; + seriesDataMax = xExtremes.max; + if ( + !isNumber(seriesDataMin) && + // #5010: + !(seriesDataMin instanceof Date) + ) { + xData = xData.filter(isNumber); + xExtremes = series.getXExtremes(xData); + // Do it again with valid data + seriesDataMin = xExtremes.min; + seriesDataMax = xExtremes.max; + } + if (xData.length) { + axis.dataMin = Math.min( + pick(axis.dataMin, seriesDataMin), + seriesDataMin + ); + axis.dataMax = Math.max( + pick(axis.dataMax, seriesDataMax), + seriesDataMax + ); + } + } + // Get dataMin and dataMax for Y axes, as well as handle + // stacking and processed data + } else { + // Get this particular series extremes + var dataExtremes = series.applyExtremes(); + // 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 (isNumber(dataExtremes.dataMin)) { + seriesDataMin = dataExtremes.dataMin; + axis.dataMin = Math.min( + pick(axis.dataMin, seriesDataMin), + seriesDataMin + ); + } + if (isNumber(dataExtremes.dataMax)) { + seriesDataMax = dataExtremes.dataMax; + 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; + } + } + } + }); + }); + fireEvent(this, "afterGetSeriesExtremes"); + }; /** - * An extendable collection of functions for defining symbol paths. Symbols are - * used internally for point markers, button and label borders and backgrounds, - * or custom shapes. Extendable by adding to {@link SVGRenderer#symbols}. + * Translate from axis value to pixel position on the chart, or back. Use + * the `toPixels` and `toValue` functions in applications. * - * @interface Highcharts.SymbolDictionary - */ /** - * @name Highcharts.SymbolDictionary#[key:string] - * @type {Function|undefined} - */ /** - * @name Highcharts.SymbolDictionary#arc - * @type {Function|undefined} - */ /** - * @name Highcharts.SymbolDictionary#callout - * @type {Function|undefined} - */ /** - * @name Highcharts.SymbolDictionary#circle - * @type {Function|undefined} - */ /** - * @name Highcharts.SymbolDictionary#diamond - * @type {Function|undefined} - */ /** - * @name Highcharts.SymbolDictionary#square - * @type {Function|undefined} - */ /** - * @name Highcharts.SymbolDictionary#triangle - * @type {Function|undefined} - */ - /** - * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`, `triangle`, - * and `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}. + * @private + * @function Highcharts.Axis#translate + * + * @param {number} val + * TO-DO: parameter description + * + * @param {boolean|null} [backwards] + * TO-DO: parameter description + * + * @param {boolean|null} [cvsCoord] + * TO-DO: parameter description + * + * @param {boolean|null} [old] + * TO-DO: parameter description + * + * @param {boolean} [handleLog] + * TO-DO: parameter description + * + * @param {number} [pointPlacement] + * TO-DO: parameter description + * + * @return {number|undefined} + */ + Axis.prototype.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 = 0, + minPixelPadding = axis.minPixelPadding, + doPostTranslate = + (axis.isOrdinal || + (axis.brokenAxis && axis.brokenAxis.hasBreaks) || + (axis.logarithmic && 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; + // from chart pixel to value: + returnValue = val / localA + localMin; + 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) + : void 0; + } + return returnValue; + }; + /** + * Translate a value in terms of axis units into pixels within the chart. + * + * @function Highcharts.Axis#toPixels * - * @typedef {"arc"|"callout"|"circle"|"diamond"|"square"|"triangle"|"triangle-down"} Highcharts.SymbolKeyValue + * @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. */ + Axis.prototype.toPixels = function (value, paneCoordinates) { + return ( + this.translate(value, false, !this.horiz, null, true) + + (paneCoordinates ? 0 : this.pos) + ); + }; /** - * Additional options, depending on the actual symbol drawn. + * Translate a pixel position along the axis to a value in terms of axis + * units. * - * @interface Highcharts.SymbolOptionsObject - */ /** - * The anchor X position for the `callout` symbol. This is where the chevron - * points to. - * - * @name Highcharts.SymbolOptionsObject#anchorX - * @type {number|undefined} - */ /** - * The anchor Y position for the `callout` symbol. This is where the chevron - * points to. - * - * @name Highcharts.SymbolOptionsObject#anchorY - * @type {number|undefined} - */ /** - * The end angle of an `arc` symbol. - * - * @name Highcharts.SymbolOptionsObject#end - * @type {number|undefined} - */ /** - * Whether to draw `arc` symbol open or closed. - * - * @name Highcharts.SymbolOptionsObject#open - * @type {boolean|undefined} - */ /** - * The radius of an `arc` symbol, or the border radius for the `callout` symbol. - * - * @name Highcharts.SymbolOptionsObject#r - * @type {number|undefined} - */ /** - * The start angle of an `arc` symbol. - * - * @name Highcharts.SymbolOptionsObject#start - * @type {number|undefined} - */ - /* eslint-disable no-invalid-this, valid-jsdoc */ - var charts = H.charts, - deg2rad = H.deg2rad, - doc = H.doc, - isFirefox = H.isFirefox, - isMS = H.isMS, - isWebKit = H.isWebKit, - noop = H.noop, - svg = H.svg, - SVG_NS = H.SVG_NS, - symbolSizes = H.symbolSizes, - win = H.win; - /** - * 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. - * - * @sample highcharts/members/renderer-on-chart - * Annotating a chart programmatically. - * @sample highcharts/members/renderer-basic - * Independent SVG drawing. + * @function Highcharts.Axis#toValue * - * @example - * // Use directly without a chart object. - * var renderer = new Highcharts.Renderer(parentNode, 600, 400); + * @param {number} pixel + * The pixel value coordinate. * - * @class - * @name Highcharts.SVGRenderer + * @param {boolean} [paneCoordinates=false] + * Whether the input pixel is relative to the chart or just the axis/pane + * itself. * - * @param {Highcharts.HTMLDOMElement} container - * Where to put the SVG in the web page. + * @return {number} + * The axis value. + */ + Axis.prototype.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. + * + * @function Highcharts.Axis#getPlotLinePath + * + * @param {Highcharts.AxisPlotLinePathOptionsObject} options + * Options for the path. + * + * @return {Highcharts.SVGPathArray|null} + * The SVG path definition for the plot line. + */ + Axis.prototype.getPlotLinePath = function (options) { + var axis = this, + chart = axis.chart, + axisLeft = axis.left, + axisTop = axis.top, + old = options.old, + value = options.value, + translatedValue = options.translatedValue, + lineWidth = options.lineWidth, + force = options.force, + x1, + y1, + x2, + y2, + cHeight = (old && chart.oldChartHeight) || chart.chartHeight, + cWidth = (old && chart.oldChartWidth) || chart.chartWidth, + skip, + transB = axis.transB, + evt; + // eslint-disable-next-line valid-jsdoc + /** + * Check if x is between a and b. If not, either move to a/b + * or skip, depending on the force parameter. + * @private + */ + function between(x, a, b) { + if ((force !== "pass" && x < a) || x > b) { + if (force) { + x = clamp(x, a, b); + } else { + skip = true; + } + } + return x; + } + evt = { + value: value, + lineWidth: lineWidth, + old: old, + force: force, + acrossPanes: options.acrossPanes, + translatedValue: translatedValue, + }; + fireEvent(this, "getPlotLinePath", evt, function (e) { + translatedValue = pick( + translatedValue, + axis.translate(value, null, null, old) + ); + // Keep the translated value within sane bounds, and avoid Infinity + // to fail the isNumber test (#7709). + translatedValue = clamp(translatedValue, -1e5, 1e5); + 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); + } + e.path = + skip && !force + ? null + : chart.renderer.crispLine( + [ + ["M", x1, y1], + ["L", x2, y2], + ], + lineWidth || 1 + ); + }); + return evt.path; + }; + /** + * Internal function to et the tick positions of a linear axis to round + * values like whole tens or every five. + * + * @function Highcharts.Axis#getLinearTickPositions + * + * @param {number} tickInterval + * The normalized tick interval. + * + * @param {number} min + * Axis minimum. + * + * @param {number} max + * Axis maximum. + * + * @return {Array<number>} + * An array of axis values where ticks should be placed. + */ + Axis.prototype.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. * - * @param {number} width - * The width of the SVG. + * @function Highcharts.Axis#getMinorTickInterval * - * @param {number} height - * The height of the SVG. + * @return {number|"auto"|null} + */ + Axis.prototype.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. + * + * @function Highcharts.Axis#getMinorTickPositions + * + * @return {Array<number>} + * An array of axis values where ticks should be placed. + */ + Axis.prototype.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 + var logarithmic_1 = axis.logarithmic; + if (logarithmic_1) { + // For each interval in the major ticks, compute the minor ticks + // separately. + this.paddedTicks.forEach(function (_pos, i, paddedTicks) { + if (i) { + minorTickPositions.push.apply( + minorTickPositions, + logarithmic_1.getLogTickPositions( + minorTickInterval, + paddedTicks[i - 1], + paddedTicks[i], + true + ) + ); + } + }); + } else if ( + axis.dateTime && + this.getMinorTickInterval() === "auto" + ) { + // #1314 + minorTickPositions = minorTickPositions.concat( + axis.getTimeTicks( + axis.dateTime.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. * - * @param {Highcharts.CSSObject} [style] - * The box style, if not in styleMode + * @private + * @function Highcharts.Axis#adjustForMinRange + */ + Axis.prototype.adjustForMinRange = function () { + var axis = this, + options = axis.options, + min = axis.min, + max = axis.max, + log = axis.logarithmic, + zoomOffset, + spaceAvailable, + closestDataRange, + i, + distance, + xData, + loopLength, + minArgs, + maxArgs, + minRange; + // Set the automatic minimum range based on the closest point distance + if (axis.isXAxis && typeof axis.minRange === "undefined" && !log) { + 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) + axis.series.forEach(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 ( + typeof 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.logarithmic + ? axis.logarithmic.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] = log ? log.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; + }; + // eslint-disable-next-line valid-jsdoc + /** + * Find the closestPointRange across all series. * - * @param {boolean} [forExport=false] - * Whether the rendered content is intended for export. + * @private + * @function Highcharts.Axis#getClosest + */ + Axis.prototype.getClosest = function () { + var ret; + if (this.categories) { + ret = 1; + } else { + this.series.forEach(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 + * @function Highcharts.Axis#nameToX * - * @param {boolean} [allowHTML=true] - * Whether the renderer is allowed to include HTML text, which will be - * projected on top of the SVG. + * @param {Highcharts.Point} point + * The point to inspect. * - * @param {boolean} [styledMode=false] - * Whether the renderer belongs to a chart that is in styled mode. - * If it does, it will avoid setting presentational attributes in - * some cases, but not when set explicitly through `.attr` and `.css` - * etc. - */ - var SVGRenderer = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function SVGRenderer(container, width, height, style, forExport, allowHTML, styledMode) { - /* * - * - * Properties - * - * */ - this.alignedObjects = void 0; - /** - * The root `svg` node of the renderer. - * - * @name Highcharts.SVGRenderer#box - * @type {Highcharts.SVGDOMElement} - */ - this.box = void 0; - /** - * The wrapper for the root `svg` node of the renderer. - * - * @name Highcharts.SVGRenderer#boxWrapper - * @type {Highcharts.SVGElement} - */ - this.boxWrapper = void 0; - this.cache = void 0; - this.cacheKeys = void 0; - this.chartIndex = void 0; - /** - * A pointer to the `defs` node of the root SVG. - * - * @name Highcharts.SVGRenderer#defs - * @type {Highcharts.SVGElement} - */ - this.defs = void 0; - this.globalAnimation = void 0; - this.gradients = void 0; - this.height = void 0; - this.imgCount = void 0; - this.isSVG = void 0; - this.style = void 0; - /** - * Page url used for internal references. - * - * @private - * @name Highcharts.SVGRenderer#url - * @type {string} - */ - this.url = void 0; - this.width = void 0; - this.init(container, width, height, style, forExport, allowHTML, styledMode); + * @return {number} + * The X value that the point is given. + */ + Axis.prototype.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() + : explicitCategories + ? names.indexOf(point.name) + : pick(names.keys[point.name], -1); + } + if (nameX === -1) { + // Not found in currenct categories + if (!explicitCategories) { + x = names.length; } - /* * - * - * Functions - * - * */ - /** - * Initialize the SVGRenderer. Overridable initializer function that takes - * the same parameters as the constructor. - * - * @function Highcharts.SVGRenderer#init - * - * @param {Highcharts.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 {Highcharts.CSSObject} [style] - * The box style, if not in styleMode - * - * @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. - * - * @param {boolean} [styledMode=false] - * Whether the renderer belongs to a chart that is in styled mode. If it - * does, it will avoid setting presentational attributes in some cases, but - * not when set explicitly through `.attr` and `.css` etc. - */ - SVGRenderer.prototype.init = function (container, width, height, style, forExport, allowHTML, styledMode) { - var renderer = this, - boxWrapper, - element, - desc; - boxWrapper = renderer.createElement('svg') - .attr({ - version: '1.1', - 'class': 'highcharts-root' - }); - if (!styledMode) { - boxWrapper.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; - this.box = element; - this.boxWrapper = boxWrapper; - renderer.alignedObjects = []; - // #24, #672, #1070 - this.url = ((isFirefox || isWebKit) && - doc.getElementsByTagName('base').length) ? - win.location.href - .split('#')[0] // 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 8.2.2')); - renderer.defs = this.createElement('defs').add(); - renderer.allowHTML = allowHTML; - renderer.forExport = forExport; - renderer.styledMode = styledMode; - 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); + } else { + x = nameX; + } + // Write the last point's name to the names array + if (typeof x !== "undefined") { + this.names[x] = point.name; + // Backwards mapping is much faster than array searching (#7725) + this.names.keys[point.name] = x; + } + return x; + }; + /** + * When changes have been done to series data, update the axis.names. + * + * @private + * @function Highcharts.Axis#updateNames + */ + Axis.prototype.updateNames = function () { + var axis = this, + names = this.names, + i = names.length; + if (i > 0) { + Object.keys(names.keys).forEach(function (key) { + delete names.keys[key]; + }); + names.length = 0; + this.minRange = this.userMinRange; // Reset + (this.series || []).forEach(function (series) { + // Reset incrementer (#5928) + series.xIncrement = null; + // When adding a series, points are not yet generated + if (!series.points || series.isDirtyData) { + // When we're updating the series with data that is longer + // than it was, and cropThreshold is passed, we need to make + // sure that the axis.max is increased _before_ running the + // premature processData. Otherwise this early iteration of + // processData will crop the points to axis.max, and the + // names array will be too short (#5857). + axis.max = Math.max(axis.max, series.xData.length - 1); + series.processData(); + series.generatePoints(); + } + series.data.forEach(function (point, i) { + var x; + if ( + point && + point.options && + typeof point.name !== "undefined" // #9562 + ) { + x = axis.nameToX(point); + if (typeof x !== "undefined" && x !== point.x) { + point.x = x; + series.xData[i] = x; + } } - }; - /** - * General method for adding a definition to the SVG `defs` tag. Can be used - * for gradients, fills, filters etc. Styled mode only. A hook for adding - * general definitions to the SVG's defs tag. Definitions can be referenced - * from the CSS by its `id`. Read more in - * [gradients, shadows and patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns). - * Styled mode only. - * - * @function Highcharts.SVGRenderer#definition - * - * @param {Highcharts.SVGDefinitionObject} def - * A serialized form of an SVG definition, including children. - * - * @return {Highcharts.SVGElement} - * The inserted node. - */ - SVGRenderer.prototype.definition = function (def) { - var ren = this; - /** - * @private - * @param {Highcharts.SVGDefinitionObject} config - SVG definition - * @param {Highcharts.SVGElement} [parent] - parent node - */ - function recurse(config, parent) { - var ret; - splat(config).forEach(function (item) { - var node = ren.createElement(item.tagName), - attr = {}; - // Set attributes - objectEach(item, function (val, key) { - if (key !== 'tagName' && - key !== 'children' && - key !== 'textContent') { - attr[key] = val; - } - }); - node.attr(attr); - // Add to the tree - node.add(parent || ren.defs); - // Add text content - if (item.textContent) { - node.element.appendChild(doc.createTextNode(item.textContent)); - } - // Recurse - recurse(item.children || [], node); - ret = node; - }); - // Return last node added (on top level it's the only one) - return ret; - } - return recurse(def); - }; - /** - * Get the global style setting for the renderer. - * - * @private - * @function Highcharts.SVGRenderer#getStyle - * - * @param {Highcharts.CSSObject} style - * Style settings. - * - * @return {Highcharts.CSSObject} - * The style settings mixed with defaults. - */ - SVGRenderer.prototype.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. - * - * @function Highcharts.SVGRenderer#setStyle - * - * @param {Highcharts.CSSObject} style - * CSS to apply. - */ - SVGRenderer.prototype.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. - * - * @function Highcharts.SVGRenderer#isHidden - * - * @return {boolean} - * True if it is hidden. - */ - SVGRenderer.prototype.isHidden = function () { - return !this.boxWrapper.getBBox().width; - }; - /** - * Destroys the renderer and its allocated members. - * - * @function Highcharts.SVGRenderer#destroy - * - * @return {null} - */ - SVGRenderer.prototype.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}. - * - * @function Highcharts.SVGRenderer#createElement - * - * @param {string} nodeName - * The node name, for example `rect`, `g` etc. - * - * @return {Highcharts.SVGElement} - * The generated SVGElement. - */ - SVGRenderer.prototype.createElement = function (nodeName) { - var wrapper = new this.Element(); - wrapper.init(this, nodeName); - return wrapper; - }; - /** - * Get converted radial gradient attributes according to the radial - * reference. Used internally from the {@link SVGElement#colorGradient} - * function. - * - * @private - * @function Highcharts.SVGRenderer#getRadialAttr - */ - SVGRenderer.prototype.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] - }; - }; - /** - * Truncate the text node contents to a given length. Used when the css - * width is set. If the `textOverflow` is `ellipsis`, the text is truncated - * character by character to the given length. If not, the text is - * word-wrapped line by line. - * - * @private - * @function Highcharts.SVGRenderer#truncate - * - * @return {boolean} - * True if tspan is too long. - */ - SVGRenderer.prototype.truncate = function (wrapper, tspan, text, words, startAt, width, getString) { - var renderer = this, - rotation = wrapper.rotation, - str, - // Word wrap can not be truncated to shorter than one word, ellipsis - // text can be completely blank. - minIndex = words ? 1 : 0, - maxIndex = (text || words).length, - currentIndex = maxIndex, - // Cache the lengths to avoid checking the same twice - lengths = [], - updateTSpan = function (s) { - if (tspan.firstChild) { - tspan.removeChild(tspan.firstChild); - } - if (s) { - tspan.appendChild(doc.createTextNode(s)); - } - }, getSubStringLength = function (charEnd, concatenatedEnd) { - // charEnd is useed when finding the character-by-character - // break for ellipsis, concatenatedEnd is used for word-by-word - // break for word wrapping. - var end = concatenatedEnd || charEnd; - if (typeof lengths[end] === 'undefined') { - // Modern browsers - if (tspan.getSubStringLength) { - // Fails with DOM exception on unit-tests/legend/members - // of unknown reason. Desired width is 0, text content - // is "5" and end is 1. - try { - lengths[end] = startAt + - tspan.getSubStringLength(0, words ? end + 1 : end); - } - catch (e) { - ''; - } - // Legacy - } - else if (renderer.getSpanWidth) { // #9058 jsdom - updateTSpan(getString(text || words, charEnd)); - lengths[end] = startAt + - renderer.getSpanWidth(wrapper, tspan); - } - } - return lengths[end]; - }, actualWidth, truncated; - wrapper.rotation = 0; // discard rotation when computing box - actualWidth = getSubStringLength(tspan.textContent.length); - truncated = startAt + actualWidth > width; - if (truncated) { - // Do a binary search for the index where to truncate the text - while (minIndex <= maxIndex) { - currentIndex = Math.ceil((minIndex + maxIndex) / 2); - // When checking words for word-wrap, we need to build the - // string and measure the subStringLength at the concatenated - // word length. - if (words) { - str = getString(words, currentIndex); - } - actualWidth = getSubStringLength(currentIndex, str && str.length - 1); - 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 the shortest possible text was also - // too large. For ellipsis that means only the ellipsis, while for - // word wrap it means the whole first word. - if (maxIndex === 0) { - // Remove ellipsis - updateTSpan(''); - // If the new text length is one less than the original, we don't - // need the ellipsis - } - else if (!(text && maxIndex === text.length - 1)) { - updateTSpan(str || getString(text || words, currentIndex)); - } - } - // When doing line wrapping, prepare for the next line by removing the - // items from this line. - if (words) { - words.splice(0, currentIndex); - } - wrapper.actualWidth = actualWidth; - wrapper.rotation = rotation; // Apply rotation again. - return truncated; - }; - /** - * 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 - * @function Highcharts.SVGRenderer#buildText - * - * @param {Highcharts.SVGElement} wrapper - * The parent SVGElement. - */ - SVGRenderer.prototype.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, truncated, 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; - if (!renderer.styledMode) { - 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, except) { - objectEach(renderer.escapes, function (value, key) { - if (!except || except.indexOf(value) === -1) { - inputStr = inputStr.toString().replace(new RegExp(value, 'g'), key); - } - }); - return inputStr; - }, parseAttribute = function (s, attr) { - var start, - delimiter; - start = s.indexOf('<'); - s = s.substring(start, s.indexOf('>') - start); - start = s.indexOf(attr + '='); - if (start !== -1) { - start = start + attr.length + 1; - delimiter = s.charAt(start); - if (delimiter === '"' || delimiter === "'") { // eslint-disable-line quotes - s = s.substring(start + 1); - return s.substring(0, s.indexOf(delimiter)); - } - } - }; - var regexMatchBreaks = /<br.*?>/g; - // 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 || - (noWrap && !regexMatchBreaks.test(textStr)))) { - textNode.appendChild(doc.createTextNode(unescapeEntities(textStr))); - // Complex strings, add more logic - } - else { - if (tempParent) { - // attach it to the DOM to read offset width - tempParent.appendChild(textNode); - } - if (hasMarkup) { - lines = renderer.styledMode ? (textStr - .replace(/<(b|strong)>/g, '<span class="highcharts-strong">') - .replace(/<(i|em)>/g, '<span class="highcharts-emphasized">')) : (textStr - .replace(/<(b|strong)>/g, '<span style="font-weight:bold">') - .replace(/<(i|em)>/g, '<span style="font-style:italic">')); - lines = lines - .replace(/<a/g, '<span') - .replace(/<\/(b|strong|i|em|a)>/g, '</span>') - .split(regexMatchBreaks); - } - else { - lines = [textStr]; - } - // Trim empty lines (#5261) - lines = lines.filter(function (line) { - return line !== ''; - }); - // build the lines - lines.forEach(function (line, lineNo) { - var spans, - spanNo = 0, - lineLength = 0; - line = line - // Trim to prevent useless/costly process on the spaces - // (#5258) - .replace(/^\s+|\s+$/g, '') - .replace(/<span/g, '|||<span') - .replace(/<\/span>/g, '</span>|||'); - spans = line.split('|||'); - spans.forEach(function buildTextSpans(span) { - if (span !== '' || spans.length === 1) { - var attributes = {}, - tspan = doc.createElementNS(renderer.SVG_NS, 'tspan'), - a, - classAttribute, - styleAttribute, // #390 - hrefAttribute; - classAttribute = parseAttribute(span, 'class'); - if (classAttribute) { - attr(tspan, 'class', classAttribute); - } - styleAttribute = parseAttribute(span, 'style'); - if (styleAttribute) { - styleAttribute = styleAttribute.replace(/(;| |^)color([ :])/, '$1fill$2'); - attr(tspan, 'style', styleAttribute); - } - // For anchors, wrap the tspan in an <a> tag and apply - // the href attribute as is (#13559). Not for export - // (#1529) - hrefAttribute = parseAttribute(span, 'href'); - if (hrefAttribute && !forExport) { - if ( - // Stop JavaScript links, vulnerable to XSS - hrefAttribute.split(':')[0].toLowerCase() - .indexOf('javascript') === -1) { - a = doc.createElementNS(renderer.SVG_NS, 'a'); - attr(a, 'href', hrefAttribute); - attr(tspan, 'class', 'highcharts-anchor'); - a.appendChild(tspan); - if (!renderer.styledMode) { - 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(a || 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)); - } - // Check width and apply soft breaks or ellipsis - if (width) { - var words = span.replace(/([^\^])-/g, '$1- ').split(' '), // #1273 - hasWhiteSpace = !noWrap && (spans.length > 1 || - lineNo || - words.length > 1), - wrapLineNo = 0, - dy = getLineHeight(tspan); - if (ellipsis) { - truncated = renderer.truncate(wrapper, tspan, span, void 0, 0, - // Target width - Math.max(0, - // Substract the font face to make - // room for the ellipsis itself - width - parseInt(fontSize || 12, 10)), - // Build the text to test for - function (text, currentIndex) { - return text.substring(0, currentIndex) + '\u2026'; - }); - } - else if (hasWhiteSpace) { - while (words.length) { - // For subsequent lines, create tspans - // with the same style attributes as the - // parent text node. - if (words.length && - !noWrap && - wrapLineNo > 0) { - tspan = doc.createElementNS(SVG_NS, 'tspan'); - attr(tspan, { - dy: dy, - x: parentX - }); - if (styleAttribute) { // #390 - attr(tspan, 'style', styleAttribute); - } - // Start by appending the full - // remaining text - tspan.appendChild(doc.createTextNode(words.join(' ') - .replace(/- /g, '-'))); - textNode.appendChild(tspan); - } - // For each line, truncate the remaining - // words into the line length. - renderer.truncate(wrapper, tspan, null, words, wrapLineNo === 0 ? lineLength : 0, width, - // Build the text to test for - function (text, currentIndex) { - return words - .slice(0, currentIndex) - .join(' ') - .replace(/- /g, '-'); - }); - lineLength = wrapper.actualWidth; - wrapLineNo++; - } - } - } - spanNo++; - } - } - }); - // To avoid beginning lines that doesn't add to the textNode - // (#6144) - isSubsequentLine = (isSubsequentLine || - textNode.childNodes.length); - }); - if (ellipsis && truncated) { - wrapper.attr('title', unescapeEntities(wrapper.textStr || '', ['<', '>']) // #7179 - ); - } - if (tempParent) { - tempParent.removeChild(textNode); - } - // Apply the text outline - if (isString(textOutline) && wrapper.applyTextOutline) { - wrapper.applyTextOutline(textOutline); - } - } - }; - /** - * Returns white for dark colors and black for bright colors. - * - * @function Highcharts.SVGRenderer#getContrast - * - * @param {Highcharts.ColorString} rgba - * The color to get the contrast for. - * - * @return {Highcharts.ColorString} - * The contrast color, either `#000000` or `#FFFFFF`. - */ - SVGRenderer.prototype.getContrast = function (rgba) { - rgba = Color.parse(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.5; // blue - return rgba[0] + rgba[1] + rgba[2] > - 1.8 * 255 ? - '#000000' : - '#FFFFFF'; - }; - /** - * Create a button with preset states. - * - * @function Highcharts.SVGRenderer#button - * - * @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 {Highcharts.EventCallbackFunction<Highcharts.SVGElement>} callback - * The function to execute on button click or touch. - * - * @param {Highcharts.SVGAttributes} [normalState] - * SVG attributes for the normal state. - * - * @param {Highcharts.SVGAttributes} [hoverState] - * SVG attributes for the hover state. - * - * @param {Highcharts.SVGAttributes} [pressedState] - * SVG attributes for the pressed state. - * - * @param {Highcharts.SVGAttributes} [disabledState] - * SVG attributes for the disabled state. - * - * @param {Highcharts.SymbolKeyValue} [shape=rect] - * The shape type. - * - * @param {boolean} [useHTML=false] - * Wether to use HTML to render the label. - * - * @return {Highcharts.SVGElement} - * The button element. - */ - SVGRenderer.prototype.button = function (text, x, y, callback, normalState, hoverState, pressedState, disabledState, shape, useHTML) { - var label = this.label(text, - x, - y, - shape, - void 0, - void 0, - useHTML, - void 0, 'button'), - curState = 0, - styledMode = this.styledMode, - // Make a copy of normalState (#13798) - // (reference to options.rangeSelector.buttonTheme) - normalState = normalState ? merge(normalState) : normalState, - userNormalStyle = normalState && normalState.style || {}; - // Remove stylable attributes - if (normalState && normalState.style) { - delete normalState.style; - } - // Default, non-stylable attributes - label.attr(merge({ padding: 8, r: 2 }, normalState)); - if (!styledMode) { - // 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' - } - }, { - style: userNormalStyle - }, 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]); - if (!styledMode) { - label - .attr([ - normalState, - hoverState, - pressedState, - disabledState - ][state || 0]) - .css([ - normalStyle, - hoverStyle, - pressedStyle, - disabledStyle - ][state || 0]); - } - }; - // Presentational attributes - if (!styledMode) { - 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. - * - * @function Highcharts.SVGRenderer#crispLine - * - * @param {Highcharts.SVGPathArray} points - * The original points on the format `[['M', 0, 0], ['L', 100, 0]]`. - * - * @param {number} width - * The width of the line. - * - * @param {string} roundingFunction - * The rounding function name on the `Math` object, can be one of - * `round`, `floor` or `ceil`. - * - * @return {Highcharts.SVGPathArray} - * The original points array, but modified to render crisply. - */ - SVGRenderer.prototype.crispLine = function (points, width, roundingFunction) { - if (roundingFunction === void 0) { roundingFunction = 'round'; } - var start = points[0]; - var end = points[1]; - // Normalize to a crisp line - if (start[1] === end[1]) { - // Substract due to #1129. Now bottom and left axis gridlines behave - // the same. - start[1] = end[1] = - Math[roundingFunction](start[1]) - (width % 2 / 2); - } - if (start[2] === end[2]) { - start[2] = end[2] = - Math[roundingFunction](start[2]) + (width % 2 / 2); - } - return points; - }; - /** - * Draw a path, wraps the SVG `path` 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 - * - * @example - * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z']) - * .attr({ stroke: '#ff00ff' }) - * .add(); - * - * @function Highcharts.SVGRenderer#path - * - * @param {Highcharts.SVGPathArray} [path] - * An SVG path definition in array form. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - * - */ /** - * Draw a path, wraps the SVG `path` element. - * - * @function Highcharts.SVGRenderer#path - * - * @param {Highcharts.SVGAttributes} [attribs] - * The initial attributes. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ - SVGRenderer.prototype.path = function (path) { - var attribs = (this.styledMode ? {} : { - 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. - * - * @sample highcharts/members/renderer-circle/ - * Drawing a circle - * - * @function Highcharts.SVGRenderer#circle - * - * @param {number} [x] - * The center x position. - * - * @param {number} [y] - * The center y position. - * - * @param {number} [r] - * The radius. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ /** - * Draw a circle, wraps the SVG `circle` element. - * - * @function Highcharts.SVGRenderer#circle - * - * @param {Highcharts.SVGAttributes} [attribs] - * The initial attributes. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ - SVGRenderer.prototype.circle = function (x, y, r) { - var attribs = (isObject(x) ? - x : - typeof x === 'undefined' ? {} : { 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. - * - * @sample highcharts/members/renderer-arc/ - * Drawing an arc - * - * @function Highcharts.SVGRenderer#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. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ /** - * Draw and return an arc. Overloaded function that takes arguments object. - * - * @function Highcharts.SVGRenderer#arc - * - * @param {Highcharts.SVGAttributes} attribs - * Initial SVG attributes. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ - SVGRenderer.prototype.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; + }); + }); + } + }; + /** + * Update translation information. + * + * @private + * @function Highcharts.Axis#setAxisTranslation + * + * @param {boolean} [saveOld] + * TO-DO: parameter description + * + * @fires Highcharts.Axis#event:afterSetAxisTranslation + */ + Axis.prototype.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 { + axis.series.forEach(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 || hasCategories) { + // TODO: series should internally set x- and y- + // pointPlacement to simplify this logic. + var isPointPlacementAxis = series.is("xrange") + ? !isXAxis + : isXAxis; + // 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, + isPointPlacementAxis && 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, + isPointPlacementAxis && pointPlacement === "on" + ? 0 + : seriesPointRange + ); + } + }); + } + // Record minPointOffset and pointRangePadding + ordinalCorrection = + axis.ordinal && axis.ordinal.slope && closestPointRange + ? axis.ordinal.slope / 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, + axis.single && hasCategories ? 1 : 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.staticScale || axis.len / (range + pointRangePadding || 1); + // Translation addend + axis.transB = axis.horiz ? axis.left : axis.bottom; + axis.minPixelPadding = transA * minPointOffset; + fireEvent(this, "afterSetAxisTranslation"); + }; + /** + * @private + * @function Highcharts.Axis#minFromRange + * + * @return {number} + */ + Axis.prototype.minFromRange = function () { + var axis = this; + return axis.max - axis.range; + }; + /** + * Set the tick positions to round values and optionally extend the extremes + * to the nearest tick. + * + * @private + * @function Highcharts.Axis#setTickInterval + * + * @param {boolean} secondPass + * TO-DO: parameter description + * + * @fires Highcharts.Axis#event:foundExtremes + */ + Axis.prototype.setTickInterval = function (secondPass) { + var axis = this, + chart = axis.chart, + log = axis.logarithmic, + options = axis.options, + 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 = isNumber(axis.threshold) ? axis.threshold : null, + softThreshold = axis.softThreshold, + thresholdMin, + thresholdMax, + hardMin, + hardMax; + if (!axis.dateTime && !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) { + // Can't link axes of different type + error(11, 1, chart); + } + // 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 (log) { + if ( + axis.positiveValuesOnly && + !secondPass && + Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0 + ) { + // #978 + // Can't plot negative values on log axis + error(10, 1, chart); + } + // 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(log.log2lin(axis.min), 16); + axis.max = correctFloat(log.log2lin(axis.max), 16); + } + // handle zoomed range + if (axis.range && defined(axis.max)) { + // #618, #6773: + axis.userMin = + axis.min = + hardMin = + Math.max(axis.dataMin, axis.minFromRange()); + 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.stacking && axis.stacking.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(axis.userMin)) { + if (isNumber(options.softMin) && options.softMin < axis.min) { + axis.min = hardMin = options.softMin; // #6894 + } + if (isNumber(options.floor)) { + axis.min = Math.max(axis.min, options.floor); + } + } + if (!isNumber(axis.userMax)) { + if (isNumber(options.softMax) && options.softMax > axis.max) { + axis.max = hardMax = options.softMax; // #6894 + } + 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 = axis.options.minRange + ? Math.min(threshold, axis.max - axis.minRange) + : threshold; + } else if ( + !defined(hardMax) && + axis.max > threshold && + axis.dataMax <= threshold + ) { + axis.max = axis.options.minRange + ? Math.max(threshold, axis.min + axis.minRange) + : threshold; + } + } + // get tickInterval + if ( + axis.min === axis.max || + typeof axis.min === "undefined" || + typeof 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) + : void 0, + // 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) { + axis.series.forEach(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 + fireEvent(this, "initialAxisTranslation"); + // 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, + // In datetime axes, don't go below the data interval, except when + // there are scatter-like series involved (#13369). + axis.dateTime && + !axis.series.some(function (s) { + return s.noSharedTooltip; + }) + ? axis.closestPointRange + : 0 + ); + if (!tickIntervalOption && axis.tickInterval < minTickInterval) { + axis.tickInterval = minTickInterval; + } + // for linear axes, get magnitude and normalize the interval + if (!axis.dateTime && !axis.logarithmic && !tickIntervalOption) { + axis.tickInterval = normalizeTickInterval( + axis.tickInterval, + void 0, + getMagnitude(axis.tickInterval), + pick( + options.allowDecimals, + // If the tick interval is greather than 0.5, avoid + // decimals, as linear axes are often used to render + // discrete values. #3363. If a tick amount is set, allow + // decimals by default, as it increases the chances for a + // good fit. + axis.tickInterval < 0.5 || this.tickAmount !== void 0 + ), + !!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. + * + * @private + * @function Highcharts.Axis#setTickPositions + * + * @fires Highcharts.Axis#event:afterSetTickPositions + */ + Axis.prototype.setTickPositions = function () { + var axis = this, + options = this.options, + tickPositions, + tickPositionsOption = options.tickPositions, + minorTickIntervalOption = this.getMinorTickInterval(), + tickPositioner = options.tickPositioner, + hasVerticalPanning = this.hasVerticalPanning(), + isColorAxis = this.coll === "colorAxis", + startOnTick = + (isColorAxis || !hasVerticalPanning) && options.startOnTick, + endOnTick = + (isColorAxis || !hasVerticalPanning) && 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); + /** + * Contains the current positions that are laid out on the axis. The + * positions are numbers in terms of axis values. In a category axis + * they are integers, in a datetime axis they are also integers, but + * designating milliseconds. + * + * This property is read only - for modifying the tick positions, use + * the `tickPositioner` callback or [axis.tickPositions( + * https://api.highcharts.com/highcharts/xAxis.tickPositions) option + * instead. + * + * @name Highcharts.Axis#tickPositions + * @type {Highcharts.AxisTickPositionsArray|undefined} + */ + this.tickPositions = + // Find the tick positions. Work on a copy (#1565) + tickPositions = tickPositionsOption && tickPositionsOption.slice(); + if (!tickPositions) { + // Too many ticks (#6405). Create a friendly warning and provide two + // ticks so at least we can show the data series. + if ( + (!axis.ordinal || !axis.ordinal.positions) && + (this.max - this.min) / this.tickInterval > + Math.max(2 * this.len, 200) + ) { + tickPositions = [this.min, this.max]; + error(19, false, this.chart); + } else if (axis.dateTime) { + tickPositions = axis.getTimeTicks( + axis.dateTime.normalizeTimeTickInterval( + this.tickInterval, + options.units + ), + this.min, + this.max, + options.startOfWeek, + axis.ordinal && axis.ordinal.positions, + this.closestPointRange, + true + ); + } else if (axis.logarithmic) { + tickPositions = axis.logarithmic.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(axis, [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.categories && + !this.series.some(function (s) { + return ( + s.is("heatmap") && s.options.pointPlacement === "between" + ); + }) + ) { + this.min -= 0.5; + this.max += 0.5; + } + if (!tickPositionsOption && !tickPositioner) { + this.adjustTickAmount(); + } + } + fireEvent(this, "afterSetTickPositions"); + }; + /** + * Handle startOnTick and endOnTick by either adapting to padding min/max or + * rounded min/max. Also handle single data points. + * + * @private + * @function Highcharts.Axis#trimTicks + * + * @param {Array<number>} tickPositions + * TO-DO: parameter description + * + * @param {boolean} [startOnTick] + * TO-DO: parameter description + * + * @param {boolean} [endOnTick] + * TO-DO: parameter description + */ + Axis.prototype.trimTicks = function ( + tickPositions, + startOnTick, + endOnTick + ) { + var roundedMin = tickPositions[0], + roundedMax = tickPositions[tickPositions.length - 1], + minPointOffset = (!this.isOrdinal && this.minPointOffset) || 0; // (#12716) + fireEvent(this, "trimTicks"); + 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 + * @function Highcharts.Axis#alignToOthers + * + * @return {boolean|undefined} + * True if there are other axes. + */ + Axis.prototype.alignToOthers = function () { + var axis = this, + others = + // Whether there is another axis to pair with this one + {}, + hasOther, + options = axis.options; + if ( + // Only if alignTicks is true + this.chart.options.chart.alignTicks !== false && + options.alignTicks !== false && + // Disabled when startOnTick or endOnTick are false (#7604) + options.startOnTick !== false && + options.endOnTick !== false && + // Don't try to align ticks on a log axis, they are not evenly + // spaced (#6021) + !axis.logarithmic + ) { + this.chart[this.coll].forEach(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 + * @function Highcharts.Axis#getTickAmount + */ + Axis.prototype.getTickAmount = function () { + var axis = this, + options = this.options, + tickAmount = options.tickAmount, + tickPixelInterval = options.tickPixelInterval; + if ( + !defined(options.tickInterval) && + !tickAmount && + this.len < tickPixelInterval && + !this.isRadial && + !axis.logarithmic && + 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 + * @function Highcharts.Axis#adjustTickAmount + */ + Axis.prototype.adjustTickAmount = function () { + var axis = this, + axisOptions = axis.options, + tickInterval = axis.tickInterval, + tickPositions = axis.tickPositions, + tickAmount = axis.tickAmount, + finalTickAmt = axis.finalTickAmt, + currentTickAmount = tickPositions && tickPositions.length, + threshold = pick(axis.threshold, axis.softThreshold ? 0 : null), + min, + len, + i; + if (axis.hasData()) { + if (currentTickAmount < tickAmount) { + min = axis.min; + while (tickPositions.length < tickAmount) { + // Extend evenly for both sides unless we're on the + // threshold (#3965) + if (tickPositions.length % 2 || min === threshold) { + // to the end + tickPositions.push( + correctFloat( + tickPositions[tickPositions.length - 1] + tickInterval + ) + ); + } else { + // to the start + tickPositions.unshift( + correctFloat(tickPositions[0] - tickInterval) + ); + } + } + axis.transA *= (currentTickAmount - 1) / (tickAmount - 1); + // Do not crop when ticks are not extremes (#9841) + axis.min = axisOptions.startOnTick + ? tickPositions[0] + : Math.min(axis.min, tickPositions[0]); + axis.max = axisOptions.endOnTick + ? tickPositions[tickPositions.length - 1] + : Math.max(axis.max, tickPositions[tickPositions.length - 1]); + // We have too many ticks, run second pass to try to reduce ticks + } else if (currentTickAmount > tickAmount) { + axis.tickInterval *= 2; + axis.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); } - 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. - * - * @function Highcharts.SVGRenderer#rect - * - * @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. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ /** - * Draw and return a rectangle. - * - * @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 - * - * @function Highcharts.SVGRenderer#rect - * - * @param {Highcharts.SVGAttributes} [attributes] - * General SVG attributes for the rectangle. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ - SVGRenderer.prototype.rect = function (x, y, width, height, r, strokeWidth) { - r = isObject(x) ? x.r : r; - var wrapper = this.createElement('rect'), - attribs = isObject(x) ? - x : - typeof x === 'undefined' ? - {} : - { - x: x, - y: y, - width: Math.max(width, 0), - height: Math.max(height, 0) - }; - if (!this.styledMode) { - if (typeof strokeWidth !== 'undefined') { - attribs.strokeWidth = strokeWidth; - attribs = wrapper.crisp(attribs); - } - attribs.fill = 'none'; - } - if (r) { - attribs.r = r; - } - wrapper.rSetter = function (value, key, element) { - wrapper.r = value; - attr(element, { - rx: value, - ry: value - }); - }; - wrapper.rGetter = function () { - return wrapper.r; - }; - return wrapper.attr(attribs); - }; - /** - * Resize the {@link SVGRenderer#box} and re-align all aligned child - * elements. - * - * @sample highcharts/members/renderer-g/ - * Show and hide grouped objects - * - * @function Highcharts.SVGRenderer#setSize - * - * @param {number} width - * The new pixel width. - * - * @param {number} height - * The new pixel height. - * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animate=true] - * Whether and how to animate. - */ - SVGRenderer.prototype.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) ? void 0 : 0 + } + axis.finalTickAmt = void 0; + } + } + }; + /** + * Set the scale based on data min and max, user set min and max or options. + * + * @private + * @function Highcharts.Axis#setScale + * + * @fires Highcharts.Axis#event:afterSetScale + */ + Axis.prototype.setScale = function () { + var axis = this, + isDirtyAxisLength, + isDirtyData = false, + isXAxisDirty = false; + axis.series.forEach(function (series) { + var _a; + isDirtyData = isDirtyData || series.isDirtyData || series.isDirty; + // When x axis is dirty, we need new data extremes for y as + // well: + isXAxisDirty = + isXAxisDirty || + ((_a = series.xAxis) === null || _a === void 0 + ? void 0 + : _a.isDirty) || + false; + }); + axis.oldMin = axis.min; + axis.oldMax = axis.max; + axis.oldAxisLength = axis.len; + // set the new axisLength + axis.setAxisSize(); + isDirtyAxisLength = axis.len !== axis.oldAxisLength; + // do we really need to go through all this? + if ( + isDirtyAxisLength || + isDirtyData || + isXAxisDirty || + axis.isLinked || + axis.forceRedraw || + axis.userMin !== axis.oldUserMin || + axis.userMax !== axis.oldUserMax || + axis.alignToOthers() + ) { + if (axis.stacking) { + axis.stacking.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.stacking) { + axis.stacking.cleanStacks(); + } + // Recalculate panning state object, when the data + // has changed. It is required when vertical panning is enabled. + if (isDirtyData && axis.panningState) { + axis.panningState.isDirty = true; + } + fireEvent(this, "afterSetScale"); + }; + /** + * 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. + * + * @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 + * + * @function Highcharts.Axis#setExtremes + * + * @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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] + * Enable or modify animations. + * + * @param {*} [eventArguments] + * Arguments to be accessed in event handler. + * + * @fires Highcharts.Axis#event:setExtremes + */ + Axis.prototype.setExtremes = function ( + newMin, + newMax, + redraw, + animation, + eventArguments + ) { + var axis = this, + chart = axis.chart; + redraw = pick(redraw, true); // defaults to true + axis.series.forEach(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 + * @function Highcharts.Axis#zoom + * + * @param {number} newMin + * TO-DO: parameter description + * + * @param {number} newMax + * TO-DO: parameter description + * + * @return {boolean} + */ + Axis.prototype.zoom = function (newMin, newMax) { + var axis = this, + 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)), + evt = { + newMin: newMin, + newMax: newMax, + }; + fireEvent(this, "zoom", evt, function (e) { + // Use e.newMin and e.newMax - event handlers may have altered them + var newMin = e.newMin, + newMax = e.newMax; + if (newMin !== axis.min || newMax !== axis.max) { + // #5790 + // Prevent pinch zooming out of range. Check for defined is for + // #1946. #1734. + if (!axis.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 + axis.displayBtn = + typeof newMin !== "undefined" || typeof newMax !== "undefined"; + // Do it + axis.setExtremes(newMin, newMax, false, void 0, { + trigger: "zoom", + }); + } + e.zoomed = true; + }); + return evt.zoomed; + }; + /** + * Update the axis metrics. + * + * @private + * @function Highcharts.Axis#setAxisSize + */ + Axis.prototype.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( + relativeLength( + pick(options.width, chart.plotWidth - offsets[3] + offsets[1]), + chart.plotWidth + ) + )), + height = (this.height = Math.round( + relativeLength( + pick( + options.height, + chart.plotHeight - offsets[0] + offsets[2] + ), + chart.plotHeight + ) + )), + top = (this.top = Math.round( + relativeLength( + pick(options.top, chart.plotTop + offsets[0]), + chart.plotHeight, + chart.plotTop + ) + )), + left = (this.left = Math.round( + 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 + }; + /** + * Get the current extremes for the axis. + * + * @sample highcharts/members/axis-getextremes/ + * Report extremes by click on a button + * @sample maps/members/axis-getextremes/ + * Get extremes in Highmaps + * + * @function Highcharts.Axis#getExtremes + * + * @return {Highcharts.ExtremesObject} + * An object containing extremes information. + */ + Axis.prototype.getExtremes = function () { + var axis = this; + var log = axis.logarithmic; + return { + min: log ? correctFloat(log.lin2log(axis.min)) : axis.min, + max: log ? correctFloat(log.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. + * + * @function Highcharts.Axis#getThreshold + * + * @param {number} threshold + * The threshold in axis values. + * + * @return {number|undefined} + * The translated threshold position in terms of pixels, and corrected to + * stay within the axis bounds. + */ + Axis.prototype.getThreshold = function (threshold) { + var axis = this, + log = axis.logarithmic, + realMin = log ? log.lin2log(axis.min) : axis.min, + realMax = log ? log.lin2log(axis.max) : axis.max; + if (threshold === null || threshold === -Infinity) { + threshold = realMin; + } else if (threshold === Infinity) { + threshold = realMax; + } 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. + * + * @private + * @function Highcharts.Axis#autoLabelAlign + * + * @param {number} rotation + * The rotation in degrees as set by either the `rotation` or `autoRotation` + * options. + * + * @return {Highcharts.AlignValue} + * Can be `"center"`, `"left"` or `"right"`. + */ + Axis.prototype.autoLabelAlign = function (rotation) { + var angle = (pick(rotation, 0) - this.side * 90 + 720) % 360, + evt = { align: "center" }; + fireEvent(this, "autoLabelAlign", evt, function (e) { + if (angle > 15 && angle < 165) { + e.align = "right"; + } else if (angle > 195 && angle < 345) { + e.align = "left"; + } + }); + return evt.align; + }; + /** + * Get the tick length and width for the axis based on axis options. + * @private + * @function Highcharts.Axis#tickSize + * + * @param {string} [prefix] + * 'tick' or 'minorTick' + * + * @return {Array<number,number>|undefined} + * An array of tickLength and tickWidth + */ + Axis.prototype.tickSize = function (prefix) { + var options = this.options, + tickLength = + options[prefix === "tick" ? "tickLength" : "minorTickLength"], + tickWidth = pick( + options[prefix === "tick" ? "tickWidth" : "minorTickWidth"], + // Default to 1 on linear and datetime X axes + prefix === "tick" && this.isXAxis && !this.categories ? 1 : 0 + ), + e, + tickSize; + if (tickWidth && tickLength) { + // Negate the length + if (options[prefix + "Position"] === "inside") { + tickLength = -tickLength; + } + tickSize = [tickLength, tickWidth]; + } + e = { tickSize: tickSize }; + fireEvent(this, "afterTickSize", e); + return e.tickSize; + }; + /** + * Return the size of the labels. + * + * @private + * @function Highcharts.Axis#labelMetrics + * + * @return {Highcharts.FontMetricsObject} + */ + Axis.prototype.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 + * @function Highcharts.Axis#unsquish + * + * @return {number} + */ + Axis.prototype.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, + range = this.max - this.min, + // 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; + // Guard for very small or negative angles (#9835) + if ( + step * tickInterval > range && + spaceNeeded !== Infinity && + slotSize !== Infinity && + range + ) { + step = Math.ceil(range / tickInterval); + } + return correctFloat(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. + autoRotation.forEach(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 + * @function Highcharts.Axis#getSlotWidth + * + * @param {Highcharts.Tick} [tick] Optionally, calculate the slot width + * basing on tick label. It is used in highcharts-3d module, where the slots + * has different widths depending on perspective angles. + * + * @return {number} + * The pixel width allocated to each axis label. + */ + Axis.prototype.getSlotWidth = function (tick) { + var _a; + // #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]; + // Used by grid axis + if (tick && isNumber(tick.slotWidth)) { + // #13221, can be 0 + return tick.slotWidth; + } + if (horiz && labelOptions && (labelOptions.step || 0) < 2) { + if (labelOptions.rotation) { + // #4415 + return 0; + } + return ((this.staggerLines || 1) * this.len) / slotCount; + } + if (!horiz) { + // #7028 + var cssWidth = + (_a = + labelOptions === null || labelOptions === void 0 + ? void 0 + : labelOptions.style) === null || _a === void 0 + ? void 0 + : _a.width; + if (cssWidth !== void 0) { + return parseInt(cssWidth, 10); + } + if (marginLeft) { + return marginLeft - chart.spacing[3]; + } + } + // Last resort, a fraction of the available size + return chart.chartWidth * 0.33; + }; + /** + * Render the axis labels and determine whether ellipsis or rotation need to + * be applied. + * + * @private + * @function Highcharts.Axis#renderUnsquish + */ + Axis.prototype.renderUnsquish = function () { + var chart = this.chart, + renderer = chart.renderer, + tickPositions = this.tickPositions, + ticks = this.ticks, + labelOptions = this.options.labels, + labelStyleOptions = (labelOptions && labelOptions.style) || {}, + 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, + commonWidth, + commonTextOverflow, + maxLabelLength = 0, + label, + i, + pos; + // Set rotation option unless it is "auto", like in gauges + if (!isString(labelOptions.rotation)) { + // #4443: + attr.rotation = labelOptions.rotation || 0; + } + // Get the longest label length + tickPositions.forEach(function (tick) { + tick = ticks[tick]; + // Replace label - sorting animation + if (tick.movedLabel) { + tick.replaceMovedLabel(); + } + if ( + tick && + tick.label && + tick.label.textPxLength > maxLabelLength + ) { + maxLabelLength = tick.label.textPxLength; + } + }); + 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 + commonWidth = innerWidth; + if (!textOverflowOption) { + commonTextOverflow = "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 (label.textPxLength > slotWidth) { + label.css({ width: slotWidth + "px" }); + } + if ( + label.getBBox().height > + this.len / tickPositions.length - + (labelMetrics.h - labelMetrics.f) + ) { + label.specificTextOverflow = "ellipsis"; + } + } + } + } + } + // Add ellipsis if the label length is significantly longer than ideal + if (attr.rotation) { + commonWidth = + maxLabelLength > chart.chartHeight * 0.5 + ? chart.chartHeight * 0.33 + : maxLabelLength; + if (!textOverflowOption) { + commonTextOverflow = "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 + tickPositions.forEach(function (pos) { + var tick = ticks[pos], + label = tick && tick.label, + widthOption = labelStyleOptions.width, + css = {}; + if (label) { + // This needs to go before the CSS in old IE (#4502) + label.attr(attr); + if (tick.shortenLabel) { + tick.shortenLabel(); + } else if ( + commonWidth && + !widthOption && + // Setting width in this case messes with the bounding box + // (#7975) + labelStyleOptions.whiteSpace !== "nowrap" && + // Speed optimizing, #7656 + (commonWidth < label.textPxLength || + // Resetting CSS, #4928 + label.element.tagName === "SPAN") + ) { + css.width = commonWidth + "px"; + if (!textOverflowOption) { + css.textOverflow = + label.specificTextOverflow || commonTextOverflow; + } + label.css(css); + // Reset previously shortened label (#8210) + } else if ( + label.styles && + label.styles.width && + !css.width && + !widthOption + ) { + label.css({ width: null }); + } + delete label.specificTextOverflow; + tick.rotation = attr.rotation; + } + }, this); + // 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. + * + * @function Highcharts.Axis#hasData + * + * @return {boolean} + * True if the axis has associated visible series and those series have + * either valid data points or explicit `min` and `max` settings. + */ + Axis.prototype.hasData = function () { + return ( + this.series.some(function (s) { + return s.hasData(); + }) || + (this.options.showEmpty && defined(this.min) && defined(this.max)) + ); + }; + /** + * Adds the title defined in axis.options.title. + * + * @function Highcharts.Axis#addTitle + * + * @param {boolean} [display] + * Whether or not to display the title. + */ + Axis.prototype.addTitle = function (display) { + var axis = this, + renderer = axis.chart.renderer, + horiz = axis.horiz, + opposite = axis.opposite, + options = axis.options, + axisTitleOptions = options.title, + textAlign, + styledMode = axis.chart.styledMode; + 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"); + // #7814, don't mutate style option + if (!styledMode) { + axis.axisTitle.css(merge(axisTitleOptions.style)); + } + axis.axisTitle.add(axis.axisGroup); + axis.axisTitle.isNew = true; + } + // Max width defaults to the length of the axis + if (!styledMode && !axisTitleOptions.style.width && !axis.isRadial) { + axis.axisTitle.css({ + width: axis.len + "px", + }); + } + // hide or show the title depending on whether showEmpty is set + axis.axisTitle[display ? "show" : "hide"](display); + }; + /** + * Generates a tick for initial positioning. + * + * @private + * @function Highcharts.Axis#generateTick + * + * @param {number} pos + * The tick position in axis values. + * + * @param {number} [i] + * The index of the tick in {@link Axis.tickPositions}. + */ + Axis.prototype.generateTick = function (pos) { + var axis = this; + var ticks = axis.ticks; + if (!ticks[pos]) { + ticks[pos] = new Tick(axis, pos); + } else { + ticks[pos].addLabel(); // update labels depending on tick interval + } + }; + /** + * Render the tick labels to a preliminary position to get their sizes + * + * @private + * @function Highcharts.Axis#getOffset + * + * @fires Highcharts.Axis#event:afterGetOffset + */ + Axis.prototype.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; + // 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 + tickPositions.forEach(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 + ) + ) { + tickPositions.forEach(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] ? axisOffset[side] + (options.margin || 0) : 0 + ); + 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); + if (axis.getMaxLabelDimensions) { + axis.maxLabelDimensions = axis.getMaxLabelDimensions( + ticks, + tickPositions + ); + } + // Due to GridAxis.tickSize, tickSize should be calculated after ticks + // has rendered. + var tickSize = this.tickSize("tick"); + axisOffset[side] = Math.max( + axisOffset[side], + axis.axisTitleMargin + titleOffset + directionFactor * axis.offset, + labelOffsetPadded, // #3027 + tickPositions && 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 + : // #4308, #4371: + Math.floor(axis.axisLine.strokeWidth() / 2) * 2; + clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip); + fireEvent(this, "afterGetOffset"); + }; + /** + * Internal function to get the path for the axis line. Extended for polar + * charts. + * + * @function Highcharts.Axis#getLinePath + * + * @param {number} lineWidth + * The line width in pixels. + * + * @return {Highcharts.SVGPathArray} + * The SVG path definition in array form. + */ + Axis.prototype.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. + * + * @function Highcharts.Axis#renderLine + */ + Axis.prototype.renderLine = function () { + if (!this.axisLine) { + this.axisLine = this.chart.renderer + .path() + .addClass("highcharts-axis-line") + .add(this.axisGroup); + if (!this.chart.styledMode) { + this.axisLine.attr({ + stroke: this.options.lineColor, + "stroke-width": this.options.lineWidth, + zIndex: 7, + }); + } + } + }; + /** + * Position the axis title. + * + * @private + * @function Highcharts.Axis#getTitlePosition + * + * @return {Highcharts.PositionObject} + * X and Y positions for the title. + */ + Axis.prototype.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, + textHeightOvershoot, + fontMetrics.f, + -textHeightOvershoot, // left + ][this.side], + titlePosition = { + x: horiz + ? alongAxis + xOption + : offAxis + (opposite ? this.width : 0) + offset + xOption, + y: horiz + ? offAxis + yOption - (opposite ? this.height : 0) + offset + : alongAxis + yOption, + }; + fireEvent(this, "afterGetTitlePosition", { + titlePosition: titlePosition, + }); + return titlePosition; + }; + /** + * Render a minor tick into the given position. If a minor tick already + * exists in this position, move it. + * + * @function Highcharts.Axis#renderMinorTick + * + * @param {number} pos + * The position in axis values. + */ + Axis.prototype.renderMinorTick = function (pos) { + var axis = this; + var slideInTicks = axis.chart.hasRendered && isNumber(axis.oldMin); + var minorTicks = axis.minorTicks; + if (!minorTicks[pos]) { + minorTicks[pos] = new Tick(axis, 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. + * + * @function Highcharts.Axis#renderTick + * + * @param {number} pos + * The position in axis values. + * + * @param {number} i + * The tick index. + */ + Axis.prototype.renderTick = function (pos, i) { + var _a; + var axis = this; + var isLinked = axis.isLinked; + var ticks = axis.ticks; + var slideInTicks = axis.chart.hasRendered && isNumber(axis.oldMin); + // Linked axes need an extra check to find out if + if ( + !isLinked || + (pos >= axis.min && pos <= axis.max) || + ((_a = axis.grid) === null || _a === void 0 ? void 0 : _a.isColumn) + ) { + if (!ticks[pos]) { + ticks[pos] = new Tick(axis, pos); + } + // NOTE this seems like overkill. Could be handled in tick.render by + // setting old position in attr, then set new position in animate. + // render new ticks in old position + if (slideInTicks && ticks[pos].isNew) { + // Start with negative opacity so that it is visible from + // halfway into the animation + ticks[pos].render(i, true, -1); + } + ticks[pos].render(i); + } + }; + /** + * Render the axis. + * + * @private + * @function Highcharts.Axis#render + * + * @fires Highcharts.Axis#event:afterRender + */ + Axis.prototype.render = function () { + var axis = this, + chart = axis.chart, + log = axis.logarithmic, + renderer = chart.renderer, + options = axis.options, + 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 + [ticks, minorTicks, alternateBands].forEach(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) { + axis.getMinorTickPositions().forEach(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 + tickPositions.forEach(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) { + tickPositions.forEach(function (pos, i) { + to = + typeof 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]) { + // Should be imported from PlotLineOrBand.js, but + // the dependency cycle with axis is a problem + alternateBands[pos] = new H.PlotLineOrBand(axis); + } + from = pos + tickmarkOffset; // #949 + alternateBands[pos].options = { + from: log ? log.lin2log(from) : from, + to: log ? log.lin2log(to) : to, + color: alternateGridColor, + className: "highcharts-alternate-grid", + }; + alternateBands[pos].render(); + alternateBands[pos].isActive = true; + } + }); + } + // custom plot lines and bands + if (!axis._addedPlotLB) { + // only first time + (options.plotLines || []) + .concat(options.plotBands || []) + .forEach(function (plotLineOptions) { + axis.addPlotBandOrLine(plotLineOptions); }); + axis._addedPlotLB = true; + } + } // end if hasData + // Remove inactive ticks + [ticks, minorTicks, alternateBands].forEach(function (coll) { + var i, + forDestruction = [], + delay = animation.duration, + destroyInactiveItems = function () { + i = forDestruction.length; 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()}. - * - * @function Highcharts.SVGRenderer#g - * - * @param {string} [name] - * The group will be given a class name of `highcharts-{name}`. This - * can be used for styling and scripting. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ - SVGRenderer.prototype.g = function (name) { - var elem = this.createElement('g'); - return name ? - elem.attr({ 'class': 'highcharts-' + name }) : - elem; - }; - /** - * Display an image. - * - * @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 - * - * @function Highcharts.SVGRenderer#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. - * - * @param {Function} [onload] - * Event handler for image load. - * - * @return {Highcharts.SVGElement} - * The generated wrapper element. - */ - SVGRenderer.prototype.image = function (src, x, y, width, height, onload) { - var attribs = { preserveAspectRatio: 'none' }, elemWrapper, dummy, setSVGImageSource = function (el, src) { - // Set the href in the xlink namespace - if (el.setAttributeNS) { - el.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 - el.setAttribute('hc-svg-href', src); - } - }, onDummyLoad = function (e) { - setSVGImageSource(elemWrapper.element, src); - onload.call(elemWrapper, e); - }; - // optional properties - if (arguments.length > 1) { - extend(attribs, { - x: x, - y: y, - width: width, - height: height - }); - } - elemWrapper = this.createElement('image').attr(attribs); - // Add load event if supplied - if (onload) { - // We have to use a dummy HTML image since IE support for SVG image - // load events is very buggy. First set a transparent src, wait for - // dummy to load, and then add the real src to the SVG image. - setSVGImageSource(elemWrapper.element, 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* eslint-disable-line */); - dummy = new win.Image(); - addEvent(dummy, 'load', onDummyLoad); - dummy.src = src; - if (dummy.complete) { - onDummyLoad({}); - } - } - else { - setSVGImageSource(elemWrapper.element, 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. - * - * @function Highcharts.SVGRenderer#symbol - * - * @param {string} 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 {Highcharts.SymbolOptionsObject} [options] - * Additional options, depending on the actual symbol drawn. - * - * @return {Highcharts.SVGElement} - */ - SVGRenderer.prototype.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]), - path, - imageSrc, - centerImage; - if (symbolFn) { - // Check if there's a path defined for this symbol - if (typeof x === 'number') { - path = symbolFn.call(this.symbols, Math.round(x || 0), Math.round(y || 0), width || 0, height || 0, options); - } - obj = this.path(path); - if (!ren.styledMode) { - 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. - */ - ['width', 'height'].forEach(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)) { - // Scale and center the image within its container. - // The name `backgroundSize` is taken from the CSS spec, - // but the value `within` is made up. Other possible - // values in the spec, `cover` and `contain`, can be - // implemented if needed. - if (options && - options.backgroundSize === 'within' && - this.width && - this.height) { - imgSize = Math.round(imgSize * Math.min(this.width / this.imgwidth, this.height / this.imgheight)); - } - 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] = { - 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.hasLoaded) { - chart.onload(); - } - }, - src: imageSrc - }); - this.imgCount++; - } - } - return obj; - }; - /** - * Define a clipping rectangle. The clipping rectangle is later applied - * to {@link SVGElement} objects through 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); - * - * @function Highcharts.SVGRenderer#clipRect - * - * @param {number} [x] - * - * @param {number} [y] - * - * @param {number} [width] - * - * @param {number} [height] - * - * @return {Highcharts.ClipRectElement} - * A clipping rectangle. - */ - SVGRenderer.prototype.clipRect = function (x, y, width, height) { - var wrapper, - // Add a hyphen at the end to avoid confusion in testing indexes - // -1 and -10, -11 etc (#6550) - id = 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' })`. - * - * @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 - * - * @function Highcharts.SVGRenderer#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 {Highcharts.SVGElement} - * The text object. - */ - SVGRenderer.prototype.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 (defined(str)) { - 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. - * - * @function Highcharts.SVGRenderer#fontMetrics - * - * @param {number|string} [fontSize] - * The current font size to inspect. If not given, the font size - * will be found from the DOM element. - * - * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement} [elem] - * The element to inspect for a current font size. - * - * @return {Highcharts.FontMetricsObject} - * The font metrics. - */ - SVGRenderer.prototype.fontMetrics = function (fontSize, elem) { - var lineHeight, - baseline; - if ((this.styledMode || !/px/.test(fontSize)) && - win.getComputedStyle // old IE doesn't support it - ) { - fontSize = elem && SVGElement.prototype.getStyle.call(elem, 'font-size'); - } - else { - 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 { - fontSize = 12; - } - // Empirical values found by comparing font size and bounding box - // height. Applies to the default font family. - // https://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 - * @function Highcharts.SVGRenderer#rotCorr - * - * @param {number} baseline - * - * @param {number} rotation - * - * @param {boolean} [alterY] - * - * @param {Highcharts.PositionObject} - */ - SVGRenderer.prototype.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 - }; - }; - /** - * Compatibility function to convert the legacy one-dimensional path array - * into an array of segments. - * - * It is used in maps to parse the `path` option, and in SVGRenderer.dSetter - * to support legacy paths from demos. - * - * @private - * @function Highcharts.SVGRenderer#pathToSegments - */ - SVGRenderer.prototype.pathToSegments = function (path) { - var ret = []; - var segment = []; - var commandLength = { - A: 8, - C: 7, - H: 2, - L: 3, - M: 3, - Q: 5, - S: 5, - T: 3, - V: 2 - }; - // Short, non-typesafe parsing of the one-dimensional array. It splits - // the path on any string. This is not type checked against the tuple - // types, but is shorter, and doesn't require specific checks for any - // command type in SVG. - for (var i = 0; i < path.length; i++) { - // Command skipped, repeat previous or insert L/l for M/m - if (isString(segment[0]) && - isNumber(path[i]) && - segment.length === commandLength[(segment[0].toUpperCase())]) { - path.splice(i, 0, segment[0].replace('M', 'L').replace('m', 'l')); - } - // Split on string - if (typeof path[i] === 'string') { - if (segment.length) { - ret.push(segment.slice(0)); - } - segment.length = 0; - } - segment.push(path[i]); - } - ret.push(segment.slice(0)); - return ret; - /* - // Fully type-safe version where each tuple type is checked. The - // downside is filesize and a lack of flexibility for unsupported - // commands - const ret: SVGPath = [], - commands = { - A: 7, - C: 6, - H: 1, - L: 2, - M: 2, - Q: 4, - S: 4, - T: 2, - V: 1, - Z: 0 - }; - - let i = 0, - lastI = 0, - lastCommand; - - while (i < path.length) { - const item = path[i]; - - let command; - - if (typeof item === 'string') { - command = item; - i += 1; - } else { - command = lastCommand || 'M'; - } - - // Upper case - const commandUC = command.toUpperCase(); - - if (commandUC in commands) { - - // No numeric parameters - if (command === 'Z' || command === 'z') { - ret.push([command]); - - // One numeric parameter - } else { - const val0 = path[i]; - if (typeof val0 === 'number') { - - // Horizontal line to - if (command === 'H' || command === 'h') { - ret.push([command, val0]); - i += 1; - - // Vertical line to - } else if (command === 'V' || command === 'v') { - ret.push([command, val0]); - i += 1; - - // Two numeric parameters - } else { - const val1 = path[i + 1]; - if (typeof val1 === 'number') { - // lineTo - if (command === 'L' || command === 'l') { - ret.push([command, val0, val1]); - i += 2; - - // moveTo - } else if (command === 'M' || command === 'm') { - ret.push([command, val0, val1]); - i += 2; - - // Smooth quadratic bezier - } else if (command === 'T' || command === 't') { - ret.push([command, val0, val1]); - i += 2; - - // Four numeric parameters - } else { - const val2 = path[i + 2], - val3 = path[i + 3]; - if ( - typeof val2 === 'number' && - typeof val3 === 'number' - ) { - // Quadratic bezier to - if ( - command === 'Q' || - command === 'q' - ) { - ret.push([ - command, - val0, - val1, - val2, - val3 - ]); - i += 4; - - // Smooth cubic bezier to - } else if ( - command === 'S' || - command === 's' - ) { - ret.push([ - command, - val0, - val1, - val2, - val3 - ]); - i += 4; - - // Six numeric parameters - } else { - const val4 = path[i + 4], - val5 = path[i + 5]; - - if ( - typeof val4 === 'number' && - typeof val5 === 'number' - ) { - // Curve to - if ( - command === 'C' || - command === 'c' - ) { - ret.push([ - command, - val0, - val1, - val2, - val3, - val4, - val5 - ]); - i += 6; - - // Seven numeric parameters - } else { - const val6 = path[i + 6]; - - // Arc to - if ( - typeof val6 === - 'number' && - ( - command === 'A' || - command === 'a' - ) - ) { - ret.push([ - command, - val0, - val1, - val2, - val3, - val4, - val5, - val6 - ]); - i += 7; - - } - - } - } - } - } - } - } - - } - } - } - } - - // An unmarked command following a moveTo is a lineTo - lastCommand = command === 'M' ? 'L' : command; - - if (i === lastI) { - break; - } - lastI = i; - } - return ret; - */ - }; - /** - * 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' })`. - * - * @sample highcharts/members/renderer-label-on-chart/ - * A label on the chart - * - * @function Highcharts.SVGRenderer#label - * - * @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='rect'] - * 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} [useHTML=false] - * Wether to use HTML to render the label. - * - * @param {boolean} [baseline=false] - * 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 {Highcharts.SVGElement} - * The generated label. - */ - SVGRenderer.prototype.label = function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { - return new SVGLabel(this, str, x, y, shape, anchorX, anchorY, useHTML, baseline, className); - }; - return SVGRenderer; - }()); + // 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"](showAxis); + } + 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.stacking) { + axis.stacking.renderStackTotals(); + } + // End stacked totals + axis.isDirty = false; + fireEvent(this, "afterRender"); + }; /** - * A pointer to the renderer's associated Element class. The VMLRenderer - * will have a pointer to VMLElement here. + * Redraw the axis to reflect changes in the data or axis extremes. Called + * internally from Highcharts.Chart#redraw. * - * @name Highcharts.SVGRenderer#Element - * @type {Highcharts.SVGElement} - */ - SVGRenderer.prototype.Element = SVGElement; - /** * @private - */ - SVGRenderer.prototype.SVG_NS = SVG_NS; - /** - * Dummy function for plugins, called every time the renderer is updated. - * Prior to Highcharts 5, this was used for the canvg renderer. - * - * @deprecated - * @function Highcharts.SVGRenderer#draw - */ - SVGRenderer.prototype.draw = noop; + * @function Highcharts.Axis#redraw + */ + Axis.prototype.redraw = function () { + if (this.visible) { + // render the axis + this.render(); + // move plot lines and bands + this.plotLinesAndBands.forEach(function (plotLine) { + plotLine.render(); + }); + } + // mark associated series as dirty and ready for redraw + this.series.forEach(function (series) { + series.isDirty = true; + }); + }; /** - * 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 `<`. + * Returns an array of axis properties, that should be untouched during + * reinitialization. * - * @example - * // Add support for unescaping quotes - * Highcharts.SVGRenderer.prototype.escapes['"'] = '"'; + * @private + * @function Highcharts.Axis#getKeepProps * - * @name Highcharts.SVGRenderer#escapes - * @type {Highcharts.Dictionary<string>} + * @return {Array<string>} */ - SVGRenderer.prototype.escapes = { - '&': '&', - '<': '<', - '>': '>', - "'": ''', - '"': '"' + Axis.prototype.getKeepProps = function () { + return this.keepProps || Axis.keepProps; }; /** - * An extendable collection of functions for defining symbol paths. + * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint + * to fully remove the axis. + * + * @private + * @function Highcharts.Axis#destroy * - * @name Highcharts.SVGRenderer#symbols - * @type {Highcharts.SymbolDictionary} + * @param {boolean} [keepEvents] + * Whether to preserve events, used internally in Axis.update. */ - SVGRenderer.prototype.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: Math.PI * 0.5, - end: Math.PI * 2.5, - open: false - }); - }, - square: function (x, y, w, h) { - return [ - ['M', x, y], - ['L', x + w, y], - ['L', x + w, y + h], - ['L', x, y + h], - ['Z'] - ]; - }, - triangle: function (x, y, w, h) { - return [ - ['M', x + w / 2, y], - ['L', x + w, y + h], - ['L', x, y + h], - ['Z'] - ]; - }, - 'triangle-down': function (x, y, w, h) { - return [ - ['M', x, y], - ['L', x + w, y], - ['L', x + w / 2, y + h], - ['Z'] - ]; - }, - diamond: function (x, y, w, h) { - return [ - ['M', x + w / 2, y], - ['L', x + w, y + h / 2], - ['L', x + w / 2, y + h], - ['L', x, y + h / 2], - ['Z'] - ]; - }, - arc: function (x, y, w, h, options) { - var arc = []; - if (options) { - var start = options.start || 0, - end = options.end || 0, - rx = options.r || w, - ry = options.r || h || w, - proximity = 0.001, - fullCircle = Math.abs(end - 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 = 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 = pick(options.longArc, - end - start - Math.PI < proximity ? 0 : 1); - arc.push([ - 'M', - x + rx * cosStart, - y + ry * sinStart - ], [ - 'A', - rx, - ry, - 0, - longArc, - pick(options.clockwise, 1), - x + rx * cosEnd, - y + ry * sinEnd - ]); - if (defined(innerRadius)) { - arc.push(open ? - [ - 'M', - x + innerRadius * cosEnd, - y + innerRadius * sinEnd - ] : [ - 'L', - x + innerRadius * cosEnd, - y + innerRadius * sinEnd - ], [ - 'A', - innerRadius, - innerRadius, - 0, - longArc, - // Clockwise - opposite to the outer arc clockwise - defined(options.clockwise) ? 1 - options.clockwise : 0, - x + innerRadius * cosStart, - y + innerRadius * sinStart - ]); - } - if (!open) { - arc.push(['Z']); - } - } - 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 || 0, - anchorY = options && options.anchorY || 0, - path; - path = [ - ['M', x + r, y], - ['L', x + w - r, y], - ['C', x + w, y, x + w, y, x + w, y + r], - ['L', x + w, y + h - r], - ['C', x + w, y + h, x + w, y + h, x + w - r, y + h], - ['L', x + r, y + h], - ['C', x, y + h, x, y + h, x, y + h - r], - ['L', x, y + r], - ['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(3, 1, ['L', x + w, anchorY - halfDistance], ['L', x + w + arrowLength, anchorY], ['L', x + w, anchorY + halfDistance], ['L', x + w, y + h - r]); - // Simple connector - } - else { - path.splice(3, 1, ['L', x + w, h / 2], ['L', anchorX, anchorY], ['L', x + w, h / 2], ['L', 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(7, 1, ['L', x, anchorY + halfDistance], ['L', x - arrowLength, anchorY], ['L', x, anchorY - halfDistance], ['L', x, y + r]); - // Simple connector - } - else { - path.splice(7, 1, ['L', x, h / 2], ['L', anchorX, anchorY], ['L', x, h / 2], ['L', x, y + r]); - } - } - else if ( // replace bottom - anchorY && - anchorY > h && - anchorX > x + safeDistance && - anchorX < x + w - safeDistance) { - path.splice(5, 1, ['L', anchorX + halfDistance, y + h], ['L', anchorX, y + h + arrowLength], ['L', anchorX - halfDistance, y + h], ['L', x + r, y + h]); - } - else if ( // replace top - anchorY && - anchorY < 0 && - anchorX > x + safeDistance && - anchorX < x + w - safeDistance) { - path.splice(1, 1, ['L', anchorX - halfDistance, y], ['L', anchorX, y - arrowLength], ['L', anchorX + halfDistance, y], ['L', w - r, y]); - } - return path; + Axis.prototype.destroy = function (keepEvents) { + var axis = this, + plotLinesAndBands = axis.plotLinesAndBands, + plotGroup, + i; + fireEvent(this, "destroy", { keepEvents: keepEvents }); + // Remove the events + if (!keepEvents) { + removeEvent(axis); + } + // Destroy collections + [axis.ticks, axis.minorTicks, axis.alternateBands].forEach(function ( + coll + ) { + destroyObjectProperties(coll); + }); + if (plotLinesAndBands) { + i = plotLinesAndBands.length; + while (i--) { + // #1975 + plotLinesAndBands[i].destroy(); + } + } + // Destroy elements + [ + "axisLine", + "axisTitle", + "axisGroup", + "gridGroup", + "labelGroup", + "cross", + "scrollbar", + ].forEach(function (prop) { + if (axis[prop]) { + axis[prop] = axis[prop].destroy(); } + }); + // Destroy each generated group for plotlines and plotbands + for (plotGroup in axis.plotLinesAndBandsGroups) { + // eslint-disable-line guard-for-in + axis.plotLinesAndBandsGroups[plotGroup] = + axis.plotLinesAndBandsGroups[plotGroup].destroy(); + } + // Delete all properties and fall back to the prototype. + objectEach(axis, function (val, key) { + if (axis.getKeepProps().indexOf(key) === -1) { + delete axis[key]; + } + }); }; - H.SVGRenderer = SVGRenderer; - H.Renderer = H.SVGRenderer; - - return H.Renderer; - }); - _registerModule(_modules, 'Core/Renderer/HTML/HTMLElement.js', [_modules['Core/Globals.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (H, SVGElement, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var css = U.css, - defined = U.defined, - extend = U.extend, - pick = U.pick, - pInt = U.pInt; /** - * Element placebo - * @private - */ - var HTMLElement = SVGElement; - var isFirefox = H.isFirefox; - /* eslint-disable valid-jsdoc */ - // Extend SvgElement for useHTML option. - extend(HTMLElement.prototype, /** @lends SVGElement.prototype */ { - /** - * Apply CSS to HTML elements. This is used in text within SVG rendering and - * by the VML renderer - * - * @private - * @function Highcharts.SVGElement#htmlCss - * - * @param {Highcharts.CSSObject} styles - * - * @return {Highcharts.SVGElement} - */ - htmlCss: function (styles) { - var wrapper = this, - element = wrapper.element, - // When setting or unsetting the width style, we need to update - // transform (#8809) - isSettingWidth = (element.tagName === 'SPAN' && - styles && - 'width' in styles), - textWidth = pick(isSettingWidth && styles.width, - void 0), - doTransform; - if (isSettingWidth) { - delete styles.width; - wrapper.textWidth = textWidth; - doTransform = true; - } - if (styles && styles.textOverflow === 'ellipsis') { - styles.whiteSpace = 'nowrap'; - styles.overflow = 'hidden'; - } - wrapper.styles = extend(wrapper.styles, styles); - css(wrapper.element, styles); - // Now that all styles are applied, to the transform - if (doTransform) { - wrapper.htmlUpdateTransform(); - } - return wrapper; - }, - /** - * VML and useHTML method for calculating the bounding box based on offsets. - * - * @private - * @function Highcharts.SVGElement#htmlGetBBox - * - * @param {boolean} refresh - * Whether to force a fresh value from the DOM or to use the cached - * value. - * - * @return {Highcharts.BBoxObject} - * 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. - * - * @private - * @function Highcharts.SVGElement#htmlUpdateTransform - * @return {void} - */ - 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, - whiteSpace = styles && styles.whiteSpace; - /** - * @private - * @return {number} - */ - function getTextPxLength() { - // Reset multiline/ellipsis in order to read width (#4928, - // #5417) - css(elem, { - width: '', - whiteSpace: whiteSpace || 'nowrap' - }); - return elem.offsetWidth; - } - // apply translate - css(elem, { - marginLeft: translateX, - marginTop: translateY + * Internal function to draw a crosshair. + * + * @function Highcharts.Axis#drawCrosshair + * + * @param {Highcharts.PointerEventObject} [e] + * The event arguments from the modified pointer event, extended with + * `chartX` and `chartY` + * + * @param {Highcharts.Point} [point] + * The Point object if the crosshair snaps to points. + * + * @fires Highcharts.Axis#event:afterDrawCrosshair + * @fires Highcharts.Axis#event:drawCrosshair + */ + Axis.prototype.drawCrosshair = function (e, point) { + var path, + options = this.crosshair, + snap = pick(options.snap, true), + pos, + categorized, + graphic = this.cross, + crossOptions, + chart = this.chart; + fireEvent(this, "drawCrosshair", { e: e, point: point }); + // 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 = pick( + this.coll !== "colorAxis" + ? point.crosshairPos // 3D axis extension + : null, + this.isXAxis ? point.plotX : this.len - point.plotY + ); + } + if (defined(pos)) { + crossOptions = { + // value, only used on radial + value: + point && + (this.isXAxis ? point.x : pick(point.stackY, point.y)), + translatedValue: pos, + }; + if (chart.polar) { + // Additional information required for crosshairs in + // polar chart + extend(crossOptions, { + isCrosshair: true, + chartX: e && e.chartX, + chartY: e && e.chartY, + point: point, }); - if (!renderer.styledMode && wrapper.shadows) { // used in labels/tooltip - wrapper.shadows.forEach(function (shadow) { - css(shadow, { - marginLeft: translateX + 1, - marginTop: translateY + 1 - }); - }); - } - // apply inversion - if (wrapper.inverted) { // wrapper is a group - [].forEach.call(elem.childNodes, function (child) { - renderer.invertChild(child, elem); - }); - } - if (elem.tagName === 'SPAN') { - var rotation = wrapper.rotation, baseline, textWidth = wrapper.textWidth && pInt(wrapper.textWidth), currentTextTransform = [ - rotation, - align, - elem.innerHTML, - wrapper.textWidth, - wrapper.textAlign - ].join(','); - // Update textWidth. Use the memoized textPxLength if possible, to - // avoid the getTextPxLength function using elem.offsetWidth. - // Calling offsetWidth affects rendering time as it forces layout - // (#7656). - if (textWidth !== wrapper.oldTextWidth && - ((textWidth > wrapper.oldTextWidth) || - (wrapper.textPxLength || getTextPxLength()) > textWidth) && ( - // Only set the width if the text is able to word-wrap, or - // text-overflow is ellipsis (#9537) - /[ \-]/.test(elem.textContent || elem.innerText) || - elem.style.textOverflow === 'ellipsis')) { // #983, #1254 - css(elem, { - width: textWidth + 'px', - display: 'block', - whiteSpace: whiteSpace || 'normal' // #3331 - }); - wrapper.oldTextWidth = textWidth; - wrapper.hasBoxWidthChanged = true; // #8159 - } - else { - wrapper.hasBoxWidthChanged = false; // #8159 - } - // Do the calculations and DOM access only if properties changed - if (currentTextTransform !== wrapper.cTT) { - baseline = renderer.fontMetrics(elem.style.fontSize, elem).b; - // Renderer specific handling of span rotation, but only if we - // have something to update. - if (defined(rotation) && - ((rotation !== (wrapper.oldRotation || 0)) || - (align !== wrapper.oldAlign))) { - wrapper.setSpanRotation(rotation, alignCorrection, baseline); - } - wrapper.getSpanCorrection( - // Avoid elem.offsetWidth if we can, it affects rendering - // time heavily (#7656) - ((!defined(rotation) && wrapper.textPxLength) || // #7920 - elem.offsetWidth), baseline, alignCorrection, rotation, align); - } - // apply position with correction - css(elem, { - left: (x + (wrapper.xCorr || 0)) + 'px', - top: (y + (wrapper.yCorr || 0)) + 'px' - }); - // record current text transform - wrapper.cTT = currentTextTransform; - wrapper.oldRotation = rotation; - wrapper.oldAlign = align; - } - }, - /** - * Set the rotation of an individual HTML span. - * - * @private - * @function Highcharts.SVGElement#setSpanRotation - * @param {number} rotation - * @param {number} alignCorrection - * @param {number} baseline - * @return {void} - */ - 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. - * - * @private - * @function Highcharts.SVGElement#getSpanCorrection - * @param {number} width - * @param {number} baseline - * @param {number} alignCorrection - * @return {void} - */ - getSpanCorrection: function (width, baseline, alignCorrection) { - this.xCorr = -width * alignCorrection; - this.yCorr = -baseline; + } + path = this.getPlotLinePath(crossOptions) || null; // #3189 } - }); - - return HTMLElement; - }); - _registerModule(_modules, 'Core/Renderer/HTML/HTMLRenderer.js', [_modules['Core/Globals.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Utilities.js']], function (H, SVGElement, SVGRenderer, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var isFirefox = H.isFirefox, - isMS = H.isMS, - isWebKit = H.isWebKit, - win = H.win; - var attr = U.attr, - createElement = U.createElement, - extend = U.extend, - pick = U.pick; - /** - * Renderer placebo - * @private - */ - var HTMLRenderer = SVGRenderer; - /* eslint-disable valid-jsdoc */ - // Extend SvgRenderer for useHTML option. - extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */ { - /** - * @private - * @function Highcharts.SVGRenderer#getTransformKey - * - * @return {string} - */ - 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. - * - * @private - * @function Highcharts.SVGRenderer#html - * - * @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. - * - * @return {Highcharts.HTMLDOMElement} - */ - html: function (str, x, y) { - var wrapper = this.createElement('span'), element = wrapper.element, renderer = wrapper.renderer, isSVG = renderer.isSVG, addSetters = function (gWrapper, style) { - // These properties are set as attributes on the SVG group, and - // as identical CSS properties on the div. (#3542) - ['opacity', 'visibility'].forEach(function (prop) { - gWrapper[prop + 'Setter'] = function (value, key, elem) { - var styleObject = gWrapper.div ? - gWrapper.div.style : - style; - SVGElement.prototype[prop + 'Setter'] - .call(this, value, key, elem); - if (styleObject) { - styleObject[key] = value; - } - }; - }); - gWrapper.addedSetters = true; - }; - // Text setter - wrapper.textSetter = function (value) { - if (value !== element.innerHTML) { - delete this.bBox; - delete this.oldTextWidth; - } - this.textStr = value; - element.innerHTML = pick(value, ''); - wrapper.doTransform = true; - }; - // 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. - wrapper.alignValue = wrapper.textAlign = value; - } - else { - wrapper[key] = value; - } - wrapper.doTransform = true; - }; - // Runs at the end of .attr() - wrapper.afterSetters = function () { - // Update transform. Do this outside the loop to prevent redundant - // updating for batch setting of attributes. - if (this.doTransform) { - this.htmlUpdateTransform(); - this.doTransform = false; - } - }; - // Set the default attributes - wrapper - .attr({ - text: str, - x: Math.round(x), - y: Math.round(y) + if (!defined(path)) { + this.hideCrosshair(); + return; + } + categorized = this.categories && !this.isRadial; + // Draw the cross + if (!graphic) { + this.cross = graphic = chart.renderer + .path() + .addClass( + "highcharts-crosshair highcharts-crosshair-" + + (categorized ? "category " : "thin ") + + options.className + ) + .attr({ + zIndex: pick(options.zIndex, 2), }) - .css({ - position: 'absolute' - }); - if (!renderer.styledMode) { - wrapper.css({ - fontFamily: this.style.fontFamily, - fontSize: this.style.fontSize - }); - } - // 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 - parents.reverse().forEach(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) - * @private - * @param {*} value - * @param {string} key - * @return {void} - */ - function translateSetter(value, key) { - parentGroup[key] = value; - if (key === 'translateX') { - htmlGroupStyle.left = value + 'px'; - } - else { - htmlGroupStyle.top = value + 'px'; - } - parentGroup.doTransform = true; - } - // 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 ? { className: cls } : void 0, { - position: 'absolute', - left: (parentGroup.translateX || 0) + 'px', - top: (parentGroup.translateY || 0) + 'px', - display: parentGroup.display, - opacity: parentGroup.opacity, - 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 - }); - if (!parentGroup.addedSetters) { - addSetters(parentGroup); - } - }); - } - } - else { - htmlGroup = container; - } - htmlGroup.appendChild(element); - // Shared with VML: - wrapper.added = true; - if (wrapper.alignOnAdd) { - wrapper.htmlUpdateTransform(); - } - return wrapper; - }; - } - return wrapper; + .add(); + // Presentational attributes + if (!chart.styledMode) { + graphic + .attr({ + stroke: + options.color || + (categorized + ? Color.parse("#ccd6eb").setOpacity(0.25).get() + : "#cccccc"), + "stroke-width": pick(options.width, 1), + }) + .css({ + "pointer-events": "none", + }); + if (options.dashStyle) { + graphic.attr({ + dashstyle: options.dashStyle, + }); + } + } } - }); - - return HTMLRenderer; - }); - _registerModule(_modules, 'Core/Axis/Tick.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - /** - * Optional parameters for the tick. - * @private - * @interface Highcharts.TickParametersObject - */ /** - * Set category for the tick. - * @name Highcharts.TickParametersObject#category - * @type {string|undefined} - */ /** - * @name Highcharts.TickParametersObject#options - * @type {Highcharts.Dictionary<any>|undefined} - */ /** - * Set tickmarkOffset for the tick. - * @name Highcharts.TickParametersObject#tickmarkOffset - * @type {number|undefined} - */ - var clamp = U.clamp, - correctFloat = U.correctFloat, - defined = U.defined, - destroyObjectProperties = U.destroyObjectProperties, - extend = U.extend, - fireEvent = U.fireEvent, - isNumber = U.isNumber, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick; - var deg2rad = H.deg2rad; - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * The Tick class. - * - * @class - * @name Highcharts.Tick - * - * @param {Highcharts.Axis} axis - * The axis of the tick. - * - * @param {number} pos - * The position of the tick on the axis in terms of axis values. - * - * @param {string} [type] - * The type of tick, either 'minor' or an empty string - * - * @param {boolean} [noLabel=false] - * Whether to disable the label or not. Defaults to false. - * - * @param {object} [parameters] - * Optional parameters for the tick. - */ - var Tick = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function Tick(axis, pos, type, noLabel, parameters) { - this.isNew = true; - this.isNewLabel = true; - /** - * The related axis of the tick. - * @name Highcharts.Tick#axis - * @type {Highcharts.Axis} - */ - this.axis = axis; - /** - * The logical position of the tick on the axis in terms of axis values. - * @name Highcharts.Tick#pos - * @type {number} - */ - this.pos = pos; - /** - * The tick type, which can be `"minor"`, or an empty string. - * @name Highcharts.Tick#type - * @type {string} - */ - this.type = type || ''; - this.parameters = parameters || {}; - /** - * The mark offset of the tick on the axis. Usually `undefined`, numeric - * for grid axes. - * @name Highcharts.Tick#tickmarkOffset - * @type {number|undefined} - */ - this.tickmarkOffset = this.parameters.tickmarkOffset; - this.options = this.parameters.options; - fireEvent(this, 'init'); - if (!type && !noLabel) { - this.addLabel(); - } + graphic.show().attr({ + d: path, + }); + if (categorized && !options.width) { + graphic.attr({ + "stroke-width": this.transA, + }); } - /* * - * - * Functions - * - * */ - /** - * Write the tick label. - * - * @private - * @function Highcharts.Tick#addLabel - * @return {void} - */ - Tick.prototype.addLabel = function () { - var tick = this, - axis = tick.axis, - options = axis.options, - chart = axis.chart, - categories = axis.categories, - log = axis.logarithmic, - names = axis.names, - pos = tick.pos, - labelOptions = pick(tick.options && tick.options.labels, - options.labels), - str, - tickPositions = axis.tickPositions, - isFirst = pos === tickPositions[0], - isLast = pos === tickPositions[tickPositions.length - 1], - value = this.parameters.category || (categories ? - pick(categories[pos], - names[pos], - pos) : - pos), - label = tick.label, - animateLabels = (!labelOptions.step || labelOptions.step === 1) && - axis.tickInterval === 1, - tickPositionInfo = tickPositions.info, - dateTimeLabelFormat, - dateTimeLabelFormats, - i, - list; - // Set the datetime label format. If a higher rank is set for this - // position, use that. If not, use the general format. - if (axis.dateTime && tickPositionInfo) { - dateTimeLabelFormats = chart.time.resolveDTLFormat(options.dateTimeLabelFormats[(!options.grid && - tickPositionInfo.higherRanks[pos]) || - tickPositionInfo.unitName]); - dateTimeLabelFormat = dateTimeLabelFormats.main; - } - // set properties for access in render method - /** - * True if the tick is the first one on the axis. - * @name Highcharts.Tick#isFirst - * @readonly - * @type {boolean|undefined} - */ - tick.isFirst = isFirst; - /** - * True if the tick is the last one on the axis. - * @name Highcharts.Tick#isLast - * @readonly - * @type {boolean|undefined} - */ - tick.isLast = isLast; - // Get the string - tick.formatCtx = { - axis: axis, - chart: chart, - isFirst: isFirst, - isLast: isLast, - dateTimeLabelFormat: dateTimeLabelFormat, - tickPositionInfo: tickPositionInfo, - value: log ? correctFloat(log.lin2log(value)) : value, - pos: pos - }; - str = axis.labelFormatter.call(tick.formatCtx, this.formatCtx); - // Set up conditional formatting based on the format list if existing. - list = dateTimeLabelFormats && dateTimeLabelFormats.list; - if (list) { - tick.shortenLabel = function () { - for (i = 0; i < list.length; i++) { - label.attr({ - text: axis.labelFormatter.call(extend(tick.formatCtx, { dateTimeLabelFormat: list[i] })) - }); - if (label.getBBox().width < - axis.getSlotWidth(tick) - 2 * - pick(labelOptions.padding, 5)) { - return; - } - } - label.attr({ - text: '' - }); - }; - } - // Call only after first render - if (animateLabels && axis._addedPlotLB) { - tick.moveLabel(str, labelOptions); - } - // First call - if (!defined(label) && !tick.movedLabel) { - /** - * The rendered text label of the tick. - * @name Highcharts.Tick#label - * @type {Highcharts.SVGElement|undefined} - */ - tick.label = label = tick.createLabel({ x: 0, y: 0 }, str, labelOptions); - // Base value to detect change for new calls to getBBox - tick.rotation = 0; - // update - } - else if (label && label.textStr !== str && !animateLabels) { - // When resetting text, also reset the width if dynamically set - // (#8809) - if (label.textWidth && - !(labelOptions.style && labelOptions.style.width) && - !label.styles.width) { - label.css({ width: null }); - } - label.attr({ text: str }); - label.textPxLength = label.getBBox().width; - } - }; - /** - * Render and return the label of the tick. - * - * @private - * @function Highcharts.Tick#createLabel - * @param {Highcharts.PositionObject} xy - * @param {string} str - * @param {Highcharts.XAxisLabelsOptions} labelOptions - * @return {Highcharts.SVGElement|undefined} - */ - Tick.prototype.createLabel = function (xy, str, labelOptions) { - var axis = this.axis, - chart = axis.chart, - label = defined(str) && labelOptions.enabled ? - chart.renderer - .text(str, - xy.x, - xy.y, - labelOptions.useHTML) - .add(axis.labelGroup) : - null; - // Un-rotated length - if (label) { - // Without position absolute, IE export sometimes is wrong - if (!chart.styledMode) { - label.css(merge(labelOptions.style)); - } - label.textPxLength = label.getBBox().width; - } - return label; - }; - /** - * Destructor for the tick prototype - * - * @private - * @function Highcharts.Tick#destroy - * @return {void} - */ - Tick.prototype.destroy = function () { - destroyObjectProperties(this, this.axis); - }; - /** - * Gets the x and y positions for ticks in terms of pixels. - * - * @private - * @function Highcharts.Tick#getPosition - * - * @param {boolean} horiz - * Whether the tick is on an horizontal axis or not. - * - * @param {number} tickPos - * Position of the tick. - * - * @param {number} tickmarkOffset - * Tickmark offset for all ticks. - * - * @param {boolean} [old] - * Whether the axis has changed or not. - * - * @return {Highcharts.PositionObject} - * The tick position. - * - * @fires Highcharts.Tick#event:afterGetPosition - */ - Tick.prototype.getPosition = function (horiz, tickPos, tickmarkOffset, old) { - var axis = this.axis, - chart = axis.chart, - cHeight = (old && chart.oldChartHeight) || chart.chartHeight, - pos; - pos = { - x: horiz ? - correctFloat(axis.translate(tickPos + 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)) : - correctFloat(cHeight - - axis.translate(tickPos + tickmarkOffset, null, null, old) - - axis.transB) - }; - // Chrome workaround for #10516 - pos.y = clamp(pos.y, -1e5, 1e5); - fireEvent(this, 'afterGetPosition', { pos: pos }); - return pos; - }; - /** - * Get the x, y position of the tick label - * - * @private - * @return {Highcharts.PositionObject} - */ - Tick.prototype.getLabelPosition = function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { - var axis = this.axis, - transA = axis.transA, - reversed = ( // #7911 - axis.isLinked && axis.linkedParent ? - axis.linkedParent.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, - pos = {}; - 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); - } - pos.x = x; - pos.y = Math.round(y); - fireEvent(this, 'afterGetLabelPosition', { pos: pos, tickmarkOffset: tickmarkOffset, index: index }); - return pos; - }; - /** - * Get the offset height or width of the label - * - * @private - * @function Highcharts.Tick#getLabelSize - * @return {number} - */ - Tick.prototype.getLabelSize = function () { - return this.label ? - this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] : - 0; - }; - /** - * Extendible method to return the path of the marker - * - * @private - * - */ - Tick.prototype.getMarkPath = function (x, y, tickLength, tickWidth, horiz, renderer) { - return renderer.crispLine([[ - 'M', - x, - y - ], [ - 'L', - x + (horiz ? 0 : -tickLength), - y + (horiz ? tickLength : 0) - ]], tickWidth); - }; - /** - * Handle the label overflow by adjusting the labels to the left and right - * edge, or hide them if they collide into the neighbour label. - * - * @private - * @function Highcharts.Tick#handleOverflow - * @param {Highcharts.PositionObject} xy - * @return {void} - */ - Tick.prototype.handleOverflow = function (xy) { - var tick = this, - 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(tick), - 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 && - pick(labelOptions.overflow, 'justify') === 'justify') { - 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) { - if (tick.shortenLabel) { - tick.shortenLabel(); - } - else { - css.width = Math.floor(textWidth) + 'px'; - if (!(labelOptions.style || {}).textOverflow) { - css.textOverflow = 'ellipsis'; - } - label.css(css); - } - } - }; - /** - * Try to replace the label if the same one already exists. - * - * @private - * @function Highcharts.Tick#moveLabel - * @param {string} str - * @param {Highcharts.XAxisLabelsOptions} labelOptions - * - * @return {void} - */ - Tick.prototype.moveLabel = function (str, labelOptions) { - var tick = this, - label = tick.label, - moved = false, - axis = tick.axis, - labelPos, - reversed = axis.reversed, - xPos, - yPos; - if (label && label.textStr === str) { - tick.movedLabel = label; - moved = true; - delete tick.label; - } - else { // Find a label with the same string - objectEach(axis.ticks, function (currentTick) { - if (!moved && - !currentTick.isNew && - currentTick !== tick && - currentTick.label && - currentTick.label.textStr === str) { - tick.movedLabel = currentTick.label; - moved = true; - currentTick.labelPos = tick.movedLabel.xy; - delete currentTick.label; - } - }); - } - // Create new label if the actual one is moved - if (!moved && (tick.labelPos || label)) { - labelPos = tick.labelPos || label.xy; - xPos = axis.horiz ? - (reversed ? 0 : axis.width + axis.left) : labelPos.x; - yPos = axis.horiz ? - labelPos.y : (reversed ? (axis.width + axis.left) : 0); - tick.movedLabel = tick.createLabel({ x: xPos, y: yPos }, str, labelOptions); - if (tick.movedLabel) { - tick.movedLabel.attr({ opacity: 0 }); - } - } - }; - /** - * Put everything in place - * - * @private - * @param {number} index - * @param {boolean} [old] - * Use old coordinates to prepare an animation into new position - * @param {number} [opacity] - * @return {voids} - */ - Tick.prototype.render = function (index, old, opacity) { - var tick = this, - axis = tick.axis, - horiz = axis.horiz, - pos = tick.pos, - tickmarkOffset = pick(tick.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; - fireEvent(this, 'afterRender'); - }; - /** - * Renders the gridLine. - * - * @private - * @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 {void} - */ - Tick.prototype.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 = pick(tick.tickmarkOffset, axis.tickmarkOffset), renderer = axis.chart.renderer, gridPrefix = type ? type + 'Grid' : 'grid', gridLineWidth = options[gridPrefix + 'LineWidth'], gridLineColor = options[gridPrefix + 'LineColor'], dashStyle = options[gridPrefix + 'LineDashStyle']; - if (!gridLine) { - if (!axis.chart.styledMode) { - attribs.stroke = gridLineColor; - attribs['stroke-width'] = gridLineWidth; - if (dashStyle) { - attribs.dashstyle = dashStyle; - } - } - if (!type) { - attribs.zIndex = 1; - } - if (old) { - opacity = 0; - } - /** - * The rendered grid line of the tick. - * @name Highcharts.Tick#gridLine - * @type {Highcharts.SVGElement|undefined} - */ - tick.gridLine = gridLine = renderer.path() - .attr(attribs) - .addClass('highcharts-' + (type ? type + '-' : '') + 'grid-line') - .add(axis.gridGroup); - } - if (gridLine) { - gridLinePath = axis.getPlotLinePath({ - value: pos + tickmarkOffset, - lineWidth: gridLine.strokeWidth() * reverseCrisp, - force: 'pass', - old: old - }); - // If the parameter 'old' is set, the current call will be followed - // by another call, therefore do not do any animations this time - if (gridLinePath) { - gridLine[old || tick.isNew ? 'attr' : 'animate']({ - d: gridLinePath, - opacity: opacity - }); - } - } - }; - /** - * Renders the tick mark. - * - * @private - * @param {Highcharts.PositionObject} xy The position vector of the mark - * @param {number} opacity The opacity of the mark - * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1 - * @return {void} - */ - Tick.prototype.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, 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) { - /** - * The rendered mark of the tick. - * @name Highcharts.Tick#mark - * @type {Highcharts.SVGElement|undefined} - */ - tick.mark = mark = renderer.path() - .addClass('highcharts-' + (type ? type + '-' : '') + 'tick') - .add(axis.axisGroup); - if (!axis.chart.styledMode) { - 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. - * - * @private - * @param {Highcharts.PositionObject} xy The position vector 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 {void} - */ - Tick.prototype.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 = pick(tick.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; - } - } - }; - /** - * Replace labels with the moved ones to perform animation. Additionally - * destroy unused labels. - * - * @private - * @function Highcharts.Tick#replaceMovedLabel - * @return {void} - */ - Tick.prototype.replaceMovedLabel = function () { - var tick = this, - label = tick.label, - axis = tick.axis, - reversed = axis.reversed, - x, - y; - // Animate and destroy - if (label && !tick.isNew) { - x = axis.horiz ? (reversed ? axis.left : axis.width + axis.left) : label.xy.x; - y = axis.horiz ? - label.xy.y : - (reversed ? axis.width + axis.top : axis.top); - label.animate({ x: x, y: y, opacity: 0 }, void 0, label.destroy); - delete tick.label; - } - axis.isDirty = true; - tick.label = tick.movedLabel; - delete tick.movedLabel; - }; - return Tick; - }()); - H.Tick = Tick; - - return H.Tick; - }); - _registerModule(_modules, 'Core/Time.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (Highcharts, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - /** - * Normalized interval. - * - * @interface Highcharts.TimeNormalizedObject - */ /** - * The count. - * - * @name Highcharts.TimeNormalizedObject#count - * @type {number} - */ /** - * The interval in axis values (ms). - * - * @name Highcharts.TimeNormalizedObject#unitRange - * @type {number} - */ - /** - * Function of an additional date format specifier. - * - * @callback Highcharts.TimeFormatCallbackFunction - * - * @param {number} timestamp - * The time to format. - * - * @return {string} - * The formatted portion of the date. - */ - /** - * Additonal time tick information. - * - * @interface Highcharts.TimeTicksInfoObject - * @extends Highcharts.TimeNormalizedObject - */ /** - * @name Highcharts.TimeTicksInfoObject#higherRanks - * @type {Array<string>} - */ /** - * @name Highcharts.TimeTicksInfoObject#totalRange - * @type {number} - */ - /** - * Time ticks. - * - * @interface Highcharts.AxisTickPositionsArray - * @extends global.Array<number> - */ /** - * @name Highcharts.AxisTickPositionsArray#info - * @type {Highcharts.TimeTicksInfoObject|undefined} - */ - /** - * 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. - * - * @callback Highcharts.TimezoneOffsetCallbackFunction - * - * @param {number} timestamp - * Timestamp in terms of milliseconds since January 1 1970. - * - * @return {number} - * Timezone offset in minutes. - */ - /** - * Allows to manually load the `moment.js` library from Highcharts options - * instead of the `window`. - * In case of loading the library from a `script` tag, - * this option is not needed, it will be loaded from there by default. - * - * @type {function} - * @since 8.2.0 - * @apioption time.moment - */ - var defined = U.defined, - error = U.error, - extend = U.extend, - isObject = U.isObject, - merge = U.merge, - objectEach = U.objectEach, - pad = U.pad, - pick = U.pick, - splat = U.splat, - timeUnits = U.timeUnits; - var H = Highcharts, - win = H.win; - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * 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 {@link 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()) - * ); - * - * @since 6.0.5 - * - * @class - * @name Highcharts.Time - * - * @param {Highcharts.TimeOptions} options - * Time options as defined in [chart.options.time](/highcharts/time). - */ - var Time = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function Time(options) { - /* * - * - * Properties - * - * */ - this.options = {}; - this.useUTC = false; - this.variableTimezone = false; - this.Date = win.Date; - /** - * Get the time zone offset based on the current timezone information as - * set in the global options. - * - * @function Highcharts.Time#getTimezoneOffset - * - * @param {number} timestamp - * The JavaScript timestamp to inspect. - * - * @return {number} - * The timezone offset in minutes compared to UTC. - */ - this.getTimezoneOffset = this.timezoneOffsetFunction(); - this.update(options); - } - /* * - * - * Functions - * - * */ - /** - * Time units used in `Time.get` and `Time.set` - * - * @typedef {"Date"|"Day"|"FullYear"|"Hours"|"Milliseconds"|"Minutes"|"Month"|"Seconds"} Highcharts.TimeUnitValue - */ - /** - * Get the value of a date object in given units, and subject to the Time - * object's current timezone settings. This function corresponds directly to - * JavaScripts `Date.getXXX / Date.getUTCXXX`, so instead of calling - * `date.getHours()` or `date.getUTCHours()` we will call - * `time.get('Hours')`. - * - * @function Highcharts.Time#get - * - * @param {Highcharts.TimeUnitValue} unit - * @param {Date} date - * - * @return {number} - * The given time unit - */ - Time.prototype.get = function (unit, date) { - if (this.variableTimezone || this.timezoneOffset) { - var realMs = date.getTime(); - var ms = realMs - this.getTimezoneOffset(date); - date.setTime(ms); // Temporary adjust to timezone - var ret = date['getUTC' + unit](); - date.setTime(realMs); // Reset - return ret; - } - // UTC time with no timezone handling - if (this.useUTC) { - return date['getUTC' + unit](); - } - // Else, local time - return date['get' + unit](); - }; - /** - * Set the value of a date object in given units, and subject to the Time - * object's current timezone settings. This function corresponds directly to - * JavaScripts `Date.setXXX / Date.setUTCXXX`, so instead of calling - * `date.setHours(0)` or `date.setUTCHours(0)` we will call - * `time.set('Hours', 0)`. - * - * @function Highcharts.Time#set - * - * @param {Highcharts.TimeUnitValue} unit - * @param {Date} date - * @param {number} value - * - * @return {number} - * The epoch milliseconds of the updated date - */ - Time.prototype.set = function (unit, date, value) { - // UTC time with timezone handling - if (this.variableTimezone || this.timezoneOffset) { - // For lower order time units, just set it directly using UTC - // time - if (unit === 'Milliseconds' || - unit === 'Seconds' || - unit === 'Minutes') { - return date['setUTC' + unit](value); - } - // Higher order time units need to take the time zone into - // account - // Adjust by timezone - var offset = this.getTimezoneOffset(date); - var ms = date.getTime() - offset; - date.setTime(ms); - date['setUTC' + unit](value); - var newOffset = this.getTimezoneOffset(date); - ms = date.getTime() + newOffset; - return date.setTime(ms); - } - // UTC time with no timezone handling - if (this.useUTC) { - return date['setUTC' + unit](value); - } - // Else, local time - return date['set' + unit](value); - }; - /** - * Update the Time object with current options. It is called internally on - * initializing Highcharts, after running `Highcharts.setOptions` and on - * `Chart.update`. - * - * @private - * @function Highcharts.Time#update - * - * @param {Highcharts.TimeOptions} options - * - * @return {void} - */ - Time.prototype.update = function (options) { - var useUTC = pick(options && options.useUTC, - true), - time = this; - this.options = options = merge(true, this.options || {}, options); - // Allow using a different Date class - this.Date = options.Date || win.Date || Date; - this.useUTC = useUTC; - this.timezoneOffset = (useUTC && options.timezoneOffset); - 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 = !!(!useUTC || - options.getTimezoneOffset || - options.timezone); - }; - /** - * 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. - * - * @function Highcharts.Time#makeTime - * - * @param {number} year - * The year - * - * @param {number} month - * The month. Zero-based, so January is 0. - * - * @param {number} [date=1] - * The day of the month - * - * @param {number} [hours=0] - * The hour of the day, 0-23. - * - * @param {number} [minutes=0] - * The minutes - * - * @param {number} [seconds=0] - * The seconds - * - * @return {number} - * The time in milliseconds since January 1st 1970. - */ - Time.prototype.makeTime = function (year, month, date, hours, minutes, seconds) { - var d, - offset, - newOffset; - if (this.useUTC) { - d = this.Date.UTC.apply(0, arguments); - offset = this.getTimezoneOffset(d); - d += offset; - newOffset = this.getTimezoneOffset(d); - if (offset !== newOffset) { - d += newOffset - offset; - // A special case for transitioning from summer time to winter time. - // When the clock is set back, the same time is repeated twice, i.e. - // 02:30 am is repeated since the clock is set back from 3 am to - // 2 am. We need to make the same time as local Date does. - } - else if (offset - 36e5 === this.getTimezoneOffset(d - 36e5) && - !H.isSafari) { - d -= 36e5; - } - } - 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 - * @function Highcharts.Time#timezoneOffsetFunction - * - * @return {Function} - * A getTimezoneOffset function - */ - Time.prototype.timezoneOffsetFunction = function () { - var time = this, - options = this.options, - moment = options.moment || win.moment; - if (!this.useUTC) { - return function (timestamp) { - return new Date(timestamp.toString()).getTimezoneOffset() * 60000; - }; - } - if (options.timezone) { - if (!moment) { - // getTimezoneOffset-function stays undefined because it depends - // on Moment.js - 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.valueOf()) * 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 available format keys are listed - * below. Additional formats can be given in the - * {@link Highcharts.dateFormats} hook. - * - * Supported format keys: - * - `%a`: Short weekday, like 'Mon' - * - `%A`: Long weekday, like 'Monday' - * - `%d`: Two digit day of the month, 01 to 31 - * - `%e`: Day of the month, 1 through 31 - * - `%w`: Day of the week, 0 through 6 - * - `%b`: Short month, like 'Jan' - * - `%B`: Long month, like 'January' - * - `%m`: Two digit month number, 01 through 12 - * - `%y`: Two digits year, like 09 for 2009 - * - `%Y`: Four digits year, like 2009 - * - `%H`: Two digits hours in 24h format, 00 through 23 - * - `%k`: Hours in 24h format, 0 through 23 - * - `%I`: Two digits hours in 12h format, 00 through 11 - * - `%l`: Hours in 12h format, 1 through 12 - * - `%M`: Two digits minutes, 00 through 59 - * - `%p`: Upper case AM or PM - * - `%P`: Lower case AM or PM - * - `%S`: Two digits seconds, 00 through 59 - * - `%L`: Milliseconds (naming from Ruby) - * - * @example - * const time = new Highcharts.Time(); - * const s = time.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2020, 0, 1)); - * console.log(s); // => 2020-01-01 00:00:00 - * - * @function Highcharts.Time#dateFormat - * - * @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. - * - * @return {string} - * The formatted date. - */ - Time.prototype.dateFormat = function (format, timestamp, capitalize) { - var _a; - if (!defined(timestamp) || isNaN(timestamp)) { - return ((_a = H.defaultOptions.lang) === null || _a === void 0 ? void 0 : _a.invalidDate) || ''; - } - format = pick(format, '%Y-%m-%d %H:%M:%S'); - var time = this, date = new this.Date(timestamp), - // get the basic time values - hours = this.get('Hours', date), day = this.get('Day', date), dayOfMonth = this.get('Date', date), month = this.get('Month', date), fullYear = this.get('FullYear', date), lang = H.defaultOptions.lang, langWeekdays = lang === null || lang === void 0 ? void 0 : lang.weekdays, shortWeekdays = lang === null || lang === void 0 ? void 0 : lang.shortWeekdays, - // List all format keys. Custom formats can be added from the - // outside. - replacements = 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, ' '), - // Day of the week, 0 through 6 - 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), - // Month number, 1 through 12 (#8150) - o: 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(this.get('Minutes', date)), - // 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.floor(timestamp % 1000), 3) - }, H.dateFormats); - // Do the replaces - 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; - }; - /** - * Resolve legacy formats of dateTimeLabelFormats (strings and arrays) into - * an object. - * @private - * @param {string|Array<T>|Highcharts.Dictionary<T>} f - General format description - * @return {Highcharts.Dictionary<T>} - The object definition - */ - Time.prototype.resolveDTLFormat = function (f) { - if (!isObject(f, true)) { // check for string or array - f = splat(f); - return { - main: f[0], - from: f[1], - to: f[2] - }; - } - return f; - }; - /** - * Return an array with time positions distributed on round time values - * right and right after min and max. Used in datetime axes as well as for - * grouping data on a datetime axis. - * - * @function Highcharts.Time#getTimeTicks - * - * @param {Highcharts.TimeNormalizedObject} 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=1] - * - * @return {Highcharts.AxisTickPositionsArray} - */ - Time.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek) { - var time = this, - 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), - interval = normalizedInterval.unitRange, - count = normalizedInterval.count || 1, - variableDayLength, - minDay; - startOfWeek = pick(startOfWeek, 1); - if (defined(min)) { // #1300 - time.set('Milliseconds', minDate, interval >= timeUnits.second ? - 0 : // #3935 - count * Math.floor(time.get('Milliseconds', minDate) / count)); // #3652, #3654 - if (interval >= timeUnits.second) { // second - time.set('Seconds', minDate, interval >= timeUnits.minute ? - 0 : // #3935 - count * Math.floor(time.get('Seconds', minDate) / count)); - } - if (interval >= timeUnits.minute) { // minute - time.set('Minutes', minDate, interval >= timeUnits.hour ? - 0 : - count * Math.floor(time.get('Minutes', minDate) / count)); - } - if (interval >= timeUnits.hour) { // hour - time.set('Hours', minDate, interval >= timeUnits.day ? - 0 : - count * Math.floor(time.get('Hours', minDate) / count)); - } - if (interval >= timeUnits.day) { // day - time.set('Date', minDate, interval >= timeUnits.month ? - 1 : - Math.max(1, count * Math.floor(time.get('Date', minDate) / count))); - } - if (interval >= timeUnits.month) { // month - time.set('Month', minDate, interval >= timeUnits.year ? 0 : - count * Math.floor(time.get('Month', minDate) / count)); - minYear = time.get('FullYear', minDate); - } - if (interval >= timeUnits.year) { // year - minYear -= minYear % count; - time.set('FullYear', minDate, minYear); - } - // week is a special case that runs outside the hierarchy - if (interval === timeUnits.week) { - // get start of current week, independent of count - minDay = time.get('Day', minDate); - time.set('Date', minDate, (time.get('Date', minDate) - - minDay + startOfWeek + - // We don't want to skip days that are before - // startOfWeek (#7051) - (minDay < startOfWeek ? -7 : 0))); - } - // Get basics for variable time spans - minYear = time.get('FullYear', minDate); - var minMonth = time.get('Month', minDate), minDateDate = time.get('Date', minDate), minHours = time.get('Hours', minDate); - // 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)); - } - // 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 && - count > 1) { - // make sure higher ranks are preserved across DST (#6797, - // #7621) - t = time.makeTime(minYear, minMonth, minDateDate, minHours + i * count); - // 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) { - tickPositions.forEach(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; - }; - return Time; - }()); - H.Time = Time; - - return H.Time; - }); - _registerModule(_modules, 'Core/Options.js', [_modules['Core/Globals.js'], _modules['Core/Color/Color.js'], _modules['Core/Time.js'], _modules['Core/Utilities.js']], function (H, Color, Time, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var isTouchDevice = H.isTouchDevice, - svg = H.svg; - var color = Color.parse; - var merge = U.merge; - /** - * @typedef {"plotBox"|"spacingBox"} Highcharts.ButtonRelativeToValue - */ - /** - * Gets fired when a series is added to the chart after load time, using the - * `addSeries` method. Returning `false` prevents the series from being added. - * - * @callback Highcharts.ChartAddSeriesCallbackFunction - * - * @param {Highcharts.Chart} this - * The chart on which the event occured. - * - * @param {Highcharts.ChartAddSeriesEventObject} event - * The event that occured. - */ - /** - * Contains common event information. Through the `options` property you can - * access the series options that were passed to the `addSeries` method. - * - * @interface Highcharts.ChartAddSeriesEventObject - */ /** - * The series options that were passed to the `addSeries` method. - * @name Highcharts.ChartAddSeriesEventObject#options - * @type {Highcharts.SeriesOptionsType} - */ /** - * Prevents the default behaviour of the event. - * @name Highcharts.ChartAddSeriesEventObject#preventDefault - * @type {Function} - */ /** - * The event target. - * @name Highcharts.ChartAddSeriesEventObject#target - * @type {Highcharts.Chart} - */ /** - * The event type. - * @name Highcharts.ChartAddSeriesEventObject#type - * @type {"addSeries"} - */ - /** - * Gets fired when clicking on the plot background. - * - * @callback Highcharts.ChartClickCallbackFunction - * - * @param {Highcharts.Chart} this - * The chart on which the event occured. - * - * @param {Highcharts.PointerEventObject} event - * The event that occured. - */ - /** - * Contains an axes of the clicked spot. - * - * @interface Highcharts.ChartClickEventAxisObject - */ /** - * Axis at the clicked spot. - * @name Highcharts.ChartClickEventAxisObject#axis - * @type {Highcharts.Axis} - */ /** - * Axis value at the clicked spot. - * @name Highcharts.ChartClickEventAxisObject#value - * @type {number} - */ - /** - * Contains information about the clicked spot on the chart. Remember the unit - * of a datetime axis is milliseconds since 1970-01-01 00:00:00. - * - * @interface Highcharts.ChartClickEventObject - * @extends Highcharts.PointerEventObject - */ /** - * Information about the x-axis on the clicked spot. - * @name Highcharts.ChartClickEventObject#xAxis - * @type {Array<Highcharts.ChartClickEventAxisObject>} - */ /** - * Information about the y-axis on the clicked spot. - * @name Highcharts.ChartClickEventObject#yAxis - * @type {Array<Highcharts.ChartClickEventAxisObject>} - */ /** - * Information about the z-axis on the clicked spot. - * @name Highcharts.ChartClickEventObject#zAxis - * @type {Array<Highcharts.ChartClickEventAxisObject>|undefined} - */ - /** - * Gets fired when the chart is finished loading. - * - * @callback Highcharts.ChartLoadCallbackFunction - * - * @param {Highcharts.Chart} this - * The chart on which the event occured. - * - * @param {global.Event} event - * The event that occured. - */ - /** - * 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`. - * - * @callback Highcharts.ChartRedrawCallbackFunction - * - * @param {Highcharts.Chart} this - * The chart on which the event occured. - * - * @param {global.Event} event - * The event that occured. - */ - /** - * Gets fired after initial load of the chart (directly after the `load` event), - * and after each redraw (directly after the `redraw` event). - * - * @callback Highcharts.ChartRenderCallbackFunction - * - * @param {Highcharts.Chart} this - * The chart on which the event occured. - * - * @param {global.Event} event - * The event that occured. - */ - /** - * Gets fired when an area of the chart has been selected. The default action - * for the selection event is to zoom the chart to the selected area. It can be - * prevented by calling `event.preventDefault()` or return false. - * - * @callback Highcharts.ChartSelectionCallbackFunction - * - * @param {Highcharts.Chart} this - * The chart on which the event occured. - * - * @param {global.ChartSelectionContextObject} event - * Event informations - * - * @return {boolean|undefined} - * Return false to prevent the default action, usually zoom. - */ - /** - * The primary axes are `xAxis[0]` and `yAxis[0]`. Remember the unit of a - * datetime axis is milliseconds since 1970-01-01 00:00:00. - * - * @interface Highcharts.ChartSelectionContextObject - * @extends global.Event - */ /** - * Arrays containing the axes of each dimension and each axis' min and max - * values. - * @name Highcharts.ChartSelectionContextObject#xAxis - * @type {Array<Highcharts.ChartSelectionAxisContextObject>} - */ /** - * Arrays containing the axes of each dimension and each axis' min and max - * values. - * @name Highcharts.ChartSelectionContextObject#yAxis - * @type {Array<Highcharts.ChartSelectionAxisContextObject>} - */ - /** - * Axis context of the selection. - * - * @interface Highcharts.ChartSelectionAxisContextObject - */ /** - * The selected Axis. - * @name Highcharts.ChartSelectionAxisContextObject#axis - * @type {Highcharts.Axis} - */ /** - * The maximum axis value, either automatic or set manually. - * @name Highcharts.ChartSelectionAxisContextObject#max - * @type {number} - */ /** - * The minimum axis value, either automatic or set manually. - * @name Highcharts.ChartSelectionAxisContextObject#min - * @type {number} - */ - ''; // detach doclets above - /* ************************************************************************** * - * Handle the options * - * ************************************************************************** */ - /** - * Global default settings. - * - * @name Highcharts.defaultOptions - * @type {Highcharts.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: - * ```js - * colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', - * '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a'] - * ``` - * - * In Highcharts 2.x, the default colors were: - * ```js - * colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', - * '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'] - * ``` - * - * @sample {highcharts} highcharts/chart/colors/ - * Assign a global color theme - * - * @type {Array<Highcharts.ColorString>} - * @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](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns) - * for more information and code examples. - * - * @type {*} - * @since 5.0.0 - * @apioption defs - */ - /** - * @ignore-option - */ - symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'], - /** - * The language object is global and it can't be set on each chart - * initialization. Instead, use `Highcharts.setOptions` to set it before any - * chart is initialized. - * - * ```js - * 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' - * ] - * } - * }); - * ``` - */ - lang: { - /** - * The loading text that appears when the chart is set into the loading - * state following a call to `chart.showLoading`. - */ - loading: 'Loading...', - /** - * An array containing the months names. Corresponds to the `%B` format - * in `Highcharts.dateFormat()`. - * - * @type {Array<string>} - * @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<string>} - * @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<string>} - * @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. - * - * @sample highcharts/lang/shortweekdays/ - * Finnish two-letter abbreviations - * - * @type {Array<string>} - * @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 title appearing on hovering the zoom in button. The text itself - * defaults to "+" and can be changed in the button options. - * - * @type {string} - * @default Zoom in - * @product highmaps - * @apioption lang.zoomIn - */ - /** - * The title appearing on hovering the zoom out button. The text itself - * defaults to "-" and can be changed in the button options. - * - * @type {string} - * @default Zoom out - * @product highmaps - * @apioption lang.zoomOut - */ - /** - * The default decimal point used in the `Highcharts.numberFormat` - * method unless otherwise specified in the function arguments. - * - * @since 1.2.2 - */ - decimalPoint: '.', - /** - * [Metric prefixes](https://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. - * - * @sample {highcharts} highcharts/lang/numericsymbols/ - * Replacing the symbols with text - * @sample {highstock} highcharts/lang/numericsymbols/ - * Replacing the symbols with text - * - * @type {Array<string>} - * @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. - * - * @sample highcharts/lang/numericsymbolmagnitude/ - * 10000 magnitude for Japanese - * - * @type {number} - * @default 1000 - * @since 5.0.3 - * @apioption lang.numericSymbolMagnitude - */ - /** - * The text for the label appearing when a chart is zoomed. - * - * @since 1.2.4 - */ - resetZoom: 'Reset zoom', - /** - * The tooltip title for the label appearing when a chart is zoomed. - * - * @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. Defaults - * to a single space character, which is recommended in - * [ISO 31-0](https://en.wikipedia.org/wiki/ISO_31-0#Numbers) and works - * across Anglo-American and continental European languages. - * - * @default \u0020 - * @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. - * - * ```js - * Highcharts.setOptions({ - * global: { - * useUTC: false - * } - * }); - * ``` - */ - /** - * _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](https://github.com/canvg/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. - * - * @deprecated - * - * @type {string} - * @default https://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 - * - * @type {boolean} - * @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 - * - * @type {Function} - * @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 - * - * @type {Function} - * @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 - * - * @type {string} - * @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 - * - * @type {number} - * @product highcharts highstock - * @apioption global.timezoneOffset - */ - global: {}, - /** - * 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 - * @optionparent time - */ - time: { - /** - * A custom `Date` class for advanced date handling. For example, - * [JDate](https://github.com/tahajahangir/jdate) can be hooked in to - * handle Jalali dates. - * - * @type {*} - * @since 4.0.4 - * @product highcharts highstock gantt - */ - Date: void 0, - /** - * 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. - * - * @see [global.timezoneOffset](#global.timezoneOffset) - * - * @sample {highcharts|highstock} highcharts/time/gettimezoneoffset/ - * Use moment.js to draw Oslo time regardless of browser locale - * - * @type {Highcharts.TimezoneOffsetCallbackFunction} - * @since 4.1.0 - * @product highcharts highstock gantt - */ - getTimezoneOffset: void 0, - /** - * Requires [moment.js](https://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. - * - * @see [getTimezoneOffset](#time.getTimezoneOffset) - * - * @sample {highcharts|highstock} highcharts/time/timezone/ - * Europe/Oslo - * - * @type {string} - * @since 5.0.7 - * @product highcharts highstock gantt - */ - timezone: void 0, - /** - * 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. - * - * @see [time.getTimezoneOffset](#time.getTimezoneOffset) - * - * @sample {highcharts|highstock} highcharts/time/timezoneoffset/ - * Timezone offset - * - * @since 3.0.8 - * @product highcharts highstock gantt - */ - timezoneOffset: 0, - /** - * 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. - * - * @sample {highcharts} highcharts/time/useutc-true/ - * True by default - * @sample {highcharts} highcharts/time/useutc-false/ - * False - */ - useUTC: true - }, - /** - * General options for the chart. - */ - chart: { - /** - * Default `mapData` for all series. If set to a string, it functions - * as an index into the `Highcharts.maps` array. Otherwise it is - * interpreted as map data. - * - * @see [mapData](#series.map.mapData) - * - * @sample maps/demo/geojson - * Loading geoJSON data - * @sample maps/chart/topojson - * Loading topoJSON converted to geoJSON - * - * @type {string|Array<*>|Highcharts.GeoJSON} - * @since 5.0.0 - * @product highmaps - * @apioption chart.map - */ - /** - * Set lat/lon transformation definitions for the chart. If not defined, - * these are extracted from the map data. - * - * @type {*} - * @since 5.0.0 - * @product highmaps - * @apioption chart.mapTransforms - */ - /** - * 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. - * - * If `startOnTick` or `endOnTick` in an Axis options are set to false, - * then the `alignTicks ` will be disabled for the Axis. - * - * Disabled for logarithmic axes. - * - * @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 - * - * @type {boolean} - * @default true - * @product highcharts highstock gantt - * @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: - * - * - `defer`: The animation delay time in milliseconds. - * - * - `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](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-animation-easing/). - * - * When zooming on a series with less than 100 points, the chart redraw - * will be done with animation, but in case of more data points, it is - * necessary to set this option to ensure animation on zoom. - * - * @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 - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - * @default undefined - * @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 were passed to - * the `addSeries` method. Returning false prevents the series from - * being added. - * - * @sample {highcharts} highcharts/chart/events-addseries/ - * Alert on add series - * @sample {highstock} stock/chart/events-addseries/ - * Alert on add series - * - * @type {Highcharts.ChartAddSeriesCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Chart - * @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. - * - * ```js - * click: function(e) { - * console.log( - * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value), - * e.yAxis[0].value - * ) - * } - * ``` - * - * @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 - * - * @type {Highcharts.ChartClickCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Chart - * @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. - * - * @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 - * - * @type {Highcharts.ChartLoadCallbackFunction} - * @context Highcharts.Chart - * @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. - * - * @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 - * - * @type {Highcharts.ChartRedrawCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Chart - * @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 {Highcharts.ChartRenderCallbackFunction} - * @since 5.0.7 - * @context Highcharts.Chart - * @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()` or return false. - * - * 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. - * - * ```js - * 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); - * } - * ``` - * - * @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) - * - * @type {Highcharts.ChartSelectionCallbackFunction} - * @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. - * - * @sample {highcharts} highcharts/chart/margins-zero/ - * Zero margins - * @sample {highstock} stock/chart/margin-zero/ - * Zero margins - * - * @type {number|Array<number>} - * @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`. - * - * @sample {highcharts} highcharts/chart/marginbottom/ - * 100px bottom margin - * @sample {highstock} stock/chart/marginbottom/ - * 100px bottom margin - * @sample {highmaps} maps/chart/margin/ - * 100px margins - * - * @type {number} - * @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`. - * - * @sample {highcharts} highcharts/chart/marginleft/ - * 150px left margin - * @sample {highstock} stock/chart/marginleft/ - * 150px left margin - * @sample {highmaps} maps/chart/margin/ - * 100px margins - * - * @type {number} - * @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`. - * - * @sample {highcharts} highcharts/chart/marginright/ - * 100px right margin - * @sample {highstock} stock/chart/marginright/ - * 100px right margin - * @sample {highmaps} maps/chart/margin/ - * 100px margins - * - * @type {number} - * @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`. - * - * @sample {highcharts} highcharts/chart/margintop/ 100px top margin - * @sample {highstock} stock/chart/margintop/ - * 100px top margin - * @sample {highmaps} maps/chart/margin/ - * 100px margins - * - * @type {number} - * @since 2.0 - * @apioption chart.marginTop - */ - /** - * Callback function to override the default function that formats all - * the numbers in the chart. Returns a string with the formatted number. - * - * @sample highcharts/members/highcharts-numberformat - * Arabic digits in Highcharts - * @type {Highcharts.NumberFormatterCallbackFunction} - * @since 8.0.0 - * @apioption chart.numberFormatter - */ - /** - * 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`). - * - * @type {string} - * @since 4.0.3 - * @product highcharts gantt - * @validvalue ["alt", "ctrl", "meta", "shift"] - * @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`. - * - * @sample {highcharts} highcharts/chart/pankey/ Zooming and panning - * @sample {highstock} stock/chart/panning/ Zooming and xy panning - * - * @product highcharts highstock gantt - * @apioption chart.panning - */ - /** - * Enable or disable chart panning. - * - * @type {boolean} - * @default {highcharts} false - * @default {highstock} true - * @apioption chart.panning.enabled - */ - /** - * Decides in what dimensions the user can pan the chart. Can be - * one of `x`, `y`, or `xy`. - * - * @sample {highcharts} highcharts/chart/panning-type - * Zooming and xy panning - * - * @type {string} - * @validvalue ["x", "y", "xy"] - * @default x - * @apioption chart.panning.type - */ - /** - * 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. - * - * @type {string} - * @default {highcharts} undefined - * @default {highstock} x - * @since 3.0 - * @product highcharts highstock gantt - * @validvalue ["x", "y", "xy"] - * @apioption chart.pinchType - */ - /** - * Whether to apply styled mode. When in styled mode, no presentational - * attributes or CSS are applied to the chart SVG. Instead, CSS rules - * are required to style the chart. The default style sheet is - * available from `https://code.highcharts.com/css/highcharts.css`. - * - * @type {boolean} - * @default false - * @since 7.0 - * @apioption chart.styledMode - */ - styledMode: false, - /** - * The corner radius of the outer chart border. - * - * @sample {highcharts} highcharts/chart/borderradius/ - * 20px radius - * @sample {highstock} stock/chart/border/ - * 10px radius - * @sample {highmaps} maps/chart/border/ - * Border options - * - */ - borderRadius: 0, - /** - * In styled mode, this sets how many colors the class names - * should rotate between. With ten colors, series (or points) are - * given class names like `highcharts-color-0`, `highcharts-color-0` - * [...] `highcharts-color-9`. The equivalent in non-styled mode - * is to set colors using the [colors](#colors) setting. - * - * @since 5.0.0 - */ - colorCount: 10, - /** - * Alias of `type`. - * - * @sample {highcharts} highcharts/chart/defaultseriestype/ - * Bar - * - * @deprecated - * - * @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. - * - * @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 - * - * @since 1.2.0 - * @product highcharts highstock gantt - */ - 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). - * - * @sample {highcharts} highcharts/chart/inverted/ - * Inverted line - * @sample {highstock} stock/navigator/inverted/ - * Inverted stock chart - * - * @type {boolean} - * @default false - * @product highcharts highstock gantt - * @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<number>} - * @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: { - /** - * What frame the button placement should be related to. Can be - * either `plotBox` or `spacingBox`. - * - * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/ - * Relative to the chart - * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/ - * Relative to the chart - * - * @type {Highcharts.ButtonRelativeToValue} - * @default plot - * @since 2.2 - * @apioption chart.resetZoomButton.relativeTo - */ - /** - * 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`. - * - * @sample {highcharts} highcharts/chart/resetzoombutton-theme/ - * Theming the button - * @sample {highstock} highcharts/chart/resetzoombutton-theme/ - * Theming the button - * - * @type {Highcharts.SVGAttributes} - * @since 2.2 - */ - theme: { - /** @internal */ - zIndex: 6 - }, - /** - * The position of the button. - * - * @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 - * - * @type {Highcharts.AlignObject} - * @since 2.2 - */ - position: { - /** - * The horizontal alignment of the button. - */ - align: 'right', - /** - * The horizontal offset of the button. - */ - x: -10, - /** - * The vertical alignment of the button. - * - * @type {Highcharts.VerticalAlignValue} - * @default top - * @apioption chart.resetZoomButton.position.verticalAlign - */ - /** - * The vertical offset of the button. - */ - y: 10 - } - }, - /** - * The pixel width of the plot area border. - * - * @sample {highcharts} highcharts/chart/plotborderwidth/ - * 1px border - * @sample {highstock} stock/chart/plotborder/ - * 2px border - * @sample {highmaps} maps/chart/plotborder/ - * Plot border options - * - * @type {number} - * @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`. - * - * @sample {highcharts} highcharts/chart/plotshadow/ - * Plot shadow - * @sample {highstock} stock/chart/plotshadow/ - * Plot shadow - * @sample {highmaps} maps/chart/plotborder/ - * Plot border options - * - * @type {boolean|Highcharts.CSSObject} - * @default false - * @apioption chart.plotShadow - */ - /** - * When true, cartesian charts like line, spline, area and column are - * transformed into the polar coordinate system. This produces _polar - * charts_, also known as _radar charts_. - * - * @sample {highcharts} highcharts/demo/polar/ - * Polar chart - * @sample {highcharts} highcharts/demo/polar-wind-rose/ - * Wind rose, stacked polar column chart - * @sample {highcharts} highcharts/demo/polar-spider/ - * Spider web chart - * @sample {highcharts} highcharts/parallel-coordinates/polar/ - * Star plot, multivariate data in a polar chart - * - * @type {boolean} - * @default false - * @since 2.3.0 - * @product highcharts - * @requires highcharts-more - * @apioption chart.polar - */ - /** - * Whether to reflow the chart to fit the width of the container div - * on resizing the window. - * - * @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 - * - * @type {boolean} - * @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. - * - * @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 - * - * @type {string|Highcharts.HTMLDOMElement} - * @apioption chart.renderTo - */ - /** - * The background color of the marker square when selecting (zooming - * in on) an area of the chart. - * - * @see In styled mode, the selection marker fill is set with the - * `.highcharts-selection-marker` class. - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @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`. - * - * @sample {highcharts} highcharts/chart/shadow/ - * Shadow - * @sample {highstock} stock/chart/shadow/ - * Shadow - * @sample {highmaps} maps/chart/border/ - * Chart border and shadow - * - * @type {boolean|Highcharts.CSSObject} - * @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. - * - * @sample {highcharts} highcharts/chart/showaxes-false/ - * False by default - * @sample {highcharts} highcharts/chart/showaxes-true/ - * True - * - * @type {boolean} - * @since 1.2.5 - * @product highcharts gantt - * @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). - * - * @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 - * - * @type {number} - * @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). - * - * @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 - * - * @type {number} - * @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). - * - * @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 - * - * @type {number} - * @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). - * - * @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 - * - * @type {number} - * @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. - * - * @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 - * - * @type {Highcharts.CSSObject} - * @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) and [series](#series) or can - * be a series provided by an additional module. - * - * In TypeScript this option has no effect in sense of typing and - * instead the `type` option must always be set in the series. - * - * @sample {highcharts} highcharts/chart/type-bar/ - * Bar - * @sample {highstock} stock/chart/type/ - * Areaspline - * @sample {highmaps} maps/chart/type-mapline/ - * Mapline - * - * @type {string} - * @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`. - * - * @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 - * - * @type {string} - * @product highcharts highstock gantt - * @validvalue ["x", "y", "xy"] - * @apioption chart.zoomType - */ - /** - * An explicit width for the chart. By default (when `null`) the width - * is calculated from the offset width of the containing element. - * - * @sample {highcharts} highcharts/chart/width/ - * 800px wide - * @sample {highstock} stock/chart/width/ - * 800px wide - * @sample {highmaps} maps/chart/size/ - * Chart with explicit size - * - * @type {null|number|string} - */ - 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. - * - * @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 - * - * @type {null|number|string} - */ - height: null, - /** - * The color of the outer chart border. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ - borderColor: '#335cad', - /** - * The pixel width of the outer chart border. - * - * @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 - * - * @type {number} - * @default 0 - * @apioption chart.borderWidth - */ - /** - * The background color or gradient for the outer chart area. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ - backgroundColor: '#ffffff', - /** - * The background color or gradient for the plot area. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @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. - * - * @see In styled mode, a plot background image can be set with the - * `.highcharts-plot-background` class and a [custom pattern]( - * https://www.highcharts.com/docs/chart-design-and-style/ - * gradients-shadows-and-patterns). - * - * @sample {highcharts} highcharts/chart/plotbackgroundimage/ - * Skies - * @sample {highstock} stock/chart/plotbackgroundimage/ - * Skies - * - * @type {string} - * @apioption chart.plotBackgroundImage - */ - /** - * The color of the inner chart or plot area border. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ - plotBorderColor: '#cccccc' - }, - /** - * The chart's main title. - * - * @sample {highmaps} maps/title/title/ - * Title options demonstrated - */ - title: { - /** - * When the title is floating, the plot area will not move to make space - * for it. - * - * @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 - * - * @type {boolean} - * @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. - * - * @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 - * - * @type {Highcharts.CSSObject} - * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" } - * @default {highstock} { "color": "#333333", "fontSize": "16px" } - * @apioption title.style - */ - /** - * Whether to - * [use HTML](https://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`. - * - * @sample {highcharts} highcharts/title/verticalalign/ - * Chart title in bottom right corner - * @sample {highstock} stock/chart/title-verticalalign/ - * Chart title in bottom right corner - * - * @type {Highcharts.VerticalAlignValue} - * @since 2.1 - * @apioption title.verticalAlign - */ - /** - * The x position of the title relative to the alignment within - * `chart.spacingLeft` and `chart.spacingRight`. - * - * @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) - * - * @type {number} - * @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. - * - * @sample {highcharts} highcharts/title/y/ - * Title inside the plot area - * @sample {highstock} stock/chart/title-verticalalign/ - * Chart title in bottom right corner - * - * @type {number} - * @since 2.0 - * @apioption title.y - */ - /** - * The title of the chart. To disable the title, set the `text` to - * `undefined`. - * - * @sample {highcharts} highcharts/title/text/ - * Custom title - * @sample {highstock} stock/chart/title-text/ - * Custom title - * - * @default {highcharts|highmaps} Chart title - * @default {highstock} undefined - */ - text: 'Chart title', - /** - * The horizontal alignment of the title. Can be one of "left", "center" - * and "right". - * - * @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) - * - * @type {Highcharts.AlignValue} - * @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. - * - * @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 - * - * @since 2.1 - */ - margin: 15, - /** - * Adjustment made to the title width, normally to reserve space for - * the exporting burger menu. - * - * @sample highcharts/title/widthadjust/ - * Wider menu, greater padding - * - * @since 4.2.5 - */ - widthAdjust: -44 - }, - /** - * 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: { - /** - * When the subtitle is floating, the plot area will not move to make - * space for it. - * - * @sample {highcharts} highcharts/subtitle/floating/ - * Floating title and subtitle - * @sample {highstock} stock/chart/subtitle-footnote - * Footnote floating at bottom right of plot area - * - * @type {boolean} - * @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. - * - * @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 - * - * @type {Highcharts.CSSObject} - * @default {"color": "#666666"} - * @apioption subtitle.style - */ - /** - * Whether to - * [use HTML](https://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 middle, the subtitle behaves as - * floating. - * - * @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 - * - * @type {Highcharts.VerticalAlignValue} - * @since 2.1 - * @apioption subtitle.verticalAlign - */ - /** - * The x position of the subtitle relative to the alignment within - * `chart.spacingLeft` and `chart.spacingRight`. - * - * @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 - * - * @type {number} - * @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. - * - * @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 - * - * @type {number} - * @since 2.0 - * @apioption subtitle.y - */ - /** - * The subtitle of the chart. - * - * @sample {highcharts|highstock} highcharts/subtitle/text/ - * Custom subtitle - * @sample {highcharts|highstock} highcharts/subtitle/text-formatted/ - * Formatted and linked text. - */ - text: '', - /** - * The horizontal alignment of the subtitle. Can be one of "left", - * "center" and "right". - * - * @sample {highcharts} highcharts/subtitle/align/ - * Footnote at right of plot area - * @sample {highstock} stock/chart/subtitle-footnote - * Footnote at bottom right of plot area - * - * @type {Highcharts.AlignValue} - * @since 2.0 - */ - align: 'center', - /** - * Adjustment made to the subtitle width, normally to reserve space - * for the exporting burger menu. - * - * @see [title.widthAdjust](#title.widthAdjust) - * - * @sample highcharts/title/widthadjust/ - * Wider menu, greater padding - * - * @since 4.2.5 - */ - widthAdjust: -44 - }, - /** - * The chart's caption, which will render below the chart and will be part - * of exported charts. The caption can be updated after chart initialization - * through the `Chart.update` or `Chart.caption.update` methods. - * - * @sample highcharts/caption/text/ - * A chart with a caption - * @since 7.2.0 - */ - caption: { - /** - * When the caption is floating, the plot area will not move to make - * space for it. - * - * @type {boolean} - * @default false - * @apioption caption.floating - */ - /** - * The margin between the caption and the plot area. - */ - margin: 15, - /** - * CSS styles for the caption. - * - * In styled mode, the caption style is given in the - * `.highcharts-caption` class. - * - * @sample {highcharts} highcharts/css/titles/ - * Styled mode - * - * @type {Highcharts.CSSObject} - * @default {"color": "#666666"} - * @apioption caption.style - */ - /** - * Whether to - * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) - * to render the text. - * - * @type {boolean} - * @default false - * @apioption caption.useHTML - */ - /** - * The x position of the caption relative to the alignment within - * `chart.spacingLeft` and `chart.spacingRight`. - * - * @type {number} - * @default 0 - * @apioption caption.x - */ - /** - * The y position of the caption relative to the alignment within - * `chart.spacingTop` and `chart.spacingBottom`. - * - * @type {number} - * @apioption caption.y - */ - /** - * The caption text of the chart. - * - * @sample {highcharts} highcharts/caption/text/ - * Custom caption - */ - text: '', - /** - * The horizontal alignment of the caption. Can be one of "left", - * "center" and "right". - * - * @type {Highcharts.AlignValue} - */ - align: 'left', - /** - * The vertical alignment of the caption. Can be one of `"top"`, - * `"middle"` and `"bottom"`. When middle, the caption behaves as - * floating. - * - * @type {Highcharts.VerticalAlignValue} - */ - verticalAlign: 'bottom' - }, - /** - * 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. - * - * This option is deprecated since v7.1.2. Instead, use - * [annotations](#annotations) that support labels. - * - * @deprecated - * @product highcharts highstock - */ - labels: { - /** - * An HTML label that can be positioned anywhere in the chart area. - * - * @deprecated - * @type {Array<*>} - * @apioption labels.items - */ - /** - * Inner HTML or text for the label. - * - * @deprecated - * @type {string} - * @apioption labels.items.html - */ - /** - * CSS styles for each label. To position the label, use left and top - * like this: - * ```js - * style: { - * left: '100px', - * top: '100px' - * } - * ``` - * - * @deprecated - * @type {Highcharts.CSSObject} - * @apioption labels.items.style - */ - /** - * Shared CSS styles for all labels. - * - * @deprecated - * @type {Highcharts.CSSObject} - * @default {"color": "#333333", "position": "absolute"} - */ - style: { - /** - * @ignore-option - */ - position: 'absolute', - /** - * @ignore-option - */ - 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](https://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. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @apioption legend.backgroundColor - */ - /** - * The width of the drawn border around the legend. - * - * @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 - * - * @type {number} - * @default 0 - * @apioption legend.borderWidth - */ - /** - * Enable or disable the legend. There is also a series-specific option, - * [showInLegend](#plotOptions.series.showInLegend), that can hide the - * series from the legend. In some series types this is `false` by - * default, so it must set to `true` in order to show the legend for the - * series. - * - * @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 - * @default {gantt} false - */ - 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. - * - * @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 - * - * @type {Highcharts.AlignValue} - * @since 2.0 - */ - align: 'center', - /** - * If the [layout](legend.layout) is `horizontal` and the legend items - * span over two lines or more, whether to align the items into vertical - * columns. Setting this to `false` makes room for more items, but will - * look more messy. - * - * @since 6.1.0 - */ - alignColumns: true, - /** - * When the legend is floating, the plot area ignores it and is allowed - * to be placed below it. - * - * @sample {highcharts} highcharts/legend/floating-false/ - * False by default - * @sample {highcharts} highcharts/legend/floating-true/ - * True - * @sample {highmaps} maps/legend/alignment/ - * Floating legend - * - * @type {boolean} - * @default false - * @since 2.1 - * @apioption legend.floating - */ - /** - * The layout of the legend items. Can be one of `horizontal` or - * `vertical` or `proximate`. When `proximate`, the legend items will be - * placed as close as possible to the graphs they're representing, - * except in inverted charts or when the legend position doesn't allow - * it. - * - * @sample {highcharts} highcharts/legend/layout-horizontal/ - * Horizontal by default - * @sample {highcharts} highcharts/legend/layout-vertical/ - * Vertical - * @sample highcharts/legend/layout-proximate - * Labels proximate to the data - * @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 - * - * @validvalue ["horizontal", "vertical", "proximate"] - */ - layout: 'horizontal', - /** - * In a legend with horizontal layout, the itemDistance defines the - * pixel distance between each item. - * - * @sample {highcharts} highcharts/legend/layout-horizontal/ - * 50px item distance - * @sample {highstock} highcharts/legend/layout-horizontal/ - * 50px item distance - * - * @type {number} - * @default {highcharts} 20 - * @default {highstock} 20 - * @default {highmaps} 8 - * @since 3.0.3 - * @apioption legend.itemDistance - */ - /** - * The pixel bottom margin for each legend item. - * - * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ - * Padding and item margins demonstrated - * @sample {highmaps} maps/legend/padding-itemmargin/ - * Padding and item margins demonstrated - * - * @type {number} - * @default 0 - * @since 2.2.0 - * @apioption legend.itemMarginBottom - */ - /** - * The pixel top margin for each legend item. - * - * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ - * Padding and item margins demonstrated - * @sample {highmaps} maps/legend/padding-itemmargin/ - * Padding and item margins demonstrated - * - * @type {number} - * @default 0 - * @since 2.2.0 - * @apioption legend.itemMarginTop - */ - /** - * The width for each legend item. By default the items are laid out - * successively. In a [horizontal layout](legend.layout), if the items - * are laid out across two rows or more, they will be vertically aligned - * depending on the [legend.alignColumns](legend.alignColumns) option. - * - * @sample {highcharts} highcharts/legend/itemwidth-default/ - * Undefined by default - * @sample {highcharts} highcharts/legend/itemwidth-80/ - * 80 for aligned legend items - * - * @type {number} - * @since 2.0 - * @apioption legend.itemWidth - */ - /** - * A [format string](https://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 - */ - /* eslint-disable valid-jsdoc */ - /** - * 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`. - * - * @sample {highcharts} highcharts/legend/labelformatter/ - * Add text - * @sample {highmaps} maps/legend/labelformatter/ - * Data classes with label formatter - * - * @type {Highcharts.FormatterCallbackFunction<Point|Series>} - */ - labelFormatter: function () { - /** eslint-enable valid-jsdoc */ - 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`. - * - * @sample {highcharts} highcharts/legend/lineheight/ - * Setting padding - * - * @deprecated - * - * @type {number} - * @default 16 - * @since 2.0 - * @product highcharts gantt - * @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. - * - * @sample {highcharts} highcharts/legend/margin-default/ - * 12 pixels by default - * @sample {highcharts} highcharts/legend/margin-30/ - * 30 pixels - * - * @type {number} - * @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} - * @since 2.3.0 - * @apioption legend.maxHeight - */ - /** - * The color of the drawn border around the legend. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ - borderColor: '#999999', - /** - * The border corner radius of the legend. - * - * @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 - */ - 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](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-enabled-false/). - */ - navigation: { - /** - * 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. - * - * @sample {highcharts} highcharts/legend/navigation/ - * Legend page navigation demonstrated - * @sample {highstock} highcharts/legend/navigation/ - * Legend page navigation demonstrated - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - * @default true - * @since 2.2.4 - * @apioption legend.navigation.animation - */ - /** - * The pixel size of the up and down arrows in the legend paging - * navigation. - * - * @sample {highcharts} highcharts/legend/navigation/ - * Legend page navigation demonstrated - * @sample {highstock} highcharts/legend/navigation/ - * Legend page navigation demonstrated - * - * @type {number} - * @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]( - * https://www.highcharts.com/products/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. - * - * @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 - * - * @type {Highcharts.CSSObject} - * @since 2.2.4 - * @apioption legend.navigation.style - */ - /** - * The color for the active up or down arrow in the legend page - * navigation. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @since 2.2.4 - */ - activeColor: '#003399', - /** - * The color of the inactive up or down arrow in the legend page - * navigation. . - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @since 2.2.4 - */ - inactiveColor: '#cccccc' - }, - /** - * The inner padding of the legend box. - * - * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ - * Padding and item margins demonstrated - * @sample {highmaps} maps/legend/padding-itemmargin/ - * Padding and item margins demonstrated - * - * @type {number} - * @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. - * - * @see [yAxis.reversedStacks](#yAxis.reversedStacks), - * [series.legendIndex](#series.legendIndex) - * - * @sample {highcharts} highcharts/legend/reversed/ - * Stacked bar with reversed legend - * - * @type {boolean} - * @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. - * - * @sample {highcharts} highcharts/legend/rtl/ - * Symbol to the right - * - * @type {boolean} - * @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. - * - * @deprecated - * - * @type {Highcharts.CSSObject} - * @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 `undefined` to wrap - * text instead. A `width` property can be added to control the text - * width. - * - * @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 - * - * @type {Highcharts.CSSObject} - * @default {"color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis"} - */ - itemStyle: { - /** - * @ignore - */ - color: '#333333', - /** - * @ignore - */ - cursor: 'pointer', - /** - * @ignore - */ - fontSize: '12px', - /** - * @ignore - */ - fontWeight: 'bold', - /** - * @ignore - */ - 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. - * - * @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 - * - * @type {Highcharts.CSSObject} - * @default {"color": "#000000"} - */ - itemHoverStyle: { - /** - * @ignore - */ - 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. - * - * @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 - * - * @type {Highcharts.CSSObject} - * @default {"color": "#cccccc"} - */ - itemHiddenStyle: { - /** - * @ignore - */ - 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`. - * - * @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 - * - * @type {boolean|Highcharts.CSSObject} - */ - shadow: false, - /** - * Default styling for the checkbox next to a legend item when - * `showCheckbox` is true. - * - * @type {Highcharts.CSSObject} - * @default {"width": "13px", "height": "13px", "position":"absolute"} - */ - itemCheckboxStyle: { - /** - * @ignore - */ - position: 'absolute', - /** - * @ignore - */ - width: '13px', - /** - * @ignore - */ - 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. - * - * @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. - * - * @sample {highmaps} maps/legend/layout-vertical-sized/ - * Sized vertical gradient - * @sample {highmaps} maps/legend/padding-itemmargin/ - * No distance between data classes - * - * @type {number} - * @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`. - * - * @sample {highcharts} highcharts/legend/symbolradius/ - * Round symbols - * @sample {highstock} highcharts/legend/symbolradius/ - * Round symbols - * @sample {highmaps} highcharts/legend/symbolradius/ - * Round symbols - * - * @type {number} - * @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. - * - * @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 - * - * @type {number} - * @apioption legend.symbolWidth - */ - /** - * Whether to [use HTML](https://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. If a number is set, it translates to - * pixels. Since v7.0.2 it allows setting a percent string of the full - * chart width, for example `40%`. - * - * Defaults to the full chart width for legends below or above the - * chart, half the chart width for legends to the left and right. - * - * @sample {highcharts} highcharts/legend/width/ - * Aligned to the plot area - * @sample {highcharts} highcharts/legend/width-percent/ - * A percent of the chart width - * - * @type {number|string} - * @since 2.0 - * @apioption legend.width - */ - /** - * The pixel padding between the legend item symbol and the legend - * item text. - * - * @sample {highcharts} highcharts/legend/symbolpadding/ - * Greater symbol width and padding - */ - 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. - * - * When the [layout](#legend.layout) option is `proximate`, the - * `verticalAlign` option doesn't apply. - * - * @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 - * - * @type {Highcharts.VerticalAlignValue} - * @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. - * - * @sample {highcharts} highcharts/legend/width/ - * Aligned to the plot area - * - * @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. - * - * @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 - * - * @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} - * @since 3.0 - * @apioption legend.title.text - */ - /** - * Generic CSS styles for the legend title. - * - * @see In styled mode, the legend title is styled with the - * `.highcharts-legend-title` class. - * - * @type {Highcharts.CSSObject} - * @default {"fontWeight": "bold"} - * @since 3.0 - */ - style: { - /** - * @ignore - */ - 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. - * - * @sample highcharts/loading/hideduration/ - * Fade in and out over a second - * - * @type {number} - * @default 100 - * @since 1.2.0 - * @apioption loading.hideDuration - */ - /** - * The duration in milliseconds of the fade in effect. - * - * @sample highcharts/loading/hideduration/ - * Fade in and out over a second - * - * @type {number} - * @default 100 - * @since 1.2.0 - * @apioption loading.showDuration - */ - /** - * CSS styles for the loading label `span`. - * - * @see In styled mode, the loading label is styled with the - * `.highcharts-loading-inner` class. - * - * @sample {highcharts|highmaps} highcharts/loading/labelstyle/ - * Vertically centered - * @sample {highstock} stock/loading/general/ - * Label styles - * - * @type {Highcharts.CSSObject} - * @default {"fontWeight": "bold", "position": "relative", "top": "45%"} - * @since 1.2.0 - */ - labelStyle: { - /** - * @ignore - */ - fontWeight: 'bold', - /** - * @ignore - */ - position: 'relative', - /** - * @ignore - */ - top: '45%' - }, - /** - * CSS styles for the loading screen that covers the plot area. - * - * In styled mode, the loading label is styled with the - * `.highcharts-loading` class. - * - * @sample {highcharts|highmaps} highcharts/loading/style/ - * Gray plot area, white text - * @sample {highstock} stock/loading/general/ - * Gray plot area, white text - * - * @type {Highcharts.CSSObject} - * @default {"position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center"} - * @since 1.2.0 - */ - style: { - /** - * @ignore - */ - position: 'absolute', - /** - * @ignore - */ - backgroundColor: '#ffffff', - /** - * @ignore - */ - opacity: 0.5, - /** - * @ignore - */ - textAlign: 'center' - } - }, - /** - * Options for the tooltip that appears when the user hovers over a - * series or point. - * - * @declare Highcharts.TooltipOptions - */ - tooltip: { - /** - * The color of the tooltip border. When `undefined`, the border takes - * the color of the corresponding series or point. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @apioption tooltip.borderColor - */ - /** - * A CSS class name to apply to the tooltip's container div, - * allowing unique CSS styling for each chart. - * - * @type {string} - * @apioption tooltip.className - */ - /** - * 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). - * - * @sample {highcharts} highcharts/tooltip/crosshairs-x/ - * Enable a crosshair for the x value - * - * @deprecated - * - * @type {*} - * @default true - * @apioption tooltip.crosshairs - */ - /** - * Distance from point to tooltip in pixels. - * - * @type {number} - * @default 16 - * @apioption tooltip.distance - */ - /** - * 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 pie, polygon, map, sankey - * and wordcloud 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 update as the finger 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. - * - * Note the difference to [followPointer](#tooltip.followPointer) that - * only defines the _position_ of the tooltip. If `followPointer` is - * false in for example a column series, the tooltip will show above or - * below the column, but as `followTouchMove` is true, the tooltip will - * jump from column to column as the user swipes across the plot area. - * - * @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. In - * case of single or [shared](#tooltip.shared) tooltips, a string should - * be returned. In case of [split](#tooltip.split) tooltips, it should - * return an array where the first item is the header, and subsequent - * items are mapped to the points. 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 HTML tags are supported: `b`, - * `br`, `em`, `i`, `span`, `strong`. Spans can be styled with a `style` - * attribute, but only text-related CSS, that is shared with SVG, is - * handled. - * - * The available data in the formatter differ a bit depending on whether - * the tooltip is shared or split, or belongs to a single point. In a - * shared/split 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. - * - * @sample {highcharts} highcharts/tooltip/formatter-simple/ - * Simple string formatting - * @sample {highcharts} highcharts/tooltip/formatter-shared/ - * Formatting with shared tooltip - * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/ - * Formatting with split tooltip - * @sample highcharts/tooltip/formatter-conditional-default/ - * Extending default formatter - * @sample {highstock} stock/tooltip/formatter/ - * Formatting with shared tooltip - * @sample {highmaps} maps/tooltip/formatter/ - * String formatting - * - * @type {Highcharts.TooltipFormatterCallbackFunction} - * @apioption tooltip.formatter - */ - /** - * Callback function to format the text of the tooltip for - * visible null points. - * Works analogously to [formatter](#tooltip.formatter). - * - * @sample highcharts/plotoptions/series-nullformat - * Format data label and tooltip for null point. - * - * @type {Highcharts.TooltipFormatterCallbackFunction} - * @apioption tooltip.nullFormatter - */ - /** - * 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 - */ - /** - * Whether to allow the tooltip to render outside the chart's SVG - * element box. By default (`false`), the tooltip is rendered within the - * chart's SVG element, which results in the tooltip being aligned - * inside the chart area. For small charts, this may result in clipping - * or overlapping. When `true`, a separate SVG element is created and - * overlaid on the page, allowing the tooltip to be aligned inside the - * page itself. - * - * Defaults to `true` if `chart.scrollablePlotArea` is activated, - * otherwise `false`. - * - * @sample highcharts/tooltip/outside - * Small charts with tooltips outside - * - * @type {boolean|undefined} - * @default undefined - * @since 6.1.1 - * @apioption tooltip.outside - */ - /** - * A callback function for formatting the HTML output for a single point - * in the tooltip. Like the `pointFormat` string, but with more - * flexibility. - * - * @type {Highcharts.FormatterCallbackFunction<Highcharts.Point>} - * @since 4.1.0 - * @context Highcharts.Point - * @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. - * - * Since v7, when [tooltip.split](#tooltip.split) option is enabled, - * positioner is called for each of the boxes separately, including - * xAxis header. xAxis header is not a point, instead `point` argument - * contains info: - * `{ plotX: Number, plotY: Number, isHeader: Boolean }` - * - * - * The return should be an object containing x and y values, for example - * `{ x: 100, y: 100 }`. - * - * @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 - * @sample {highstock} stock/tooltip/split-positioner/ - * Split tooltip with fixed positions - * @sample {highstock} stock/tooltip/positioner-scrollable-plotarea/ - * Scrollable plot area combined with tooltip positioner - * - * @type {Highcharts.TooltipPositionerCallbackFunction} - * @since 2.2.4 - * @apioption tooltip.positioner - */ - /** - * The name of a symbol to use for the border around the tooltip. Can - * be one of: `"callout"`, `"circle"`, or `"square"`. When - * [tooltip.split](#tooltip.split) - * option is enabled, shape is applied to all boxes except header, which - * is controlled by - * [tooltip.headerShape](#tooltip.headerShape). - * - * Custom callbacks for symbol path generation can also be added to - * `Highcharts.SVGRenderer.prototype.symbols` the same way as for - * [series.marker.symbol](plotOptions.line.marker.symbol). - * - * @type {Highcharts.TooltipShapeValue} - * @default callout - * @since 4.0 - * @apioption tooltip.shape - */ - /** - * The name of a symbol to use for the border around the tooltip - * header. Applies only when [tooltip.split](#tooltip.split) is - * enabled. - * - * Custom callbacks for symbol path generation can also be added to - * `Highcharts.SVGRenderer.prototype.symbols` the same way as for - * [series.marker.symbol](plotOptions.line.marker.symbol). - * - * @see [tooltip.shape](#tooltip.shape) - * - * @sample {highstock} stock/tooltip/split-positioner/ - * Different shapes for header and split boxes - * - * @type {Highcharts.TooltipShapeValue} - * @default callout - * @validvalue ["callout", "square"] - * @since 7.0 - * @apioption tooltip.headerShape - */ - /** - * 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. The - * `tooltip.split` option takes precedence over `tooltip.shared`. - * - * @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 - * - * @type {boolean} - * @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. This option takes precedence over `tooltip.shared`. - * - * @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. - * - * @sample highcharts/tooltip/split/ - * Split tooltip - * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/ - * Split tooltip and custom formatter callback - * - * @type {boolean} - * @default {highcharts} false - * @default {highstock} true - * @since 5.0.0 - * @product highcharts highstock - * @apioption tooltip.split - */ - /** - * Prevents the tooltip from switching or closing, when touched or - * pointed. - * - * @sample highcharts/tooltip/stickoncontact/ - * Tooltip sticks on pointer contact - * - * @type {boolean} - * @since 8.0.1 - * @apioption tooltip.stickOnContact - */ - /** - * 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. - * - * @sample {highcharts|highstock} highcharts/tooltip/footerformat/ - * A table for value alignment - * @sample {highcharts|highstock} highcharts/tooltip/fullhtml/ - * Full HTML tooltip - * @sample {highmaps} maps/tooltip/usehtml/ - * Pure HTML tooltip - * - * @type {boolean} - * @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. - * - * @sample {highcharts|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 - * - * @type {number} - * @since 2.2 - * @apioption tooltip.valueDecimals - */ - /** - * A string to prepend to each series' y value. Overridable in each - * series' tooltip options object. - * - * @sample {highcharts|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 - * - * @type {string} - * @since 2.2 - * @apioption tooltip.valuePrefix - */ - /** - * A string to append to each series' y value. Overridable in each - * series' tooltip options object. - * - * @sample {highcharts|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 - * - * @type {string} - * @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. - * - * @sample {highcharts} highcharts/tooltip/xdateformat/ - * A different format - * - * @type {string} - * @product highcharts highstock gantt - * @apioption tooltip.xDateFormat - */ - /** - * How many decimals to show for the `point.change` value when the - * `series.compare` option is set. This is overridable in each series' - * tooltip options object. The default is to preserve all decimals. - * - * @type {number} - * @since 1.0.1 - * @product highstock - * @apioption tooltip.changeDecimals - */ - /** - * Enable or disable the tooltip. - * - * @sample {highcharts} highcharts/tooltip/enabled/ - * Disabled - * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ - * Disable tooltip and show values on chart instead - */ - enabled: true, - /** - * Enable or disable animation of the tooltip. - * - * @type {boolean} - * @default true - * @since 2.3.0 - */ - animation: svg, - /** - * The radius of the rounded border corners. - * - * @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 - */ - 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](/class-reference/Highcharts#dateFormat). - * - * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats) - * - * @type {Highcharts.Dictionary<string>} - * @product highcharts highstock gantt - */ - dateTimeLabelFormats: { - /** @internal */ - millisecond: '%A, %b %e, %H:%M:%S.%L', - /** @internal */ - second: '%A, %b %e, %H:%M:%S', - /** @internal */ - minute: '%A, %b %e, %H:%M', - /** @internal */ - hour: '%A, %b %e, %H:%M', - /** @internal */ - day: '%A, %b %e, %Y', - /** @internal */ - week: 'Week from %A, %b %e, %Y', - /** @internal */ - month: '%B %Y', - /** @internal */ - 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. - * - * @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). - * - * @sample {highcharts} highcharts/tooltip/bordercolor-default/ - * 10 px by default - * @sample {highcharts} highcharts/tooltip/snap-50/ - * 50 px on graph - * - * @type {number} - * @default 10/25 - * @since 1.2.0 - * @product highcharts highstock - */ - snap: isTouchDevice ? 25 : 10, - /** - * 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`. - * - * @sample {highcharts} highcharts/tooltip/footerformat/ - * An HTML table in the tooltip - * @sample {highstock} highcharts/tooltip/footerformat/ - * An HTML table in the tooltip - * @sample {highmaps} maps/tooltip/format/ - * Format demo - * - * @type {string} - * @apioption tooltip.headerFormat - */ - headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>', - /** - * The HTML of the null point's line in the tooltip. Works analogously - * to [pointFormat](#tooltip.pointFormat). - * - * @sample {highcharts} highcharts/plotoptions/series-nullformat - * Format data label and tooltip for null point. - * - * @type {string} - * @apioption tooltip.nullFormat - */ - /** - * 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. - * - * @sample {highcharts} highcharts/tooltip/pointformat/ - * A different point format with value suffix - * @sample {highmaps} maps/tooltip/format/ - * Format demo - * - * @type {string} - * @since 2.2 - * @apioption tooltip.pointFormat - */ - pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>', - /** - * The background color or gradient for the tooltip. - * - * In styled mode, the stroke width is set in the - * `.highcharts-tooltip-box` class. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ - 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. - * - * @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 - */ - borderWidth: 1, - /** - * Whether to apply a drop shadow to the tooltip. - * - * @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 - * - * @type {boolean|Highcharts.ShadowOptionsObject} - */ - shadow: true, - /** - * CSS styles for the tooltip. The tooltip can also be styled through - * the CSS class `.highcharts-tooltip`. - * - * Note that the default `pointerEvents` style makes the tooltip ignore - * mouse events, so in order to use clickable tooltips, this value must - * be set to `auto`. - * - * @sample {highcharts} highcharts/tooltip/style/ - * Greater padding, bold text - * - * @type {Highcharts.CSSObject} - */ - style: { - /** @internal */ - color: '#333333', - /** @internal */ - cursor: 'default', - /** @internal */ - fontSize: '12px', - /** @internal */ - whiteSpace: 'nowrap' - } - }, - /** - * Highchart by default puts a credits label in the lower right corner - * of the chart. This can be changed using these options. - */ - credits: { - /** - * Credits for map source to be concatenated with conventional credit - * text. By default this is a format string that collects copyright - * information from the map if available. - * - * @see [mapTextFull](#credits.mapTextFull) - * @see [text](#credits.text) - * - * @type {string} - * @default \u00a9 <a href="{geojson.copyrightUrl}">{geojson.copyrightShort}</a> - * @since 4.2.2 - * @product highmaps - * @apioption credits.mapText - */ - /** - * Detailed credits for map source to be displayed on hover of credits - * text. By default this is a format string that collects copyright - * information from the map if available. - * - * @see [mapText](#credits.mapText) - * @see [text](#credits.text) - * - * @type {string} - * @default {geojson.copyright} - * @since 4.2.2 - * @product highmaps - * @apioption credits.mapTextFull - */ - /** - * Whether to show the credits text. - * - * @sample {highcharts} highcharts/credits/enabled-false/ - * Credits disabled - * @sample {highstock} stock/credits/enabled/ - * Credits disabled - * @sample {highmaps} maps/credits/enabled-false/ - * Credits disabled - */ - enabled: true, - /** - * The URL for the credits label. - * - * @sample {highcharts} highcharts/credits/href/ - * Custom URL and text - * @sample {highmaps} maps/credits/customized/ - * Custom URL and text - */ - href: 'https://www.highcharts.com?credits', - /** - * Position configuration for the credits label. - * - * @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 - * - * @type {Highcharts.AlignObject} - * @since 2.1 - */ - position: { - /** @internal */ - align: 'right', - /** @internal */ - x: -10, - /** @internal */ - verticalAlign: 'bottom', - /** @internal */ - y: -5 - }, - /** - * CSS styles for the credits label. - * - * @see In styled mode, credits styles can be set with the - * `.highcharts-credits` class. - * - * @type {Highcharts.CSSObject} - */ - style: { - /** @internal */ - cursor: 'pointer', - /** @internal */ - color: '#999999', - /** @internal */ - 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`. - * - * @sample {highcharts} highcharts/credits/href/ - * Custom URL and text - * @sample {highmaps} maps/credits/customized/ - * Custom URL and text - */ - text: 'Highcharts.com' - } - }; - /* eslint-disable spaced-comment */ - - ''; - /** - * Global `Time` object with default options. Since v6.0.5, time settings can be - * applied individually for each chart. If no individual settings apply, this - * `Time` object is shared by all instances. - * - * @name Highcharts.time - * @type {Highcharts.Time} - */ - H.time = new Time(merge(H.defaultOptions.global, H.defaultOptions.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](https://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 - * {@link Highcharts.Chart#time} instance to respect chart-level time settings. - * The `Highcharts.dateFormat` function only reflects global time settings set - * with `setOptions`. - * - * Supported format keys: - * - `%a`: Short weekday, like 'Mon' - * - `%A`: Long weekday, like 'Monday' - * - `%d`: Two digit day of the month, 01 to 31 - * - `%e`: Day of the month, 1 through 31 - * - `%w`: Day of the week, 0 through 6 - * - `%b`: Short month, like 'Jan' - * - `%B`: Long month, like 'January' - * - `%m`: Two digit month number, 01 through 12 - * - `%y`: Two digits year, like 09 for 2009 - * - `%Y`: Four digits year, like 2009 - * - `%H`: Two digits hours in 24h format, 00 through 23 - * - `%k`: Hours in 24h format, 0 through 23 - * - `%I`: Two digits hours in 12h format, 00 through 11 - * - `%l`: Hours in 12h format, 1 through 12 - * - `%M`: Two digits minutes, 00 through 59 - * - `%p`: Upper case AM or PM - * - `%P`: Lower case AM or PM - * - `%S`: Two digits seconds, 00 through 59 - * - `%L`: Milliseconds (naming from Ruby) - * - * @function Highcharts.dateFormat - * - * @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. - * - * @return {string} - * The formatted date. - */ - H.dateFormat = function (format, timestamp, capitalize) { - return H.time.dateFormat(format, timestamp, capitalize); - }; - var optionsModule = { - dateFormat: H.dateFormat, - defaultOptions: H.defaultOptions, - time: H.time - }; - - return optionsModule; - }); - _registerModule(_modules, 'Core/Axis/Axis.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Axis/Tick.js'], _modules['Core/Utilities.js'], _modules['Core/Options.js']], function (A, Color, H, Tick, U, O) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var animObject = A.animObject; - var addEvent = U.addEvent, - arrayMax = U.arrayMax, - arrayMin = U.arrayMin, - clamp = U.clamp, - correctFloat = U.correctFloat, - defined = U.defined, - destroyObjectProperties = U.destroyObjectProperties, - error = U.error, - extend = U.extend, - fireEvent = U.fireEvent, - format = U.format, - getMagnitude = U.getMagnitude, - isArray = U.isArray, - isFunction = U.isFunction, - isNumber = U.isNumber, - isString = U.isString, - merge = U.merge, - normalizeTickInterval = U.normalizeTickInterval, - objectEach = U.objectEach, - pick = U.pick, - relativeLength = U.relativeLength, - removeEvent = U.removeEvent, - splat = U.splat, - syncTimeout = U.syncTimeout; - /** - * Options for the path on the Axis to be calculated. - * @interface Highcharts.AxisPlotLinePathOptionsObject - */ /** - * Axis value. - * @name Highcharts.AxisPlotLinePathOptionsObject#value - * @type {number|undefined} - */ /** - * Line width used for calculation crisp line coordinates. Defaults to 1. - * @name Highcharts.AxisPlotLinePathOptionsObject#lineWidth - * @type {number|undefined} - */ /** - * If `false`, the function will return null when it falls outside the axis - * bounds. If `true`, the function will return a path aligned to the plot area - * sides if it falls outside. If `pass`, it will return a path outside. - * @name Highcharts.AxisPlotLinePathOptionsObject#force - * @type {string|boolean|undefined} - */ /** - * Used in Highstock. When `true`, plot paths (crosshair, plotLines, gridLines) - * will be rendered on all axes when defined on the first axis. - * @name Highcharts.AxisPlotLinePathOptionsObject#acrossPanes - * @type {boolean|undefined} - */ /** - * Use old coordinates (for resizing and rescaling). - * If not set, defaults to `false`. - * @name Highcharts.AxisPlotLinePathOptionsObject#old - * @type {boolean|undefined} - */ /** - * If given, return the plot line path of a pixel position on the axis. - * @name Highcharts.AxisPlotLinePathOptionsObject#translatedValue - * @type {number|undefined} - */ /** - * Used in Polar axes. Reverse the positions for concatenation of polygonal - * plot bands - * @name Highcharts.AxisPlotLinePathOptionsObject#reverse - * @type {boolean|undefined} - */ - /** - * Options for crosshairs on axes. - * - * @product highstock - * - * @typedef {Highcharts.XAxisCrosshairOptions|Highcharts.YAxisCrosshairOptions} Highcharts.AxisCrosshairOptions - */ - /** - * @typedef {"navigator"|"pan"|"rangeSelectorButton"|"rangeSelectorInput"|"scrollbar"|"traverseUpButton"|"zoom"} Highcharts.AxisExtremesTriggerValue - */ - /** - * @callback Highcharts.AxisEventCallbackFunction - * - * @param {Highcharts.Axis} this - */ - /** - * @callback Highcharts.AxisLabelsFormatterCallbackFunction - * - * @param {Highcharts.AxisLabelsFormatterContextObject<number>} this - * - * @param {Highcharts.AxisLabelsFormatterContextObject<string>} that - * - * @return {string} - */ - /** - * @interface Highcharts.AxisLabelsFormatterContextObject<T> - */ /** - * @name Highcharts.AxisLabelsFormatterContextObject<T>#axis - * @type {Highcharts.Axis} - */ /** - * @name Highcharts.AxisLabelsFormatterContextObject<T>#chart - * @type {Highcharts.Chart} - */ /** - * @name Highcharts.AxisLabelsFormatterContextObject<T>#isFirst - * @type {boolean} - */ /** - * @name Highcharts.AxisLabelsFormatterContextObject<T>#isLast - * @type {boolean} - */ /** - * @name Highcharts.AxisLabelsFormatterContextObject<T>#pos - * @type {number} - */ /** - * This can be either a numeric value or a category string. - * @name Highcharts.AxisLabelsFormatterContextObject<T>#value - * @type {T} - */ - /** - * Options for axes. - * - * @typedef {Highcharts.XAxisOptions|Highcharts.YAxisOptions|Highcharts.ZAxisOptions} Highcharts.AxisOptions - */ - /** - * @callback Highcharts.AxisPointBreakEventCallbackFunction - * - * @param {Highcharts.Axis} this - * - * @param {Highcharts.AxisPointBreakEventObject} evt - */ - /** - * @interface Highcharts.AxisPointBreakEventObject - */ /** - * @name Highcharts.AxisPointBreakEventObject#brk - * @type {Highcharts.Dictionary<number>} - */ /** - * @name Highcharts.AxisPointBreakEventObject#point - * @type {Highcharts.Point} - */ /** - * @name Highcharts.AxisPointBreakEventObject#preventDefault - * @type {Function} - */ /** - * @name Highcharts.AxisPointBreakEventObject#target - * @type {Highcharts.SVGElement} - */ /** - * @name Highcharts.AxisPointBreakEventObject#type - * @type {"pointBreak"|"pointInBreak"} - */ - /** - * @callback Highcharts.AxisSetExtremesEventCallbackFunction - * - * @param {Highcharts.Axis} this - * - * @param {Highcharts.AxisSetExtremesEventObject} evt - */ - /** - * @interface Highcharts.AxisSetExtremesEventObject - * @extends Highcharts.ExtremesObject - */ /** - * @name Highcharts.AxisSetExtremesEventObject#preventDefault - * @type {Function} - */ /** - * @name Highcharts.AxisSetExtremesEventObject#target - * @type {Highcharts.SVGElement} - */ /** - * @name Highcharts.AxisSetExtremesEventObject#trigger - * @type {Highcharts.AxisExtremesTriggerValue|string} - */ /** - * @name Highcharts.AxisSetExtremesEventObject#type - * @type {"setExtremes"} - */ - /** - * @callback Highcharts.AxisTickPositionerCallbackFunction - * - * @param {Highcharts.Axis} this - * - * @return {Highcharts.AxisTickPositionsArray} - */ - /** - * @interface Highcharts.AxisTickPositionsArray - * @augments Array<number> - */ - /** - * @typedef {"high"|"low"|"middle"} Highcharts.AxisTitleAlignValue - */ - /** - * @typedef {Highcharts.XAxisTitleOptions|Highcharts.YAxisTitleOptions|Highcharts.ZAxisTitleOptions} Highcharts.AxisTitleOptions - */ - /** - * @typedef {"linear"|"logarithmic"|"datetime"|"category"|"treegrid"} Highcharts.AxisTypeValue - */ - /** - * The returned object literal from the {@link Highcharts.Axis#getExtremes} - * function. - * - * @interface Highcharts.ExtremesObject - */ /** - * The maximum value of the axis' associated series. - * @name Highcharts.ExtremesObject#dataMax - * @type {number} - */ /** - * The minimum value of the axis' associated series. - * @name Highcharts.ExtremesObject#dataMin - * @type {number} - */ /** - * 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`. - * @name Highcharts.ExtremesObject#max - * @type {number} - */ /** - * 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`. - * @name Highcharts.ExtremesObject#min - * @type {number} - */ /** - * The user defined maximum, either from the `max` option or from a zoom or - * `setExtremes` action. - * @name Highcharts.ExtremesObject#userMax - * @type {number} - */ /** - * The user defined minimum, either from the `min` option or from a zoom or - * `setExtremes` action. - * @name Highcharts.ExtremesObject#userMin - * @type {number} - */ - /** - * Formatter function for the text of a crosshair label. - * - * @callback Highcharts.XAxisCrosshairLabelFormatterCallbackFunction - * - * @param {Highcharts.Axis} this - * Axis context - * - * @param {number} value - * Y value of the data point - * - * @return {string} - */ - var defaultOptions = O.defaultOptions; - var deg2rad = H.deg2rad; - /** - * 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 - * @name Highcharts.Axis - * - * @param {Highcharts.Chart} chart - * The Chart instance to apply the axis on. - * - * @param {Highcharts.AxisOptions} userOptions - * Axis options. - */ - var Axis = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function Axis(chart, userOptions) { - this.alternateBands = void 0; - this.bottom = void 0; - this.categories = void 0; - this.chart = void 0; - this.closestPointRange = void 0; - this.coll = void 0; - this.hasNames = void 0; - this.hasVisibleSeries = void 0; - this.height = void 0; - this.isLinked = void 0; - this.labelEdge = void 0; // @todo - this.labelFormatter = void 0; - this.left = void 0; - this.len = void 0; - this.max = void 0; - this.maxLabelLength = void 0; - this.min = void 0; - this.minorTickInterval = void 0; - this.minorTicks = void 0; - this.minPixelPadding = void 0; - this.names = void 0; - this.offset = void 0; - this.oldMax = void 0; - this.oldMin = void 0; - this.options = void 0; - this.overlap = void 0; - this.paddedTicks = void 0; - this.plotLinesAndBands = void 0; - this.plotLinesAndBandsGroups = void 0; - this.pointRange = void 0; - this.pointRangePadding = void 0; - this.pos = void 0; - this.positiveValuesOnly = void 0; - this.right = void 0; - this.series = void 0; - this.side = void 0; - this.tickAmount = void 0; - this.tickInterval = void 0; - this.tickmarkOffset = void 0; - this.tickPositions = void 0; - this.tickRotCorr = void 0; - this.ticks = void 0; - this.top = void 0; - this.transA = void 0; - this.transB = void 0; - this.translationSlope = void 0; - this.userOptions = void 0; - this.visible = void 0; - this.width = void 0; - this.zoomEnabled = void 0; - this.init(chart, userOptions); - } - /* * - * - * Functions - * - * */ - /** - * Overrideable function to initialize the axis. - * - * @see {@link Axis} - * - * @function Highcharts.Axis#init - * - * @param {Highcharts.Chart} chart - * The Chart instance to apply the axis on. - * - * @param {Highcharts.AxisOptions} userOptions - * Axis options. - * - * @fires Highcharts.Axis#event:afterInit - * @fires Highcharts.Axis#event:init - */ - Axis.prototype.init = function (chart, userOptions) { - var isXAxis = userOptions.isX, - axis = this; - /** - * The Chart that the axis belongs to. - * - * @name Highcharts.Axis#chart - * @type {Highcharts.Chart} - */ - axis.chart = chart; - /** - * Whether the axis is horizontal. - * - * @name Highcharts.Axis#horiz - * @type {boolean|undefined} - */ - axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis; - /** - * Whether the axis is the x-axis. - * - * @name Highcharts.Axis#isXAxis - * @type {boolean|undefined} - */ - 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 Highcharts.Axis#coll - * @type {string} - */ - axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis'); - fireEvent(this, 'init', { userOptions: userOptions }); - 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 Highcharts.Axis#side - * @type {number} - */ - axis.side = userOptions.side || (axis.horiz ? - (axis.opposite ? 0 : 2) : // top : bottom - (axis.opposite ? 1 : 3)); // right : left - /** - * Current options for the axis after merge of defaults and user's - * options. - * - * @name Highcharts.Axis#options - * @type {Highcharts.AxisOptions} - */ - axis.setOptions(userOptions); - var options = this.options, - type = options.type; - axis.labelFormatter = (options.labels.formatter || - // can be overwritten by dynamic format - axis.defaultLabelFormatter); - /** - * User's options for this axis without defaults. - * - * @name Highcharts.Axis#userOptions - * @type {Highcharts.AxisOptions} - */ - 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 Highcharts.Axis#reversed - * @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; - /** - * If categories are present for the axis, names are used instead of - * numbers for that axis. - * - * Since Highcharts 3.0, categories can also be extracted by giving each - * point a name and setting axis type to `category`. However, if you - * have multiple series, best practice remains defining the `categories` - * array. - * - * @see [xAxis.categories](/highcharts/xAxis.categories) - * - * @name Highcharts.Axis#categories - * @type {Array<string>} - * @readonly - */ - axis.categories = options.categories || axis.hasNames; - if (!axis.names) { // Preserve on update (#3830) - axis.names = []; - axis.names.keys = {}; - } - // Placeholder for plotlines and plotbands groups - axis.plotLinesAndBandsGroups = {}; - // Shorthand types - axis.positiveValuesOnly = !!axis.logarithmic; - // Flag, if axis is linked to another axis - axis.isLinked = defined(options.linkedTo); - /** - * List of major ticks mapped by postition on axis. - * - * @see {@link Highcharts.Tick} - * - * @name Highcharts.Axis#ticks - * @type {Highcharts.Dictionary<Highcharts.Tick>} - */ - axis.ticks = {}; - axis.labelEdge = []; - /** - * List of minor ticks mapped by position on the axis. - * - * @see {@link Highcharts.Tick} - * - * @name Highcharts.Axis#minorTicks - * @type {Highcharts.Dictionary<Highcharts.Tick>} - */ - 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; - /** - * 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 Highcharts.Axis#max - * @type {number|null} - */ - 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 Highcharts.Axis#min - * @type {number|null} - */ - axis.min = null; - /** - * The processed crosshair options. - * - * @name Highcharts.Axis#crosshair - * @type {boolean|Highcharts.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 (chart.axes.indexOf(axis) === -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 Highcharts.Axis#series - * @type {Array<Highcharts.Series>} - */ - axis.series = axis.series || []; // populated by Series - // Reversed axis - if (chart.inverted && - !axis.isZAxis && - isXAxis && - typeof axis.reversed === 'undefined') { - axis.reversed = true; - } - axis.labelRotation = axis.options.labels.rotation; - // register event listeners - objectEach(events, function (event, eventType) { - if (isFunction(event)) { - addEvent(axis, eventType, event); - } - }); - fireEvent(this, 'afterInit'); - }; - /** - * Merge and set options. - * - * @private - * @function Highcharts.Axis#setOptions - * - * @param {Highcharts.AxisOptions} userOptions - * Axis options. - * - * @fires Highcharts.Axis#event:afterSetOptions - */ - Axis.prototype.setOptions = function (userOptions) { - this.options = merge(Axis.defaultOptions, (this.coll === 'yAxis') && Axis.defaultYAxisOptions, [ - Axis.defaultTopAxisOptions, - Axis.defaultRightAxisOptions, - Axis.defaultBottomAxisOptions, - Axis.defaultLeftAxisOptions - ][this.side], merge( - // if set in setOptions (#1053): - defaultOptions[this.coll], userOptions)); - fireEvent(this, 'afterSetOptions', { userOptions: userOptions }); - }; - /** - * The default label formatter. The context is a special config object for - * the label. In apps, use the - * [labels.formatter](https://api.highcharts.com/highcharts/xAxis.labels.formatter) - * instead, except when a modification is needed. - * - * @function Highcharts.Axis#defaultLabelFormatter - * - * @param {Highcharts.AxisLabelsFormatterContextObject<number>|Highcharts.AxisLabelsFormatterContextObject<string>} this - * Formatter context of axis label. - * - * @return {string} - * The formatted label content. - */ - Axis.prototype.defaultLabelFormatter = function () { - var axis = this.axis, - value = isNumber(this.value) ? this.value : NaN, - 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.logarithmic ? - Math.abs(value) : - axis.tickInterval; - var chart = this.chart; - var numberFormatter = chart.numberFormatter; - if (formatOption) { - ret = format(formatOption, this, chart); - } - else if (categories) { - ret = "" + this.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-- && typeof 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 = numberFormatter(value / multi, -1) + numericSymbols[i]; - } - } - } - if (typeof ret === 'undefined') { - if (Math.abs(value) >= 10000) { // add thousands separators - ret = numberFormatter(value, -1); - } - else { // small numbers - ret = numberFormatter(value, -1, void 0, ''); // #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 - * @function Highcharts.Axis#getSeriesExtremes - * - * @fires Highcharts.Axis#event:afterGetSeriesExtremes - * @fires Highcharts.Axis#event:getSeriesExtremes - */ - Axis.prototype.getSeriesExtremes = function () { - var axis = this, - chart = axis.chart, - xExtremes; - fireEvent(this, 'getSeriesExtremes', null, function () { - axis.hasVisibleSeries = false; - // Reset properties in case we're redrawing (#3353) - axis.dataMin = axis.dataMax = axis.threshold = null; - axis.softThreshold = !axis.isXAxis; - if (axis.stacking) { - axis.stacking.buildStacks(); - } - // loop through this axis' series - axis.series.forEach(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) { - var isPositive = function (number) { return number > 0; }; - xData = axis.logarithmic ? - xData.filter(axis.validatePositiveValue) : - xData; - xExtremes = series.getXExtremes(xData); - // 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 = xExtremes.min; - seriesDataMax = xExtremes.max; - if (!isNumber(seriesDataMin) && - // #5010: - !(seriesDataMin instanceof Date)) { - xData = xData.filter(isNumber); - xExtremes = series.getXExtremes(xData); - // Do it again with valid data - seriesDataMin = xExtremes.min; - seriesDataMax = xExtremes.max; - } - if (xData.length) { - axis.dataMin = Math.min(pick(axis.dataMin, seriesDataMin), seriesDataMin); - axis.dataMax = Math.max(pick(axis.dataMax, seriesDataMax), seriesDataMax); - } - } - // Get dataMin and dataMax for Y axes, as well as handle - // stacking and processed data - } - else { - // Get this particular series extremes - var dataExtremes = series.applyExtremes(); - // 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 (isNumber(dataExtremes.dataMin)) { - seriesDataMin = dataExtremes.dataMin; - axis.dataMin = Math.min(pick(axis.dataMin, seriesDataMin), seriesDataMin); - } - if (isNumber(dataExtremes.dataMax)) { - seriesDataMax = dataExtremes.dataMax; - 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; - } - } - } - }); - }); - fireEvent(this, 'afterGetSeriesExtremes'); - }; - /** - * Translate from axis value to pixel position on the chart, or back. Use - * the `toPixels` and `toValue` functions in applications. - * - * @private - * @function Highcharts.Axis#translate - * - * @param {number} val - * TO-DO: parameter description - * - * @param {boolean|null} [backwards] - * TO-DO: parameter description - * - * @param {boolean|null} [cvsCoord] - * TO-DO: parameter description - * - * @param {boolean|null} [old] - * TO-DO: parameter description - * - * @param {boolean} [handleLog] - * TO-DO: parameter description - * - * @param {number} [pointPlacement] - * TO-DO: parameter description - * - * @return {number|undefined} - */ - Axis.prototype.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 = 0, - minPixelPadding = axis.minPixelPadding, - doPostTranslate = (axis.isOrdinal || - axis.brokenAxis && axis.brokenAxis.hasBreaks || - (axis.logarithmic && 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; - // from chart pixel to value: - returnValue = val / localA + localMin; - 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)) : - void 0; - } - return returnValue; - }; - /** - * Translate a value in terms of axis units into pixels within the chart. - * - * @function Highcharts.Axis#toPixels - * - * @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. - */ - Axis.prototype.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. - * - * @function Highcharts.Axis#toValue - * - * @param {number} pixel - * The pixel value coordinate. - * - * @param {boolean} [paneCoordinates=false] - * Whether the input pixel is relative to the chart or just the axis/pane - * itself. - * - * @return {number} - * The axis value. - */ - Axis.prototype.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. - * - * @function Highcharts.Axis#getPlotLinePath - * - * @param {Highcharts.AxisPlotLinePathOptionsObject} options - * Options for the path. - * - * @return {Highcharts.SVGPathArray|null} - * The SVG path definition for the plot line. - */ - Axis.prototype.getPlotLinePath = function (options) { - var axis = this, - chart = axis.chart, - axisLeft = axis.left, - axisTop = axis.top, - old = options.old, - value = options.value, - translatedValue = options.translatedValue, - lineWidth = options.lineWidth, - force = options.force, - x1, - y1, - x2, - y2, - cHeight = (old && chart.oldChartHeight) || chart.chartHeight, - cWidth = (old && chart.oldChartWidth) || chart.chartWidth, - skip, - transB = axis.transB, - evt; - // eslint-disable-next-line valid-jsdoc - /** - * Check if x is between a and b. If not, either move to a/b - * or skip, depending on the force parameter. - * @private - */ - function between(x, a, b) { - if (force !== 'pass' && x < a || x > b) { - if (force) { - x = clamp(x, a, b); - } - else { - skip = true; - } - } - return x; - } - evt = { - value: value, - lineWidth: lineWidth, - old: old, - force: force, - acrossPanes: options.acrossPanes, - translatedValue: translatedValue - }; - fireEvent(this, 'getPlotLinePath', evt, function (e) { - translatedValue = pick(translatedValue, axis.translate(value, null, null, old)); - // Keep the translated value within sane bounds, and avoid Infinity - // to fail the isNumber test (#7709). - translatedValue = clamp(translatedValue, -1e5, 1e5); - 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); - } - e.path = skip && !force ? - null : - chart.renderer.crispLine([['M', x1, y1], ['L', x2, y2]], lineWidth || 1); - }); - return evt.path; - }; - /** - * Internal function to et the tick positions of a linear axis to round - * values like whole tens or every five. - * - * @function Highcharts.Axis#getLinearTickPositions - * - * @param {number} tickInterval - * The normalized tick interval. - * - * @param {number} min - * Axis minimum. - * - * @param {number} max - * Axis maximum. - * - * @return {Array<number>} - * An array of axis values where ticks should be placed. - */ - Axis.prototype.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. - * - * @function Highcharts.Axis#getMinorTickInterval - * - * @return {number|"auto"|null} - */ - Axis.prototype.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. - * - * @function Highcharts.Axis#getMinorTickPositions - * - * @return {Array<number>} - * An array of axis values where ticks should be placed. - */ - Axis.prototype.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 - var logarithmic_1 = axis.logarithmic; - if (logarithmic_1) { - // For each interval in the major ticks, compute the minor ticks - // separately. - this.paddedTicks.forEach(function (_pos, i, paddedTicks) { - if (i) { - minorTickPositions.push.apply(minorTickPositions, logarithmic_1.getLogTickPositions(minorTickInterval, paddedTicks[i - 1], paddedTicks[i], true)); - } - }); - } - else if (axis.dateTime && - this.getMinorTickInterval() === 'auto') { // #1314 - minorTickPositions = minorTickPositions.concat(axis.getTimeTicks(axis.dateTime.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 - * @function Highcharts.Axis#adjustForMinRange - */ - Axis.prototype.adjustForMinRange = function () { - var axis = this, - options = axis.options, - min = axis.min, - max = axis.max, - log = axis.logarithmic, - zoomOffset, - spaceAvailable, - closestDataRange, - i, - distance, - xData, - loopLength, - minArgs, - maxArgs, - minRange; - // Set the automatic minimum range based on the closest point distance - if (axis.isXAxis && - typeof axis.minRange === 'undefined' && - !log) { - 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) - axis.series.forEach(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 (typeof 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.logarithmic ? - axis.logarithmic.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] = log ? - log.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; - }; - // eslint-disable-next-line valid-jsdoc - /** - * Find the closestPointRange across all series. - * - * @private - * @function Highcharts.Axis#getClosest - */ - Axis.prototype.getClosest = function () { - var ret; - if (this.categories) { - ret = 1; - } - else { - this.series.forEach(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 - * @function Highcharts.Axis#nameToX - * - * @param {Highcharts.Point} point - * The point to inspect. - * - * @return {number} - * The X value that the point is given. - */ - Axis.prototype.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() : - (explicitCategories ? - names.indexOf(point.name) : - pick(names.keys[point.name], -1)); - } - if (nameX === -1) { // Not found in currenct categories - if (!explicitCategories) { - x = names.length; - } - } - else { - x = nameX; - } - // Write the last point's name to the names array - if (typeof x !== 'undefined') { - this.names[x] = point.name; - // Backwards mapping is much faster than array searching (#7725) - this.names.keys[point.name] = x; - } - return x; - }; - /** - * When changes have been done to series data, update the axis.names. - * - * @private - * @function Highcharts.Axis#updateNames - */ - Axis.prototype.updateNames = function () { - var axis = this, - names = this.names, - i = names.length; - if (i > 0) { - Object.keys(names.keys).forEach(function (key) { - delete (names.keys)[key]; - }); - names.length = 0; - this.minRange = this.userMinRange; // Reset - (this.series || []).forEach(function (series) { - // Reset incrementer (#5928) - series.xIncrement = null; - // When adding a series, points are not yet generated - if (!series.points || series.isDirtyData) { - // When we're updating the series with data that is longer - // than it was, and cropThreshold is passed, we need to make - // sure that the axis.max is increased _before_ running the - // premature processData. Otherwise this early iteration of - // processData will crop the points to axis.max, and the - // names array will be too short (#5857). - axis.max = Math.max(axis.max, series.xData.length - 1); - series.processData(); - series.generatePoints(); - } - series.data.forEach(function (point, i) { - var x; - if (point && - point.options && - typeof point.name !== 'undefined' // #9562 - ) { - x = axis.nameToX(point); - if (typeof x !== 'undefined' && x !== point.x) { - point.x = x; - series.xData[i] = x; - } - } - }); - }); - } - }; - /** - * Update translation information. - * - * @private - * @function Highcharts.Axis#setAxisTranslation - * - * @param {boolean} [saveOld] - * TO-DO: parameter description - * - * @fires Highcharts.Axis#event:afterSetAxisTranslation - */ - Axis.prototype.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 { - axis.series.forEach(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 || hasCategories) { - // TODO: series should internally set x- and y- - // pointPlacement to simplify this logic. - var isPointPlacementAxis = series.is('xrange') ? !isXAxis : isXAxis; - // 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, isPointPlacementAxis && 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, isPointPlacementAxis && pointPlacement === 'on' ? - 0 : - seriesPointRange); - } - }); - } - // Record minPointOffset and pointRangePadding - ordinalCorrection = axis.ordinal && axis.ordinal.slope && closestPointRange ? - axis.ordinal.slope / 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, axis.single && hasCategories ? 1 : 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.staticScale || - axis.len / ((range + pointRangePadding) || 1); - // Translation addend - axis.transB = axis.horiz ? axis.left : axis.bottom; - axis.minPixelPadding = transA * minPointOffset; - fireEvent(this, 'afterSetAxisTranslation'); - }; - /** - * @private - * @function Highcharts.Axis#minFromRange - * - * @return {number} - */ - Axis.prototype.minFromRange = function () { - var axis = this; - return axis.max - axis.range; - }; - /** - * Set the tick positions to round values and optionally extend the extremes - * to the nearest tick. - * - * @private - * @function Highcharts.Axis#setTickInterval - * - * @param {boolean} secondPass - * TO-DO: parameter description - * - * @fires Highcharts.Axis#event:foundExtremes - */ - Axis.prototype.setTickInterval = function (secondPass) { - var axis = this, - chart = axis.chart, - log = axis.logarithmic, - options = axis.options, - 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 = isNumber(axis.threshold) ? axis.threshold : null, - softThreshold = axis.softThreshold, - thresholdMin, - thresholdMax, - hardMin, - hardMax; - if (!axis.dateTime && !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) { - // Can't link axes of different type - error(11, 1, chart); - } - // 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 (log) { - if (axis.positiveValuesOnly && - !secondPass && - Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 - // Can't plot negative values on log axis - error(10, 1, chart); - } - // 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(log.log2lin(axis.min), 16); - axis.max = correctFloat(log.log2lin(axis.max), 16); - } - // handle zoomed range - if (axis.range && defined(axis.max)) { - // #618, #6773: - axis.userMin = axis.min = hardMin = - Math.max(axis.dataMin, axis.minFromRange()); - 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.stacking && axis.stacking.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(axis.userMin)) { - if (isNumber(options.softMin) && options.softMin < axis.min) { - axis.min = hardMin = options.softMin; // #6894 - } - if (isNumber(options.floor)) { - axis.min = Math.max(axis.min, options.floor); - } - } - if (!isNumber(axis.userMax)) { - if (isNumber(options.softMax) && options.softMax > axis.max) { - axis.max = hardMax = options.softMax; // #6894 - } - 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 = axis.options.minRange ? - Math.min(threshold, axis.max - - axis.minRange) : - threshold; - } - else if (!defined(hardMax) && - axis.max > threshold && - axis.dataMax <= threshold) { - axis.max = axis.options.minRange ? - Math.max(threshold, axis.min + - axis.minRange) : - threshold; - } - } - // get tickInterval - if (axis.min === axis.max || - typeof axis.min === 'undefined' || - typeof 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)) : - void 0, - // 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) { - axis.series.forEach(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 - fireEvent(this, 'initialAxisTranslation'); - // 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, - // In datetime axes, don't go below the data interval, except when - // there are scatter-like series involved (#13369). - axis.dateTime && - !axis.series.some(function (s) { return s.noSharedTooltip; }) ? - axis.closestPointRange : 0); - if (!tickIntervalOption && axis.tickInterval < minTickInterval) { - axis.tickInterval = minTickInterval; - } - // for linear axes, get magnitude and normalize the interval - if (!axis.dateTime && !axis.logarithmic && !tickIntervalOption) { - axis.tickInterval = normalizeTickInterval(axis.tickInterval, void 0, getMagnitude(axis.tickInterval), pick(options.allowDecimals, - // If the tick interval is greather than 0.5, avoid - // decimals, as linear axes are often used to render - // discrete values. #3363. If a tick amount is set, allow - // decimals by default, as it increases the chances for a - // good fit. - axis.tickInterval < 0.5 || this.tickAmount !== void 0), !!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. - * - * @private - * @function Highcharts.Axis#setTickPositions - * - * @fires Highcharts.Axis#event:afterSetTickPositions - */ - Axis.prototype.setTickPositions = function () { - var axis = this, - options = this.options, - tickPositions, - tickPositionsOption = options.tickPositions, - minorTickIntervalOption = this.getMinorTickInterval(), - tickPositioner = options.tickPositioner, - hasVerticalPanning = this.hasVerticalPanning(), - isColorAxis = this.coll === 'colorAxis', - startOnTick = (isColorAxis || !hasVerticalPanning) && options.startOnTick, - endOnTick = (isColorAxis || !hasVerticalPanning) && 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); - /** - * Contains the current positions that are laid out on the axis. The - * positions are numbers in terms of axis values. In a category axis - * they are integers, in a datetime axis they are also integers, but - * designating milliseconds. - * - * This property is read only - for modifying the tick positions, use - * the `tickPositioner` callback or [axis.tickPositions( - * https://api.highcharts.com/highcharts/xAxis.tickPositions) option - * instead. - * - * @name Highcharts.Axis#tickPositions - * @type {Highcharts.AxisTickPositionsArray|undefined} - */ - this.tickPositions = - // Find the tick positions. Work on a copy (#1565) - tickPositions = - (tickPositionsOption && tickPositionsOption.slice()); - if (!tickPositions) { - // Too many ticks (#6405). Create a friendly warning and provide two - // ticks so at least we can show the data series. - if ((!axis.ordinal || !axis.ordinal.positions) && - ((this.max - this.min) / - this.tickInterval > - Math.max(2 * this.len, 200))) { - tickPositions = [this.min, this.max]; - error(19, false, this.chart); - } - else if (axis.dateTime) { - tickPositions = axis.getTimeTicks(axis.dateTime.normalizeTimeTickInterval(this.tickInterval, options.units), this.min, this.max, options.startOfWeek, axis.ordinal && axis.ordinal.positions, this.closestPointRange, true); - } - else if (axis.logarithmic) { - tickPositions = axis.logarithmic.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(axis, [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.categories && - !this.series.some(function (s) { - return (s.is('heatmap') && s.options.pointPlacement === 'between'); - })) { - this.min -= 0.5; - this.max += 0.5; - } - if (!tickPositionsOption && !tickPositioner) { - this.adjustTickAmount(); - } - } - fireEvent(this, 'afterSetTickPositions'); - }; - /** - * Handle startOnTick and endOnTick by either adapting to padding min/max or - * rounded min/max. Also handle single data points. - * - * @private - * @function Highcharts.Axis#trimTicks - * - * @param {Array<number>} tickPositions - * TO-DO: parameter description - * - * @param {boolean} [startOnTick] - * TO-DO: parameter description - * - * @param {boolean} [endOnTick] - * TO-DO: parameter description - */ - Axis.prototype.trimTicks = function (tickPositions, startOnTick, endOnTick) { - var roundedMin = tickPositions[0], - roundedMax = tickPositions[tickPositions.length - 1], - minPointOffset = (!this.isOrdinal && this.minPointOffset) || 0; // (#12716) - fireEvent(this, 'trimTicks'); - 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 - * @function Highcharts.Axis#alignToOthers - * - * @return {boolean|undefined} - * True if there are other axes. - */ - Axis.prototype.alignToOthers = function () { - var axis = this, - others = // Whether there is another axis to pair with this one - {}, - hasOther, - options = axis.options; - if ( - // Only if alignTicks is true - this.chart.options.chart.alignTicks !== false && - options.alignTicks !== false && - // Disabled when startOnTick or endOnTick are false (#7604) - options.startOnTick !== false && - options.endOnTick !== false && - // Don't try to align ticks on a log axis, they are not evenly - // spaced (#6021) - !axis.logarithmic) { - this.chart[this.coll].forEach(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 - * @function Highcharts.Axis#getTickAmount - */ - Axis.prototype.getTickAmount = function () { - var axis = this, - options = this.options, - tickAmount = options.tickAmount, - tickPixelInterval = options.tickPixelInterval; - if (!defined(options.tickInterval) && - !tickAmount && this.len < tickPixelInterval && - !this.isRadial && - !axis.logarithmic && - 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 - * @function Highcharts.Axis#adjustTickAmount - */ - Axis.prototype.adjustTickAmount = function () { - var axis = this, - axisOptions = axis.options, - tickInterval = axis.tickInterval, - tickPositions = axis.tickPositions, - tickAmount = axis.tickAmount, - finalTickAmt = axis.finalTickAmt, - currentTickAmount = tickPositions && tickPositions.length, - threshold = pick(axis.threshold, - axis.softThreshold ? 0 : null), - min, - len, - i; - if (axis.hasData()) { - if (currentTickAmount < tickAmount) { - min = axis.min; - while (tickPositions.length < tickAmount) { - // Extend evenly for both sides unless we're on the - // threshold (#3965) - if (tickPositions.length % 2 || - min === threshold) { - // to the end - tickPositions.push(correctFloat(tickPositions[tickPositions.length - 1] + - tickInterval)); - } - else { - // to the start - tickPositions.unshift(correctFloat(tickPositions[0] - tickInterval)); - } - } - axis.transA *= (currentTickAmount - 1) / (tickAmount - 1); - // Do not crop when ticks are not extremes (#9841) - axis.min = axisOptions.startOnTick ? - tickPositions[0] : - Math.min(axis.min, tickPositions[0]); - axis.max = axisOptions.endOnTick ? - tickPositions[tickPositions.length - 1] : - Math.max(axis.max, tickPositions[tickPositions.length - 1]); - // We have too many ticks, run second pass to try to reduce ticks - } - else if (currentTickAmount > tickAmount) { - axis.tickInterval *= 2; - axis.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); - } - } - axis.finalTickAmt = void 0; - } - } - }; - /** - * Set the scale based on data min and max, user set min and max or options. - * - * @private - * @function Highcharts.Axis#setScale - * - * @fires Highcharts.Axis#event:afterSetScale - */ - Axis.prototype.setScale = function () { - var axis = this, - isDirtyAxisLength, - isDirtyData = false, - isXAxisDirty = false; - axis.series.forEach(function (series) { - var _a; - isDirtyData = isDirtyData || series.isDirtyData || series.isDirty; - // When x axis is dirty, we need new data extremes for y as - // well: - isXAxisDirty = isXAxisDirty || ((_a = series.xAxis) === null || _a === void 0 ? void 0 : _a.isDirty) || false; - }); - axis.oldMin = axis.min; - axis.oldMax = axis.max; - axis.oldAxisLength = axis.len; - // set the new axisLength - axis.setAxisSize(); - isDirtyAxisLength = axis.len !== axis.oldAxisLength; - // do we really need to go through all this? - if (isDirtyAxisLength || - isDirtyData || - isXAxisDirty || - axis.isLinked || - axis.forceRedraw || - axis.userMin !== axis.oldUserMin || - axis.userMax !== axis.oldUserMax || - axis.alignToOthers()) { - if (axis.stacking) { - axis.stacking.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.stacking) { - axis.stacking.cleanStacks(); - } - // Recalculate panning state object, when the data - // has changed. It is required when vertical panning is enabled. - if (isDirtyData && axis.panningState) { - axis.panningState.isDirty = true; - } - fireEvent(this, 'afterSetScale'); - }; - /** - * 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. - * - * @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 - * - * @function Highcharts.Axis#setExtremes - * - * @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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] - * Enable or modify animations. - * - * @param {*} [eventArguments] - * Arguments to be accessed in event handler. - * - * @fires Highcharts.Axis#event:setExtremes - */ - Axis.prototype.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) { - var axis = this, - chart = axis.chart; - redraw = pick(redraw, true); // defaults to true - axis.series.forEach(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 - * @function Highcharts.Axis#zoom - * - * @param {number} newMin - * TO-DO: parameter description - * - * @param {number} newMax - * TO-DO: parameter description - * - * @return {boolean} - */ - Axis.prototype.zoom = function (newMin, newMax) { - var axis = this, - 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)), - evt = { - newMin: newMin, - newMax: newMax - }; - fireEvent(this, 'zoom', evt, function (e) { - // Use e.newMin and e.newMax - event handlers may have altered them - var newMin = e.newMin, - newMax = e.newMax; - if (newMin !== axis.min || newMax !== axis.max) { // #5790 - // Prevent pinch zooming out of range. Check for defined is for - // #1946. #1734. - if (!axis.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 - axis.displayBtn = (typeof newMin !== 'undefined' || - typeof newMax !== 'undefined'); - // Do it - axis.setExtremes(newMin, newMax, false, void 0, { trigger: 'zoom' }); - } - e.zoomed = true; - }); - return evt.zoomed; - }; - /** - * Update the axis metrics. - * - * @private - * @function Highcharts.Axis#setAxisSize - */ - Axis.prototype.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(relativeLength(pick(options.width, - chart.plotWidth - offsets[3] + offsets[1]), - chart.plotWidth)), - height = this.height = Math.round(relativeLength(pick(options.height, - chart.plotHeight - offsets[0] + offsets[2]), - chart.plotHeight)), - top = this.top = Math.round(relativeLength(pick(options.top, - chart.plotTop + offsets[0]), - chart.plotHeight, - chart.plotTop)), - left = this.left = Math.round(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 - }; - /** - * Get the current extremes for the axis. - * - * @sample highcharts/members/axis-getextremes/ - * Report extremes by click on a button - * @sample maps/members/axis-getextremes/ - * Get extremes in Highmaps - * - * @function Highcharts.Axis#getExtremes - * - * @return {Highcharts.ExtremesObject} - * An object containing extremes information. - */ - Axis.prototype.getExtremes = function () { - var axis = this; - var log = axis.logarithmic; - return { - min: log ? - correctFloat(log.lin2log(axis.min)) : - axis.min, - max: log ? - correctFloat(log.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. - * - * @function Highcharts.Axis#getThreshold - * - * @param {number} threshold - * The threshold in axis values. - * - * @return {number|undefined} - * The translated threshold position in terms of pixels, and corrected to - * stay within the axis bounds. - */ - Axis.prototype.getThreshold = function (threshold) { - var axis = this, - log = axis.logarithmic, - realMin = log ? log.lin2log(axis.min) : axis.min, - realMax = log ? log.lin2log(axis.max) : axis.max; - if (threshold === null || threshold === -Infinity) { - threshold = realMin; - } - else if (threshold === Infinity) { - threshold = realMax; - } - 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. - * - * @private - * @function Highcharts.Axis#autoLabelAlign - * - * @param {number} rotation - * The rotation in degrees as set by either the `rotation` or `autoRotation` - * options. - * - * @return {Highcharts.AlignValue} - * Can be `"center"`, `"left"` or `"right"`. - */ - Axis.prototype.autoLabelAlign = function (rotation) { - var angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360, - evt = { align: 'center' }; - fireEvent(this, 'autoLabelAlign', evt, function (e) { - if (angle > 15 && angle < 165) { - e.align = 'right'; - } - else if (angle > 195 && angle < 345) { - e.align = 'left'; - } - }); - return evt.align; - }; - /** - * Get the tick length and width for the axis based on axis options. - * @private - * @function Highcharts.Axis#tickSize - * - * @param {string} [prefix] - * 'tick' or 'minorTick' - * - * @return {Array<number,number>|undefined} - * An array of tickLength and tickWidth - */ - Axis.prototype.tickSize = function (prefix) { - var options = this.options, tickLength = options[prefix === 'tick' ? 'tickLength' : 'minorTickLength'], tickWidth = pick(options[prefix === 'tick' ? 'tickWidth' : 'minorTickWidth'], - // Default to 1 on linear and datetime X axes - prefix === 'tick' && this.isXAxis && !this.categories ? 1 : 0), e, tickSize; - if (tickWidth && tickLength) { - // Negate the length - if (options[prefix + 'Position'] === 'inside') { - tickLength = -tickLength; - } - tickSize = [tickLength, tickWidth]; - } - e = { tickSize: tickSize }; - fireEvent(this, 'afterTickSize', e); - return e.tickSize; - }; - /** - * Return the size of the labels. - * - * @private - * @function Highcharts.Axis#labelMetrics - * - * @return {Highcharts.FontMetricsObject} - */ - Axis.prototype.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 - * @function Highcharts.Axis#unsquish - * - * @return {number} - */ - Axis.prototype.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, - range = this.max - this.min, - // 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; - // Guard for very small or negative angles (#9835) - if (step * tickInterval > range && - spaceNeeded !== Infinity && - slotSize !== Infinity && - range) { - step = Math.ceil(range / tickInterval); - } - return correctFloat(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. - autoRotation.forEach(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 - * @function Highcharts.Axis#getSlotWidth - * - * @param {Highcharts.Tick} [tick] Optionally, calculate the slot width - * basing on tick label. It is used in highcharts-3d module, where the slots - * has different widths depending on perspective angles. - * - * @return {number} - * The pixel width allocated to each axis label. - */ - Axis.prototype.getSlotWidth = function (tick) { - var _a; - // #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]; - // Used by grid axis - if (tick && isNumber(tick.slotWidth)) { // #13221, can be 0 - return tick.slotWidth; - } - if (horiz && - labelOptions && - (labelOptions.step || 0) < 2) { - if (labelOptions.rotation) { // #4415 - return 0; - } - return ((this.staggerLines || 1) * this.len) / slotCount; - } - if (!horiz) { - // #7028 - var cssWidth = (_a = labelOptions === null || labelOptions === void 0 ? void 0 : labelOptions.style) === null || _a === void 0 ? void 0 : _a.width; - if (cssWidth !== void 0) { - return parseInt(cssWidth, 10); - } - if (marginLeft) { - return marginLeft - chart.spacing[3]; - } - } - // Last resort, a fraction of the available size - return chart.chartWidth * 0.33; - }; - /** - * Render the axis labels and determine whether ellipsis or rotation need to - * be applied. - * - * @private - * @function Highcharts.Axis#renderUnsquish - */ - Axis.prototype.renderUnsquish = function () { - var chart = this.chart, - renderer = chart.renderer, - tickPositions = this.tickPositions, - ticks = this.ticks, - labelOptions = this.options.labels, - labelStyleOptions = (labelOptions && labelOptions.style || {}), - 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), - commonWidth, - commonTextOverflow, - maxLabelLength = 0, - label, - i, - pos; - // Set rotation option unless it is "auto", like in gauges - if (!isString(labelOptions.rotation)) { - // #4443: - attr.rotation = labelOptions.rotation || 0; - } - // Get the longest label length - tickPositions.forEach(function (tick) { - tick = ticks[tick]; - // Replace label - sorting animation - if (tick.movedLabel) { - tick.replaceMovedLabel(); - } - if (tick && - tick.label && - tick.label.textPxLength > maxLabelLength) { - maxLabelLength = tick.label.textPxLength; - } - }); - 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 - commonWidth = innerWidth; - if (!textOverflowOption) { - commonTextOverflow = '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 (label.textPxLength > slotWidth) { - label.css({ width: slotWidth + 'px' }); - } - if (label.getBBox().height > (this.len / tickPositions.length - - (labelMetrics.h - labelMetrics.f))) { - label.specificTextOverflow = 'ellipsis'; - } - } - } - } - } - // Add ellipsis if the label length is significantly longer than ideal - if (attr.rotation) { - commonWidth = (maxLabelLength > chart.chartHeight * 0.5 ? - chart.chartHeight * 0.33 : - maxLabelLength); - if (!textOverflowOption) { - commonTextOverflow = '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 - tickPositions.forEach(function (pos) { - var tick = ticks[pos], - label = tick && tick.label, - widthOption = labelStyleOptions.width, - css = {}; - if (label) { - // This needs to go before the CSS in old IE (#4502) - label.attr(attr); - if (tick.shortenLabel) { - tick.shortenLabel(); - } - else if (commonWidth && - !widthOption && - // Setting width in this case messes with the bounding box - // (#7975) - labelStyleOptions.whiteSpace !== 'nowrap' && - ( - // Speed optimizing, #7656 - commonWidth < label.textPxLength || - // Resetting CSS, #4928 - label.element.tagName === 'SPAN')) { - css.width = commonWidth + 'px'; - if (!textOverflowOption) { - css.textOverflow = (label.specificTextOverflow || - commonTextOverflow); - } - label.css(css); - // Reset previously shortened label (#8210) - } - else if (label.styles && - label.styles.width && - !css.width && - !widthOption) { - label.css({ width: null }); - } - delete label.specificTextOverflow; - tick.rotation = attr.rotation; - } - }, this); - // 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. - * - * @function Highcharts.Axis#hasData - * - * @return {boolean} - * True if the axis has associated visible series and those series have - * either valid data points or explicit `min` and `max` settings. - */ - Axis.prototype.hasData = function () { - return this.series.some(function (s) { - return s.hasData(); - }) || - (this.options.showEmpty && - defined(this.min) && - defined(this.max)); - }; - /** - * Adds the title defined in axis.options.title. - * - * @function Highcharts.Axis#addTitle - * - * @param {boolean} [display] - * Whether or not to display the title. - */ - Axis.prototype.addTitle = function (display) { - var axis = this, - renderer = axis.chart.renderer, - horiz = axis.horiz, - opposite = axis.opposite, - options = axis.options, - axisTitleOptions = options.title, - textAlign, - styledMode = axis.chart.styledMode; - 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'); - // #7814, don't mutate style option - if (!styledMode) { - axis.axisTitle.css(merge(axisTitleOptions.style)); - } - axis.axisTitle.add(axis.axisGroup); - axis.axisTitle.isNew = true; - } - // Max width defaults to the length of the axis - if (!styledMode && - !axisTitleOptions.style.width && - !axis.isRadial) { - axis.axisTitle.css({ - width: axis.len + 'px' - }); - } - // hide or show the title depending on whether showEmpty is set - axis.axisTitle[display ? 'show' : 'hide'](display); - }; - /** - * Generates a tick for initial positioning. - * - * @private - * @function Highcharts.Axis#generateTick - * - * @param {number} pos - * The tick position in axis values. - * - * @param {number} [i] - * The index of the tick in {@link Axis.tickPositions}. - */ - Axis.prototype.generateTick = function (pos) { - var axis = this; - var ticks = axis.ticks; - if (!ticks[pos]) { - ticks[pos] = new Tick(axis, pos); - } - else { - ticks[pos].addLabel(); // update labels depending on tick interval - } - }; - /** - * Render the tick labels to a preliminary position to get their sizes - * - * @private - * @function Highcharts.Axis#getOffset - * - * @fires Highcharts.Axis#event:afterGetOffset - */ - Axis.prototype.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; - // 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 - tickPositions.forEach(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)) { - tickPositions.forEach(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] ? axisOffset[side] + (options.margin || 0) : 0); - 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); - if (axis.getMaxLabelDimensions) { - axis.maxLabelDimensions = axis.getMaxLabelDimensions(ticks, tickPositions); - } - // Due to GridAxis.tickSize, tickSize should be calculated after ticks - // has rendered. - var tickSize = this.tickSize('tick'); - axisOffset[side] = Math.max(axisOffset[side], axis.axisTitleMargin + titleOffset + - directionFactor * axis.offset, labelOffsetPadded, // #3027 - tickPositions && 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 : - // #4308, #4371: - Math.floor(axis.axisLine.strokeWidth() / 2) * 2; - clipOffset[invertedSide] = - Math.max(clipOffset[invertedSide], clip); - fireEvent(this, 'afterGetOffset'); - }; - /** - * Internal function to get the path for the axis line. Extended for polar - * charts. - * - * @function Highcharts.Axis#getLinePath - * - * @param {number} lineWidth - * The line width in pixels. - * - * @return {Highcharts.SVGPathArray} - * The SVG path definition in array form. - */ - Axis.prototype.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. - * - * @function Highcharts.Axis#renderLine - */ - Axis.prototype.renderLine = function () { - if (!this.axisLine) { - this.axisLine = this.chart.renderer.path() - .addClass('highcharts-axis-line') - .add(this.axisGroup); - if (!this.chart.styledMode) { - this.axisLine.attr({ - stroke: this.options.lineColor, - 'stroke-width': this.options.lineWidth, - zIndex: 7 - }); - } - } - }; - /** - * Position the axis title. - * - * @private - * @function Highcharts.Axis#getTitlePosition - * - * @return {Highcharts.PositionObject} - * X and Y positions for the title. - */ - Axis.prototype.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, - textHeightOvershoot, - fontMetrics.f, - -textHeightOvershoot // left - ][this.side], - titlePosition = { - x: horiz ? - alongAxis + xOption : - offAxis + (opposite ? this.width : 0) + offset + xOption, - y: horiz ? - offAxis + yOption - (opposite ? this.height : 0) + offset : - alongAxis + yOption - }; - fireEvent(this, 'afterGetTitlePosition', { titlePosition: titlePosition }); - return titlePosition; - }; - /** - * Render a minor tick into the given position. If a minor tick already - * exists in this position, move it. - * - * @function Highcharts.Axis#renderMinorTick - * - * @param {number} pos - * The position in axis values. - */ - Axis.prototype.renderMinorTick = function (pos) { - var axis = this; - var slideInTicks = axis.chart.hasRendered && isNumber(axis.oldMin); - var minorTicks = axis.minorTicks; - if (!minorTicks[pos]) { - minorTicks[pos] = new Tick(axis, 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. - * - * @function Highcharts.Axis#renderTick - * - * @param {number} pos - * The position in axis values. - * - * @param {number} i - * The tick index. - */ - Axis.prototype.renderTick = function (pos, i) { - var _a; - var axis = this; - var isLinked = axis.isLinked; - var ticks = axis.ticks; - var slideInTicks = axis.chart.hasRendered && isNumber(axis.oldMin); - // Linked axes need an extra check to find out if - if (!isLinked || - (pos >= axis.min && pos <= axis.max) || ((_a = axis.grid) === null || _a === void 0 ? void 0 : _a.isColumn)) { - if (!ticks[pos]) { - ticks[pos] = new Tick(axis, pos); - } - // NOTE this seems like overkill. Could be handled in tick.render by - // setting old position in attr, then set new position in animate. - // render new ticks in old position - if (slideInTicks && ticks[pos].isNew) { - // Start with negative opacity so that it is visible from - // halfway into the animation - ticks[pos].render(i, true, -1); - } - ticks[pos].render(i); - } - }; - /** - * Render the axis. - * - * @private - * @function Highcharts.Axis#render - * - * @fires Highcharts.Axis#event:afterRender - */ - Axis.prototype.render = function () { - var axis = this, - chart = axis.chart, - log = axis.logarithmic, - renderer = chart.renderer, - options = axis.options, - 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 - [ticks, minorTicks, alternateBands].forEach(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) { - axis.getMinorTickPositions().forEach(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 - tickPositions.forEach(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) { - tickPositions.forEach(function (pos, i) { - to = typeof 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]) { - // Should be imported from PlotLineOrBand.js, but - // the dependency cycle with axis is a problem - alternateBands[pos] = new H.PlotLineOrBand(axis); - } - from = pos + tickmarkOffset; // #949 - alternateBands[pos].options = { - from: log ? log.lin2log(from) : from, - to: log ? log.lin2log(to) : to, - color: alternateGridColor, - className: 'highcharts-alternate-grid' - }; - alternateBands[pos].render(); - alternateBands[pos].isActive = true; - } - }); - } - // custom plot lines and bands - if (!axis._addedPlotLB) { // only first time - (options.plotLines || []) - .concat(options.plotBands || []) - .forEach(function (plotLineOptions) { - axis.addPlotBandOrLine(plotLineOptions); - }); - axis._addedPlotLB = true; - } - } // end if hasData - // Remove inactive ticks - [ticks, minorTicks, alternateBands].forEach(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'](showAxis); - } - 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.stacking) { - axis.stacking.renderStackTotals(); - } - // End stacked totals - axis.isDirty = false; - fireEvent(this, 'afterRender'); - }; - /** - * Redraw the axis to reflect changes in the data or axis extremes. Called - * internally from Highcharts.Chart#redraw. - * - * @private - * @function Highcharts.Axis#redraw - */ - Axis.prototype.redraw = function () { - if (this.visible) { - // render the axis - this.render(); - // move plot lines and bands - this.plotLinesAndBands.forEach(function (plotLine) { - plotLine.render(); - }); - } - // mark associated series as dirty and ready for redraw - this.series.forEach(function (series) { - series.isDirty = true; - }); - }; - /** - * Returns an array of axis properties, that should be untouched during - * reinitialization. - * - * @private - * @function Highcharts.Axis#getKeepProps - * - * @return {Array<string>} - */ - Axis.prototype.getKeepProps = function () { - return (this.keepProps || Axis.keepProps); - }; - /** - * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint - * to fully remove the axis. - * - * @private - * @function Highcharts.Axis#destroy - * - * @param {boolean} [keepEvents] - * Whether to preserve events, used internally in Axis.update. - */ - Axis.prototype.destroy = function (keepEvents) { - var axis = this, - plotLinesAndBands = axis.plotLinesAndBands, - plotGroup, - i; - fireEvent(this, 'destroy', { keepEvents: keepEvents }); - // Remove the events - if (!keepEvents) { - removeEvent(axis); - } - // Destroy collections - [axis.ticks, axis.minorTicks, axis.alternateBands].forEach(function (coll) { - destroyObjectProperties(coll); - }); - if (plotLinesAndBands) { - i = plotLinesAndBands.length; - while (i--) { // #1975 - plotLinesAndBands[i].destroy(); - } - } - // Destroy elements - ['axisLine', 'axisTitle', 'axisGroup', - 'gridGroup', 'labelGroup', 'cross', 'scrollbar'].forEach(function (prop) { - if (axis[prop]) { - axis[prop] = axis[prop].destroy(); - } - }); - // Destroy each generated group for plotlines and plotbands - for (plotGroup in axis.plotLinesAndBandsGroups) { // eslint-disable-line guard-for-in - axis.plotLinesAndBandsGroups[plotGroup] = - axis.plotLinesAndBandsGroups[plotGroup].destroy(); - } - // Delete all properties and fall back to the prototype. - objectEach(axis, function (val, key) { - if (axis.getKeepProps().indexOf(key) === -1) { - delete axis[key]; - } - }); - }; - /** - * Internal function to draw a crosshair. - * - * @function Highcharts.Axis#drawCrosshair - * - * @param {Highcharts.PointerEventObject} [e] - * The event arguments from the modified pointer event, extended with - * `chartX` and `chartY` - * - * @param {Highcharts.Point} [point] - * The Point object if the crosshair snaps to points. - * - * @fires Highcharts.Axis#event:afterDrawCrosshair - * @fires Highcharts.Axis#event:drawCrosshair - */ - Axis.prototype.drawCrosshair = function (e, point) { - var path, - options = this.crosshair, - snap = pick(options.snap, - true), - pos, - categorized, - graphic = this.cross, - crossOptions, - chart = this.chart; - fireEvent(this, 'drawCrosshair', { e: e, point: point }); - // 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 = pick(this.coll !== 'colorAxis' ? - point.crosshairPos : // 3D axis extension - null, this.isXAxis ? - point.plotX : - this.len - point.plotY); - } - if (defined(pos)) { - crossOptions = { - // value, only used on radial - value: point && (this.isXAxis ? - point.x : - pick(point.stackY, point.y)), - translatedValue: pos - }; - if (chart.polar) { - // Additional information required for crosshairs in - // polar chart - extend(crossOptions, { - isCrosshair: true, - chartX: e && e.chartX, - chartY: e && e.chartY, - point: point - }); - } - path = this.getPlotLinePath(crossOptions) || - null; // #3189 - } - if (!defined(path)) { - this.hideCrosshair(); - return; - } - categorized = this.categories && !this.isRadial; - // Draw the cross - if (!graphic) { - this.cross = graphic = chart.renderer - .path() - .addClass('highcharts-crosshair highcharts-crosshair-' + - (categorized ? 'category ' : 'thin ') + - options.className) - .attr({ - zIndex: pick(options.zIndex, 2) - }) - .add(); - // Presentational attributes - if (!chart.styledMode) { - graphic.attr({ - stroke: options.color || - (categorized ? - Color - .parse('#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; - } - fireEvent(this, 'afterDrawCrosshair', { e: e, point: point }); - }; - /** - * Hide the crosshair if visible. - * - * @function Highcharts.Axis#hideCrosshair - */ - Axis.prototype.hideCrosshair = function () { - if (this.cross) { - this.cross.hide(); - } - fireEvent(this, 'afterHideCrosshair'); - }; - /** - * Check whether the chart has vertical panning ('y' or 'xy' type). - * - * @private - * @function Highcharts.Axis#hasVerticalPanning - * @return {boolean} - * - */ - Axis.prototype.hasVerticalPanning = function () { - var _a, - _b; - return /y/.test(((_b = (_a = this.chart.options.chart) === null || _a === void 0 ? void 0 : _a.panning) === null || _b === void 0 ? void 0 : _b.type) || ''); - }; - /** - * Check whether the given value is a positive valid axis value. - * - * @private - * @function Highcharts.Axis#validatePositiveValue - * - * @param {unknown} value - * The axis value - * @return {boolean} - * - */ - Axis.prototype.validatePositiveValue = function (value) { - return isNumber(value) && value > 0; - }; - /* * - * - * Static Properties - * - * */ - /** - * 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 class](/class-reference/Highcharts.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. - * - * @type {*|Array<*>} - * @optionparent xAxis - * - * @private - */ - Axis.defaultOptions = { - /** - * 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. - * - * If `startOnTick` or `endOnTick` in an Axis options are set to false, - * then the `alignTicks ` will be disabled for the Axis. - * - * Disabled for logarithmic axes. - * - * @type {boolean} - * @default true - * @product highcharts highstock gantt - * @apioption xAxis.alignTicks - */ - /** - * 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. - * - * @see [minTickInterval](#xAxis.minTickInterval) - * - * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-true/ - * True by default - * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-false/ - * False - * - * @type {boolean} - * @default true - * @since 2.0 - * @apioption xAxis.allowDecimals - */ - /** - * When using an alternate grid color, a band is painted across the - * plot area between every other grid line. - * - * @sample {highcharts} highcharts/yaxis/alternategridcolor/ - * Alternate grid color on the Y axis - * @sample {highstock} stock/xaxis/alternategridcolor/ - * Alternate grid color on the Y axis - * - * @type {Highcharts.ColorType} - * @apioption xAxis.alternateGridColor - */ - /** - * 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. - * - * @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 - * - * @type {Array<*>} - * @since 4.1.0 - * @product highcharts highstock gantt - * @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 gantt - * @apioption xAxis.breaks.breakSize - */ - /** - * The point where the break starts. - * - * @type {number} - * @since 4.1.0 - * @product highcharts highstock gantt - * @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 gantt - * @apioption xAxis.breaks.repeat - */ - /** - * The point where the break ends. - * - * @type {number} - * @since 4.1.0 - * @product highcharts highstock gantt - * @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']` - * - * @sample {highcharts} highcharts/demo/line-labels/ - * With - * @sample {highcharts} highcharts/xaxis/categories/ - * Without - * - * @type {Array<string>} - * @product highcharts gantt - * @apioption xAxis.categories - */ - /** - * The highest allowed value for automatically computed axis extremes. - * - * @see [floor](#xAxis.floor) - * - * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/ - * Floor and ceiling - * - * @type {number} - * @since 4.0 - * @product highcharts highstock gantt - * @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. - * - * @sample {highcharts|highstock|highmaps} highcharts/css/axis/ - * Multiple axes with separate styling - * - * @type {string} - * @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, by default, the crosshair is enabled on the X axis and - * disabled on the Y axis. - * - * @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 - * - * @declare Highcharts.AxisCrosshairOptions - * @type {boolean|*} - * @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. - * - * @sample {highcharts|highstock|highmaps} highcharts/xaxis/crosshair-customized/ - * Customized crosshairs - * - * @type {Highcharts.ColorType} - * @default #cccccc - * @since 4.1 - * @apioption xAxis.crosshair.color - */ - /** - * The dash style for the crosshair. See - * [plotOptions.series.dashStyle](#plotOptions.series.dashStyle) - * for possible values. - * - * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/ - * Dotted crosshair - * @sample {highstock} stock/xaxis/crosshair-dashed/ - * Dashed X axis crosshair - * - * @type {Highcharts.DashStyleValue} - * @default Solid - * @since 4.1 - * @apioption xAxis.crosshair.dashStyle - */ - /** - * A label on the axis next to the crosshair. - * - * In styled mode, the label is styled with the - * `.highcharts-crosshair-label` class. - * - * @sample {highstock} stock/xaxis/crosshair-label/ - * Crosshair labels - * @sample {highstock} highcharts/css/crosshair-label/ - * Style mode - * - * @declare Highcharts.AxisCrosshairLabelOptions - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label - */ - /** - * Alignment of the label compared to the axis. Defaults to `"left"` for - * right-side axes, `"right"` for left-side axes and `"center"` for - * horizontal axes. - * - * @type {Highcharts.AlignValue} - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.align - */ - /** - * The background color for the label. Defaults to the related series - * color, or `#666666` if that is not available. - * - * @type {Highcharts.ColorType} - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.backgroundColor - */ - /** - * The border color for the crosshair label - * - * @type {Highcharts.ColorType} - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.borderColor - */ - /** - * The border corner radius of the crosshair label. - * - * @type {number} - * @default 3 - * @since 2.1.10 - * @product highstock - * @apioption xAxis.crosshair.label.borderRadius - */ - /** - * The border width for the crosshair label. - * - * @type {number} - * @default 0 - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.borderWidth - */ - /** - * Flag to enable crosshair's label. - * - * @sample {highstock} stock/xaxis/crosshairs-xy/ - * Enabled label for yAxis' crosshair - * - * @type {boolean} - * @default false - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.enabled - */ - /** - * A format string for the crosshair label. Defaults to `{value}` for - * numeric axes and `{value:%b %d, %Y}` for datetime axes. - * - * @type {string} - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.format - */ - /** - * Formatter function for the label text. - * - * @type {Highcharts.XAxisCrosshairLabelFormatterCallbackFunction} - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.formatter - */ - /** - * Padding inside the crosshair label. - * - * @type {number} - * @default 8 - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.padding - */ - /** - * The shape to use for the label box. - * - * @type {string} - * @default callout - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.shape - */ - /** - * Text styles for the crosshair label. - * - * @type {Highcharts.CSSObject} - * @default {"color": "white", "fontWeight": "normal", "fontSize": "11px", "textAlign": "center"} - * @since 2.1 - * @product highstock - * @apioption xAxis.crosshair.label.style - */ - /** - * Whether the crosshair should snap to the point or follow the pointer - * independent of points. - * - * @sample {highcharts|highstock} highcharts/xaxis/crosshair-snap-false/ - * True by default - * @sample {highmaps} maps/demo/latlon-advanced/ - * Snap is false - * - * @type {boolean} - * @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. - * - * @sample {highcharts} highcharts/xaxis/crosshair-customized/ - * Customized crosshairs - * @sample {highstock} highcharts/xaxis/crosshair-customized/ - * Customized crosshairs - * @sample {highmaps} highcharts/xaxis/crosshair-customized/ - * Customized crosshairs - * - * @type {number} - * @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 - */ - /** - * Whether to zoom axis. If `chart.zoomType` is set, the option allows - * to disable zooming on an individual axis. - * - * @sample {highcharts} highcharts/xaxis/zoomenabled/ - * Zoom enabled is false - * - * - * @type {boolean} - * @default enabled - * @apioption xAxis.zoomEnabled - */ - /** - * 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](/class-reference/Highcharts#dateFormat). - * - * Defaults to: - * ```js - * { - * 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' - * } - * ``` - * - * @sample {highcharts} highcharts/xaxis/datetimelabelformats/ - * Different day format on X axis - * @sample {highstock} stock/xaxis/datetimelabelformats/ - * More information in x axis labels - * - * @declare Highcharts.AxisDateTimeLabelFormatsOptions - * @product highcharts highstock gantt - */ - dateTimeLabelFormats: { - /** - * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject - * @type {string|*} - */ - millisecond: { - main: '%H:%M:%S.%L', - range: false - }, - /** - * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject - * @type {string|*} - */ - second: { - main: '%H:%M:%S', - range: false - }, - /** - * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject - * @type {string|*} - */ - minute: { - main: '%H:%M', - range: false - }, - /** - * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject - * @type {string|*} - */ - hour: { - main: '%H:%M', - range: false - }, - /** - * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject - * @type {string|*} - */ - day: { - main: '%e. %b' - }, - /** - * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject - * @type {string|*} - */ - week: { - main: '%e. %b' - }, - /** - * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject - * @type {string|*} - */ - month: { - main: '%b \'%y' - }, - /** - * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject - * @type {string|*} - */ - year: { - main: '%Y' - } - }, - /** - * 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. - * - * @type {*} - * @apioption xAxis.events - */ - /** - * An event fired after the breaks have rendered. - * - * @see [breaks](#xAxis.breaks) - * - * @sample {highcharts} highcharts/axisbreak/break-event/ - * AfterBreak Event - * - * @type {Highcharts.AxisEventCallbackFunction} - * @since 4.1.0 - * @product highcharts gantt - * @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 {Highcharts.AxisSetExtremesEventCallbackFunction} - * @since 2.3 - * @context Highcharts.Axis - * @apioption xAxis.events.afterSetExtremes - */ - /** - * An event fired when a break from this axis occurs on a point. - * - * @see [breaks](#xAxis.breaks) - * - * @sample {highcharts} highcharts/axisbreak/break-visualized/ - * Visualization of a Break - * - * @type {Highcharts.AxisPointBreakEventCallbackFunction} - * @since 4.1.0 - * @product highcharts gantt - * @context Highcharts.Axis - * @apioption xAxis.events.pointBreak - */ - /** - * An event fired when a point falls inside a break from this axis. - * - * @type {Highcharts.AxisPointBreakEventCallbackFunction} - * @product highcharts highstock gantt - * @context Highcharts.Axis - * @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`. - * - * @sample {highstock} stock/xaxis/events-setextremes/ - * Log new extremes on x axis - * - * @type {Highcharts.AxisSetExtremesEventCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Axis - * @apioption xAxis.events.setExtremes - */ - /** - * The lowest allowed value for automatically computed axis extremes. - * - * @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 - * - * @type {number} - * @since 4.0 - * @product highcharts highstock gantt - * @apioption xAxis.floor - */ - /** - * The dash or dot style of the grid lines. For possible values, see - * [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). - * - * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/ - * Long dashes - * @sample {highstock} stock/xaxis/gridlinedashstyle/ - * Long dashes - * - * @type {Highcharts.DashStyleValue} - * @default Solid - * @since 1.2 - * @apioption xAxis.gridLineDashStyle - */ - /** - * The Z index of the grid lines. - * - * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/ - * A Z index of 4 renders the grid above the graph - * - * @type {number} - * @default 1 - * @product highcharts highstock gantt - * @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()`. - * - * @sample {highcharts} highcharts/xaxis/id/ - * Get the object - * @sample {highstock} stock/xaxis/id/ - * Get the object - * - * @type {string} - * @since 1.2.0 - * @apioption xAxis.id - */ - /** - * The axis labels show the number or category for each tick. - * - * Since v8.0.0: Labels are animated in categorized x-axis with - * updating data if `tickInterval` and `step` is set to 1. - * - * @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. - * - * @see [reserveSpace](#xAxis.labels.reserveSpace) - * - * @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 - * - * @type {Highcharts.AlignValue} - * @apioption xAxis.labels.align - */ - /** - * 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. - * - * @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 - * - * @type {Array<number>|false} - * @default [-45] - * @since 4.1.0 - * @product highcharts highstock gantt - * @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. - * - * @sample {highcharts} highcharts/xaxis/labels-autorotationlimit/ - * Lower limit - * - * @type {number} - * @default 80 - * @since 4.1.5 - * @product highcharts gantt - * @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 gantt - * @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|gantt} true - * @default {highmaps} false - */ - enabled: true, - /** - * A [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) - * for the axis label. - * - * @sample {highcharts|highstock} highcharts/yaxis/labels-format/ - * Add units to Y axis label - * - * @type {string} - * @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: - * ```js - * function() { - * return this.value; - * } - * ``` - * - * @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 - * - * @type {Highcharts.AxisLabelsFormatterCallbackFunction} - * @apioption xAxis.labels.formatter - */ - /** - * The number of pixels to indent the labels per level in a treegrid - * axis. - * - * @sample gantt/treegrid-axis/demo - * Indentation 10px by default. - * @sample gantt/treegrid-axis/indentation-0px - * Indentation set to 0px. - * - * @product gantt - */ - indentation: 10, - /** - * Horizontal axis only. When `staggerLines` is not set, - * `maxStaggerLines` defines how many lines the axis is allowed to - * add to automatically avoid overlapping X labels. Set to `1` to - * disable overlap detection. - * - * @deprecated - * @type {number} - * @default 5 - * @since 1.3.3 - * @apioption xAxis.labels.maxStaggerLines - */ - /** - * How to handle overflowing labels on horizontal axis. If set to - * `"allow"`, it will not be aligned at all. By default it - * `"justify"` labels inside the chart area. If there is room to - * move it, it will be aligned to the edge, else it will be removed. - * - * @type {string} - * @default justify - * @since 2.2.5 - * @validvalue ["allow", "justify"] - * @apioption xAxis.labels.overflow - */ - /** - * The pixel padding for axis labels, to ensure white space between - * them. - * - * @type {number} - * @default 5 - * @product highcharts gantt - * @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. - * - * @see [labels.align](#xAxis.labels.align) - * - * @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 - * - * @type {boolean} - * @since 4.1.10 - * @product highcharts gantt - * @apioption xAxis.labels.reserveSpace - */ - /** - * Rotation of the labels in degrees. - * - * @sample {highcharts} highcharts/xaxis/labels-rotation/ - * X axis labels rotated 90° - * - * @type {number} - * @default 0 - * @apioption xAxis.labels.rotation - */ - /** - * Horizontal axes only. The number of lines to spread the labels - * over to make room or tighter labels. - * - * @sample {highcharts} highcharts/xaxis/labels-staggerlines/ - * Show labels over two lines - * @sample {highstock} stock/xaxis/labels-staggerlines/ - * Show labels over two lines - * - * @type {number} - * @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](https://www.highcharts.com/docs/chart-concepts/axes) - * => What axis should I use? - * - * @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 - * - * @type {number} - * @since 2.1 - * @apioption xAxis.labels.step - */ - /** - * Whether to [use HTML](https://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 all labels relative to the tick - * positions on the axis. - * - * @sample {highcharts} highcharts/xaxis/labels-x/ - * Y axis labels placed on grid lines - */ - x: 0, - /** - * The y position offset of all labels relative to the tick - * positions on the axis. The default makes it adapt to the font - * size of the bottom axis. - * - * @sample {highcharts} highcharts/xaxis/labels-x/ - * Y axis labels placed on grid lines - * - * @type {number} - * @apioption xAxis.labels.y - */ - /** - * The Z index for the axis labels. - * - * @type {number} - * @default 7 - * @apioption xAxis.labels.zIndex - */ - /** - * 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. - * - * @sample {highcharts} highcharts/xaxis/labels-style/ - * Red X axis labels - * - * @type {Highcharts.CSSObject} - */ - style: { - /** @internal */ - color: '#666666', - /** @internal */ - cursor: 'default', - /** @internal */ - fontSize: '11px' - } - }, - /** - * The left position as the horizontal axis. If it's a number, it is - * interpreted as pixel position relative to the chart. - * - * Since Highcharts v5.0.13: If it's a percentage string, it is - * interpreted as percentages of the plot width, offset from plot area - * left. - * - * @type {number|string} - * @product highcharts highstock - * @apioption xAxis.left - */ - /** - * The top position as the vertical axis. If it's a number, it is - * interpreted as pixel position relative to the chart. - * - * Since Highcharts 2: If it's a percentage string, it is interpreted - * as percentages of the plot height, offset from plot area top. - * - * @type {number|string} - * @product highcharts highstock - * @apioption xAxis.top - */ - /** - * 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. - * - * @sample {highcharts} highcharts/xaxis/linkedto/ - * Different string formats of the same date - * @sample {highcharts} highcharts/yaxis/linkedto/ - * Y values on both sides - * - * @type {number} - * @since 2.0.2 - * @product highcharts highstock gantt - * @apioption xAxis.linkedTo - */ - /** - * The maximum value of the axis. If `null`, the max value is - * automatically calculated. - * - * If the [endOnTick](#yAxis.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. - * - * @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 - * - * @type {number|null} - * @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](#yAxis.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). - * - * @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 - * - * @type {number|null} - * @apioption xAxis.min - */ - /** - * The dash or dot style of the minor grid lines. For possible values, - * see [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). - * - * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/ - * Long dashes on minor grid lines - * @sample {highstock} stock/xaxis/minorgridlinedashstyle/ - * Long dashes on minor grid lines - * - * @type {Highcharts.DashStyleValue} - * @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` or `undefined`, 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. - * - * @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" - * - * @type {number|string|null} - * @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`. - * - * @sample {highcharts} highcharts/yaxis/minortickposition-outside/ - * Outside by default - * @sample {highcharts} highcharts/yaxis/minortickposition-inside/ - * Inside - * @sample {highstock} stock/xaxis/minorticks/ - * Inside - * - * @validvalue ["inside", "outside"] - */ - 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. - * - * @sample {highcharts} highcharts/yaxis/minorticks-true/ - * Enabled on linear Y axis - * - * @type {boolean} - * @default false - * @since 6.0.0 - * @apioption xAxis.minorTicks - */ - /** - * The pixel width of the minor tick mark. - * - * @sample {highcharts} highcharts/yaxis/minortickwidth/ - * 3px width - * @sample {highstock} stock/xaxis/minorticks/ - * 1px width - * - * @type {number} - * @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 - * @product highcharts highstock gantt - */ - 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**: The `minPadding`, `maxPadding`, `startOnTick` and - * `endOnTick` settings also affect how the extremes of the axis - * are computed. - * - * @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 - * - * @type {number} - * @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. - * - * @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 - * - * @type {number} - * @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. - * - * @sample {highcharts} highcharts/yaxis/opposite/ - * Secondary Y axis opposite - * @sample {highstock} stock/xaxis/opposite/ - * Y axis on left side - * - * @type {boolean} - * @default {highcharts|highstock|highmaps} false - * @default {gantt} true - * @apioption xAxis.opposite - */ - /** - * In an ordinal axis, the points are equally spaced in the chart - * regardless of the actual time or x distance between them. This means - * that missing data periods (e.g. nights or weekends for a stock chart) - * will not take up space in the chart. - * Having `ordinal: false` will show any gaps created by the `gapSize` - * setting proportionate to their duration. - * - * In stock charts the X axis is ordinal by default, unless - * the boost module is used and at least one of the series' data length - * exceeds the [boostThreshold](#series.line.boostThreshold). - * - * @sample {highstock} stock/xaxis/ordinal-true/ - * True by default - * @sample {highstock} stock/xaxis/ordinal-false/ - * False - * - * @type {boolean} - * @default true - * @since 1.1 - * @product highstock - * @apioption xAxis.ordinal - */ - /** - * 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 the navigator's `xAxis`. - * - * @sample {highstock} stock/xaxis/overscroll/ - * One minute overscroll with live data - * - * @type {number} - * @default 0 - * @since 6.0.0 - * @product highstock - * @apioption xAxis.overscroll - */ - /** - * 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. - * - * @sample highcharts/demo/gauge-vu-meter - * Two gauges with different center - * - * @type {number} - * @product highcharts - * @apioption xAxis.pane - */ - /** - * The zoomed range to display when only defining one or none of `min` - * or `max`. For example, to show the latest month, a range of one month - * can be set. - * - * @sample {highstock} stock/xaxis/range/ - * Setting a zoomed range when the rangeSelector is disabled - * - * @type {number} - * @product highstock - * @apioption xAxis.range - */ - /** - * 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. - * - * @sample {highcharts} highcharts/yaxis/reversed/ - * Reversed Y axis - * @sample {highstock} stock/xaxis/reversed/ - * Reversed Y axis - * - * @type {boolean} - * @default false - * @apioption xAxis.reversed - */ - // reversed: false, - /** - * This option determines how stacks should be ordered within a group. - * For example reversed xAxis also reverses stacks, so first series - * comes last in a group. To keep order like for non-reversed xAxis - * enable this option. - * - * @sample {highcharts} highcharts/xaxis/reversedstacks/ - * Reversed stacks comparison - * @sample {highstock} highcharts/xaxis/reversedstacks/ - * Reversed stacks comparison - * - * @type {boolean} - * @default false - * @since 6.1.1 - * @product highcharts highstock - * @apioption xAxis.reversedStacks - */ - /** - * An optional scrollbar to display on the X axis in response to - * limiting the minimum and maximum of the axis values. - * - * 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`. - * - * @sample {highstock} stock/yaxis/heatmap-scrollbars/ - * Heatmap with both scrollbars - * - * @extends scrollbar - * @since 4.2.6 - * @product highstock - * @apioption xAxis.scrollbar - */ - /** - * Whether to show the axis line and title when the axis has no data. - * - * @sample {highcharts} highcharts/yaxis/showempty/ - * When clicking the legend to hide series, one axis preserves - * line and title, the other doesn't - * @sample {highstock} highcharts/yaxis/showempty/ - * When clicking the legend to hide series, one axis preserves - * line and title, the other doesn't - * - * @since 1.1 - */ - showEmpty: true, - /** - * Whether to show the first tick label. - * - * @sample {highcharts} highcharts/xaxis/showfirstlabel-false/ - * Set to false on X axis - * @sample {highstock} stock/xaxis/showfirstlabel/ - * Labels below plot lines on Y axis - * - * @type {boolean} - * @default true - * @apioption xAxis.showFirstLabel - */ - /** - * Whether to show the last tick label. Defaults to `true` on cartesian - * charts, and `false` on polar charts. - * - * @sample {highcharts} highcharts/xaxis/showlastlabel-true/ - * Set to true on X axis - * @sample {highstock} stock/xaxis/showfirstlabel/ - * Labels below plot lines on Y axis - * - * @type {boolean} - * @default true - * @product highcharts highstock gantt - * @apioption xAxis.showLastLabel - */ - /** - * A soft maximum for the axis. If the series data maximum is less than - * this, the axis will stay at this maximum, but if the series data - * maximum is higher, the axis will flex to show all data. - * - * @sample highcharts/yaxis/softmin-softmax/ - * Soft min and max - * - * @type {number} - * @since 5.0.1 - * @product highcharts highstock gantt - * @apioption xAxis.softMax - */ - /** - * A soft minimum for the axis. If the series data minimum is greater - * than this, the axis will stay at this minimum, but if the series - * data minimum is lower, the axis will flex to show all data. - * - * @sample highcharts/yaxis/softmin-softmax/ - * Soft min and max - * - * @type {number} - * @since 5.0.1 - * @product highcharts highstock gantt - * @apioption xAxis.softMin - */ - /** - * 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 gantt - */ - 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 - * - * @since 1.2.0 - */ - startOnTick: false, - /** - * The amount of ticks to draw on the axis. This opens up for aligning - * the ticks of multiple charts or panes within a chart. This option - * overrides the `tickPixelInterval` option. - * - * This option only has an effect on linear axes. Datetime, logarithmic - * or category axes are not affected. - * - * @sample {highcharts} highcharts/yaxis/tickamount/ - * 8 ticks on Y axis - * @sample {highstock} highcharts/yaxis/tickamount/ - * 8 ticks on Y axis - * - * @type {number} - * @since 4.1.0 - * @product highcharts highstock gantt - * @apioption xAxis.tickAmount - */ - /** - * The interval of the tick marks in axis units. When `undefined`, the - * tick interval is computed to approximately follow the - * [tickPixelInterval](#xAxis.tickPixelInterval) on linear and datetime - * axes. On categorized axes, a `undefined` tickInterval will default to - * 1, one category. Note that datetime axes are based on milliseconds, - * so for example an interval of one day is expressed as - * `24 * 3600 * 1000`. - * - * On logarithmic axes, the tickInterval is based on powers, so a - * tickInterval of 1 means one tick on each of 0.1, 1, 10, 100 etc. A - * tickInterval of 2 means a tick of 0.1, 10, 1000 etc. A tickInterval - * of 0.2 puts a tick on 0.1, 0.2, 0.4, 0.6, 0.8, 1, 2, 4, 6, 8, 10, 20, - * 40 etc. - * - * - * If the tickInterval is too dense for labels to be drawn, Highcharts - * may remove ticks. - * - * If the chart has multiple axes, the [alignTicks](#chart.alignTicks) - * option may interfere with the `tickInterval` setting. - * - * @see [tickPixelInterval](#xAxis.tickPixelInterval) - * @see [tickPositions](#xAxis.tickPositions) - * @see [tickPositioner](#xAxis.tickPositioner) - * - * @sample {highcharts} highcharts/xaxis/tickinterval-5/ - * Tick interval of 5 on a linear axis - * @sample {highstock} stock/xaxis/tickinterval/ - * Tick interval of 0.01 on Y axis - * - * @type {number} - * @apioption xAxis.tickInterval - */ - /** - * 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, - /** - * 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) - * @see [tickPositioner](#xAxis.tickPositioner) - * @see [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, - /** - * 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`. - * - * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/ - * "between" by default - * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/ - * "on" - * - * @product highcharts gantt - * @validvalue ["on", "between"] - */ - tickmarkPlacement: 'between', - /** - * The position of the major tick marks relative to the axis line. - * Can be one of `inside` and `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 - * - * @validvalue ["inside", "outside"] - */ - tickPosition: 'outside', - /** - * A callback function returning array defining where the ticks are - * laid out on the axis. This overrides the default behaviour of - * [tickPixelInterval](#xAxis.tickPixelInterval) and [tickInterval]( - * #xAxis.tickInterval). The automatic tick positions are accessible - * through `this.tickPositions` and can be modified by the callback. - * - * @see [tickPositions](#xAxis.tickPositions) - * - * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/ - * Demo of tickPositions and tickPositioner - * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/ - * Demo of tickPositions and tickPositioner - * - * @type {Highcharts.AxisTickPositionerCallbackFunction} - * @apioption xAxis.tickPositioner - */ - /** - * An array defining where the ticks are laid out on the axis. This - * overrides the default behaviour of [tickPixelInterval]( - * #xAxis.tickPixelInterval) and [tickInterval](#xAxis.tickInterval). - * - * @see [tickPositioner](#xAxis.tickPositioner) - * - * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/ - * Demo of tickPositions and tickPositioner - * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/ - * Demo of tickPositions and tickPositioner - * - * @type {Array<number>} - * @apioption xAxis.tickPositions - */ - /** - * The pixel width of the major tick marks. Defaults to 0 on category - * axes, otherwise 1. - * - * In styled mode, the stroke width is given in the `.highcharts-tick` - * class, but in order for the element to be generated on category axes, - * the option must be explicitly set to 1. - * - * @sample {highcharts} highcharts/xaxis/tickwidth/ - * 10 px width - * @sample {highcharts} highcharts/css/axis-grid/ - * Styled mode - * @sample {highstock} stock/xaxis/ticks/ - * Formatted ticks on X axis - * @sample {highstock} highcharts/css/axis-grid/ - * Styled mode - * - * @type {undefined|number} - * @default {highstock} 1 - * @default {highmaps} 0 - * @apioption xAxis.tickWidth - */ - /** - * 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: { - /** - * Deprecated. Set the `text` to `null` to disable the title. - * - * @deprecated - * @type {boolean} - * @product highcharts - * @apioption xAxis.title.enabled - */ - /** - * The pixel distance between the axis labels or line and the title. - * Defaults to 0 for horizontal axes, 10 for vertical - * - * @sample {highcharts} highcharts/xaxis/title-margin/ - * Y axis title margin of 60 - * - * @type {number} - * @apioption xAxis.title.margin - */ - /** - * The distance of the axis title from the axis line. By default, - * this distance is computed from the offset width of the labels, - * the labels' distance from the axis and the title's margin. - * However when the offset option is set, it overrides all this. - * - * @sample {highcharts} highcharts/yaxis/title-offset/ - * Place the axis title on top of the axis - * @sample {highstock} highcharts/yaxis/title-offset/ - * Place the axis title on top of the Y axis - * - * @type {number} - * @since 2.2.0 - * @apioption xAxis.title.offset - */ - /** - * Whether to reserve space for the title when laying out the axis. - * - * @type {boolean} - * @default true - * @since 5.0.11 - * @product highcharts highstock gantt - * @apioption xAxis.title.reserveSpace - */ - /** - * 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 - * - * @type {number} - * @default 0 - * @apioption xAxis.title.rotation - */ - /** - * The actual text of the axis title. It can contain basic HTML tags - * like `b`, `i` and `span` with style. - * - * @sample {highcharts} highcharts/xaxis/title-text/ - * Custom HTML - * @sample {highstock} stock/xaxis/title-text/ - * Titles for both axes - * - * @type {string|null} - * @apioption xAxis.title.text - */ - /** - * Alignment of the text, can be `"left"`, `"right"` or `"center"`. - * Default alignment depends on the - * [title.align](xAxis.title.align): - * - * Horizontal axes: - * - for `align` = `"low"`, `textAlign` is set to `left` - * - for `align` = `"middle"`, `textAlign` is set to `center` - * - for `align` = `"high"`, `textAlign` is set to `right` - * - * Vertical axes: - * - for `align` = `"low"` and `opposite` = `true`, `textAlign` is - * set to `right` - * - for `align` = `"low"` and `opposite` = `false`, `textAlign` is - * set to `left` - * - for `align` = `"middle"`, `textAlign` is set to `center` - * - for `align` = `"high"` and `opposite` = `true` `textAlign` is - * set to `left` - * - for `align` = `"high"` and `opposite` = `false` `textAlign` is - * set to `right` - * - * @type {Highcharts.AlignValue} - * @apioption xAxis.title.textAlign - */ - /** - * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) - * to render the axis title. - * - * @type {boolean} - * @default false - * @product highcharts highstock gantt - * @apioption xAxis.title.useHTML - */ - /** - * Horizontal pixel offset of the title position. - * - * @type {number} - * @default 0 - * @since 4.1.6 - * @product highcharts highstock gantt - * @apioption xAxis.title.x - */ - /** - * Vertical pixel offset of the title position. - * - * @type {number} - * @product highcharts highstock gantt - * @apioption xAxis.title.y - */ - /** - * Alignment of the title relative to the axis values. Possible - * values are "low", "middle" or "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 - * - * @type {Highcharts.AxisTitleAlignValue} - */ - 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 `whiteSpace: 'nowrap'`. - * - * In styled mode, the stroke width is given in the - * `.highcharts-axis-title` class. - * - * @sample {highcharts} highcharts/xaxis/title-style/ - * Red - * @sample {highcharts} highcharts/css/axis/ - * Styled mode - * - * @type {Highcharts.CSSObject} - */ - style: { - /** @internal */ - 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. - * - * @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 - * - * @type {Highcharts.AxisTypeValue} - * @product highcharts gantt - */ - type: 'linear', - /** - * If there are multiple axes on the same side of the chart, the pixel - * margin between the axes. Defaults to 0 on vertical axes, 15 on - * horizontal axes. - * - * @type {number} - * @since 7.0.3 - * @apioption xAxis.margin - */ - /** - * Applies only when the axis `type` is `category`. When `uniqueNames` - * is true, points are placed on the X axis according to their names. - * If the same point name is repeated in the same or another series, - * the point is placed on the same X position as other points of the - * same name. When `uniqueNames` is false, the points are laid out in - * increasing X positions regardless of their names, and the X axis - * category will take the name of the last point in each position. - * - * @sample {highcharts} highcharts/xaxis/uniquenames-true/ - * True by default - * @sample {highcharts} highcharts/xaxis/uniquenames-false/ - * False - * - * @type {boolean} - * @default true - * @since 4.2.7 - * @product highcharts gantt - * @apioption xAxis.uniqueNames - */ - /** - * Datetime axis only. An array determining what time intervals the - * ticks are allowed to fall on. 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: - * ```js - * 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<Array<string,(Array<number>|null)>>} - * @product highcharts highstock gantt - * @apioption xAxis.units - */ - /** - * Whether axis, including axis title, line, ticks and labels, should - * be visible. - * - * @type {boolean} - * @default true - * @since 4.1.9 - * @product highcharts highstock gantt - * @apioption xAxis.visible - */ - /** - * Color of the minor, secondary grid lines. - * - * In styled mode, the stroke width is given in the - * `.highcharts-minor-grid-line` class. - * - * @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 - * - * @type {Highcharts.ColorType} - * @default #f2f2f2 - */ - minorGridLineColor: '#f2f2f2', - /** - * 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. - * - * @sample {highcharts} highcharts/yaxis/minortickcolor/ - * Black tick marks on Y axis - * @sample {highstock} stock/xaxis/minorticks/ - * Black tick marks on Y axis - * - * @type {Highcharts.ColorType} - * @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. - * - * @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 - * - * @type {Highcharts.ColorType} - * @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. - * - * @sample {highcharts} highcharts/yaxis/gridlinecolor/ - * Green lines - * @sample {highcharts|highstock} highcharts/css/axis-grid/ - * Styled mode - * @sample {highstock} stock/xaxis/gridlinecolor/ - * Green lines - * - * @type {Highcharts.ColorType} - * @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. - * - * @sample {highcharts} highcharts/yaxis/gridlinewidth/ - * 2px lines - * @sample {highcharts|highstock} highcharts/css/axis-grid/ - * Styled mode - * @sample {highstock} stock/xaxis/gridlinewidth/ - * 2px lines - * - * @type {number} - * @default 0 - * @apioption xAxis.gridLineWidth - */ - // gridLineWidth: 0, - /** - * The height as the vertical axis. If it's a number, it is - * interpreted as pixels. - * - * Since Highcharts 2: If it's a percentage string, it is interpreted - * as percentages of the total plot height. - * - * @type {number|string} - * @product highcharts highstock - * @apioption xAxis.height - */ - /** - * The width as the horizontal axis. If it's a number, it is interpreted - * as pixels. - * - * Since Highcharts v5.0.13: If it's a percentage string, it is - * interpreted as percentages of the total plot width. - * - * @type {number|string} - * @product highcharts highstock - * @apioption xAxis.width - */ - /** - * Color for the main tick marks. - * - * In styled mode, the stroke is given in the `.highcharts-tick` - * class. - * - * @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 - * - * @type {Highcharts.ColorType} - * @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](/class-reference/Highcharts.Axis) for programmatic - * access to the axis. - * - * @type {*|Array<*>} - * @extends xAxis - * @excluding currentDateIndicator,ordinal,overscroll - * @optionparent yAxis - * - * @private - */ - Axis.defaultYAxisOptions = { - /** - * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`, - * `category` or `treegrid`. Defaults to `treegrid` for Gantt charts, - * `linear` for other chart types. - * - * 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 or treegrid axis, the [point names](#series.line.data.name) - * of the chart's series are used for categories, if a - * [categories](#xAxis.categories) array is not defined. - * - * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/ - * Logarithmic with minor grid lines - * @sample {highcharts} highcharts/yaxis/type-log-negative/ - * Logarithmic with extension to emulate negative values - * @sample {gantt} gantt/treegrid-axis/demo - * Treegrid axis - * - * @type {Highcharts.AxisTypeValue} - * @default {highcharts} linear - * @default {gantt} treegrid - * @product highcharts gantt - * @apioption yAxis.type - */ - /** - * The height of the Y axis. If it's a number, it is interpreted as - * pixels. - * - * Since Highcharts 2: If it's a percentage string, it is interpreted as - * percentages of the total plot height. - * - * @see [yAxis.top](#yAxis.top) - * - * @sample {highstock} stock/demo/candlestick-and-volume/ - * Percentage height panes - * - * @type {number|string} - * @product highcharts highstock - * @apioption yAxis.height - */ - /** - * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color - * to represent the maximum value of the Y axis. - * - * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/ - * Min and max colors - * - * @type {Highcharts.ColorType} - * @default #003399 - * @since 4.0 - * @product highcharts - * @apioption yAxis.maxColor - */ - /** - * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color - * to represent the minimum value of the Y axis. - * - * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/ - * Min and max color - * - * @type {Highcharts.ColorType} - * @default #e6ebf5 - * @since 4.0 - * @product highcharts - * @apioption yAxis.minColor - */ - /** - * Whether to reverse the axis so that the highest number is closest - * to the origin. - * - * @sample {highcharts} highcharts/yaxis/reversed/ - * Reversed Y axis - * @sample {highstock} stock/xaxis/reversed/ - * Reversed Y axis - * - * @type {boolean} - * @default {highcharts} false - * @default {highstock} false - * @default {highmaps} true - * @default {gantt} true - * @apioption yAxis.reversed - */ - /** - * If `true`, the first series in a stack will be drawn on top in a - * positive, non-reversed Y axis. If `false`, the first series is in - * the base of the stack. - * - * @sample {highcharts} highcharts/yaxis/reversedstacks-false/ - * Non-reversed stacks - * @sample {highstock} highcharts/yaxis/reversedstacks-false/ - * Non-reversed stacks - * - * @type {boolean} - * @default true - * @since 3.0.10 - * @product highcharts highstock - * @apioption yAxis.reversedStacks - */ - /** - * Solid gauge series only. Color stops for the solid gauge. Use this - * in cases where a linear gradient between a `minColor` and `maxColor` - * is not sufficient. The stops is an array of tuples, where the first - * item is a float between 0 and 1 assigning the relative position in - * the gradient, and the second item is the color. - * - * For solid gauges, the Y axis also inherits the concept of - * [data classes](https://api.highcharts.com/highmaps#colorAxis.dataClasses) - * from the Highmaps color axis. - * - * @see [minColor](#yAxis.minColor) - * @see [maxColor](#yAxis.maxColor) - * - * @sample {highcharts} highcharts/demo/gauge-solid/ - * True by default - * - * @type {Array<Array<number,Highcharts.ColorType>>} - * @since 4.0 - * @product highcharts - * @apioption yAxis.stops - */ - /** - * The pixel width of the major tick marks. - * - * @sample {highcharts} highcharts/xaxis/tickwidth/ 10 px width - * @sample {highstock} stock/xaxis/ticks/ Formatted ticks on X axis - * - * @type {number} - * @default 0 - * @product highcharts highstock gantt - * @apioption yAxis.tickWidth - */ - /** - * Whether to force the axis to end on a tick. Use this option with - * the `maxPadding` option to control the axis end. - * - * This option is always disabled, when panning type is - * either `y` or `xy`. - * - * @see [type](#chart.panning.type) - * - * - * @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 for Y axis - * - * @since 1.2.0 - */ - endOnTick: true, - /** - * 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. - * - * Also the `softThreshold` option takes precedence over `maxPadding`, - * so if the data is tangent to the threshold, `maxPadding` may not - * apply unless `softThreshold` is set to false. - * - * @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 gantt - */ - 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. - * - * Also the `softThreshold` option takes precedence over `minPadding`, - * so if the data is tangent to the threshold, `minPadding` may not - * apply unless `softThreshold` is set to false. - * - * @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 gantt - */ - minPadding: 0.05, - /** - * @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 - * - * @type {boolean} - * @default {highstock} true - * @default {highcharts} false - * @product highstock highcharts gantt - * @apioption yAxis.opposite - */ - /** - * @see [tickInterval](#xAxis.tickInterval) - * @see [tickPositioner](#xAxis.tickPositioner) - * @see [tickPositions](#xAxis.tickPositions) - */ - tickPixelInterval: 72, - showLastLabel: true, - /** - * @extends xAxis.labels - */ - labels: { - /** - * Angular gauges and solid gauges only. - * The label's pixel distance from the perimeter of the plot area. - * - * Since v7.1.2: If it's a percentage string, it is interpreted the - * same as [series.radius](#plotOptions.gauge.radius), so label can be - * aligned under the gauge's shape. - * - * @sample {highcharts} highcharts/yaxis/labels-distance/ - * Labels centered under the arc - * - * @type {number|string} - * @default -25 - * @product highcharts - * @apioption yAxis.labels.distance - */ - /** - * The y position offset of all labels relative to the tick - * positions on the axis. For polar and radial axis consider the use - * of the [distance](#yAxis.labels.distance) option. - * - * @sample {highcharts} highcharts/xaxis/labels-x/ - * Y axis labels placed on grid lines - * - * @type {number} - * @default {highcharts} 3 - * @default {highstock} -2 - * @default {highmaps} 3 - * @apioption yAxis.labels.y - */ - /** - * 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"`. - * Solid gauges with two labels have additional option `"auto"` - * for automatic horizontal and vertical alignment. - * - * @see [yAxis.labels.distance](#yAxis.labels.distance) - * - * @sample {highcharts} highcharts/yaxis/labels-align-left/ - * Left - * @sample {highcharts} highcharts/series-solidgauge/labels-auto-aligned/ - * Solid gauge labels auto aligned - * - * @type {Highcharts.AlignValue} - * @default {highcharts|highmaps} right - * @default {highstock} left - * @apioption yAxis.labels.align - */ - /** - * The x position offset of all labels relative to the tick - * positions 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. - * - * @type {Highcharts.ColorType} - * @apioption yAxis.lineColor - */ - /** - * @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 - */ - /** - * @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 - */ - /** - * An optional scrollbar to display on the Y axis in response to - * limiting the minimum an maximum of the axis values. - * - * 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`. - * - * @sample {highstock} stock/yaxis/scrollbar/ - * Scrollbar on the Y axis - * - * @extends scrollbar - * @since 4.2.6 - * @product highstock - * @excluding height - * @apioption yAxis.scrollbar - */ - /** - * Enable the scrollbar on the Y axis. - * - * @sample {highstock} stock/yaxis/scrollbar/ - * Enabled on Y axis - * - * @type {boolean} - * @default false - * @since 4.2.6 - * @product highstock - * @apioption yAxis.scrollbar.enabled - */ - /** - * Pixel margin between the scrollbar and the axis elements. - * - * @type {number} - * @default 10 - * @since 4.2.6 - * @product highstock - * @apioption yAxis.scrollbar.margin - */ - /** - * Whether to show the scrollbar when it is fully zoomed out at max - * range. Setting it to `false` on the Y axis makes the scrollbar stay - * hidden until the user zooms in, like common in browsers. - * - * @type {boolean} - * @default true - * @since 4.2.6 - * @product highstock - * @apioption yAxis.scrollbar.showFull - */ - /** - * The width of a vertical scrollbar or height of a horizontal - * scrollbar. Defaults to 20 on touch devices. - * - * @type {number} - * @default 14 - * @since 4.2.6 - * @product highstock - * @apioption yAxis.scrollbar.size - */ - /** - * Z index of the scrollbar elements. - * - * @type {number} - * @default 3 - * @since 4.2.6 - * @product highstock - * @apioption yAxis.scrollbar.zIndex - */ - /** - * A soft maximum for the axis. If the series data maximum is less - * than this, the axis will stay at this maximum, but if the series - * data maximum is higher, the axis will flex to show all data. - * - * **Note**: The [series.softThreshold]( - * #plotOptions.series.softThreshold) option takes precedence over this - * option. - * - * @sample highcharts/yaxis/softmin-softmax/ - * Soft min and max - * - * @type {number} - * @since 5.0.1 - * @product highcharts highstock gantt - * @apioption yAxis.softMax - */ - /** - * A soft minimum for the axis. If the series data minimum is greater - * than this, the axis will stay at this minimum, but if the series - * data minimum is lower, the axis will flex to show all data. - * - * **Note**: The [series.softThreshold]( - * #plotOptions.series.softThreshold) option takes precedence over this - * option. - * - * @sample highcharts/yaxis/softmin-softmax/ - * Soft min and max - * - * @type {number} - * @since 5.0.1 - * @product highcharts highstock gantt - * @apioption yAxis.softMin - */ - /** - * Defines the horizontal alignment of the stack total label. Can be one - * of `"left"`, `"center"` or `"right"`. The default value is calculated - * at runtime and depends on orientation and whether the stack is - * positive or negative. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-align-left/ - * Aligned to the left - * @sample {highcharts} highcharts/yaxis/stacklabels-align-center/ - * Aligned in center - * @sample {highcharts} highcharts/yaxis/stacklabels-align-right/ - * Aligned to the right - * - * @type {Highcharts.AlignValue} - * @since 2.1.5 - * @product highcharts - * @apioption yAxis.stackLabels.align - */ - /** - * A format string for the data label. Available variables are the same - * as for `formatter`. - * - * @type {string} - * @default {total} - * @since 3.0.2 - * @product highcharts highstock - * @apioption yAxis.stackLabels.format - */ - /** - * Rotation of the labels in degrees. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-rotation/ - * Labels rotated 45° - * - * @type {number} - * @default 0 - * @since 2.1.5 - * @product highcharts - * @apioption yAxis.stackLabels.rotation - */ - /** - * The text alignment for the label. While `align` determines where the - * texts anchor point is placed with regards to the stack, `textAlign` - * determines how the text is aligned against its anchor point. Possible - * values are `"left"`, `"center"` and `"right"`. The default value is - * calculated at runtime and depends on orientation and whether the - * stack is positive or negative. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-textalign-left/ - * Label in center position but text-aligned left - * - * @type {Highcharts.AlignValue} - * @since 2.1.5 - * @product highcharts - * @apioption yAxis.stackLabels.textAlign - */ - /** - * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) - * to render the labels. - * - * @type {boolean} - * @default false - * @since 3.0 - * @product highcharts highstock - * @apioption yAxis.stackLabels.useHTML - */ - /** - * Defines the vertical alignment of the stack total label. Can be one - * of `"top"`, `"middle"` or `"bottom"`. The default value is calculated - * at runtime and depends on orientation and whether the stack is - * positive or negative. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-top/ - * Vertically aligned top - * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-middle/ - * Vertically aligned middle - * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-bottom/ - * Vertically aligned bottom - * - * @type {Highcharts.VerticalAlignValue} - * @since 2.1.5 - * @product highcharts - * @apioption yAxis.stackLabels.verticalAlign - */ - /** - * The x position offset of the label relative to the left of the - * stacked bar. The default value is calculated at runtime and depends - * on orientation and whether the stack is positive or negative. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-x/ - * Stack total labels with x offset - * - * @type {number} - * @since 2.1.5 - * @product highcharts - * @apioption yAxis.stackLabels.x - */ - /** - * The y position offset of the label relative to the tick position - * on the axis. The default value is calculated at runtime and depends - * on orientation and whether the stack is positive or negative. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-y/ - * Stack total labels with y offset - * - * @type {number} - * @since 2.1.5 - * @product highcharts - * @apioption yAxis.stackLabels.y - */ - /** - * Whether to force the axis to start on a tick. Use this option with - * the `maxPadding` option to control the axis start. - * - * This option is always disabled, when panning type is - * either `y` or `xy`. - * - * @see [type](#chart.panning.type) - * - * @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 gantt - */ - startOnTick: true, - title: { - /** - * The pixel distance between the axis labels and the title. - * Positive values are outside the axis line, negative are inside. - * - * @sample {highcharts} highcharts/xaxis/title-margin/ - * Y axis title margin of 60 - * - * @type {number} - * @default 40 - * @apioption yAxis.title.margin - */ - /** - * 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 `undefined`. - * - * @sample {highcharts} highcharts/xaxis/title-text/ - * Custom HTML - * - * @type {string|null} - * @default {highcharts} Values - * @default {highstock} undefined - * @product highcharts highstock gantt - */ - text: 'Values' - }, - /** - * The top position of the Y axis. If it's a number, it is interpreted - * as pixel position relative to the chart. - * - * Since Highcharts 2: If it's a percentage string, it is interpreted as - * percentages of the plot height, offset from plot area top. - * - * @see [yAxis.height](#yAxis.height) - * - * @sample {highstock} stock/demo/candlestick-and-volume/ - * Percentage height panes - * - * @type {number|string} - * @product highcharts highstock - * @apioption yAxis.top - */ - /** - * 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: { - /** - * Enable or disable the initial animation when a series is - * displayed for the `stackLabels`. The animation can also be set as - * a configuration object. Please note that this option only - * applies to the initial animation. - * For other animations, see [chart.animation](#chart.animation) - * and the animation parameter under the API methods. - * The following properties are supported: - * - * - `defer`: The animation delay time in milliseconds. - * - * @sample {highcharts} highcharts/plotoptions/animation-defer/ - * Animation defer settings - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - * @since 8.2.0 - * @apioption yAxis.stackLabels.animation - */ - animation: {}, - /** - * The animation delay time in milliseconds. - * Set to `0` renders stackLabel immediately. - * As `undefined` inherits defer time from the [series.animation.defer](#plotOptions.series.animation.defer). - * - * @type {number} - * @since 8.2.0 - * @apioption yAxis.stackLabels.animation.defer - */ - /** - * Allow the stack labels to overlap. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-allowoverlap-false/ - * Default false - * - * @since 5.0.13 - * @product highcharts - */ - allowOverlap: false, - /** - * The background color or gradient for the stack label. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-box/ - * Stack labels box options - * @type {Highcharts.ColorType} - * @since 8.1.0 - * @apioption yAxis.stackLabels.backgroundColor - */ - /** - * The border color for the stack label. Defaults to `undefined`. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-box/ - * Stack labels box options - * @type {Highcharts.ColorType} - * @since 8.1.0 - * @apioption yAxis.stackLabels.borderColor - */ - /** - * The border radius in pixels for the stack label. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-box/ - * Stack labels box options - * @type {number} - * @default 0 - * @since 8.1.0 - * @apioption yAxis.stackLabels.borderRadius - */ - /** - * The border width in pixels for the stack label. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-box/ - * Stack labels box options - * @type {number} - * @default 0 - * @since 8.1.0 - * @apioption yAxis.stackLabels.borderWidth - */ - /** - * Enable or disable the stack total labels. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/ - * Enabled stack total labels - * @sample {highcharts} highcharts/yaxis/stacklabels-enabled-waterfall/ - * Enabled stack labels in waterfall chart - * - * @since 2.1.5 - * @product highcharts - */ - enabled: false, - /** - * Whether to hide stack labels that are outside the plot area. - * By default, the stack label is moved - * inside the plot area according to the - * [overflow](/highcharts/#yAxis/stackLabels/overflow) - * option. - * - * @type {boolean} - * @since 7.1.3 - */ - crop: true, - /** - * How to handle stack total labels that flow outside the plot area. - * The default is set to `"justify"`, - * which aligns them inside the plot area. - * For columns and bars, this means it will be moved inside the bar. - * To display stack labels outside the plot area, - * set `crop` to `false` and `overflow` to `"allow"`. - * - * @sample highcharts/yaxis/stacklabels-overflow/ - * Stack labels flows outside the plot area. - * - * @type {Highcharts.DataLabelsOverflowValue} - * @since 7.1.3 - */ - overflow: 'justify', - /* eslint-disable valid-jsdoc */ - /** - * Callback JavaScript function to format the label. The value is - * given by `this.total`. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/ - * Added units to stack total value - * - * @type {Highcharts.FormatterCallbackFunction<Highcharts.StackItemObject>} - * @since 2.1.5 - * @product highcharts - */ - formatter: function () { - var numberFormatter = this.axis.chart.numberFormatter; - /* eslint-enable valid-jsdoc */ - return numberFormatter(this.total, -1); - }, - /** - * CSS styles for the label. - * - * In styled mode, the styles are set in the - * `.highcharts-stack-label` class. - * - * @sample {highcharts} highcharts/yaxis/stacklabels-style/ - * Red stack total labels - * - * @type {Highcharts.CSSObject} - * @since 2.1.5 - * @product highcharts - */ - style: { - /** @internal */ - color: '#000000', - /** @internal */ - fontSize: '11px', - /** @internal */ - fontWeight: 'bold', - /** @internal */ - textOutline: '1px contrast' - } - }, - gridLineWidth: 1, - lineWidth: 0 - // tickWidth: 0 - }; - /** - * The Z axis or depth axis for 3D plots. - * - * See the [Axis class](/class-reference/Highcharts.Axis) for programmatic - * access to the axis. - * - * @sample {highcharts} highcharts/3d/scatter-zaxis-categories/ - * Z-Axis with Categories - * @sample {highcharts} highcharts/3d/scatter-zaxis-grid/ - * Z-Axis with styling - * - * @type {*|Array<*>} - * @extends xAxis - * @since 5.0.0 - * @product highcharts - * @excluding breaks, crosshair, height, left, lineColor, lineWidth, - * nameToX, showEmpty, top, width - * @apioption zAxis - * - * @private - */ - // This variable extends the defaultOptions for left axes. - Axis.defaultLeftAxisOptions = { - labels: { - x: -15 - }, - title: { - rotation: 270 - } - }; - // This variable extends the defaultOptions for right axes. - Axis.defaultRightAxisOptions = { - labels: { - x: 15 - }, - title: { - rotation: 90 - } - }; - // This variable extends the defaultOptions for bottom axes. - Axis.defaultBottomAxisOptions = { - labels: { - autoRotation: [-45], - x: 0 - // overflow: undefined, - // staggerLines: null - }, - margin: 15, - title: { - rotation: 0 - } - }; - // This variable extends the defaultOptions for top axes. - Axis.defaultTopAxisOptions = { - labels: { - autoRotation: [-45], - x: 0 - // overflow: undefined - // staggerLines: null - }, - margin: 15, - title: { - rotation: 0 - } - }; - // Properties to survive after destroy, needed for Axis.update (#4317, - // #5773, #5881). - Axis.keepProps = ['extKey', 'hcEvents', 'names', 'series', 'userMax', 'userMin']; - return Axis; - }()); - H.Axis = Axis; - - return H.Axis; - }); - _registerModule(_modules, 'Core/Axis/DateTimeAxis.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Utilities.js']], function (Axis, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var addEvent = U.addEvent, - getMagnitude = U.getMagnitude, - normalizeTickInterval = U.normalizeTickInterval, - timeUnits = U.timeUnits; - /* eslint-disable valid-jsdoc */ - var DateTimeAxisAdditions = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function DateTimeAxisAdditions(axis) { - this.axis = axis; - } - /* * - * - * Functions - * - * */ - /** - * 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. - * @private - */ - /** - * 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. - * @private - */ - DateTimeAxisAdditions.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) { - var units = unitsOption || [[ - 'millisecond', - [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' ? // #1913, #2360 - Math.max(getMagnitude(tickInterval / interval), 1) : - 1); - return { - unitRange: interval, - count: count, - unitName: unit[0] - }; - }; - return DateTimeAxisAdditions; - }()); - /** - * Date and time support for axes. - * - * @private - * @class - */ - var DateTimeAxis = /** @class */ (function () { - function DateTimeAxis() { - } - /* * - * - * Static Functions - * - * */ - /** - * Extends axis class with date and time support. - * @private - */ - DateTimeAxis.compose = function (AxisClass) { - AxisClass.keepProps.push('dateTime'); - var axisProto = AxisClass.prototype; - /** - * 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. - * - * @private - * @function Highcharts.Axis#getTimeTicks - * - * @param {Highcharts.TimeNormalizeObject} normalizedInterval - * The interval in axis values (ms) and thecount. - * - * @param {number} min - * The minimum in axis values. - * - * @param {number} max - * The maximum in axis values. - * - * @param {number} startOfWeek - * - * @return {Highcharts.AxisTickPositionsArray} - */ - axisProto.getTimeTicks = function () { - return this.chart.time.getTimeTicks.apply(this.chart.time, arguments); - }; - /* eslint-disable no-invalid-this */ - addEvent(AxisClass, 'init', function (e) { - var axis = this; - var options = e.userOptions; - if (options.type !== 'datetime') { - axis.dateTime = void 0; - return; - } - if (!axis.dateTime) { - axis.dateTime = new DateTimeAxisAdditions(axis); - } - }); - /* eslint-enable no-invalid-this */ - }; - /* * - * - * Static Properties - * - * */ - DateTimeAxis.AdditionsClass = DateTimeAxisAdditions; - return DateTimeAxis; - }()); - DateTimeAxis.compose(Axis); - - return DateTimeAxis; - }); - _registerModule(_modules, 'Core/Axis/LogarithmicAxis.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Utilities.js']], function (Axis, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var addEvent = U.addEvent, - getMagnitude = U.getMagnitude, - normalizeTickInterval = U.normalizeTickInterval, - pick = U.pick; - /* eslint-disable valid-jsdoc */ - /** - * Provides logarithmic support for axes. - * - * @private - * @class - */ - var LogarithmicAxisAdditions = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function LogarithmicAxisAdditions(axis) { - this.axis = axis; - } - /* * - * - * Functions - * - * */ - /** - * Set the tick positions of a logarithmic axis. - */ - LogarithmicAxisAdditions.prototype.getLogTickPositions = function (interval, min, max, minor) { - var log = this; - var axis = log.axis; - var axisLength = axis.len; - var options = axis.options; - // Since we use this method for both major and minor ticks, - // use a local variable and return the result - var positions = []; - // Reset - if (!minor) { - log.minorAutoInterval = void 0; - } - // 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]; - // 0.2 equals five minor ticks per 1, 10, 100 etc - } - else if (interval > 0.15) { - 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 = log.log2lin(log.lin2log(i) * intermediate[j]); - // #1670, lastPos is #3113 - if (pos > min && - (!minor || lastPos <= max) && - typeof lastPos !== 'undefined') { - 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 = log.lin2log(min), - realMax = log.lin2log(max), - tickIntervalOption = minor ? - axis.getMinorTickInterval() : - options.tickInterval, - filteredTickIntervalOption = tickIntervalOption === 'auto' ? - null : - tickIntervalOption, - tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), - totalPixelLength = minor ? - axisLength / axis.tickPositions.length : - axisLength; - interval = pick(filteredTickIntervalOption, log.minorAutoInterval, (realMax - realMin) * - tickPixelIntervalOption / (totalPixelLength || 1)); - interval = normalizeTickInterval(interval, void 0, getMagnitude(interval)); - positions = axis.getLinearTickPositions(interval, realMin, realMax).map(log.log2lin); - if (!minor) { - log.minorAutoInterval = interval / 5; - } - } - // Set the axis-level tickInterval variable - if (!minor) { - axis.tickInterval = interval; - } - return positions; - }; - LogarithmicAxisAdditions.prototype.lin2log = function (num) { - return Math.pow(10, num); - }; - LogarithmicAxisAdditions.prototype.log2lin = function (num) { - return Math.log(num) / Math.LN10; - }; - return LogarithmicAxisAdditions; - }()); - var LogarithmicAxis = /** @class */ (function () { - function LogarithmicAxis() { - } - /** - * Provides logarithmic support for axes. - * - * @private - */ - LogarithmicAxis.compose = function (AxisClass) { - AxisClass.keepProps.push('logarithmic'); - // HC <= 8 backwards compatibility, allow wrapping - // Axis.prototype.lin2log and log2lin - // @todo Remove this in next major - var axisProto = AxisClass.prototype; - var logAxisProto = LogarithmicAxisAdditions.prototype; - axisProto.log2lin = logAxisProto.log2lin; - axisProto.lin2log = logAxisProto.lin2log; - /* eslint-disable no-invalid-this */ - addEvent(AxisClass, 'init', function (e) { - var axis = this; - var options = e.userOptions; - var logarithmic = axis.logarithmic; - if (options.type !== 'logarithmic') { - axis.logarithmic = void 0; - } - else { - if (!logarithmic) { - logarithmic = axis.logarithmic = new LogarithmicAxisAdditions(axis); - } - // HC <= 8 backwards compatibility, allow wrapping - // Axis.prototype.lin2log and log2lin - // @todo Remove this in next major - if (axis.log2lin !== logarithmic.log2lin) { - logarithmic.log2lin = axis.log2lin.bind(axis); - } - if (axis.lin2log !== logarithmic.lin2log) { - logarithmic.lin2log = axis.lin2log.bind(axis); - } - } - }); - addEvent(AxisClass, 'afterInit', function () { - var axis = this; - var log = axis.logarithmic; - // extend logarithmic axis - if (log) { - axis.lin2val = function (num) { - return log.lin2log(num); - }; - axis.val2lin = function (num) { - return log.log2lin(num); - }; - } - }); - }; - return LogarithmicAxis; - }()); - LogarithmicAxis.compose(Axis); // @todo move to factory functions - - return LogarithmicAxis; - }); - _registerModule(_modules, 'Core/Axis/PlotLineOrBand.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (Axis, H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - /** - * Options for plot bands on axes. - * - * @typedef {Highcharts.XAxisPlotBandsOptions|Highcharts.YAxisPlotBandsOptions|Highcharts.ZAxisPlotBandsOptions} Highcharts.AxisPlotBandsOptions - */ - /** - * Options for plot band labels on axes. - * - * @typedef {Highcharts.XAxisPlotBandsLabelOptions|Highcharts.YAxisPlotBandsLabelOptions|Highcharts.ZAxisPlotBandsLabelOptions} Highcharts.AxisPlotBandsLabelOptions - */ - /** - * Options for plot lines on axes. - * - * @typedef {Highcharts.XAxisPlotLinesOptions|Highcharts.YAxisPlotLinesOptions|Highcharts.ZAxisPlotLinesOptions} Highcharts.AxisPlotLinesOptions - */ - /** - * Options for plot line labels on axes. - * - * @typedef {Highcharts.XAxisPlotLinesLabelOptions|Highcharts.YAxisPlotLinesLabelOptions|Highcharts.ZAxisPlotLinesLabelOptions} Highcharts.AxisPlotLinesLabelOptions - */ - var arrayMax = U.arrayMax, - arrayMin = U.arrayMin, - defined = U.defined, - destroyObjectProperties = U.destroyObjectProperties, - erase = U.erase, - extend = U.extend, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick; - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * The object wrapper for plot lines and plot bands - * - * @class - * @name Highcharts.PlotLineOrBand - * - * @param {Highcharts.Axis} axis - * - * @param {Highcharts.AxisPlotLinesOptions|Highcharts.AxisPlotBandsOptions} [options] - */ - var PlotLineOrBand = /** @class */ (function () { - function PlotLineOrBand(axis, options) { - this.axis = axis; - if (options) { - this.options = options; - this.id = options.id; - } - } - /** - * Render the plot line or plot band. If it is already existing, - * move it. - * - * @private - * @function Highcharts.PlotLineOrBand#render - * @return {Highcharts.PlotLineOrBand|undefined} - */ - PlotLineOrBand.prototype.render = function () { - H.fireEvent(this, 'render'); - var plotLine = this, - axis = plotLine.axis, - horiz = axis.horiz, - log = axis.logarithmic, - 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; - // logarithmic conversion - if (log) { - from = log.log2lin(from); - to = log.log2lin(to); - value = log.log2lin(value); - } - // Set the presentational attributes - if (!axis.chart.styledMode) { - if (isLine) { - attribs.stroke = color || '#999999'; - attribs['stroke-width'] = pick(options.width, 1); - if (options.dashStyle) { - attribs.dashstyle = - options.dashStyle; - } - } - else if (isBand) { // plot band - attribs.fill = color || '#e6ebf5'; - 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) { - /** - * SVG element of the plot line or band. - * - * @name Highcharts.PlotLineOrBand#svgElement - * @type {Highcharts.SVGElement} - */ - plotLine.svgElem = svgElem = renderer - .path() - .attr(attribs) - .add(group); - } - // Set the path or return - if (isLine) { - path = axis.getPlotLinePath({ - value: value, - lineWidth: svgElem.strokeWidth(), - acrossPanes: options.acrossPanes - }); - } - else if (isBand) { // plot band - path = axis.getPlotBandPath(from, to, options); - } - else { - return; - } - // common for lines and bands - // Add events only if they were not added before. - if (!plotLine.eventsAdded && events) { - objectEach(events, function (event, eventType) { - svgElem.on(eventType, function (e) { - events[eventType].apply(plotLine, [e]); - }); - }); - plotLine.eventsAdded = true; - } - if ((isNew || !svgElem.d) && path && path.length) { - svgElem.attr({ d: path }); - } - else if (svgElem) { - if (path) { - svgElem.show(true); - svgElem.animate({ d: path }); - } - else if (svgElem.d) { - svgElem.hide(); - if (label) { - plotLine.label = label = label.destroy(); - } - } - } - // the plot band/line label - if (optionsLabel && - (defined(optionsLabel.text) || defined(optionsLabel.formatter)) && - path && - path.length && - axis.width > 0 && - axis.height > 0 && - !path.isFlat) { - // 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. - * - * @private - * @function Highcharts.PlotLineOrBand#renderLabel - * @param {Highcharts.AxisPlotLinesLabelOptions|Highcharts.AxisPlotBandsLabelOptions} optionsLabel - * @param {Highcharts.SVGPathArray} path - * @param {boolean} [isBand] - * @param {number} [zIndex] - * @return {void} - */ - PlotLineOrBand.prototype.renderLabel = function (optionsLabel, path, isBand, zIndex) { - var plotLine = this, - label = plotLine.label, - renderer = plotLine.axis.chart.renderer, - attribs, - xBounds, - yBounds, - x, - y, - labelText; - // 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; - labelText = this.getLabelText(optionsLabel); - /** - * SVG element of the label. - * - * @name Highcharts.PlotLineOrBand#label - * @type {Highcharts.SVGElement} - */ - plotLine.label = label = renderer - .text(labelText, 0, 0, optionsLabel.useHTML) - .attr(attribs) - .add(); - if (!this.axis.chart.styledMode) { - 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[0][1], path[1][1], (isBand ? path[2][1] : path[0][1])]; - yBounds = path.yBounds || - [path[0][2], path[1][2], (isBand ? path[2][2] : path[0][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(true); - }; - /** - * Get label's text content. - * - * @private - * @function Highcharts.PlotLineOrBand#getLabelText - * @param {Highcharts.AxisPlotLinesLabelOptions|Highcharts.AxisPlotBandsLabelOptions} optionsLabel - * @return {string} - */ - PlotLineOrBand.prototype.getLabelText = function (optionsLabel) { - return defined(optionsLabel.formatter) ? - optionsLabel.formatter - .call(this) : - optionsLabel.text; - }; - /** - * Remove the plot line or band. - * - * @function Highcharts.PlotLineOrBand#destroy - * @return {void} - */ - PlotLineOrBand.prototype.destroy = function () { - // remove it from the lookup - erase(this.axis.plotLinesAndBands, this); - delete this.axis; - destroyObjectProperties(this); - }; - return PlotLineOrBand; - }()); - /* eslint-enable no-invalid-this, valid-jsdoc */ - // Object with members for extending the Axis prototype - extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ { - /** - * An array of colored bands stretching across the plot area marking an - * interval on the axis. - * - * In styled mode, the plot bands are styled by the `.highcharts-plot-band` - * class in addition to the `className` option. - * - * @productdesc {highcharts} - * In a gauge, a plot band on the Y axis (value axis) will stretch along the - * perimeter of the gauge. - * - * @type {Array<*>} - * @product highcharts highstock gantt - * @apioption xAxis.plotBands - */ - /** - * Flag to decide if plotBand should be rendered across all panes. - * - * @since 7.1.2 - * @product highstock - * @type {boolean} - * @default true - * @apioption xAxis.plotBands.acrossPanes - */ - /** - * Border color for the plot band. Also requires `borderWidth` to be set. - * - * @type {Highcharts.ColorString} - * @apioption xAxis.plotBands.borderColor - */ - /** - * Border width for the plot band. Also requires `borderColor` to be set. - * - * @type {number} - * @default 0 - * @apioption xAxis.plotBands.borderWidth - */ - /** - * A custom class name, in addition to the default `highcharts-plot-band`, - * to apply to each individual band. - * - * @type {string} - * @since 5.0.0 - * @apioption xAxis.plotBands.className - */ - /** - * The color of the plot band. - * - * @sample {highcharts} highcharts/xaxis/plotbands-color/ - * Color band - * @sample {highstock} stock/xaxis/plotbands/ - * Plot band on Y axis - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @default #e6ebf5 - * @apioption xAxis.plotBands.color - */ - /** - * An object defining mouse events for the plot band. Supported properties - * are `click`, `mouseover`, `mouseout`, `mousemove`. - * - * @sample {highcharts} highcharts/xaxis/plotbands-events/ - * Mouse events demonstrated - * - * @since 1.2 - * @apioption xAxis.plotBands.events - */ - /** - * Click event on a plot band. - * - * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotBands.events.click - */ - /** - * Mouse move event on a plot band. - * - * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotBands.events.mousemove - */ - /** - * Mouse out event on the corner of a plot band. - * - * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotBands.events.mouseout - */ - /** - * Mouse over event on a plot band. - * - * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotBands.events.mouseover - */ - /** - * The start position of the plot band in axis units. - * - * @sample {highcharts} highcharts/xaxis/plotbands-color/ - * Datetime axis - * @sample {highcharts} highcharts/xaxis/plotbands-from/ - * Categorized axis - * @sample {highstock} stock/xaxis/plotbands/ - * Plot band on Y axis - * - * @type {number} - * @apioption xAxis.plotBands.from - */ - /** - * An id used for identifying the plot band in Axis.removePlotBand. - * - * @sample {highcharts} highcharts/xaxis/plotbands-id/ - * Remove plot band by id - * @sample {highstock} highcharts/xaxis/plotbands-id/ - * Remove plot band by id - * - * @type {string} - * @apioption xAxis.plotBands.id - */ - /** - * The end position of the plot band in axis units. - * - * @sample {highcharts} highcharts/xaxis/plotbands-color/ - * Datetime axis - * @sample {highcharts} highcharts/xaxis/plotbands-from/ - * Categorized axis - * @sample {highstock} stock/xaxis/plotbands/ - * Plot band on Y axis - * - * @type {number} - * @apioption xAxis.plotBands.to - */ - /** - * The z index of the plot band within the chart, relative to other - * elements. Using the same z index as another element may give - * unpredictable results, as the last rendered element will be on top. - * Values from 0 to 20 make sense. - * - * @sample {highcharts} highcharts/xaxis/plotbands-color/ - * Behind plot lines by default - * @sample {highcharts} highcharts/xaxis/plotbands-zindex/ - * Above plot lines - * @sample {highcharts} highcharts/xaxis/plotbands-zindex-above-series/ - * Above plot lines and series - * - * @type {number} - * @since 1.2 - * @apioption xAxis.plotBands.zIndex - */ - /** - * Text labels for the plot bands - * - * @product highcharts highstock gantt - * @apioption xAxis.plotBands.label - */ - /** - * Horizontal alignment of the label. Can be one of "left", "center" or - * "right". - * - * @sample {highcharts} highcharts/xaxis/plotbands-label-align/ - * Aligned to the right - * @sample {highstock} stock/xaxis/plotbands-label/ - * Plot band with labels - * - * @type {Highcharts.AlignValue} - * @default center - * @since 2.1 - * @apioption xAxis.plotBands.label.align - */ - /** - * Rotation of the text label in degrees . - * - * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/ - * Vertical text - * - * @type {number} - * @default 0 - * @since 2.1 - * @apioption xAxis.plotBands.label.rotation - */ - /** - * CSS styles for the text label. - * - * In styled mode, the labels are styled by the - * `.highcharts-plot-band-label` class. - * - * @sample {highcharts} highcharts/xaxis/plotbands-label-style/ - * Blue and bold label - * - * @type {Highcharts.CSSObject} - * @since 2.1 - * @apioption xAxis.plotBands.label.style - */ - /** - * The string text itself. A subset of HTML is supported. - * - * @type {string} - * @since 2.1 - * @apioption xAxis.plotBands.label.text - */ - /** - * The text alignment for the label. While `align` determines where the - * texts anchor point is placed within the plot band, `textAlign` determines - * how the text is aligned against its anchor point. Possible values are - * "left", "center" and "right". Defaults to the same as the `align` option. - * - * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/ - * Vertical text in center position but text-aligned left - * - * @type {Highcharts.AlignValue} - * @since 2.1 - * @apioption xAxis.plotBands.label.textAlign - */ - /** - * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) - * to render the labels. - * - * @type {boolean} - * @default false - * @since 3.0.3 - * @apioption xAxis.plotBands.label.useHTML - */ - /** - * Vertical alignment of the label relative to the plot band. Can be one of - * "top", "middle" or "bottom". - * - * @sample {highcharts} highcharts/xaxis/plotbands-label-verticalalign/ - * Vertically centered label - * @sample {highstock} stock/xaxis/plotbands-label/ - * Plot band with labels - * - * @type {Highcharts.VerticalAlignValue} - * @default top - * @since 2.1 - * @apioption xAxis.plotBands.label.verticalAlign - */ - /** - * Horizontal position relative the alignment. Default varies by - * orientation. - * - * @sample {highcharts} highcharts/xaxis/plotbands-label-align/ - * Aligned 10px from the right edge - * @sample {highstock} stock/xaxis/plotbands-label/ - * Plot band with labels - * - * @type {number} - * @since 2.1 - * @apioption xAxis.plotBands.label.x - */ - /** - * Vertical position of the text baseline relative to the alignment. Default - * varies by orientation. - * - * @sample {highcharts} highcharts/xaxis/plotbands-label-y/ - * Label on x axis - * @sample {highstock} stock/xaxis/plotbands-label/ - * Plot band with labels - * - * @type {number} - * @since 2.1 - * @apioption xAxis.plotBands.label.y - */ - /** - * An array of lines stretching across the plot area, marking a specific - * value on one of the axes. - * - * In styled mode, the plot lines are styled by the - * `.highcharts-plot-line` class in addition to the `className` option. - * - * @type {Array<*>} - * @product highcharts highstock gantt - * @sample {highcharts} highcharts/xaxis/plotlines-color/ - * Basic plot line - * @sample {highcharts} highcharts/series-solidgauge/labels-auto-aligned/ - * Solid gauge plot line - * @apioption xAxis.plotLines - */ - /** - * Flag to decide if plotLine should be rendered across all panes. - * - * @sample {highstock} stock/xaxis/plotlines-acrosspanes/ - * Plot lines on different panes - * - * @since 7.1.2 - * @product highstock - * @type {boolean} - * @default true - * @apioption xAxis.plotLines.acrossPanes - */ - /** - * A custom class name, in addition to the default `highcharts-plot-line`, - * to apply to each individual line. - * - * @type {string} - * @since 5.0.0 - * @apioption xAxis.plotLines.className - */ - /** - * The color of the line. - * - * @sample {highcharts} highcharts/xaxis/plotlines-color/ - * A red line from X axis - * @sample {highstock} stock/xaxis/plotlines/ - * Plot line on Y axis - * - * @type {Highcharts.ColorString} - * @default #999999 - * @apioption xAxis.plotLines.color - */ - /** - * The dashing or dot style for the plot line. For possible values see - * [this overview](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). - * - * @sample {highcharts} highcharts/xaxis/plotlines-dashstyle/ - * Dash and dot pattern - * @sample {highstock} stock/xaxis/plotlines/ - * Plot line on Y axis - * - * @type {Highcharts.DashStyleValue} - * @default Solid - * @since 1.2 - * @apioption xAxis.plotLines.dashStyle - */ - /** - * An object defining mouse events for the plot line. Supported - * properties are `click`, `mouseover`, `mouseout`, `mousemove`. - * - * @sample {highcharts} highcharts/xaxis/plotlines-events/ - * Mouse events demonstrated - * - * @since 1.2 - * @apioption xAxis.plotLines.events - */ - /** - * Click event on a plot band. - * - * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotLines.events.click - */ - /** - * Mouse move event on a plot band. - * - * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotLines.events.mousemove - */ - /** - * Mouse out event on the corner of a plot band. - * - * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotLines.events.mouseout - */ - /** - * Mouse over event on a plot band. - * - * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotLines.events.mouseover - */ - /** - * An id used for identifying the plot line in Axis.removePlotLine. - * - * @sample {highcharts} highcharts/xaxis/plotlines-id/ - * Remove plot line by id - * - * @type {string} - * @apioption xAxis.plotLines.id - */ - /** - * The position of the line in axis units. - * - * @sample {highcharts} highcharts/xaxis/plotlines-color/ - * Between two categories on X axis - * @sample {highstock} stock/xaxis/plotlines/ - * Plot line on Y axis - * - * @type {number} - * @apioption xAxis.plotLines.value - */ - /** - * The width or thickness of the plot line. - * - * @sample {highcharts} highcharts/xaxis/plotlines-color/ - * 2px wide line from X axis - * @sample {highstock} stock/xaxis/plotlines/ - * Plot line on Y axis - * - * @type {number} - * @default 2 - * @apioption xAxis.plotLines.width - */ - /** - * The z index of the plot line within the chart. - * - * @sample {highcharts} highcharts/xaxis/plotlines-zindex-behind/ - * Behind plot lines by default - * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above/ - * Above plot lines - * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above-all/ - * Above plot lines and series - * - * @type {number} - * @since 1.2 - * @apioption xAxis.plotLines.zIndex - */ - /** - * Text labels for the plot bands - * - * @apioption xAxis.plotLines.label - */ - /** - * Horizontal alignment of the label. Can be one of "left", "center" or - * "right". - * - * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/ - * Aligned to the right - * @sample {highstock} stock/xaxis/plotlines/ - * Plot line on Y axis - * - * @type {Highcharts.AlignValue} - * @default left - * @since 2.1 - * @apioption xAxis.plotLines.label.align - */ - /** - * Callback JavaScript function to format the label. Useful properties like - * the value of plot line or the range of plot band (`from` & `to` - * properties) can be found in `this.options` object. - * - * @sample {highcharts} highcharts/xaxis/plotlines-plotbands-label-formatter - * Label formatters for plot line and plot band. - * @type {Highcharts.FormatterCallbackFunction<Highcharts.PlotLineOrBand>} - * @apioption xAxis.plotLines.label.formatter - */ - /** - * Rotation of the text label in degrees. Defaults to 0 for horizontal plot - * lines and 90 for vertical lines. - * - * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/ - * Slanted text - * - * @type {number} - * @since 2.1 - * @apioption xAxis.plotLines.label.rotation - */ - /** - * CSS styles for the text label. - * - * In styled mode, the labels are styled by the - * `.highcharts-plot-line-label` class. - * - * @sample {highcharts} highcharts/xaxis/plotlines-label-style/ - * Blue and bold label - * - * @type {Highcharts.CSSObject} - * @since 2.1 - * @apioption xAxis.plotLines.label.style - */ - /** - * The text itself. A subset of HTML is supported. - * - * @type {string} - * @since 2.1 - * @apioption xAxis.plotLines.label.text - */ - /** - * The text alignment for the label. While `align` determines where the - * texts anchor point is placed within the plot band, `textAlign` determines - * how the text is aligned against its anchor point. Possible values are - * "left", "center" and "right". Defaults to the same as the `align` option. - * - * @sample {highcharts} highcharts/xaxis/plotlines-label-textalign/ - * Text label in bottom position - * - * @type {Highcharts.AlignValue} - * @since 2.1 - * @apioption xAxis.plotLines.label.textAlign - */ - /** - * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) - * to render the labels. - * - * @type {boolean} - * @default false - * @since 3.0.3 - * @apioption xAxis.plotLines.label.useHTML - */ - /** - * Vertical alignment of the label relative to the plot line. Can be - * one of "top", "middle" or "bottom". - * - * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/ - * Vertically centered label - * - * @type {Highcharts.VerticalAlignValue} - * @default {highcharts} top - * @default {highstock} top - * @since 2.1 - * @apioption xAxis.plotLines.label.verticalAlign - */ - /** - * Horizontal position relative the alignment. Default varies by - * orientation. - * - * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/ - * Aligned 10px from the right edge - * @sample {highstock} stock/xaxis/plotlines/ - * Plot line on Y axis - * - * @type {number} - * @since 2.1 - * @apioption xAxis.plotLines.label.x - */ - /** - * Vertical position of the text baseline relative to the alignment. Default - * varies by orientation. - * - * @sample {highcharts} highcharts/xaxis/plotlines-label-y/ - * Label below the plot line - * @sample {highstock} stock/xaxis/plotlines/ - * Plot line on Y axis - * - * @type {number} - * @since 2.1 - * @apioption xAxis.plotLines.label.y - */ - /** - * - * @type {Array<*>} - * @extends xAxis.plotBands - * @apioption yAxis.plotBands - */ - /** - * In a gauge chart, this option determines the inner radius of the - * plot band that stretches along the perimeter. It can be given as - * a percentage string, like `"100%"`, or as a pixel number, like `100`. - * By default, the inner radius is controlled by the [thickness]( - * #yAxis.plotBands.thickness) option. - * - * @sample {highcharts} highcharts/xaxis/plotbands-gauge - * Gauge plot band - * - * @type {number|string} - * @since 2.3 - * @product highcharts - * @apioption yAxis.plotBands.innerRadius - */ - /** - * In a gauge chart, this option determines the outer radius of the - * plot band that stretches along the perimeter. It can be given as - * a percentage string, like `"100%"`, or as a pixel number, like `100`. - * - * @sample {highcharts} highcharts/xaxis/plotbands-gauge - * Gauge plot band - * - * @type {number|string} - * @default 100% - * @since 2.3 - * @product highcharts - * @apioption yAxis.plotBands.outerRadius - */ - /** - * In a gauge chart, this option sets the width of the plot band - * stretching along the perimeter. It can be given as a percentage - * string, like `"10%"`, or as a pixel number, like `10`. The default - * value 10 is the same as the default [tickLength](#yAxis.tickLength), - * thus making the plot band act as a background for the tick markers. - * - * @sample {highcharts} highcharts/xaxis/plotbands-gauge - * Gauge plot band - * - * @type {number|string} - * @default 10 - * @since 2.3 - * @product highcharts - * @apioption yAxis.plotBands.thickness - */ - /** - * @type {Array<*>} - * @extends xAxis.plotLines - * @apioption yAxis.plotLines - */ - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * Internal function to create the SVG path definition for a plot band. - * - * @function Highcharts.Axis#getPlotBandPath - * - * @param {number} from - * The axis value to start from. - * - * @param {number} to - * The axis value to end on. - * - * @param {Highcharts.AxisPlotBandsOptions|Highcharts.AxisPlotLinesOptions} options - * The plotBand or plotLine configuration object. - * - * @return {Highcharts.SVGPathArray} - * The SVG path definition in array form. - */ - getPlotBandPath: function (from, to, options) { - if (options === void 0) { options = this.options; } - var toPath = this.getPlotLinePath({ - value: to, - force: true, - acrossPanes: options.acrossPanes - }), - path = this.getPlotLinePath({ - value: from, - force: true, - acrossPanes: options.acrossPanes - }), - result = [], - i, - // #4964 check if chart is inverted or plotband is on yAxis - horiz = this.horiz, - plus = 1, - isFlat, - 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) { - isFlat = path.toString() === toPath.toString(); - plus = 0; - } - // Go over each subpath - for panes in Highstock - for (i = 0; i < path.length; i += 2) { - var pathStart = path[i], - pathEnd = path[i + 1], - toPathStart = toPath[i], - toPathEnd = toPath[i + 1]; - // Type checking all affected path segments. Consider something - // smarter. - if ((pathStart[0] === 'M' || pathStart[0] === 'L') && - (pathEnd[0] === 'M' || pathEnd[0] === 'L') && - (toPathStart[0] === 'M' || toPathStart[0] === 'L') && - (toPathEnd[0] === 'M' || toPathEnd[0] === 'L')) { - // Add 1 pixel when coordinates are the same - if (horiz && toPathStart[1] === pathStart[1]) { - toPathStart[1] += plus; - toPathEnd[1] += plus; - } - else if (!horiz && toPathStart[2] === pathStart[2]) { - toPathStart[2] += plus; - toPathEnd[2] += plus; - } - result.push(['M', pathStart[1], pathStart[2]], ['L', pathEnd[1], pathEnd[2]], ['L', toPathEnd[1], toPathEnd[2]], ['L', toPathStart[1], toPathStart[2]], ['Z']); - } - result.isFlat = isFlat; - } - } - else { // outside the axis area - path = null; - } - return result; - }, - /** - * Add a plot band after render time. - * - * @sample highcharts/members/axis-addplotband/ - * Toggle the plot band from a button - * - * @function Highcharts.Axis#addPlotBand - * - * @param {Highcharts.AxisPlotBandsOptions} options - * A configuration object for the plot band, as defined in - * [xAxis.plotBands](https://api.highcharts.com/highcharts/xAxis.plotBands). - * - * @return {Highcharts.PlotLineOrBand|undefined} - * The added plot band. - */ - addPlotBand: function (options) { - return this.addPlotBandOrLine(options, 'plotBands'); - }, - /** - * Add a plot line after render time. - * - * @sample highcharts/members/axis-addplotline/ - * Toggle the plot line from a button - * - * @function Highcharts.Axis#addPlotLine - * - * @param {Highcharts.AxisPlotLinesOptions} options - * A configuration object for the plot line, as defined in - * [xAxis.plotLines](https://api.highcharts.com/highcharts/xAxis.plotLines). - * - * @return {Highcharts.PlotLineOrBand|undefined} - * The added plot line. - */ - 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 - * @function Highcharts.Axis#addPlotBandOrLine - * - * @param {Highcharts.AxisPlotBandsOptions|Highcharts.AxisPlotLinesOptions} options - * The plotBand or plotLine configuration object. - * - * @param {"plotBands"|"plotLines"} [coll] - * - * @return {Highcharts.PlotLineOrBand|undefined} - */ - addPlotBandOrLine: function (options, coll) { - var obj = new H.PlotLineOrBand(this, - options), - userOptions = this.userOptions; - if (this.visible) { - obj = obj.render(); - } - if (obj) { // #2189 - // Add it to the user options for exporting and Axis.update - if (coll) { - // Workaround Microsoft/TypeScript issue #32693 - var updatedOptions = (userOptions[coll] || []); - updatedOptions.push(options); - userOptions[coll] = updatedOptions; - } - this.plotLinesAndBands.push(obj); - this._addedPlotLB = true; - } - return obj; - }, - /** - * Remove a plot band or plot line from the chart by id. Called internally - * from `removePlotBand` and `removePlotLine`. - * - * @private - * @function Highcharts.Axis#removePlotBandOrLine - * @param {string} id - * @return {void} - */ - 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(); - } - } - ([ - options.plotLines || [], - userOptions.plotLines || [], - options.plotBands || [], - userOptions.plotBands || [] - ]).forEach(function (arr) { - i = arr.length; - while (i--) { - if ((arr[i] || {}).id === id) { - erase(arr, arr[i]); - } - } - }); - }, - /** - * Remove a plot band by its id. - * - * @sample highcharts/members/axis-removeplotband/ - * Remove plot band by id - * @sample highcharts/members/axis-addplotband/ - * Toggle the plot band from a button - * - * @function Highcharts.Axis#removePlotBand - * - * @param {string} id - * The plot band's `id` as given in the original configuration - * object or in the `addPlotBand` option. - * - * @return {void} - */ - removePlotBand: function (id) { - this.removePlotBandOrLine(id); - }, - /** - * Remove a plot line by its id. - * - * @sample highcharts/xaxis/plotlines-id/ - * Remove plot line by id - * @sample highcharts/members/axis-addplotline/ - * Toggle the plot line from a button - * - * @function Highcharts.Axis#removePlotLine - * - * @param {string} id - * The plot line's `id` as given in the original configuration - * object or in the `addPlotLine` option. - */ - removePlotLine: function (id) { - this.removePlotBandOrLine(id); - } - }); - H.PlotLineOrBand = PlotLineOrBand; - - return H.PlotLineOrBand; - }); - _registerModule(_modules, 'Core/Tooltip.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var doc = H.doc; - var clamp = U.clamp, - css = U.css, - defined = U.defined, - discardElement = U.discardElement, - extend = U.extend, - fireEvent = U.fireEvent, - format = U.format, - isNumber = U.isNumber, - isString = U.isString, - merge = U.merge, - pick = U.pick, - splat = U.splat, - syncTimeout = U.syncTimeout, - timeUnits = U.timeUnits; - /** - * Callback function to format the text of the tooltip from scratch. - * - * In case of single or shared tooltips, a string should be be returned. In case - * of splitted tooltips, it should return an array where the first item is the - * header, and subsequent items are mapped to the points. Return `false` to - * disable tooltip for a specific point on series. - * - * @callback Highcharts.TooltipFormatterCallbackFunction - * - * @param {Highcharts.TooltipFormatterContextObject} this - * Context to format - * - * @param {Highcharts.Tooltip} tooltip - * The tooltip instance - * - * @return {false|string|Array<(string|null|undefined)>|null|undefined} - * Formatted text or false - */ - /** - * @interface Highcharts.TooltipFormatterContextObject - */ /** - * @name Highcharts.TooltipFormatterContextObject#color - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ /** - * @name Highcharts.TooltipFormatterContextObject#colorIndex - * @type {number|undefined} - */ /** - * @name Highcharts.TooltipFormatterContextObject#key - * @type {number} - */ /** - * @name Highcharts.TooltipFormatterContextObject#percentage - * @type {number|undefined} - */ /** - * @name Highcharts.TooltipFormatterContextObject#point - * @type {Highcharts.Point} - */ /** - * @name Highcharts.TooltipFormatterContextObject#points - * @type {Array<Highcharts.TooltipFormatterContextObject>|undefined} - */ /** - * @name Highcharts.TooltipFormatterContextObject#series - * @type {Highcharts.Series} - */ /** - * @name Highcharts.TooltipFormatterContextObject#total - * @type {number|undefined} - */ /** - * @name Highcharts.TooltipFormatterContextObject#x - * @type {number} - */ /** - * @name Highcharts.TooltipFormatterContextObject#y - * @type {number} - */ - /** - * A callback function to place the tooltip in a specific position. - * - * @callback Highcharts.TooltipPositionerCallbackFunction - * - * @param {Highcharts.Tooltip} this - * Tooltip context of the callback. - * - * @param {number} labelWidth - * Width of the tooltip. - * - * @param {number} labelHeight - * Height of the tooltip. - * - * @param {Highcharts.TooltipPositionerPointObject} point - * Point information for positioning a tooltip. - * - * @return {Highcharts.PositionObject} - * New position for the tooltip. - */ - /** - * Point information for positioning a tooltip. - * - * @interface Highcharts.TooltipPositionerPointObject - * @extends Highcharts.Point - */ /** - * If `tooltip.split` option is enabled and positioner is called for each of the - * boxes separately, this property indicates the call on the xAxis header, which - * is not a point itself. - * @name Highcharts.TooltipPositionerPointObject#isHeader - * @type {boolean} - */ /** - * The reference point relative to the plot area. Add chart.plotLeft to get the - * full coordinates. - * @name Highcharts.TooltipPositionerPointObject#plotX - * @type {number} - */ /** - * The reference point relative to the plot area. Add chart.plotTop to get the - * full coordinates. - * @name Highcharts.TooltipPositionerPointObject#plotY - * @type {number} - */ - /** - * @typedef {"callout"|"circle"|"square"} Highcharts.TooltipShapeValue - */ - ''; // separates doclets above from variables below - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * Tooltip of a chart. - * - * @class - * @name Highcharts.Tooltip - * - * @param {Highcharts.Chart} chart - * The chart instance. - * - * @param {Highcharts.TooltipOptions} options - * Tooltip options. - */ - var Tooltip = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function Tooltip(chart, options) { - this.container = void 0; - this.crosshairs = []; - this.distance = 0; - this.isHidden = true; - this.isSticky = false; - this.now = {}; - this.options = {}; - this.outside = false; - this.chart = chart; - this.init(chart, options); - } - /* * - * - * Functions - * - * */ - /** - * In styled mode, apply the default filter for the tooltip drop-shadow. It - * needs to have an id specific to the chart, otherwise there will be issues - * when one tooltip adopts the filter of a different chart, specifically one - * where the container is hidden. - * - * @private - * @function Highcharts.Tooltip#applyFilter - */ - Tooltip.prototype.applyFilter = function () { - var chart = this.chart; - chart.renderer.definition({ - tagName: 'filter', - id: 'drop-shadow-' + chart.index, - opacity: 0.5, - children: [{ - tagName: 'feGaussianBlur', - 'in': 'SourceAlpha', - stdDeviation: 1 - }, { - tagName: 'feOffset', - dx: 1, - dy: 1 - }, { - tagName: 'feComponentTransfer', - children: [{ - tagName: 'feFuncA', - type: 'linear', - slope: 0.3 - }] - }, { - tagName: 'feMerge', - children: [{ - tagName: 'feMergeNode' - }, { - tagName: 'feMergeNode', - 'in': 'SourceGraphic' - }] - }] - }); - chart.renderer.definition({ - tagName: 'style', - textContent: '.highcharts-tooltip-' + chart.index + '{' + - 'filter:url(#drop-shadow-' + chart.index + ')' + - '}' - }); - }; - /** - * 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. - * - * @private - * @function Highcharts.Tooltip#bodyFormatter - * @param {Array<(Highcharts.Point|Highcharts.Series)>} items - * @return {Array<string>} - */ - Tooltip.prototype.bodyFormatter = function (items) { - return items.map(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'] || ''); - }); - }; - /** - * Destroy the single tooltips in a split tooltip. - * If the tooltip is active then it is not destroyed, unless forced to. - * - * @private - * @function Highcharts.Tooltip#cleanSplit - * - * @param {boolean} [force] - * Force destroy all tooltips. - */ - Tooltip.prototype.cleanSplit = function (force) { - this.chart.series.forEach(function (series) { - var tt = series && series.tt; - if (tt) { - if (!tt.isActive || force) { - series.tt = tt.destroy(); - } - else { - tt.isActive = false; - } - } - }); - }; - /** - * 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. - * - * @function Highcharts.Tooltip#defaultFormatter - * - * @param {Highcharts.Tooltip} tooltip - * - * @return {Array<string>} - */ - Tooltip.prototype.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; - }; - /** - * Removes and destroys the tooltip and its elements. - * - * @function Highcharts.Tooltip#destroy - */ - Tooltip.prototype.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(); - } - if (this.renderer) { - this.renderer = this.renderer.destroy(); - discardElement(this.container); - } - U.clearTimeout(this.hideTimer); - U.clearTimeout(this.tooltipTimeout); - }; - /** - * Extendable method to get the anchor position of the tooltip - * from a point or set of points - * - * @private - * @function Highcharts.Tooltip#getAnchor - * - * @param {Highcharts.Point|Array<Highcharts.Point>} points - * - * @param {Highcharts.PointerEventObject} [mouseEvent] - * - * @return {Array<number>} - */ - Tooltip.prototype.getAnchor = function (points, mouseEvent) { - var ret, - chart = this.chart, - pointer = chart.pointer, - inverted = chart.inverted, - plotTop = chart.plotTop, - plotLeft = chart.plotLeft, - plotX = 0, - plotY = 0, - yAxis, - xAxis; - points = splat(points); - // When tooltip follows mouse, relate the position to the mouse - if (this.followPointer && mouseEvent) { - if (typeof mouseEvent.chartX === 'undefined') { - mouseEvent = pointer.normalize(mouseEvent); - } - ret = [ - mouseEvent.chartX - plotLeft, - mouseEvent.chartY - plotTop - ]; - // Some series types use a specificly calculated tooltip position for - // each point - } - else if (points[0].tooltipPos) { - ret = points[0].tooltipPos; - // When shared, use the average position - } - else { - points.forEach(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 ret.map(Math.round); - }; - /** - * Get the optimal date format for a point, based on a range. - * - * @private - * @function Highcharts.Tooltip#getDateFormat - * - * @param {number} range - * The time range - * - * @param {number} 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 {Highcharts.Dictionary<string>} dateTimeLabelFormats - * A map of time units to formats. - * - * @return {string} - * The optimal date format for a point. - */ - Tooltip.prototype.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) { // eslint-disable-line guard-for-in - // 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 = time.resolveDTLFormat(dateTimeLabelFormats[n]).main; - } - return format; - }; - /** - * Creates the Tooltip label element if it does not exist, then returns it. - * - * @function Highcharts.Tooltip#getLabel - * @return {Highcharts.SVGElement} - */ - Tooltip.prototype.getLabel = function () { - var _a, - _b; - var tooltip = this, - renderer = this.chart.renderer, - styledMode = this.chart.styledMode, - options = this.options, - className = ('tooltip' + (defined(options.className) ? - ' ' + options.className : - '')), - pointerEvents = (((_a = options.style) === null || _a === void 0 ? void 0 : _a.pointerEvents) || - (!this.followPointer && options.stickOnContact ? 'auto' : 'none')), - container, - set, - onMouseEnter = function () { - tooltip.inContact = true; - }, onMouseLeave = function () { - var series = tooltip.chart.hoverSeries; - tooltip.inContact = false; - if (series && - series.onMouseOut) { - series.onMouseOut(); - } - }; - if (!this.label) { - if (this.outside) { - /** - * Reference to the tooltip's container, when - * [Highcharts.Tooltip#outside] is set to true, otherwise - * it's undefined. - * - * @name Highcharts.Tooltip#container - * @type {Highcharts.HTMLDOMElement|undefined} - */ - this.container = container = H.doc.createElement('div'); - container.className = 'highcharts-tooltip-container'; - css(container, { - position: 'absolute', - top: '1px', - pointerEvents: pointerEvents, - zIndex: 3 - }); - H.doc.body.appendChild(container); - /** - * Reference to the tooltip's renderer, when - * [Highcharts.Tooltip#outside] is set to true, otherwise - * it's undefined. - * - * @name Highcharts.Tooltip#renderer - * @type {Highcharts.SVGRenderer|undefined} - */ - this.renderer = renderer = new H.Renderer(container, 0, 0, (_b = this.chart.options.chart) === null || _b === void 0 ? void 0 : _b.style, void 0, void 0, renderer.styledMode); - } - // Create the label - if (this.split) { - this.label = renderer.g(className); - } - else { - this.label = renderer - .label('', 0, 0, options.shape || 'callout', null, null, options.useHTML, null, className) - .attr({ - padding: options.padding, - r: options.borderRadius - }); - if (!styledMode) { - this.label - .attr({ - fill: options.backgroundColor, - 'stroke-width': options.borderWidth - }) - // #2301, #2657 - .css(options.style) - .css({ pointerEvents: pointerEvents }) - .shadow(options.shadow); - } - } - if (styledMode) { - // Apply the drop-shadow filter - this.applyFilter(); - this.label.addClass('highcharts-tooltip-' + this.chart.index); - } - // Split tooltip use updateTooltipContainer to position the tooltip - // container. - if (tooltip.outside && !tooltip.split) { - var label_1 = this.label; - var xSetter_1 = label_1.xSetter, - ySetter_1 = label_1.ySetter; - label_1.xSetter = function (value) { - xSetter_1.call(label_1, tooltip.distance); - container.style.left = value + 'px'; - }; - label_1.ySetter = function (value) { - ySetter_1.call(label_1, tooltip.distance); - container.style.top = value + 'px'; - }; - } - this.label - .on('mouseenter', onMouseEnter) - .on('mouseleave', onMouseLeave) - .attr({ zIndex: 8 }) - .add(); - } - return this.label; - }; - /** - * Place the tooltip in a chart without spilling over - * and not covering the point it self. - * - * @private - * @function Highcharts.Tooltip#getPosition - * - * @param {number} boxWidth - * - * @param {number} boxHeight - * - * @param {Highcharts.Point} point - * - * @return {Highcharts.PositionObject} - */ - Tooltip.prototype.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, - outside = this.outside, - outerWidth = outside ? - // substract distance to prevent scrollbars - doc.documentElement.clientWidth - 2 * distance : - chart.chartWidth, - outerHeight = outside ? - Math.max(doc.body.scrollHeight, - doc.documentElement.scrollHeight, - doc.body.offsetHeight, - doc.documentElement.offsetHeight, - doc.documentElement.clientHeight) : - chart.chartHeight, - chartPosition = chart.pointer.getChartPosition(), - containerScaling = chart.containerScaling, - scaleX = function (val) { return ( // eslint-disable-line no-confusing-arrow - containerScaling ? val * containerScaling.scaleX : val); }, - scaleY = function (val) { return ( // eslint-disable-line no-confusing-arrow - containerScaling ? val * containerScaling.scaleY : val); }, - // Build parameter arrays for firstDimension()/secondDimension() - buildDimensionArray = function (dim) { - var isX = dim === 'x'; - return [ - dim, - isX ? outerWidth : outerHeight, - isX ? boxWidth : boxHeight - ].concat(outside ? [ - // If we are using tooltip.outside, we need to scale the - // position to match scaling of the container in case there - // is a transform/zoom on the container. #11329 - isX ? scaleX(boxWidth) : scaleY(boxHeight), - isX ? chartPosition.left - distance + - scaleX(point.plotX + chart.plotLeft) : - chartPosition.top - distance + - scaleY(point.plotY + chart.plotTop), - 0, - isX ? outerWidth : outerHeight - ] : [ - // Not outside, no scaling is needed - isX ? boxWidth : boxHeight, - isX ? point.plotX + chart.plotLeft : - point.plotY + chart.plotTop, - isX ? chart.plotLeft : chart.plotTop, - isX ? chart.plotLeft + chart.plotWidth : - chart.plotTop + chart.plotHeight - ]); - }, first = buildDimensionArray('y'), second = buildDimensionArray('x'), - // 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. - * - * @private - */ - firstDimension = function (dim, outerSize, innerSize, scaledInnerSize, // #11329 - point, min, max) { - var scaledDist = dim === 'y' ? - scaleY(distance) : scaleX(distance), - scaleDiff = (innerSize - scaledInnerSize) / 2, - roomLeft = scaledInnerSize < point - distance, - roomRight = point + distance + scaledInnerSize < outerSize, - alignedLeft = point - scaledDist - innerSize + scaleDiff, - alignedRight = point + scaledDist - scaleDiff; - if (preferFarSide && roomRight) { - ret[dim] = alignedRight; - } - else if (!preferFarSide && roomLeft) { - ret[dim] = alignedLeft; - } - else if (roomLeft) { - ret[dim] = Math.min(max - scaledInnerSize, 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. - * - * @private - */ - secondDimension = function (dim, outerSize, innerSize, scaledInnerSize, // #11329 - 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 - scaledInnerSize / 2) { - ret[dim] = outerSize - scaledInnerSize - 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; - }; - /** - * Get the best X date format based on the closest point range on the axis. - * - * @private - * @function Highcharts.Tooltip#getXDateFormat - * - * @param {Highcharts.Point} point - * - * @param {Highcharts.TooltipOptions} options - * - * @param {Highcharts.Axis} xAxis - * - * @return {string} - */ - Tooltip.prototype.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 - }; - /** - * Hides the tooltip with a fade out animation. - * - * @function Highcharts.Tooltip#hide - * - * @param {number} [delay] - * The fade out in milliseconds. If no value is provided the value - * of the tooltip.hideDelay option is used. A value of 0 disables - * the fade out animation. - */ - Tooltip.prototype.hide = function (delay) { - var tooltip = this; - // disallow duplicate timers (#1728, #1766) - U.clearTimeout(this.hideTimer); - delay = pick(delay, this.options.hideDelay, 500); - if (!this.isHidden) { - this.hideTimer = syncTimeout(function () { - // If there is a delay, do fadeOut with the default duration. If - // the hideDelay is 0, we assume no animation is wanted, so we - // pass 0 duration. #12994. - tooltip.getLabel().fadeOut(delay ? void 0 : delay); - tooltip.isHidden = true; - }, delay); - } - }; - /** - * @private - * @function Highcharts.Tooltip#init - * - * @param {Highcharts.Chart} chart - * The chart instance. - * - * @param {Highcharts.TooltipOptions} options - * Tooltip options. - */ - Tooltip.prototype.init = function (chart, options) { - /** - * Chart of the tooltip. - * - * @readonly - * @name Highcharts.Tooltip#chart - * @type {Highcharts.Chart} - */ - this.chart = chart; - /** - * Used tooltip options. - * - * @readonly - * @name Highcharts.Tooltip#options - * @type {Highcharts.TooltipOptions} - */ - this.options = options; - /** - * List of crosshairs. - * - * @private - * @readonly - * @name Highcharts.Tooltip#crosshairs - * @type {Array<null>} - */ - this.crosshairs = []; - /** - * Current values of x and y when animating. - * - * @private - * @readonly - * @name Highcharts.Tooltip#now - * @type {Highcharts.PositionObject} - */ - this.now = { x: 0, y: 0 }; - /** - * Tooltips are initially hidden. - * - * @private - * @readonly - * @name Highcharts.Tooltip#isHidden - * @type {boolean} - */ - this.isHidden = true; - /** - * True, if the tooltip is split into one label per series, with the - * header close to the axis. - * - * @readonly - * @name Highcharts.Tooltip#split - * @type {boolean|undefined} - */ - this.split = options.split && !chart.inverted && !chart.polar; - /** - * When the tooltip is shared, the entire plot area will capture mouse - * movement or touch events. - * - * @readonly - * @name Highcharts.Tooltip#shared - * @type {boolean|undefined} - */ - this.shared = options.shared || this.split; - /** - * Whether to allow the tooltip to render outside the chart's SVG - * element box. By default (false), the tooltip is rendered within the - * chart's SVG element, which results in the tooltip being aligned - * inside the chart area. - * - * @readonly - * @name Highcharts.Tooltip#outside - * @type {boolean} - * - * @todo - * Split tooltip does not support outside in the first iteration. Should - * not be too complicated to implement. - */ - this.outside = pick(options.outside, Boolean(chart.scrollablePixelsX || chart.scrollablePixelsY)); - }; - /** - * Returns true, if the pointer is in contact with the tooltip tracker. - */ - Tooltip.prototype.isStickyOnContact = function () { - return !!(!this.followPointer && - this.options.stickOnContact && - this.inContact); - }; - /** - * Moves the tooltip with a soft animation to a new position. - * - * @private - * @function Highcharts.Tooltip#move - * - * @param {number} x - * - * @param {number} y - * - * @param {number} anchorX - * - * @param {number} anchorY - */ - Tooltip.prototype.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 ? - void 0 : - animate ? (2 * now.anchorX + anchorX) / 3 : anchorX, - anchorY: skipAnchor ? - void 0 : - animate ? (now.anchorY + anchorY) / 2 : anchorY - }); - // Move to the intermediate value - tooltip.getLabel().attr(now); - tooltip.drawTracker(); - // Run on next tick of the mouse tracker - if (animate) { - // Never allow two timeouts - U.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); - } - }; - /** - * Refresh the tooltip's text and position. - * - * @function Highcharts.Tooltip#refresh - * - * @param {Highcharts.Point|Array<Highcharts.Point>} pointOrPoints - * Either a point or an array of points. - * - * @param {Highcharts.PointerEventObject} [mouseEvent] - * Mouse event, that is responsible for the refresh and should be - * used for the tooltip update. - */ - Tooltip.prototype.refresh = function (pointOrPoints, mouseEvent) { - var tooltip = this, - chart = this.chart, - options = tooltip.options, - x, - y, - point = pointOrPoints, - anchor, - textConfig = {}, - text, - pointConfig = [], - formatter = options.formatter || tooltip.defaultFormatter, - shared = tooltip.shared, - currentSeries, - styledMode = chart.styledMode; - if (!options.enabled) { - return; - } - U.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)) { - chart.pointer.applyInactiveState(point); - // Now set hover state for the choosen ones: - point.forEach(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 { - // update text - if (tooltip.split) { - this.renderSplit(text, splat(pointOrPoints)); - } - else { - var label = tooltip.getLabel(); - // Prevent the tooltip from flowing over the chart box (#6659) - if (!options.style.width || styledMode) { - label.css({ - width: this.chart.spacingBox.width + 'px' - }); - } - 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)); - if (!styledMode) { - 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 - }); - } - // show it - if (tooltip.isHidden && tooltip.label) { - tooltip.label.attr({ - opacity: 1 - }).show(); - } - tooltip.isHidden = false; - } - fireEvent(this, 'refresh'); - }; - /** - * 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. - * - * @private - * @function Highcharts.Tooltip#renderSplit - * - * @param {string|Array<(boolean|string)>} labels - * - * @param {Array<Highcharts.Point>} points - */ - Tooltip.prototype.renderSplit = function (labels, points) { - var tooltip = this; - var chart = tooltip.chart, - _a = tooltip.chart, - chartWidth = _a.chartWidth, - chartHeight = _a.chartHeight, - plotHeight = _a.plotHeight, - plotLeft = _a.plotLeft, - plotTop = _a.plotTop, - pointer = _a.pointer, - ren = _a.renderer, - _b = _a.scrollablePixelsY, - scrollablePixelsY = _b === void 0 ? 0 : _b, - _c = _a.scrollingContainer, - _d = _c === void 0 ? { scrollLeft: 0, - scrollTop: 0 } : _c, - scrollLeft = _d.scrollLeft, - scrollTop = _d.scrollTop, - styledMode = _a.styledMode, - distance = tooltip.distance, - options = tooltip.options, - positioner = tooltip.options.positioner; - // The area which the tooltip should be limited to. Limit to scrollable - // plot area if enabled, otherwise limit to the chart container. - var bounds = { - left: scrollLeft, - right: scrollLeft + chartWidth, - top: scrollTop, - bottom: scrollTop + chartHeight - }; - var tooltipLabel = tooltip.getLabel(); - var headerTop = Boolean(chart.xAxis[0] && chart.xAxis[0].opposite); - var distributionBoxTop = plotTop + scrollTop; - var headerHeight = 0; - var adjustedPlotHeight = plotHeight - scrollablePixelsY; - /** - * Calculates the anchor position for the partial tooltip - * - * @private - * @param {Highcharts.Point} point The point related to the tooltip - * @return {object} Returns an object with anchorX and anchorY - */ - function getAnchor(point) { - var isHeader = point.isHeader, - _a = point.plotX, - plotX = _a === void 0 ? 0 : _a, - _b = point.plotY, - plotY = _b === void 0 ? 0 : _b, - series = point.series; - var anchorX; - var anchorY; - if (isHeader) { - // Set anchorX to plotX - anchorX = plotLeft + plotX; - // Set anchorY to center of visible plot area. - anchorY = plotTop + plotHeight / 2; - } - else { - var xAxis = series.xAxis, - yAxis = series.yAxis; - // Set anchorX to plotX. Limit to within xAxis. - anchorX = xAxis.pos + clamp(plotX, -distance, xAxis.len + distance); - // Set anchorY, limit to the scrollable plot area - if (yAxis.pos + plotY >= scrollTop + plotTop && - yAxis.pos + plotY <= scrollTop + plotTop + plotHeight - scrollablePixelsY) { - anchorY = yAxis.pos + plotY; - } - } - // Limit values to plot area - anchorX = clamp(anchorX, bounds.left - distance, bounds.right + distance); - return { anchorX: anchorX, anchorY: anchorY }; - } - /** - * Calculates the position of the partial tooltip - * - * @private - * @param {number} anchorX The partial tooltip anchor x position - * @param {number} anchorY The partial tooltip anchor y position - * @param {boolean} isHeader Whether the partial tooltip is a header - * @param {number} boxWidth Width of the partial tooltip - * @return {Highcharts.PositionObject} Returns the partial tooltip x and - * y position - */ - function defaultPositioner(anchorX, anchorY, isHeader, boxWidth, alignedLeft) { - if (alignedLeft === void 0) { alignedLeft = true; } - var y; - var x; - if (isHeader) { - y = headerTop ? 0 : adjustedPlotHeight; - x = clamp(anchorX - (boxWidth / 2), bounds.left, bounds.right - boxWidth); - } - else { - y = anchorY - distributionBoxTop; - x = alignedLeft ? - anchorX - boxWidth - distance : - anchorX + distance; - x = clamp(x, alignedLeft ? x : bounds.left, bounds.right); - } - // NOTE: y is relative to distributionBoxTop - return { x: x, y: y }; - } - /** - * Updates the attributes and styling of the partial tooltip. Creates a - * new partial tooltip if it does not exists. - * - * @private - * @param {Highcharts.SVGElement|undefined} partialTooltip - * The partial tooltip to update - * @param {Highcharts.Point} point - * The point related to the partial tooltip - * @param {boolean|string} str The text for the partial tooltip - * @return {Highcharts.SVGElement} Returns the updated partial tooltip - */ - function updatePartialTooltip(partialTooltip, point, str) { - var tt = partialTooltip; - var isHeader = point.isHeader, - series = point.series; - var colorClass = 'highcharts-color-' + pick(point.colorIndex, series.colorIndex, 'none'); - if (!tt) { - var attribs = { - padding: options.padding, - r: options.borderRadius - }; - if (!styledMode) { - attribs.fill = options.backgroundColor; - attribs['stroke-width'] = options.borderWidth; - } - tt = ren - .label('', 0, 0, (options[isHeader ? 'headerShape' : 'shape']) || - 'callout', void 0, void 0, options.useHTML) - .addClass((isHeader ? 'highcharts-tooltip-header ' : '') + - 'highcharts-tooltip-box ' + - colorClass) - .attr(attribs) - .add(tooltipLabel); - } - tt.isActive = true; - tt.attr({ - text: str - }); - if (!styledMode) { - tt.css(options.style) - .shadow(options.shadow) - .attr({ - stroke: (options.borderColor || - point.color || - series.color || - '#333333') - }); - } - return tt; - } - // Graceful degradation for legacy formatters - if (isString(labels)) { - labels = [false, labels]; - } - // Create the individual labels for header and points, ignore footer - var boxes = labels.slice(0, - points.length + 1).reduce(function (boxes, - str, - i) { - if (str !== false && str !== '') { - 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, - plotY: plotHeight, - series: {} - }); - var isHeader = point.isHeader; - // Store the tooltip label referance on the series - var owner = isHeader ? tooltip : point.series; - var tt = owner.tt = updatePartialTooltip(owner.tt, - point, - str); - // Get X position now, so we can move all to the other side in - // case of overflow - var bBox = tt.getBBox(); - var boxWidth = bBox.width + tt.strokeWidth(); - if (isHeader) { - headerHeight = bBox.height; - adjustedPlotHeight += headerHeight; - if (headerTop) { - distributionBoxTop -= headerHeight; - } - } - var _a = getAnchor(point), - anchorX = _a.anchorX, - anchorY = _a.anchorY; - if (typeof anchorY === 'number') { - var size = bBox.height + 1; - var boxPosition = (positioner ? - positioner.call(tooltip, - boxWidth, - size, - point) : - defaultPositioner(anchorX, - anchorY, - isHeader, - boxWidth)); - boxes.push({ - // 0-align to the top, 1-align to the bottom - align: positioner ? 0 : void 0, - anchorX: anchorX, - anchorY: anchorY, - boxWidth: boxWidth, - point: point, - rank: pick(boxPosition.rank, isHeader ? 1 : 0), - size: size, - target: boxPosition.y, - tt: tt, - x: boxPosition.x - }); - } - else { - // Hide tooltips which anchorY is outside the visible plot - // area - tt.isActive = false; - } - } - return boxes; - }, []); - // If overflow left then align all labels to the right - if (!positioner && boxes.some(function (box) { return box.x < bounds.left; })) { - boxes = boxes.map(function (box) { - var _a = defaultPositioner(box.anchorX, - box.anchorY, - box.point.isHeader, - box.boxWidth, - false), - x = _a.x, - y = _a.y; - return extend(box, { - target: y, - x: x - }); - }); - } - // Clean previous run (for missing points) - tooltip.cleanSplit(); - // Distribute and put in place - H.distribute(boxes, adjustedPlotHeight); - boxes.forEach(function (box) { - var anchorX = box.anchorX, - anchorY = box.anchorY, - pos = box.pos, - x = box.x; - // Put the label in place - box.tt.attr({ - visibility: typeof pos === 'undefined' ? 'hidden' : 'inherit', - x: x, - /* NOTE: y should equal pos to be consistent with !split - * tooltip, but is currently relative to plotTop. Is left as is - * to avoid breaking change. Remove distributionBoxTop to make - * it consistent. - */ - y: pos + distributionBoxTop, - anchorX: anchorX, - anchorY: anchorY - }); - }); - /* If we have a seperate tooltip container, then update the necessary - * container properties. - * Test that tooltip has its own container and renderer before executing - * the operation. - */ - var container = tooltip.container, - outside = tooltip.outside, - renderer = tooltip.renderer; - if (outside && container && renderer) { - // Set container size to fit the tooltip - var _e = tooltipLabel.getBBox(), - width = _e.width, - height = _e.height, - x = _e.x, - y = _e.y; - renderer.setSize(width + x, height + y, false); - // Position the tooltip container to the chart container - var chartPosition = pointer.getChartPosition(); - container.style.left = chartPosition.left + 'px'; - container.style.top = chartPosition.top + 'px'; - } - }; - /** - * If the `stickOnContact` option is active, this will add a tracker shape. - * - * @private - * @function Highcharts.Tooltip#drawTracker - */ - Tooltip.prototype.drawTracker = function () { - var tooltip = this; - if (tooltip.followPointer || - !tooltip.options.stickOnContact) { - if (tooltip.tracker) { - tooltip.tracker.destroy(); - } - return; - } - var chart = tooltip.chart; - var label = tooltip.label; - var point = chart.hoverPoint; - if (!label || !point) { - return; - } - var box = { - x: 0, - y: 0, - width: 0, - height: 0 - }; - // Combine anchor and tooltip - var anchorPos = this.getAnchor(point); - var labelBBox = label.getBBox(); - anchorPos[0] += chart.plotLeft - label.translateX; - anchorPos[1] += chart.plotTop - label.translateY; - // When the mouse pointer is between the anchor point and the label, - // the label should stick. - box.x = Math.min(0, anchorPos[0]); - box.y = Math.min(0, anchorPos[1]); - box.width = (anchorPos[0] < 0 ? - Math.max(Math.abs(anchorPos[0]), (labelBBox.width - anchorPos[0])) : - Math.max(Math.abs(anchorPos[0]), labelBBox.width)); - box.height = (anchorPos[1] < 0 ? - Math.max(Math.abs(anchorPos[1]), (labelBBox.height - Math.abs(anchorPos[1]))) : - Math.max(Math.abs(anchorPos[1]), labelBBox.height)); - if (tooltip.tracker) { - tooltip.tracker.attr(box); - } - else { - tooltip.tracker = label.renderer - .rect(box) - .addClass('highcharts-tracker') - .add(label); - if (!chart.styledMode) { - tooltip.tracker.attr({ - fill: 'rgba(0,0,0,0)' - }); - } - } - }; - /** - * @private - */ - Tooltip.prototype.styledModeFormat = function (formatString) { - return formatString - .replace('style="font-size: 10px"', 'class="highcharts-header"') - .replace(/style="color:{(point|series)\.color}"/g, 'class="highcharts-color-{$1.colorIndex}"'); - }; - /** - * Format the footer/header of the tooltip - * #3397: abstraction to enable formatting of footer and header - * - * @private - * @function Highcharts.Tooltip#tooltipFooterHeaderFormatter - * @param {Highcharts.PointLabelObject} labelConfig - * @param {boolean} [isFooter] - * @return {string} - */ - Tooltip.prototype.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'], - e = { - isFooter: isFooter, - labelConfig: labelConfig - }; - fireEvent(this, 'headerFormatter', e, function (e) { - // 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) { - ((labelConfig.point && labelConfig.point.tooltipDateKeys) || - ['key']).forEach(function (key) { - formatString = formatString.replace('{point.' + key + '}', '{point.' + key + ':' + xDateFormat + '}'); - }); - } - // Replace default header style with class name - if (series.chart.styledMode) { - formatString = this.styledModeFormat(formatString); - } - e.text = format(formatString, { - point: labelConfig, - series: series - }, this.chart); - }); - return e.text; - }; - /** - * Updates the tooltip with the provided tooltip options. - * - * @function Highcharts.Tooltip#update - * - * @param {Highcharts.TooltipOptions} options - * The tooltip options to update. - */ - Tooltip.prototype.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)); - }; - /** - * Find the new position and perform the move - * - * @private - * @function Highcharts.Tooltip#updatePosition - * - * @param {Highcharts.Point} point - */ - Tooltip.prototype.updatePosition = function (point) { - var chart = this.chart, - pointer = chart.pointer, - label = this.getLabel(), - pos, - anchorX = point.plotX + chart.plotLeft, - anchorY = point.plotY + chart.plotTop, - pad; - // Needed for outside: true (#11688) - var chartPosition = pointer.getChartPosition(); - pos = (this.options.positioner || this.getPosition).call(this, label.width, label.height, point); - // Set the renderer size dynamically to prevent document size to change - if (this.outside) { - pad = (this.options.borderWidth || 0) + 2 * this.distance; - this.renderer.setSize(label.width + pad, label.height + pad, false); - // Anchor and tooltip container need scaling if chart container has - // scale transform/css zoom. #11329. - var containerScaling = chart.containerScaling; - if (containerScaling) { - css(this.container, { - transform: "scale(" + containerScaling.scaleX + ", " + containerScaling.scaleY + ")" - }); - anchorX *= containerScaling.scaleX; - anchorY *= containerScaling.scaleY; - } - anchorX += chartPosition.left - pos.x; - anchorY += chartPosition.top - pos.y; - } - // do the move - this.move(Math.round(pos.x), Math.round(pos.y || 0), // can be undefined (#3977) - anchorX, anchorY); - }; - return Tooltip; - }()); - H.Tooltip = Tooltip; - - return H.Tooltip; - }); - _registerModule(_modules, 'Core/Pointer.js', [_modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Tooltip.js'], _modules['Core/Utilities.js']], function (Color, H, Tooltip, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var color = Color.parse; - var charts = H.charts, - noop = H.noop; - var addEvent = U.addEvent, - attr = U.attr, - css = U.css, - defined = U.defined, - extend = U.extend, - find = U.find, - fireEvent = U.fireEvent, - isNumber = U.isNumber, - isObject = U.isObject, - objectEach = U.objectEach, - offset = U.offset, - pick = U.pick, - splat = U.splat; - /** - * One position in relation to an axis. - * - * @interface Highcharts.PointerAxisCoordinateObject - */ /** - * Related axis. - * - * @name Highcharts.PointerAxisCoordinateObject#axis - * @type {Highcharts.Axis} - */ /** - * Axis value. - * - * @name Highcharts.PointerAxisCoordinateObject#value - * @type {number} - */ - /** - * Positions in terms of axis values. - * - * @interface Highcharts.PointerAxisCoordinatesObject - */ /** - * Positions on the x-axis. - * @name Highcharts.PointerAxisCoordinatesObject#xAxis - * @type {Array<Highcharts.PointerAxisCoordinateObject>} - */ /** - * Positions on the y-axis. - * @name Highcharts.PointerAxisCoordinatesObject#yAxis - * @type {Array<Highcharts.PointerAxisCoordinateObject>} - */ - /** - * Pointer coordinates. - * - * @interface Highcharts.PointerCoordinatesObject - */ /** - * @name Highcharts.PointerCoordinatesObject#chartX - * @type {number} - */ /** - * @name Highcharts.PointerCoordinatesObject#chartY - * @type {number} - */ - /** - * A native browser mouse or touch event, extended with position information - * relative to the {@link Chart.container}. - * - * @interface Highcharts.PointerEventObject - * @extends global.PointerEvent - */ /** - * The X coordinate of the pointer interaction relative to the chart. - * - * @name Highcharts.PointerEventObject#chartX - * @type {number} - */ /** - * The Y coordinate of the pointer interaction relative to the chart. - * - * @name Highcharts.PointerEventObject#chartY - * @type {number} - */ - /** - * Axis-specific data of a selection. - * - * @interface Highcharts.SelectDataObject - */ /** - * @name Highcharts.SelectDataObject#axis - * @type {Highcharts.Axis} - */ /** - * @name Highcharts.SelectDataObject#max - * @type {number} - */ /** - * @name Highcharts.SelectDataObject#min - * @type {number} - */ - /** - * Object for select events. - * - * @interface Highcharts.SelectEventObject - */ /** - * @name Highcharts.SelectEventObject#originalEvent - * @type {global.Event} - */ /** - * @name Highcharts.SelectEventObject#xAxis - * @type {Array<Highcharts.SelectDataObject>} - */ /** - * @name Highcharts.SelectEventObject#yAxis - * @type {Array<Highcharts.SelectDataObject>} - */ - ''; // detach doclets above - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * 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 - * @name Highcharts.Pointer - * - * @param {Highcharts.Chart} chart - * The chart instance. - * - * @param {Highcharts.Options} options - * The root options object. The pointer uses options from the chart and - * tooltip structures. - */ - var Pointer = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function Pointer(chart, options) { - this.lastValidTouch = {}; - this.pinchDown = []; - this.runChartClick = false; - this.chart = chart; - this.hasDragged = false; - this.options = options; - this.unbindContainerMouseLeave = function () { }; - this.unbindContainerMouseEnter = function () { }; - this.init(chart, options); - } - /* * - * - * Functions - * - * */ - /** - * Set inactive state to all series that are not currently hovered, - * or, if `inactiveOtherPoints` is set to true, set inactive state to - * all points within that series. - * - * @private - * @function Highcharts.Pointer#applyInactiveState - * @param {Array<Highcharts.Point>} points - * Currently hovered points - */ - Pointer.prototype.applyInactiveState = function (points) { - var activeSeries = [], - series; - // Get all active series from the hovered points - (points || []).forEach(function (item) { - series = item.series; - // Include itself - activeSeries.push(series); - // Include parent series - if (series.linkedParent) { - activeSeries.push(series.linkedParent); - } - // Include all child series - if (series.linkedSeries) { - activeSeries = activeSeries.concat(series.linkedSeries); - } - // Include navigator series - if (series.navigatorSeries) { - activeSeries.push(series.navigatorSeries); - } - }); - // Now loop over all series, filtering out active series - this.chart.series.forEach(function (inactiveSeries) { - if (activeSeries.indexOf(inactiveSeries) === -1) { - // Inactive series - inactiveSeries.setState('inactive', true); - } - else if (inactiveSeries.options.inactiveOtherPoints) { - // Active series, but other points should be inactivated - inactiveSeries.setAllPointsToState('inactive'); - } - }); - }; - /** - * Destroys the Pointer object and disconnects DOM events. - * - * @function Highcharts.Pointer#destroy - */ - Pointer.prototype.destroy = function () { - var pointer = this; - if (typeof pointer.unDocMouseMove !== 'undefined') { - 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); - objectEach(pointer, function (_val, prop) { - pointer[prop] = void 0; - }); - }; - /** - * Perform a drag operation in response to a mousemove event while the mouse - * is down. - * - * @private - * @function Highcharts.Pointer#drag - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - Pointer.prototype.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 || 0), - mouseDownY = (this.mouseDownY || 0), - panningEnabled = isObject(chartOptions.panning) ? - chartOptions.panning && chartOptions.panning.enabled : - chartOptions.panning, - 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({ - 'class': 'highcharts-selection-marker', - zIndex: 7 - }) - .add(); - if (!chart.styledMode) { - selectionMarker.attr({ - fill: (chartOptions.selectionMarkerFill || - color('#335cad') - .setOpacity(0.25).get()) - }); - } - } - } - // 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 && - panningEnabled) { - chart.pan(e, chartOptions.panning); - } - } - }; - /** - * Start a drag operation. - * - * @private - * @function Highcharts.Pointer#dragStart - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - Pointer.prototype.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; - }; - /** - * On mouse up or touch end across the entire document, drop the selection. - * - * @private - * @function Highcharts.Pointer#drop - * - * @param {global.Event} e - */ - Pointer.prototype.drop = function (e) { - var pointer = this, - chart = this.chart, - hasPinched = this.hasPinched; - if (this.selectionMarker) { - var selectionData = { - originalEvent: e, - 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 - chart.axes.forEach(function (axis) { - if (axis.zoomEnabled && - defined(axis.min) && - (hasPinched || - pointer[{ - xAxis: 'zoomX', - yAxis: 'zoomY' - }[axis.coll]]) && - isNumber(selectionLeft) && - isNumber(selectionTop)) { // #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/max for reversed axes - min: Math.min(selectionMin, selectionMax), - max: Math.max(selectionMin, selectionMax) - }); - runZoom = true; - } - }); - if (runZoom) { - fireEvent(chart, 'selection', selectionData, function (args) { - chart.zoom(extend(args, hasPinched ? - { animation: false } : - null)); - }); - } - } - if (isNumber(chart.index)) { - this.selectionMarker = this.selectionMarker.destroy(); - } - // Reset scaling preview - if (hasPinched) { - this.scaleGroups(); - } - } - // Reset all. Check isNumber because it may be destroyed on mouse up - // (#877) - if (chart && isNumber(chart.index)) { - css(chart.container, { cursor: chart._cursor }); - chart.cancelClick = this.hasDragged > 10; // #370 - chart.mouseIsDown = this.hasDragged = this.hasPinched = false; - this.pinchDown = []; - } - }; - /** - * Finds the closest point to a set of coordinates, using the k-d-tree - * algorithm. - * - * @function Highcharts.Pointer#findNearestKDPoint - * - * @param {Array<Highcharts.Series>} series - * All the series to search in. - * - * @param {boolean|undefined} shared - * Whether it is a shared tooltip or not. - * - * @param {Highcharts.PointerEventObject} e - * The pointer event object, containing chart coordinates of the - * pointer. - * - * @return {Highcharts.Point|undefined} - * The point closest to given coordinates. - */ - Pointer.prototype.findNearestKDPoint = function (series, shared, e) { - var chart = this.chart; - var hoverPoint = chart.hoverPoint; - var tooltip = chart.tooltip; - if (hoverPoint && - tooltip && - tooltip.isStickyOnContact()) { - return hoverPoint; - } - var closest; - /** @private */ - function sort(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; - } - series.forEach(function (s) { - var noSharedTooltip = s.noSharedTooltip && shared, - compareX = (!noSharedTooltip && - s.options.findNearestPointBy.indexOf('y') < 0), - point = s.searchPoint(e, - 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; - }; - /** - * @private - * @function Highcharts.Pointer#getChartCoordinatesFromPoint - * @param {Highcharts.Point} point - * @param {boolean} [inverted] - * @return {Highcharts.PointerCoordinatesObject|undefined} - */ - Pointer.prototype.getChartCoordinatesFromPoint = function (point, inverted) { - var series = point.series, - xAxis = series.xAxis, - yAxis = series.yAxis, - plotX = pick(point.clientX, - point.plotX), - shapeArgs = point.shapeArgs; - 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 - }; - } - if (shapeArgs && shapeArgs.x && shapeArgs.y) { - // E.g. pies do not have axes - return { - chartX: shapeArgs.x, - chartY: shapeArgs.y - }; - } - }; - /** - * Return the cached chartPosition if it is available on the Pointer, - * otherwise find it. Running offset is quite expensive, so it should be - * avoided when we know the chart hasn't moved. - * - * @function Highcharts.Pointer#getChartPosition - * - * @return {Highcharts.OffsetObject} - * The offset of the chart container within the page - */ - Pointer.prototype.getChartPosition = function () { - return (this.chartPosition || - (this.chartPosition = offset(this.chart.container))); - }; - /** - * Get the click position in terms of axis values. - * - * @function Highcharts.Pointer#getCoordinates - * - * @param {Highcharts.PointerEventObject} e - * Pointer event, extended with `chartX` and `chartY` properties. - * - * @return {Highcharts.PointerAxisCoordinatesObject} - */ - Pointer.prototype.getCoordinates = function (e) { - var coordinates = { - xAxis: [], - yAxis: [] - }; - this.chart.axes.forEach(function (axis) { - coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({ - axis: axis, - value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY']) - }); - }); - return coordinates; - }; - /** - * Calculates what is the current hovered point/points and series. - * - * @private - * @function Highcharts.Pointer#getHoverData - * - * @param {Highcharts.Point|undefined} existingHoverPoint - * The point currrently beeing hovered. - * - * @param {Highcharts.Series|undefined} existingHoverSeries - * The series currently beeing hovered. - * - * @param {Array<Highcharts.Series>} series - * All the series in the chart. - * - * @param {boolean} isDirectTouch - * Is the pointer directly hovering the point. - * - * @param {boolean|undefined} shared - * Whether it is a shared tooltip or not. - * - * @param {Highcharts.PointerEventObject} [e] - * The triggering event, containing chart coordinates of the pointer. - * - * @return {object} - * Object containing resulting hover data: hoverPoint, hoverSeries, - * and hoverPoints. - */ - Pointer.prototype.getHoverData = function (existingHoverPoint, existingHoverSeries, series, isDirectTouch, shared, e) { - var hoverPoint, - hoverPoints = [], - hoverSeries = existingHoverSeries, - useExisting = !!(isDirectTouch && existingHoverPoint), - notSticky = hoverSeries && !hoverSeries.stickyTracking, - // Which series to look in for the hover point - searchSeries, - // Parameters needed for beforeGetHoverData event. - eventArgs = { - chartX: e ? e.chartX : void 0, - chartY: e ? e.chartY : void 0, - shared: shared - }, - filter = function (s) { - return (s.visible && - !(!shared && s.directTouch) && // #3821 - pick(s.options.enableMouseTracking, - true)); - }; - // Find chart.hoverPane and update filter method in polar. - fireEvent(this, 'beforeGetHoverData', eventArgs); - searchSeries = notSticky ? - // Only search on hovered series if it has stickyTracking false - [hoverSeries] : - // Filter what series to look in. - series.filter(function (s) { - return eventArgs.filter ? eventArgs.filter(s) : filter(s) && - s.stickyTracking; - }); - // Use existing hovered point or find the one closest to coordinates. - hoverPoint = useExisting || !e ? - existingHoverPoint : - this.findNearestKDPoint(searchSeries, shared, e); - // 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 = series.filter(function (s) { - return eventArgs.filter ? - eventArgs.filter(s) : filter(s) && !s.noSharedTooltip; - }); - // Get all points with the same x value as the hoverPoint - searchSeries.forEach(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 (s.chart.isBoosting) { - point = s.getPoint(point); - } - hoverPoints.push(point); - } - }); - } - else { - hoverPoints.push(hoverPoint); - } - } - // Check whether the hoverPoint is inside pane we are hovering over. - eventArgs = { hoverPoint: hoverPoint }; - fireEvent(this, 'afterGetHoverData', eventArgs); - return { - hoverPoint: eventArgs.hoverPoint, - hoverSeries: hoverSeries, - hoverPoints: hoverPoints - }; - }; - /** - * @private - * @function Highcharts.Pointer#getPointFromEvent - * - * @param {global.Event} e - * - * @return {Highcharts.Point|undefined} - */ - Pointer.prototype.getPointFromEvent = function (e) { - var target = e.target, - point; - while (target && !point) { - point = target.point; - target = target.parentNode; - } - return point; - }; - /** - * @private - * @function Highcharts.Pointer#onTrackerMouseOut - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - Pointer.prototype.onTrackerMouseOut = function (e) { - var chart = this.chart; - var relatedTarget = e.relatedTarget || e.toElement; - var series = chart.hoverSeries; - this.isDirectTouch = false; - if (series && - relatedTarget && - !series.stickyTracking && - !this.inClass(relatedTarget, 'highcharts-tooltip') && - (!this.inClass(relatedTarget, 'highcharts-series-' + series.index) || // #2499, #4465, #5553 - !this.inClass(relatedTarget, 'highcharts-tracker'))) { - series.onMouseOut(); - } - }; - /** - * Utility to detect whether an element has, or has a parent with, a - * specificclass name. Used on detection of tracker objects and on deciding - * whether hovering the tooltip should cause the active series to mouse out. - * - * @function Highcharts.Pointer#inClass - * - * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element - * The element to investigate. - * - * @param {string} className - * The class name to look for. - * - * @return {boolean|undefined} - * True if either the element or one of its parents has the given - * class name. - */ - Pointer.prototype.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; - } - }; - /** - * Initialize the Pointer. - * - * @private - * @function Highcharts.Pointer#init - * - * @param {Highcharts.Chart} chart - * The Chart instance. - * - * @param {Highcharts.Options} options - * The root options object. The pointer uses options from the chart - * and tooltip structures. - * - * @return {void} - */ - Pointer.prototype.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) { - /** - * Tooltip object for points of series. - * - * @name Highcharts.Chart#tooltip - * @type {Highcharts.Tooltip} - */ - chart.tooltip = new Tooltip(chart, options.tooltip); - this.followTouchMove = pick(options.tooltip.followTouchMove, true); - } - this.setDOMEvents(); - }; - /** - * Takes a browser event object and extends it with custom Highcharts - * properties `chartX` and `chartY` in order to work on the internal - * coordinate system. - * - * @function Highcharts.Pointer#normalize - * - * @param {global.MouseEvent|global.PointerEvent|global.TouchEvent} e - * Event object in standard browsers. - * - * @param {Highcharts.OffsetObject} [chartPosition] - * Additional chart offset. - * - * @return {Highcharts.PointerEventObject} - * A browser event with extended properties `chartX` and `chartY`. - */ - Pointer.prototype.normalize = function (e, chartPosition) { - var touches = e.touches; - // iOS (#2757) - var ePos = (touches ? - touches.length ? - touches.item(0) : - (pick(// #13534 - touches.changedTouches, - e.changedTouches))[0] : - e); - // Get mouse position - if (!chartPosition) { - chartPosition = this.getChartPosition(); - } - var chartX = ePos.pageX - chartPosition.left, - chartY = ePos.pageY - chartPosition.top; - // #11329 - when there is scaling on a parent element, we need to take - // this into account - var containerScaling = this.chart.containerScaling; - if (containerScaling) { - chartX /= containerScaling.scaleX; - chartY /= containerScaling.scaleY; - } - return extend(e, { - chartX: Math.round(chartX), - chartY: Math.round(chartY) - }); - }; - /** - * @private - * @function Highcharts.Pointer#onContainerClick - */ - Pointer.prototype.onContainerClick = function (e) { - var chart = this.chart; - var hoverPoint = chart.hoverPoint; - var pEvt = this.normalize(e); - var plotLeft = chart.plotLeft; - var plotTop = chart.plotTop; - if (!chart.cancelClick) { - // On tracker click, fire the series and point events. #783, #1583 - if (hoverPoint && - this.inClass(pEvt.target, 'highcharts-tracker')) { - // the series click event - fireEvent(hoverPoint.series, 'click', extend(pEvt, { - point: hoverPoint - })); - // the point click event - if (chart.hoverPoint) { // it may be destroyed (#1844) - hoverPoint.firePointEvent('click', pEvt); - } - // When clicking outside a tracker, fire a chart event - } - else { - extend(pEvt, this.getCoordinates(pEvt)); - // fire a click event in the chart - if (chart.isInsidePlot((pEvt.chartX - plotLeft), (pEvt.chartY - plotTop))) { - fireEvent(chart, 'click', pEvt); - } - } - } - }; - /** - * @private - * @function Highcharts.Pointer#onContainerMouseDown - * - * @param {global.MouseEvent} e - */ - Pointer.prototype.onContainerMouseDown = function (e) { - var isPrimaryButton = ((e.buttons || e.button) & 1) === 1; - // Normalize before the 'if' for the legacy IE (#7850) - e = this.normalize(e); - // #11635, Firefox does not reliable fire move event after click scroll - if (H.isFirefox && - e.button !== 0) { - this.onContainerMouseMove(e); - } - // #11635, limiting to primary button (incl. IE 8 support) - if (typeof e.button === 'undefined' || - isPrimaryButton) { - this.zoomOption(e); - // #295, #13737 solve conflict between container drag and chart zoom - if (isPrimaryButton && - e.preventDefault) { - e.preventDefault(); - } - this.dragStart(e); - } - }; - /** - * When mouse leaves the container, hide the tooltip. - * - * @private - * @function Highcharts.Pointer#onContainerMouseLeave - * - * @param {global.MouseEvent} e - * - * @return {void} - */ - Pointer.prototype.onContainerMouseLeave = function (e) { - var chart = charts[pick(H.hoverChartIndex, -1)]; - var tooltip = this.chart.tooltip; - e = this.normalize(e); - // #4886, MS Touch end fires mouseleave but with no related target - if (chart && - (e.relatedTarget || e.toElement)) { - chart.pointer.reset(); - // Also reset the chart position, used in #149 fix - chart.pointer.chartPosition = void 0; - } - if ( // #11635, Firefox wheel scroll does not fire out events consistently - tooltip && - !tooltip.isHidden) { - this.reset(); - } - }; - /** - * When mouse enters the container, delete pointer's chartPosition. - * - * @private - * @function Highcharts.Pointer#onContainerMouseEnter - * - * @param {global.MouseEvent} e - * - * @return {void} - */ - Pointer.prototype.onContainerMouseEnter = function (e) { - delete this.chartPosition; - }; - /** - * The mousemove, touchmove and touchstart event handler - * - * @private - * @function Highcharts.Pointer#onContainerMouseMove - * - * @param {global.MouseEvent} e - * - * @return {void} - */ - Pointer.prototype.onContainerMouseMove = function (e) { - var chart = this.chart; - var pEvt = this.normalize(e); - this.setHoverChartIndex(); - // In IE8 we apparently need this returnValue set to false in order to - // avoid text being selected. But in Chrome, e.returnValue is prevented, - // plus we don't need to run e.preventDefault to prevent selected text - // in modern browsers. So we set it conditionally. Remove it when IE8 is - // no longer needed. #2251, #3224. - if (!pEvt.preventDefault) { - pEvt.returnValue = false; - } - if (chart.mouseIsDown === 'mousedown') { - this.drag(pEvt); - } - // Show the tooltip and run mouse over events (#977) - if (!chart.openMenu && - (this.inClass(pEvt.target, 'highcharts-tracker') || - chart.isInsidePlot((pEvt.chartX - chart.plotLeft), (pEvt.chartY - chart.plotTop)))) { - this.runPointActions(pEvt); - } - }; - /** - * @private - * @function Highcharts.Pointer#onDocumentTouchEnd - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - Pointer.prototype.onDocumentTouchEnd = function (e) { - if (charts[H.hoverChartIndex]) { - charts[H.hoverChartIndex].pointer.drop(e); - } - }; - /** - * @private - * @function Highcharts.Pointer#onContainerTouchMove - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - Pointer.prototype.onContainerTouchMove = function (e) { - this.touch(e); - }; - /** - * @private - * @function Highcharts.Pointer#onContainerTouchStart - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - Pointer.prototype.onContainerTouchStart = function (e) { - this.zoomOption(e); - this.touch(e, true); - }; - /** - * 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 - * @function Highcharts.Pointer#onDocumentMouseMove - * - * @param {global.MouseEvent} e - * - * @return {void} - */ - Pointer.prototype.onDocumentMouseMove = function (e) { - var chart = this.chart; - var chartPosition = this.chartPosition; - var pEvt = this.normalize(e, - chartPosition); - var tooltip = chart.tooltip; - // If we're outside, hide the tooltip - if (chartPosition && - (!tooltip || - !tooltip.isStickyOnContact()) && - !chart.isInsidePlot(pEvt.chartX - chart.plotLeft, pEvt.chartY - chart.plotTop) && - !this.inClass(pEvt.target, 'highcharts-tracker')) { - this.reset(); - } - }; - /** - * @private - * @function Highcharts.Pointer#onDocumentMouseUp - * - * @param {global.MouseEvent} e - * - * @return {void} - */ - Pointer.prototype.onDocumentMouseUp = function (e) { - var chart = charts[pick(H.hoverChartIndex, -1)]; - if (chart) { - chart.pointer.drop(e); - } - }; - /** - * Handle touch events with two touches - * - * @private - * @function Highcharts.Pointer#pinch - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - Pointer.prototype.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.cancelable !== false) { - e.preventDefault(); - } - // Normalize each touch - [].map.call(touches, function (e) { - return self.normalize(e); - }); - // Register the touch start position - if (e.type === 'touchstart') { - [].forEach.call(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 - chart.axes.forEach(function (axis) { - if (axis.zoomEnabled) { - var bounds = chart.bounds[axis.horiz ? 'h' : 'v'], - minPixelPadding = axis.minPixelPadding, - min = axis.toPixels(Math.min(pick(axis.options.min, - axis.dataMin), - axis.dataMin)), - max = axis.toPixels(Math.max(pick(axis.options.max, - axis.dataMax), - 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); - } - } - }; - /** - * Run translation operations - * - * @private - * @function Highcharts.Pointer#pinchTranslate - * - * @param {Array<*>} pinchDown - * - * @param {Array<Highcharts.PointerEventObject>} touches - * - * @param {*} transform - * - * @param {*} selectionMarker - * - * @param {*} clip - * - * @param {*} lastValidTouch - * - * @return {void} - */ - Pointer.prototype.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. - * - * @private - * @function Highcharts.Pointer#pinchTranslateDirection - * - * @param {boolean} horiz - * - * @param {Array<*>} pinchDown - * - * @param {Array<Highcharts.PointerEventObject>} touches - * - * @param {*} transform - * - * @param {*} selectionMarker - * - * @param {*} clip - * - * @param {*} lastValidTouch - * - * @param {number|undefined} [forcedScale=1] - * - * @return {void} - */ - Pointer.prototype.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 (typeof touch1Now === 'number' && - 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 (typeof touch1Now === 'number') { - 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)); - }; - /** - * Reset the tracking by hiding the tooltip, the hover series state and the - * hover point - * - * @function Highcharts.Pointer#reset - * - * @param {boolean} [allowMove] - * Instead of destroying the tooltip altogether, allow moving it if - * possible. - * - * @param {number} [delay] - * - * @return {void} - */ - Pointer.prototype.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) { - splat(tooltipPoints).forEach(function (point) { - if (point.series.isCartesian && - typeof point.plotX === 'undefined') { - allowMove = false; - } - }); - } - // Just move the tooltip, #349 - if (allowMove) { - if (tooltip && tooltipPoints && splat(tooltipPoints).length) { - tooltip.refresh(tooltipPoints); - if (tooltip.shared && hoverPoints) { // #8284 - hoverPoints.forEach(function (point) { - point.setState(point.state, true); - if (point.series.isCartesian) { - if (point.series.xAxis.crosshair) { - point.series.xAxis - .drawCrosshair(null, point); - } - if (point.series.yAxis.crosshair) { - point.series.yAxis - .drawCrosshair(null, point); - } - } - }); - } - else if (hoverPoint) { // #2500 - hoverPoint.setState(hoverPoint.state, true); - chart.axes.forEach(function (axis) { - if (axis.crosshair && - hoverPoint.series[axis.coll] === axis) { - axis.drawCrosshair(null, hoverPoint); - } - }); - } - } - // Full reset - } - else { - if (hoverPoint) { - hoverPoint.onMouseOut(); - } - if (hoverPoints) { - hoverPoints.forEach(function (point) { - point.setState(); - }); - } - if (hoverSeries) { - hoverSeries.onMouseOut(); - } - if (tooltip) { - tooltip.hide(delay); - } - if (pointer.unDocMouseMove) { - pointer.unDocMouseMove = pointer.unDocMouseMove(); - } - // Remove crosshairs - chart.axes.forEach(function (axis) { - axis.hideCrosshair(); - }); - pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null; - } - }; - /** - * 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 - * @function Highcharts.Pointer#runPointActions - * - * @param {global.Event} e - * - * @param {Highcharts.PointerEventObject} [p] - * - * @return {void} - * - * @fires Highcharts.Point#event:mouseOut - * @fires Highcharts.Point#event:mouseOver - */ - Pointer.prototype.runPointActions = function (e, p) { - var pointer = this, - chart = pointer.chart, - series = chart.series, - tooltip = (chart.tooltip && chart.tooltip.options.enabled ? - chart.tooltip : - void 0), - shared = (tooltip ? - tooltip.shared : - false), - hoverPoint = p || chart.hoverPoint, - hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries, - // onMouseOver or already hovering a series with directTouch - isDirectTouch = (!e || e.type !== 'touchmove') && (!!p || ((hoverSeries && hoverSeries.directTouch) && - pointer.isDirectTouch)), - hoverData = this.getHoverData(hoverPoint, - hoverSeries, - series, - isDirectTouch, - shared, - e), - 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))) { - (chart.hoverPoints || []).forEach(function (p) { - if (points.indexOf(p) === -1) { - p.setState(); - } - }); - // Set normal state to previous series - if (chart.hoverSeries !== hoverSeries) { - hoverSeries.onMouseOver(); - } - pointer.applyInactiveState(points); - // Do mouseover on all points (#3919, #3985, #4410, #5622) - (points || []).forEach(function (p) { - p.setState('hover'); - }); - // 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; - } - /** - * Contains all hovered points. - * - * @name Highcharts.Chart#hoverPoints - * @type {Array<Highcharts.Point>|null} - */ - chart.hoverPoints = points; - /** - * Contains the original hovered point. - * - * @name Highcharts.Chart#hoverPoint - * @type {Highcharts.Point|null} - */ - chart.hoverPoint = hoverPoint; - /** - * Hover state should not be lost when axis is updated (#12569) - * Axis.update runs pointer.reset which uses chart.hoverPoint.state - * to apply state which does not exist in hoverPoint yet. - * The mouseOver event should be triggered when hoverPoint - * is correct. - */ - hoverPoint.firePointEvent('mouseOver'); - // 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 - chart.axes.forEach(function drawAxisCrosshair(axis) { - var snap = pick((axis.crosshair || {}).snap, - true); - var point; - if (snap) { - point = chart.hoverPoint; // #13002 - if (!point || point.series[axis.coll] !== axis) { - point = 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(); - } - }); - }; - /** - * Scale series groups to a certain scale and translation. - * - * @private - * @function Highcharts.Pointer#scaleGroups - * - * @param {Highcharts.SeriesPlotBoxObject} [attribs] - * - * @param {boolean} [clip] - * - * @return {void} - */ - Pointer.prototype.scaleGroups = function (attribs, clip) { - var chart = this.chart, - seriesAttribs; - // Scale each series - chart.series.forEach(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); - }; - /** - * 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 - * @function Highcharts.Pointer#setDOMEvents - * - * @return {void} - */ - Pointer.prototype.setDOMEvents = function () { - var container = this.chart.container, - ownerDoc = container.ownerDocument; - container.onmousedown = this.onContainerMouseDown.bind(this); - container.onmousemove = this.onContainerMouseMove.bind(this); - container.onclick = this.onContainerClick.bind(this); - this.unbindContainerMouseEnter = addEvent(container, 'mouseenter', this.onContainerMouseEnter.bind(this)); - this.unbindContainerMouseLeave = addEvent(container, 'mouseleave', this.onContainerMouseLeave.bind(this)); - if (!H.unbindDocumentMouseUp) { - H.unbindDocumentMouseUp = addEvent(ownerDoc, 'mouseup', this.onDocumentMouseUp.bind(this)); - } - if (H.hasTouch) { - addEvent(container, 'touchstart', this.onContainerTouchStart.bind(this)); - addEvent(container, 'touchmove', this.onContainerTouchMove.bind(this)); - if (!H.unbindDocumentTouchEnd) { - H.unbindDocumentTouchEnd = addEvent(ownerDoc, 'touchend', this.onDocumentTouchEnd.bind(this)); - } - } - }; - /** - * Sets the index of the hovered chart and leaves the previous hovered - * chart, to reset states like tooltip. - * - * @private - * @function Highcharts.Pointer#setHoverChartIndex - */ - Pointer.prototype.setHoverChartIndex = function () { - var chart = this.chart; - var hoverChart = H.charts[pick(H.hoverChartIndex, -1)]; - if (hoverChart && - hoverChart !== chart) { - hoverChart.pointer.onContainerMouseLeave({ relatedTarget: true }); - } - if (!hoverChart || - !hoverChart.mouseIsDown) { - H.hoverChartIndex = chart.index; - } - }; - /** - * General touch handler shared by touchstart and touchmove. - * - * @private - * @function Highcharts.Pointer#touch - * - * @param {Highcharts.PointerEventObject} e - * - * @param {boolean} [start] - * - * @return {void} - */ - Pointer.prototype.touch = function (e, start) { - var chart = this.chart, - hasMoved, - pinchDown, - isInside; - this.setHoverChartIndex(); - 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); - } - }; - /** - * Resolve the zoomType option, this is reset on all touch start and mouse - * down events. - * - * @private - * @function Highcharts.Pointer#zoomOption - * - * @param {global.Event} e - * Event object. - * - * @param {void} - */ - Pointer.prototype.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; - }; - return Pointer; - }()); - H.Pointer = Pointer; - - return Pointer; - }); - _registerModule(_modules, 'Core/MSPointer.js', [_modules['Core/Globals.js'], _modules['Core/Pointer.js'], _modules['Core/Utilities.js']], function (H, Pointer, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, - b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, - b) { d.__proto__ = b; }) || - function (d, - b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; - })(); - var charts = H.charts, - doc = H.doc, - noop = H.noop, - win = H.win; - var addEvent = U.addEvent, - css = U.css, - objectEach = U.objectEach, - removeEvent = U.removeEvent; - /* globals MSPointerEvent, PointerEvent */ - // The touches object keeps track of the points being touched at all times - var touches = {}; - var hasPointerEvent = !!win.PointerEvent; - /* eslint-disable valid-jsdoc */ - /** @private */ - function getWebkitTouches() { - var fake = []; - fake.item = function (i) { - return this[i]; - }; - objectEach(touches, function (touch) { - fake.push({ - pageX: touch.pageX, - pageY: touch.pageY, - target: touch.target - }); - }); - return fake; - } - /** @private */ - function translateMSPointer(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() - }); - } - } - /** @private */ - var MSPointer = /** @class */ (function (_super) { - __extends(MSPointer, _super); - function MSPointer() { - return _super !== null && _super.apply(this, arguments) || this; - } - /* * - * - * Functions - * - * */ - /** - * Add or remove the MS Pointer specific events - * - * @private - * @function Highcharts.Pointer#batchMSEvents - * - * @param {Function} fn - * - * @return {void} - */ - MSPointer.prototype.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); - }; - // Destroy MS events also - MSPointer.prototype.destroy = function () { - this.batchMSEvents(removeEvent); - _super.prototype.destroy.call(this); - }; - // Disable default IE actions for pinch and such on chart element - MSPointer.prototype.init = function (chart, options) { - _super.prototype.init.call(this, chart, options); - if (this.hasZoom) { // #4014 - css(chart.container, { - '-ms-touch-action': 'none', - 'touch-action': 'none' - }); - } - }; - /** - * @private - * @function Highcharts.Pointer#onContainerPointerDown - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - MSPointer.prototype.onContainerPointerDown = function (e) { - translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) { - touches[e.pointerId] = { - pageX: e.pageX, - pageY: e.pageY, - target: e.currentTarget - }; - }); - }; - /** - * @private - * @function Highcharts.Pointer#onContainerPointerMove - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - MSPointer.prototype.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; - } - }); - }; - /** - * @private - * @function Highcharts.Pointer#onDocumentPointerUp - * - * @param {Highcharts.PointerEventObject} e - * - * @return {void} - */ - MSPointer.prototype.onDocumentPointerUp = function (e) { - translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function (e) { - delete touches[e.pointerId]; - }); - }; - // Add IE specific touch events to chart - MSPointer.prototype.setDOMEvents = function () { - _super.prototype.setDOMEvents.call(this); - if (this.hasZoom || this.followTouchMove) { - this.batchMSEvents(addEvent); - } - }; - return MSPointer; - }(Pointer)); - - return MSPointer; - }); - _registerModule(_modules, 'Core/Legend.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (A, H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var animObject = A.animObject, - setAnimation = A.setAnimation; - /** - * Gets fired when the legend item belonging to a point is clicked. The default - * action is to toggle the visibility of the point. This can be prevented by - * returning `false` or calling `event.preventDefault()`. - * - * @callback Highcharts.PointLegendItemClickCallbackFunction - * - * @param {Highcharts.Point} this - * The point on which the event occured. - * - * @param {Highcharts.PointLegendItemClickEventObject} event - * The event that occured. - */ - /** - * Information about the legend click event. - * - * @interface Highcharts.PointLegendItemClickEventObject - */ /** - * Related browser event. - * @name Highcharts.PointLegendItemClickEventObject#browserEvent - * @type {Highcharts.PointerEvent} - */ /** - * Prevent the default action of toggle the visibility of the point. - * @name Highcharts.PointLegendItemClickEventObject#preventDefault - * @type {Function} - */ /** - * Related point. - * @name Highcharts.PointLegendItemClickEventObject#target - * @type {Highcharts.Point} - */ /** - * Event type. - * @name Highcharts.PointLegendItemClickEventObject#type - * @type {"legendItemClick"} - */ - /** - * Gets fired when the legend item belonging to a series is clicked. The default - * action is to toggle the visibility of the series. This can be prevented by - * returning `false` or calling `event.preventDefault()`. - * - * @callback Highcharts.SeriesLegendItemClickCallbackFunction - * - * @param {Highcharts.Series} this - * The series where the event occured. - * - * @param {Highcharts.SeriesLegendItemClickEventObject} event - * The event that occured. - */ - /** - * Information about the legend click event. - * - * @interface Highcharts.SeriesLegendItemClickEventObject - */ /** - * Related browser event. - * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent - * @type {Highcharts.PointerEvent} - */ /** - * Prevent the default action of toggle the visibility of the series. - * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault - * @type {Function} - */ /** - * Related series. - * @name Highcharts.SeriesLegendItemClickEventObject#target - * @type {Highcharts.Series} - */ /** - * Event type. - * @name Highcharts.SeriesLegendItemClickEventObject#type - * @type {"legendItemClick"} - */ - var addEvent = U.addEvent, - css = U.css, - defined = U.defined, - discardElement = U.discardElement, - find = U.find, - fireEvent = U.fireEvent, - format = U.format, - isNumber = U.isNumber, - merge = U.merge, - pick = U.pick, - relativeLength = U.relativeLength, - stableSort = U.stableSort, - syncTimeout = U.syncTimeout, - wrap = U.wrap; - var isFirefox = H.isFirefox, - marginNames = H.marginNames, - win = H.win; - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * The overview of the chart's series. The legend object is instanciated - * internally in the chart constructor, and is available from the `chart.legend` - * property. Each chart has only one legend. - * - * @class - * @name Highcharts.Legend - * - * @param {Highcharts.Chart} chart - * The chart instance. - * - * @param {Highcharts.LegendOptions} options - * Legend options. - */ - var Legend = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function Legend(chart, options) { - /* * - * - * Properties - * - * */ - this.allItems = []; - this.box = void 0; - this.contentGroup = void 0; - this.display = false; - this.group = void 0; - this.initialItemY = 0; - this.itemHeight = 0; - this.itemMarginBottom = 0; - this.itemMarginTop = 0; - this.itemX = 0; - this.itemY = 0; - this.lastItemY = 0; - this.lastLineHeight = 0; - this.legendHeight = 0; - this.legendWidth = 0; - this.maxItemWidth = 0; - this.maxLegendWidth = 0; - this.offsetWidth = 0; - this.options = {}; - this.padding = 0; - this.pages = []; - this.proximate = false; - this.scrollGroup = void 0; - this.symbolHeight = 0; - this.symbolWidth = 0; - this.titleHeight = 0; - this.totalItemWidth = 0; - this.widthOption = 0; - this.chart = chart; - this.init(chart, options); - } - /* * - * - * Functions - * - * */ - /** - * Initialize the legend. - * - * @private - * @function Highcharts.Legend#init - * - * @param {Highcharts.Chart} chart - * The chart instance. - * - * @param {Highcharts.LegendOptions} options - * Legend options. - */ - Legend.prototype.init = function (chart, options) { - /** - * Chart of this legend. - * - * @readonly - * @name Highcharts.Legend#chart - * @type {Highcharts.Chart} - */ - this.chart = chart; - this.setOptions(options); - if (options.enabled) { - // Render it - this.render(); - // move checkboxes - addEvent(this.chart, 'endResize', function () { - this.legend.positionCheckboxes(); - }); - if (this.proximate) { - this.unchartrender = addEvent(this.chart, 'render', function () { - this.legend.proximatePositions(); - this.legend.positionItems(); - }); - } - else if (this.unchartrender) { - this.unchartrender(); - } - } - }; - /** - * @private - * @function Highcharts.Legend#setOptions - * @param {Highcharts.LegendOptions} options - */ - Legend.prototype.setOptions = function (options) { - var padding = pick(options.padding, 8); - /** - * Legend options. - * - * @readonly - * @name Highcharts.Legend#options - * @type {Highcharts.LegendOptions} - */ - this.options = options; - if (!this.chart.styledMode) { - this.itemStyle = options.itemStyle; - this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle); - } - this.itemMarginTop = options.itemMarginTop || 0; - this.itemMarginBottom = options.itemMarginBottom || 0; - this.padding = padding; - this.initialItemY = padding - 5; // 5 is pixels above the text - this.symbolWidth = pick(options.symbolWidth, 16); - this.pages = []; - this.proximate = options.layout === 'proximate' && !this.chart.inverted; - this.baseline = void 0; // #12705: baseline has to be reset on every update - }; - /** - * Update the legend with new options. Equivalent to running `chart.update` - * with a legend configuration option. - * - * @sample highcharts/legend/legend-update/ - * Legend update - * - * @function Highcharts.Legend#update - * - * @param {Highcharts.LegendOptions} options - * Legend options. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart after the axis 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. Whether to redraw the chart. - * - * @fires Highcharts.Legends#event:afterUpdate - */ - Legend.prototype.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(); - } - fireEvent(this, 'afterUpdate'); - }; - /** - * Set the colors for the legend item. - * - * @private - * @function Highcharts.Legend#colorizeItem - * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item - * A Series or Point instance - * @param {boolean} [visible=false] - * Dimmed or colored - * - * @todo - * Make events official: Fires the event `afterColorizeItem`. - */ - Legend.prototype.colorizeItem = function (item, visible) { - item.legendGroup[visible ? 'removeClass' : 'addClass']('highcharts-legend-item-hidden'); - if (!this.chart.styledMode) { - 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) { - // #6769 - symbolAttr.stroke = symbolAttr.fill = hiddenColor; - } - } - legendSymbol.attr(symbolAttr); - } - } - fireEvent(this, 'afterColorizeItem', { item: item, visible: visible }); - }; - /** - * @private - * @function Highcharts.Legend#positionItems - */ - Legend.prototype.positionItems = function () { - // Now that the legend width and height are established, put the items - // in the final position - this.allItems.forEach(this.positionItem, this); - if (!this.chart.isResizing) { - this.positionCheckboxes(); - } - }; - /** - * Position the legend item. - * - * @private - * @function Highcharts.Legend#positionItem - * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item - * The item to position - */ - Legend.prototype.positionItem = function (item) { - var _this = this; - 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) { - var attribs = { - translateX: ltr ? - itemX : - legend.legendWidth - itemX - 2 * symbolPadding - 4, - translateY: itemY - }; - var complete = function () { - fireEvent(_this, 'afterPositionItem', { item: item }); - }; - if (defined(legendGroup.translateY)) { - legendGroup.animate(attribs, void 0, complete); - } - else { - legendGroup.attr(attribs); - complete(); - } - } - if (checkbox) { - checkbox.x = itemX; - checkbox.y = itemY; - } - }; - /** - * Destroy a single legend item, used internally on removing series items. - * - * @private - * @function Highcharts.Legend#destroyItem - * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item - * The item to remove - */ - Legend.prototype.destroyItem = function (item) { - var checkbox = item.checkbox; - // destroy SVG elements - ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'].forEach(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. - * - * @private - * @function Highcharts.Legend#destroy - */ - Legend.prototype.destroy = function () { - /** - * @private - * @param {string} key - * @return {void} - */ - function destroyItems(key) { - if (this[key]) { - this[key] = this[key].destroy(); - } - } - // Destroy items - this.getAllItems().forEach(function (item) { - ['legendItem', 'legendGroup'].forEach(destroyItems, item); - }); - // Destroy legend elements - [ - 'clipRect', - 'up', - 'down', - 'pager', - 'nav', - 'box', - 'title', - 'group' - ].forEach(destroyItems, this); - this.display = null; // Reset in .render on update. - }; - /** - * Position the checkboxes after the width is determined. - * - * @private - * @function Highcharts.Legend#positionCheckboxes - */ - Legend.prototype.positionCheckboxes = function () { - var alignAttr = this.group && this.group.alignAttr, - translateY, - clipHeight = this.clipHeight || this.legendHeight, - titleHeight = this.titleHeight; - if (alignAttr) { - translateY = alignAttr.translateY; - this.allItems.forEach(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: this.proximate || (top > translateY - 6 && - top < translateY + clipHeight - 6) ? - '' : - 'none' - }); - } - }, this); - } - }; - /** - * Render the legend title on top of the legend. - * - * @private - * @function Highcharts.Legend#renderTitle - */ - Legend.prototype.renderTitle = function () { - var options = this.options, - padding = this.padding, - titleOptions = options.title, - titleHeight = 0, - bBox; - if (titleOptions.text) { - if (!this.title) { - /** - * SVG element of the legend title. - * - * @readonly - * @name Highcharts.Legend#title - * @type {Highcharts.SVGElement} - */ - this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, options.useHTML, null, 'legend-title') - .attr({ zIndex: 1 }); - if (!this.chart.styledMode) { - this.title.css(titleOptions.style); - } - this.title.add(this.group); - } - // Set the max title width (#7253) - if (!titleOptions.width) { - this.title.css({ - width: this.maxLegendWidth + 'px' - }); - } - 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. - * - * @function Highcharts.Legend#setText - * @param {Highcharts.Point|Highcharts.Series} item - * The item for which to update the text in the legend. - */ - Legend.prototype.setText = function (item) { - var options = this.options; - item.legendItem.attr({ - text: options.labelFormat ? - format(options.labelFormat, item, this.chart) : - options.labelFormatter.call(item) - }); - }; - /** - * Render a single specific legend item. Called internally from the `render` - * function. - * - * @private - * @function Highcharts.Legend#renderItem - * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item - * The item to render. - */ - Legend.prototype.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, - itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, - ltr = !options.rtl, - bBox, - 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, - 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); - if (!chart.styledMode) { - // merge to prevent modifying original (#1021) - li.css(merge(item.visible ? - itemStyle : - itemHiddenStyle)); - } - li - .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) { - legend.fontMetrics = renderer.fontMetrics(chart.styledMode ? 12 : itemStyle.fontSize, li); - legend.baseline = - legend.fontMetrics.f + 3 + legend.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 && !item.checkbox && legend.createCheckboxForItem) { - legend.createCheckboxForItem(item); - } - // Colorize the items - legend.colorizeItem(item, item.visible); - // Take care of max width and text overflow (#6659) - if (chart.styledMode || !itemStyle.width) { - li.css({ - width: ((options.itemWidth || - legend.widthOption || - chart.spacingBox.width) - itemExtraWidth) + 'px' - }); - } - // Always update the text - legend.setText(item); - // calculate the positions for the next line - bBox = li.getBBox(); - item.itemWidth = item.checkboxOffset = - options.itemWidth || - item.legendItemWidth || - bBox.width + itemExtraWidth; - legend.maxItemWidth = Math.max(legend.maxItemWidth, item.itemWidth); - legend.totalItemWidth += item.itemWidth; - legend.itemHeight = item.itemHeight = Math.round(item.legendItemHeight || bBox.height || legend.symbolHeight); - }; - /** - * Get the position of the item in the layout. We now know the - * maxItemWidth from the previous loop. - * - * @private - * @function Highcharts.Legend#layoutItem - * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item - */ - Legend.prototype.layoutItem = function (item) { - var options = this.options, - padding = this.padding, - horizontal = options.layout === 'horizontal', - itemHeight = item.itemHeight, - itemMarginBottom = this.itemMarginBottom, - itemMarginTop = this.itemMarginTop, - itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, - maxLegendWidth = this.maxLegendWidth, - itemWidth = (options.alignColumns && - this.totalItemWidth > maxLegendWidth) ? - this.maxItemWidth : - item.itemWidth; - // If the item exceeds the width, start a new line - if (horizontal && - this.itemX - padding + itemWidth > maxLegendWidth) { - this.itemX = padding; - if (this.lastLineHeight) { // Not for the first line (#10167) - this.itemY += (itemMarginTop + - this.lastLineHeight + - itemMarginBottom); - } - this.lastLineHeight = 0; // reset for next line (#915, #3976) - } - // Set the edge positions - this.lastItemY = itemMarginTop + this.itemY + itemMarginBottom; - this.lastLineHeight = Math.max(// #915 - itemHeight, this.lastLineHeight); - // cache the position of the newly generated or reordered items - item._legendItemPos = [this.itemX, this.itemY]; - // advance - if (horizontal) { - this.itemX += itemWidth; - } - else { - this.itemY += - itemMarginTop + itemHeight + itemMarginBottom; - this.lastLineHeight = itemHeight; - } - // the width of the widest item - this.offsetWidth = this.widthOption || Math.max((horizontal ? this.itemX - padding - (item.checkbox ? - // decrease by itemDistance only when no checkbox #4853 - 0 : - itemDistance) : itemWidth) + padding, this.offsetWidth); - }; - /** - * Get all items, which is one item per series for most series and one - * item per point for pie series and its derivatives. Fires the event - * `afterGetAllItems`. - * - * @private - * @function Highcharts.Legend#getAllItems - * @return {Array<(Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series)>} - * The current items in the legend. - * @fires Highcharts.Legend#event:afterGetAllItems - */ - Legend.prototype.getAllItems = function () { - var allItems = []; - this.chart.series.forEach(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) ? void 0 : false, true)) { - // Use points or series for the legend item depending on - // legendType - allItems = allItems.concat(series.legendItems || - (seriesOptions.legendType === 'point' ? - series.data : - series)); - } - }); - fireEvent(this, 'afterGetAllItems', { allItems: allItems }); - return allItems; - }; - /** - * Get a short, three letter string reflecting the alignment and layout. - * - * @private - * @function Highcharts.Legend#getAlignment - * @return {string} - * The alignment, empty string if floating - */ - Legend.prototype.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) - if (this.proximate) { - return options.align.charAt(0) + 'tv'; - } - 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 - * @function Highcharts.Legend#adjustMargins - * @param {Array<number>} margin - * @param {Array<number>} spacing - */ - Legend.prototype.adjustMargins = function (margin, spacing) { - var chart = this.chart, - options = this.options, - alignment = this.getAlignment(); - if (alignment) { - ([ - /(lth|ct|rth)/, - /(rtv|rm|rbv)/, - /(rbh|cb|lbh)/, - /(lbv|lm|ltv)/ - ]).forEach(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] + - (chart.titleOffset[side] || 0))); - } - }); - } - }; - /** - * @private - * @function Highcharts.Legend#proximatePositions - */ - Legend.prototype.proximatePositions = function () { - var chart = this.chart, - boxes = [], - alignLeft = this.options.align === 'left'; - this.allItems.forEach(function (item) { - var lastPoint, - height, - useFirstPoint = alignLeft, - target, - top; - if (item.yAxis) { - if (item.xAxis.options.reversed) { - useFirstPoint = !useFirstPoint; - } - if (item.points) { - lastPoint = find(useFirstPoint ? - item.points : - item.points.slice(0).reverse(), function (item) { - return isNumber(item.plotY); - }); - } - height = this.itemMarginTop + - item.legendItem.getBBox().height + - this.itemMarginBottom; - top = item.yAxis.top - chart.plotTop; - if (item.visible) { - target = lastPoint ? - lastPoint.plotY : - item.yAxis.height; - target += top - 0.3 * height; - } - else { - target = top + item.yAxis.height; - } - boxes.push({ - target: target, - size: height, - item: item - }); - } - }, this); - H.distribute(boxes, chart.plotHeight); - boxes.forEach(function (box) { - box.item._legendItemPos[1] = - chart.plotTop - chart.spacing[0] + box.pos; - }); - }; - /** - * 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. - * - * @private - * @function Highcharts.Legend#render - */ - Legend.prototype.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, - allowedWidth; - legend.itemX = padding; - legend.itemY = legend.initialItemY; - legend.offsetWidth = 0; - legend.lastItemY = 0; - legend.widthOption = relativeLength(options.width, chart.spacingBox.width - padding); - // Compute how wide the legend is allowed to be - allowedWidth = - chart.spacingBox.width - 2 * padding - options.x; - if (['rm', 'lm'].indexOf(legend.getAlignment().substring(0, 2)) > -1) { - allowedWidth /= 2; - } - legend.maxLegendWidth = legend.widthOption || allowedWidth; - if (!legendGroup) { - /** - * SVG group of the legend. - * - * @readonly - * @name Highcharts.Legend#group - * @type {Highcharts.SVGElement} - */ - 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(); - } - /** - * All items for the legend, which is an array of series for most series - * and an array of points for pie series and its derivatives. - * - * @readonly - * @name Highcharts.Legend#allItems - * @type {Array<(Highcharts.Point|Highcharts.Series)>} - */ - legend.allItems = allItems; - legend.display = display = !!allItems.length; - // Render the items. First we run a loop to set the text and properties - // and read all the bounding boxes. The next loop computes the item - // positions based on the bounding boxes. - legend.lastLineHeight = 0; - legend.maxItemWidth = 0; - legend.totalItemWidth = 0; - legend.itemHeight = 0; - allItems.forEach(legend.renderItem, legend); - allItems.forEach(legend.layoutItem, legend); - // Get the box - legendWidth = (legend.widthOption || legend.offsetWidth) + padding; - legendHeight = legend.lastItemY + legend.lastLineHeight + - legend.titleHeight; - legendHeight = legend.handleOverflow(legendHeight); - legendHeight += padding; - // Draw the border and/or background - if (!box) { - /** - * SVG element of the legend box. - * - * @readonly - * @name Highcharts.Legend#box - * @type {Highcharts.SVGElement} - */ - legend.box = box = renderer.rect() - .addClass('highcharts-legend-box') - .attr({ - r: options.borderRadius - }) - .add(legendGroup); - box.isNew = true; - } - // Presentational - if (!chart.styledMode) { - 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({}, { - x: 0, - y: 0, - width: legendWidth, - height: legendHeight - }, box.strokeWidth())); - box.isNew = false; - } - // hide the border if no items - box[display ? 'show' : 'hide'](); - // Open for responsiveness - if (chart.styledMode && legendGroup.getStyle('display') === 'none') { - legendWidth = legendHeight = 0; - } - legend.legendWidth = legendWidth; - legend.legendHeight = legendHeight; - if (display) { - legend.align(); - } - if (!this.proximate) { - this.positionItems(); - } - fireEvent(this, 'afterRender'); - }; - /** - * Align the legend to chart's box. - * - * @private - * @function Highcharts.align - * @param {Highcharts.BBoxObject} alignTo - * @return {void} - */ - Legend.prototype.align = function (alignTo) { - if (alignTo === void 0) { alignTo = this.chart.spacingBox; } - var chart = this.chart, - options = this.options; - // If aligning to the top and the layout is horizontal, adjust for - // the title (#7428) - var y = alignTo.y; - if (/(lth|ct|rth)/.test(this.getAlignment()) && - chart.titleOffset[0] > 0) { - y += chart.titleOffset[0]; - } - else if (/(lbh|cb|rbh)/.test(this.getAlignment()) && - chart.titleOffset[2] > 0) { - y -= chart.titleOffset[2]; - } - if (y !== alignTo.y) { - alignTo = merge(alignTo, { y: y }); - } - this.group.align(merge(options, { - width: this.legendWidth, - height: this.legendHeight, - verticalAlign: this.proximate ? 'top' : options.verticalAlign - }), true, alignTo); - }; - /** - * Set up the overflow handling by adding navigation with up and down arrows - * below the legend. - * - * @private - * @function Highcharts.Legend#handleOverflow - * @param {number} legendHeight - * @return {number} - */ - Legend.prototype.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'; - } - }, addTracker = function (key) { - legend[key] = renderer - .circle(0, 0, arrowSize * 1.3) - .translate(arrowSize / 2, arrowSize / 2) - .add(nav); - if (!chart.styledMode) { - legend[key].attr('fill', 'rgba(0,0,0,0.0001)'); - } - return legend[key]; - }; - // 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) - allItems.forEach(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 && - y !== lastY // #2617 - ) { - 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) - .add(nav); - addTracker('upTracker') - .on('click', function () { - legend.scroll(-1, animation); - }); - this.pager = renderer.text('', 15, 10) - .addClass('highcharts-legend-navigation'); - if (!chart.styledMode) { - this.pager.css(navOptions.style); - } - this.pager.add(nav); - this.down = renderer - .symbol('triangle-down', 0, 0, arrowSize, arrowSize) - .add(nav); - addTracker('downTracker') - .on('click', function () { - legend.scroll(1, animation); - }); - } - // 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. - * - * @private - * @function Highcharts.Legend#scroll - * - * @param {number} scrollBy - * The number of pages to scroll. - * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] - * Whether and how to apply animation. - * - * @return {void} - */ - Legend.prototype.scroll = function (scrollBy, animation) { - var _this = this; - var chart = this.chart, - 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 (typeof animation !== 'undefined') { - setAnimation(animation, chart); - } - this.nav.attr({ - translateX: padding, - translateY: clipHeight + this.padding + 7 + this.titleHeight, - visibility: 'visible' - }); - [this.up, this.upTracker].forEach(function (elem) { - elem.attr({ - 'class': currentPage === 1 ? - 'highcharts-legend-nav-inactive' : - 'highcharts-legend-nav-active' - }); - }); - pager.attr({ - text: currentPage + '/' + pageCount - }); - [this.down, this.downTracker].forEach(function (elem) { - elem.attr({ - // adjust to text width - x: 18 + this.pager.getBBox().width, - 'class': currentPage === pageCount ? - 'highcharts-legend-nav-inactive' : - 'highcharts-legend-nav-active' - }); - }, this); - if (!chart.styledMode) { - this.up - .attr({ - fill: currentPage === 1 ? - navOptions.inactiveColor : - navOptions.activeColor - }); - this.upTracker - .css({ - cursor: currentPage === 1 ? 'default' : 'pointer' - }); - this.down - .attr({ - fill: currentPage === pageCount ? - navOptions.inactiveColor : - navOptions.activeColor - }); - this.downTracker - .css({ - cursor: currentPage === pageCount ? - 'default' : - 'pointer' - }); - } - this.scrollOffset = -pages[currentPage - 1] + this.initialItemY; - this.scrollGroup.animate({ - translateY: this.scrollOffset - }); - this.currentPage = currentPage; - this.positionCheckboxes(); - // Fire event after scroll animation is complete - var animOptions = animObject(pick(animation, - chart.renderer.globalAnimation, - true)); - syncTimeout(function () { - fireEvent(_this, 'afterScroll', { currentPage: currentPage }); - }, animOptions.duration); - } - }; - return Legend; - }()); - // 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 && win.navigator.userAgent) || - isFirefox) { - wrap(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 - if (!legend.bubbleLegend) { - setTimeout(runPositionItem); - } - }); - } - H.Legend = Legend; - - return H.Legend; - }); - _registerModule(_modules, 'Core/Series/Point.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (A, H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var animObject = A.animObject; - var defined = U.defined, - erase = U.erase, - extend = U.extend, - fireEvent = U.fireEvent, - format = U.format, - getNestedProperty = U.getNestedProperty, - isArray = U.isArray, - isNumber = U.isNumber, - isObject = U.isObject, - syncTimeout = U.syncTimeout, - pick = U.pick, - removeEvent = U.removeEvent, - uniqueKey = U.uniqueKey; - /** - * Function callback when a series point is clicked. Return false to cancel the - * action. - * - * @callback Highcharts.PointClickCallbackFunction - * - * @param {Highcharts.Point} this - * The point where the event occured. - * - * @param {Highcharts.PointClickEventObject} event - * Event arguments. - */ - /** - * Common information for a click event on a series point. - * - * @interface Highcharts.PointClickEventObject - * @extends Highcharts.PointerEventObject - */ /** - * Clicked point. - * @name Highcharts.PointClickEventObject#point - * @type {Highcharts.Point} - */ - /** - * Configuration hash for the data label and tooltip formatters. - * - * @interface Highcharts.PointLabelObject - */ /** - * The point's current color. - * @name Highcharts.PointLabelObject#color - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} - */ /** - * 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 Highcharts.PointLabelObject#colorIndex - * @type {number} - */ /** - * The name of the related point. - * @name Highcharts.PointLabelObject#key - * @type {string|undefined} - */ /** - * The percentage for related points in a stacked series or pies. - * @name Highcharts.PointLabelObject#percentage - * @type {number} - */ /** - * The related point. The point name, if defined, is available through - * `this.point.name`. - * @name Highcharts.PointLabelObject#point - * @type {Highcharts.Point} - */ /** - * The related series. The series name is available through `this.series.name`. - * @name Highcharts.PointLabelObject#series - * @type {Highcharts.Series} - */ /** - * The total of values in either a stack for stacked series, or a pie in a pie - * series. - * @name Highcharts.PointLabelObject#total - * @type {number|undefined} - */ /** - * For categorized axes this property holds the category name for the point. For - * other axes it holds the X value. - * @name Highcharts.PointLabelObject#x - * @type {number|string|undefined} - */ /** - * The y value of the point. - * @name Highcharts.PointLabelObject#y - * @type {number|undefined} - */ - /** - * Gets fired when the mouse leaves the area close to the point. - * - * @callback Highcharts.PointMouseOutCallbackFunction - * - * @param {Highcharts.Point} this - * Point where the event occured. - * - * @param {global.PointerEvent} event - * Event that occured. - */ - /** - * Gets fired when the mouse enters the area close to the point. - * - * @callback Highcharts.PointMouseOverCallbackFunction - * - * @param {Highcharts.Point} this - * Point where the event occured. - * - * @param {global.Event} event - * Event that occured. - */ - /** - * The generic point options for all series. - * - * In TypeScript you have to extend `PointOptionsObject` with an additional - * declaration to allow custom data options: - * - * ``` - * declare interface PointOptionsObject { - * customProperty: string; - * } - * ``` - * - * @interface Highcharts.PointOptionsObject - */ - /** - * Possible option types for a data point. Use `null` to indicate a gap. - * - * @typedef {number|string|Highcharts.PointOptionsObject|Array<(number|string|null)>|null} Highcharts.PointOptionsType - */ - /** - * Gets fired when the point is removed using the `.remove()` method. - * - * @callback Highcharts.PointRemoveCallbackFunction - * - * @param {Highcharts.Point} this - * Point where the event occured. - * - * @param {global.Event} event - * Event that occured. - */ - /** - * Possible key values for the point state options. - * - * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.PointStateValue - */ - /** - * Gets fired when the point is updated programmatically through the `.update()` - * method. - * - * @callback Highcharts.PointUpdateCallbackFunction - * - * @param {Highcharts.Point} this - * Point where the event occured. - * - * @param {Highcharts.PointUpdateEventObject} event - * Event that occured. - */ - /** - * Information about the update event. - * - * @interface Highcharts.PointUpdateEventObject - * @extends global.Event - */ /** - * Options data of the update event. - * @name Highcharts.PointUpdateEventObject#options - * @type {Highcharts.PointOptionsType} - */ - ''; // detach doclet above - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * 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 instantiate points are through {@link - * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}. - * - * @class - * @name Highcharts.Point - */ - var Point = /** @class */ (function () { - function Point() { - /* * - * - * Properties - * - * */ - /** - * For categorized axes this property holds the category name for the - * point. For other axes it holds the X value. - * - * @name Highcharts.Point#category - * @type {string} - */ - this.category = void 0; - /** - * 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 Highcharts.Point#colorIndex - * @type {number} - */ - this.colorIndex = void 0; - this.formatPrefix = 'point'; - this.id = void 0; - this.isNull = false; - /** - * 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 Highcharts.Point#name - * @type {string} - */ - this.name = void 0; - /** - * The point's options as applied in the initial configuration, or - * extended through `Point.update`. - * - * In TypeScript you have to extend `PointOptionsObject` via an - * additional interface to allow custom data options: - * - * ``` - * declare interface PointOptionsObject { - * customProperty: string; - * } - * ``` - * - * @name Highcharts.Point#options - * @type {Highcharts.PointOptionsObject} - */ - this.options = void 0; - /** - * The percentage for points in a stacked series or pies. - * - * @name Highcharts.Point#percentage - * @type {number|undefined} - */ - this.percentage = void 0; - this.selected = false; - /** - * The series object associated with the point. - * - * @name Highcharts.Point#series - * @type {Highcharts.Series} - */ - this.series = void 0; - /** - * The total of values in either a stack for stacked series, or a pie in a - * pie series. - * - * @name Highcharts.Point#total - * @type {number|undefined} - */ - this.total = void 0; - /** - * For certain series types, like pie charts, where individual points can - * be shown or hidden. - * - * @name Highcharts.Point#visible - * @type {boolean} - * @default true - */ - this.visible = true; - this.x = void 0; - } - /* * - * - * Functions - * - * */ - /** - * Animate SVG elements associated with the point. - * - * @private - * @function Highcharts.Point#animateBeforeDestroy - */ - Point.prototype.animateBeforeDestroy = function () { - var point = this, - animateParams = { x: point.startXPos, - opacity: 0 }, - isDataLabel, - graphicalProps = point.getGraphicalProps(); - graphicalProps.singular.forEach(function (prop) { - isDataLabel = prop === 'dataLabel'; - point[prop] = point[prop].animate(isDataLabel ? { - x: point[prop].startXPos, - y: point[prop].startYPos, - opacity: 0 - } : animateParams); - }); - graphicalProps.plural.forEach(function (plural) { - point[plural].forEach(function (item) { - if (item.element) { - item.animate(extend({ x: point.startXPos }, (item.startYPos ? { - x: item.startXPos, - y: item.startYPos - } : {}))); - } - }); - }); - }; - /** - * Apply the options containing the x and y data and possible some extra - * properties. Called on point init or from point.update. - * - * @private - * @function Highcharts.Point#applyOptions - * - * @param {Highcharts.PointOptionsType} options - * The point options as defined in series.data. - * - * @param {number} [x] - * Optionally, the x value. - * - * @return {Highcharts.Point} - * The Point instance. - */ - Point.prototype.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; - } - if (options.dataLabels) { - delete point.dataLabels; - } - /** - * The y value of the point. - * @name Highcharts.Point#y - * @type {number|undefined} - */ - // For higher dimension series types. For instance, for ranges, point.y - // is mapped to point.low. - if (pointValKey) { - point.y = Point.prototype.getNestedProperty.call(point, pointValKey); - } - point.isNull = pick(point.isValid && !point.isValid(), point.x === null || !isNumber(point.y)); // #3571, check for NaN - point.formatPrefix = point.isNull ? 'null' : 'point'; // #9233, #10874 - // The point is initially selected by options (#5777) - if (point.selected) { - point.state = 'select'; - } - /** - * The x value of the point. - * @name Highcharts.Point#x - * @type {number} - */ - // 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 && - typeof x === 'undefined' && - series.xAxis && - series.xAxis.hasNames) { - point.x = series.xAxis.nameToX(point); - } - if (typeof point.x === 'undefined' && series) { - if (typeof x === 'undefined') { - point.x = series.autoIncrement(point); - } - else { - point.x = x; - } - } - return point; - }; - /** - * Destroy a point to clear memory. Its reference still stays in - * `series.data`. - * - * @private - * @function Highcharts.Point#destroy - */ - Point.prototype.destroy = function () { - var point = this, - series = point.series, - chart = series.chart, - dataSorting = series.options.dataSorting, - hoverPoints = chart.hoverPoints, - globalAnimation = point.series.chart.renderer.globalAnimation, - animation = animObject(globalAnimation), - prop; - /** - * Allow to call after animation. - * @private - */ - function destroyPoint() { - // Remove all events and elements - if (point.graphic || point.dataLabel || point.dataLabels) { - removeEvent(point); - point.destroyElements(); - } - for (prop in point) { // eslint-disable-line guard-for-in - point[prop] = null; - } - } - if (point.legendItem) { // pies have legend items - chart.legend.destroyItem(point); - } - if (hoverPoints) { - point.setState(); - erase(hoverPoints, point); - if (!hoverPoints.length) { - chart.hoverPoints = null; - } - } - if (point === chart.hoverPoint) { - point.onMouseOut(); - } - // Remove properties after animation - if (!dataSorting || !dataSorting.enabled) { - destroyPoint(); - } - else { - this.animateBeforeDestroy(); - syncTimeout(destroyPoint, animation.duration); - } - chart.pointCount--; - }; - /** - * Destroy SVG elements associated with the point. - * - * @private - * @function Highcharts.Point#destroyElements - * @param {Highcharts.Dictionary<number>} [kinds] - */ - Point.prototype.destroyElements = function (kinds) { - var point = this, - props = point.getGraphicalProps(kinds); - props.singular.forEach(function (prop) { - point[prop] = point[prop].destroy(); - }); - props.plural.forEach(function (plural) { - point[plural].forEach(function (item) { - if (item.element) { - item.destroy(); - } - }); - delete point[plural]; - }); - }; - /** - * Fire an event on the Point object. - * - * @private - * @function Highcharts.Point#firePointEvent - * - * @param {string} eventType - * Type of the event. - * - * @param {Highcharts.Dictionary<any>|Event} [eventArgs] - * Additional event arguments. - * - * @param {Highcharts.EventCallbackFunction<Highcharts.Point>|Function} [defaultFunction] - * Default event handler. - * - * @fires Highcharts.Point#event:* - */ - Point.prototype.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])) { - point.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) { // #2911 - point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); - } - }; - } - fireEvent(point, eventType, eventArgs, defaultFunction); - }; - /** - * Get the CSS class names for individual points. Used internally where the - * returned value is set on every point. - * - * @function Highcharts.Point#getClassName - * - * @return {string} - * The class names. - */ - Point.prototype.getClassName = function () { - var point = this; - return 'highcharts-point' + - (point.selected ? ' highcharts-point-select' : '') + - (point.negative ? ' highcharts-negative' : '') + - (point.isNull ? ' highcharts-null-point' : '') + - (typeof point.colorIndex !== 'undefined' ? - ' highcharts-color-' + point.colorIndex : '') + - (point.options.className ? ' ' + point.options.className : '') + - (point.zone && point.zone.className ? ' ' + - point.zone.className.replace('highcharts-negative', '') : ''); - }; - /** - * Get props of all existing graphical point elements. - * - * @private - * @function Highcharts.Point#getGraphicalProps - * @param {Highcharts.Dictionary<number>} [kinds] - * @return {Highcharts.PointGraphicalProps} - */ - Point.prototype.getGraphicalProps = function (kinds) { - var point = this, - props = [], - prop, - i, - graphicalProps = { singular: [], - plural: [] }; - kinds = kinds || { graphic: 1, dataLabel: 1 }; - if (kinds.graphic) { - props.push('graphic', 'shadowGroup'); - } - if (kinds.dataLabel) { - props.push('dataLabel', 'dataLabelUpper', 'connector'); - } - i = props.length; - while (i--) { - prop = props[i]; - if (point[prop]) { - graphicalProps.singular.push(prop); - } - } - ['dataLabel', 'connector'].forEach(function (prop) { - var plural = prop + 's'; - if (kinds[prop] && point[plural]) { - graphicalProps.plural.push(plural); - } - }); - return graphicalProps; - }; - /** - * Return the configuration hash needed for the data label and tooltip - * formatters. - * - * @function Highcharts.Point#getLabelConfig - * - * @return {Highcharts.PointLabelObject} - * Abstract object used in formatters and formats. - */ - Point.prototype.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 - }; - }; - /** - * Returns the value of the point property for a given value. - * @private - */ - Point.prototype.getNestedProperty = function (key) { - if (!key) { - return; - } - if (key.indexOf('custom.') === 0) { - return getNestedProperty(key, this.options); - } - return this[key]; - }; - /** - * In a series with `zones`, return the zone that the point belongs to. - * - * @function Highcharts.Point#getZone - * - * @return {Highcharts.SeriesZonesOptionsObject} - * The zone item. - */ - Point.prototype.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]; - } - // For resetting or reusing the point (#8100) - if (!this.nonZonedColor) { - this.nonZonedColor = this.color; - } - if (zone && zone.color && !this.options.color) { - this.color = zone.color; - } - else { - this.color = this.nonZonedColor; - } - return zone; - }; - /** - * Utility to check if point has new shape type. Used in column series and - * all others that are based on column series. - * - * @return boolean|undefined - */ - Point.prototype.hasNewShapeType = function () { - var point = this; - var oldShapeType = point.graphic && - (point.graphic.symbolName || point.graphic.element.nodeName); - return oldShapeType !== this.shapeType; - }; - /** - * Initialize the point. Called internally based on the `series.data` - * option. - * - * @function Highcharts.Point#init - * - * @param {Highcharts.Series} series - * The series object containing this point. - * - * @param {Highcharts.PointOptionsType} options - * The data in either number, array or object format. - * - * @param {number} [x] - * Optionally, the X value of the point. - * - * @return {Highcharts.Point} - * The Point instance. - * - * @fires Highcharts.Point#event:afterInit - */ - Point.prototype.init = function (series, options, x) { - this.series = series; - this.applyOptions(options, x); - // Add a unique ID to the point if none is assigned - this.id = defined(this.id) ? this.id : uniqueKey(); - this.resolveColor(); - series.chart.pointCount++; - fireEvent(this, 'afterInit'); - return this; - }; - /** - * Transform number or array configs into objects. Also called for object - * configs. 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 }`. - * - * @function Highcharts.Point#optionsToObject - * - * @param {Highcharts.PointOptionsType} options - * The input option. - * - * @return {Highcharts.Dictionary<*>} - * Transformed options. - */ - Point.prototype.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) { - // Skip undefined positions for keys - if (!keys || typeof options[i] !== 'undefined') { - if (pointArrayMap[j].indexOf('.') > 0) { - // Handle nested keys, e.g. ['color.pattern.image'] - // Avoid function call unless necessary. - Point.prototype.setNestedProperty(ret, options[i], pointArrayMap[j]); - } - else { - 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; - }; - /** - * @private - * @function Highcharts.Point#resolveColor - * @return {void} - */ - Point.prototype.resolveColor = function () { - var series = this.series, - colors, - optionsChart = series.chart.options.chart, - colorCount = optionsChart.colorCount, - styledMode = series.chart.styledMode, - colorIndex; - // remove points nonZonedColor for later recalculation - delete this.nonZonedColor; - /** - * The point's current color. - * - * @name Highcharts.Point#color - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} - */ - if (!styledMode && !this.options.color) { - this.color = series.color; // #3445 - } - if (series.options.colorByPoint) { - if (!styledMode) { - colors = series.options.colors || series.chart.options.colors; - this.color = this.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; - } - this.colorIndex = pick(this.colorIndex, colorIndex); - }; - /** - * Set a value in an object, on the property defined by key. The key - * supports nested properties using dot notation. The function modifies the - * input object and does not make a copy. - * - * @function Highcharts.Point#setNestedProperty<T> - * - * @param {T} object - * The object to set the value on. - * - * @param {*} value - * The value to set. - * - * @param {string} key - * Key to the property to set. - * - * @return {T} - * The modified object. - */ - Point.prototype.setNestedProperty = function (object, value, key) { - var nestedKeys = key.split('.'); - nestedKeys.reduce(function (result, key, i, arr) { - var isLastKey = arr.length - 1 === i; - result[key] = (isLastKey ? - value : - isObject(result[key], true) ? - result[key] : - {}); - return result[key]; - }, object); - return object; - }; - /** - * Extendable method for formatting each point's tooltip line. - * - * @function Highcharts.Point#tooltipFormatter - * - * @param {string} pointFormat - * The point format. - * - * @return {string} - * A string to be concatenated in to the common tooltip text. - */ - Point.prototype.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 || ''; - // Replace default point style with class name - if (series.chart.styledMode) { - pointFormat = - series.chart.tooltip.styledModeFormat(pointFormat); - } - // Loop over the point array map and replace unformatted values with - // sprintf formatting markup - (series.pointArrayMap || ['y']).forEach(function (key) { - key = '{point.' + key; // without the closing bracket - if (valuePrefix || valueSuffix) { - pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), valuePrefix + key + '}' + valueSuffix); - } - pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), key + ':,.' + valueDecimals + 'f}'); - }); - return format(pointFormat, { - point: this, - series: this.series - }, series.chart); - }; - return Point; - }()); - H.Point = Point; - - return Point; - }); - _registerModule(_modules, 'Core/Series/Series.js', [_modules['Core/Globals.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js']], function (H, Point, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var error = U.error, - extendClass = U.extendClass, - fireEvent = U.fireEvent, - getOptions = U.getOptions, - isObject = U.isObject, - merge = U.merge, - objectEach = U.objectEach; - /** - * @class - * @name Highcharts.Series - */ - var BaseSeries = /** @class */ (function () { - /* * - * - * Constructor - * - * */ - function BaseSeries(chart, options) { - var mergedOptions = merge(BaseSeries.defaultOptions, options); - this.chart = chart; - this._i = chart.series.length; - chart.series.push(this); - this.options = mergedOptions; - this.userOptions = merge(options); - } - /* * - * - * Static Functions - * - * */ - BaseSeries.addSeries = function (seriesName, seriesType) { - BaseSeries.seriesTypes[seriesName] = seriesType; - }; - BaseSeries.cleanRecursively = function (toClean, reference) { - var clean = {}; - objectEach(toClean, function (_val, key) { - var ob; - // Dive into objects (except DOM nodes) - if (isObject(toClean[key], true) && - !toClean.nodeType && // #10044 - reference[key]) { - ob = BaseSeries.cleanRecursively(toClean[key], reference[key]); - if (Object.keys(ob).length) { - clean[key] = ob; - } - // Arrays, primitives and DOM nodes are copied directly - } - else if (isObject(toClean[key]) || - toClean[key] !== reference[key]) { - clean[key] = toClean[key]; - } - }); - return clean; - }; - // eslint-disable-next-line valid-jsdoc - /** - * Internal function to initialize an individual series. - * @private - */ - BaseSeries.getSeries = function (chart, options) { - if (options === void 0) { options = {}; } - var optionsChart = chart.options.chart, - type = (options.type || - optionsChart.type || - optionsChart.defaultSeriesType || - ''), - Series = BaseSeries.seriesTypes[type]; - // No such series type - if (!Series) { - error(17, true, chart, { missingModuleFor: type }); - } - return new Series(chart, options); - }; - /** - * Factory to create new series prototypes. - * - * @function Highcharts.seriesType - * - * @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 {Highcharts.SeriesOptionsType|Highcharts.Dictionary<*>} options - * The additional default options that are merged with the parent's options. - * - * @param {Highcharts.Dictionary<*>} [props] - * The properties (functions and primitives) to set on the new prototype. - * - * @param {Highcharts.Dictionary<*>} [pointProps] - * Members for a series-specific extension of the {@link Point} prototype if - * needed. - * - * @return {Highcharts.Series} - * The newly created prototype as extended from {@link Series} or its - * derivatives. - */ - // docs: add to API + extending Highcharts - BaseSeries.seriesType = function (type, parent, options, seriesProto, pointProto) { - var defaultOptions = getOptions().plotOptions || {}, - seriesTypes = BaseSeries.seriesTypes; - parent = parent || ''; - // Merge the options - defaultOptions[type] = merge(defaultOptions[parent], options); - // Create the class - BaseSeries.addSeries(type, extendClass(seriesTypes[parent] || function () { }, seriesProto)); - seriesTypes[type].prototype.type = type; - // Create the point class if needed - if (pointProto) { - seriesTypes[type].prototype.pointClass = - extendClass(Point, pointProto); - } - return seriesTypes[type]; - }; - BaseSeries.prototype.update = function (newOptions, redraw) { - if (redraw === void 0) { redraw = true; } - var series = this; - newOptions = BaseSeries.cleanRecursively(newOptions, this.userOptions); - var newType = newOptions.type; - if (typeof newType !== 'undefined' && - newType !== series.type) { - series = BaseSeries.getSeries(series.chart, newOptions); - } - fireEvent(series, 'update', { newOptions: newOptions }); - series.userOptions = merge(newOptions); - fireEvent(series, 'afterUpdate', { newOptions: newOptions }); - if (redraw) { - series.chart.redraw(); - } - return series; - }; - /* * - * - * Static Properties - * - * */ - BaseSeries.defaultOptions = { - type: 'base' - }; - BaseSeries.seriesTypes = {}; - return BaseSeries; - }()); - BaseSeries.prototype.pointClass = Point; - // backwards compatibility - H.seriesType = BaseSeries.seriesType; - H.seriesTypes = BaseSeries.seriesTypes; - /* * - * - * Export - * - * */ - - return BaseSeries; - }); - _registerModule(_modules, 'Core/Chart/Chart.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Axis/Axis.js'], _modules['Core/Series/Series.js'], _modules['Core/Globals.js'], _modules['Core/Legend.js'], _modules['Core/MSPointer.js'], _modules['Core/Options.js'], _modules['Core/Pointer.js'], _modules['Core/Time.js'], _modules['Core/Utilities.js']], function (A, Axis, BaseSeries, H, Legend, MSPointer, O, Pointer, Time, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var animate = A.animate, - animObject = A.animObject, - setAnimation = A.setAnimation; - var charts = H.charts, - doc = H.doc, - win = H.win; - var defaultOptions = O.defaultOptions; - var addEvent = U.addEvent, - attr = U.attr, - createElement = U.createElement, - css = U.css, - defined = U.defined, - discardElement = U.discardElement, - erase = U.erase, - error = U.error, - extend = U.extend, - find = U.find, - fireEvent = U.fireEvent, - getStyle = U.getStyle, - isArray = U.isArray, - isFunction = U.isFunction, - isNumber = U.isNumber, - isObject = U.isObject, - isString = U.isString, - merge = U.merge, - numberFormat = U.numberFormat, - objectEach = U.objectEach, - pick = U.pick, - pInt = U.pInt, - relativeLength = U.relativeLength, - removeEvent = U.removeEvent, - splat = U.splat, - syncTimeout = U.syncTimeout, - uniqueKey = U.uniqueKey; - /** - * Callback for chart constructors. - * - * @callback Highcharts.ChartCallbackFunction - * - * @param {Highcharts.Chart} chart - * Created chart. - */ - /** - * Format a number and return a string based on input settings. - * - * @callback Highcharts.NumberFormatterCallbackFunction - * - * @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. - * - * @return {string} The formatted number. - */ - /** - * The chart title. The title has an `update` method that allows modifying the - * options directly or indirectly via `chart.update`. - * - * @interface Highcharts.TitleObject - * @extends Highcharts.SVGElement - */ /** - * Modify options for the title. - * - * @function Highcharts.TitleObject#update - * - * @param {Highcharts.TitleOptions} titleOptions - * Options to modify. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart after the title 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. - */ - /** - * The chart subtitle. The subtitle has an `update` method that - * allows modifying the options directly or indirectly via - * `chart.update`. - * - * @interface Highcharts.SubtitleObject - * @extends Highcharts.SVGElement - */ /** - * Modify options for the subtitle. - * - * @function Highcharts.SubtitleObject#update - * - * @param {Highcharts.SubtitleOptions} subtitleOptions - * Options to modify. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart after the subtitle 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. - */ - /** - * The chart caption. The caption has an `update` method that - * allows modifying the options directly or indirectly via - * `chart.update`. - * - * @interface Highcharts.CaptionObject - * @extends Highcharts.SVGElement - */ /** - * Modify options for the caption. - * - * @function Highcharts.CaptionObject#update - * - * @param {Highcharts.CaptionOptions} captionOptions - * Options to modify. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart after the caption 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. - */ - var marginNames = H.marginNames; - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** - * The Chart class. The recommended constructor is {@link Highcharts#chart}. - * - * @example - * var chart = Highcharts.chart('container', { - * title: { - * text: 'My chart' - * }, - * series: [{ - * data: [1, 3, 2, 4] - * }] - * }) - * - * @class - * @name Highcharts.Chart - * - * @param {string|Highcharts.HTMLDOMElement} [renderTo] - * The DOM element to render to, or its id. - * - * @param {Highcharts.Options} options - * The chart options structure. - * - * @param {Highcharts.ChartCallbackFunction} [callback] - * Function to run when the chart has loaded and and all external images - * are loaded. Defining a - * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) - * handler is equivalent. - */ - var Chart = /** @class */ (function () { - function Chart(a, b, c) { - this.axes = void 0; - this.axisOffset = void 0; - this.bounds = void 0; - this.chartHeight = void 0; - this.chartWidth = void 0; - this.clipBox = void 0; - this.colorCounter = void 0; - this.container = void 0; - this.index = void 0; - this.isResizing = void 0; - this.labelCollectors = void 0; - this.legend = void 0; - this.margin = void 0; - this.numberFormatter = void 0; - this.options = void 0; - this.plotBox = void 0; - this.plotHeight = void 0; - this.plotLeft = void 0; - this.plotTop = void 0; - this.plotWidth = void 0; - this.pointCount = void 0; - this.pointer = void 0; - this.renderer = void 0; - this.renderTo = void 0; - this.series = void 0; - this.spacing = void 0; - this.spacingBox = void 0; - this.symbolCounter = void 0; - this.time = void 0; - this.titleOffset = void 0; - this.userOptions = void 0; - this.xAxis = void 0; - this.yAxis = void 0; - this.getArgs(a, b, c); - } - /* * - * - * Functions - * - * */ - /** - * Handle the arguments passed to the constructor. - * - * @private - * @function Highcharts.Chart#getArgs - * - * @param {...Array<*>} arguments - * All arguments for the constructor. - * - * @fires Highcharts.Chart#event:init - * @fires Highcharts.Chart#event:afterInit - */ - Chart.prototype.getArgs = function (a, b, c) { - // Remove the optional first argument, renderTo, and - // set it on this. - if (isString(a) || a.nodeName) { - this.renderTo = a; - this.init(b, c); - } - else { - this.init(a, b); - } - }; - /** - * Overridable function that initializes the chart. The constructor's - * arguments are passed on directly. - * - * @function Highcharts.Chart#init - * - * @param {Highcharts.Options} userOptions - * Custom options. - * - * @param {Function} [callback] - * Function to run when the chart has loaded and and all external - * images are loaded. - * - * @return {void} - * - * @fires Highcharts.Chart#event:init - * @fires Highcharts.Chart#event:afterInit - */ - Chart.prototype.init = function (userOptions, callback) { - // Handle regular options - var options, - // skip merging data points to increase performance - seriesOptions = userOptions.series, - userPlotOptions = userOptions.plotOptions || {}; - // Fire the event with a default function - fireEvent(this, 'init', { args: arguments }, function () { - userOptions.series = null; - options = merge(defaultOptions, userOptions); // do the merge - var optionsChart = options.chart || {}; - // Override (by copy of user options) or clear tooltip options - // in chart.options.plotOptions (#6218) - objectEach(options.plotOptions, function (typeOptions, type) { - if (isObject(typeOptions)) { // #8766 - typeOptions.tooltip = (userPlotOptions[type] && // override by copy: - merge(userPlotOptions[type].tooltip)) || void 0; // 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; - // set back the series data - options.series = userOptions.series = seriesOptions; - /** - * The original options given to the constructor or a chart factory - * like {@link Highcharts.chart} and {@link Highcharts.stockChart}. - * - * @name Highcharts.Chart#userOptions - * @type {Highcharts.Options} - */ - this.userOptions = userOptions; - var chartEvents = optionsChart.events; - this.margin = []; - this.spacing = []; - // Pixel data bounds for touch zoom - this.bounds = { h: {}, v: {} }; - // 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 after merging - * {@link #defaultOptions} and {@link #userOptions}. It contains - * members for the sub elements like series, legend, tooltip etc. - * - * @name Highcharts.Chart#options - * @type {Highcharts.Options} - */ - this.options = options; - /** - * All the axes in the chart. - * - * @see Highcharts.Chart.xAxis - * @see Highcharts.Chart.yAxis - * - * @name Highcharts.Chart#axes - * @type {Array<Highcharts.Axis>} - */ - this.axes = []; - /** - * All the current series in the chart. - * - * @name Highcharts.Chart#series - * @type {Array<Highcharts.Series>} - */ - this.series = []; - /** - * 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. - * - * @name Highcharts.Chart#time - * @type {Highcharts.Time} - */ - this.time = - userOptions.time && Object.keys(userOptions.time).length ? - new Time(userOptions.time) : - H.time; - /** - * Callback function to override the default function that formats - * all the numbers in the chart. Returns a string with the formatted - * number. - * - * @name Highcharts.Chart#numberFormatter - * @type {Highcharts.NumberFormatterCallbackFunction} - */ - this.numberFormatter = optionsChart.numberFormatter || numberFormat; - /** - * Whether the chart is in styled mode, meaning all presentatinoal - * attributes are avoided. - * - * @name Highcharts.Chart#styledMode - * @type {boolean} - */ - this.styledMode = optionsChart.styledMode; - this.hasCartesianSeries = optionsChart.showAxes; - var chart = this; - /** - * Index position of the chart in the {@link Highcharts#charts} - * property. - * - * @name Highcharts.Chart#index - * @type {number} - * @readonly - */ - chart.index = charts.length; // Add the chart to the global lookup - charts.push(chart); - H.chartCount++; - // Chart event handlers - if (chartEvents) { - objectEach(chartEvents, function (event, eventType) { - if (isFunction(event)) { - addEvent(chart, eventType, event); - } - }); - } - /** - * A collection of the X axes in the chart. - * - * @name Highcharts.Chart#xAxis - * @type {Array<Highcharts.Axis>} - */ - chart.xAxis = []; - /** - * A collection of the Y axes in the chart. - * - * @name Highcharts.Chart#yAxis - * @type {Array<Highcharts.Axis>} - * - * @todo - * Make events official: Fire the event `afterInit`. - */ - chart.yAxis = []; - chart.pointCount = chart.colorCounter = chart.symbolCounter = 0; - // Fire after init but before first render, before axes and series - // have been initialized. - fireEvent(chart, 'afterInit'); - chart.firstRender(); - }); - }; - /** - * Internal function to unitialize an individual series. - * - * @private - * @function Highcharts.Chart#initSeries - */ - Chart.prototype.initSeries = function (options) { - var chart = this, - optionsChart = chart.options.chart, - type = (options.type || - optionsChart.type || - optionsChart.defaultSeriesType), - series, - Constr = BaseSeries.seriesTypes[type]; - // No such series type - if (!Constr) { - error(17, true, chart, { missingModuleFor: type }); - } - series = new Constr(chart, options); - if (typeof series.init === 'function') { - series.init(this, options); - } - return series; - }; - /** - * Internal function to set data for all series with enabled sorting. - * - * @private - * @function Highcharts.Chart#setSeriesData - */ - Chart.prototype.setSeriesData = function () { - this.getSeriesOrderByLinks().forEach(function (series) { - // We need to set data for series with sorting after series init - if (!series.points && !series.data && series.enabledDataSorting) { - series.setData(series.options.data, false); - } - }); - }; - /** - * Sort and return chart series in order depending on the number of linked - * series. - * - * @private - * @function Highcharts.Series#getSeriesOrderByLinks - * @return {Array<Highcharts.Series>} - */ - Chart.prototype.getSeriesOrderByLinks = function () { - return this.series.concat().sort(function (a, b) { - if (a.linkedSeries.length || b.linkedSeries.length) { - return b.linkedSeries.length - a.linkedSeries.length; - } - return 0; - }); - }; - /** - * 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 - * @function Highcharts.Series#orderSeries - * @param {number} [fromIndex] - * If this is given, only the series above this index are handled. - */ - Chart.prototype.orderSeries = function (fromIndex) { - var series = this.series, - i = fromIndex || 0; - for (; i < series.length; i++) { - if (series[i]) { - /** - * Contains the series' index in the `Chart.series` array. - * - * @name Highcharts.Series#index - * @type {number} - * @readonly - */ - series[i].index = i; - series[i].name = series[i].getName(); - } - } - }; - /** - * Check whether a given point is within the plot area. - * - * @function Highcharts.Chart#isInsidePlot - * - * @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. - */ - Chart.prototype.isInsidePlot = function (plotX, plotY, inverted) { - var x = inverted ? plotY : plotX, - y = inverted ? plotX : plotY, - e = { - x: x, - y: y, - isInsidePlot: x >= 0 && - x <= this.plotWidth && - y >= 0 && - y <= this.plotHeight - }; - fireEvent(this, 'afterIsInsidePlot', e); - return e.isInsidePlot; - }; - /** - * 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. - * - * @function Highcharts.Chart#redraw - * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] - * If or how to apply animation to the redraw. - * - * @fires Highcharts.Chart#event:afterSetExtremes - * @fires Highcharts.Chart#event:beforeRedraw - * @fires Highcharts.Chart#event:predraw - * @fires Highcharts.Chart#event:redraw - * @fires Highcharts.Chart#event:render - * @fires Highcharts.Chart#event:updatedData - */ - Chart.prototype.redraw = function (animation) { - fireEvent(this, 'beforeRedraw'); - var chart = this, - axes = chart.axes, - series = chart.series, - pointer = chart.pointer, - legend = chart.legend, - legendUserOptions = chart.userOptions.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); - } - // Set the global animation. When chart.hasRendered is not true, the - // redraw call comes from a responsive rule and animation should not - // occur. - setAnimation(chart.hasRendered ? animation : false, 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 - series.forEach(function (serie) { - if (serie.isDirty) { - if (serie.options.legendType === 'point') { - if (typeof serie.updateTotals === 'function') { - serie.updateTotals(); - } - redrawLegend = true; - } - else if (legendUserOptions && - (legendUserOptions.labelFormatter || - legendUserOptions.labelFormat)) { - redrawLegend = true; // #2165 - } - } - if (serie.isDirtyData) { - fireEvent(serie, 'updatedData'); - } - }); - // handle added or removed series - if (redrawLegend && legend && legend.options.enabled) { - // draw legend graphics - legend.render(); - chart.isDirtyLegend = false; - } - // reset stacks - if (hasStackedSeries) { - chart.getStacks(); - } - if (hasCartesianSeries) { - // set axes scales - axes.forEach(function (axis) { - // Don't do setScale again if we're only resizing. Regression - // #13507. But we need it after chart.update (responsive), as - // axis is initialized again (#12137). - if (!chart.isResizing || !isNumber(axis.min)) { - axis.updateNames(); - axis.setScale(); - } - }); - } - chart.getMargins(); // #3098 - if (hasCartesianSeries) { - // If one axis is dirty, all axes must be redrawn (#792, #2169) - axes.forEach(function (axis) { - if (axis.isDirty) { - isDirtyBox = true; - } - }); - // redraw axes - axes.forEach(function (axis) { - // Fire 'afterSetExtremes' only if extremes are set - var key = axis.min + ',' + axis.max; - if (axis.extKey !== key) { // #821, #4452 - axis.extKey = key; - // prevent a recursive call to chart.redraw() (#1119) - afterRedraw.push(function () { - 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 - series.forEach(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 - afterRedraw.forEach(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. - * - * @sample highcharts/plotoptions/series-id/ - * Get series by id - * - * @function Highcharts.Chart#get - * - * @param {string} id - * The id as given in the configuration options. - * - * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined} - * The retrieved item. - */ - Chart.prototype.get = function (id) { - var ret, - series = this.series, - i; - /** - * @private - * @param {Highcharts.Axis|Highcharts.Series} item - * @return {boolean} - */ - 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 - * @function Highcharts.Chart#getAxes - * @fires Highcharts.Chart#event:afterGetAxes - * @fires Highcharts.Chart#event:getAxes - */ - Chart.prototype.getAxes = function () { - var chart = this, - options = this.options, - xAxisOptions = options.xAxis = splat(options.xAxis || {}), - yAxisOptions = options.yAxis = splat(options.yAxis || {}), - optionsArray; - fireEvent(this, 'getAxes'); - // make sure the options are arrays and add some members - xAxisOptions.forEach(function (axis, i) { - axis.index = i; - axis.isX = true; - }); - yAxisOptions.forEach(function (axis, i) { - axis.index = i; - }); - // concatenate all axis options into one array - optionsArray = xAxisOptions.concat(yAxisOptions); - optionsArray.forEach(function (axisOptions) { - new Axis(chart, axisOptions); // eslint-disable-line no-new - }); - fireEvent(this, 'afterGetAxes'); - }; - /** - * 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. - * - * @sample highcharts/plotoptions/series-allowpointselect-line/ - * Get selected points - * - * @function Highcharts.Chart#getSelectedPoints - * - * @return {Array<Highcharts.Point>} - * The currently selected points. - */ - Chart.prototype.getSelectedPoints = function () { - var points = []; - this.series.forEach(function (serie) { - // For one-to-one points inspect series.data in order to retrieve - // points outside the visible range (#6445). For grouped data, - // inspect the generated series.points. - points = points.concat(serie.getPointsCollection().filter(function (point) { - return pick(point.selectedStaging, 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 - * [series.showCheckBox](https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox) - * is true. - * - * @sample highcharts/members/chart-getselectedseries/ - * Get selected series - * - * @function Highcharts.Chart#getSelectedSeries - * - * @return {Array<Highcharts.Series>} - * The currently selected series. - */ - Chart.prototype.getSelectedSeries = function () { - return this.series.filter(function (serie) { - return serie.selected; - }); - }; - /** - * Set a new title or subtitle for the chart. - * - * @sample highcharts/members/chart-settitle/ - * Set title text and styles - * - * @function Highcharts.Chart#setTitle - * - * @param {Highcharts.TitleOptions} [titleOptions] - * New title options. The title text itself is set by the - * `titleOptions.text` property. - * - * @param {Highcharts.SubtitleOptions} [subtitleOptions] - * New subtitle options. The subtitle text itself is set by the - * `subtitleOptions.text` property. - * - * @param {boolean} [redraw] - * Whether to redraw the chart or wait for a later call to - * `chart.redraw()`. - */ - Chart.prototype.setTitle = function (titleOptions, subtitleOptions, redraw) { - this.applyDescription('title', titleOptions); - this.applyDescription('subtitle', subtitleOptions); - // The initial call also adds the caption. On update, chart.update will - // relay to Chart.setCaption. - this.applyDescription('caption', void 0); - this.layOutTitles(redraw); - }; - /** - * Apply a title, subtitle or caption for the chart - * - * @private - * @function Highcharts.Chart#applyDescription - * @param name {string} - * Either title, subtitle or caption - * @param {Highcharts.TitleOptions|Highcharts.SubtitleOptions|Highcharts.CaptionOptions|undefined} explicitOptions - * The options to set, will be merged with default options. - */ - Chart.prototype.applyDescription = function (name, explicitOptions) { - var chart = this; - // Default style - var style = name === 'title' ? { - color: '#333333', - fontSize: this.options.isStock ? '16px' : '18px' // #2944 - } : { - color: '#666666' - }; - // Merge default options with explicit options - var options = this.options[name] = merge( - // Default styles - (!this.styledMode && { style: style }), - this.options[name], - explicitOptions); - var elem = this[name]; - if (elem && explicitOptions) { - this[name] = elem = elem.destroy(); // remove old - } - if (options && !elem) { - elem = this.renderer.text(options.text, 0, 0, options.useHTML) - .attr({ - align: options.align, - 'class': 'highcharts-' + name, - zIndex: options.zIndex || 4 - }) - .add(); - // Update methods, shortcut to Chart.setTitle, Chart.setSubtitle and - // Chart.setCaption - elem.update = function (updateOptions) { - var fn = { - title: 'setTitle', - subtitle: 'setSubtitle', - caption: 'setCaption' - }[name]; - chart[fn](updateOptions); - }; - // Presentational - if (!this.styledMode) { - elem.css(options.style); - } - /** - * The chart title. The title has an `update` method that allows - * modifying the options directly or indirectly via - * `chart.update`. - * - * @sample highcharts/members/title-update/ - * Updating titles - * - * @name Highcharts.Chart#title - * @type {Highcharts.TitleObject} - */ - /** - * The chart subtitle. The subtitle has an `update` method that - * allows modifying the options directly or indirectly via - * `chart.update`. - * - * @name Highcharts.Chart#subtitle - * @type {Highcharts.SubtitleObject} - */ - this[name] = elem; - } - }; - /** - * Internal function to lay out the chart title, subtitle and caption, and - * cache the full offset height for use in `getMargins`. The result is - * stored in `this.titleOffset`. - * - * @private - * @function Highcharts.Chart#layOutTitles - * - * @param {boolean} [redraw=true] - * @fires Highcharts.Chart#event:afterLayOutTitles - */ - Chart.prototype.layOutTitles = function (redraw) { - var titleOffset = [0, 0, 0], - requiresDirtyBox, - renderer = this.renderer, - spacingBox = this.spacingBox; - // Lay out the title and the subtitle respectively - ['title', 'subtitle', 'caption'].forEach(function (key) { - var title = this[key], titleOptions = this.options[key], verticalAlign = titleOptions.verticalAlign || 'top', offset = key === 'title' ? -3 : - // Floating subtitle (#6574) - verticalAlign === 'top' ? titleOffset[0] + 2 : 0, titleSize, height; - if (title) { - if (!this.styledMode) { - titleSize = titleOptions.style.fontSize; - } - titleSize = renderer.fontMetrics(titleSize, title).b; - title - .css({ - width: (titleOptions.width || - spacingBox.width + (titleOptions.widthAdjust || 0)) + 'px' - }); - // Skip the cache for HTML (#3481, #11666) - height = Math.round(title.getBBox(titleOptions.useHTML).height); - title.align(extend({ - y: verticalAlign === 'bottom' ? - titleSize : - offset + titleSize, - height: height - }, titleOptions), false, 'spacingBox'); - if (!titleOptions.floating) { - if (verticalAlign === 'top') { - titleOffset[0] = Math.ceil(titleOffset[0] + - height); - } - else if (verticalAlign === 'bottom') { - titleOffset[2] = Math.ceil(titleOffset[2] + - height); - } - } - } - }, this); - // Handle title.margin and caption.margin - if (titleOffset[0] && - (this.options.title.verticalAlign || 'top') === 'top') { - titleOffset[0] += this.options.title.margin; - } - if (titleOffset[2] && - this.options.caption.verticalAlign === 'bottom') { - titleOffset[2] += this.options.caption.margin; - } - requiresDirtyBox = (!this.titleOffset || - this.titleOffset.join(',') !== titleOffset.join(',')); - // Used in getMargins - this.titleOffset = titleOffset; - fireEvent(this, 'afterLayOutTitles'); - if (!this.isDirtyBox && requiresDirtyBox) { - this.isDirtyBox = this.isDirtyLegend = 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}. - * - * @private - * @function Highcharts.Chart#getChartSize - */ - Chart.prototype.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 = getStyle(renderTo, 'width'); - } - if (!defined(heightOption)) { - chart.containerHeight = getStyle(renderTo, 'height'); - } - /** - * The current pixel width of the chart. - * - * @name Highcharts.Chart#chartWidth - * @type {number} - */ - chart.chartWidth = Math.max(// #1393 - 0, widthOption || chart.containerWidth || 600 // #1460 - ); - /** - * The current pixel height of the chart. - * - * @name Highcharts.Chart#chartHeight - * @type {number} - */ - chart.chartHeight = Math.max(0, 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 - * @function Highcharts.Chart#temporaryDisplay - * - * @param {boolean} [revert] - * Revert to the saved original styles. - */ - Chart.prototype.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 (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; - } - 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) { - 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`. - * - * @function Highcharts.Chart#setClassName - * - * @param {string} [className] - * The additional class name. - */ - Chart.prototype.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 - * @function Highcharts.Chart#afterGetContainer - * @fires Highcharts.Chart#event:afterGetContainer - */ - Chart.prototype.getContainer = function () { - var chart = this, - container, - options = chart.options, - optionsChart = options.chart, - chartWidth, - chartHeight, - renderTo = chart.renderTo, - indexAttrName = 'data-highcharts-chart', - oldChartIndex, - Ren, - containerId = 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) { - error(13, true, chart); - } - // 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; - // Allow table cells and flex-boxes to shrink without the chart blocking - // them out (#6427) - css(renderTo, { overflow: 'hidden' }); - // Create the inner container - if (!chart.styledMode) { - containerStyle = extend({ - position: 'relative', - // needed for context menu (avoidscrollbars) and content - // overflow in IE - overflow: 'hidden', - width: chartWidth + 'px', - height: chartHeight + 'px', - textAlign: 'left', - lineHeight: 'normal', - zIndex: 0, - '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', - userSelect: 'none' // #13503 - }, optionsChart.style); - } - /** - * The containing HTML element of the chart. The container is - * dynamically inserted into the element given as the `renderTo` - * parameter in the {@link Highcharts#chart} constructor. - * - * @name Highcharts.Chart#container - * @type {Highcharts.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. - * - * @name Highcharts.Chart#renderer - * @type {Highcharts.SVGRenderer} - */ - chart.renderer = new Ren(container, chartWidth, chartHeight, null, optionsChart.forExport, options.exporting && options.exporting.allowHTML, chart.styledMode); - // Set the initial animation from the options - setAnimation(void 0, chart); - chart.setClassName(optionsChart.className); - if (!chart.styledMode) { - chart.renderer.setStyle(optionsChart.style); - } - else { - // Initialize definitions - for (key in options.defs) { // eslint-disable-line guard-for-in - this.renderer.definition(options.defs[key]); - } - } - // Add a reference to the charts index - chart.renderer.chartIndex = chart.index; - fireEvent(this, 'afterGetContainer'); - }; - /** - * 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 - * @function Highcharts.Chart#getMargins - * @fires Highcharts.Chart#event:getMargins - */ - Chart.prototype.getMargins = function (skipAxes) { - var _a = this, - spacing = _a.spacing, - margin = _a.margin, - titleOffset = _a.titleOffset; - this.resetMargins(); - // Adjust for title and subtitle - if (titleOffset[0] && !defined(margin[0])) { - this.plotTop = Math.max(this.plotTop, titleOffset[0] + spacing[0]); - } - if (titleOffset[2] && !defined(margin[2])) { - this.marginBottom = Math.max(this.marginBottom, titleOffset[2] + spacing[2]); - } - // Adjust for legend - if (this.legend && this.legend.display) { - this.legend.adjustMargins(margin, spacing); - } - fireEvent(this, 'getMargins'); - if (!skipAxes) { - this.getAxisMargins(); - } - }; - /** - * @private - * @function Highcharts.Chart#getAxisMargins - */ - Chart.prototype.getAxisMargins = function () { - var chart = this, - // [top, right, bottom, left] - axisOffset = chart.axisOffset = [0, 0, 0, 0], - colorAxis = chart.colorAxis, - margin = chart.margin, - getOffset = function (axes) { - axes.forEach(function (axis) { - if (axis.visible) { - axis.getOffset(); - } - }); - }; - // pre-render axes to get labels offset width - if (chart.hasCartesianSeries) { - getOffset(chart.axes); - } - else if (colorAxis && colorAxis.length) { - getOffset(colorAxis); - } - // Add the axis offsets - marginNames.forEach(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 [chart.reflow](https://api.highcharts.com/highcharts/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. - * - * @sample highcharts/members/chart-reflow/ - * Resize div and reflow - * @sample highcharts/chart/events-container/ - * Pop up and reflow - * - * @function Highcharts.Chart#reflow - * - * @param {global.Event} [e] - * Event arguments. Used primarily when the function is called - * internally as a response to window resize. - */ - Chart.prototype.reflow = function (e) { - var chart = this, optionsChart = chart.options.chart, renderTo = chart.renderTo, hasUserSize = (defined(optionsChart.width) && - defined(optionsChart.height)), width = optionsChart.width || getStyle(renderTo, 'width'), height = optionsChart.height || 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) { - U.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(void 0, void 0, false); - } - }, e ? 100 : 0); - } - chart.containerWidth = width; - chart.containerHeight = height; - } - }; - /** - * Toggle the event handlers necessary for auto resizing, depending on the - * `chart.reflow` option. - * - * @private - * @function Highcharts.Chart#setReflow - */ - Chart.prototype.setReflow = function (reflow) { - var chart = this; - if (reflow !== false && !this.unbindReflow) { - this.unbindReflow = addEvent(win, 'resize', function (e) { - // a removed event listener still runs in Edge and IE if the - // listener was removed while the event runs, so check if the - // chart is not destroyed (#11609) - if (chart.options) { - chart.reflow(e); - } - }); - addEvent(this, 'destroy', this.unbindReflow); - } - else if (reflow === false && this.unbindReflow) { - // Unbind and unset - this.unbindReflow = this.unbindReflow(); - } - // 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. - * - * @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 - * - * @function Highcharts.Chart#setSize - * - * @param {number|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|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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] - * Whether and how to apply animation. - * - * @return {void} - * - * @fires Highcharts.Chart#event:endResize - * @fires Highcharts.Chart#event:resize - */ - Chart.prototype.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 - setAnimation(animation, chart); - globalAnimation = renderer.globalAnimation; - chart.oldChartHeight = chart.chartHeight; - chart.oldChartWidth = chart.chartWidth; - if (typeof width !== 'undefined') { - chart.options.chart.width = width; - } - if (typeof height !== 'undefined') { - chart.options.chart.height = height; - } - chart.getChartSize(); - // Resize the container with the global animation applied if enabled - // (#2503) - if (!chart.styledMode) { - (globalAnimation ? animate : css)(chart.container, { - width: chart.chartWidth + 'px', - height: chart.chartHeight + 'px' - }, globalAnimation); - } - chart.setChartSize(true); - renderer.setSize(chart.chartWidth, chart.chartHeight, globalAnimation); - // handle axes - chart.axes.forEach(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(globalAnimation); - 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 - * @function Highcharts.Chart#setChartSize - * @fires Highcharts.Chart#event:afterSetChartSize - */ - Chart.prototype.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 Highcharts.Chart#plotLeft - * @type {number} - */ - chart.plotLeft = plotLeft = Math.round(chart.plotLeft); - /** - * The current top position of the plot area in pixels. - * - * @name Highcharts.Chart#plotTop - * @type {number} - */ - chart.plotTop = plotTop = Math.round(chart.plotTop); - /** - * The current width of the plot area in pixels. - * - * @name Highcharts.Chart#plotWidth - * @type {number} - */ - chart.plotWidth = plotWidth = Math.max(0, Math.round(chartWidth - plotLeft - chart.marginRight)); - /** - * The current height of the plot area in pixels. - * - * @name Highcharts.Chart#plotHeight - * @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) { - chart.axes.forEach(function (axis) { - axis.setAxisSize(); - axis.setAxisTranslation(); - }); - } - fireEvent(chart, 'afterSetChartSize', { skipAxes: skipAxes }); - }; - /** - * Initial margins before auto size margins are applied. - * - * @private - * @function Highcharts.Chart#resetMargins - */ - Chart.prototype.resetMargins = function () { - fireEvent(this, 'resetMargins'); - var chart = this, - chartOptions = chart.options.chart; - // Create margin and spacing array - ['margin', 'spacing'].forEach(function splashArrays(target) { - var value = chartOptions[target], - values = isObject(value) ? value : [value, - value, - value, - value]; - [ - 'Top', - 'Right', - 'Bottom', - 'Left' - ].forEach(function (sideName, side) { - chart[target][side] = pick(chartOptions[target + sideName], values[side]); - }); - }); - // Set margin names like chart.plotTop, chart.plotLeft, - // chart.marginRight, chart.marginBottom. - marginNames.forEach(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 - * @function Highcharts.Chart#drawChartBox - * @fires Highcharts.Chart#event:afterDrawChartBox - */ - Chart.prototype.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, - styledMode = chart.styledMode, - 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'; - } - if (!styledMode) { - // 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); - } - else { - chartBorderWidth = mgn = chartBackground.strokeWidth(); - } - 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); - if (!styledMode) { - // 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 { - if (plotBackgroundImage !== plotBGImage.attr('href')) { - plotBGImage.attr('href', plotBackgroundImage); - } - 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(); - } - if (!styledMode) { - // 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; - fireEvent(this, 'afterDrawChartBox'); - }; - /** - * 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 - * @function Highcharts.Chart#propFromSeries - * @return {void} - */ - Chart.prototype.propFromSeries = function () { - var chart = this, - optionsChart = chart.options.chart, - klass, - seriesOptions = chart.options.series, - i, - value; - /** - * The flag is set to `true` if a series of the chart is inverted. - * - * @name Highcharts.Chart#inverted - * @type {boolean|undefined} - */ - ['inverted', 'angular', 'polar'].forEach(function (key) { - // The default series type's class - klass = BaseSeries.seriesTypes[(optionsChart.type || - optionsChart.defaultSeriesType)]; - // Get the value from available chart-wide properties - value = - // It is set in the options: - optionsChart[key] || - // The default series class: - (klass && klass.prototype[key]); - // requires it - // 4. Check if any the chart's series require it - i = seriesOptions && seriesOptions.length; - while (!value && i--) { - klass = BaseSeries.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 - * @function Highcharts.Chart#linkSeries - * @fires Highcharts.Chart#event:afterLinkSeries - */ - Chart.prototype.linkSeries = function () { - var chart = this, - chartSeries = chart.series; - // Reset links - chartSeries.forEach(function (series) { - series.linkedSeries.length = 0; - }); - // Apply new links - chartSeries.forEach(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; - if (linkedTo.enabledDataSorting) { - series.setDataSortingOptions(); - } - series.visible = pick(series.options.visible, linkedTo.options.visible, series.visible); // #3879 - } - } - }); - fireEvent(this, 'afterLinkSeries'); - }; - /** - * Render series for the chart. - * - * @private - * @function Highcharts.Chart#renderSeries - */ - Chart.prototype.renderSeries = function () { - this.series.forEach(function (serie) { - serie.translate(); - serie.render(); - }); - }; - /** - * Render labels for the chart. - * - * @private - * @function Highcharts.Chart#renderLabels - */ - Chart.prototype.renderLabels = function () { - var chart = this, - labels = chart.options.labels; - if (labels.items) { - labels.items.forEach(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 - * @function Highcharts.Chart#render - */ - Chart.prototype.render = function () { - var chart = this, - axes = chart.axes, - colorAxis = chart.colorAxis, - renderer = chart.renderer, - options = chart.options, - correction = 0, // correction for X axis labels - tempWidth, - tempHeight, - redoHorizontal, - redoVertical, - renderAxes = function (axes) { - axes.forEach(function (axis) { - if (axis.visible) { - axis.render(); - } - }); - }; - // Title - chart.setTitle(); - /** - * The overview of the chart's series. - * - * @name Highcharts.Chart#legend - * @type {Highcharts.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; - axes.some(function (axis) { - if (axis.horiz && - axis.visible && - axis.options.labels.enabled && - axis.series.length) { - // 21 is the most common correction for X axis labels - correction = 21; - return true; - } - }); - // use Math.max to prevent negative plotHeight - chart.plotHeight = Math.max(chart.plotHeight - correction, 0); - tempHeight = chart.plotHeight; - // Get margins by pre-rendering axes - axes.forEach(function (axis) { - axis.setScale(); - }); - chart.getAxisMargins(); - // If the plot area size has changed significantly, calculate tick - // positions again - redoHorizontal = tempWidth / chart.plotWidth > 1.1; - // Height is more sensitive, use lower threshold - redoVertical = tempHeight / chart.plotHeight > 1.05; - if (redoHorizontal || redoVertical) { - axes.forEach(function (axis) { - if ((axis.horiz && redoHorizontal) || - (!axis.horiz && redoVertical)) { - // update to reflect the new margins - axis.setTickInterval(true); - } - }); - chart.getMargins(); // second pass to check for new labels - } - // Draw the borders and backgrounds - chart.drawChartBox(); - // Axes - if (chart.hasCartesianSeries) { - renderAxes(axes); - } - else if (colorAxis && colorAxis.length) { - renderAxes(colorAxis); - } - // 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(); - } - // Handle scaling - chart.updateContainerScaling(); - // Set flag - chart.hasRendered = true; - }; - /** - * Set a new credits label for the chart. - * - * @sample highcharts/credits/credits-update/ - * Add and update credits - * - * @function Highcharts.Chart#addCredits - * - * @param {Highcharts.CreditsOptions} [credits] - * A configuration object for the new credits. - */ - Chart.prototype.addCredits = function (credits) { - var chart = this, - creds = merge(true, - this.options.credits, - credits); - if (creds.enabled && !this.credits) { - /** - * The chart's credits label. The label has an `update` method that - * allows setting new options as per the - * [credits options set](https://api.highcharts.com/highcharts/credits). - * - * @name Highcharts.Chart#credits - * @type {Highcharts.SVGElement} - */ - this.credits = this.renderer.text(creds.text + (this.mapCredits || ''), 0, 0) - .addClass('highcharts-credits') - .on('click', function () { - if (creds.href) { - win.location.href = creds.href; - } - }) - .attr({ - align: creds.position.align, - zIndex: 8 - }); - if (!chart.styledMode) { - this.credits.css(creds.style); - } - this.credits - .add() - .align(creds.position); - // Dynamically update - this.credits.update = function (options) { - chart.credits = chart.credits.destroy(); - chart.addCredits(options); - }; - } - }; - /** - * Handle scaling, #11329 - when there is scaling/transform on the container - * or on a parent element, we need to take this into account. We calculate - * the scaling once here and it is picked up where we need to use it - * (Pointer, Tooltip). - * - * @private - * @function Highcharts.Chart#updateContainerScaling - */ - Chart.prototype.updateContainerScaling = function () { - var container = this.container; - // #13342 - tooltip was not visible in Chrome, when chart - // updates height. - if (container.offsetWidth > 2 && // #13342 - container.offsetHeight > 2 && // #13342 - container.getBoundingClientRect) { - var bb = container.getBoundingClientRect(), - scaleX = bb.width / container.offsetWidth, - scaleY = bb.height / container.offsetHeight; - if (scaleX !== 1 || scaleY !== 1) { - this.containerScaling = { scaleX: scaleX, scaleY: scaleY }; - } - else { - delete this.containerScaling; - } - } - }; - /** - * 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 - * - * @function Highcharts.Chart#destroy - * - * @fires Highcharts.Chart#event:destroy - */ - Chart.prototype.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) { - erase(charts, chart); // #6569 - } - else { - charts[chart.index] = void 0; - } - 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: - [ - 'title', 'subtitle', 'chartBackground', 'plotBackground', - 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits', - 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', - 'renderer' - ].forEach(function (name) { - var prop = chart[name]; - if (prop && prop.destroy) { - chart[name] = prop.destroy(); - } - }); - // Remove container and all SVG, check container as it can break in IE - // when destroyed before finished loading - if (container) { - container.innerHTML = ''; - removeEvent(container); - if (parentNode) { - discardElement(container); - } - } - // clean it all up - objectEach(chart, function (val, key) { - delete chart[key]; - }); - }; - /** - * Prepare for first rendering after all data are loaded. - * - * @private - * @function Highcharts.Chart#firstRender - * @fires Highcharts.Chart#event:beforeRender - */ - Chart.prototype.firstRender = function () { - var chart = this, - options = chart.options; - // Hook for oldIE to check whether the chart is ready to render - if (chart.isReadyToRender && !chart.isReadyToRender()) { - return; - } - // Create the container - chart.getContainer(); - chart.resetMargins(); - chart.setChartSize(); - // Set the common chart properties (mainly invert) from the given series - chart.propFromSeries(); - // get axes - chart.getAxes(); - // Initialize the series - (isArray(options.series) ? options.series : []).forEach( - // #9680 - function (serieOptions) { - chart.initSeries(serieOptions); - }); - chart.linkSeries(); - chart.setSeriesData(); - // 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) { - if (!H.hasTouch && (win.PointerEvent || win.MSPointerEvent)) { - chart.pointer = new MSPointer(chart, options); - } - else { - /** - * The Pointer that keeps track of mouse and touch interaction. - * - * @memberof Highcharts.Chart - * @name pointer - * @type {Highcharts.Pointer} - * @instance - */ - chart.pointer = new Pointer(chart, options); - } - } - chart.render(); - // Fire the load event if there are no external images - if (!chart.renderer.imgCount && !chart.hasLoaded) { - 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 - * @function Highcharts.Chart#onload - * @fires Highcharts.Chart#event:load - * @fires Highcharts.Chart#event:render - */ - Chart.prototype.onload = function () { - // Run callbacks, first the ones registered by modules, then user's one - this.callbacks.concat([this.callback]).forEach(function (fn) { - // Chart destroyed in its own callback (#3600) - if (fn && typeof this.index !== 'undefined') { - 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.setReflow(this.options.chart.reflow); - } - // Don't run again - this.hasLoaded = true; - }; - return Chart; - }()); - // Hook for adding callbacks in modules - Chart.prototype.callbacks = []; - /** - * Factory function for basic charts. - * - * @example - * // Render a chart in to div#container - * var chart = Highcharts.chart('container', { - * title: { - * text: 'My chart' - * }, - * series: [{ - * data: [1, 3, 2, 4] - * }] - * }); - * - * @function Highcharts.chart - * - * @param {string|Highcharts.HTMLDOMElement} [renderTo] - * The DOM element to render to, or its id. - * - * @param {Highcharts.Options} options - * The chart options structure. - * - * @param {Highcharts.ChartCallbackFunction} [callback] - * Function to run when the chart has loaded and and all external images - * are loaded. Defining a - * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) - * handler is equivalent. - * - * @return {Highcharts.Chart} - * Returns the Chart object. - */ - function chart(a, b, c) { - return new Chart(a, b, c); - } - H.chart = chart; - H.Chart = Chart; - - return Chart; - }); - _registerModule(_modules, 'Extensions/ScrollablePlotArea.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (A, Chart, H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * Highcharts feature to make the Y axis stay fixed when scrolling the chart - * horizontally on mobile devices. Supports left and right side axes. - */ - /* - WIP on vertical scrollable plot area (#9378). To do: - - Bottom axis positioning - - Test with Gantt - - Look for size optimizing the code - - API and demos - */ - var stop = A.stop; - var addEvent = U.addEvent, - createElement = U.createElement, - pick = U.pick; - /** - * Options for a scrollable plot area. This feature provides a minimum size for - * the plot area of the chart. If the size gets smaller than this, typically - * on mobile devices, a native browser scrollbar is presented. This scrollbar - * provides smooth scrolling for the contents of the plot area, whereas the - * title, legend and unaffected axes are fixed. - * - * Since v7.1.2, a scrollable plot area can be defined for either horizontal or - * vertical scrolling, depending on whether the `minWidth` or `minHeight` - * option is set. - * - * @sample highcharts/chart/scrollable-plotarea - * Scrollable plot area - * @sample highcharts/chart/scrollable-plotarea-vertical - * Vertically scrollable plot area - * @sample {gantt} highcharts/chart/scrollable-plotarea-vertical - * Gantt chart with vertically scrollable plot area - * - * @since 6.1.0 - * @product highcharts gantt - * @apioption chart.scrollablePlotArea - */ - /** - * The minimum height for the plot area. If it gets smaller than this, the plot - * area will become scrollable. - * - * @type {number} - * @apioption chart.scrollablePlotArea.minHeight - */ - /** - * The minimum width for the plot area. If it gets smaller than this, the plot - * area will become scrollable. - * - * @type {number} - * @apioption chart.scrollablePlotArea.minWidth - */ - /** - * The initial scrolling position of the scrollable plot area. Ranges from 0 to - * 1, where 0 aligns the plot area to the left and 1 aligns it to the right. - * Typically we would use 1 if the chart has right aligned Y axes. - * - * @type {number} - * @apioption chart.scrollablePlotArea.scrollPositionX - */ - /** - * The initial scrolling position of the scrollable plot area. Ranges from 0 to - * 1, where 0 aligns the plot area to the top and 1 aligns it to the bottom. - * - * @type {number} - * @apioption chart.scrollablePlotArea.scrollPositionY - */ - /** - * The opacity of mask applied on one of the sides of the plot - * area. - * - * @sample {highcharts} highcharts/chart/scrollable-plotarea-opacity - * Disabled opacity for the mask - * - * @type {number} - * @default 0.85 - * @since 7.1.1 - * @apioption chart.scrollablePlotArea.opacity - */ - ''; // detach API doclets - /* eslint-disable no-invalid-this, valid-jsdoc */ - addEvent(Chart, 'afterSetChartSize', function (e) { - var scrollablePlotArea = this.options.chart.scrollablePlotArea, - scrollableMinWidth = scrollablePlotArea && scrollablePlotArea.minWidth, - scrollableMinHeight = scrollablePlotArea && scrollablePlotArea.minHeight, - scrollablePixelsX, - scrollablePixelsY, - corrections; - if (!this.renderer.forExport) { - // The amount of pixels to scroll, the difference between chart - // width and scrollable width - if (scrollableMinWidth) { - this.scrollablePixelsX = scrollablePixelsX = Math.max(0, scrollableMinWidth - this.chartWidth); - if (scrollablePixelsX) { - this.plotWidth += scrollablePixelsX; - if (this.inverted) { - this.clipBox.height += scrollablePixelsX; - this.plotBox.height += scrollablePixelsX; - } - else { - this.clipBox.width += scrollablePixelsX; - this.plotBox.width += scrollablePixelsX; - } - corrections = { - // Corrections for right side - 1: { name: 'right', value: scrollablePixelsX } - }; - } - // Currently we can only do either X or Y - } - else if (scrollableMinHeight) { - this.scrollablePixelsY = scrollablePixelsY = Math.max(0, scrollableMinHeight - this.chartHeight); - if (scrollablePixelsY) { - this.plotHeight += scrollablePixelsY; - if (this.inverted) { - this.clipBox.width += scrollablePixelsY; - this.plotBox.width += scrollablePixelsY; - } - else { - this.clipBox.height += scrollablePixelsY; - this.plotBox.height += scrollablePixelsY; - } - corrections = { - 2: { name: 'bottom', value: scrollablePixelsY } - }; - } - } - if (corrections && !e.skipAxes) { - this.axes.forEach(function (axis) { - // For right and bottom axes, only fix the plot line length - if (corrections[axis.side]) { - // Get the plot lines right in getPlotLinePath, - // temporarily set it to the adjusted plot width. - axis.getPlotLinePath = function () { - var marginName = corrections[axis.side].name, - correctionValue = corrections[axis.side].value, - // axis.right or axis.bottom - margin = this[marginName], - path; - // Temporarily adjust - this[marginName] = margin - correctionValue; - path = H.Axis.prototype.getPlotLinePath.apply(this, arguments); - // Reset - this[marginName] = margin; - return path; - }; - } - else { - // Apply the corrected plotWidth - axis.setAxisSize(); - axis.setAxisTranslation(); - } - }); - } - } - }); - addEvent(Chart, 'render', function () { - if (this.scrollablePixelsX || this.scrollablePixelsY) { - if (this.setUpScrolling) { - this.setUpScrolling(); - } - this.applyFixed(); - } - else if (this.fixedDiv) { // Has been in scrollable mode - this.applyFixed(); - } - }); - /** - * @private - * @function Highcharts.Chart#setUpScrolling - * @return {void} - */ - Chart.prototype.setUpScrolling = function () { - var _this = this; - var attribs = { - WebkitOverflowScrolling: 'touch', - overflowX: 'hidden', - overflowY: 'hidden' - }; - if (this.scrollablePixelsX) { - attribs.overflowX = 'auto'; - } - if (this.scrollablePixelsY) { - attribs.overflowY = 'auto'; - } - // Insert a container with position relative - // that scrolling and fixed container renders to (#10555) - this.scrollingParent = createElement('div', { - className: 'highcharts-scrolling-parent' - }, { - position: 'relative' - }, this.renderTo); - // Add the necessary divs to provide scrolling - this.scrollingContainer = createElement('div', { - 'className': 'highcharts-scrolling' - }, attribs, this.scrollingParent); - // On scroll, reset the chart position because it applies to the scrolled - // container - addEvent(this.scrollingContainer, 'scroll', function () { - if (_this.pointer) { - delete _this.pointer.chartPosition; - } - }); - this.innerContainer = createElement('div', { - 'className': 'highcharts-inner-container' - }, null, this.scrollingContainer); - // Now move the container inside - this.innerContainer.appendChild(this.container); - // Don't run again - this.setUpScrolling = null; - }; - /** - * These elements are moved over to the fixed renderer and stay fixed when the - * user scrolls the chart - * @private - */ - Chart.prototype.moveFixedElements = function () { - var container = this.container, - fixedRenderer = this.fixedRenderer, - fixedSelectors = [ - '.highcharts-contextbutton', - '.highcharts-credits', - '.highcharts-legend', - '.highcharts-legend-checkbox', - '.highcharts-navigator-series', - '.highcharts-navigator-xaxis', - '.highcharts-navigator-yaxis', - '.highcharts-navigator', - '.highcharts-reset-zoom', - '.highcharts-scrollbar', - '.highcharts-subtitle', - '.highcharts-title' - ], - axisClass; - if (this.scrollablePixelsX && !this.inverted) { - axisClass = '.highcharts-yaxis'; - } - else if (this.scrollablePixelsX && this.inverted) { - axisClass = '.highcharts-xaxis'; - } - else if (this.scrollablePixelsY && !this.inverted) { - axisClass = '.highcharts-xaxis'; - } - else if (this.scrollablePixelsY && this.inverted) { - axisClass = '.highcharts-yaxis'; - } - fixedSelectors.push(axisClass, axisClass + '-labels'); - fixedSelectors.forEach(function (className) { - [].forEach.call(container.querySelectorAll(className), function (elem) { - (elem.namespaceURI === fixedRenderer.SVG_NS ? - fixedRenderer.box : - fixedRenderer.box.parentNode).appendChild(elem); - elem.style.pointerEvents = 'auto'; - }); - }); - }; - /** - * @private - * @function Highcharts.Chart#applyFixed - * @return {void} - */ - Chart.prototype.applyFixed = function () { - var _a, - _b; - var fixedRenderer, - scrollableWidth, - scrollableHeight, - firstTime = !this.fixedDiv, - scrollableOptions = this.options.chart.scrollablePlotArea; - // First render - if (firstTime) { - this.fixedDiv = createElement('div', { - className: 'highcharts-fixed' - }, { - position: 'absolute', - overflow: 'hidden', - pointerEvents: 'none', - zIndex: 2, - top: 0 - }, null, true); - (_a = this.scrollingContainer) === null || _a === void 0 ? void 0 : _a.parentNode.insertBefore(this.fixedDiv, this.scrollingContainer); - this.renderTo.style.overflow = 'visible'; - this.fixedRenderer = fixedRenderer = new H.Renderer(this.fixedDiv, this.chartWidth, this.chartHeight, (_b = this.options.chart) === null || _b === void 0 ? void 0 : _b.style); - // Mask - this.scrollableMask = fixedRenderer - .path() - .attr({ - fill: this.options.chart.backgroundColor || '#fff', - 'fill-opacity': pick(scrollableOptions.opacity, 0.85), - zIndex: -1 - }) - .addClass('highcharts-scrollable-mask') - .add(); - this.moveFixedElements(); - addEvent(this, 'afterShowResetZoom', this.moveFixedElements); - addEvent(this, 'afterLayOutTitles', this.moveFixedElements); - } - else { - // Set the size of the fixed renderer to the visible width - this.fixedRenderer.setSize(this.chartWidth, this.chartHeight); - } - // Increase the size of the scrollable renderer and background - scrollableWidth = this.chartWidth + (this.scrollablePixelsX || 0); - scrollableHeight = this.chartHeight + (this.scrollablePixelsY || 0); - stop(this.container); - this.container.style.width = scrollableWidth + 'px'; - this.container.style.height = scrollableHeight + 'px'; - this.renderer.boxWrapper.attr({ - width: scrollableWidth, - height: scrollableHeight, - viewBox: [0, 0, scrollableWidth, scrollableHeight].join(' ') - }); - this.chartBackground.attr({ - width: scrollableWidth, - height: scrollableHeight - }); - this.scrollingContainer.style.height = this.chartHeight + 'px'; - // Set scroll position - if (firstTime) { - if (scrollableOptions.scrollPositionX) { - this.scrollingContainer.scrollLeft = - this.scrollablePixelsX * - scrollableOptions.scrollPositionX; - } - if (scrollableOptions.scrollPositionY) { - this.scrollingContainer.scrollTop = - this.scrollablePixelsY * - scrollableOptions.scrollPositionY; - } - } - // Mask behind the left and right side - var axisOffset = this.axisOffset, - maskTop = this.plotTop - axisOffset[0] - 1, - maskLeft = this.plotLeft - axisOffset[3] - 1, - maskBottom = this.plotTop + this.plotHeight + axisOffset[2] + 1, - maskRight = this.plotLeft + this.plotWidth + axisOffset[1] + 1, - maskPlotRight = this.plotLeft + this.plotWidth - - (this.scrollablePixelsX || 0), - maskPlotBottom = this.plotTop + this.plotHeight - - (this.scrollablePixelsY || 0), - d; - if (this.scrollablePixelsX) { - d = [ - // Left side - ['M', 0, maskTop], - ['L', this.plotLeft - 1, maskTop], - ['L', this.plotLeft - 1, maskBottom], - ['L', 0, maskBottom], - ['Z'], - // Right side - ['M', maskPlotRight, maskTop], - ['L', this.chartWidth, maskTop], - ['L', this.chartWidth, maskBottom], - ['L', maskPlotRight, maskBottom], - ['Z'] - ]; - } - else if (this.scrollablePixelsY) { - d = [ - // Top side - ['M', maskLeft, 0], - ['L', maskLeft, this.plotTop - 1], - ['L', maskRight, this.plotTop - 1], - ['L', maskRight, 0], - ['Z'], - // Bottom side - ['M', maskLeft, maskPlotBottom], - ['L', maskLeft, this.chartHeight], - ['L', maskRight, this.chartHeight], - ['L', maskRight, maskPlotBottom], - ['Z'] - ]; - } - else { - d = [['M', 0, 0]]; - } - if (this.redrawTrigger !== 'adjustHeight') { - this.scrollableMask.attr({ d: d }); - } - }; - - }); - _registerModule(_modules, 'Core/Axis/StackingAxis.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Utilities.js']], function (A, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var getDeferredAnimation = A.getDeferredAnimation; - var addEvent = U.addEvent, - destroyObjectProperties = U.destroyObjectProperties, - fireEvent = U.fireEvent, - objectEach = U.objectEach, - pick = U.pick; - /* eslint-disable valid-jsdoc */ - /** - * Adds stacking support to axes. - * @private - * @class - */ - var StackingAxisAdditions = /** @class */ (function () { - /* * - * - * Constructors - * - * */ - function StackingAxisAdditions(axis) { - this.oldStacks = {}; - this.stacks = {}; - this.stacksTouched = 0; - this.axis = axis; - } - /* * - * - * Functions - * - * */ - /** - * Build the stacks from top down - * @private - */ - StackingAxisAdditions.prototype.buildStacks = function () { - var stacking = this; - var axis = stacking.axis; - var axisSeries = axis.series; - var reversedStacks = pick(axis.options.reversedStacks, - true); - var len = axisSeries.length; - var actualSeries, - i; - if (!axis.isXAxis) { - stacking.usePercentage = false; - i = len; - while (i--) { - actualSeries = axisSeries[reversedStacks ? i : len - i - 1]; - actualSeries.setStackedPoints(); - actualSeries.setGroupedPoints(); - } - // Loop up again to compute percent and stream stack - for (i = 0; i < len; i++) { - axisSeries[i].modifyStacks(); - } - fireEvent(axis, 'afterBuildStacks'); - } - }; - /** - * @private - */ - StackingAxisAdditions.prototype.cleanStacks = function () { - var stacking = this; - var axis = stacking.axis; - var stacks; - if (!axis.isXAxis) { - if (stacking.oldStacks) { - stacks = stacking.stacks = stacking.oldStacks; - } - // reset stacks - objectEach(stacks, function (type) { - objectEach(type, function (stack) { - stack.cumulative = stack.total; - }); - }); - } - }; - /** - * Set all the stacks to initial states and destroy unused ones. - * @private - */ - StackingAxisAdditions.prototype.resetStacks = function () { - var stacking = this; - var axis = stacking.axis; - var stacks = stacking.stacks; - if (!axis.isXAxis) { - objectEach(stacks, function (type) { - objectEach(type, function (stack, key) { - // Clean up memory after point deletion (#1044, #4320) - if (stack.touched < stacking.stacksTouched) { - stack.destroy(); - delete type[key]; - // Reset stacks - } - else { - stack.total = null; - stack.cumulative = null; - } - }); - }); - } - }; - /** - * @private - */ - StackingAxisAdditions.prototype.renderStackTotals = function () { - var stacking = this; - var axis = stacking.axis; - var chart = axis.chart; - var renderer = chart.renderer; - var stacks = stacking.stacks; - var stackLabelsAnim = axis.options.stackLabels.animation; - var animationConfig = getDeferredAnimation(chart, - stackLabelsAnim); - var stackTotalGroup = stacking.stackTotalGroup = (stacking.stackTotalGroup || - renderer - .g('stack-labels') - .attr({ - visibility: 'visible', - zIndex: 6, - opacity: 0 - }) - .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); - }); - }); - stackTotalGroup.animate({ - opacity: 1 - }, animationConfig); - }; - return StackingAxisAdditions; - }()); - /** - * Axis with stacking support. - * @private - * @class - */ - var StackingAxis = /** @class */ (function () { - function StackingAxis() { - } - /* * - * - * Static Functions - * - * */ - /** - * Extends axis with stacking support. - * @private - */ - StackingAxis.compose = function (AxisClass) { - var axisProto = AxisClass.prototype; - addEvent(AxisClass, 'init', StackingAxis.onInit); - addEvent(AxisClass, 'destroy', StackingAxis.onDestroy); - }; - /** - * @private - */ - StackingAxis.onDestroy = function () { - var stacking = this.stacking; - if (!stacking) { - return; - } - var stacks = stacking.stacks; - // Destroy each stack total - objectEach(stacks, function (stack, stackKey) { - destroyObjectProperties(stack); - stacks[stackKey] = null; - }); - if (stacking && - stacking.stackTotalGroup) { - stacking.stackTotalGroup.destroy(); - } - }; - /** - * @private - */ - StackingAxis.onInit = function () { - var axis = this; - if (!axis.stacking) { - axis.stacking = new StackingAxisAdditions(axis); - } - }; - return StackingAxis; - }()); - - return StackingAxis; - }); - _registerModule(_modules, 'Mixins/LegendSymbol.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var merge = U.merge, - pick = U.pick; - /* eslint-disable valid-jsdoc */ - /** - * Legend symbol mixin. - * - * @private - * @mixin Highcharts.LegendSymbolMixin - */ - var LegendSymbolMixin = H.LegendSymbolMixin = { - /** - * Get the series' symbol in the legend - * - * @private - * @function Highcharts.LegendSymbolMixin.drawRectangle - * - * @param {Highcharts.Legend} legend - * The legend object - * - * @param {Highcharts.Point|Highcharts.Series} 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. - * - * @private - * @function Highcharts.LegendSymbolMixin.drawLineMarker - * - * @param {Highcharts.Legend} 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 - if (!this.chart.styledMode) { - 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 && symbolWidth) { - // 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; - } - } - }; - - return LegendSymbolMixin; - }); - _registerModule(_modules, 'Core/Series/CartesianSeries.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Series/Series.js'], _modules['Core/Globals.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/Options.js'], _modules['Core/Series/Point.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (A, BaseSeries, H, LegendSymbolMixin, O, Point, SVGElement, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var animObject = A.animObject; - var defaultOptions = O.defaultOptions; - var addEvent = U.addEvent, - arrayMax = U.arrayMax, - arrayMin = U.arrayMin, - clamp = U.clamp, - correctFloat = U.correctFloat, - defined = U.defined, - erase = U.erase, - error = U.error, - extend = U.extend, - find = U.find, - fireEvent = U.fireEvent, - getNestedProperty = U.getNestedProperty, - isArray = U.isArray, - isFunction = U.isFunction, - isNumber = U.isNumber, - isString = U.isString, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick, - removeEvent = U.removeEvent, - splat = U.splat, - syncTimeout = U.syncTimeout; - /** - * This is a placeholder type of the possible series options for - * [Highcharts](../highcharts/series), [Highstock](../highstock/series), - * [Highmaps](../highmaps/series), and [Gantt](../gantt/series). - * - * In TypeScript is this dynamically generated to reference all possible types - * of series options. - * - * @ignore-declaration - * @typedef {Highcharts.SeriesOptions|Highcharts.Dictionary<*>} Highcharts.SeriesOptionsType - */ - /** - * Options for `dataSorting`. - * - * @interface Highcharts.DataSortingOptionsObject - * @since 8.0.0 - */ /** - * Enable or disable data sorting for the series. - * @name Highcharts.DataSortingOptionsObject#enabled - * @type {boolean|undefined} - */ /** - * Whether to allow matching points by name in an update. - * @name Highcharts.DataSortingOptionsObject#matchByName - * @type {boolean|undefined} - */ /** - * Determines what data value should be used to sort by. - * @name Highcharts.DataSortingOptionsObject#sortKey - * @type {string|undefined} - */ - /** - * Function callback when a series has been animated. - * - * @callback Highcharts.SeriesAfterAnimateCallbackFunction - * - * @param {Highcharts.Series} this - * The series where the event occured. - * - * @param {Highcharts.SeriesAfterAnimateEventObject} event - * Event arguments. - */ - /** - * Event information regarding completed animation of a series. - * - * @interface Highcharts.SeriesAfterAnimateEventObject - */ /** - * Animated series. - * @name Highcharts.SeriesAfterAnimateEventObject#target - * @type {Highcharts.Series} - */ /** - * Event type. - * @name Highcharts.SeriesAfterAnimateEventObject#type - * @type {"afterAnimate"} - */ - /** - * Function callback when the checkbox next to the series' name in the legend is - * clicked. - * - * @callback Highcharts.SeriesCheckboxClickCallbackFunction - * - * @param {Highcharts.Series} this - * The series where the event occured. - * - * @param {Highcharts.SeriesCheckboxClickEventObject} event - * Event arguments. - */ - /** - * Event information regarding check of a series box. - * - * @interface Highcharts.SeriesCheckboxClickEventObject - */ /** - * Whether the box has been checked. - * @name Highcharts.SeriesCheckboxClickEventObject#checked - * @type {boolean} - */ /** - * Related series. - * @name Highcharts.SeriesCheckboxClickEventObject#item - * @type {Highcharts.Series} - */ /** - * Related series. - * @name Highcharts.SeriesCheckboxClickEventObject#target - * @type {Highcharts.Series} - */ /** - * Event type. - * @name Highcharts.SeriesCheckboxClickEventObject#type - * @type {"checkboxClick"} - */ - /** - * Function callback when a series is clicked. Return false to cancel toogle - * actions. - * - * @callback Highcharts.SeriesClickCallbackFunction - * - * @param {Highcharts.Series} this - * The series where the event occured. - * - * @param {Highcharts.SeriesClickEventObject} event - * Event arguments. - */ - /** - * Common information for a click event on a series. - * - * @interface Highcharts.SeriesClickEventObject - * @extends global.Event - */ /** - * Nearest point on the graph. - * @name Highcharts.SeriesClickEventObject#point - * @type {Highcharts.Point} - */ - /** - * Gets fired when the series is hidden after chart generation time, either by - * clicking the legend item or by calling `.hide()`. - * - * @callback Highcharts.SeriesHideCallbackFunction - * - * @param {Highcharts.Series} this - * The series where the event occured. - * - * @param {global.Event} event - * The event that occured. - */ - /** - * The SVG value used for the `stroke-linecap` and `stroke-linejoin` of a line - * graph. - * - * @typedef {"butt"|"round"|"square"|string} Highcharts.SeriesLinecapValue - */ - /** - * Gets fired when the legend item belonging to the series is clicked. The - * default action is to toggle the visibility of the series. This can be - * prevented by returning `false` or calling `event.preventDefault()`. - * - * @callback Highcharts.SeriesLegendItemClickCallbackFunction - * - * @param {Highcharts.Series} this - * The series where the event occured. - * - * @param {Highcharts.SeriesLegendItemClickEventObject} event - * The event that occured. - */ - /** - * Information about the event. - * - * @interface Highcharts.SeriesLegendItemClickEventObject - */ /** - * Related browser event. - * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent - * @type {global.PointerEvent} - */ /** - * Prevent the default action of toggle the visibility of the series. - * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault - * @type {Function} - */ /** - * Related series. - * @name Highcharts.SeriesCheckboxClickEventObject#target - * @type {Highcharts.Series} - */ /** - * Event type. - * @name Highcharts.SeriesCheckboxClickEventObject#type - * @type {"checkboxClick"} - */ - /** - * Gets fired when the mouse leaves the graph. - * - * @callback Highcharts.SeriesMouseOutCallbackFunction - * - * @param {Highcharts.Series} this - * Series where the event occured. - * - * @param {global.PointerEvent} event - * Event that occured. - */ - /** - * Gets fired when the mouse enters the graph. - * - * @callback Highcharts.SeriesMouseOverCallbackFunction - * - * @param {Highcharts.Series} this - * Series where the event occured. - * - * @param {global.PointerEvent} event - * Event that occured. - */ - /** - * Translation and scale for the plot area of a series. - * - * @interface Highcharts.SeriesPlotBoxObject - */ /** - * @name Highcharts.SeriesPlotBoxObject#scaleX - * @type {number} - */ /** - * @name Highcharts.SeriesPlotBoxObject#scaleY - * @type {number} - */ /** - * @name Highcharts.SeriesPlotBoxObject#translateX - * @type {number} - */ /** - * @name Highcharts.SeriesPlotBoxObject#translateY - * @type {number} - */ - /** - * Gets fired when the series is shown after chart generation time, either by - * clicking the legend item or by calling `.show()`. - * - * @callback Highcharts.SeriesShowCallbackFunction - * - * @param {Highcharts.Series} this - * Series where the event occured. - * - * @param {global.Event} event - * Event that occured. - */ - /** - * Possible key values for the series state options. - * - * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.SeriesStateValue - */ - ''; // detach doclets above - var seriesTypes = BaseSeries.seriesTypes, - win = H.win; - /** - * This is the base series prototype that all other series types inherit from. - * A new series is initialized either through the - * [series](https://api.highcharts.com/highcharts/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 - * [plotOptions.series](https://api.highcharts.com/highcharts/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 - * arguments 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 - * @name Highcharts.Series - * - * @param {Highcharts.Chart} chart - * The chart instance. - * - * @param {Highcharts.SeriesOptionsType|object} options - * The series options. - */ /** - * The line series is the base type and is therefor the series base prototype. - * - * @private - * @class - * @name Highcharts.seriesTypes.line - * - * @augments Highcharts.Series - */ - var CartesianSeries = BaseSeries.seriesType('line', - /** - * Series options for specific data and the data itself. In TypeScript you - * have to cast the series options to specific series types, - to get all - * possible options for a series. - * - * @example - * // TypeScript example - * Highcharts.chart('container', { - * series: [{ - * color: '#06C', - * data: [[0, 1], - [2, 3]] - * } as Highcharts.SeriesLineOptions ] - * }); - * - * @type {Array<*>} - * @apioption series - */ - /** - * An id for the series. This can be used after render time to get a pointer - * to the series object through `chart.get()`. - * - * @sample {highcharts} highcharts/plotoptions/series-id/ - * Get series by id - * - * @type {string} - * @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} - * @since 2.3.0 - * @apioption series.index - */ - /** - * The sequential index of the series in the legend. - * - * @see [legend.reversed](#legend.reversed), - * [yAxis.reversedStacks](#yAxis.reversedStacks) - * - * @sample {highcharts|highstock} highcharts/series/legendindex/ - * Legend in opposite order - * - * @type {number} - * @apioption series.legendIndex - */ - /** - * The name of the series as shown in the legend, tooltip etc. - * - * @sample {highcharts} highcharts/series/name/ - * Series name - * @sample {highmaps} maps/demo/category-map/ - * Series name - * - * @type {string} - * @apioption series.name - */ - /** - * This option allows grouping series in a stacked chart. The stack option - * can be a string or anything else, as long as the grouped series' stack - * options match each other after conversion into a string. - * - * @sample {highcharts} highcharts/series/stack/ - * Stacked and grouped columns - * - * @type {number|string} - * @since 2.1 - * @product highcharts highstock - * @apioption series.stack - */ - /** - * 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. - * - * @sample {highcharts} highcharts/series/type/ - * Line and column in the same chart - * @sample highcharts/series/type-dynamic/ - * Dynamic types with button selector - * @sample {highmaps} maps/demo/mapline-mappoint/ - * Multiple types in the same map - * - * @type {string} - * @apioption series.type - */ - /** - * When using dual or multiple x axes, this number defines which xAxis the - * particular series is connected to. It refers to either the - * {@link #xAxis.id|axis 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 - * {@link #yAxis.id|axis id} - * or the index of the axis in the yAxis array, with 0 being the first. - * - * @sample {highcharts} highcharts/series/yaxis/ - * Apply the column series to the secondary Y axis - * - * @type {number|string} - * @default 0 - * @product highcharts highstock - * @apioption series.yAxis - */ - /** - * Define the visual z index of the series. - * - * @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 - * - * @type {number} - * @product highcharts highstock - * @apioption series.zIndex - */ - void 0, - /** - * General options for all series types. - * - * @optionparent plotOptions.series - */ - { - /** - * 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. - * - * @type {Highcharts.SeriesLinecapValue} - * @default round - * @since 3.0.7 - * @apioption plotOptions.line.linecap - */ - /** - * Pixel width of the graph line. - * - * @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 - * - * @product highcharts highstock - * - * @private - */ - 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 does - * not 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). - * - * The selected points can be handled by point select and unselect - * events, or collectively by the [getSelectedPoints - * ](/class-reference/Highcharts.Chart#getSelectedPoints) function. - * - * And alternative way of selecting points is through dragging. - * - * @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 {highcharts} highcharts/chart/events-selection-points/ - * Select a range of points through a drag selection - * @sample {highmaps} maps/plotoptions/series-allowpointselect/ - * Map area - * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/ - * Map bubble - * - * @since 1.2.0 - * - * @private - */ - allowPointSelect: false, - /** - * When true, each point or 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. - * - * @sample {highcharts} highcharts/plotoptions/column-crisp-false/ - * Crisp is false - * - * @since 5.0.10 - * @product highcharts highstock gantt - * - * @private - */ - crisp: true, - /** - * 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. - * - * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/ - * Show select box - * - * @since 1.2.0 - * - * @private - */ - 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: - * - * - `defer`: The animation delay time in milliseconds. - * - * - `duration`: The duration of the animation in milliseconds. - * - * - `easing`: Can be a string reference to an easing function set on - * the `Math` object or a function. See the _Custom easing function_ - * demo below. - * - * Due to poor performance, animation is disabled in old IE browsers - * for several chart types. - * - * @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 - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - * @default {highcharts} true - * @default {highstock} true - * @default {highmaps} false - * - * @private - */ - animation: { - /** @internal */ - duration: 1000 - }, - /** - * @default 0 - * @type {number} - * @since 8.2.0 - * @apioption plotOptions.series.animation.defer - */ - /** - * An additional class name to apply to the series' graphical elements. - * This option does not replace default class names of the graphical - * element. - * - * @type {string} - * @since 5.0.0 - * @apioption plotOptions.series.className - */ - /** - * Disable this option to allow series rendering in the whole plotting - * area. - * - * **Note:** Clipping should be always enabled when - * [chart.zoomType](#chart.zoomType) is set - * - * @sample {highcharts} highcharts/plotoptions/series-clip/ - * Disabled clipping - * - * @default true - * @type {boolean} - * @since 3.0.0 - * @apioption plotOptions.series.clip - */ - /** - * 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. - * - * @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 {highcharts} highcharts/series/infographic/ - * Pattern fill - * @sample {highmaps} maps/demo/category-map/ - * Category map by multiple series - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/ - * False by default - * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/ - * True - * - * @type {boolean} - * @default false - * @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. - * - * 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 - * - * @type {string|Highcharts.CursorValue} - * @apioption plotOptions.series.cursor - */ - /** - * A reserved subspace to store options and values for customized - * functionality. Here you can add additional data for your own event - * callbacks and formatter callbacks. - * - * @sample {highcharts} highcharts/point/custom/ - * Point and series with custom data - * - * @type {Highcharts.Dictionary<*>} - * @apioption plotOptions.series.custom - */ - /** - * Name of the dash style to use for the graph, or for some series types - * the outline of each shape. - * - * In styled mode, the - * [stroke dash-array](https://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 - * - * @type {Highcharts.DashStyleValue} - * @default Solid - * @since 2.1 - * @apioption plotOptions.series.dashStyle - */ - /** - * A description of the series to add to the screen reader information - * about the series. - * - * @type {string} - * @since 5.0.0 - * @requires modules/accessibility - * @apioption plotOptions.series.description - */ - /** - * Options for the series data sorting. - * - * @type {Highcharts.DataSortingOptionsObject} - * @since 8.0.0 - * @product highcharts highstock - * @apioption plotOptions.series.dataSorting - */ - /** - * Enable or disable data sorting for the series. Use [xAxis.reversed]( - * #xAxis.reversed) to change the sorting order. - * - * @sample {highcharts} highcharts/datasorting/animation/ - * Data sorting in scatter-3d - * @sample {highcharts} highcharts/datasorting/labels-animation/ - * Axis labels animation - * @sample {highcharts} highcharts/datasorting/dependent-sorting/ - * Dependent series sorting - * @sample {highcharts} highcharts/datasorting/independent-sorting/ - * Independent series sorting - * - * @type {boolean} - * @since 8.0.0 - * @apioption plotOptions.series.dataSorting.enabled - */ - /** - * Whether to allow matching points by name in an update. If this option - * is disabled, points will be matched by order. - * - * @sample {highcharts} highcharts/datasorting/match-by-name/ - * Enabled match by name - * - * @type {boolean} - * @since 8.0.0 - * @apioption plotOptions.series.dataSorting.matchByName - */ - /** - * Determines what data value should be used to sort by. - * - * @sample {highcharts} highcharts/datasorting/sort-key/ - * Sort key as `z` value - * - * @type {string} - * @since 8.0.0 - * @default y - * @apioption plotOptions.series.dataSorting.sortKey - */ - /** - * 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. - * - * @sample {highcharts} highcharts/plotoptions/series-enablemousetracking-false/ - * No mouse tracking - * @sample {highmaps} maps/plotoptions/series-enablemousetracking-false/ - * No mouse tracking - * - * @type {boolean} - * @default true - * @apioption plotOptions.series.enableMouseTracking - */ - /** - * 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 gantt - * @apioption plotOptions.series.getExtremesFromAll - */ - /** - * 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. - * - * @see [series.data](#series.line.data) - * - * @sample {highcharts|highstock} highcharts/series/data-keys/ - * An extended data array with keys - * @sample {highcharts|highstock} highcharts/series/data-nested-keys/ - * Nested keys used to access object properties - * - * @type {Array<string>} - * @since 4.1.6 - * @apioption plotOptions.series.keys - */ - /** - * The line cap used for line ends and line joins on the graph. - * - * @type {Highcharts.SeriesLinecapValue} - * @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. - * - * If master series uses data sorting and linked series does not have - * its own sorting definition, the linked series will be sorted in the - * same order as the master one. - * - * @sample {highcharts|highstock} highcharts/demo/arearange-line/ - * Linked series - * - * @type {string} - * @since 3.0 - * @product highcharts highstock gantt - * @apioption plotOptions.series.linkedTo - */ - /** - * 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. - * - * @see [navigator.series](#navigator.series) - * - * @type {Highcharts.PlotSeriesOptions} - * @since 5.0.0 - * @product highstock - * @apioption plotOptions.series.navigatorOptions - */ - /** - * The color for the parts of the graph or points that are below the - * [threshold](#plotOptions.series.threshold). Note that `zones` takes - * precedence over the negative color. Using `negativeColor` is - * equivalent to applying a zone with value of 0. - * - * @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 - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @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. - * - * Please note that this options applies to the _series data_, not the - * interval of the axis ticks, which is independent. - * - * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/ - * Datetime X axis - * @sample {highstock} stock/plotoptions/pointinterval-pointstart/ - * Using pointStart and pointInterval - * - * @type {number} - * @default 1 - * @product highcharts highstock gantt - * @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. - * - * Please note that this options applies to the _series data_, not the - * interval of the axis ticks, which is independent. - * - * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/ - * One point a month - * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/ - * One point a month - * - * @type {string} - * @since 4.1.0 - * @product highcharts highstock gantt - * @validvalue ["day", "month", "year"] - * @apioption plotOptions.series.pointIntervalUnit - */ - /** - * Possible values: `"on"`, `"between"`, `number`. - * - * 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. - * - * For the `xrange` series type and gantt charts, if the Y axis is a - * category axis, the `pointPlacement` applies to the Y axis rather than - * the (typically datetime) X axis. - * - * Defaults to `undefined` in cartesian charts, `"between"` in polar - * charts. - * - * @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 - * @sample {highcharts|highstock} maps/plotoptions/heatmap-pointplacement/ - * Placement in heatmap - * - * @type {string|number} - * @since 2.3.0 - * @product highcharts highstock gantt - * @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. - * - * @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 - * - * @type {number} - * @default 0 - * @product highcharts highstock gantt - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-selected/ - * One out of two series selected - * - * @type {boolean} - * @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`. - * - * @sample {highcharts} highcharts/plotoptions/series-shadow/ - * Shadow enabled - * - * @type {boolean|Highcharts.ShadowOptionsObject} - * @default false - * @apioption plotOptions.series.shadow - */ - /** - * Whether to display this particular series or series type in the - * legend. Standalone series are shown in legend by default, and linked - * series are not. Since v7.2.0 it is possible to show series that use - * colorAxis by setting this option to `true`. - * - * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ - * One series in the legend, one hidden - * - * @type {boolean} - * @apioption plotOptions.series.showInLegend - */ - /** - * Whether or not to show the series in the navigator. Takes precedence - * over [navigator.baseSeries](#navigator.baseSeries) if defined. - * - * @type {boolean} - * @since 5.0.0 - * @product highstock - * @apioption plotOptions.series.showInNavigator - */ - /** - * 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 - */ - /** - * Whether to stack the values of each series on top of each other. - * Possible values are `undefined` to disable, `"normal"` to stack by - * value or `"percent"`. - * - * When stacking is enabled, data must be sorted - * in ascending X order. - * - * Some stacking options are related to specific series types. In the - * streamgraph series type, the stacking option is set to `"stream"`. - * The second one is `"overlap"`, which only applies to waterfall - * series. - * - * @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 {highcharts} highcharts/plotoptions/series-waterfall-with-normal-stacking - * Waterfall with normal stacking - * @sample {highcharts} highcharts/plotoptions/series-waterfall-with-overlap-stacking - * Waterfall with overlap stacking - * @sample {highstock} stock/plotoptions/stacking/ - * Area - * - * @type {string} - * @product highcharts highstock - * @validvalue ["normal", "overlap", "percent", "stream"] - * @apioption plotOptions.series.stacking - */ - /** - * Whether to apply steps to the line. Possible values are `left`, - * `center` and `right`. - * - * @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 - * - * @type {string} - * @since 1.2.5 - * @product highcharts highstock - * @validvalue ["left", "center", "right"] - * @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). - * - * @see [softThreshold](#plotOptions.series.softThreshold). - * - * @type {number} - * @default 0 - * @since 3.0 - * @product highcharts highstock - * @apioption plotOptions.series.threshold - */ - /** - * Set the initial visibility of the series. - * - * @sample {highcharts} highcharts/plotoptions/series-visible/ - * Two series, one hidden and one visible - * @sample {highstock} stock/plotoptions/series-visibility/ - * Hidden series - * - * @type {boolean} - * @default true - * @apioption plotOptions.series.visible - */ - /** - * Defines the Axis on which the zones are applied. - * - * @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 - * - * @type {string} - * @default y - * @since 4.1.0 - * @product highcharts highstock - * @apioption plotOptions.series.zoneAxis - */ - /** - * 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. - * - * @declare Highcharts.SeriesEventsOptionsObject - * - * @private - */ - events: {}, - /** - * Fires after the series has finished its initial animation, or in case - * animation is disabled, immediately as the series is displayed. - * - * @sample {highcharts} highcharts/plotoptions/series-events-afteranimate/ - * Show label after animate - * @sample {highstock} highcharts/plotoptions/series-events-afteranimate/ - * Show label after animate - * - * @type {Highcharts.SeriesAfterAnimateCallbackFunction} - * @since 4.0 - * @product highcharts highstock gantt - * @context Highcharts.Series - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/ - * Alert checkbox status - * - * @type {Highcharts.SeriesCheckboxClickCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Series - * @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. - * - * @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 - * - * @type {Highcharts.SeriesClickCallbackFunction} - * @context Highcharts.Series - * @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()`. - * - * @sample {highcharts} highcharts/plotoptions/series-events-hide/ - * Alert when the series is hidden by clicking the legend item - * - * @type {Highcharts.SeriesHideCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Series - * @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()`. - * - * @sample {highcharts} highcharts/plotoptions/series-events-legenditemclick/ - * Confirm hiding and showing - * - * @type {Highcharts.SeriesLegendItemClickCallbackFunction} - * @context Highcharts.Series - * @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. - * - * @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 - * - * @type {Highcharts.SeriesMouseOutCallbackFunction} - * @context Highcharts.Series - * @apioption plotOptions.series.events.mouseOut - */ - /** - * Fires when the mouse enters the graph. One parameter, `event`, is - * passed to the function, containing common event information. - * - * @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 - * - * @type {Highcharts.SeriesMouseOverCallbackFunction} - * @context Highcharts.Series - * @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()`. - * - * @sample {highcharts} highcharts/plotoptions/series-events-show/ - * Alert when the series is shown by clicking the legend item. - * - * @type {Highcharts.SeriesShowCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Series - * @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. - * - * @declare Highcharts.PointMarkerOptionsObject - * - * @private - */ - marker: { - /** - * Enable or disable the point marker. If `undefined`, the markers - * are hidden when the data is dense, and shown for more widespread - * data points. - * - * @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 - * - * @type {boolean} - * @default {highcharts} undefined - * @default {highstock} false - * @apioption plotOptions.series.marker.enabled - */ - /** - * The threshold for how dense the point markers should be before - * they are hidden, given that `enabled` is not defined. The number - * indicates the horizontal distance between the two closest points - * in the series, as multiples of the `marker.radius`. In other - * words, the default value of 2 means points are hidden if - * overlapping horizontally. - * - * @sample highcharts/plotoptions/series-marker-enabledthreshold - * A higher threshold - * - * @since 6.0.5 - */ - enabledThreshold: 2, - /** - * The fill color of the point marker. When `undefined`, the series' - * or point's color is used. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ - * White fill - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @apioption plotOptions.series.marker.fillColor - */ - /** - * Image markers only. Set the image width explicitly. When using - * this option, a `width` must also be set. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/ - * Fixed width and height - * @sample {highstock} highcharts/plotoptions/series-marker-width-height/ - * Fixed width and height - * - * @type {number} - * @since 4.0.4 - * @apioption plotOptions.series.marker.height - */ - /** - * The color of the point marker's outline. When `undefined`, the - * series' or point's color is used. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ - * Inherit from series color (undefined) - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ - lineColor: '#ffffff', - /** - * The width of the point marker's outline. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ - * 2px blue marker - */ - lineWidth: 0, - /** - * The radius of the point marker. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-radius/ - * Bigger markers - * - * @default {highstock} 2 - */ - radius: 4, - /** - * A predefined shape or symbol for the marker. When undefined, 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. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/ - * Predefined, graphic and custom markers - * @sample {highstock} highcharts/plotoptions/series-marker-symbol/ - * Predefined, graphic and custom markers - * - * @type {string} - * @apioption plotOptions.series.marker.symbol - */ - /** - * Image markers only. Set the image width explicitly. When using - * this option, a `height` must also be set. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/ - * Fixed width and height - * @sample {highstock} highcharts/plotoptions/series-marker-width-height/ - * Fixed width and height - * - * @type {number} - * @since 4.0.4 - * @apioption plotOptions.series.marker.width - */ - /** - * States for a single point marker. - * - * @declare Highcharts.PointStatesOptionsObject - */ - states: { - /** - * The normal state of a single point marker. Currently only - * used for setting animation when returning to normal state - * from hover. - * - * @declare Highcharts.PointStatesNormalOptionsObject - */ - normal: { - /** - * Animation when returning to normal state after hovering. - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - */ - animation: true - }, - /** - * The hover state for a single point marker. - * - * @declare Highcharts.PointStatesHoverOptionsObject - */ - hover: { - /** - * Animation when hovering over the marker. - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - */ - animation: { - /** @internal */ - duration: 50 - }, - /** - * Enable or disable the point marker. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-enabled/ - * Disabled hover state - */ - enabled: true, - /** - * The fill color of the marker in hover state. When - * `undefined`, the series' or point's fillColor for normal - * state is used. - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @apioption plotOptions.series.marker.states.hover.fillColor - */ - /** - * The color of the point marker's outline. When - * `undefined`, the series' or point's lineColor for normal - * state is used. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linecolor/ - * White fill color, black line color - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @apioption plotOptions.series.marker.states.hover.lineColor - */ - /** - * The width of the point marker's outline. When - * `undefined`, the series' or point's lineWidth for normal - * state is used. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linewidth/ - * 3px line width - * - * @type {number} - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/ - * 10px radius - * - * @type {number} - * @apioption plotOptions.series.marker.states.hover.radius - */ - /** - * The number of pixels to increase the radius of the - * hovered point. - * - * @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 - * - * @since 4.0.3 - */ - radiusPlus: 2, - /** - * The additional line width for a hovered point. - * - * @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 - * - * @since 4.0.3 - */ - 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. - * - * @declare Highcharts.PointStatesSelectOptionsObject - */ - select: { - /** - * Enable or disable visible feedback for selection. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/ - * Disabled select state - * - * @type {boolean} - * @default true - * @apioption plotOptions.series.marker.states.select.enabled - */ - /** - * The radius of the point marker. In hover state, it - * defaults to the normal state's radius + 2. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/ - * 10px radius for selected points - * - * @type {number} - * @apioption plotOptions.series.marker.states.select.radius - */ - /** - * The fill color of the point marker. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/ - * Solid red discs for selected points - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ - fillColor: '#cccccc', - /** - * The color of the point marker's outline. When - * `undefined`, the series' or point's color is used. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/ - * Red line color for selected points - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - */ - lineColor: '#000000', - /** - * The width of the point marker's outline. - * - * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/ - * 3px line width for selected points - */ - lineWidth: 2 - } - } - }, - /** - * Properties for each single point. - * - * @declare Highcharts.PlotSeriesPointOptions - * - * @private - */ - point: { - /** - * 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. - * - * @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 - * - * @type {Highcharts.PointClickCallbackFunction} - * @context Highcharts.Point - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ - * Show values in the chart's corner on mouse over - * - * @type {Highcharts.PointMouseOutCallbackFunction} - * @context Highcharts.Point - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ - * Show values in the chart's corner on mouse over - * - * @type {Highcharts.PointMouseOverCallbackFunction} - * @context Highcharts.Point - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/ - * Remove point and confirm - * - * @type {Highcharts.PointRemoveCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Point - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-point-events-select/ - * Report the last selected point - * @sample {highmaps} maps/plotoptions/series-allowpointselect/ - * Report select and unselect - * - * @type {Highcharts.PointSelectCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Point - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/ - * Report the last unselected point - * @sample {highmaps} maps/plotoptions/series-allowpointselect/ - * Report select and unselect - * - * @type {Highcharts.PointUnselectCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Point - * @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. - * - * @sample {highcharts} highcharts/plotoptions/series-point-events-update/ - * Confirm point updating - * - * @type {Highcharts.PointUpdateCallbackFunction} - * @since 1.2.0 - * @context Highcharts.Point - * @apioption plotOptions.series.point.events.update - */ - /** - * Events for each single point. - * - * @declare Highcharts.PointEventsOptionsObject - */ - events: {} - }, - /** - * Options for the series data labels, appearing next to each data - * point. - * - * Since v6.2.0, multiple data labels can be applied to each single - * point by defining them as an array of configs. - * - * In styled mode, the data labels can be styled with the - * `.highcharts-data-label-box` and `.highcharts-data-label` class names - * ([see example](https://www.highcharts.com/samples/highcharts/css/series-datalabels)). - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled - * Data labels enabled - * @sample {highcharts} highcharts/plotoptions/series-datalabels-multiple - * Multiple data labels on a bar series - * @sample {highcharts} highcharts/css/series-datalabels - * Style mode example - * - * @type {*|Array<*>} - * @product highcharts highstock highmaps gantt - * - * @private - */ - dataLabels: { - /** - * Enable or disable the initial animation when a series is - * displayed for the `dataLabels`. The animation can also be set as - * a configuration object. Please note that this option only - * applies to the initial animation. - * For other animations, see [chart.animation](#chart.animation) - * and the animation parameter under the API methods. - * The following properties are supported: - * - * - `defer`: The animation delay time in milliseconds. - * - * @sample {highcharts} highcharts/plotoptions/animation-defer/ - * Animation defer settings - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - * @since 8.2.0 - * @apioption plotOptions.series.dataLabels.animation - */ - animation: {}, - /** - * The animation delay time in milliseconds. - * Set to `0` renders dataLabel immediately. - * As `undefined` inherits defer time from the [series.animation.defer](#plotOptions.series.animation.defer). - * - * @type {number} - * @since 8.2.0 - * @apioption plotOptions.series.dataLabels.animation.defer - */ - /** - * 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`. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/ - * Left aligned - * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/ - * Data labels inside the bar - * - * @type {Highcharts.AlignValue|null} - */ - 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. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/ - * Don't allow overlap - * - * @type {boolean} - * @default false - * @since 4.1.0 - * @apioption plotOptions.series.dataLabels.allowOverlap - */ - /** - * The background color or gradient for the data label. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ - * Data labels box options - * @sample {highmaps} maps/plotoptions/series-datalabels-box/ - * Data labels box options - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @since 2.2.1 - * @apioption plotOptions.series.dataLabels.backgroundColor - */ - /** - * The border color for the data label. Defaults to `undefined`. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ - * Data labels box options - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @since 2.2.1 - * @apioption plotOptions.series.dataLabels.borderColor - */ - /** - * The border radius in pixels for the data label. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ - * Data labels box options - * @sample {highmaps} maps/plotoptions/series-datalabels-box/ - * Data labels box options - * - * @type {number} - * @default 0 - * @since 2.2.1 - * @apioption plotOptions.series.dataLabels.borderRadius - */ - /** - * The border width in pixels for the data label. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ - * Data labels box options - * - * @type {number} - * @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. - * - * @sample {highcharts} highcharts/css/data-label-contrast/ - * Contrast text shadow - * @sample {highcharts} highcharts/css/series-datalabels/ - * Styling by CSS - * - * @type {string} - * @since 5.0.0 - * @apioption plotOptions.series.dataLabels.className - */ - /** - * The text color for the data labels. Defaults to `undefined`. 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. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/ - * Red data labels - * @sample {highmaps} maps/demo/color-axis/ - * White data labels - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @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. Setting to `false` renders the - * data label immediately. If set to `true` inherits the defer - * time set in [plotOptions.series.animation](#plotOptions.series.animation). - * - * @sample highcharts/plotoptions/animation-defer - * Set defer time - * - * @since 4.0.0 - * @product highcharts highstock gantt - */ - defer: true, - /** - * Enable or disable the data labels. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/ - * Data labels enabled - * @sample {highmaps} maps/demo/color-axis/ - * Data labels enabled - * - * @type {boolean} - * @default false - * @apioption plotOptions.series.dataLabels.enabled - */ - /** - * A declarative filter to control of 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 - * `undefined` to disable a single data label. - * - * @example - * filter: { - * property: 'percentage', - * operator: '>', - * value: 4 - * } - * - * @sample {highcharts} highcharts/demo/pie-monochrome - * Data labels filtered by percentage - * - * @declare Highcharts.DataLabelsFilterOptionsObject - * @since 6.0.3 - * @apioption plotOptions.series.dataLabels.filter - */ - /** - * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`, - * `==`, and `===`. - * - * @type {string} - * @validvalue [">", "<", ">=", "<=", "==", "==="] - * @apioption plotOptions.series.dataLabels.filter.operator - */ - /** - * The point property to filter by. Point options are passed - * directly to properties, additionally there are `y` value, - * `percentage` and others listed under {@link Highcharts.Point} - * members. - * - * @type {string} - * @apioption plotOptions.series.dataLabels.filter.property - */ - /** - * The value to compare against. - * - * @type {number} - * @apioption plotOptions.series.dataLabels.filter.value - */ - /** - * A - * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) - * for the data label. Available variables are the same as for - * `formatter`. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ - * Add a unit - * @sample {highmaps} maps/plotoptions/series-datalabels-format/ - * Formatted value in the data label - * - * @type {string} - * @default y - * @default point.value - * @since 3.0 - * @apioption plotOptions.series.dataLabels.format - */ - // eslint-disable-next-line valid-jsdoc - /** - * Callback JavaScript function to format the data label. Note that - * if a `format` is defined, the format takes precedence and the - * formatter is ignored. - * - * @sample {highmaps} maps/plotoptions/series-datalabels-format/ - * Formatted value - * - * @type {Highcharts.DataLabelsFormatterCallbackFunction} - */ - formatter: function () { - var numberFormatter = this.series.chart.numberFormatter; - return typeof this.y !== 'number' ? '' : numberFormatter(this.y, -1); - }, - /** - * 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 - */ - /** - * Format for points with the value of null. Works analogously to - * [format](#plotOptions.series.dataLabels.format). `nullFormat` can - * be applied only to series which support displaying null points. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ - * Format data label and tooltip for null point. - * - * @type {boolean|string} - * @since 7.1.0 - * @apioption plotOptions.series.dataLabels.nullFormat - */ - /** - * Callback JavaScript function that defines formatting for points - * with the value of null. Works analogously to - * [formatter](#plotOptions.series.dataLabels.formatter). - * `nullPointFormatter` can be applied only to series which support - * displaying null points. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ - * Format data label and tooltip for null point. - * - * @type {Highcharts.DataLabelsFormatterCallbackFunction} - * @since 7.1.0 - * @apioption plotOptions.series.dataLabels.nullFormatter - */ - /** - * 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 `"allow"`. - * - * @type {Highcharts.DataLabelsOverflowValue} - * @default justify - * @since 3.0.6 - * @apioption plotOptions.series.dataLabels.overflow - */ - /** - * When either the `borderWidth` or the `backgroundColor` is set, - * this is the padding within the box. - * - * @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 - */ - padding: 5, - /** - * Aligns data labels relative to points. If `center` alignment is - * not possible, it defaults to `right`. - * - * @type {Highcharts.AlignValue} - * @default center - * @apioption plotOptions.series.dataLabels.position - */ - /** - * Text rotation in degrees. Note that due to a more complex - * structure, backgrounds, borders and padding will be lost on a - * rotated data label. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ - * Vertical labels - * - * @type {number} - * @default 0 - * @apioption plotOptions.series.dataLabels.rotation - */ - /** - * 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`. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ - * Data labels box options - * - * @type {boolean|Highcharts.ShadowOptionsObject} - * @default false - * @since 2.2.1 - * @apioption plotOptions.series.dataLabels.shadow - */ - /** - * The name of a symbol to use for the border around the label. - * Symbols are predefined functions on the Renderer object. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/ - * A callout for annotations - * - * @type {string} - * @default square - * @since 4.1.2 - * @apioption plotOptions.series.dataLabels.shape - */ - /** - * 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. - * - * For some series types, where each point has an extent, like for - * example tree maps, the data label may overflow the point. There - * are two strategies for handling overflow. By default, the text - * will wrap to multiple lines. The other strategy is to set - * `style.textOverflow` to `ellipsis`, which will keep the text on - * one line plus it will break inside long words. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/ - * Bold labels - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow/ - * Long labels truncated with an ellipsis in a pie - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap/ - * Long labels are wrapped in a pie - * @sample {highmaps} maps/demo/color-axis/ - * Bold labels - * - * @type {Highcharts.CSSObject} - * @since 4.1.0 - * @apioption plotOptions.series.dataLabels.style - */ - style: { - /** @internal */ - fontSize: '11px', - /** @internal */ - fontWeight: 'bold', - /** @internal */ - color: 'contrast', - /** @internal */ - textOutline: '1px contrast' - }, - /** - * Options for a label text which should follow marker's shape. - * Border and background are disabled for a label that follows a - * path. - * - * **Note:** Only SVG-based renderer supports this option. Setting - * `useHTML` to true will disable this option. - * - * @declare Highcharts.DataLabelsTextPathOptionsObject - * @since 7.1.0 - * @apioption plotOptions.series.dataLabels.textPath - */ - /** - * Presentation attributes for the text path. - * - * @type {Highcharts.SVGAttributes} - * @since 7.1.0 - * @apioption plotOptions.series.dataLabels.textPath.attributes - */ - /** - * Enable or disable `textPath` option for link's or marker's data - * labels. - * - * @type {boolean} - * @since 7.1.0 - * @apioption plotOptions.series.dataLabels.textPath.enabled - */ - /** - * Whether to - * [use HTML](https://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. - * - * @type {Highcharts.VerticalAlignValue|null} - * @since 2.3.3 - */ - verticalAlign: 'bottom', - /** - * The x position offset of the label relative to the point in - * pixels. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ - * Vertical and positioned - * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/ - * Data labels inside the bar - */ - x: 0, - /** - * 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.z - */ - /** - * The y position offset of the label relative to the point in - * pixels. - * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ - * Vertical and positioned - */ - y: 0 + this.cross.e = e; + } + fireEvent(this, "afterDrawCrosshair", { e: e, point: point }); + }; + /** + * Hide the crosshair if visible. + * + * @function Highcharts.Axis#hideCrosshair + */ + Axis.prototype.hideCrosshair = function () { + if (this.cross) { + this.cross.hide(); + } + fireEvent(this, "afterHideCrosshair"); + }; + /** + * Check whether the chart has vertical panning ('y' or 'xy' type). + * + * @private + * @function Highcharts.Axis#hasVerticalPanning + * @return {boolean} + * + */ + Axis.prototype.hasVerticalPanning = function () { + var _a, _b; + return /y/.test( + ((_b = + (_a = this.chart.options.chart) === null || _a === void 0 + ? void 0 + : _a.panning) === null || _b === void 0 + ? void 0 + : _b.type) || "" + ); + }; + /** + * Check whether the given value is a positive valid axis value. + * + * @private + * @function Highcharts.Axis#validatePositiveValue + * + * @param {unknown} value + * The axis value + * @return {boolean} + * + */ + Axis.prototype.validatePositiveValue = function (value) { + return isNumber(value) && value > 0; + }; + /* * + * + * Static Properties + * + * */ + /** + * 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 class](/class-reference/Highcharts.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. + * + * @type {*|Array<*>} + * @optionparent xAxis + * + * @private + */ + Axis.defaultOptions = { + /** + * 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. + * + * If `startOnTick` or `endOnTick` in an Axis options are set to false, + * then the `alignTicks ` will be disabled for the Axis. + * + * Disabled for logarithmic axes. + * + * @type {boolean} + * @default true + * @product highcharts highstock gantt + * @apioption xAxis.alignTicks + */ + /** + * 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. + * + * @see [minTickInterval](#xAxis.minTickInterval) + * + * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-true/ + * True by default + * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-false/ + * False + * + * @type {boolean} + * @default true + * @since 2.0 + * @apioption xAxis.allowDecimals + */ + /** + * When using an alternate grid color, a band is painted across the + * plot area between every other grid line. + * + * @sample {highcharts} highcharts/yaxis/alternategridcolor/ + * Alternate grid color on the Y axis + * @sample {highstock} stock/xaxis/alternategridcolor/ + * Alternate grid color on the Y axis + * + * @type {Highcharts.ColorType} + * @apioption xAxis.alternateGridColor + */ + /** + * 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. + * + * @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 + * + * @type {Array<*>} + * @since 4.1.0 + * @product highcharts highstock gantt + * @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 gantt + * @apioption xAxis.breaks.breakSize + */ + /** + * The point where the break starts. + * + * @type {number} + * @since 4.1.0 + * @product highcharts highstock gantt + * @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 gantt + * @apioption xAxis.breaks.repeat + */ + /** + * The point where the break ends. + * + * @type {number} + * @since 4.1.0 + * @product highcharts highstock gantt + * @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']` + * + * @sample {highcharts} highcharts/demo/line-labels/ + * With + * @sample {highcharts} highcharts/xaxis/categories/ + * Without + * + * @type {Array<string>} + * @product highcharts gantt + * @apioption xAxis.categories + */ + /** + * The highest allowed value for automatically computed axis extremes. + * + * @see [floor](#xAxis.floor) + * + * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/ + * Floor and ceiling + * + * @type {number} + * @since 4.0 + * @product highcharts highstock gantt + * @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. + * + * @sample {highcharts|highstock|highmaps} highcharts/css/axis/ + * Multiple axes with separate styling + * + * @type {string} + * @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, by default, the crosshair is enabled on the X axis and + * disabled on the Y axis. + * + * @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 + * + * @declare Highcharts.AxisCrosshairOptions + * @type {boolean|*} + * @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. + * + * @sample {highcharts|highstock|highmaps} highcharts/xaxis/crosshair-customized/ + * Customized crosshairs + * + * @type {Highcharts.ColorType} + * @default #cccccc + * @since 4.1 + * @apioption xAxis.crosshair.color + */ + /** + * The dash style for the crosshair. See + * [plotOptions.series.dashStyle](#plotOptions.series.dashStyle) + * for possible values. + * + * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/ + * Dotted crosshair + * @sample {highstock} stock/xaxis/crosshair-dashed/ + * Dashed X axis crosshair + * + * @type {Highcharts.DashStyleValue} + * @default Solid + * @since 4.1 + * @apioption xAxis.crosshair.dashStyle + */ + /** + * A label on the axis next to the crosshair. + * + * In styled mode, the label is styled with the + * `.highcharts-crosshair-label` class. + * + * @sample {highstock} stock/xaxis/crosshair-label/ + * Crosshair labels + * @sample {highstock} highcharts/css/crosshair-label/ + * Style mode + * + * @declare Highcharts.AxisCrosshairLabelOptions + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label + */ + /** + * Alignment of the label compared to the axis. Defaults to `"left"` for + * right-side axes, `"right"` for left-side axes and `"center"` for + * horizontal axes. + * + * @type {Highcharts.AlignValue} + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.align + */ + /** + * The background color for the label. Defaults to the related series + * color, or `#666666` if that is not available. + * + * @type {Highcharts.ColorType} + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.backgroundColor + */ + /** + * The border color for the crosshair label + * + * @type {Highcharts.ColorType} + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.borderColor + */ + /** + * The border corner radius of the crosshair label. + * + * @type {number} + * @default 3 + * @since 2.1.10 + * @product highstock + * @apioption xAxis.crosshair.label.borderRadius + */ + /** + * The border width for the crosshair label. + * + * @type {number} + * @default 0 + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.borderWidth + */ + /** + * Flag to enable crosshair's label. + * + * @sample {highstock} stock/xaxis/crosshairs-xy/ + * Enabled label for yAxis' crosshair + * + * @type {boolean} + * @default false + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.enabled + */ + /** + * A format string for the crosshair label. Defaults to `{value}` for + * numeric axes and `{value:%b %d, %Y}` for datetime axes. + * + * @type {string} + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.format + */ + /** + * Formatter function for the label text. + * + * @type {Highcharts.XAxisCrosshairLabelFormatterCallbackFunction} + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.formatter + */ + /** + * Padding inside the crosshair label. + * + * @type {number} + * @default 8 + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.padding + */ + /** + * The shape to use for the label box. + * + * @type {string} + * @default callout + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.shape + */ + /** + * Text styles for the crosshair label. + * + * @type {Highcharts.CSSObject} + * @default {"color": "white", "fontWeight": "normal", "fontSize": "11px", "textAlign": "center"} + * @since 2.1 + * @product highstock + * @apioption xAxis.crosshair.label.style + */ + /** + * Whether the crosshair should snap to the point or follow the pointer + * independent of points. + * + * @sample {highcharts|highstock} highcharts/xaxis/crosshair-snap-false/ + * True by default + * @sample {highmaps} maps/demo/latlon-advanced/ + * Snap is false + * + * @type {boolean} + * @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. + * + * @sample {highcharts} highcharts/xaxis/crosshair-customized/ + * Customized crosshairs + * @sample {highstock} highcharts/xaxis/crosshair-customized/ + * Customized crosshairs + * @sample {highmaps} highcharts/xaxis/crosshair-customized/ + * Customized crosshairs + * + * @type {number} + * @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 + */ + /** + * Whether to zoom axis. If `chart.zoomType` is set, the option allows + * to disable zooming on an individual axis. + * + * @sample {highcharts} highcharts/xaxis/zoomenabled/ + * Zoom enabled is false + * + * + * @type {boolean} + * @default enabled + * @apioption xAxis.zoomEnabled + */ + /** + * 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](/class-reference/Highcharts#dateFormat). + * + * Defaults to: + * ```js + * { + * 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' + * } + * ``` + * + * @sample {highcharts} highcharts/xaxis/datetimelabelformats/ + * Different day format on X axis + * @sample {highstock} stock/xaxis/datetimelabelformats/ + * More information in x axis labels + * + * @declare Highcharts.AxisDateTimeLabelFormatsOptions + * @product highcharts highstock gantt + */ + dateTimeLabelFormats: { + /** + * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject + * @type {string|*} + */ + millisecond: { + main: "%H:%M:%S.%L", + range: false, }, /** - * 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. - * - * @since 2.2 - * @product highcharts highstock - * - * @private + * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject + * @type {string|*} */ - cropThreshold: 300, + second: { + main: "%H:%M:%S", + range: false, + }, /** - * Opacity of a series parts: line, fill (e.g. area) and dataLabels. - * - * @see [states.inactive.opacity](#plotOptions.series.states.inactive.opacity) - * - * @since 7.1.0 - * - * @private + * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject + * @type {string|*} */ - opacity: 1, + minute: { + main: "%H:%M", + range: false, + }, /** - * 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. - * - * @product highstock - * - * @private + * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject + * @type {string|*} */ - pointRange: 0, + hour: { + main: "%H:%M", + range: false, + }, /** - * 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. - * - * @since 4.1.9 - * @product highcharts highstock - * - * @private + * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject + * @type {string|*} */ - softThreshold: true, + day: { + main: "%e. %b", + }, /** - * @declare Highcharts.SeriesStatesOptionsObject - * - * @private + * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject + * @type {string|*} */ - states: { - /** - * The normal state of a series, or for point items in column, pie - * and similar series. Currently only used for setting animation - * when returning to normal state from hover. - * - * @declare Highcharts.SeriesStatesNormalOptionsObject - */ - normal: { - /** - * Animation when returning to normal state after hovering. - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - */ - animation: true - }, - /** - * Options for the hovered series. These settings override the - * normal state options when a series is moused over or touched. - * - * @declare Highcharts.SeriesStatesHoverOptionsObject - */ - hover: { - /** - * Enable separate styles for the hovered series to visualize - * that the user hovers either the series itself or the legend. - * - * @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 - * - * @type {boolean} - * @default true - * @since 1.2 - * @apioption plotOptions.series.states.hover.enabled - */ - /** - * Animation setting for hovering the graph in line-type series. - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - * @since 5.0.8 - * @product highcharts highstock - */ - animation: { - /** - * The duration of the hover animation in milliseconds. By - * default the hover state animates quickly in, and slowly - * back to normal. - * - * @internal - */ - duration: 50 - }, - /** - * Pixel width of the graph line. By default this property is - * undefined, and the `lineWidthPlus` property dictates how much - * to increase the linewidth from normal state. - * - * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/ - * 5px line on hover - * - * @type {number} - * @product highcharts highstock - * @apioption plotOptions.series.states.hover.lineWidth - */ - /** - * The additional line width for the graph of a hovered series. - * - * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ - * 5 pixels wider - * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ - * 5 pixels wider - * - * @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). - * - * @deprecated - * - * @extends plotOptions.series.marker - * @excluding states - * @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 `null`. - * - * In styled mode, the halo is styled with the - * `.highcharts-halo` class, with colors inherited from - * `.highcharts-color-{n}`. - * - * @sample {highcharts} highcharts/plotoptions/halo/ - * Halo options - * @sample {highstock} highcharts/plotoptions/halo/ - * Halo options - * - * @declare Highcharts.SeriesStatesHoverHaloOptionsObject - * @type {null|*} - * @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 {Highcharts.SVGAttributes} - * @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. - * - * @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. - * - * @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. - * - * @sample maps/plotoptions/series-allowpointselect/ - * Allow point select demo - * - * @declare Highcharts.SeriesStatesSelectOptionsObject - * @extends plotOptions.series.states.hover - * @excluding brightness - */ - select: { - animation: { - /** @internal */ - duration: 0 - } - }, - /** - * The opposite state of a hover for series. - * - * @sample highcharts/plotoptions/series-states-inactive-disabled - * Disabled inactive state - * - * @declare Highcharts.SeriesStatesInactiveOptionsObject - */ - inactive: { - /** - * Enable or disable the inactive state for a series - * - * @sample highcharts/plotoptions/series-states-inactive-disabled - * Disabled inactive state - * - * @type {boolean} - * @default true - * @apioption plotOptions.series.states.inactive.enabled - */ - /** - * The animation for entering the inactive state. - * - * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} - */ - animation: { - /** @internal */ - duration: 50 - }, - /** - * Opacity of series elements (dataLabels, line, area). - * - * @type {number} - */ - opacity: 0.2 - } + week: { + main: "%e. %b", }, /** - * 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. - * - * **Note:** The boost module will force this option because of - * technical limitations. - * - * @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 - * - * @private + * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject + * @type {string|*} */ - stickyTracking: true, + month: { + main: "%b '%y", + }, /** - * 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. - * - * @declare Highcharts.SeriesTooltipOptionsObject - * @since 2.3 - * @extends tooltip - * @excluding animation, backgroundColor, borderColor, borderRadius, - * borderWidth, className, crosshairs, enabled, formatter, - * headerShape, hideDelay, outside, padding, positioner, - * shadow, shape, shared, snap, split, stickOnContact, - * style, useHTML - * @apioption plotOptions.series.tooltip + * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject + * @type {string|*} */ - /** - * 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. + year: { + main: "%Y", + }, + }, + /** + * 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. + * + * @type {*} + * @apioption xAxis.events + */ + /** + * An event fired after the breaks have rendered. + * + * @see [breaks](#xAxis.breaks) + * + * @sample {highcharts} highcharts/axisbreak/break-event/ + * AfterBreak Event + * + * @type {Highcharts.AxisEventCallbackFunction} + * @since 4.1.0 + * @product highcharts gantt + * @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 {Highcharts.AxisSetExtremesEventCallbackFunction} + * @since 2.3 + * @context Highcharts.Axis + * @apioption xAxis.events.afterSetExtremes + */ + /** + * An event fired when a break from this axis occurs on a point. + * + * @see [breaks](#xAxis.breaks) + * + * @sample {highcharts} highcharts/axisbreak/break-visualized/ + * Visualization of a Break + * + * @type {Highcharts.AxisPointBreakEventCallbackFunction} + * @since 4.1.0 + * @product highcharts gantt + * @context Highcharts.Axis + * @apioption xAxis.events.pointBreak + */ + /** + * An event fired when a point falls inside a break from this axis. + * + * @type {Highcharts.AxisPointBreakEventCallbackFunction} + * @product highcharts highstock gantt + * @context Highcharts.Axis + * @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`. + * + * @sample {highstock} stock/xaxis/events-setextremes/ + * Log new extremes on x axis + * + * @type {Highcharts.AxisSetExtremesEventCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Axis + * @apioption xAxis.events.setExtremes + */ + /** + * The lowest allowed value for automatically computed axis extremes. + * + * @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 + * + * @type {number} + * @since 4.0 + * @product highcharts highstock gantt + * @apioption xAxis.floor + */ + /** + * The dash or dot style of the grid lines. For possible values, see + * [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). + * + * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/ + * Long dashes + * @sample {highstock} stock/xaxis/gridlinedashstyle/ + * Long dashes + * + * @type {Highcharts.DashStyleValue} + * @default Solid + * @since 1.2 + * @apioption xAxis.gridLineDashStyle + */ + /** + * The Z index of the grid lines. + * + * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/ + * A Z index of 4 renders the grid above the graph + * + * @type {number} + * @default 1 + * @product highcharts highstock gantt + * @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()`. + * + * @sample {highcharts} highcharts/xaxis/id/ + * Get the object + * @sample {highstock} stock/xaxis/id/ + * Get the object + * + * @type {string} + * @since 1.2.0 + * @apioption xAxis.id + */ + /** + * The axis labels show the number or category for each tick. + * + * Since v8.0.0: Labels are animated in categorized x-axis with + * updating data if `tickInterval` and `step` is set to 1. + * + * @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. + * + * @see [reserveSpace](#xAxis.labels.reserveSpace) + * + * @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 * - * Note: - * In boost mode turbo threshold is forced. Only array of numbers or - * two dimensional arrays are allowed. + * @type {Highcharts.AlignValue} + * @apioption xAxis.labels.align + */ + /** + * 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. * - * @since 2.2 - * @product highcharts highstock gantt + * @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 * - * @private + * @type {Array<number>|false} + * @default [-45] + * @since 4.1.0 + * @product highcharts highstock gantt + * @apioption xAxis.labels.autoRotation */ - 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. The zone definitions have to be in ascending order regarding - * to the value. + * 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. * - * In styled mode, the color zones are styled with the - * `.highcharts-zone-{n}` class, or custom classed from the `className` - * option - * ([view live demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-zones/)). + * @sample {highcharts} highcharts/xaxis/labels-autorotationlimit/ + * Lower limit * - * @see [zoneAxis](#plotOptions.series.zoneAxis) - * - * @sample {highcharts} highcharts/series/color-zones-simple/ - * Color zones - * @sample {highstock} highcharts/series/color-zones-simple/ - * Color zones + * @type {number} + * @default 80 + * @since 4.1.5 + * @product highcharts gantt + * @apioption xAxis.labels.autoRotationLimit + */ + /** + * Polar charts only. The label's pixel distance from the perimeter + * of the plot area. * - * @declare Highcharts.SeriesZonesOptionsObject - * @type {Array<*>} - * @since 4.1.0 - * @product highcharts highstock - * @apioption plotOptions.series.zones + * @type {number} + * @default 15 + * @product highcharts gantt + * @apioption xAxis.labels.distance */ /** - * Styled mode only. A custom class name for the zone. + * Enable or disable the axis labels. * - * @sample highcharts/css/color-zones/ - * Zones styled by class name + * @sample {highcharts} highcharts/xaxis/labels-enabled/ + * X axis labels disabled + * @sample {highstock} stock/xaxis/labels-enabled/ + * X axis labels disabled * - * @type {string} - * @since 5.0.0 - * @apioption plotOptions.series.zones.className + * @default {highcharts|highstock|gantt} true + * @default {highmaps} false */ + enabled: true, /** - * Defines the color of the series. + * A [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) + * for the axis label. * - * @see [series color](#plotOptions.series.color) + * @sample {highcharts|highstock} highcharts/yaxis/labels-format/ + * Add units to Y axis label * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @since 4.1.0 - * @product highcharts highstock - * @apioption plotOptions.series.zones.color + * @type {string} + * @default {value} + * @since 3.0 + * @apioption xAxis.labels.format */ /** - * A name for the dash style to use for the graph. + * 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. * - * @see [plotOptions.series.dashStyle](#plotOptions.series.dashStyle) + * Defaults to: + * ```js + * function() { + * return this.value; + * } + * ``` * - * @sample {highcharts|highstock} highcharts/series/color-zones-dashstyle-dot/ - * Dashed line indicates prognosis + * @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 * - * @type {Highcharts.DashStyleValue} - * @since 4.1.0 - * @product highcharts highstock - * @apioption plotOptions.series.zones.dashStyle + * @type {Highcharts.AxisLabelsFormatterCallbackFunction} + * @apioption xAxis.labels.formatter */ /** - * Defines the fill color for the series (in area type series) + * The number of pixels to indent the labels per level in a treegrid + * axis. * - * @see [fillColor](#plotOptions.area.fillColor) + * @sample gantt/treegrid-axis/demo + * Indentation 10px by default. + * @sample gantt/treegrid-axis/indentation-0px + * Indentation set to 0px. * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @since 4.1.0 - * @product highcharts highstock - * @apioption plotOptions.series.zones.fillColor + * @product gantt */ + indentation: 10, /** - * The value up to where the zone extends, if undefined the zones - * stretches to the last value in the series. + * Horizontal axis only. When `staggerLines` is not set, + * `maxStaggerLines` defines how many lines the axis is allowed to + * add to automatically avoid overlapping X labels. Set to `1` to + * disable overlap detection. * + * @deprecated * @type {number} - * @since 4.1.0 - * @product highcharts highstock - * @apioption plotOptions.series.zones.value + * @default 5 + * @since 1.3.3 + * @apioption xAxis.labels.maxStaggerLines */ /** - * When using dual or multiple color axes, this number defines which - * colorAxis the particular series is connected to. It refers to - * either the - * {@link #colorAxis.id|axis id} - * or the index of the axis in the colorAxis array, with 0 being the - * first. Set this option to false to prevent a series from connecting - * to the default color axis. - * - * Since v7.2.0 the option can also be an axis id or an axis index - * instead of a boolean flag. - * - * @sample highcharts/coloraxis/coloraxis-with-pie/ - * Color axis with pie series - * @sample highcharts/coloraxis/multiple-coloraxis/ - * Multiple color axis - * - * @type {number|string|boolean} - * @default 0 - * @product highcharts highstock highmaps - * @apioption plotOptions.series.colorAxis + * How to handle overflowing labels on horizontal axis. If set to + * `"allow"`, it will not be aligned at all. By default it + * `"justify"` labels inside the chart area. If there is room to + * move it, it will be aligned to the edge, else it will be removed. + * + * @type {string} + * @default justify + * @since 2.2.5 + * @validvalue ["allow", "justify"] + * @apioption xAxis.labels.overflow */ /** - * Determines what data value should be used to calculate point color - * if `colorAxis` is used. Requires to set `min` and `max` if some - * custom point property is used or if approximation for data grouping - * is set to `'sum'`. - * - * @sample highcharts/coloraxis/custom-color-key/ - * Custom color key - * @sample highcharts/coloraxis/changed-default-color-key/ - * Changed default color key + * The pixel padding for axis labels, to ensure white space between + * them. * - * @type {string} - * @default y - * @since 7.2.0 - * @product highcharts highstock highmaps - * @apioption plotOptions.series.colorKey + * @type {number} + * @default 5 + * @product highcharts gantt + * @apioption xAxis.labels.padding */ /** - * 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. + * Whether to reserve space for the labels. By default, space is + * reserved for the labels in these cases: * - * Applies only to series types using nearest neighbor search (not - * direct hover) for tooltip. + * * 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`. * - * @sample {highcharts} highcharts/series/findnearestpointby/ - * Different hover behaviors - * @sample {highstock} highcharts/series/findnearestpointby/ - * Different hover behaviors - * @sample {highmaps} highcharts/series/findnearestpointby/ - * Different hover behaviors + * This can be turned off when for example the labels are rendered + * inside the plot area instead of outside. * - * @since 5.0.10 - * @validvalue ["x", "xy"] + * @see [labels.align](#xAxis.labels.align) * - * @private + * @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 + * + * @type {boolean} + * @since 4.1.10 + * @product highcharts gantt + * @apioption xAxis.labels.reserveSpace */ - findNearestPointBy: 'x' - }, - /* eslint-disable no-invalid-this, valid-jsdoc */ - /** @lends Highcharts.Series.prototype */ - { - axisTypes: ['xAxis', 'yAxis'], - coll: 'series', - colorCounter: 0, - cropShoulder: 1, - directTouch: false, - isCartesian: true, - // each point's x and y values are stored in this.xData and this.yData - parallelArrays: ['x', 'y'], - pointClass: Point, - requireSorting: true, - sorted: true, - init: function (chart, options) { - fireEvent(this, 'init', { options: options }); - var series = this, - events, - chartSeries = chart.series, - lastSeries; - // A lookup over those events that are added by _options_ (not - // programmatically). These are updated through Series.update() - // (#10861). - this.eventOptions = this.eventOptions || {}; - // The 'eventsToUnbind' property moved from prototype into the - // Series init to avoid reference to the same array between - // the different series and charts. #12959, #13937 - this.eventsToUnbind = []; - /** - * Read only. The chart that the series belongs to. - * - * @name Highcharts.Series#chart - * @type {Highcharts.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 Highcharts.Series#type - * @type {string} - */ - /** - * Read only. The series' current options. To update, use - * {@link Series#update}. - * - * @name Highcharts.Series#options - * @type {Highcharts.SeriesOptionsType} - */ - 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 Highcharts.Series#name - * @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 Highcharts.Series#visible - * @type {boolean} - */ - visible: options.visible !== false, - /** - * Read only. The series' selected state as set by {@link - * Highcharts.Series#select}. - * - * @name Highcharts.Series#selected - * @type {boolean} - */ - selected: options.selected === true // false by default - }); - // Register event listeners - events = options.events; - objectEach(events, function (event, eventType) { - if (isFunction(event)) { - // If event does not exist, or is changed by Series.update - if (series.eventOptions[eventType] !== event) { - // Remove existing if set by option - if (isFunction(series.eventOptions[eventType])) { - removeEvent(series, eventType, series.eventOptions[eventType]); - } - series.eventOptions[eventType] = event; - 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(); - // Initialize the parallel data arrays - series.parallelArrays.forEach(function (key) { - if (!series[key + 'Data']) { - series[key + 'Data'] = []; - } - }); - // 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; - series.opacity = series.options.opacity; - // Insert the series and re-order all series above the insertion - // point. - chart.orderSeries(this.insert(chartSeries)); - // Set options for series with sorting and set data later. - if (options.dataSorting && options.dataSorting.enabled) { - series.setDataSortingOptions(); - } - else if (!series.points && !series.data) { - series.setData(options.data, false); - } - fireEvent(this, 'afterInit'); - }, /** - * Check whether the series item is itself or inherits from a certain - * series type. + * Rotation of the labels in degrees. * - * @function Highcharts.Series#is - * @param {string} type The type of series to check for, can be either - * featured or custom series types. For example `column`, `pie`, - * `ohlc` etc. + * @sample {highcharts} highcharts/xaxis/labels-rotation/ + * X axis labels rotated 90° * - * @return {boolean} - * True if this item is or inherits from the given type. + * @type {number} + * @default 0 + * @apioption xAxis.labels.rotation */ - is: function (type) { - return seriesTypes[type] && this instanceof seriesTypes[type]; - }, /** - * 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. + * Horizontal axes only. The number of lines to spread the labels + * over to make room or tighter labels. * - * @private - * @function Highcharts.Series#insert - * @param {Array<Highcharts.Series>} collection - * A collection of series, like `chart.series` or `xAxis.series`. - * @return {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. + * @sample {highcharts} highcharts/xaxis/labels-staggerlines/ + * Show labels over two lines + * @sample {highstock} stock/xaxis/labels-staggerlines/ + * Show labels over two lines * - * @private - * @function Highcharts.Series#bindAxes - * @return {void} - * @exception 18 + * @type {number} + * @since 2.1 + * @apioption xAxis.labels.staggerLines */ - bindAxes: function () { - var series = this, - seriesOptions = series.options, - chart = series.chart, - axisOptions; - fireEvent(this, 'bindAxes', null, function () { - // repeat for xAxis and yAxis - (series.axisTypes || []).forEach(function (AXIS) { - // loop through the chart's axis objects - chart[AXIS].forEach(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 || - (typeof seriesOptions[AXIS] !== - 'undefined' && - seriesOptions[AXIS] === axisOptions.id) || - (typeof 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 Highcharts.Series#xAxis - * @type {Highcharts.Axis} - */ - /** - * Read only. The unique yAxis object associated - * with the series. - * - * @name Highcharts.Series#yAxis - * @type {Highcharts.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) { - error(18, true, chart); - } - }); - }); - fireEvent(this, 'afterBindAxes'); - }, /** - * 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. + * To show only every _n_'th label on the axis, set the step to _n_. + * Setting the step to 2 shows every other label. * - * @private - * @function Highcharts.Series#updateParallelArrays - * @param {Highcharts.Point} point - * @param {number|string} i - * @return {void} - */ - 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)); - }; - series.parallelArrays.forEach(fn); - }, - /** - * Define hasData functions for series. These return true if there - * are data points on this series within the plot area. + * 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. * - * @private - * @function Highcharts.Series#hasData - * @return {boolean} - */ - hasData: function () { - return ((this.visible && - typeof this.dataMax !== 'undefined' && - typeof this.dataMin !== 'undefined') || ( // #3703 - this.visible && - this.yData && - this.yData.length > 0) // #9758 - ); - }, - /** - * 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. + * Read more at + * [Axis docs](https://www.highcharts.com/docs/chart-concepts/axes) + * => What axis should I use? * - * @private - * @function Highcharts.Series#autoIncrement - * @return {number} - */ - autoIncrement: function () { - var options = this.options, - xIncrement = this.xIncrement, - date, - pointInterval, - pointIntervalUnit = options.pointIntervalUnit, - 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') { - time.set('Date', date, time.get('Date', date) + pointInterval); - } - else if (pointIntervalUnit === 'month') { - time.set('Month', date, time.get('Month', date) + pointInterval); - } - else if (pointIntervalUnit === 'year') { - time.set('FullYear', date, time.get('FullYear', date) + pointInterval); - } - pointInterval = date.getTime() - xIncrement; - } - this.xIncrement = xIncrement + pointInterval; - return xIncrement; - }, - /** - * Internal function to set properties for series if data sorting is - * enabled. + * @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 * - * @private - * @function Highcharts.Series#setDataSortingOptions - * @return {void} - */ - setDataSortingOptions: function () { - var options = this.options; - extend(this, { - requireSorting: false, - sorted: false, - enabledDataSorting: true, - allowDG: false - }); - // To allow unsorted data for column series. - if (!defined(options.pointRange)) { - options.pointRange = 1; - } - }, - /** - * Set the series options by merging from the options tree. Called - * internally on initializing and updating series. This function will - * not redraw the series. For API usage, use {@link Series#update}. - * @private - * @function Highcharts.Series#setOptions - * @param {Highcharts.SeriesOptionsType} itemOptions - * The series options. - * @return {Highcharts.SeriesOptionsType} - * @fires Highcharts.Series#event:afterSetOptions + * @type {number} + * @since 2.1 + * @apioption xAxis.labels.step */ - setOptions: function (itemOptions) { - var chart = this.chart, - chartOptions = chart.options, - plotOptions = chartOptions.plotOptions, - userOptions = chart.userOptions || {}, - seriesUserOptions = merge(itemOptions), - options, - zones, - zone, - styledMode = chart.styledMode, - e = { - plotOptions: plotOptions, - userOptions: seriesUserOptions - }; - fireEvent(this, 'setOptions', e); - // These may be modified by the event - var typeOptions = e.plotOptions[this.type], - userPlotOptions = (userOptions.plotOptions || {}); - // use copy to prevent undetected changes (#9762) - /** - * Contains series options by the user without defaults. - * @name Highcharts.Series#userOptions - * @type {Highcharts.SeriesOptionsType} - */ - this.userOptions = e.userOptions; - options = merge(typeOptions, plotOptions.series, - // #3881, chart instance plotOptions[type] should trump - // plotOptions.series - userOptions.plotOptions && - userOptions.plotOptions[this.type], seriesUserOptions); - // 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 - seriesUserOptions.tooltip // 7 - ); - // When shared tooltip, stickyTracking is true by default, - // unless user says otherwise. - this.stickyTracking = pick(seriesUserOptions.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) { - zone = { - value: options[this.zoneAxis + 'Threshold'] || - options.threshold || - 0, - className: 'highcharts-negative' - }; - if (!styledMode) { - zone.color = options.negativeColor; - zone.fillColor = options.negativeFillColor; - } - zones.push(zone); - } - if (zones.length) { // Push one extra zone for the rest - if (defined(zones[zones.length - 1].value)) { - zones.push(styledMode ? {} : { - color: this.color, - fillColor: this.fillColor - }); - } - } - fireEvent(this, 'afterSetOptions', { options: options }); - return options; - }, /** - * Return series name in "Series {Number}" format or the one defined by - * a user. This method can be simply overridden as series name format - * can vary (e.g. technical indicators). + * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) + * to render the labels. * - * @function Highcharts.Series#getName - * @return {string} - * The series name. + * @type {boolean} + * @default false + * @apioption xAxis.labels.useHTML */ - getName: function () { - // #4119 - return pick(this.options.name, 'Series ' + (this.index + 1)); - }, /** - * @private - * @function Highcharts.Series#getCyclic - * @param {string} prop - * @param {*} [value] - * @param {Highcharts.Dictionary<any>} [defaults] - * @return {void} + * The x position offset of all labels relative to the tick + * positions on the axis. + * + * @sample {highcharts} highcharts/xaxis/labels-x/ + * Y axis labels placed on grid lines */ - 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 (typeof i !== 'undefined') { - this[indexName] = i; - } - this[prop] = value; - }, + x: 0, /** - * Get the series' color based on either the options or pulled from - * global options. + * The y position offset of all labels relative to the tick + * positions on the axis. The default makes it adapt to the font + * size of the bottom axis. * - * @private - * @function Highcharts.Series#getColor - * @return {void} + * @sample {highcharts} highcharts/xaxis/labels-x/ + * Y axis labels placed on grid lines + * + * @type {number} + * @apioption xAxis.labels.y */ - getColor: function () { - if (this.chart.styledMode) { - this.getCyclic('color'); - } - else 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 || - defaultOptions.plotOptions[this.type].color, this.chart.options.colors); - } - }, /** - * Get all points' instances created for this series. + * The Z index for the axis labels. * - * @private - * @function Highcharts.Series#getPointsCollection - * @return {Array<Highcharts.Point>} + * @type {number} + * @default 7 + * @apioption xAxis.labels.zIndex */ - getPointsCollection: function () { - return (this.hasGroupedData ? this.points : this.data) || []; - }, /** - * Get the series' symbol based on either the options or pulled from - * global options. + * CSS styles for the label. Use `whiteSpace: 'nowrap'` to prevent + * wrapping of category labels. Use `textOverflow: 'none'` to + * prevent ellipsis (dots). * - * @private - * @function Highcharts.Series#getSymbol - * @return {void} + * In styled mode, the labels are styled with the + * `.highcharts-axis-labels` class. + * + * @sample {highcharts} highcharts/xaxis/labels-style/ + * Red X axis labels + * + * @type {Highcharts.CSSObject} */ - getSymbol: function () { - var seriesMarkerOption = this.options.marker; - this.getCyclic('symbol', seriesMarkerOption.symbol, this.chart.options.symbols); + style: { + /** @internal */ + color: "#666666", + /** @internal */ + cursor: "default", + /** @internal */ + fontSize: "11px", }, - /** - * Finds the index of an existing point that matches the given point - * options. + }, + /** + * The left position as the horizontal axis. If it's a number, it is + * interpreted as pixel position relative to the chart. + * + * Since Highcharts v5.0.13: If it's a percentage string, it is + * interpreted as percentages of the plot width, offset from plot area + * left. + * + * @type {number|string} + * @product highcharts highstock + * @apioption xAxis.left + */ + /** + * The top position as the vertical axis. If it's a number, it is + * interpreted as pixel position relative to the chart. + * + * Since Highcharts 2: If it's a percentage string, it is interpreted + * as percentages of the plot height, offset from plot area top. + * + * @type {number|string} + * @product highcharts highstock + * @apioption xAxis.top + */ + /** + * 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. + * + * @sample {highcharts} highcharts/xaxis/linkedto/ + * Different string formats of the same date + * @sample {highcharts} highcharts/yaxis/linkedto/ + * Y values on both sides + * + * @type {number} + * @since 2.0.2 + * @product highcharts highstock gantt + * @apioption xAxis.linkedTo + */ + /** + * The maximum value of the axis. If `null`, the max value is + * automatically calculated. + * + * If the [endOnTick](#yAxis.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. + * + * @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 + * + * @type {number|null} + * @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](#yAxis.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). + * + * @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 + * + * @type {number|null} + * @apioption xAxis.min + */ + /** + * The dash or dot style of the minor grid lines. For possible values, + * see [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). + * + * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/ + * Long dashes on minor grid lines + * @sample {highstock} stock/xaxis/minorgridlinedashstyle/ + * Long dashes on minor grid lines + * + * @type {Highcharts.DashStyleValue} + * @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` or `undefined`, 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. + * + * @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" + * + * @type {number|string|null} + * @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`. + * + * @sample {highcharts} highcharts/yaxis/minortickposition-outside/ + * Outside by default + * @sample {highcharts} highcharts/yaxis/minortickposition-inside/ + * Inside + * @sample {highstock} stock/xaxis/minorticks/ + * Inside + * + * @validvalue ["inside", "outside"] + */ + 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. + * + * @sample {highcharts} highcharts/yaxis/minorticks-true/ + * Enabled on linear Y axis + * + * @type {boolean} + * @default false + * @since 6.0.0 + * @apioption xAxis.minorTicks + */ + /** + * The pixel width of the minor tick mark. + * + * @sample {highcharts} highcharts/yaxis/minortickwidth/ + * 3px width + * @sample {highstock} stock/xaxis/minorticks/ + * 1px width + * + * @type {number} + * @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 + * @product highcharts highstock gantt + */ + 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**: The `minPadding`, `maxPadding`, `startOnTick` and + * `endOnTick` settings also affect how the extremes of the axis + * are computed. + * + * @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 + * + * @type {number} + * @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. + * + * @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 + * + * @type {number} + * @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. + * + * @sample {highcharts} highcharts/yaxis/opposite/ + * Secondary Y axis opposite + * @sample {highstock} stock/xaxis/opposite/ + * Y axis on left side + * + * @type {boolean} + * @default {highcharts|highstock|highmaps} false + * @default {gantt} true + * @apioption xAxis.opposite + */ + /** + * In an ordinal axis, the points are equally spaced in the chart + * regardless of the actual time or x distance between them. This means + * that missing data periods (e.g. nights or weekends for a stock chart) + * will not take up space in the chart. + * Having `ordinal: false` will show any gaps created by the `gapSize` + * setting proportionate to their duration. + * + * In stock charts the X axis is ordinal by default, unless + * the boost module is used and at least one of the series' data length + * exceeds the [boostThreshold](#series.line.boostThreshold). + * + * @sample {highstock} stock/xaxis/ordinal-true/ + * True by default + * @sample {highstock} stock/xaxis/ordinal-false/ + * False + * + * @type {boolean} + * @default true + * @since 1.1 + * @product highstock + * @apioption xAxis.ordinal + */ + /** + * 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 the navigator's `xAxis`. + * + * @sample {highstock} stock/xaxis/overscroll/ + * One minute overscroll with live data + * + * @type {number} + * @default 0 + * @since 6.0.0 + * @product highstock + * @apioption xAxis.overscroll + */ + /** + * 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. + * + * @sample highcharts/demo/gauge-vu-meter + * Two gauges with different center + * + * @type {number} + * @product highcharts + * @apioption xAxis.pane + */ + /** + * The zoomed range to display when only defining one or none of `min` + * or `max`. For example, to show the latest month, a range of one month + * can be set. + * + * @sample {highstock} stock/xaxis/range/ + * Setting a zoomed range when the rangeSelector is disabled + * + * @type {number} + * @product highstock + * @apioption xAxis.range + */ + /** + * 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. + * + * @sample {highcharts} highcharts/yaxis/reversed/ + * Reversed Y axis + * @sample {highstock} stock/xaxis/reversed/ + * Reversed Y axis + * + * @type {boolean} + * @default false + * @apioption xAxis.reversed + */ + // reversed: false, + /** + * This option determines how stacks should be ordered within a group. + * For example reversed xAxis also reverses stacks, so first series + * comes last in a group. To keep order like for non-reversed xAxis + * enable this option. + * + * @sample {highcharts} highcharts/xaxis/reversedstacks/ + * Reversed stacks comparison + * @sample {highstock} highcharts/xaxis/reversedstacks/ + * Reversed stacks comparison + * + * @type {boolean} + * @default false + * @since 6.1.1 + * @product highcharts highstock + * @apioption xAxis.reversedStacks + */ + /** + * An optional scrollbar to display on the X axis in response to + * limiting the minimum and maximum of the axis values. + * + * 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`. + * + * @sample {highstock} stock/yaxis/heatmap-scrollbars/ + * Heatmap with both scrollbars + * + * @extends scrollbar + * @since 4.2.6 + * @product highstock + * @apioption xAxis.scrollbar + */ + /** + * Whether to show the axis line and title when the axis has no data. + * + * @sample {highcharts} highcharts/yaxis/showempty/ + * When clicking the legend to hide series, one axis preserves + * line and title, the other doesn't + * @sample {highstock} highcharts/yaxis/showempty/ + * When clicking the legend to hide series, one axis preserves + * line and title, the other doesn't + * + * @since 1.1 + */ + showEmpty: true, + /** + * Whether to show the first tick label. + * + * @sample {highcharts} highcharts/xaxis/showfirstlabel-false/ + * Set to false on X axis + * @sample {highstock} stock/xaxis/showfirstlabel/ + * Labels below plot lines on Y axis + * + * @type {boolean} + * @default true + * @apioption xAxis.showFirstLabel + */ + /** + * Whether to show the last tick label. Defaults to `true` on cartesian + * charts, and `false` on polar charts. + * + * @sample {highcharts} highcharts/xaxis/showlastlabel-true/ + * Set to true on X axis + * @sample {highstock} stock/xaxis/showfirstlabel/ + * Labels below plot lines on Y axis + * + * @type {boolean} + * @default true + * @product highcharts highstock gantt + * @apioption xAxis.showLastLabel + */ + /** + * A soft maximum for the axis. If the series data maximum is less than + * this, the axis will stay at this maximum, but if the series data + * maximum is higher, the axis will flex to show all data. + * + * @sample highcharts/yaxis/softmin-softmax/ + * Soft min and max + * + * @type {number} + * @since 5.0.1 + * @product highcharts highstock gantt + * @apioption xAxis.softMax + */ + /** + * A soft minimum for the axis. If the series data minimum is greater + * than this, the axis will stay at this minimum, but if the series + * data minimum is lower, the axis will flex to show all data. + * + * @sample highcharts/yaxis/softmin-softmax/ + * Soft min and max + * + * @type {number} + * @since 5.0.1 + * @product highcharts highstock gantt + * @apioption xAxis.softMin + */ + /** + * 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 gantt + */ + 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 + * + * @since 1.2.0 + */ + startOnTick: false, + /** + * The amount of ticks to draw on the axis. This opens up for aligning + * the ticks of multiple charts or panes within a chart. This option + * overrides the `tickPixelInterval` option. + * + * This option only has an effect on linear axes. Datetime, logarithmic + * or category axes are not affected. + * + * @sample {highcharts} highcharts/yaxis/tickamount/ + * 8 ticks on Y axis + * @sample {highstock} highcharts/yaxis/tickamount/ + * 8 ticks on Y axis + * + * @type {number} + * @since 4.1.0 + * @product highcharts highstock gantt + * @apioption xAxis.tickAmount + */ + /** + * The interval of the tick marks in axis units. When `undefined`, the + * tick interval is computed to approximately follow the + * [tickPixelInterval](#xAxis.tickPixelInterval) on linear and datetime + * axes. On categorized axes, a `undefined` tickInterval will default to + * 1, one category. Note that datetime axes are based on milliseconds, + * so for example an interval of one day is expressed as + * `24 * 3600 * 1000`. + * + * On logarithmic axes, the tickInterval is based on powers, so a + * tickInterval of 1 means one tick on each of 0.1, 1, 10, 100 etc. A + * tickInterval of 2 means a tick of 0.1, 10, 1000 etc. A tickInterval + * of 0.2 puts a tick on 0.1, 0.2, 0.4, 0.6, 0.8, 1, 2, 4, 6, 8, 10, 20, + * 40 etc. + * + * + * If the tickInterval is too dense for labels to be drawn, Highcharts + * may remove ticks. + * + * If the chart has multiple axes, the [alignTicks](#chart.alignTicks) + * option may interfere with the `tickInterval` setting. + * + * @see [tickPixelInterval](#xAxis.tickPixelInterval) + * @see [tickPositions](#xAxis.tickPositions) + * @see [tickPositioner](#xAxis.tickPositioner) + * + * @sample {highcharts} highcharts/xaxis/tickinterval-5/ + * Tick interval of 5 on a linear axis + * @sample {highstock} stock/xaxis/tickinterval/ + * Tick interval of 0.01 on Y axis + * + * @type {number} + * @apioption xAxis.tickInterval + */ + /** + * 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, + /** + * 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) + * @see [tickPositioner](#xAxis.tickPositioner) + * @see [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, + /** + * 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`. + * + * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/ + * "between" by default + * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/ + * "on" + * + * @product highcharts gantt + * @validvalue ["on", "between"] + */ + tickmarkPlacement: "between", + /** + * The position of the major tick marks relative to the axis line. + * Can be one of `inside` and `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 + * + * @validvalue ["inside", "outside"] + */ + tickPosition: "outside", + /** + * A callback function returning array defining where the ticks are + * laid out on the axis. This overrides the default behaviour of + * [tickPixelInterval](#xAxis.tickPixelInterval) and [tickInterval]( + * #xAxis.tickInterval). The automatic tick positions are accessible + * through `this.tickPositions` and can be modified by the callback. + * + * @see [tickPositions](#xAxis.tickPositions) + * + * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/ + * Demo of tickPositions and tickPositioner + * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/ + * Demo of tickPositions and tickPositioner + * + * @type {Highcharts.AxisTickPositionerCallbackFunction} + * @apioption xAxis.tickPositioner + */ + /** + * An array defining where the ticks are laid out on the axis. This + * overrides the default behaviour of [tickPixelInterval]( + * #xAxis.tickPixelInterval) and [tickInterval](#xAxis.tickInterval). + * + * @see [tickPositioner](#xAxis.tickPositioner) + * + * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/ + * Demo of tickPositions and tickPositioner + * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/ + * Demo of tickPositions and tickPositioner + * + * @type {Array<number>} + * @apioption xAxis.tickPositions + */ + /** + * The pixel width of the major tick marks. Defaults to 0 on category + * axes, otherwise 1. + * + * In styled mode, the stroke width is given in the `.highcharts-tick` + * class, but in order for the element to be generated on category axes, + * the option must be explicitly set to 1. + * + * @sample {highcharts} highcharts/xaxis/tickwidth/ + * 10 px width + * @sample {highcharts} highcharts/css/axis-grid/ + * Styled mode + * @sample {highstock} stock/xaxis/ticks/ + * Formatted ticks on X axis + * @sample {highstock} highcharts/css/axis-grid/ + * Styled mode + * + * @type {undefined|number} + * @default {highstock} 1 + * @default {highmaps} 0 + * @apioption xAxis.tickWidth + */ + /** + * 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: { + /** + * Deprecated. Set the `text` to `null` to disable the title. * - * @private - * @function Highcharts.Series#findPointIndex - * @param {Highcharts.PointOptionsObject} optionsObject - * The options of the point. - * @param {number} fromIndex - * The index to start searching from, used for optimizing - * series with required sorting. - * @returns {number|undefined} - * Returns the index of a matching point, or undefined if no - * match is found. + * @deprecated + * @type {boolean} + * @product highcharts + * @apioption xAxis.title.enabled */ - findPointIndex: function (optionsObject, fromIndex) { - var id = optionsObject.id, - x = optionsObject.x, - oldData = this.points, - matchingPoint, - matchedById, - pointIndex, - matchKey, - dataSorting = this.options.dataSorting; - if (id) { - matchingPoint = this.chart.get(id); - } - else if (this.linkedParent || this.enabledDataSorting) { - matchKey = (dataSorting && dataSorting.matchByName) ? - 'name' : 'index'; - matchingPoint = find(oldData, function (oldPoint) { - return !oldPoint.touched && oldPoint[matchKey] === - optionsObject[matchKey]; - }); - // Add unmatched point as a new point - if (!matchingPoint) { - return void 0; - } - } - if (matchingPoint) { - pointIndex = matchingPoint && matchingPoint.index; - if (typeof pointIndex !== 'undefined') { - matchedById = true; - } - } - // Search for the same X in the existing data set - if (typeof pointIndex === 'undefined' && isNumber(x)) { - pointIndex = this.xData.indexOf(x, fromIndex); - } - // Reduce pointIndex if data is cropped - if (pointIndex !== -1 && - typeof pointIndex !== 'undefined' && - this.cropped) { - pointIndex = (pointIndex >= this.cropStart) ? - pointIndex - this.cropStart : pointIndex; - } - if (!matchedById && - oldData[pointIndex] && oldData[pointIndex].touched) { - pointIndex = void 0; - } - return pointIndex; - }, /** - * @private - * @borrows LegendSymbolMixin.drawLineMarker as Highcharts.Series#drawLegendSymbol + * The pixel distance between the axis labels or line and the title. + * Defaults to 0 for horizontal axes, 10 for vertical + * + * @sample {highcharts} highcharts/xaxis/title-margin/ + * Y axis title margin of 60 + * + * @type {number} + * @apioption xAxis.title.margin */ - drawLegendSymbol: LegendSymbolMixin.drawLineMarker, /** - * Internal function called from setData. If the point count is the same - * as is was, or if there are overlapping X values, just run - * Point.update which is cheaper, allows animation, and keeps references - * to points. This also allows adding or removing points if the X-es - * don't match. + * The distance of the axis title from the axis line. By default, + * this distance is computed from the offset width of the labels, + * the labels' distance from the axis and the title's margin. + * However when the offset option is set, it overrides all this. * - * @private - * @function Highcharts.Series#updateData + * @sample {highcharts} highcharts/yaxis/title-offset/ + * Place the axis title on top of the axis + * @sample {highstock} highcharts/yaxis/title-offset/ + * Place the axis title on top of the Y axis * - * @param {Array<Highcharts.PointOptionsType>} data + * @type {number} + * @since 2.2.0 + * @apioption xAxis.title.offset + */ + /** + * Whether to reserve space for the title when laying out the axis. * - * @return {boolean} + * @type {boolean} + * @default true + * @since 5.0.11 + * @product highcharts highstock gantt + * @apioption xAxis.title.reserveSpace */ - updateData: function (data, animation) { - var options = this.options, - dataSorting = options.dataSorting, - oldData = this.points, - pointsToAdd = [], - hasUpdatedByKey, - i, - point, - lastIndex, - requireSorting = this.requireSorting, - equalLength = data.length === oldData.length, - succeeded = true; - this.xIncrement = null; - // Iterate the new data - data.forEach(function (pointOptions, i) { - var id, - x, - pointIndex, - optionsObject = (defined(pointOptions) && - this.pointClass.prototype.optionsToObject.call({ series: this }, - pointOptions)) || {}; - // Get the x of the new data point - x = optionsObject.x; - id = optionsObject.id; - if (id || isNumber(x)) { - pointIndex = this.findPointIndex(optionsObject, lastIndex); - // Matching X not found - // or used already due to ununique x values (#8995), - // add point (but later) - if (pointIndex === -1 || - typeof pointIndex === 'undefined') { - pointsToAdd.push(pointOptions); - // Matching X found, update - } - else if (oldData[pointIndex] && - pointOptions !== options.data[pointIndex]) { - oldData[pointIndex].update(pointOptions, false, null, false); - // Mark it touched, below we will remove all points that - // are not touched. - oldData[pointIndex].touched = true; - // Speed optimize by only searching after last known - // index. Performs ~20% bettor on large data sets. - if (requireSorting) { - lastIndex = pointIndex + 1; - } - // Point exists, no changes, don't remove it - } - else if (oldData[pointIndex]) { - oldData[pointIndex].touched = true; - } - // If the length is equal and some of the nodes had a - // match in the same position, we don't want to remove - // non-matches. - if (!equalLength || - i !== pointIndex || - (dataSorting && dataSorting.enabled) || - this.hasDerivedData) { - hasUpdatedByKey = true; - } - } - else { - // Gather all points that are not matched - pointsToAdd.push(pointOptions); - } - }, this); - // Remove points that don't exist in the updated data set - if (hasUpdatedByKey) { - i = oldData.length; - while (i--) { - point = oldData[i]; - if (point && !point.touched && point.remove) { - point.remove(false, animation); - } - } - // If we did not find keys (ids or x-values), and the length is the - // same, update one-to-one - } - else if (equalLength && (!dataSorting || !dataSorting.enabled)) { - data.forEach(function (point, i) { - // .update doesn't exist on a linked, hidden series (#3709) - // (#10187) - if (oldData[i].update && point !== oldData[i].y) { - oldData[i].update(point, false, null, false); - } - }); - // Don't add new points since those configs are used above - pointsToAdd.length = 0; - // Did not succeed in updating data - } - else { - succeeded = false; - } - oldData.forEach(function (point) { - if (point) { - point.touched = false; - } - }); - if (!succeeded) { - return false; - } - // Add new points - pointsToAdd.forEach(function (point) { - this.addPoint(point, false, null, null, false); - }, this); - if (this.xIncrement === null && - this.xData && - this.xData.length) { - this.xIncrement = arrayMax(this.xData); - this.autoIncrement(); - } - return true; - }, /** - * 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. - * - * @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 - * - * @function Highcharts.Series#setData - * - * @param {Array<Highcharts.PointOptionsType>} data - * Takes an array of data in the same format as described under - * `series.{type}.data` for the given series type, for example a - * line series would take data in the form described under - * [series.line.data](https://api.highcharts.com/highcharts/series.line.data). - * - * @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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [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 this is true, points will be updated instead of replaced - * whenever possible. This occurs a) when the updated data is the - * same length as the existing data, b) when points are matched - * by their id's, or c) when points can be matched by X values. - * This allows updating with animation and performs better. In - * this case, the original array is not passed by reference. Set - * `false` to prevent. - * - * @return {void} + * 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 + * + * @type {number} + * @default 0 + * @apioption xAxis.title.rotation */ - setData: function (data, redraw, animation, updatePoints) { - var series = this, - oldData = series.points, - oldDataLength = (oldData && oldData.length) || 0, - dataLength, - options = series.options, - chart = series.chart, - dataSorting = options.dataSorting, - firstPoint = null, - xAxis = series.xAxis, - i, - turboThreshold = options.turboThreshold, - pt, - xData = this.xData, - yData = this.yData, - pointArrayMap = series.pointArrayMap, - valueCount = pointArrayMap && pointArrayMap.length, - keys = options.keys, - indexOfX = 0, - indexOfY = 1, - updatedData; - data = data || []; - dataLength = data.length; - redraw = pick(redraw, true); - if (dataSorting && dataSorting.enabled) { - data = this.sortData(data); - } - // First try to run Point.update which is cheaper, allows animation, - // and keeps references to points. - if (updatePoints !== false && - dataLength && - oldDataLength && - !series.cropped && - !series.hasGroupedData && - series.visible && - // Soft updating has no benefit in boost, and causes JS error - // (#8355) - !series.isSeriesBoosting) { - updatedData = this.updateData(data, animation); - } - if (!updatedData) { - // Reset properties - series.xIncrement = null; - series.colorCounter = 0; // for series with colorByPoint (#1547) - // Update parallel arrays - this.parallelArrays.forEach(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) { - firstPoint = series.getFirstValidPoint(data); - 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] - if (keys) { - indexOfX = keys.indexOf('x'); - indexOfY = keys.indexOf('y'); - indexOfX = indexOfX >= 0 ? indexOfX : 0; - indexOfY = indexOfY >= 0 ? indexOfY : 1; - } - for (i = 0; i < dataLength; i++) { - pt = data[i]; - xData[i] = pt[indexOfX]; - yData[i] = pt[indexOfY]; - } - } - } - else { - // Highcharts expects configs to be numbers or arrays in - // turbo mode - error(12, false, chart); - } - } - else { - for (i = 0; i < dataLength; i++) { - // stray commas in oldIE: - if (typeof data[i] !== 'undefined') { - 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])) { - error(14, true, chart); - } - 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 sort series data + * The actual text of the axis title. It can contain basic HTML tags + * like `b`, `i` and `span` with style. * - * @private - * @function Highcharts.Series#sortData - * @param {Array<Highcharts.PointOptionsType>} data - * Force data grouping. - * @return {Array<Highcharts.PointOptionsObject>} + * @sample {highcharts} highcharts/xaxis/title-text/ + * Custom HTML + * @sample {highstock} stock/xaxis/title-text/ + * Titles for both axes + * + * @type {string|null} + * @apioption xAxis.title.text */ - sortData: function (data) { - var series = this, - options = series.options, - dataSorting = options.dataSorting, - sortKey = dataSorting.sortKey || 'y', - sortedData, - getPointOptionsObject = function (series, - pointOptions) { - return (defined(pointOptions) && - series.pointClass.prototype.optionsToObject.call({ - series: series - }, - pointOptions)) || {}; - }; - data.forEach(function (pointOptions, i) { - data[i] = getPointOptionsObject(series, pointOptions); - data[i].index = i; - }, this); - // Sorting - sortedData = data.concat().sort(function (a, b) { - var aValue = getNestedProperty(sortKey, - a); - var bValue = getNestedProperty(sortKey, - b); - return bValue < aValue ? -1 : bValue > aValue ? 1 : 0; - }); - // Set x value depending on the position in the array - sortedData.forEach(function (point, i) { - point.x = i; - }, this); - // Set the same x for linked series points if they don't have their - // own sorting - if (series.linkedSeries) { - series.linkedSeries.forEach(function (linkedSeries) { - var options = linkedSeries.options, - seriesData = options.data; - if ((!options.dataSorting || - !options.dataSorting.enabled) && - seriesData) { - seriesData.forEach(function (pointOptions, i) { - seriesData[i] = getPointOptionsObject(linkedSeries, pointOptions); - if (data[i]) { - seriesData[i].x = data[i].x; - seriesData[i].index = i; - } - }); - linkedSeries.setData(seriesData, false); - } - }); - } - return data; - }, /** - * 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. + * Alignment of the text, can be `"left"`, `"right"` or `"center"`. + * Default alignment depends on the + * [title.align](xAxis.title.align): * - * @private - * @function Highcharts.Series#getProcessedData - * @param {boolean} [forceExtremesFromAll] - * Force getting extremes of a total series data range. - * @return {Highcharts.SeriesProcessedDataObject} + * Horizontal axes: + * - for `align` = `"low"`, `textAlign` is set to `left` + * - for `align` = `"middle"`, `textAlign` is set to `center` + * - for `align` = `"high"`, `textAlign` is set to `right` + * + * Vertical axes: + * - for `align` = `"low"` and `opposite` = `true`, `textAlign` is + * set to `right` + * - for `align` = `"low"` and `opposite` = `false`, `textAlign` is + * set to `left` + * - for `align` = `"middle"`, `textAlign` is set to `center` + * - for `align` = `"high"` and `opposite` = `true` `textAlign` is + * set to `left` + * - for `align` = `"high"` and `opposite` = `false` `textAlign` is + * set to `right` + * + * @type {Highcharts.AlignValue} + * @apioption xAxis.title.textAlign */ - getProcessedData: function (forceExtremesFromAll) { - var series = this, - // copied during slice operation: - processedXData = series.xData, - 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 = forceExtremesFromAll || - series.getExtremesFromAll || - options.getExtremesFromAll, // #4599 - isCartesian = series.isCartesian, - xExtremes, - val2lin = xAxis && xAxis.val2lin, - isLog = !!(xAxis && xAxis.logarithmic), - throwOnUnsorted = series.requireSorting, - min, - max; - if (xAxis) { - // corrected for log axis (#3053) - xExtremes = xAxis.getExtremes(); - 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 (series.yData && (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 && - (typeof 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) { - error(15, false, series.chart); - throwOnUnsorted = false; // Only once - } - } - return { - xData: processedXData, - yData: processedYData, - cropped: cropped, - cropStart: cropStart, - closestPointRange: closestPointRange - }; - }, /** - * Internal function to apply processed data. - * In Highstock, this function is extended to provide data grouping. + * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) + * to render the axis title. * - * @private - * @function Highcharts.Series#processData - * @param {boolean} [force] - * Force data grouping. - * @return {boolean|undefined} + * @type {boolean} + * @default false + * @product highcharts highstock gantt + * @apioption xAxis.title.useHTML */ - processData: function (force) { - var series = this, - xAxis = series.xAxis, - processedData; - // 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 (series.isCartesian && - !series.isDirty && - !xAxis.isDirty && - !series.yAxis.isDirty && - !force) { - return false; - } - processedData = series.getProcessedData(); - // Record the properties - series.cropped = processedData.cropped; // undefined or true - series.cropStart = processedData.cropStart; - series.processedXData = processedData.xData; - series.processedYData = processedData.yData; - series.closestPointRange = - series.basePointRange = processedData.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. + * Horizontal pixel offset of the title position. * - * @private - * @function Highcharts.Series#cropData - * @param {Array<number>} xData - * @param {Array<number>} yData - * @param {number} min - * @param {number} max - * @param {number} [cropShoulder] - * @return {Highcharts.SeriesCropDataObject} + * @type {number} + * @default 0 + * @since 4.1.6 + * @product highcharts highstock gantt + * @apioption xAxis.title.x */ - cropData: function (xData, yData, min, max, cropShoulder) { - var dataLength = xData.length, - cropStart = 0, - cropEnd = dataLength, - i, - j; - // line-type series need one point outside - cropShoulder = pick(cropShoulder, this.cropShoulder); - // 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. + * Vertical pixel offset of the title position. * - * @private - * @function Highcharts.Series#generatePoints + * @type {number} + * @product highcharts highstock gantt + * @apioption xAxis.title.y */ - 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]; - // #970: - if (!point && - typeof dataOptions[cursor] !== 'undefined') { - 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. - * - * @product highstock - * - * @name Highcharts.Point#dataGroup - * @type {Highcharts.DataGroupingInfoObject|undefined} - */ - point.dataGroup = series.groupMap[i]; - if (point.dataGroup.options) { - point.options = point.dataGroup.options; - extend(point, point.dataGroup.options); - // Collision of props and options (#9770) - delete point.dataLabels; - } - } - if (point) { // #6279 - /** - * Contains the point's index in the `Series.points` array. - * - * @name Highcharts.Point#index - * @type {number} - * @readonly - */ - 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 = void 0; // #1003 - } - } - } - /** - * Read only. An array containing those values converted to points. - * In case the series data length exceeds the `cropThreshold`, or if - * the data is grouped, `series.data` doesn't contain all the - * points. Also, in case a series is hidden, the `data` array may be - * empty. To access raw values, `series.options.data` will always be - * up to date. `Series.data` only contains the points that have been - * created on demand. To modify the data, use - * {@link Highcharts.Series#setData} or - * {@link Highcharts.Point#update}. - * - * @see Series.points - * - * @name Highcharts.Series#data - * @type {Array<Highcharts.Point>} - */ - 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 Highcharts.Series#points - * @type {Array<Highcharts.Point>} - */ - series.points = points; - fireEvent(this, 'afterGeneratePoints'); - }, /** - * Get current X extremes for the visible data. + * Alignment of the title relative to the axis values. Possible + * values are "low", "middle" or "high". * - * @private - * @function Highcharts.Series#getXExtremes + * @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 * - * @param {Array<number>} xData - * The data to inspect. Defaults to the current data within the - * visible range. - * @return {Highcharts.RangeObject} + * @type {Highcharts.AxisTitleAlignValue} */ - getXExtremes: function (xData) { - return { - min: arrayMin(xData), - max: arrayMax(xData) - }; - }, + align: "middle", /** - * Calculate Y extremes for the visible data. The result is returned - * as an object with `dataMin` and `dataMax` properties. + * 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 `whiteSpace: 'nowrap'`. * - * @private - * @function Highcharts.Series#getExtremes - * @param {Array<number>} [yData] - * The data to inspect. Defaults to the current data within the - * visible range. - * @param {boolean} [forceExtremesFromAll] - * Force getting extremes of a total series data range. - * @return {Highcharts.DataExtremesObject} + * In styled mode, the stroke width is given in the + * `.highcharts-axis-title` class. + * + * @sample {highcharts} highcharts/xaxis/title-style/ + * Red + * @sample {highcharts} highcharts/css/axis/ + * Styled mode + * + * @type {Highcharts.CSSObject} */ - getExtremes: function (yData, forceExtremesFromAll) { - var xAxis = this.xAxis, - yAxis = this.yAxis, - xData = this.processedXData || this.xData, - yDataLength, - activeYData = [], - activeCounter = 0, - // #2117, need to compensate for log X axis - xExtremes, - xMin = 0, - xMax = 0, - validValue, - withinRange, - // Handle X outside the viewed area. This does not work with - // non-sorted data like scatter (#7639). - shoulder = this.requireSorting ? this.cropShoulder : 0, - positiveValuesOnly = yAxis ? yAxis.positiveValuesOnly : false, - x, - y, - i, - j; - yData = yData || this.stackedYData || this.processedYData || []; - yDataLength = yData.length; - if (xAxis) { - xExtremes = xAxis.getExtremes(); - xMin = xExtremes.min; - xMax = xExtremes.max; - } - 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) || isArray(y)) && - ((y.length || y > 0) || !positiveValuesOnly)); - withinRange = (forceExtremesFromAll || - this.getExtremesFromAll || - this.options.getExtremesFromAll || - this.cropped || - !xAxis || // for colorAxis support - ((xData[i + shoulder] || x) >= xMin && - (xData[i - shoulder] || x) <= xMax)); - if (validValue && withinRange) { - j = y.length; - if (j) { // array, like ohlc or range data - while (j--) { - if (isNumber(y[j])) { // #7380, #11513 - activeYData[activeCounter++] = y[j]; - } - } - } - else { - activeYData[activeCounter++] = y; - } - } - } - var dataExtremes = { - dataMin: arrayMin(activeYData), - dataMax: arrayMax(activeYData) - }; - fireEvent(this, 'afterGetExtremes', { dataExtremes: dataExtremes }); - return dataExtremes; + style: { + /** @internal */ + 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. + * + * @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 + * + * @type {Highcharts.AxisTypeValue} + * @product highcharts gantt + */ + type: "linear", + /** + * If there are multiple axes on the same side of the chart, the pixel + * margin between the axes. Defaults to 0 on vertical axes, 15 on + * horizontal axes. + * + * @type {number} + * @since 7.0.3 + * @apioption xAxis.margin + */ + /** + * Applies only when the axis `type` is `category`. When `uniqueNames` + * is true, points are placed on the X axis according to their names. + * If the same point name is repeated in the same or another series, + * the point is placed on the same X position as other points of the + * same name. When `uniqueNames` is false, the points are laid out in + * increasing X positions regardless of their names, and the X axis + * category will take the name of the last point in each position. + * + * @sample {highcharts} highcharts/xaxis/uniquenames-true/ + * True by default + * @sample {highcharts} highcharts/xaxis/uniquenames-false/ + * False + * + * @type {boolean} + * @default true + * @since 4.2.7 + * @product highcharts gantt + * @apioption xAxis.uniqueNames + */ + /** + * Datetime axis only. An array determining what time intervals the + * ticks are allowed to fall on. 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: + * ```js + * 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<Array<string,(Array<number>|null)>>} + * @product highcharts highstock gantt + * @apioption xAxis.units + */ + /** + * Whether axis, including axis title, line, ticks and labels, should + * be visible. + * + * @type {boolean} + * @default true + * @since 4.1.9 + * @product highcharts highstock gantt + * @apioption xAxis.visible + */ + /** + * Color of the minor, secondary grid lines. + * + * In styled mode, the stroke width is given in the + * `.highcharts-minor-grid-line` class. + * + * @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 + * + * @type {Highcharts.ColorType} + * @default #f2f2f2 + */ + minorGridLineColor: "#f2f2f2", + /** + * 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. + * + * @sample {highcharts} highcharts/yaxis/minortickcolor/ + * Black tick marks on Y axis + * @sample {highstock} stock/xaxis/minorticks/ + * Black tick marks on Y axis + * + * @type {Highcharts.ColorType} + * @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. + * + * @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 + * + * @type {Highcharts.ColorType} + * @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. + * + * @sample {highcharts} highcharts/yaxis/gridlinecolor/ + * Green lines + * @sample {highcharts|highstock} highcharts/css/axis-grid/ + * Styled mode + * @sample {highstock} stock/xaxis/gridlinecolor/ + * Green lines + * + * @type {Highcharts.ColorType} + * @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. + * + * @sample {highcharts} highcharts/yaxis/gridlinewidth/ + * 2px lines + * @sample {highcharts|highstock} highcharts/css/axis-grid/ + * Styled mode + * @sample {highstock} stock/xaxis/gridlinewidth/ + * 2px lines + * + * @type {number} + * @default 0 + * @apioption xAxis.gridLineWidth + */ + // gridLineWidth: 0, + /** + * The height as the vertical axis. If it's a number, it is + * interpreted as pixels. + * + * Since Highcharts 2: If it's a percentage string, it is interpreted + * as percentages of the total plot height. + * + * @type {number|string} + * @product highcharts highstock + * @apioption xAxis.height + */ + /** + * The width as the horizontal axis. If it's a number, it is interpreted + * as pixels. + * + * Since Highcharts v5.0.13: If it's a percentage string, it is + * interpreted as percentages of the total plot width. + * + * @type {number|string} + * @product highcharts highstock + * @apioption xAxis.width + */ + /** + * Color for the main tick marks. + * + * In styled mode, the stroke is given in the `.highcharts-tick` + * class. + * + * @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 + * + * @type {Highcharts.ColorType} + * @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](/class-reference/Highcharts.Axis) for programmatic + * access to the axis. + * + * @type {*|Array<*>} + * @extends xAxis + * @excluding currentDateIndicator,ordinal,overscroll + * @optionparent yAxis + * + * @private + */ + Axis.defaultYAxisOptions = { + /** + * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`, + * `category` or `treegrid`. Defaults to `treegrid` for Gantt charts, + * `linear` for other chart types. + * + * 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 or treegrid axis, the [point names](#series.line.data.name) + * of the chart's series are used for categories, if a + * [categories](#xAxis.categories) array is not defined. + * + * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/ + * Logarithmic with minor grid lines + * @sample {highcharts} highcharts/yaxis/type-log-negative/ + * Logarithmic with extension to emulate negative values + * @sample {gantt} gantt/treegrid-axis/demo + * Treegrid axis + * + * @type {Highcharts.AxisTypeValue} + * @default {highcharts} linear + * @default {gantt} treegrid + * @product highcharts gantt + * @apioption yAxis.type + */ + /** + * The height of the Y axis. If it's a number, it is interpreted as + * pixels. + * + * Since Highcharts 2: If it's a percentage string, it is interpreted as + * percentages of the total plot height. + * + * @see [yAxis.top](#yAxis.top) + * + * @sample {highstock} stock/demo/candlestick-and-volume/ + * Percentage height panes + * + * @type {number|string} + * @product highcharts highstock + * @apioption yAxis.height + */ + /** + * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color + * to represent the maximum value of the Y axis. + * + * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/ + * Min and max colors + * + * @type {Highcharts.ColorType} + * @default #003399 + * @since 4.0 + * @product highcharts + * @apioption yAxis.maxColor + */ + /** + * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color + * to represent the minimum value of the Y axis. + * + * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/ + * Min and max color + * + * @type {Highcharts.ColorType} + * @default #e6ebf5 + * @since 4.0 + * @product highcharts + * @apioption yAxis.minColor + */ + /** + * Whether to reverse the axis so that the highest number is closest + * to the origin. + * + * @sample {highcharts} highcharts/yaxis/reversed/ + * Reversed Y axis + * @sample {highstock} stock/xaxis/reversed/ + * Reversed Y axis + * + * @type {boolean} + * @default {highcharts} false + * @default {highstock} false + * @default {highmaps} true + * @default {gantt} true + * @apioption yAxis.reversed + */ + /** + * If `true`, the first series in a stack will be drawn on top in a + * positive, non-reversed Y axis. If `false`, the first series is in + * the base of the stack. + * + * @sample {highcharts} highcharts/yaxis/reversedstacks-false/ + * Non-reversed stacks + * @sample {highstock} highcharts/yaxis/reversedstacks-false/ + * Non-reversed stacks + * + * @type {boolean} + * @default true + * @since 3.0.10 + * @product highcharts highstock + * @apioption yAxis.reversedStacks + */ + /** + * Solid gauge series only. Color stops for the solid gauge. Use this + * in cases where a linear gradient between a `minColor` and `maxColor` + * is not sufficient. The stops is an array of tuples, where the first + * item is a float between 0 and 1 assigning the relative position in + * the gradient, and the second item is the color. + * + * For solid gauges, the Y axis also inherits the concept of + * [data classes](https://api.highcharts.com/highmaps#colorAxis.dataClasses) + * from the Highmaps color axis. + * + * @see [minColor](#yAxis.minColor) + * @see [maxColor](#yAxis.maxColor) + * + * @sample {highcharts} highcharts/demo/gauge-solid/ + * True by default + * + * @type {Array<Array<number,Highcharts.ColorType>>} + * @since 4.0 + * @product highcharts + * @apioption yAxis.stops + */ + /** + * The pixel width of the major tick marks. + * + * @sample {highcharts} highcharts/xaxis/tickwidth/ 10 px width + * @sample {highstock} stock/xaxis/ticks/ Formatted ticks on X axis + * + * @type {number} + * @default 0 + * @product highcharts highstock gantt + * @apioption yAxis.tickWidth + */ + /** + * Whether to force the axis to end on a tick. Use this option with + * the `maxPadding` option to control the axis end. + * + * This option is always disabled, when panning type is + * either `y` or `xy`. + * + * @see [type](#chart.panning.type) + * + * + * @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 for Y axis + * + * @since 1.2.0 + */ + endOnTick: true, + /** + * 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. + * + * Also the `softThreshold` option takes precedence over `maxPadding`, + * so if the data is tangent to the threshold, `maxPadding` may not + * apply unless `softThreshold` is set to false. + * + * @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 gantt + */ + 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. + * + * Also the `softThreshold` option takes precedence over `minPadding`, + * so if the data is tangent to the threshold, `minPadding` may not + * apply unless `softThreshold` is set to false. + * + * @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 gantt + */ + minPadding: 0.05, + /** + * @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 + * + * @type {boolean} + * @default {highstock} true + * @default {highcharts} false + * @product highstock highcharts gantt + * @apioption yAxis.opposite + */ + /** + * @see [tickInterval](#xAxis.tickInterval) + * @see [tickPositioner](#xAxis.tickPositioner) + * @see [tickPositions](#xAxis.tickPositions) + */ + tickPixelInterval: 72, + showLastLabel: true, + /** + * @extends xAxis.labels + */ + labels: { + /** + * Angular gauges and solid gauges only. + * The label's pixel distance from the perimeter of the plot area. + * + * Since v7.1.2: If it's a percentage string, it is interpreted the + * same as [series.radius](#plotOptions.gauge.radius), so label can be + * aligned under the gauge's shape. + * + * @sample {highcharts} highcharts/yaxis/labels-distance/ + * Labels centered under the arc + * + * @type {number|string} + * @default -25 + * @product highcharts + * @apioption yAxis.labels.distance + */ /** - * Set the current data extremes as `dataMin` and `dataMax` on the - * Series item. Use this only when the series properties should be - * updated. + * The y position offset of all labels relative to the tick + * positions on the axis. For polar and radial axis consider the use + * of the [distance](#yAxis.labels.distance) option. * - * @private - * @function Highcharts.Series#applyExtremes - * @return {void} + * @sample {highcharts} highcharts/xaxis/labels-x/ + * Y axis labels placed on grid lines + * + * @type {number} + * @default {highcharts} 3 + * @default {highstock} -2 + * @default {highmaps} 3 + * @apioption yAxis.labels.y */ - applyExtremes: function () { - var dataExtremes = this.getExtremes(); - /** - * Contains the minimum value of the series' data point. Some series - * types like `networkgraph` do not support this property as they - * lack a `y`-value. - * @name Highcharts.Series#dataMin - * @type {number|undefined} - * @readonly - */ - this.dataMin = dataExtremes.dataMin; - /** - * Contains the maximum value of the series' data point. Some series - * types like `networkgraph` do not support this property as they - * lack a `y`-value. - * @name Highcharts.Series#dataMax - * @type {number|undefined} - * @readonly - */ - this.dataMax = dataExtremes.dataMax; - return dataExtremes; - }, /** - * Find and return the first non null point in the data + * 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. * - * @private - * @function Highcharts.Series.getFirstValidPoint - * @param {Array<Highcharts.PointOptionsType>} data - * Array of options for points + * Angular gauges and solid gauges defaults to `"center"`. + * Solid gauges with two labels have additional option `"auto"` + * for automatic horizontal and vertical alignment. + * + * @see [yAxis.labels.distance](#yAxis.labels.distance) + * + * @sample {highcharts} highcharts/yaxis/labels-align-left/ + * Left + * @sample {highcharts} highcharts/series-solidgauge/labels-auto-aligned/ + * Solid gauge labels auto aligned * - * @return {Highcharts.PointOptionsType} + * @type {Highcharts.AlignValue} + * @default {highcharts|highmaps} right + * @default {highstock} left + * @apioption yAxis.labels.align + */ + /** + * The x position offset of all labels relative to the tick + * positions 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. + * + * @type {Highcharts.ColorType} + * @apioption yAxis.lineColor + */ + /** + * @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 + */ + /** + * @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 + */ + /** + * An optional scrollbar to display on the Y axis in response to + * limiting the minimum an maximum of the axis values. + * + * 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`. + * + * @sample {highstock} stock/yaxis/scrollbar/ + * Scrollbar on the Y axis + * + * @extends scrollbar + * @since 4.2.6 + * @product highstock + * @excluding height + * @apioption yAxis.scrollbar + */ + /** + * Enable the scrollbar on the Y axis. + * + * @sample {highstock} stock/yaxis/scrollbar/ + * Enabled on Y axis + * + * @type {boolean} + * @default false + * @since 4.2.6 + * @product highstock + * @apioption yAxis.scrollbar.enabled + */ + /** + * Pixel margin between the scrollbar and the axis elements. + * + * @type {number} + * @default 10 + * @since 4.2.6 + * @product highstock + * @apioption yAxis.scrollbar.margin + */ + /** + * Whether to show the scrollbar when it is fully zoomed out at max + * range. Setting it to `false` on the Y axis makes the scrollbar stay + * hidden until the user zooms in, like common in browsers. + * + * @type {boolean} + * @default true + * @since 4.2.6 + * @product highstock + * @apioption yAxis.scrollbar.showFull + */ + /** + * The width of a vertical scrollbar or height of a horizontal + * scrollbar. Defaults to 20 on touch devices. + * + * @type {number} + * @default 14 + * @since 4.2.6 + * @product highstock + * @apioption yAxis.scrollbar.size + */ + /** + * Z index of the scrollbar elements. + * + * @type {number} + * @default 3 + * @since 4.2.6 + * @product highstock + * @apioption yAxis.scrollbar.zIndex + */ + /** + * A soft maximum for the axis. If the series data maximum is less + * than this, the axis will stay at this maximum, but if the series + * data maximum is higher, the axis will flex to show all data. + * + * **Note**: The [series.softThreshold]( + * #plotOptions.series.softThreshold) option takes precedence over this + * option. + * + * @sample highcharts/yaxis/softmin-softmax/ + * Soft min and max + * + * @type {number} + * @since 5.0.1 + * @product highcharts highstock gantt + * @apioption yAxis.softMax + */ + /** + * A soft minimum for the axis. If the series data minimum is greater + * than this, the axis will stay at this minimum, but if the series + * data minimum is lower, the axis will flex to show all data. + * + * **Note**: The [series.softThreshold]( + * #plotOptions.series.softThreshold) option takes precedence over this + * option. + * + * @sample highcharts/yaxis/softmin-softmax/ + * Soft min and max + * + * @type {number} + * @since 5.0.1 + * @product highcharts highstock gantt + * @apioption yAxis.softMin + */ + /** + * Defines the horizontal alignment of the stack total label. Can be one + * of `"left"`, `"center"` or `"right"`. The default value is calculated + * at runtime and depends on orientation and whether the stack is + * positive or negative. + * + * @sample {highcharts} highcharts/yaxis/stacklabels-align-left/ + * Aligned to the left + * @sample {highcharts} highcharts/yaxis/stacklabels-align-center/ + * Aligned in center + * @sample {highcharts} highcharts/yaxis/stacklabels-align-right/ + * Aligned to the right + * + * @type {Highcharts.AlignValue} + * @since 2.1.5 + * @product highcharts + * @apioption yAxis.stackLabels.align + */ + /** + * A format string for the data label. Available variables are the same + * as for `formatter`. + * + * @type {string} + * @default {total} + * @since 3.0.2 + * @product highcharts highstock + * @apioption yAxis.stackLabels.format + */ + /** + * Rotation of the labels in degrees. + * + * @sample {highcharts} highcharts/yaxis/stacklabels-rotation/ + * Labels rotated 45° + * + * @type {number} + * @default 0 + * @since 2.1.5 + * @product highcharts + * @apioption yAxis.stackLabels.rotation + */ + /** + * The text alignment for the label. While `align` determines where the + * texts anchor point is placed with regards to the stack, `textAlign` + * determines how the text is aligned against its anchor point. Possible + * values are `"left"`, `"center"` and `"right"`. The default value is + * calculated at runtime and depends on orientation and whether the + * stack is positive or negative. + * + * @sample {highcharts} highcharts/yaxis/stacklabels-textalign-left/ + * Label in center position but text-aligned left + * + * @type {Highcharts.AlignValue} + * @since 2.1.5 + * @product highcharts + * @apioption yAxis.stackLabels.textAlign + */ + /** + * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) + * to render the labels. + * + * @type {boolean} + * @default false + * @since 3.0 + * @product highcharts highstock + * @apioption yAxis.stackLabels.useHTML + */ + /** + * Defines the vertical alignment of the stack total label. Can be one + * of `"top"`, `"middle"` or `"bottom"`. The default value is calculated + * at runtime and depends on orientation and whether the stack is + * positive or negative. + * + * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-top/ + * Vertically aligned top + * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-middle/ + * Vertically aligned middle + * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-bottom/ + * Vertically aligned bottom + * + * @type {Highcharts.VerticalAlignValue} + * @since 2.1.5 + * @product highcharts + * @apioption yAxis.stackLabels.verticalAlign + */ + /** + * The x position offset of the label relative to the left of the + * stacked bar. The default value is calculated at runtime and depends + * on orientation and whether the stack is positive or negative. + * + * @sample {highcharts} highcharts/yaxis/stacklabels-x/ + * Stack total labels with x offset + * + * @type {number} + * @since 2.1.5 + * @product highcharts + * @apioption yAxis.stackLabels.x + */ + /** + * The y position offset of the label relative to the tick position + * on the axis. The default value is calculated at runtime and depends + * on orientation and whether the stack is positive or negative. + * + * @sample {highcharts} highcharts/yaxis/stacklabels-y/ + * Stack total labels with y offset + * + * @type {number} + * @since 2.1.5 + * @product highcharts + * @apioption yAxis.stackLabels.y + */ + /** + * Whether to force the axis to start on a tick. Use this option with + * the `maxPadding` option to control the axis start. + * + * This option is always disabled, when panning type is + * either `y` or `xy`. + * + * @see [type](#chart.panning.type) + * + * @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 gantt + */ + startOnTick: true, + title: { + /** + * The pixel distance between the axis labels and the title. + * Positive values are outside the axis line, negative are inside. + * + * @sample {highcharts} highcharts/xaxis/title-margin/ + * Y axis title margin of 60 + * + * @type {number} + * @default 40 + * @apioption yAxis.title.margin */ - getFirstValidPoint: function (data) { - var firstPoint = null, - dataLength = data.length, - i = 0; - while (firstPoint === null && i < dataLength) { - firstPoint = data[i]; - i++; - } - return firstPoint; - }, /** - * 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. - * - * @function Highcharts.Series#translate - * @return {void} - * @fires Highcharts.Series#events:translate + * 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 */ - 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, - enabledDataSorting = series.enabledDataSorting, - yAxis = series.yAxis, - points = series.points, - dataLength = points.length, - hasModifyValue = !!series.modifyValue, - i, - pointPlacement = series.pointPlacementToXValue(), // #7860 - dynamicallyPlaced = Boolean(pointPlacement), - threshold = options.threshold, - stackThreshold = options.startFromThreshold ? threshold : 0, - plotX, - lastPlotX, - stackIndicator, - zoneAxis = this.zoneAxis || 'y', - 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). - * @private - */ - function limitedRange(val) { - return clamp(val, -1e5, 1e5); - } - // 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.stacking && yAxis.stacking.stacks[(series.negStacks && - yValue < - (stackThreshold ? 0 : threshold) ? - '-' : - '') + series.stackKey], - pointStack, - stackValues; - if (yAxis.positiveValuesOnly && !yAxis.validatePositiveValue(yValue) || - xAxis.positiveValuesOnly && !xAxis.validatePositiveValue(xValue)) { - 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 && - stack && - stack[xValue]) { - stackIndicator = series.getStackIndicator(stackIndicator, xValue, series.index); - if (!point.isNull) { - pointStack = stack[xValue]; - stackValues = - pointStack.points[stackIndicator.key]; - } - } - if (isArray(stackValues)) { - yBottom = stackValues[0]; - yValue = stackValues[1]; - if (yBottom === stackThreshold && - stackIndicator.key === - stack[xValue].base) { - yBottom = pick((isNumber(threshold) && threshold), yAxis.min); - } - // #1200, #1232 - if (yAxis.positiveValuesOnly && yBottom <= 0) { - yBottom = null; - } - point.total = point.stackTotal = pointStack.total; - point.percentage = - pointStack.total && - (point.y / pointStack.total * 100); - point.stackY = yValue; - // Place the stack label - // in case of variwide series (where widths of points are - // different in most cases), stack labels are positioned - // wrongly, so the call of the setOffset is omited here and - // labels are correctly positioned later, at the end of the - // variwide's translate function (#10962) - if (!series.irregularWidths) { - 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 - // #3201 - point.plotY = ((typeof yValue === 'number' && yValue !== Infinity) ? - limitedRange(yAxis.translate(yValue, 0, 1, 0, 1)) : - void 0); - point.isInside = this.isPointInside(point); - // Set client related positions for mouse tracking - point.clientX = dynamicallyPlaced ? - correctFloat(xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)) : - plotX; // #1514, #5383, #5518 - // Negative points. For bubble charts, this means negative z - // values (#9728) - point.negative = point[zoneAxis] < (options[zoneAxis + 'Threshold'] || - threshold || - 0); - // some API data - point.category = (categories && - typeof categories[point.x] !== 'undefined' ? - categories[point.x] : - point.x); - // Determine auto enabling of markers (#3635, #5099) - if (!point.isNull && point.visible !== false) { - if (typeof lastPlotX !== 'undefined') { - closestPointRangePx = Math.min(closestPointRangePx, Math.abs(plotX - lastPlotX)); - } - lastPlotX = plotX; - } - // Find point zone - point.zone = (this.zones.length && point.getZone()); - // Animate new points with data sorting - if (!point.graphic && series.group && enabledDataSorting) { - point.isNew = true; - } - } - series.closestPointRangePx = closestPointRangePx; - fireEvent(this, 'afterTranslate'); - }, + rotation: 270, /** - * Return the series points with null points filtered out. + * 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 `undefined`. + * + * @sample {highcharts} highcharts/xaxis/title-text/ + * Custom HTML + * + * @type {string|null} + * @default {highcharts} Values + * @default {highstock} undefined + * @product highcharts highstock gantt + */ + text: "Values", + }, + /** + * The top position of the Y axis. If it's a number, it is interpreted + * as pixel position relative to the chart. + * + * Since Highcharts 2: If it's a percentage string, it is interpreted as + * percentages of the plot height, offset from plot area top. + * + * @see [yAxis.height](#yAxis.height) + * + * @sample {highstock} stock/demo/candlestick-and-volume/ + * Percentage height panes + * + * @type {number|string} + * @product highcharts highstock + * @apioption yAxis.top + */ + /** + * 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: { + /** + * Enable or disable the initial animation when a series is + * displayed for the `stackLabels`. The animation can also be set as + * a configuration object. Please note that this option only + * applies to the initial animation. + * For other animations, see [chart.animation](#chart.animation) + * and the animation parameter under the API methods. + * The following properties are supported: * - * @function Highcharts.Series#getValidPoints + * - `defer`: The animation delay time in milliseconds. * - * @param {Array<Highcharts.Point>} [points] - * The points to inspect, defaults to {@link Series.points}. + * @sample {highcharts} highcharts/plotoptions/animation-defer/ + * Animation defer settings + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + * @since 8.2.0 + * @apioption yAxis.stackLabels.animation + */ + animation: {}, + /** + * The animation delay time in milliseconds. + * Set to `0` renders stackLabel immediately. + * As `undefined` inherits defer time from the [series.animation.defer](#plotOptions.series.animation.defer). * - * @param {boolean} [insideOnly=false] - * Whether to inspect only the points that are inside the visible - * view. + * @type {number} + * @since 8.2.0 + * @apioption yAxis.stackLabels.animation.defer + */ + /** + * Allow the stack labels to overlap. * - * @param {boolean} [allowNull=false] - * Whether to allow null points to pass as valid points. + * @sample {highcharts} highcharts/yaxis/stacklabels-allowoverlap-false/ + * Default false * - * @return {Array<Highcharts.Point>} - * The valid points. + * @since 5.0.13 + * @product highcharts */ - getValidPoints: function (points, insideOnly, allowNull) { - var chart = this.chart; - // #3916, #5029, #5085 - return (points || this.points || []).filter(function isValidPoint(point) { - if (insideOnly && !chart.isInsidePlot(point.plotX, point.plotY, chart.inverted)) { - return false; - } - return point.visible !== false && - (allowNull || !point.isNull); - }); - }, + allowOverlap: false, /** - * Get the clipping for the series. Could be called for a series to - * initiate animating the clip or to set the final clip (only width - * and x). + * The background color or gradient for the stack label. * - * @private - * @function Highcharts.Series#getClip - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] - * Initialize the animation. - * @param {boolean} [finalBox] - * Final size for the clip - end state for the animation. - * @return {Highcharts.Dictionary<number>} + * @sample {highcharts} highcharts/yaxis/stacklabels-box/ + * Stack labels box options + * @type {Highcharts.ColorType} + * @since 8.1.0 + * @apioption yAxis.stackLabels.backgroundColor */ - getClipBox: function (animation, finalBox) { - var series = this, - options = series.options, - chart = series.chart, - inverted = chart.inverted, - xAxis = series.xAxis, - yAxis = xAxis && series.yAxis, - clipBox, - scrollablePlotAreaOptions = chart.options.chart.scrollablePlotArea || {}; - if (animation && options.clip === false && yAxis) { - // support for not clipped series animation (#10450) - clipBox = inverted ? { - y: -chart.chartWidth + yAxis.len + yAxis.pos, - height: chart.chartWidth, - width: chart.chartHeight, - x: -chart.chartHeight + xAxis.len + xAxis.pos - } : { - y: -yAxis.pos, - height: chart.chartHeight, - width: chart.chartWidth, - x: -xAxis.pos - }; - // x and width will be changed later when setting for animation - // initial state in Series.setClip - } - else { - clipBox = series.clipBox || chart.clipBox; - if (finalBox) { - clipBox.width = chart.plotSizeX; - clipBox.x = (chart.scrollablePixelsX || 0) * - (scrollablePlotAreaOptions.scrollPositionX || 0); - } - } - return !finalBox ? clipBox : { - width: clipBox.width, - x: clipBox.x - }; - }, /** - * 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. + * The border color for the stack label. Defaults to `undefined`. * - * @private - * @function Highcharts.Series#setClip - * @param {boolean|Highcharts.AnimationOptionsObject} [animation] + * @sample {highcharts} highcharts/yaxis/stacklabels-box/ + * Stack labels box options + * @type {Highcharts.ColorType} + * @since 8.1.0 + * @apioption yAxis.stackLabels.borderColor */ - setClip: function (animation) { - var chart = this.chart, options = this.options, renderer = chart.renderer, inverted = chart.inverted, seriesClipBox = this.clipBox, clipBox = this.getClipBox(animation), 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 (animation) { - clipBox.width = 0; - if (inverted) { - clipBox.x = chart.plotHeight + - (options.clip !== false ? 0 : chart.plotTop); - } - } - // 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) { - chart[sharedClipKey + 'm'] = markerClipRect = - renderer.clipRect( - // include the width of the first marker - inverted ? chart.plotSizeX + 99 : -99, 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 }; - // When the series is rendered again before starting animating, in - // compliance to a responsive rule - } - else if (!chart.hasLoaded) { - clipRect.attr(clipBox); - } - if (animation) { - if (!clipRect.count[this.index]) { - clipRect.count[this.index] = true; - clipRect.count.length += 1; - } - } - if (options.clip !== false || animation) { - 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. + * The border radius in pixels for the stack label. * - * @function Highcharts.Series#animate - * - * @param {boolean} [init] - * Initialize the animation. + * @sample {highcharts} highcharts/yaxis/stacklabels-box/ + * Stack labels box options + * @type {number} + * @default 0 + * @since 8.1.0 + * @apioption yAxis.stackLabels.borderRadius */ - animate: function (init) { - var series = this, - chart = series.chart, - animation = animObject(series.options.animation), - clipRect, - sharedClipKey, - finalBox; - // Initialize the animation. Set up the clipping rectangle. - if (!chart.hasRendered) { - if (init) { - series.setClip(animation); - // Run the animation - } - else { - sharedClipKey = this.sharedClipKey; - clipRect = chart[sharedClipKey]; - finalBox = series.getClipBox(animation, true); - if (clipRect) { - clipRect.animate(finalBox, animation); - } - if (chart[sharedClipKey + 'm']) { - chart[sharedClipKey + 'm'].animate({ - width: finalBox.width + 99, - x: finalBox.x - (chart.inverted ? 0 : 99) - }, animation); - } - } - } - }, /** - * This runs after animation to land on the final plot clipping. + * The border width in pixels for the stack label. * - * @private - * @function Highcharts.Series#afterAnimate - * @fires Highcharts.Series#event:afterAnimate + * @sample {highcharts} highcharts/yaxis/stacklabels-box/ + * Stack labels box options + * @type {number} + * @default 0 + * @since 8.1.0 + * @apioption yAxis.stackLabels.borderWidth */ - 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. + * Enable or disable the stack total labels. + * + * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/ + * Enabled stack total labels + * @sample {highcharts} highcharts/yaxis/stacklabels-enabled-waterfall/ + * Enabled stack labels in waterfall chart * - * @function Highcharts.Series#drawPoints + * @since 2.1.5 + * @product highcharts */ - drawPoints: function () { - var series = this, - points = series.points, - chart = series.chart, - i, - point, - graphic, - verb, - options = series.options, - seriesMarkerOptions = options.marker, - pointMarkerOptions, - hasPointMarker, - markerGroup = (series[series.specialGroup] || - series.markerGroup), - xAxis = series.xAxis, - markerAttribs, - globallyEnabled = pick(seriesMarkerOptions.enabled, !xAxis || xAxis.isRadial ? true : null, - // Use larger or equal as radius is null in bubbles (#6321) - series.closestPointRangePx >= (seriesMarkerOptions.enabledThreshold * - seriesMarkerOptions.radius)); - if (seriesMarkerOptions.enabled !== false || - series._hasPointMarkers) { - for (i = 0; i < points.length; i++) { - point = points[i]; - graphic = point.graphic; - verb = graphic ? 'animate' : 'attr'; - pointMarkerOptions = point.marker || {}; - hasPointMarker = !!point.marker; - var shouldDrawMarker = ((globallyEnabled && - typeof pointMarkerOptions.enabled === 'undefined') || pointMarkerOptions.enabled) && !point.isNull && point.visible !== false; - // only draw the point if y is defined - if (shouldDrawMarker) { - // Shortcuts - var symbol = pick(pointMarkerOptions.symbol, - series.symbol); - markerAttribs = series.markerAttribs(point, (point.selected && 'select')); - // Set starting position for point sliding animation. - if (series.enabledDataSorting) { - point.startXPos = xAxis.reversed ? - -markerAttribs.width : - xAxis.width; - } - var isInside = point.isInside !== false; - if (graphic) { // update - // Since the marker group isn't clipped, each - // individual marker must be toggled - graphic[isInside ? 'show' : 'hide'](isInside) - .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. - * - * @name Point#graphic - * @type {SVGElement} - */ - point.graphic = graphic = chart.renderer - .symbol(symbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height, hasPointMarker ? - pointMarkerOptions : - seriesMarkerOptions) - .add(markerGroup); - // Sliding animation for new points - if (series.enabledDataSorting && - chart.hasRendered) { - graphic.attr({ - x: point.startXPos - }); - verb = 'animate'; - } - } - if (graphic && verb === 'animate') { // update - // Since the marker group isn't clipped, each - // individual marker must be toggled - graphic[isInside ? 'show' : 'hide'](isInside) - .animate(markerAttribs); - } - // Presentational attributes - if (graphic && !chart.styledMode) { - graphic[verb](series.pointAttribs(point, (point.selected && 'select'))); - } - if (graphic) { - graphic.addClass(point.getClassName(), true); - } - } - else if (graphic) { - point.graphic = graphic.destroy(); // #1269 - } - } - } - }, + enabled: false, /** - * Get non-presentational attributes for a point. Used internally for - * both styled mode and classic. Can be overridden for different series - * types. + * Whether to hide stack labels that are outside the plot area. + * By default, the stack label is moved + * inside the plot area according to the + * [overflow](/highcharts/#yAxis/stackLabels/overflow) + * option. * - * @see Series#pointAttribs + * @type {boolean} + * @since 7.1.3 + */ + crop: true, + /** + * How to handle stack total labels that flow outside the plot area. + * The default is set to `"justify"`, + * which aligns them inside the plot area. + * For columns and bars, this means it will be moved inside the bar. + * To display stack labels outside the plot area, + * set `crop` to `false` and `overflow` to `"allow"`. * - * @function Highcharts.Series#markerAttribs + * @sample highcharts/yaxis/stacklabels-overflow/ + * Stack labels flows outside the plot area. * - * @param {Highcharts.Point} point - * The Point to inspect. + * @type {Highcharts.DataLabelsOverflowValue} + * @since 7.1.3 + */ + overflow: "justify", + /* eslint-disable valid-jsdoc */ + /** + * Callback JavaScript function to format the label. The value is + * given by `this.total`. * - * @param {string} [state] - * The state, can be either `hover`, `select` or undefined. + * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/ + * Added units to stack total value * - * @return {Highcharts.SVGAttributes} - * A hash containing those attributes that are not settable from - * CSS. + * @type {Highcharts.FormatterCallbackFunction<Highcharts.StackItemObject>} + * @since 2.1.5 + * @product highcharts */ - markerAttribs: function (point, state) { - var seriesOptions = this.options, - seriesMarkerOptions = seriesOptions.marker, - seriesStateOptions, - pointMarkerOptions = point.marker || {}, - symbol = (pointMarkerOptions.symbol || - seriesMarkerOptions.symbol), - 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)); - } - point.hasImage = symbol && symbol.indexOf('url') === 0; - if (point.hasImage) { - radius = 0; // and subsequently width and height is not set - } - attribs = { - // Math.floor for #1843: - x: seriesOptions.crisp ? - Math.floor(point.plotX) - radius : - point.plotX - radius, - y: point.plotY - radius - }; - if (radius) { - attribs.width = attribs.height = 2 * radius; - } - return attribs; + formatter: function () { + var numberFormatter = this.axis.chart.numberFormatter; + /* eslint-enable valid-jsdoc */ + return numberFormatter(this.total, -1); }, /** - * 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. - * - * @private - * @function Highcharts.Series#pointAttribs + * CSS styles for the label. * - * @param {Highcharts.Point} [point] - * The point instance to inspect. + * In styled mode, the styles are set in the + * `.highcharts-stack-label` class. * - * @param {string} [state] - * The point state, can be either `hover`, `select` or 'normal'. - * If undefined, normal state is assumed. + * @sample {highcharts} highcharts/yaxis/stacklabels-style/ + * Red stack total labels * - * @return {Highcharts.SVGAttributes} - * The presentational attributes to be set on the point. + * @type {Highcharts.CSSObject} + * @since 2.1.5 + * @product highcharts */ - 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, - opacity = 1; - color = (pointColorOption || - zoneColor || - pointColor || - color); - fill = (pointMarkerOptions.fillColor || - seriesMarkerOptions.fillColor || - color); - stroke = (pointMarkerOptions.lineColor || - seriesMarkerOptions.lineColor || - color); - // Handle hover and select states - state = state || 'normal'; - 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); - opacity = pick(pointStateOptions.opacity, seriesStateOptions.opacity, opacity); + style: { + /** @internal */ + color: "#000000", + /** @internal */ + fontSize: "11px", + /** @internal */ + fontWeight: "bold", + /** @internal */ + textOutline: "1px contrast", + }, + }, + gridLineWidth: 1, + lineWidth: 0, + // tickWidth: 0 + }; + /** + * The Z axis or depth axis for 3D plots. + * + * See the [Axis class](/class-reference/Highcharts.Axis) for programmatic + * access to the axis. + * + * @sample {highcharts} highcharts/3d/scatter-zaxis-categories/ + * Z-Axis with Categories + * @sample {highcharts} highcharts/3d/scatter-zaxis-grid/ + * Z-Axis with styling + * + * @type {*|Array<*>} + * @extends xAxis + * @since 5.0.0 + * @product highcharts + * @excluding breaks, crosshair, height, left, lineColor, lineWidth, + * nameToX, showEmpty, top, width + * @apioption zAxis + * + * @private + */ + // This variable extends the defaultOptions for left axes. + Axis.defaultLeftAxisOptions = { + labels: { + x: -15, + }, + title: { + rotation: 270, + }, + }; + // This variable extends the defaultOptions for right axes. + Axis.defaultRightAxisOptions = { + labels: { + x: 15, + }, + title: { + rotation: 90, + }, + }; + // This variable extends the defaultOptions for bottom axes. + Axis.defaultBottomAxisOptions = { + labels: { + autoRotation: [-45], + x: 0, + // overflow: undefined, + // staggerLines: null + }, + margin: 15, + title: { + rotation: 0, + }, + }; + // This variable extends the defaultOptions for top axes. + Axis.defaultTopAxisOptions = { + labels: { + autoRotation: [-45], + x: 0, + // overflow: undefined + // staggerLines: null + }, + margin: 15, + title: { + rotation: 0, + }, + }; + // Properties to survive after destroy, needed for Axis.update (#4317, + // #5773, #5881). + Axis.keepProps = [ + "extKey", + "hcEvents", + "names", + "series", + "userMax", + "userMin", + ]; + return Axis; + })(); + H.Axis = Axis; + + return H.Axis; + } + ); + _registerModule( + _modules, + "Core/Axis/DateTimeAxis.js", + [_modules["Core/Axis/Axis.js"], _modules["Core/Utilities.js"]], + function (Axis, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var addEvent = U.addEvent, + getMagnitude = U.getMagnitude, + normalizeTickInterval = U.normalizeTickInterval, + timeUnits = U.timeUnits; + /* eslint-disable valid-jsdoc */ + var DateTimeAxisAdditions = /** @class */ (function () { + /* * + * + * Constructors + * + * */ + function DateTimeAxisAdditions(axis) { + this.axis = axis; + } + /* * + * + * Functions + * + * */ + /** + * 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. + * @private + */ + /** + * 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. + * @private + */ + DateTimeAxisAdditions.prototype.normalizeTimeTickInterval = function ( + tickInterval, + unitsOption + ) { + var units = unitsOption || [ + [ + "millisecond", + [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" // #1913, #2360 + ? Math.max(getMagnitude(tickInterval / interval), 1) + : 1 + ); + return { + unitRange: interval, + count: count, + unitName: unit[0], + }; + }; + return DateTimeAxisAdditions; + })(); + /** + * Date and time support for axes. + * + * @private + * @class + */ + var DateTimeAxis = /** @class */ (function () { + function DateTimeAxis() {} + /* * + * + * Static Functions + * + * */ + /** + * Extends axis class with date and time support. + * @private + */ + DateTimeAxis.compose = function (AxisClass) { + AxisClass.keepProps.push("dateTime"); + var axisProto = AxisClass.prototype; + /** + * 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. + * + * @private + * @function Highcharts.Axis#getTimeTicks + * + * @param {Highcharts.TimeNormalizeObject} normalizedInterval + * The interval in axis values (ms) and thecount. + * + * @param {number} min + * The minimum in axis values. + * + * @param {number} max + * The maximum in axis values. + * + * @param {number} startOfWeek + * + * @return {Highcharts.AxisTickPositionsArray} + */ + axisProto.getTimeTicks = function () { + return this.chart.time.getTimeTicks.apply( + this.chart.time, + arguments + ); + }; + /* eslint-disable no-invalid-this */ + addEvent(AxisClass, "init", function (e) { + var axis = this; + var options = e.userOptions; + if (options.type !== "datetime") { + axis.dateTime = void 0; + return; + } + if (!axis.dateTime) { + axis.dateTime = new DateTimeAxisAdditions(axis); + } + }); + /* eslint-enable no-invalid-this */ + }; + /* * + * + * Static Properties + * + * */ + DateTimeAxis.AdditionsClass = DateTimeAxisAdditions; + return DateTimeAxis; + })(); + DateTimeAxis.compose(Axis); + + return DateTimeAxis; + } + ); + _registerModule( + _modules, + "Core/Axis/LogarithmicAxis.js", + [_modules["Core/Axis/Axis.js"], _modules["Core/Utilities.js"]], + function (Axis, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var addEvent = U.addEvent, + getMagnitude = U.getMagnitude, + normalizeTickInterval = U.normalizeTickInterval, + pick = U.pick; + /* eslint-disable valid-jsdoc */ + /** + * Provides logarithmic support for axes. + * + * @private + * @class + */ + var LogarithmicAxisAdditions = /** @class */ (function () { + /* * + * + * Constructors + * + * */ + function LogarithmicAxisAdditions(axis) { + this.axis = axis; + } + /* * + * + * Functions + * + * */ + /** + * Set the tick positions of a logarithmic axis. + */ + LogarithmicAxisAdditions.prototype.getLogTickPositions = function ( + interval, + min, + max, + minor + ) { + var log = this; + var axis = log.axis; + var axisLength = axis.len; + var options = axis.options; + // Since we use this method for both major and minor ticks, + // use a local variable and return the result + var positions = []; + // Reset + if (!minor) { + log.minorAutoInterval = void 0; + } + // 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]; + // 0.2 equals five minor ticks per 1, 10, 100 etc + } else if (interval > 0.15) { + 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 = log.log2lin(log.lin2log(i) * intermediate[j]); + // #1670, lastPos is #3113 + if ( + pos > min && + (!minor || lastPos <= max) && + typeof lastPos !== "undefined" + ) { + positions.push(lastPos); } - return { - 'stroke': stroke, - 'stroke-width': strokeWidth, - 'fill': fill, - 'opacity': opacity - }; + 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 = log.lin2log(min), + realMax = log.lin2log(max), + tickIntervalOption = minor + ? axis.getMinorTickInterval() + : options.tickInterval, + filteredTickIntervalOption = + tickIntervalOption === "auto" ? null : tickIntervalOption, + tickPixelIntervalOption = + options.tickPixelInterval / (minor ? 5 : 1), + totalPixelLength = minor + ? axisLength / axis.tickPositions.length + : axisLength; + interval = pick( + filteredTickIntervalOption, + log.minorAutoInterval, + ((realMax - realMin) * tickPixelIntervalOption) / + (totalPixelLength || 1) + ); + interval = normalizeTickInterval( + interval, + void 0, + getMagnitude(interval) + ); + positions = axis + .getLinearTickPositions(interval, realMin, realMax) + .map(log.log2lin); + if (!minor) { + log.minorAutoInterval = interval / 5; + } + } + // Set the axis-level tickInterval variable + if (!minor) { + axis.tickInterval = interval; + } + return positions; + }; + LogarithmicAxisAdditions.prototype.lin2log = function (num) { + return Math.pow(10, num); + }; + LogarithmicAxisAdditions.prototype.log2lin = function (num) { + return Math.log(num) / Math.LN10; + }; + return LogarithmicAxisAdditions; + })(); + var LogarithmicAxis = /** @class */ (function () { + function LogarithmicAxis() {} + /** + * Provides logarithmic support for axes. + * + * @private + */ + LogarithmicAxis.compose = function (AxisClass) { + AxisClass.keepProps.push("logarithmic"); + // HC <= 8 backwards compatibility, allow wrapping + // Axis.prototype.lin2log and log2lin + // @todo Remove this in next major + var axisProto = AxisClass.prototype; + var logAxisProto = LogarithmicAxisAdditions.prototype; + axisProto.log2lin = logAxisProto.log2lin; + axisProto.lin2log = logAxisProto.lin2log; + /* eslint-disable no-invalid-this */ + addEvent(AxisClass, "init", function (e) { + var axis = this; + var options = e.userOptions; + var logarithmic = axis.logarithmic; + if (options.type !== "logarithmic") { + axis.logarithmic = void 0; + } else { + if (!logarithmic) { + logarithmic = axis.logarithmic = new LogarithmicAxisAdditions( + axis + ); + } + // HC <= 8 backwards compatibility, allow wrapping + // Axis.prototype.lin2log and log2lin + // @todo Remove this in next major + if (axis.log2lin !== logarithmic.log2lin) { + logarithmic.log2lin = axis.log2lin.bind(axis); + } + if (axis.lin2log !== logarithmic.lin2log) { + logarithmic.lin2log = axis.lin2log.bind(axis); + } + } + }); + addEvent(AxisClass, "afterInit", function () { + var axis = this; + var log = axis.logarithmic; + // extend logarithmic axis + if (log) { + axis.lin2val = function (num) { + return log.lin2log(num); + }; + axis.val2lin = function (num) { + return log.log2lin(num); + }; + } + }); + }; + return LogarithmicAxis; + })(); + LogarithmicAxis.compose(Axis); // @todo move to factory functions + + return LogarithmicAxis; + } + ); + _registerModule( + _modules, + "Core/Axis/PlotLineOrBand.js", + [ + _modules["Core/Axis/Axis.js"], + _modules["Core/Globals.js"], + _modules["Core/Utilities.js"], + ], + function (Axis, H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /** + * Options for plot bands on axes. + * + * @typedef {Highcharts.XAxisPlotBandsOptions|Highcharts.YAxisPlotBandsOptions|Highcharts.ZAxisPlotBandsOptions} Highcharts.AxisPlotBandsOptions + */ + /** + * Options for plot band labels on axes. + * + * @typedef {Highcharts.XAxisPlotBandsLabelOptions|Highcharts.YAxisPlotBandsLabelOptions|Highcharts.ZAxisPlotBandsLabelOptions} Highcharts.AxisPlotBandsLabelOptions + */ + /** + * Options for plot lines on axes. + * + * @typedef {Highcharts.XAxisPlotLinesOptions|Highcharts.YAxisPlotLinesOptions|Highcharts.ZAxisPlotLinesOptions} Highcharts.AxisPlotLinesOptions + */ + /** + * Options for plot line labels on axes. + * + * @typedef {Highcharts.XAxisPlotLinesLabelOptions|Highcharts.YAxisPlotLinesLabelOptions|Highcharts.ZAxisPlotLinesLabelOptions} Highcharts.AxisPlotLinesLabelOptions + */ + var arrayMax = U.arrayMax, + arrayMin = U.arrayMin, + defined = U.defined, + destroyObjectProperties = U.destroyObjectProperties, + erase = U.erase, + extend = U.extend, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick; + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * The object wrapper for plot lines and plot bands + * + * @class + * @name Highcharts.PlotLineOrBand + * + * @param {Highcharts.Axis} axis + * + * @param {Highcharts.AxisPlotLinesOptions|Highcharts.AxisPlotBandsOptions} [options] + */ + var PlotLineOrBand = /** @class */ (function () { + function PlotLineOrBand(axis, options) { + this.axis = axis; + if (options) { + this.options = options; + this.id = options.id; + } + } + /** + * Render the plot line or plot band. If it is already existing, + * move it. + * + * @private + * @function Highcharts.PlotLineOrBand#render + * @return {Highcharts.PlotLineOrBand|undefined} + */ + PlotLineOrBand.prototype.render = function () { + H.fireEvent(this, "render"); + var plotLine = this, + axis = plotLine.axis, + horiz = axis.horiz, + log = axis.logarithmic, + 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; + // logarithmic conversion + if (log) { + from = log.log2lin(from); + to = log.log2lin(to); + value = log.log2lin(value); + } + // Set the presentational attributes + if (!axis.chart.styledMode) { + if (isLine) { + attribs.stroke = color || "#999999"; + attribs["stroke-width"] = pick(options.width, 1); + if (options.dashStyle) { + attribs.dashstyle = options.dashStyle; + } + } else if (isBand) { + // plot band + attribs.fill = color || "#e6ebf5"; + 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) { + /** + * SVG element of the plot line or band. + * + * @name Highcharts.PlotLineOrBand#svgElement + * @type {Highcharts.SVGElement} + */ + plotLine.svgElem = svgElem = renderer + .path() + .attr(attribs) + .add(group); + } + // Set the path or return + if (isLine) { + path = axis.getPlotLinePath({ + value: value, + lineWidth: svgElem.strokeWidth(), + acrossPanes: options.acrossPanes, + }); + } else if (isBand) { + // plot band + path = axis.getPlotBandPath(from, to, options); + } else { + return; + } + // common for lines and bands + // Add events only if they were not added before. + if (!plotLine.eventsAdded && events) { + objectEach(events, function (event, eventType) { + svgElem.on(eventType, function (e) { + events[eventType].apply(plotLine, [e]); + }); + }); + plotLine.eventsAdded = true; + } + if ((isNew || !svgElem.d) && path && path.length) { + svgElem.attr({ d: path }); + } else if (svgElem) { + if (path) { + svgElem.show(true); + svgElem.animate({ d: path }); + } else if (svgElem.d) { + svgElem.hide(); + if (label) { + plotLine.label = label = label.destroy(); + } + } + } + // the plot band/line label + if ( + optionsLabel && + (defined(optionsLabel.text) || defined(optionsLabel.formatter)) && + path && + path.length && + axis.width > 0 && + axis.height > 0 && + !path.isFlat + ) { + // 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. + * + * @private + * @function Highcharts.PlotLineOrBand#renderLabel + * @param {Highcharts.AxisPlotLinesLabelOptions|Highcharts.AxisPlotBandsLabelOptions} optionsLabel + * @param {Highcharts.SVGPathArray} path + * @param {boolean} [isBand] + * @param {number} [zIndex] + * @return {void} + */ + PlotLineOrBand.prototype.renderLabel = function ( + optionsLabel, + path, + isBand, + zIndex + ) { + var plotLine = this, + label = plotLine.label, + renderer = plotLine.axis.chart.renderer, + attribs, + xBounds, + yBounds, + x, + y, + labelText; + // 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; + labelText = this.getLabelText(optionsLabel); + /** + * SVG element of the label. + * + * @name Highcharts.PlotLineOrBand#label + * @type {Highcharts.SVGElement} + */ + plotLine.label = label = renderer + .text(labelText, 0, 0, optionsLabel.useHTML) + .attr(attribs) + .add(); + if (!this.axis.chart.styledMode) { + 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[0][1], + path[1][1], + isBand ? path[2][1] : path[0][1], + ]; + yBounds = path.yBounds || [ + path[0][2], + path[1][2], + isBand ? path[2][2] : path[0][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(true); + }; + /** + * Get label's text content. + * + * @private + * @function Highcharts.PlotLineOrBand#getLabelText + * @param {Highcharts.AxisPlotLinesLabelOptions|Highcharts.AxisPlotBandsLabelOptions} optionsLabel + * @return {string} + */ + PlotLineOrBand.prototype.getLabelText = function (optionsLabel) { + return defined(optionsLabel.formatter) + ? optionsLabel.formatter.call(this) + : optionsLabel.text; + }; + /** + * Remove the plot line or band. + * + * @function Highcharts.PlotLineOrBand#destroy + * @return {void} + */ + PlotLineOrBand.prototype.destroy = function () { + // remove it from the lookup + erase(this.axis.plotLinesAndBands, this); + delete this.axis; + destroyObjectProperties(this); + }; + return PlotLineOrBand; + })(); + /* eslint-enable no-invalid-this, valid-jsdoc */ + // Object with members for extending the Axis prototype + extend( + Axis.prototype, + /** @lends Highcharts.Axis.prototype */ { + /** + * An array of colored bands stretching across the plot area marking an + * interval on the axis. + * + * In styled mode, the plot bands are styled by the `.highcharts-plot-band` + * class in addition to the `className` option. + * + * @productdesc {highcharts} + * In a gauge, a plot band on the Y axis (value axis) will stretch along the + * perimeter of the gauge. + * + * @type {Array<*>} + * @product highcharts highstock gantt + * @apioption xAxis.plotBands + */ + /** + * Flag to decide if plotBand should be rendered across all panes. + * + * @since 7.1.2 + * @product highstock + * @type {boolean} + * @default true + * @apioption xAxis.plotBands.acrossPanes + */ + /** + * Border color for the plot band. Also requires `borderWidth` to be set. + * + * @type {Highcharts.ColorString} + * @apioption xAxis.plotBands.borderColor + */ + /** + * Border width for the plot band. Also requires `borderColor` to be set. + * + * @type {number} + * @default 0 + * @apioption xAxis.plotBands.borderWidth + */ + /** + * A custom class name, in addition to the default `highcharts-plot-band`, + * to apply to each individual band. + * + * @type {string} + * @since 5.0.0 + * @apioption xAxis.plotBands.className + */ + /** + * The color of the plot band. + * + * @sample {highcharts} highcharts/xaxis/plotbands-color/ + * Color band + * @sample {highstock} stock/xaxis/plotbands/ + * Plot band on Y axis + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @default #e6ebf5 + * @apioption xAxis.plotBands.color + */ + /** + * An object defining mouse events for the plot band. Supported properties + * are `click`, `mouseover`, `mouseout`, `mousemove`. + * + * @sample {highcharts} highcharts/xaxis/plotbands-events/ + * Mouse events demonstrated + * + * @since 1.2 + * @apioption xAxis.plotBands.events + */ + /** + * Click event on a plot band. + * + * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotBands.events.click + */ + /** + * Mouse move event on a plot band. + * + * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotBands.events.mousemove + */ + /** + * Mouse out event on the corner of a plot band. + * + * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotBands.events.mouseout + */ + /** + * Mouse over event on a plot band. + * + * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotBands.events.mouseover + */ + /** + * The start position of the plot band in axis units. + * + * @sample {highcharts} highcharts/xaxis/plotbands-color/ + * Datetime axis + * @sample {highcharts} highcharts/xaxis/plotbands-from/ + * Categorized axis + * @sample {highstock} stock/xaxis/plotbands/ + * Plot band on Y axis + * + * @type {number} + * @apioption xAxis.plotBands.from + */ + /** + * An id used for identifying the plot band in Axis.removePlotBand. + * + * @sample {highcharts} highcharts/xaxis/plotbands-id/ + * Remove plot band by id + * @sample {highstock} highcharts/xaxis/plotbands-id/ + * Remove plot band by id + * + * @type {string} + * @apioption xAxis.plotBands.id + */ + /** + * The end position of the plot band in axis units. + * + * @sample {highcharts} highcharts/xaxis/plotbands-color/ + * Datetime axis + * @sample {highcharts} highcharts/xaxis/plotbands-from/ + * Categorized axis + * @sample {highstock} stock/xaxis/plotbands/ + * Plot band on Y axis + * + * @type {number} + * @apioption xAxis.plotBands.to + */ + /** + * The z index of the plot band within the chart, relative to other + * elements. Using the same z index as another element may give + * unpredictable results, as the last rendered element will be on top. + * Values from 0 to 20 make sense. + * + * @sample {highcharts} highcharts/xaxis/plotbands-color/ + * Behind plot lines by default + * @sample {highcharts} highcharts/xaxis/plotbands-zindex/ + * Above plot lines + * @sample {highcharts} highcharts/xaxis/plotbands-zindex-above-series/ + * Above plot lines and series + * + * @type {number} + * @since 1.2 + * @apioption xAxis.plotBands.zIndex + */ + /** + * Text labels for the plot bands + * + * @product highcharts highstock gantt + * @apioption xAxis.plotBands.label + */ + /** + * Horizontal alignment of the label. Can be one of "left", "center" or + * "right". + * + * @sample {highcharts} highcharts/xaxis/plotbands-label-align/ + * Aligned to the right + * @sample {highstock} stock/xaxis/plotbands-label/ + * Plot band with labels + * + * @type {Highcharts.AlignValue} + * @default center + * @since 2.1 + * @apioption xAxis.plotBands.label.align + */ + /** + * Rotation of the text label in degrees . + * + * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/ + * Vertical text + * + * @type {number} + * @default 0 + * @since 2.1 + * @apioption xAxis.plotBands.label.rotation + */ + /** + * CSS styles for the text label. + * + * In styled mode, the labels are styled by the + * `.highcharts-plot-band-label` class. + * + * @sample {highcharts} highcharts/xaxis/plotbands-label-style/ + * Blue and bold label + * + * @type {Highcharts.CSSObject} + * @since 2.1 + * @apioption xAxis.plotBands.label.style + */ + /** + * The string text itself. A subset of HTML is supported. + * + * @type {string} + * @since 2.1 + * @apioption xAxis.plotBands.label.text + */ + /** + * The text alignment for the label. While `align` determines where the + * texts anchor point is placed within the plot band, `textAlign` determines + * how the text is aligned against its anchor point. Possible values are + * "left", "center" and "right". Defaults to the same as the `align` option. + * + * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/ + * Vertical text in center position but text-aligned left + * + * @type {Highcharts.AlignValue} + * @since 2.1 + * @apioption xAxis.plotBands.label.textAlign + */ + /** + * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) + * to render the labels. + * + * @type {boolean} + * @default false + * @since 3.0.3 + * @apioption xAxis.plotBands.label.useHTML + */ + /** + * Vertical alignment of the label relative to the plot band. Can be one of + * "top", "middle" or "bottom". + * + * @sample {highcharts} highcharts/xaxis/plotbands-label-verticalalign/ + * Vertically centered label + * @sample {highstock} stock/xaxis/plotbands-label/ + * Plot band with labels + * + * @type {Highcharts.VerticalAlignValue} + * @default top + * @since 2.1 + * @apioption xAxis.plotBands.label.verticalAlign + */ + /** + * Horizontal position relative the alignment. Default varies by + * orientation. + * + * @sample {highcharts} highcharts/xaxis/plotbands-label-align/ + * Aligned 10px from the right edge + * @sample {highstock} stock/xaxis/plotbands-label/ + * Plot band with labels + * + * @type {number} + * @since 2.1 + * @apioption xAxis.plotBands.label.x + */ + /** + * Vertical position of the text baseline relative to the alignment. Default + * varies by orientation. + * + * @sample {highcharts} highcharts/xaxis/plotbands-label-y/ + * Label on x axis + * @sample {highstock} stock/xaxis/plotbands-label/ + * Plot band with labels + * + * @type {number} + * @since 2.1 + * @apioption xAxis.plotBands.label.y + */ + /** + * An array of lines stretching across the plot area, marking a specific + * value on one of the axes. + * + * In styled mode, the plot lines are styled by the + * `.highcharts-plot-line` class in addition to the `className` option. + * + * @type {Array<*>} + * @product highcharts highstock gantt + * @sample {highcharts} highcharts/xaxis/plotlines-color/ + * Basic plot line + * @sample {highcharts} highcharts/series-solidgauge/labels-auto-aligned/ + * Solid gauge plot line + * @apioption xAxis.plotLines + */ + /** + * Flag to decide if plotLine should be rendered across all panes. + * + * @sample {highstock} stock/xaxis/plotlines-acrosspanes/ + * Plot lines on different panes + * + * @since 7.1.2 + * @product highstock + * @type {boolean} + * @default true + * @apioption xAxis.plotLines.acrossPanes + */ + /** + * A custom class name, in addition to the default `highcharts-plot-line`, + * to apply to each individual line. + * + * @type {string} + * @since 5.0.0 + * @apioption xAxis.plotLines.className + */ + /** + * The color of the line. + * + * @sample {highcharts} highcharts/xaxis/plotlines-color/ + * A red line from X axis + * @sample {highstock} stock/xaxis/plotlines/ + * Plot line on Y axis + * + * @type {Highcharts.ColorString} + * @default #999999 + * @apioption xAxis.plotLines.color + */ + /** + * The dashing or dot style for the plot line. For possible values see + * [this overview](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). + * + * @sample {highcharts} highcharts/xaxis/plotlines-dashstyle/ + * Dash and dot pattern + * @sample {highstock} stock/xaxis/plotlines/ + * Plot line on Y axis + * + * @type {Highcharts.DashStyleValue} + * @default Solid + * @since 1.2 + * @apioption xAxis.plotLines.dashStyle + */ + /** + * An object defining mouse events for the plot line. Supported + * properties are `click`, `mouseover`, `mouseout`, `mousemove`. + * + * @sample {highcharts} highcharts/xaxis/plotlines-events/ + * Mouse events demonstrated + * + * @since 1.2 + * @apioption xAxis.plotLines.events + */ + /** + * Click event on a plot band. + * + * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotLines.events.click + */ + /** + * Mouse move event on a plot band. + * + * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotLines.events.mousemove + */ + /** + * Mouse out event on the corner of a plot band. + * + * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotLines.events.mouseout + */ + /** + * Mouse over event on a plot band. + * + * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotLines.events.mouseover + */ + /** + * An id used for identifying the plot line in Axis.removePlotLine. + * + * @sample {highcharts} highcharts/xaxis/plotlines-id/ + * Remove plot line by id + * + * @type {string} + * @apioption xAxis.plotLines.id + */ + /** + * The position of the line in axis units. + * + * @sample {highcharts} highcharts/xaxis/plotlines-color/ + * Between two categories on X axis + * @sample {highstock} stock/xaxis/plotlines/ + * Plot line on Y axis + * + * @type {number} + * @apioption xAxis.plotLines.value + */ + /** + * The width or thickness of the plot line. + * + * @sample {highcharts} highcharts/xaxis/plotlines-color/ + * 2px wide line from X axis + * @sample {highstock} stock/xaxis/plotlines/ + * Plot line on Y axis + * + * @type {number} + * @default 2 + * @apioption xAxis.plotLines.width + */ + /** + * The z index of the plot line within the chart. + * + * @sample {highcharts} highcharts/xaxis/plotlines-zindex-behind/ + * Behind plot lines by default + * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above/ + * Above plot lines + * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above-all/ + * Above plot lines and series + * + * @type {number} + * @since 1.2 + * @apioption xAxis.plotLines.zIndex + */ + /** + * Text labels for the plot bands + * + * @apioption xAxis.plotLines.label + */ + /** + * Horizontal alignment of the label. Can be one of "left", "center" or + * "right". + * + * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/ + * Aligned to the right + * @sample {highstock} stock/xaxis/plotlines/ + * Plot line on Y axis + * + * @type {Highcharts.AlignValue} + * @default left + * @since 2.1 + * @apioption xAxis.plotLines.label.align + */ + /** + * Callback JavaScript function to format the label. Useful properties like + * the value of plot line or the range of plot band (`from` & `to` + * properties) can be found in `this.options` object. + * + * @sample {highcharts} highcharts/xaxis/plotlines-plotbands-label-formatter + * Label formatters for plot line and plot band. + * @type {Highcharts.FormatterCallbackFunction<Highcharts.PlotLineOrBand>} + * @apioption xAxis.plotLines.label.formatter + */ + /** + * Rotation of the text label in degrees. Defaults to 0 for horizontal plot + * lines and 90 for vertical lines. + * + * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/ + * Slanted text + * + * @type {number} + * @since 2.1 + * @apioption xAxis.plotLines.label.rotation + */ + /** + * CSS styles for the text label. + * + * In styled mode, the labels are styled by the + * `.highcharts-plot-line-label` class. + * + * @sample {highcharts} highcharts/xaxis/plotlines-label-style/ + * Blue and bold label + * + * @type {Highcharts.CSSObject} + * @since 2.1 + * @apioption xAxis.plotLines.label.style + */ + /** + * The text itself. A subset of HTML is supported. + * + * @type {string} + * @since 2.1 + * @apioption xAxis.plotLines.label.text + */ + /** + * The text alignment for the label. While `align` determines where the + * texts anchor point is placed within the plot band, `textAlign` determines + * how the text is aligned against its anchor point. Possible values are + * "left", "center" and "right". Defaults to the same as the `align` option. + * + * @sample {highcharts} highcharts/xaxis/plotlines-label-textalign/ + * Text label in bottom position + * + * @type {Highcharts.AlignValue} + * @since 2.1 + * @apioption xAxis.plotLines.label.textAlign + */ + /** + * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) + * to render the labels. + * + * @type {boolean} + * @default false + * @since 3.0.3 + * @apioption xAxis.plotLines.label.useHTML + */ + /** + * Vertical alignment of the label relative to the plot line. Can be + * one of "top", "middle" or "bottom". + * + * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/ + * Vertically centered label + * + * @type {Highcharts.VerticalAlignValue} + * @default {highcharts} top + * @default {highstock} top + * @since 2.1 + * @apioption xAxis.plotLines.label.verticalAlign + */ + /** + * Horizontal position relative the alignment. Default varies by + * orientation. + * + * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/ + * Aligned 10px from the right edge + * @sample {highstock} stock/xaxis/plotlines/ + * Plot line on Y axis + * + * @type {number} + * @since 2.1 + * @apioption xAxis.plotLines.label.x + */ + /** + * Vertical position of the text baseline relative to the alignment. Default + * varies by orientation. + * + * @sample {highcharts} highcharts/xaxis/plotlines-label-y/ + * Label below the plot line + * @sample {highstock} stock/xaxis/plotlines/ + * Plot line on Y axis + * + * @type {number} + * @since 2.1 + * @apioption xAxis.plotLines.label.y + */ + /** + * + * @type {Array<*>} + * @extends xAxis.plotBands + * @apioption yAxis.plotBands + */ + /** + * In a gauge chart, this option determines the inner radius of the + * plot band that stretches along the perimeter. It can be given as + * a percentage string, like `"100%"`, or as a pixel number, like `100`. + * By default, the inner radius is controlled by the [thickness]( + * #yAxis.plotBands.thickness) option. + * + * @sample {highcharts} highcharts/xaxis/plotbands-gauge + * Gauge plot band + * + * @type {number|string} + * @since 2.3 + * @product highcharts + * @apioption yAxis.plotBands.innerRadius + */ + /** + * In a gauge chart, this option determines the outer radius of the + * plot band that stretches along the perimeter. It can be given as + * a percentage string, like `"100%"`, or as a pixel number, like `100`. + * + * @sample {highcharts} highcharts/xaxis/plotbands-gauge + * Gauge plot band + * + * @type {number|string} + * @default 100% + * @since 2.3 + * @product highcharts + * @apioption yAxis.plotBands.outerRadius + */ + /** + * In a gauge chart, this option sets the width of the plot band + * stretching along the perimeter. It can be given as a percentage + * string, like `"10%"`, or as a pixel number, like `10`. The default + * value 10 is the same as the default [tickLength](#yAxis.tickLength), + * thus making the plot band act as a background for the tick markers. + * + * @sample {highcharts} highcharts/xaxis/plotbands-gauge + * Gauge plot band + * + * @type {number|string} + * @default 10 + * @since 2.3 + * @product highcharts + * @apioption yAxis.plotBands.thickness + */ + /** + * @type {Array<*>} + * @extends xAxis.plotLines + * @apioption yAxis.plotLines + */ + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * Internal function to create the SVG path definition for a plot band. + * + * @function Highcharts.Axis#getPlotBandPath + * + * @param {number} from + * The axis value to start from. + * + * @param {number} to + * The axis value to end on. + * + * @param {Highcharts.AxisPlotBandsOptions|Highcharts.AxisPlotLinesOptions} options + * The plotBand or plotLine configuration object. + * + * @return {Highcharts.SVGPathArray} + * The SVG path definition in array form. + */ + getPlotBandPath: function (from, to, options) { + if (options === void 0) { + options = this.options; + } + var toPath = this.getPlotLinePath({ + value: to, + force: true, + acrossPanes: options.acrossPanes, + }), + path = this.getPlotLinePath({ + value: from, + force: true, + acrossPanes: options.acrossPanes, + }), + result = [], + i, + // #4964 check if chart is inverted or plotband is on yAxis + horiz = this.horiz, + plus = 1, + isFlat, + 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) { + isFlat = path.toString() === toPath.toString(); + plus = 0; + } + // Go over each subpath - for panes in Highstock + for (i = 0; i < path.length; i += 2) { + var pathStart = path[i], + pathEnd = path[i + 1], + toPathStart = toPath[i], + toPathEnd = toPath[i + 1]; + // Type checking all affected path segments. Consider something + // smarter. + if ( + (pathStart[0] === "M" || pathStart[0] === "L") && + (pathEnd[0] === "M" || pathEnd[0] === "L") && + (toPathStart[0] === "M" || toPathStart[0] === "L") && + (toPathEnd[0] === "M" || toPathEnd[0] === "L") + ) { + // Add 1 pixel when coordinates are the same + if (horiz && toPathStart[1] === pathStart[1]) { + toPathStart[1] += plus; + toPathEnd[1] += plus; + } else if (!horiz && toPathStart[2] === pathStart[2]) { + toPathStart[2] += plus; + toPathEnd[2] += plus; + } + result.push( + ["M", pathStart[1], pathStart[2]], + ["L", pathEnd[1], pathEnd[2]], + ["L", toPathEnd[1], toPathEnd[2]], + ["L", toPathStart[1], toPathStart[2]], + ["Z"] + ); + } + result.isFlat = isFlat; + } + } else { + // outside the axis area + path = null; + } + return result; + }, + /** + * Add a plot band after render time. + * + * @sample highcharts/members/axis-addplotband/ + * Toggle the plot band from a button + * + * @function Highcharts.Axis#addPlotBand + * + * @param {Highcharts.AxisPlotBandsOptions} options + * A configuration object for the plot band, as defined in + * [xAxis.plotBands](https://api.highcharts.com/highcharts/xAxis.plotBands). + * + * @return {Highcharts.PlotLineOrBand|undefined} + * The added plot band. + */ + addPlotBand: function (options) { + return this.addPlotBandOrLine(options, "plotBands"); + }, + /** + * Add a plot line after render time. + * + * @sample highcharts/members/axis-addplotline/ + * Toggle the plot line from a button + * + * @function Highcharts.Axis#addPlotLine + * + * @param {Highcharts.AxisPlotLinesOptions} options + * A configuration object for the plot line, as defined in + * [xAxis.plotLines](https://api.highcharts.com/highcharts/xAxis.plotLines). + * + * @return {Highcharts.PlotLineOrBand|undefined} + * The added plot line. + */ + 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 + * @function Highcharts.Axis#addPlotBandOrLine + * + * @param {Highcharts.AxisPlotBandsOptions|Highcharts.AxisPlotLinesOptions} options + * The plotBand or plotLine configuration object. + * + * @param {"plotBands"|"plotLines"} [coll] + * + * @return {Highcharts.PlotLineOrBand|undefined} + */ + addPlotBandOrLine: function (options, coll) { + var obj = new H.PlotLineOrBand(this, options), + userOptions = this.userOptions; + if (this.visible) { + obj = obj.render(); + } + if (obj) { + // #2189 + // Add it to the user options for exporting and Axis.update + if (coll) { + // Workaround Microsoft/TypeScript issue #32693 + var updatedOptions = userOptions[coll] || []; + updatedOptions.push(options); + userOptions[coll] = updatedOptions; + } + this.plotLinesAndBands.push(obj); + this._addedPlotLB = true; + } + return obj; + }, + /** + * Remove a plot band or plot line from the chart by id. Called internally + * from `removePlotBand` and `removePlotLine`. + * + * @private + * @function Highcharts.Axis#removePlotBandOrLine + * @param {string} id + * @return {void} + */ + 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(); + } + } + [ + options.plotLines || [], + userOptions.plotLines || [], + options.plotBands || [], + userOptions.plotBands || [], + ].forEach(function (arr) { + i = arr.length; + while (i--) { + if ((arr[i] || {}).id === id) { + erase(arr, arr[i]); + } + } + }); + }, + /** + * Remove a plot band by its id. + * + * @sample highcharts/members/axis-removeplotband/ + * Remove plot band by id + * @sample highcharts/members/axis-addplotband/ + * Toggle the plot band from a button + * + * @function Highcharts.Axis#removePlotBand + * + * @param {string} id + * The plot band's `id` as given in the original configuration + * object or in the `addPlotBand` option. + * + * @return {void} + */ + removePlotBand: function (id) { + this.removePlotBandOrLine(id); + }, + /** + * Remove a plot line by its id. + * + * @sample highcharts/xaxis/plotlines-id/ + * Remove plot line by id + * @sample highcharts/members/axis-addplotline/ + * Toggle the plot line from a button + * + * @function Highcharts.Axis#removePlotLine + * + * @param {string} id + * The plot line's `id` as given in the original configuration + * object or in the `addPlotLine` option. + */ + removePlotLine: function (id) { + this.removePlotBandOrLine(id); + }, + } + ); + H.PlotLineOrBand = PlotLineOrBand; + + return H.PlotLineOrBand; + } + ); + _registerModule( + _modules, + "Core/Tooltip.js", + [_modules["Core/Globals.js"], _modules["Core/Utilities.js"]], + function (H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var doc = H.doc; + var clamp = U.clamp, + css = U.css, + defined = U.defined, + discardElement = U.discardElement, + extend = U.extend, + fireEvent = U.fireEvent, + format = U.format, + isNumber = U.isNumber, + isString = U.isString, + merge = U.merge, + pick = U.pick, + splat = U.splat, + syncTimeout = U.syncTimeout, + timeUnits = U.timeUnits; + /** + * Callback function to format the text of the tooltip from scratch. + * + * In case of single or shared tooltips, a string should be be returned. In case + * of splitted tooltips, it should return an array where the first item is the + * header, and subsequent items are mapped to the points. Return `false` to + * disable tooltip for a specific point on series. + * + * @callback Highcharts.TooltipFormatterCallbackFunction + * + * @param {Highcharts.TooltipFormatterContextObject} this + * Context to format + * + * @param {Highcharts.Tooltip} tooltip + * The tooltip instance + * + * @return {false|string|Array<(string|null|undefined)>|null|undefined} + * Formatted text or false + */ + /** + * @interface Highcharts.TooltipFormatterContextObject + */ /** + * @name Highcharts.TooltipFormatterContextObject#color + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + */ /** + * @name Highcharts.TooltipFormatterContextObject#colorIndex + * @type {number|undefined} + */ /** + * @name Highcharts.TooltipFormatterContextObject#key + * @type {number} + */ /** + * @name Highcharts.TooltipFormatterContextObject#percentage + * @type {number|undefined} + */ /** + * @name Highcharts.TooltipFormatterContextObject#point + * @type {Highcharts.Point} + */ /** + * @name Highcharts.TooltipFormatterContextObject#points + * @type {Array<Highcharts.TooltipFormatterContextObject>|undefined} + */ /** + * @name Highcharts.TooltipFormatterContextObject#series + * @type {Highcharts.Series} + */ /** + * @name Highcharts.TooltipFormatterContextObject#total + * @type {number|undefined} + */ /** + * @name Highcharts.TooltipFormatterContextObject#x + * @type {number} + */ /** + * @name Highcharts.TooltipFormatterContextObject#y + * @type {number} + */ + /** + * A callback function to place the tooltip in a specific position. + * + * @callback Highcharts.TooltipPositionerCallbackFunction + * + * @param {Highcharts.Tooltip} this + * Tooltip context of the callback. + * + * @param {number} labelWidth + * Width of the tooltip. + * + * @param {number} labelHeight + * Height of the tooltip. + * + * @param {Highcharts.TooltipPositionerPointObject} point + * Point information for positioning a tooltip. + * + * @return {Highcharts.PositionObject} + * New position for the tooltip. + */ + /** + * Point information for positioning a tooltip. + * + * @interface Highcharts.TooltipPositionerPointObject + * @extends Highcharts.Point + */ /** + * If `tooltip.split` option is enabled and positioner is called for each of the + * boxes separately, this property indicates the call on the xAxis header, which + * is not a point itself. + * @name Highcharts.TooltipPositionerPointObject#isHeader + * @type {boolean} + */ /** + * The reference point relative to the plot area. Add chart.plotLeft to get the + * full coordinates. + * @name Highcharts.TooltipPositionerPointObject#plotX + * @type {number} + */ /** + * The reference point relative to the plot area. Add chart.plotTop to get the + * full coordinates. + * @name Highcharts.TooltipPositionerPointObject#plotY + * @type {number} + */ + /** + * @typedef {"callout"|"circle"|"square"} Highcharts.TooltipShapeValue + */ + (""); // separates doclets above from variables below + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * Tooltip of a chart. + * + * @class + * @name Highcharts.Tooltip + * + * @param {Highcharts.Chart} chart + * The chart instance. + * + * @param {Highcharts.TooltipOptions} options + * Tooltip options. + */ + var Tooltip = /** @class */ (function () { + /* * + * + * Constructors + * + * */ + function Tooltip(chart, options) { + this.container = void 0; + this.crosshairs = []; + this.distance = 0; + this.isHidden = true; + this.isSticky = false; + this.now = {}; + this.options = {}; + this.outside = false; + this.chart = chart; + this.init(chart, options); + } + /* * + * + * Functions + * + * */ + /** + * In styled mode, apply the default filter for the tooltip drop-shadow. It + * needs to have an id specific to the chart, otherwise there will be issues + * when one tooltip adopts the filter of a different chart, specifically one + * where the container is hidden. + * + * @private + * @function Highcharts.Tooltip#applyFilter + */ + Tooltip.prototype.applyFilter = function () { + var chart = this.chart; + chart.renderer.definition({ + tagName: "filter", + id: "drop-shadow-" + chart.index, + opacity: 0.5, + children: [ + { + tagName: "feGaussianBlur", + in: "SourceAlpha", + stdDeviation: 1, + }, + { + tagName: "feOffset", + dx: 1, + dy: 1, + }, + { + tagName: "feComponentTransfer", + children: [ + { + tagName: "feFuncA", + type: "linear", + slope: 0.3, + }, + ], + }, + { + tagName: "feMerge", + children: [ + { + tagName: "feMergeNode", + }, + { + tagName: "feMergeNode", + in: "SourceGraphic", + }, + ], + }, + ], + }); + chart.renderer.definition({ + tagName: "style", + textContent: + ".highcharts-tooltip-" + + chart.index + + "{" + + "filter:url(#drop-shadow-" + + chart.index + + ")" + + "}", + }); + }; + /** + * 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. + * + * @private + * @function Highcharts.Tooltip#bodyFormatter + * @param {Array<(Highcharts.Point|Highcharts.Series)>} items + * @return {Array<string>} + */ + Tooltip.prototype.bodyFormatter = function (items) { + return items.map(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"] || + "" + ); + }); + }; + /** + * Destroy the single tooltips in a split tooltip. + * If the tooltip is active then it is not destroyed, unless forced to. + * + * @private + * @function Highcharts.Tooltip#cleanSplit + * + * @param {boolean} [force] + * Force destroy all tooltips. + */ + Tooltip.prototype.cleanSplit = function (force) { + this.chart.series.forEach(function (series) { + var tt = series && series.tt; + if (tt) { + if (!tt.isActive || force) { + series.tt = tt.destroy(); + } else { + tt.isActive = false; + } + } + }); + }; + /** + * 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. + * + * @function Highcharts.Tooltip#defaultFormatter + * + * @param {Highcharts.Tooltip} tooltip + * + * @return {Array<string>} + */ + Tooltip.prototype.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; + }; + /** + * Removes and destroys the tooltip and its elements. + * + * @function Highcharts.Tooltip#destroy + */ + Tooltip.prototype.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(); + } + if (this.renderer) { + this.renderer = this.renderer.destroy(); + discardElement(this.container); + } + U.clearTimeout(this.hideTimer); + U.clearTimeout(this.tooltipTimeout); + }; + /** + * Extendable method to get the anchor position of the tooltip + * from a point or set of points + * + * @private + * @function Highcharts.Tooltip#getAnchor + * + * @param {Highcharts.Point|Array<Highcharts.Point>} points + * + * @param {Highcharts.PointerEventObject} [mouseEvent] + * + * @return {Array<number>} + */ + Tooltip.prototype.getAnchor = function (points, mouseEvent) { + var ret, + chart = this.chart, + pointer = chart.pointer, + inverted = chart.inverted, + plotTop = chart.plotTop, + plotLeft = chart.plotLeft, + plotX = 0, + plotY = 0, + yAxis, + xAxis; + points = splat(points); + // When tooltip follows mouse, relate the position to the mouse + if (this.followPointer && mouseEvent) { + if (typeof mouseEvent.chartX === "undefined") { + mouseEvent = pointer.normalize(mouseEvent); + } + ret = [mouseEvent.chartX - plotLeft, mouseEvent.chartY - plotTop]; + // Some series types use a specificly calculated tooltip position for + // each point + } else if (points[0].tooltipPos) { + ret = points[0].tooltipPos; + // When shared, use the average position + } else { + points.forEach(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 ret.map(Math.round); + }; + /** + * Get the optimal date format for a point, based on a range. + * + * @private + * @function Highcharts.Tooltip#getDateFormat + * + * @param {number} range + * The time range + * + * @param {number} 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 {Highcharts.Dictionary<string>} dateTimeLabelFormats + * A map of time units to formats. + * + * @return {string} + * The optimal date format for a point. + */ + Tooltip.prototype.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, }, - /** - * Clear DOM objects and free up memory. - * - * @private - * @function Highcharts.Series#destroy - * @param {boolean} [keepEventsForUpdate] - * @return {void} - * @fires Highcharts.Series#event:destroy - */ - destroy: function (keepEventsForUpdate) { - 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 events - this.removeEvents(keepEventsForUpdate); - // erase from axes - (series.axisTypes || []).forEach(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 - U.clearTimeout(series.animationTimeout); - // Destroy all SVGElements associated to the series - objectEach(series, function (val, prop) { - // Survive provides a hook for not destroying - if (val instanceof SVGElement && !val.survive) { - // 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) { - if (!keepEventsForUpdate || prop !== 'hcEvents') { - delete series[prop]; - } - }); + lastN = "millisecond"; // for sub-millisecond data, #4223 + for (n in timeUnits) { + // eslint-disable-line guard-for-in + // 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 = time.resolveDTLFormat(dateTimeLabelFormats[n]).main; + } + return format; + }; + /** + * Creates the Tooltip label element if it does not exist, then returns it. + * + * @function Highcharts.Tooltip#getLabel + * @return {Highcharts.SVGElement} + */ + Tooltip.prototype.getLabel = function () { + var _a, _b; + var tooltip = this, + renderer = this.chart.renderer, + styledMode = this.chart.styledMode, + options = this.options, + className = + "tooltip" + + (defined(options.className) ? " " + options.className : ""), + pointerEvents = + ((_a = options.style) === null || _a === void 0 + ? void 0 + : _a.pointerEvents) || + (!this.followPointer && options.stickOnContact ? "auto" : "none"), + container, + set, + onMouseEnter = function () { + tooltip.inContact = true; }, - /** - * Get the graph path. - * - * @private - * @function Highcharts.Series#getGraphPath - * @param {Array<Highcharts.Point>} points - * @param {boolean} [nullsAsZeroes] - * @param {boolean} [connectCliffs] - * @return {Highcharts.SVGPathArray} - */ - 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) - points = this.getValidPoints(points, false, !(options.connectNulls && !nullsAsZeroes && !connectCliffs)); - // Build the line - points.forEach(function (point, i) { - var plotX = point.plotX, - plotY = point.plotY, - lastPoint = points[i - 1], - // the path to this point from the previous - pathToPoint; - 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 - ]]; - // Generate the spline as defined in the SplineSeries object - } - else if (series.getPointSpline) { - 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); - if (step === 2) { // step = center (#8073) - xMap.push(point.x); - } - } - graphPath.push.apply(graphPath, pathToPoint); - gap = false; - } + onMouseLeave = function () { + var series = tooltip.chart.hoverSeries; + tooltip.inContact = false; + if (series && series.onMouseOut) { + series.onMouseOut(); + } + }; + if (!this.label) { + if (this.outside) { + /** + * Reference to the tooltip's container, when + * [Highcharts.Tooltip#outside] is set to true, otherwise + * it's undefined. + * + * @name Highcharts.Tooltip#container + * @type {Highcharts.HTMLDOMElement|undefined} + */ + this.container = container = H.doc.createElement("div"); + container.className = "highcharts-tooltip-container"; + css(container, { + position: "absolute", + top: "1px", + pointerEvents: pointerEvents, + zIndex: 3, + }); + H.doc.body.appendChild(container); + /** + * Reference to the tooltip's renderer, when + * [Highcharts.Tooltip#outside] is set to true, otherwise + * it's undefined. + * + * @name Highcharts.Tooltip#renderer + * @type {Highcharts.SVGRenderer|undefined} + */ + this.renderer = renderer = new H.Renderer( + container, + 0, + 0, + (_b = this.chart.options.chart) === null || _b === void 0 + ? void 0 + : _b.style, + void 0, + void 0, + renderer.styledMode + ); + } + // Create the label + if (this.split) { + this.label = renderer.g(className); + } else { + this.label = renderer + .label( + "", + 0, + 0, + options.shape || "callout", + null, + null, + options.useHTML, + null, + className + ) + .attr({ + padding: options.padding, + r: options.borderRadius, }); - graphPath.xMap = xMap; - series.graphPath = graphPath; - return graphPath; + if (!styledMode) { + this.label + .attr({ + fill: options.backgroundColor, + "stroke-width": options.borderWidth, + }) + // #2301, #2657 + .css(options.style) + .css({ pointerEvents: pointerEvents }) + .shadow(options.shadow); + } + } + if (styledMode) { + // Apply the drop-shadow filter + this.applyFilter(); + this.label.addClass("highcharts-tooltip-" + this.chart.index); + } + // Split tooltip use updateTooltipContainer to position the tooltip + // container. + if (tooltip.outside && !tooltip.split) { + var label_1 = this.label; + var xSetter_1 = label_1.xSetter, + ySetter_1 = label_1.ySetter; + label_1.xSetter = function (value) { + xSetter_1.call(label_1, tooltip.distance); + container.style.left = value + "px"; + }; + label_1.ySetter = function (value) { + ySetter_1.call(label_1, tooltip.distance); + container.style.top = value + "px"; + }; + } + this.label + .on("mouseenter", onMouseEnter) + .on("mouseleave", onMouseLeave) + .attr({ zIndex: 8 }) + .add(); + } + return this.label; + }; + /** + * Place the tooltip in a chart without spilling over + * and not covering the point it self. + * + * @private + * @function Highcharts.Tooltip#getPosition + * + * @param {number} boxWidth + * + * @param {number} boxHeight + * + * @param {Highcharts.Point} point + * + * @return {Highcharts.PositionObject} + */ + Tooltip.prototype.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, + outside = this.outside, + outerWidth = outside + ? // substract distance to prevent scrollbars + doc.documentElement.clientWidth - 2 * distance + : chart.chartWidth, + outerHeight = outside + ? Math.max( + doc.body.scrollHeight, + doc.documentElement.scrollHeight, + doc.body.offsetHeight, + doc.documentElement.offsetHeight, + doc.documentElement.clientHeight + ) + : chart.chartHeight, + chartPosition = chart.pointer.getChartPosition(), + containerScaling = chart.containerScaling, + scaleX = function (val) { + return ( + // eslint-disable-line no-confusing-arrow + containerScaling ? val * containerScaling.scaleX : val + ); }, - /** - * 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. - * - * @function Highcharts.Series#drawGraph - */ - drawGraph: function () { - var series = this, - options = this.options, - graphPath = (this.gappedPath || this.getGraphPath).call(this), - styledMode = this.chart.styledMode, - props = [[ - 'graph', - 'highcharts-graph' - ]]; - // Presentational properties - if (!styledMode) { - props[0].push((options.lineColor || - this.color || - '#cccccc' // when colorByPoint = true - ), options.dashStyle); - } - props = series.getZonesGraphs(props); - // Draw the graph - props.forEach(function (prop, i) { - var graphKey = prop[0], - graph = series[graphKey], - verb = graph ? 'animate' : 'attr', - attribs; - if (graph) { - graph.endX = series.preventGraphAnimation ? - null : - graphPath.xMap; - graph.animate({ d: graphPath }); - } - else if (graphPath.length) { // #1487 - /** - * SVG element of area-based charts. Can be used for styling - * purposes. If zones are configured, this element will be - * hidden and replaced by multiple zone areas, accessible - * via `series['zone-area-x']` (where x is a number, - * starting with 0). - * - * @name Highcharts.Series#area - * @type {Highcharts.SVGElement|undefined} - */ - /** - * SVG element of line-based charts. Can be used for styling - * purposes. If zones are configured, this element will be - * hidden and replaced by multiple zone lines, accessible - * via `series['zone-graph-x']` (where x is a number, - * starting with 0). - * - * @name Highcharts.Series#graph - * @type {Highcharts.SVGElement|undefined} - */ - series[graphKey] = graph = series.chart.renderer - .path(graphPath) - .addClass(prop[1]) - .attr({ zIndex: 1 }) // #1069 - .add(series.group); - } - if (graph && !styledMode) { - attribs = { - 'stroke': prop[2], - 'stroke-width': options.lineWidth, - // Polygon series use filled graph - 'fill': (series.fillGraph && series.color) || 'none' - }; - if (prop[3]) { - attribs.dashstyle = prop[3]; - } - else if (options.linecap !== 'square') { - attribs['stroke-linecap'] = - attribs['stroke-linejoin'] = 'round'; - } - graph[verb](attribs) - // Add shadow to normal series (0) or to first - // zone (1) #3932 - .shadow((i < 2) && options.shadow); - } - // Helpers for animation - if (graph) { - graph.startX = graphPath.xMap; - graph.isArea = graphPath.isArea; // For arearange animation - } - }); + scaleY = function (val) { + return ( + // eslint-disable-line no-confusing-arrow + containerScaling ? val * containerScaling.scaleY : val + ); }, - /** - * Get zones properties for building graphs. Extendable by series with - * multiple lines within one series. - * - * @private - * @function Highcharts.Series#getZonesGraphs - * - * @param {Array<Array<string>>} props - * - * @return {Array<Array<string>>} - */ - getZonesGraphs: function (props) { - // Add the zone properties if any - this.zones.forEach(function (zone, i) { - var propset = [ - 'zone-graph-' + i, - 'highcharts-graph highcharts-zone-graph-' + i + ' ' + - (zone.className || '') - ]; - if (!this.chart.styledMode) { - propset.push((zone.color || this.color), (zone.dashStyle || this.options.dashStyle)); - } - props.push(propset); - }, this); - return props; + // Build parameter arrays for firstDimension()/secondDimension() + buildDimensionArray = function (dim) { + var isX = dim === "x"; + return [ + dim, + isX ? outerWidth : outerHeight, + isX ? boxWidth : boxHeight, + ].concat( + outside + ? [ + // If we are using tooltip.outside, we need to scale the + // position to match scaling of the container in case there + // is a transform/zoom on the container. #11329 + isX ? scaleX(boxWidth) : scaleY(boxHeight), + isX + ? chartPosition.left - + distance + + scaleX(point.plotX + chart.plotLeft) + : chartPosition.top - + distance + + scaleY(point.plotY + chart.plotTop), + 0, + isX ? outerWidth : outerHeight, + ] + : [ + // Not outside, no scaling is needed + isX ? boxWidth : boxHeight, + isX + ? point.plotX + chart.plotLeft + : point.plotY + chart.plotTop, + isX ? chart.plotLeft : chart.plotTop, + isX + ? chart.plotLeft + chart.plotWidth + : chart.plotTop + chart.plotHeight, + ] + ); }, - /** - * Clip the graphs into zones for colors and styling. - * - * @private - * @function Highcharts.Series#applyZones - * @return {void} - */ - 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, - zoneArea, - zoneGraph; - if (zones.length && - (graph || area) && - axis && - typeof 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 - // unless it is not waterfall series, then use showLine property - // to set lines between columns to be visible (#7862) - if (graph && !this.showLine) { - graph.hide(); - } - if (area) { - area.hide(); - } - // Create the clips - extremes = axis.getExtremes(); - zones.forEach(function (threshold, i) { - translatedFrom = reversed ? - (horiz ? chart.plotWidth : 0) : - (horiz ? 0 : (axis.toPixels(extremes.min) || 0)); - translatedFrom = clamp(pick(translatedTo, translatedFrom), 0, chartSizeMax); - translatedTo = clamp(Math.round(axis.toPixels(pick(threshold.value, extremes.max), true) || 0), 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); - } - // when no data, graph zone is not applied and after setData - // clip was ignored. As a result, it should be applied each - // time. - zoneArea = series['zone-area-' + i]; - zoneGraph = series['zone-graph-' + i]; - if (graph && zoneGraph) { - zoneGraph.clip(clips[i]); - } - if (area && zoneArea) { - zoneArea.clip(clips[i]); - } - // if this zone extends out of the axis, ignore the others - ignoreZones = threshold.value > extremes.max; - // Clear translatedTo for indicators - if (series.resetZones && translatedTo === 0) { - translatedTo = void 0; - } - }); - this.clips = clips; - } - else if (series.visible) { - // If zones were removed, restore graph and area - if (graph) { - graph.show(true); - } - if (area) { - area.show(true); - } - } + first = buildDimensionArray("y"), + second = buildDimensionArray("x"), + // 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. + * + * @private + */ + firstDimension = function ( + dim, + outerSize, + innerSize, + scaledInnerSize, // #11329 + point, + min, + max + ) { + var scaledDist = + dim === "y" ? scaleY(distance) : scaleX(distance), + scaleDiff = (innerSize - scaledInnerSize) / 2, + roomLeft = scaledInnerSize < point - distance, + roomRight = point + distance + scaledInnerSize < outerSize, + alignedLeft = point - scaledDist - innerSize + scaleDiff, + alignedRight = point + scaledDist - scaleDiff; + if (preferFarSide && roomRight) { + ret[dim] = alignedRight; + } else if (!preferFarSide && roomLeft) { + ret[dim] = alignedLeft; + } else if (roomLeft) { + ret[dim] = Math.min( + max - scaledInnerSize, + alignedLeft - h < 0 ? alignedLeft : alignedLeft - h + ); + } else if (roomRight) { + ret[dim] = Math.max( + min, + alignedRight + h + innerSize > outerSize + ? alignedRight + : alignedRight + h + ); + } else { + return false; + } }, - /** - * Initialize and perform group inversion on series.group and - * series.markerGroup. - * - * @private - * @function Highcharts.Series#invertGroups - * @param {boolean} [inverted] - * @return {void} + /* + * 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. + * + * @private + */ + secondDimension = function ( + dim, + outerSize, + innerSize, + scaledInnerSize, // #11329 + 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 - scaledInnerSize / 2) { + ret[dim] = outerSize - scaledInnerSize - 2; + // Align center + } else { + ret[dim] = point - innerSize / 2; + } + return retVal; + }, + /* + * Swap the dimensions */ - invertGroups: function (inverted) { - var series = this, - chart = series.chart; - /** - * @private - */ - function setInvert() { - ['group', 'markerGroup'].forEach(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; - // If inverted polar, don't invert series group - series[groupName].invert(series.isRadialSeries ? false : inverted); - } - }); - } - // Pie, go away (#1736) - if (!series.xAxis) { - return; + 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; + }; + /** + * Get the best X date format based on the closest point range on the axis. + * + * @private + * @function Highcharts.Tooltip#getXDateFormat + * + * @param {Highcharts.Point} point + * + * @param {Highcharts.TooltipOptions} options + * + * @param {Highcharts.Axis} xAxis + * + * @return {string} + */ + Tooltip.prototype.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 + }; + /** + * Hides the tooltip with a fade out animation. + * + * @function Highcharts.Tooltip#hide + * + * @param {number} [delay] + * The fade out in milliseconds. If no value is provided the value + * of the tooltip.hideDelay option is used. A value of 0 disables + * the fade out animation. + */ + Tooltip.prototype.hide = function (delay) { + var tooltip = this; + // disallow duplicate timers (#1728, #1766) + U.clearTimeout(this.hideTimer); + delay = pick(delay, this.options.hideDelay, 500); + if (!this.isHidden) { + this.hideTimer = syncTimeout(function () { + // If there is a delay, do fadeOut with the default duration. If + // the hideDelay is 0, we assume no animation is wanted, so we + // pass 0 duration. #12994. + tooltip.getLabel().fadeOut(delay ? void 0 : delay); + tooltip.isHidden = true; + }, delay); + } + }; + /** + * @private + * @function Highcharts.Tooltip#init + * + * @param {Highcharts.Chart} chart + * The chart instance. + * + * @param {Highcharts.TooltipOptions} options + * Tooltip options. + */ + Tooltip.prototype.init = function (chart, options) { + /** + * Chart of the tooltip. + * + * @readonly + * @name Highcharts.Tooltip#chart + * @type {Highcharts.Chart} + */ + this.chart = chart; + /** + * Used tooltip options. + * + * @readonly + * @name Highcharts.Tooltip#options + * @type {Highcharts.TooltipOptions} + */ + this.options = options; + /** + * List of crosshairs. + * + * @private + * @readonly + * @name Highcharts.Tooltip#crosshairs + * @type {Array<null>} + */ + this.crosshairs = []; + /** + * Current values of x and y when animating. + * + * @private + * @readonly + * @name Highcharts.Tooltip#now + * @type {Highcharts.PositionObject} + */ + this.now = { x: 0, y: 0 }; + /** + * Tooltips are initially hidden. + * + * @private + * @readonly + * @name Highcharts.Tooltip#isHidden + * @type {boolean} + */ + this.isHidden = true; + /** + * True, if the tooltip is split into one label per series, with the + * header close to the axis. + * + * @readonly + * @name Highcharts.Tooltip#split + * @type {boolean|undefined} + */ + this.split = options.split && !chart.inverted && !chart.polar; + /** + * When the tooltip is shared, the entire plot area will capture mouse + * movement or touch events. + * + * @readonly + * @name Highcharts.Tooltip#shared + * @type {boolean|undefined} + */ + this.shared = options.shared || this.split; + /** + * Whether to allow the tooltip to render outside the chart's SVG + * element box. By default (false), the tooltip is rendered within the + * chart's SVG element, which results in the tooltip being aligned + * inside the chart area. + * + * @readonly + * @name Highcharts.Tooltip#outside + * @type {boolean} + * + * @todo + * Split tooltip does not support outside in the first iteration. Should + * not be too complicated to implement. + */ + this.outside = pick( + options.outside, + Boolean(chart.scrollablePixelsX || chart.scrollablePixelsY) + ); + }; + /** + * Returns true, if the pointer is in contact with the tooltip tracker. + */ + Tooltip.prototype.isStickyOnContact = function () { + return !!( + !this.followPointer && + this.options.stickOnContact && + this.inContact + ); + }; + /** + * Moves the tooltip with a soft animation to a new position. + * + * @private + * @function Highcharts.Tooltip#move + * + * @param {number} x + * + * @param {number} y + * + * @param {number} anchorX + * + * @param {number} anchorY + */ + Tooltip.prototype.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 + ? void 0 + : animate + ? (2 * now.anchorX + anchorX) / 3 + : anchorX, + anchorY: skipAnchor + ? void 0 + : animate + ? (now.anchorY + anchorY) / 2 + : anchorY, + }); + // Move to the intermediate value + tooltip.getLabel().attr(now); + tooltip.drawTracker(); + // Run on next tick of the mouse tracker + if (animate) { + // Never allow two timeouts + U.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); + } + }; + /** + * Refresh the tooltip's text and position. + * + * @function Highcharts.Tooltip#refresh + * + * @param {Highcharts.Point|Array<Highcharts.Point>} pointOrPoints + * Either a point or an array of points. + * + * @param {Highcharts.PointerEventObject} [mouseEvent] + * Mouse event, that is responsible for the refresh and should be + * used for the tooltip update. + */ + Tooltip.prototype.refresh = function (pointOrPoints, mouseEvent) { + var tooltip = this, + chart = this.chart, + options = tooltip.options, + x, + y, + point = pointOrPoints, + anchor, + textConfig = {}, + text, + pointConfig = [], + formatter = options.formatter || tooltip.defaultFormatter, + shared = tooltip.shared, + currentSeries, + styledMode = chart.styledMode; + if (!options.enabled) { + return; + } + U.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)) { + chart.pointer.applyInactiveState(point); + // Now set hover state for the choosen ones: + point.forEach(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 { + // update text + if (tooltip.split) { + this.renderSplit(text, splat(pointOrPoints)); + } else { + var label = tooltip.getLabel(); + // Prevent the tooltip from flowing over the chart box (#6659) + if (!options.style.width || styledMode) { + label.css({ + width: this.chart.spacingBox.width + "px", + }); + } + 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) + ); + if (!styledMode) { + 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, + }); + } + // show it + if (tooltip.isHidden && tooltip.label) { + tooltip.label + .attr({ + opacity: 1, + }) + .show(); + } + tooltip.isHidden = false; + } + fireEvent(this, "refresh"); + }; + /** + * 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. + * + * @private + * @function Highcharts.Tooltip#renderSplit + * + * @param {string|Array<(boolean|string)>} labels + * + * @param {Array<Highcharts.Point>} points + */ + Tooltip.prototype.renderSplit = function (labels, points) { + var tooltip = this; + var chart = tooltip.chart, + _a = tooltip.chart, + chartWidth = _a.chartWidth, + chartHeight = _a.chartHeight, + plotHeight = _a.plotHeight, + plotLeft = _a.plotLeft, + plotTop = _a.plotTop, + pointer = _a.pointer, + ren = _a.renderer, + _b = _a.scrollablePixelsY, + scrollablePixelsY = _b === void 0 ? 0 : _b, + _c = _a.scrollingContainer, + _d = _c === void 0 ? { scrollLeft: 0, scrollTop: 0 } : _c, + scrollLeft = _d.scrollLeft, + scrollTop = _d.scrollTop, + styledMode = _a.styledMode, + distance = tooltip.distance, + options = tooltip.options, + positioner = tooltip.options.positioner; + // The area which the tooltip should be limited to. Limit to scrollable + // plot area if enabled, otherwise limit to the chart container. + var bounds = { + left: scrollLeft, + right: scrollLeft + chartWidth, + top: scrollTop, + bottom: scrollTop + chartHeight, + }; + var tooltipLabel = tooltip.getLabel(); + var headerTop = Boolean(chart.xAxis[0] && chart.xAxis[0].opposite); + var distributionBoxTop = plotTop + scrollTop; + var headerHeight = 0; + var adjustedPlotHeight = plotHeight - scrollablePixelsY; + /** + * Calculates the anchor position for the partial tooltip + * + * @private + * @param {Highcharts.Point} point The point related to the tooltip + * @return {object} Returns an object with anchorX and anchorY + */ + function getAnchor(point) { + var isHeader = point.isHeader, + _a = point.plotX, + plotX = _a === void 0 ? 0 : _a, + _b = point.plotY, + plotY = _b === void 0 ? 0 : _b, + series = point.series; + var anchorX; + var anchorY; + if (isHeader) { + // Set anchorX to plotX + anchorX = plotLeft + plotX; + // Set anchorY to center of visible plot area. + anchorY = plotTop + plotHeight / 2; + } else { + var xAxis = series.xAxis, + yAxis = series.yAxis; + // Set anchorX to plotX. Limit to within xAxis. + anchorX = + xAxis.pos + clamp(plotX, -distance, xAxis.len + distance); + // Set anchorY, limit to the scrollable plot area + if ( + yAxis.pos + plotY >= scrollTop + plotTop && + yAxis.pos + plotY <= + scrollTop + plotTop + plotHeight - scrollablePixelsY + ) { + anchorY = yAxis.pos + plotY; + } + } + // Limit values to plot area + anchorX = clamp( + anchorX, + bounds.left - distance, + bounds.right + distance + ); + return { anchorX: anchorX, anchorY: anchorY }; + } + /** + * Calculates the position of the partial tooltip + * + * @private + * @param {number} anchorX The partial tooltip anchor x position + * @param {number} anchorY The partial tooltip anchor y position + * @param {boolean} isHeader Whether the partial tooltip is a header + * @param {number} boxWidth Width of the partial tooltip + * @return {Highcharts.PositionObject} Returns the partial tooltip x and + * y position + */ + function defaultPositioner( + anchorX, + anchorY, + isHeader, + boxWidth, + alignedLeft + ) { + if (alignedLeft === void 0) { + alignedLeft = true; + } + var y; + var x; + if (isHeader) { + y = headerTop ? 0 : adjustedPlotHeight; + x = clamp( + anchorX - boxWidth / 2, + bounds.left, + bounds.right - boxWidth + ); + } else { + y = anchorY - distributionBoxTop; + x = alignedLeft + ? anchorX - boxWidth - distance + : anchorX + distance; + x = clamp(x, alignedLeft ? x : bounds.left, bounds.right); + } + // NOTE: y is relative to distributionBoxTop + return { x: x, y: y }; + } + /** + * Updates the attributes and styling of the partial tooltip. Creates a + * new partial tooltip if it does not exists. + * + * @private + * @param {Highcharts.SVGElement|undefined} partialTooltip + * The partial tooltip to update + * @param {Highcharts.Point} point + * The point related to the partial tooltip + * @param {boolean|string} str The text for the partial tooltip + * @return {Highcharts.SVGElement} Returns the updated partial tooltip + */ + function updatePartialTooltip(partialTooltip, point, str) { + var tt = partialTooltip; + var isHeader = point.isHeader, + series = point.series; + var colorClass = + "highcharts-color-" + + pick(point.colorIndex, series.colorIndex, "none"); + if (!tt) { + var attribs = { + padding: options.padding, + r: options.borderRadius, + }; + if (!styledMode) { + attribs.fill = options.backgroundColor; + attribs["stroke-width"] = options.borderWidth; + } + tt = ren + .label( + "", + 0, + 0, + options[isHeader ? "headerShape" : "shape"] || "callout", + void 0, + void 0, + options.useHTML + ) + .addClass( + (isHeader ? "highcharts-tooltip-header " : "") + + "highcharts-tooltip-box " + + colorClass + ) + .attr(attribs) + .add(tooltipLabel); + } + tt.isActive = true; + tt.attr({ + text: str, + }); + if (!styledMode) { + tt.css(options.style) + .shadow(options.shadow) + .attr({ + stroke: + options.borderColor || + point.color || + series.color || + "#333333", + }); + } + return tt; + } + // Graceful degradation for legacy formatters + if (isString(labels)) { + labels = [false, labels]; + } + // Create the individual labels for header and points, ignore footer + var boxes = labels + .slice(0, points.length + 1) + .reduce(function (boxes, str, i) { + if (str !== false && str !== "") { + 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, + plotY: plotHeight, + series: {}, + }; + var isHeader = point.isHeader; + // Store the tooltip label referance on the series + var owner = isHeader ? tooltip : point.series; + var tt = (owner.tt = updatePartialTooltip( + owner.tt, + point, + str + )); + // Get X position now, so we can move all to the other side in + // case of overflow + var bBox = tt.getBBox(); + var boxWidth = bBox.width + tt.strokeWidth(); + if (isHeader) { + headerHeight = bBox.height; + adjustedPlotHeight += headerHeight; + if (headerTop) { + distributionBoxTop -= headerHeight; + } + } + var _a = getAnchor(point), + anchorX = _a.anchorX, + anchorY = _a.anchorY; + if (typeof anchorY === "number") { + var size = bBox.height + 1; + var boxPosition = positioner + ? positioner.call(tooltip, boxWidth, size, point) + : defaultPositioner(anchorX, anchorY, isHeader, boxWidth); + boxes.push({ + // 0-align to the top, 1-align to the bottom + align: positioner ? 0 : void 0, + anchorX: anchorX, + anchorY: anchorY, + boxWidth: boxWidth, + point: point, + rank: pick(boxPosition.rank, isHeader ? 1 : 0), + size: size, + target: boxPosition.y, + tt: tt, + x: boxPosition.x, + }); + } else { + // Hide tooltips which anchorY is outside the visible plot + // area + tt.isActive = false; + } + } + return boxes; + }, []); + // If overflow left then align all labels to the right + if ( + !positioner && + boxes.some(function (box) { + return box.x < bounds.left; + }) + ) { + boxes = boxes.map(function (box) { + var _a = defaultPositioner( + box.anchorX, + box.anchorY, + box.point.isHeader, + box.boxWidth, + false + ), + x = _a.x, + y = _a.y; + return extend(box, { + target: y, + x: x, + }); + }); + } + // Clean previous run (for missing points) + tooltip.cleanSplit(); + // Distribute and put in place + H.distribute(boxes, adjustedPlotHeight); + boxes.forEach(function (box) { + var anchorX = box.anchorX, + anchorY = box.anchorY, + pos = box.pos, + x = box.x; + // Put the label in place + box.tt.attr({ + visibility: typeof pos === "undefined" ? "hidden" : "inherit", + x: x, + /* NOTE: y should equal pos to be consistent with !split + * tooltip, but is currently relative to plotTop. Is left as is + * to avoid breaking change. Remove distributionBoxTop to make + * it consistent. + */ + y: pos + distributionBoxTop, + anchorX: anchorX, + anchorY: anchorY, + }); + }); + /* If we have a seperate tooltip container, then update the necessary + * container properties. + * Test that tooltip has its own container and renderer before executing + * the operation. + */ + var container = tooltip.container, + outside = tooltip.outside, + renderer = tooltip.renderer; + if (outside && container && renderer) { + // Set container size to fit the tooltip + var _e = tooltipLabel.getBBox(), + width = _e.width, + height = _e.height, + x = _e.x, + y = _e.y; + renderer.setSize(width + x, height + y, false); + // Position the tooltip container to the chart container + var chartPosition = pointer.getChartPosition(); + container.style.left = chartPosition.left + "px"; + container.style.top = chartPosition.top + "px"; + } + }; + /** + * If the `stickOnContact` option is active, this will add a tracker shape. + * + * @private + * @function Highcharts.Tooltip#drawTracker + */ + Tooltip.prototype.drawTracker = function () { + var tooltip = this; + if (tooltip.followPointer || !tooltip.options.stickOnContact) { + if (tooltip.tracker) { + tooltip.tracker.destroy(); + } + return; + } + var chart = tooltip.chart; + var label = tooltip.label; + var point = chart.hoverPoint; + if (!label || !point) { + return; + } + var box = { + x: 0, + y: 0, + width: 0, + height: 0, + }; + // Combine anchor and tooltip + var anchorPos = this.getAnchor(point); + var labelBBox = label.getBBox(); + anchorPos[0] += chart.plotLeft - label.translateX; + anchorPos[1] += chart.plotTop - label.translateY; + // When the mouse pointer is between the anchor point and the label, + // the label should stick. + box.x = Math.min(0, anchorPos[0]); + box.y = Math.min(0, anchorPos[1]); + box.width = + anchorPos[0] < 0 + ? Math.max(Math.abs(anchorPos[0]), labelBBox.width - anchorPos[0]) + : Math.max(Math.abs(anchorPos[0]), labelBBox.width); + box.height = + anchorPos[1] < 0 + ? Math.max( + Math.abs(anchorPos[1]), + labelBBox.height - Math.abs(anchorPos[1]) + ) + : Math.max(Math.abs(anchorPos[1]), labelBBox.height); + if (tooltip.tracker) { + tooltip.tracker.attr(box); + } else { + tooltip.tracker = label.renderer + .rect(box) + .addClass("highcharts-tracker") + .add(label); + if (!chart.styledMode) { + tooltip.tracker.attr({ + fill: "rgba(0,0,0,0)", + }); + } + } + }; + /** + * @private + */ + Tooltip.prototype.styledModeFormat = function (formatString) { + return formatString + .replace('style="font-size: 10px"', 'class="highcharts-header"') + .replace( + /style="color:{(point|series)\.color}"/g, + 'class="highcharts-color-{$1.colorIndex}"' + ); + }; + /** + * Format the footer/header of the tooltip + * #3397: abstraction to enable formatting of footer and header + * + * @private + * @function Highcharts.Tooltip#tooltipFooterHeaderFormatter + * @param {Highcharts.PointLabelObject} labelConfig + * @param {boolean} [isFooter] + * @return {string} + */ + Tooltip.prototype.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"], + e = { + isFooter: isFooter, + labelConfig: labelConfig, + }; + fireEvent(this, "headerFormatter", e, function (e) { + // 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) { + ( + (labelConfig.point && labelConfig.point.tooltipDateKeys) || [ + "key", + ] + ).forEach(function (key) { + formatString = formatString.replace( + "{point." + key + "}", + "{point." + key + ":" + xDateFormat + "}" + ); + }); + } + // Replace default header style with class name + if (series.chart.styledMode) { + formatString = this.styledModeFormat(formatString); + } + e.text = format( + formatString, + { + point: labelConfig, + series: series, + }, + this.chart + ); + }); + return e.text; + }; + /** + * Updates the tooltip with the provided tooltip options. + * + * @function Highcharts.Tooltip#update + * + * @param {Highcharts.TooltipOptions} options + * The tooltip options to update. + */ + Tooltip.prototype.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)); + }; + /** + * Find the new position and perform the move + * + * @private + * @function Highcharts.Tooltip#updatePosition + * + * @param {Highcharts.Point} point + */ + Tooltip.prototype.updatePosition = function (point) { + var chart = this.chart, + pointer = chart.pointer, + label = this.getLabel(), + pos, + anchorX = point.plotX + chart.plotLeft, + anchorY = point.plotY + chart.plotTop, + pad; + // Needed for outside: true (#11688) + var chartPosition = pointer.getChartPosition(); + pos = (this.options.positioner || this.getPosition).call( + this, + label.width, + label.height, + point + ); + // Set the renderer size dynamically to prevent document size to change + if (this.outside) { + pad = (this.options.borderWidth || 0) + 2 * this.distance; + this.renderer.setSize(label.width + pad, label.height + pad, false); + // Anchor and tooltip container need scaling if chart container has + // scale transform/css zoom. #11329. + var containerScaling = chart.containerScaling; + if (containerScaling) { + css(this.container, { + transform: + "scale(" + + containerScaling.scaleX + + ", " + + containerScaling.scaleY + + ")", + }); + anchorX *= containerScaling.scaleX; + anchorY *= containerScaling.scaleY; + } + anchorX += chartPosition.left - pos.x; + anchorY += chartPosition.top - pos.y; + } + // do the move + this.move( + Math.round(pos.x), + Math.round(pos.y || 0), // can be undefined (#3977) + anchorX, + anchorY + ); + }; + return Tooltip; + })(); + H.Tooltip = Tooltip; + + return H.Tooltip; + } + ); + _registerModule( + _modules, + "Core/Pointer.js", + [ + _modules["Core/Color/Color.js"], + _modules["Core/Globals.js"], + _modules["Core/Tooltip.js"], + _modules["Core/Utilities.js"], + ], + function (Color, H, Tooltip, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var color = Color.parse; + var charts = H.charts, + noop = H.noop; + var addEvent = U.addEvent, + attr = U.attr, + css = U.css, + defined = U.defined, + extend = U.extend, + find = U.find, + fireEvent = U.fireEvent, + isNumber = U.isNumber, + isObject = U.isObject, + objectEach = U.objectEach, + offset = U.offset, + pick = U.pick, + splat = U.splat; + /** + * One position in relation to an axis. + * + * @interface Highcharts.PointerAxisCoordinateObject + */ /** + * Related axis. + * + * @name Highcharts.PointerAxisCoordinateObject#axis + * @type {Highcharts.Axis} + */ /** + * Axis value. + * + * @name Highcharts.PointerAxisCoordinateObject#value + * @type {number} + */ + /** + * Positions in terms of axis values. + * + * @interface Highcharts.PointerAxisCoordinatesObject + */ /** + * Positions on the x-axis. + * @name Highcharts.PointerAxisCoordinatesObject#xAxis + * @type {Array<Highcharts.PointerAxisCoordinateObject>} + */ /** + * Positions on the y-axis. + * @name Highcharts.PointerAxisCoordinatesObject#yAxis + * @type {Array<Highcharts.PointerAxisCoordinateObject>} + */ + /** + * Pointer coordinates. + * + * @interface Highcharts.PointerCoordinatesObject + */ /** + * @name Highcharts.PointerCoordinatesObject#chartX + * @type {number} + */ /** + * @name Highcharts.PointerCoordinatesObject#chartY + * @type {number} + */ + /** + * A native browser mouse or touch event, extended with position information + * relative to the {@link Chart.container}. + * + * @interface Highcharts.PointerEventObject + * @extends global.PointerEvent + */ /** + * The X coordinate of the pointer interaction relative to the chart. + * + * @name Highcharts.PointerEventObject#chartX + * @type {number} + */ /** + * The Y coordinate of the pointer interaction relative to the chart. + * + * @name Highcharts.PointerEventObject#chartY + * @type {number} + */ + /** + * Axis-specific data of a selection. + * + * @interface Highcharts.SelectDataObject + */ /** + * @name Highcharts.SelectDataObject#axis + * @type {Highcharts.Axis} + */ /** + * @name Highcharts.SelectDataObject#max + * @type {number} + */ /** + * @name Highcharts.SelectDataObject#min + * @type {number} + */ + /** + * Object for select events. + * + * @interface Highcharts.SelectEventObject + */ /** + * @name Highcharts.SelectEventObject#originalEvent + * @type {global.Event} + */ /** + * @name Highcharts.SelectEventObject#xAxis + * @type {Array<Highcharts.SelectDataObject>} + */ /** + * @name Highcharts.SelectEventObject#yAxis + * @type {Array<Highcharts.SelectDataObject>} + */ + (""); // detach doclets above + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * 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 + * @name Highcharts.Pointer + * + * @param {Highcharts.Chart} chart + * The chart instance. + * + * @param {Highcharts.Options} options + * The root options object. The pointer uses options from the chart and + * tooltip structures. + */ + var Pointer = /** @class */ (function () { + /* * + * + * Constructors + * + * */ + function Pointer(chart, options) { + this.lastValidTouch = {}; + this.pinchDown = []; + this.runChartClick = false; + this.chart = chart; + this.hasDragged = false; + this.options = options; + this.unbindContainerMouseLeave = function () {}; + this.unbindContainerMouseEnter = function () {}; + this.init(chart, options); + } + /* * + * + * Functions + * + * */ + /** + * Set inactive state to all series that are not currently hovered, + * or, if `inactiveOtherPoints` is set to true, set inactive state to + * all points within that series. + * + * @private + * @function Highcharts.Pointer#applyInactiveState + * @param {Array<Highcharts.Point>} points + * Currently hovered points + */ + Pointer.prototype.applyInactiveState = function (points) { + var activeSeries = [], + series; + // Get all active series from the hovered points + (points || []).forEach(function (item) { + series = item.series; + // Include itself + activeSeries.push(series); + // Include parent series + if (series.linkedParent) { + activeSeries.push(series.linkedParent); + } + // Include all child series + if (series.linkedSeries) { + activeSeries = activeSeries.concat(series.linkedSeries); + } + // Include navigator series + if (series.navigatorSeries) { + activeSeries.push(series.navigatorSeries); + } + }); + // Now loop over all series, filtering out active series + this.chart.series.forEach(function (inactiveSeries) { + if (activeSeries.indexOf(inactiveSeries) === -1) { + // Inactive series + inactiveSeries.setState("inactive", true); + } else if (inactiveSeries.options.inactiveOtherPoints) { + // Active series, but other points should be inactivated + inactiveSeries.setAllPointsToState("inactive"); + } + }); + }; + /** + * Destroys the Pointer object and disconnects DOM events. + * + * @function Highcharts.Pointer#destroy + */ + Pointer.prototype.destroy = function () { + var pointer = this; + if (typeof pointer.unDocMouseMove !== "undefined") { + 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); + objectEach(pointer, function (_val, prop) { + pointer[prop] = void 0; + }); + }; + /** + * Perform a drag operation in response to a mousemove event while the mouse + * is down. + * + * @private + * @function Highcharts.Pointer#drag + * + * @param {Highcharts.PointerEventObject} e + * + * @return {void} + */ + Pointer.prototype.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 || 0, + mouseDownY = this.mouseDownY || 0, + panningEnabled = isObject(chartOptions.panning) + ? chartOptions.panning && chartOptions.panning.enabled + : chartOptions.panning, + 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({ + class: "highcharts-selection-marker", + zIndex: 7, + }) + .add(); + if (!chart.styledMode) { + selectionMarker.attr({ + fill: + chartOptions.selectionMarkerFill || + color("#335cad").setOpacity(0.25).get(), + }); } - // A fixed size is needed for inversion to work - series.eventsToUnbind.push(addEvent(chart, 'resize', setInvert)); - // Do it now - setInvert(); - // 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 - * @function Highcharts.Series#plotGroup - * @param {string} prop - * @param {string} name - * @param {string} visibility - * @param {number} [zIndex] - * @param {Highcharts.SVGElement} [parent] - * @return {Highcharts.SVGElement} - */ - plotGroup: function (prop, name, visibility, zIndex, parent) { - var group = this[prop], - isNew = !group, - attrs = { - visibility: visibility, - zIndex: zIndex || 0.1 // IE8 and pointer logic use this - }; - // Avoid setting undefined opacity, or in styled mode - if (typeof this.opacity !== 'undefined' && - !this.chart.styledMode && this.state !== 'inactive' // #13719 + } + } + // 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 && panningEnabled) { + chart.pan(e, chartOptions.panning); + } + } + }; + /** + * Start a drag operation. + * + * @private + * @function Highcharts.Pointer#dragStart + * + * @param {Highcharts.PointerEventObject} e + * + * @return {void} + */ + Pointer.prototype.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; + }; + /** + * On mouse up or touch end across the entire document, drop the selection. + * + * @private + * @function Highcharts.Pointer#drop + * + * @param {global.Event} e + */ + Pointer.prototype.drop = function (e) { + var pointer = this, + chart = this.chart, + hasPinched = this.hasPinched; + if (this.selectionMarker) { + var selectionData = { + originalEvent: e, + 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 + chart.axes.forEach(function (axis) { + if ( + axis.zoomEnabled && + defined(axis.min) && + (hasPinched || + pointer[ + { + xAxis: "zoomX", + yAxis: "zoomY", + }[axis.coll] + ]) && + isNumber(selectionLeft) && + isNumber(selectionTop) ) { - attrs.opacity = this.opacity; - } - // Generate it on first call - if (isNew) { - this[prop] = group = this.chart.renderer - .g() - .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(attrs)[isNew ? 'attr' : 'animate'](this.getPlotBox()); - return group; - }, - /** - * Get the translation and scale for the plot area of this series. - * - * @function Highcharts.Series#getPlotBox - * - * @return {Highcharts.SeriesPlotBoxObject} - */ - 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, - scaleY: 1 + // #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/max for reversed axes + min: Math.min(selectionMin, selectionMax), + max: Math.max(selectionMin, selectionMax), + }); + runZoom = true; + } + }); + if (runZoom) { + fireEvent(chart, "selection", selectionData, function (args) { + chart.zoom( + extend(args, hasPinched ? { animation: false } : null) + ); + }); + } + } + if (isNumber(chart.index)) { + this.selectionMarker = this.selectionMarker.destroy(); + } + // Reset scaling preview + if (hasPinched) { + this.scaleGroups(); + } + } + // Reset all. Check isNumber because it may be destroyed on mouse up + // (#877) + if (chart && isNumber(chart.index)) { + css(chart.container, { cursor: chart._cursor }); + chart.cancelClick = this.hasDragged > 10; // #370 + chart.mouseIsDown = this.hasDragged = this.hasPinched = false; + this.pinchDown = []; + } + }; + /** + * Finds the closest point to a set of coordinates, using the k-d-tree + * algorithm. + * + * @function Highcharts.Pointer#findNearestKDPoint + * + * @param {Array<Highcharts.Series>} series + * All the series to search in. + * + * @param {boolean|undefined} shared + * Whether it is a shared tooltip or not. + * + * @param {Highcharts.PointerEventObject} e + * The pointer event object, containing chart coordinates of the + * pointer. + * + * @return {Highcharts.Point|undefined} + * The point closest to given coordinates. + */ + Pointer.prototype.findNearestKDPoint = function (series, shared, e) { + var chart = this.chart; + var hoverPoint = chart.hoverPoint; + var tooltip = chart.tooltip; + if (hoverPoint && tooltip && tooltip.isStickyOnContact()) { + return hoverPoint; + } + var closest; + /** @private */ + function sort(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; + } + series.forEach(function (s) { + var noSharedTooltip = s.noSharedTooltip && shared, + compareX = + !noSharedTooltip && + s.options.findNearestPointBy.indexOf("y") < 0, + point = s.searchPoint(e, 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; + }; + /** + * @private + * @function Highcharts.Pointer#getChartCoordinatesFromPoint + * @param {Highcharts.Point} point + * @param {boolean} [inverted] + * @return {Highcharts.PointerCoordinatesObject|undefined} + */ + Pointer.prototype.getChartCoordinatesFromPoint = function ( + point, + inverted + ) { + var series = point.series, + xAxis = series.xAxis, + yAxis = series.yAxis, + plotX = pick(point.clientX, point.plotX), + shapeArgs = point.shapeArgs; + 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, }; + } + if (shapeArgs && shapeArgs.x && shapeArgs.y) { + // E.g. pies do not have axes + return { + chartX: shapeArgs.x, + chartY: shapeArgs.y, + }; + } + }; + /** + * Return the cached chartPosition if it is available on the Pointer, + * otherwise find it. Running offset is quite expensive, so it should be + * avoided when we know the chart hasn't moved. + * + * @function Highcharts.Pointer#getChartPosition + * + * @return {Highcharts.OffsetObject} + * The offset of the chart container within the page + */ + Pointer.prototype.getChartPosition = function () { + return ( + this.chartPosition || + (this.chartPosition = offset(this.chart.container)) + ); + }; + /** + * Get the click position in terms of axis values. + * + * @function Highcharts.Pointer#getCoordinates + * + * @param {Highcharts.PointerEventObject} e + * Pointer event, extended with `chartX` and `chartY` properties. + * + * @return {Highcharts.PointerAxisCoordinatesObject} + */ + Pointer.prototype.getCoordinates = function (e) { + var coordinates = { + xAxis: [], + yAxis: [], + }; + this.chart.axes.forEach(function (axis) { + coordinates[axis.isXAxis ? "xAxis" : "yAxis"].push({ + axis: axis, + value: axis.toValue(e[axis.horiz ? "chartX" : "chartY"]), + }); + }); + return coordinates; + }; + /** + * Calculates what is the current hovered point/points and series. + * + * @private + * @function Highcharts.Pointer#getHoverData + * + * @param {Highcharts.Point|undefined} existingHoverPoint + * The point currrently beeing hovered. + * + * @param {Highcharts.Series|undefined} existingHoverSeries + * The series currently beeing hovered. + * + * @param {Array<Highcharts.Series>} series + * All the series in the chart. + * + * @param {boolean} isDirectTouch + * Is the pointer directly hovering the point. + * + * @param {boolean|undefined} shared + * Whether it is a shared tooltip or not. + * + * @param {Highcharts.PointerEventObject} [e] + * The triggering event, containing chart coordinates of the pointer. + * + * @return {object} + * Object containing resulting hover data: hoverPoint, hoverSeries, + * and hoverPoints. + */ + Pointer.prototype.getHoverData = function ( + existingHoverPoint, + existingHoverSeries, + series, + isDirectTouch, + shared, + e + ) { + var hoverPoint, + hoverPoints = [], + hoverSeries = existingHoverSeries, + useExisting = !!(isDirectTouch && existingHoverPoint), + notSticky = hoverSeries && !hoverSeries.stickyTracking, + // Which series to look in for the hover point + searchSeries, + // Parameters needed for beforeGetHoverData event. + eventArgs = { + chartX: e ? e.chartX : void 0, + chartY: e ? e.chartY : void 0, + shared: shared, }, - /** - * Removes the event handlers attached previously with addEvents. - * - * @private - * @function Highcharts.Series#removeEvents - * @param {boolean} [keepEventsForUpdate] - * @return {void} - */ - removeEvents: function (keepEventsForUpdate) { - var series = this; - if (!keepEventsForUpdate) { - // remove all events - removeEvent(series); - } - else if (series.eventsToUnbind.length) { - // remove only internal events for proper update - // #12355 - solves problem with multiple destroy events - series.eventsToUnbind.forEach(function (unbind) { - unbind(); - }); - series.eventsToUnbind.length = 0; - } - }, - /** - * 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. - * - * @function Highcharts.Series#render - * - * @return {void} - * - * @fires Highcharts.Series#event:afterRender - */ - render: function () { - var series = this, - chart = series.chart, - group, - options = series.options, - animOptions = animObject(options.animation), - // Animation doesn't work in IE8 quirks when the group div is - // hidden, and looks bad in other oldIE - animDuration = (!series.finishedAnimating && - chart.renderer.isSVG && - animOptions.duration), - visibility = series.visible ? 'inherit' : 'hidden', // #2597 - zIndex = options.zIndex, - hasRendered = series.hasRendered, - chartSeriesGroup = chart.seriesGroup, - inverted = chart.inverted; - fireEvent(this, 'render'); - // 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) { - series.animate(true); - } - // SVGRenderer needs to know this before drawing elements (#1089, - // #1795) - group.inverted = series.isCartesian || series.invertable ? - inverted : false; - // Draw the graph if any - if (series.drawGraph) { - series.drawGraph(); - series.applyZones(); - } - // Draw the points - if (series.visible) { - series.drawPoints(); - } - /* series.points.forEach(function (point) { - if (point.redraw) { - point.redraw(); - } - }); */ - // Draw the data labels - if (series.drawDataLabels) { - series.drawDataLabels(); - } - // In pie charts, slices are added to the DOM, but actual rendering - // is postponed until labels reserved their space - if (series.redrawPoints) { - series.redrawPoints(); - } - // 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) { - 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) { - // Additional time if defer is defined before afterAnimate - // will be triggered - if (animDuration && animOptions.defer) { - animDuration += animOptions.defer; - } - series.animationTimeout = syncTimeout(function () { - series.afterAnimate(); - }, animDuration || 0); - } - // Means data is in accordance with what you see - series.isDirty = false; - // (See #322) series.isDirty = series.isDirtyData = false; // means - // data is in accordance with what you see - series.hasRendered = true; - fireEvent(series, 'afterRender'); - }, - /** - * Redraw the series. This function is called internally from - * `chart.redraw` and normally shouldn't be called directly. - * - * @private - * @function Highcharts.Series#redraw - * @return {void} - */ - 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'], - /** - * @private - * @function Highcharts.Series#searchPoint - * @param {Highcharts.PointerEventObject} e - * @param {boolean} [compareX] - * @return {Highcharts.Point} - */ - 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, e); - }, - /** - * 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 - * @function Highcharts.Series#buildKDTree - * @param {Highcharts.PointerEventObject} [e] - * @return {void} - */ - buildKDTree: function (e) { - // 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 - * @private - */ - 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) - * @private - */ - 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. Also if touchstart, we - // may be dealing with click events on mobile, so don't delay - // (#6817). - syncTimeout(startRecursive, series.options.kdNow || (e && e.type === 'touchstart') ? 0 : 1); - }, - /** - * @private - * @function Highcharts.Series#searchKDTree - * @param {Highcharts.KDPointSearchObject} point - * @param {boolean} [compareX] - * @param {Highcharts.PointerEventObject} [e] - * @return {Highcharts.Point|undefined} - */ - searchKDTree: function (point, compareX, e) { - 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. - * @private - */ - 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; - } - /** - * @private - */ - 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(e); - } - if (this.kdTree) { - return _search(point, this.kdTree, kdDimensions, kdDimensions); - } - }, - /** - * @private - * @function Highcharts.Series#pointPlacementToXValue - * @return {number} - */ - pointPlacementToXValue: function () { - var _a = this, - _b = _a.options, - pointPlacement = _b.pointPlacement, - pointRange = _b.pointRange, - axis = _a.xAxis; - var factor = pointPlacement; - // Point placement is relative to each series pointRange (#5889) - if (factor === 'between') { - factor = axis.reversed ? -0.5 : 0.5; // #11955 - } - return isNumber(factor) ? - factor * pick(pointRange, axis.pointRange) : - 0; - }, - /** - * @private - * @function Highcharts.Series#isPointInside - * @param {Highcharts.Point} point - * @return {boolean} - */ - isPointInside: function (point) { - var isInside = typeof point.plotY !== 'undefined' && - typeof point.plotX !== 'undefined' && - point.plotY >= 0 && - point.plotY <= this.yAxis.len && // #3519 - point.plotX >= 0 && - point.plotX <= this.xAxis.len; - return isInside; - } - }); // 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 + filter = function (s) { + return ( + s.visible && + !(!shared && s.directTouch) && // #3821 + pick(s.options.enableMouseTracking, true) + ); + }; + // Find chart.hoverPane and update filter method in polar. + fireEvent(this, "beforeGetHoverData", eventArgs); + searchSeries = notSticky + ? // Only search on hovered series if it has stickyTracking false + [hoverSeries] + : // Filter what series to look in. + series.filter(function (s) { + return eventArgs.filter + ? eventArgs.filter(s) + : filter(s) && s.stickyTracking; + }); + // Use existing hovered point or find the one closest to coordinates. + hoverPoint = + useExisting || !e + ? existingHoverPoint + : this.findNearestKDPoint(searchSeries, shared, e); + // 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 = series.filter(function (s) { + return eventArgs.filter + ? eventArgs.filter(s) + : filter(s) && !s.noSharedTooltip; + }); + // Get all points with the same x value as the hoverPoint + searchSeries.forEach(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 (s.chart.isBoosting) { + point = s.getPoint(point); + } + hoverPoints.push(point); + } + }); + } else { + hoverPoints.push(hoverPoint); + } + } + // Check whether the hoverPoint is inside pane we are hovering over. + eventArgs = { hoverPoint: hoverPoint }; + fireEvent(this, "afterGetHoverData", eventArgs); + return { + hoverPoint: eventArgs.hoverPoint, + hoverSeries: hoverSeries, + hoverPoints: hoverPoints, + }; + }; + /** + * @private + * @function Highcharts.Pointer#getPointFromEvent + * + * @param {global.Event} e + * + * @return {Highcharts.Point|undefined} + */ + Pointer.prototype.getPointFromEvent = function (e) { + var target = e.target, + point; + while (target && !point) { + point = target.point; + target = target.parentNode; + } + return point; + }; + /** + * @private + * @function Highcharts.Pointer#onTrackerMouseOut + * + * @param {Highcharts.PointerEventObject} e + * + * @return {void} + */ + Pointer.prototype.onTrackerMouseOut = function (e) { + var chart = this.chart; + var relatedTarget = e.relatedTarget || e.toElement; + var series = chart.hoverSeries; + this.isDirectTouch = false; + if ( + series && + relatedTarget && + !series.stickyTracking && + !this.inClass(relatedTarget, "highcharts-tooltip") && + (!this.inClass( + relatedTarget, + "highcharts-series-" + series.index + ) || // #2499, #4465, #5553 + !this.inClass(relatedTarget, "highcharts-tracker")) + ) { + series.onMouseOut(); + } + }; + /** + * Utility to detect whether an element has, or has a parent with, a + * specificclass name. Used on detection of tracker objects and on deciding + * whether hovering the tooltip should cause the active series to mouse out. + * + * @function Highcharts.Pointer#inClass + * + * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element + * The element to investigate. + * + * @param {string} className + * The class name to look for. + * + * @return {boolean|undefined} + * True if either the element or one of its parents has the given + * class name. + */ + Pointer.prototype.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; + } + }; + /** + * Initialize the Pointer. + * + * @private + * @function Highcharts.Pointer#init + * + * @param {Highcharts.Chart} chart + * The Chart instance. + * + * @param {Highcharts.Options} options + * The root options object. The pointer uses options from the chart + * and tooltip structures. + * + * @return {void} + */ + Pointer.prototype.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) { + /** + * Tooltip object for points of series. + * + * @name Highcharts.Chart#tooltip + * @type {Highcharts.Tooltip} + */ + chart.tooltip = new Tooltip(chart, options.tooltip); + this.followTouchMove = pick(options.tooltip.followTouchMove, true); + } + this.setDOMEvents(); + }; + /** + * Takes a browser event object and extends it with custom Highcharts + * properties `chartX` and `chartY` in order to work on the internal + * coordinate system. + * + * @function Highcharts.Pointer#normalize + * + * @param {global.MouseEvent|global.PointerEvent|global.TouchEvent} e + * Event object in standard browsers. + * + * @param {Highcharts.OffsetObject} [chartPosition] + * Additional chart offset. + * + * @return {Highcharts.PointerEventObject} + * A browser event with extended properties `chartX` and `chartY`. + */ + Pointer.prototype.normalize = function (e, chartPosition) { + var touches = e.touches; + // iOS (#2757) + var ePos = touches + ? touches.length + ? touches.item(0) + : pick( + // #13534 + touches.changedTouches, + e.changedTouches + )[0] + : e; + // Get mouse position + if (!chartPosition) { + chartPosition = this.getChartPosition(); + } + var chartX = ePos.pageX - chartPosition.left, + chartY = ePos.pageY - chartPosition.top; + // #11329 - when there is scaling on a parent element, we need to take + // this into account + var containerScaling = this.chart.containerScaling; + if (containerScaling) { + chartX /= containerScaling.scaleX; + chartY /= containerScaling.scaleY; + } + return extend(e, { + chartX: Math.round(chartX), + chartY: Math.round(chartY), + }); + }; + /** + * @private + * @function Highcharts.Pointer#onContainerClick + */ + Pointer.prototype.onContainerClick = function (e) { + var chart = this.chart; + var hoverPoint = chart.hoverPoint; + var pEvt = this.normalize(e); + var plotLeft = chart.plotLeft; + var plotTop = chart.plotTop; + if (!chart.cancelClick) { + // On tracker click, fire the series and point events. #783, #1583 + if (hoverPoint && this.inClass(pEvt.target, "highcharts-tracker")) { + // the series click event + fireEvent( + hoverPoint.series, + "click", + extend(pEvt, { + point: hoverPoint, + }) + ); + // the point click event + if (chart.hoverPoint) { + // it may be destroyed (#1844) + hoverPoint.firePointEvent("click", pEvt); + } + // When clicking outside a tracker, fire a chart event + } else { + extend(pEvt, this.getCoordinates(pEvt)); + // fire a click event in the chart + if ( + chart.isInsidePlot( + pEvt.chartX - plotLeft, + pEvt.chartY - plotTop + ) + ) { + fireEvent(chart, "click", pEvt); + } + } + } + }; + /** + * @private + * @function Highcharts.Pointer#onContainerMouseDown + * + * @param {global.MouseEvent} e + */ + Pointer.prototype.onContainerMouseDown = function (e) { + var isPrimaryButton = ((e.buttons || e.button) & 1) === 1; + // Normalize before the 'if' for the legacy IE (#7850) + e = this.normalize(e); + // #11635, Firefox does not reliable fire move event after click scroll + if (H.isFirefox && e.button !== 0) { + this.onContainerMouseMove(e); + } + // #11635, limiting to primary button (incl. IE 8 support) + if (typeof e.button === "undefined" || isPrimaryButton) { + this.zoomOption(e); + // #295, #13737 solve conflict between container drag and chart zoom + if (isPrimaryButton && e.preventDefault) { + e.preventDefault(); + } + this.dragStart(e); + } + }; + /** + * When mouse leaves the container, hide the tooltip. + * + * @private + * @function Highcharts.Pointer#onContainerMouseLeave + * + * @param {global.MouseEvent} e + * + * @return {void} + */ + Pointer.prototype.onContainerMouseLeave = function (e) { + var chart = charts[pick(H.hoverChartIndex, -1)]; + var tooltip = this.chart.tooltip; + e = this.normalize(e); + // #4886, MS Touch end fires mouseleave but with no related target + if (chart && (e.relatedTarget || e.toElement)) { + chart.pointer.reset(); + // Also reset the chart position, used in #149 fix + chart.pointer.chartPosition = void 0; + } + if ( + // #11635, Firefox wheel scroll does not fire out events consistently + tooltip && + !tooltip.isHidden + ) { + this.reset(); + } + }; + /** + * When mouse enters the container, delete pointer's chartPosition. + * + * @private + * @function Highcharts.Pointer#onContainerMouseEnter + * + * @param {global.MouseEvent} e + * + * @return {void} + */ + Pointer.prototype.onContainerMouseEnter = function (e) { + delete this.chartPosition; + }; + /** + * The mousemove, touchmove and touchstart event handler + * + * @private + * @function Highcharts.Pointer#onContainerMouseMove + * + * @param {global.MouseEvent} e + * + * @return {void} + */ + Pointer.prototype.onContainerMouseMove = function (e) { + var chart = this.chart; + var pEvt = this.normalize(e); + this.setHoverChartIndex(); + // In IE8 we apparently need this returnValue set to false in order to + // avoid text being selected. But in Chrome, e.returnValue is prevented, + // plus we don't need to run e.preventDefault to prevent selected text + // in modern browsers. So we set it conditionally. Remove it when IE8 is + // no longer needed. #2251, #3224. + if (!pEvt.preventDefault) { + pEvt.returnValue = false; + } + if (chart.mouseIsDown === "mousedown") { + this.drag(pEvt); + } + // Show the tooltip and run mouse over events (#977) + if ( + !chart.openMenu && + (this.inClass(pEvt.target, "highcharts-tracker") || + chart.isInsidePlot( + pEvt.chartX - chart.plotLeft, + pEvt.chartY - chart.plotTop + )) + ) { + this.runPointActions(pEvt); + } + }; + /** + * @private + * @function Highcharts.Pointer#onDocumentTouchEnd + * + * @param {Highcharts.PointerEventObject} e + * + * @return {void} + */ + Pointer.prototype.onDocumentTouchEnd = function (e) { + if (charts[H.hoverChartIndex]) { + charts[H.hoverChartIndex].pointer.drop(e); + } + }; + /** + * @private + * @function Highcharts.Pointer#onContainerTouchMove + * + * @param {Highcharts.PointerEventObject} e + * + * @return {void} */ + Pointer.prototype.onContainerTouchMove = function (e) { + this.touch(e); + }; + /** + * @private + * @function Highcharts.Pointer#onContainerTouchStart + * + * @param {Highcharts.PointerEventObject} e + * + * @return {void} + */ + Pointer.prototype.onContainerTouchStart = function (e) { + this.zoomOption(e); + this.touch(e, true); + }; + /** + * 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 + * @function Highcharts.Pointer#onDocumentMouseMove + * + * @param {global.MouseEvent} e + * + * @return {void} + */ + Pointer.prototype.onDocumentMouseMove = function (e) { + var chart = this.chart; + var chartPosition = this.chartPosition; + var pEvt = this.normalize(e, chartPosition); + var tooltip = chart.tooltip; + // If we're outside, hide the tooltip + if ( + chartPosition && + (!tooltip || !tooltip.isStickyOnContact()) && + !chart.isInsidePlot( + pEvt.chartX - chart.plotLeft, + pEvt.chartY - chart.plotTop + ) && + !this.inClass(pEvt.target, "highcharts-tracker") + ) { + this.reset(); + } + }; /** - * 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. + * @private + * @function Highcharts.Pointer#onDocumentMouseUp + * + * @param {global.MouseEvent} e * - * @type {Highcharts.SeriesLinecapValue} - * @default round - * @since 3.0.7 - * @apioption plotOptions.line.linecap + * @return {void} */ + Pointer.prototype.onDocumentMouseUp = function (e) { + var chart = charts[pick(H.hoverChartIndex, -1)]; + if (chart) { + chart.pointer.drop(e); + } + }; /** - * A `line` series. If the [type](#series.line.type) option is not - * specified, it is inherited from [chart.type](#chart.type). + * Handle touch events with two touches * - * @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 following snippet shows only a - * few settings, see the complete options set 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" - * }] - * ``` - * - * **Note:** In TypeScript you have to extend `PointOptionsObject` with an - * additional declaration to allow custom data types: - * ```ts - * declare module `highcharts` { - * interface PointOptionsObject { - * custom: Record<string, (boolean|number|string)>; - * } - * } - * ``` + * @private + * @function Highcharts.Pointer#pinch * - * @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 + * @param {Highcharts.PointerEventObject} e * - * @declare Highcharts.PointOptionsObject - * @type {Array<number|Array<(number|string),(number|null)>|null|*>} - * @apioption series.line.data + * @return {void} */ + Pointer.prototype.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.cancelable !== false + ) { + e.preventDefault(); + } + // Normalize each touch + [].map.call(touches, function (e) { + return self.normalize(e); + }); + // Register the touch start position + if (e.type === "touchstart") { + [].forEach.call(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 + chart.axes.forEach(function (axis) { + if (axis.zoomEnabled) { + var bounds = chart.bounds[axis.horiz ? "h" : "v"], + minPixelPadding = axis.minPixelPadding, + min = axis.toPixels( + Math.min(pick(axis.options.min, axis.dataMin), axis.dataMin) + ), + max = axis.toPixels( + Math.max(pick(axis.options.max, axis.dataMax), 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); + } + } + }; /** - * An additional, individual class name for the data point's graphic - * representation. + * Run translation operations * - * @type {string} - * @since 5.0.0 - * @product highcharts gantt - * @apioption series.line.data.className - */ - /** - * Individual color for the point. By default the color is pulled from - * the global `colors` array. + * @private + * @function Highcharts.Pointer#pinchTranslate + * + * @param {Array<*>} pinchDown * - * In styled mode, the `color` option doesn't take effect. Instead, use - * `colorIndex`. + * @param {Array<Highcharts.PointerEventObject>} touches * - * @sample {highcharts} highcharts/point/color/ - * Mark the highest point + * @param {*} transform * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @product highcharts highstock gantt - * @apioption series.line.data.color + * @param {*} selectionMarker + * + * @param {*} clip + * + * @param {*} lastValidTouch + * + * @return {void} */ + Pointer.prototype.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 + ); + } + }; /** - * A specific color index to use for the point, so its graphic representations - * are given the class name `highcharts-color-{n}`. In styled mode this will - * change the color of the graphic. In non-styled mode, the color by is set by - * the `fill` attribute, so the change in class name won't have a visual effect - * by default. + * Run translation operations for each direction (horizontal and vertical) + * independently. * - * @type {number} - * @since 5.0.0 - * @product highcharts gantt - * @apioption series.line.data.colorIndex + * @private + * @function Highcharts.Pointer#pinchTranslateDirection + * + * @param {boolean} horiz + * + * @param {Array<*>} pinchDown + * + * @param {Array<Highcharts.PointerEventObject>} touches + * + * @param {*} transform + * + * @param {*} selectionMarker + * + * @param {*} clip + * + * @param {*} lastValidTouch + * + * @param {number|undefined} [forcedScale=1] + * + * @return {void} */ + Pointer.prototype.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 ( + typeof touch1Now === "number" && + 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 (typeof touch1Now === "number") { + 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); + }; /** - * A reserved subspace to store options and values for customized functionality. - * Here you can add additional data for your own event callbacks and formatter - * callbacks. + * Reset the tracking by hiding the tooltip, the hover series state and the + * hover point + * + * @function Highcharts.Pointer#reset + * + * @param {boolean} [allowMove] + * Instead of destroying the tooltip altogether, allow moving it if + * possible. * - * @sample {highcharts} highcharts/point/custom/ - * Point and series with custom data + * @param {number} [delay] * - * @type {Highcharts.Dictionary<*>} - * @apioption series.line.data.custom + * @return {void} */ + Pointer.prototype.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) { + splat(tooltipPoints).forEach(function (point) { + if ( + point.series.isCartesian && + typeof point.plotX === "undefined" + ) { + allowMove = false; + } + }); + } + // Just move the tooltip, #349 + if (allowMove) { + if (tooltip && tooltipPoints && splat(tooltipPoints).length) { + tooltip.refresh(tooltipPoints); + if (tooltip.shared && hoverPoints) { + // #8284 + hoverPoints.forEach(function (point) { + point.setState(point.state, true); + if (point.series.isCartesian) { + if (point.series.xAxis.crosshair) { + point.series.xAxis.drawCrosshair(null, point); + } + if (point.series.yAxis.crosshair) { + point.series.yAxis.drawCrosshair(null, point); + } + } + }); + } else if (hoverPoint) { + // #2500 + hoverPoint.setState(hoverPoint.state, true); + chart.axes.forEach(function (axis) { + if (axis.crosshair && hoverPoint.series[axis.coll] === axis) { + axis.drawCrosshair(null, hoverPoint); + } + }); + } + } + // Full reset + } else { + if (hoverPoint) { + hoverPoint.onMouseOut(); + } + if (hoverPoints) { + hoverPoints.forEach(function (point) { + point.setState(); + }); + } + if (hoverSeries) { + hoverSeries.onMouseOut(); + } + if (tooltip) { + tooltip.hide(delay); + } + if (pointer.unDocMouseMove) { + pointer.unDocMouseMove = pointer.unDocMouseMove(); + } + // Remove crosshairs + chart.axes.forEach(function (axis) { + axis.hideCrosshair(); + }); + pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null; + } + }; /** - * Individual data label for each point. The options are the same as - * the ones for [plotOptions.series.dataLabels]( - * #plotOptions.series.dataLabels). + * 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 + * @function Highcharts.Pointer#runPointActions * - * @sample highcharts/point/datalabels/ - * Show a label for the last value + * @param {global.Event} e * - * @declare Highcharts.DataLabelsOptions - * @extends plotOptions.line.dataLabels - * @product highcharts highstock gantt - * @apioption series.line.data.dataLabels - */ + * @param {Highcharts.PointerEventObject} [p] + * + * @return {void} + * + * @fires Highcharts.Point#event:mouseOut + * @fires Highcharts.Point#event:mouseOver + */ + Pointer.prototype.runPointActions = function (e, p) { + var pointer = this, + chart = pointer.chart, + series = chart.series, + tooltip = + chart.tooltip && chart.tooltip.options.enabled + ? chart.tooltip + : void 0, + shared = tooltip ? tooltip.shared : false, + hoverPoint = p || chart.hoverPoint, + hoverSeries = + (hoverPoint && hoverPoint.series) || chart.hoverSeries, + // onMouseOver or already hovering a series with directTouch + isDirectTouch = + (!e || e.type !== "touchmove") && + (!!p || + (hoverSeries && + hoverSeries.directTouch && + pointer.isDirectTouch)), + hoverData = this.getHoverData( + hoverPoint, + hoverSeries, + series, + isDirectTouch, + shared, + e + ), + 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)) + ) { + (chart.hoverPoints || []).forEach(function (p) { + if (points.indexOf(p) === -1) { + p.setState(); + } + }); + // Set normal state to previous series + if (chart.hoverSeries !== hoverSeries) { + hoverSeries.onMouseOver(); + } + pointer.applyInactiveState(points); + // Do mouseover on all points (#3919, #3985, #4410, #5622) + (points || []).forEach(function (p) { + p.setState("hover"); + }); + // 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; + } + /** + * Contains all hovered points. + * + * @name Highcharts.Chart#hoverPoints + * @type {Array<Highcharts.Point>|null} + */ + chart.hoverPoints = points; + /** + * Contains the original hovered point. + * + * @name Highcharts.Chart#hoverPoint + * @type {Highcharts.Point|null} + */ + chart.hoverPoint = hoverPoint; + /** + * Hover state should not be lost when axis is updated (#12569) + * Axis.update runs pointer.reset which uses chart.hoverPoint.state + * to apply state which does not exist in hoverPoint yet. + * The mouseOver event should be triggered when hoverPoint + * is correct. + */ + hoverPoint.firePointEvent("mouseOver"); + // 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 + chart.axes.forEach(function drawAxisCrosshair(axis) { + var snap = pick((axis.crosshair || {}).snap, true); + var point; + if (snap) { + point = chart.hoverPoint; // #13002 + if (!point || point.series[axis.coll] !== axis) { + point = 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(); + } + }); + }; /** - * A description of the point to add to the screen reader information - * about the point. + * Scale series groups to a certain scale and translation. * - * @type {string} - * @since 5.0.0 - * @requires modules/accessibility - * @apioption series.line.data.description + * @private + * @function Highcharts.Pointer#scaleGroups + * + * @param {Highcharts.SeriesPlotBoxObject} [attribs] + * + * @param {boolean} [clip] + * + * @return {void} */ + Pointer.prototype.scaleGroups = function (attribs, clip) { + var chart = this.chart, + seriesAttribs; + // Scale each series + chart.series.forEach(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); + }; /** - * An id for the point. This can be used after render time to get a - * pointer to the point object through `chart.get()`. + * 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. * - * @sample {highcharts} highcharts/point/id/ - * Remove an id'd point + * @private + * @function Highcharts.Pointer#setDOMEvents * - * @type {string} - * @since 1.2.0 - * @product highcharts highstock gantt - * @apioption series.line.data.id + * @return {void} */ + Pointer.prototype.setDOMEvents = function () { + var container = this.chart.container, + ownerDoc = container.ownerDocument; + container.onmousedown = this.onContainerMouseDown.bind(this); + container.onmousemove = this.onContainerMouseMove.bind(this); + container.onclick = this.onContainerClick.bind(this); + this.unbindContainerMouseEnter = addEvent( + container, + "mouseenter", + this.onContainerMouseEnter.bind(this) + ); + this.unbindContainerMouseLeave = addEvent( + container, + "mouseleave", + this.onContainerMouseLeave.bind(this) + ); + if (!H.unbindDocumentMouseUp) { + H.unbindDocumentMouseUp = addEvent( + ownerDoc, + "mouseup", + this.onDocumentMouseUp.bind(this) + ); + } + if (H.hasTouch) { + addEvent( + container, + "touchstart", + this.onContainerTouchStart.bind(this) + ); + addEvent( + container, + "touchmove", + this.onContainerTouchMove.bind(this) + ); + if (!H.unbindDocumentTouchEnd) { + H.unbindDocumentTouchEnd = addEvent( + ownerDoc, + "touchend", + this.onDocumentTouchEnd.bind(this) + ); + } + } + }; /** - * 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. + * Sets the index of the hovered chart and leaves the previous hovered + * chart, to reset states like tooltip. * - * @type {number} - * @apioption series.line.data.labelrank - */ + * @private + * @function Highcharts.Pointer#setHoverChartIndex + */ + Pointer.prototype.setHoverChartIndex = function () { + var chart = this.chart; + var hoverChart = H.charts[pick(H.hoverChartIndex, -1)]; + if (hoverChart && hoverChart !== chart) { + hoverChart.pointer.onContainerMouseLeave({ relatedTarget: true }); + } + if (!hoverChart || !hoverChart.mouseIsDown) { + H.hoverChartIndex = chart.index; + } + }; /** - * The name of the point as shown in the legend, tooltip, dataLabels, etc. + * General touch handler shared by touchstart and touchmove. * - * @see [xAxis.uniqueNames](#xAxis.uniqueNames) + * @private + * @function Highcharts.Pointer#touch * - * @sample {highcharts} highcharts/series/data-array-of-objects/ - * Point names + * @param {Highcharts.PointerEventObject} e * - * @type {string} - * @apioption series.line.data.name + * @param {boolean} [start] + * + * @return {void} */ + Pointer.prototype.touch = function (e, start) { + var chart = this.chart, + hasMoved, + pinchDown, + isInside; + this.setHoverChartIndex(); + 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); + } + }; /** - * Whether the data point is selected initially. + * Resolve the zoomType option, this is reset on all touch start and mouse + * down events. * - * @type {boolean} - * @default false - * @product highcharts highstock gantt - * @apioption series.line.data.selected - */ + * @private + * @function Highcharts.Pointer#zoomOption + * + * @param {global.Event} e + * Event object. + * + * @param {void} + */ + Pointer.prototype.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; + }; + return Pointer; + })(); + H.Pointer = Pointer; + + return Pointer; + } + ); + _registerModule( + _modules, + "Core/MSPointer.js", + [ + _modules["Core/Globals.js"], + _modules["Core/Pointer.js"], + _modules["Core/Utilities.js"], + ], + function (H, Pointer, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var __extends = + (this && this.__extends) || + (function () { + var extendStatics = function (d, b) { + extendStatics = + Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && + function (d, b) { + d.__proto__ = b; + }) || + function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { + this.constructor = d; + } + d.prototype = + b === null + ? Object.create(b) + : ((__.prototype = b.prototype), new __()); + }; + })(); + var charts = H.charts, + doc = H.doc, + noop = H.noop, + win = H.win; + var addEvent = U.addEvent, + css = U.css, + objectEach = U.objectEach, + removeEvent = U.removeEvent; + /* globals MSPointerEvent, PointerEvent */ + // The touches object keeps track of the points being touched at all times + var touches = {}; + var hasPointerEvent = !!win.PointerEvent; + /* eslint-disable valid-jsdoc */ + /** @private */ + function getWebkitTouches() { + var fake = []; + fake.item = function (i) { + return this[i]; + }; + objectEach(touches, function (touch) { + fake.push({ + pageX: touch.pageX, + pageY: touch.pageY, + target: touch.target, + }); + }); + return fake; + } + /** @private */ + function translateMSPointer(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(), + }); + } + } + /** @private */ + var MSPointer = /** @class */ (function (_super) { + __extends(MSPointer, _super); + function MSPointer() { + return (_super !== null && _super.apply(this, arguments)) || this; + } + /* * + * + * Functions + * + * */ /** - * The x value of the point. For datetime axes, the X value is the timestamp - * in milliseconds since 1970. + * Add or remove the MS Pointer specific events * - * @type {number} - * @product highcharts highstock - * @apioption series.line.data.x + * @private + * @function Highcharts.Pointer#batchMSEvents + * + * @param {Function} fn + * + * @return {void} */ + MSPointer.prototype.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 + ); + }; + // Destroy MS events also + MSPointer.prototype.destroy = function () { + this.batchMSEvents(removeEvent); + _super.prototype.destroy.call(this); + }; + // Disable default IE actions for pinch and such on chart element + MSPointer.prototype.init = function (chart, options) { + _super.prototype.init.call(this, chart, options); + if (this.hasZoom) { + // #4014 + css(chart.container, { + "-ms-touch-action": "none", + "touch-action": "none", + }); + } + }; /** - * The y value of the point. + * @private + * @function Highcharts.Pointer#onContainerPointerDown * - * @type {number|null} - * @product highcharts highstock - * @apioption series.line.data.y + * @param {Highcharts.PointerEventObject} e + * + * @return {void} */ + MSPointer.prototype.onContainerPointerDown = function (e) { + translateMSPointer( + e, + "onContainerTouchStart", + "touchstart", + function (e) { + touches[e.pointerId] = { + pageX: e.pageX, + pageY: e.pageY, + target: e.currentTarget, + }; + } + ); + }; /** - * The individual point events. + * @private + * @function Highcharts.Pointer#onContainerPointerMove + * + * @param {Highcharts.PointerEventObject} e * - * @extends plotOptions.series.point.events - * @product highcharts highstock gantt - * @apioption series.line.data.events + * @return {void} */ + MSPointer.prototype.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; + } + } + ); + }; /** - * Options for the point markers of line-like series. + * @private + * @function Highcharts.Pointer#onDocumentPointerUp * - * @declare Highcharts.PointMarkerOptionsObject - * @extends plotOptions.series.marker - * @product highcharts highstock - * @apioption series.line.data.marker + * @param {Highcharts.PointerEventObject} e + * + * @return {void} */ - ''; // include precedent doclets in transpilat + MSPointer.prototype.onDocumentPointerUp = function (e) { + translateMSPointer(e, "onDocumentTouchEnd", "touchend", function (e) { + delete touches[e.pointerId]; + }); + }; + // Add IE specific touch events to chart + MSPointer.prototype.setDOMEvents = function () { + _super.prototype.setDOMEvents.call(this); + if (this.hasZoom || this.followTouchMove) { + this.batchMSEvents(addEvent); + } + }; + return MSPointer; + })(Pointer); - return CartesianSeries; - }); - _registerModule(_modules, 'Series/LineSeries.js', [_modules['Core/Series/CartesianSeries.js'], _modules['Core/Globals.js']], function (CartesianSeries, H) { + return MSPointer; + } + ); + _registerModule( + _modules, + "Core/Legend.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Globals.js"], + _modules["Core/Utilities.js"], + ], + function (A, H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var animObject = A.animObject, + setAnimation = A.setAnimation; + /** + * Gets fired when the legend item belonging to a point is clicked. The default + * action is to toggle the visibility of the point. This can be prevented by + * returning `false` or calling `event.preventDefault()`. + * + * @callback Highcharts.PointLegendItemClickCallbackFunction + * + * @param {Highcharts.Point} this + * The point on which the event occured. + * + * @param {Highcharts.PointLegendItemClickEventObject} event + * The event that occured. + */ + /** + * Information about the legend click event. + * + * @interface Highcharts.PointLegendItemClickEventObject + */ /** + * Related browser event. + * @name Highcharts.PointLegendItemClickEventObject#browserEvent + * @type {Highcharts.PointerEvent} + */ /** + * Prevent the default action of toggle the visibility of the point. + * @name Highcharts.PointLegendItemClickEventObject#preventDefault + * @type {Function} + */ /** + * Related point. + * @name Highcharts.PointLegendItemClickEventObject#target + * @type {Highcharts.Point} + */ /** + * Event type. + * @name Highcharts.PointLegendItemClickEventObject#type + * @type {"legendItemClick"} + */ + /** + * Gets fired when the legend item belonging to a series is clicked. The default + * action is to toggle the visibility of the series. This can be prevented by + * returning `false` or calling `event.preventDefault()`. + * + * @callback Highcharts.SeriesLegendItemClickCallbackFunction + * + * @param {Highcharts.Series} this + * The series where the event occured. + * + * @param {Highcharts.SeriesLegendItemClickEventObject} event + * The event that occured. + */ + /** + * Information about the legend click event. + * + * @interface Highcharts.SeriesLegendItemClickEventObject + */ /** + * Related browser event. + * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent + * @type {Highcharts.PointerEvent} + */ /** + * Prevent the default action of toggle the visibility of the series. + * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault + * @type {Function} + */ /** + * Related series. + * @name Highcharts.SeriesLegendItemClickEventObject#target + * @type {Highcharts.Series} + */ /** + * Event type. + * @name Highcharts.SeriesLegendItemClickEventObject#type + * @type {"legendItemClick"} + */ + var addEvent = U.addEvent, + css = U.css, + defined = U.defined, + discardElement = U.discardElement, + find = U.find, + fireEvent = U.fireEvent, + format = U.format, + isNumber = U.isNumber, + merge = U.merge, + pick = U.pick, + relativeLength = U.relativeLength, + stableSort = U.stableSort, + syncTimeout = U.syncTimeout, + wrap = U.wrap; + var isFirefox = H.isFirefox, + marginNames = H.marginNames, + win = H.win; + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * The overview of the chart's series. The legend object is instanciated + * internally in the chart constructor, and is available from the `chart.legend` + * property. Each chart has only one legend. + * + * @class + * @name Highcharts.Legend + * + * @param {Highcharts.Chart} chart + * The chart instance. + * + * @param {Highcharts.LegendOptions} options + * Legend options. + */ + var Legend = /** @class */ (function () { /* * * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * Constructors * * */ - H.Series = CartesianSeries; // backwards compatibility - - return H.Series; - }); - _registerModule(_modules, 'Extensions/Stacking.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Axis/StackingAxis.js'], _modules['Core/Utilities.js']], function (Axis, Chart, H, StackingAxis, U) { + function Legend(chart, options) { + /* * + * + * Properties + * + * */ + this.allItems = []; + this.box = void 0; + this.contentGroup = void 0; + this.display = false; + this.group = void 0; + this.initialItemY = 0; + this.itemHeight = 0; + this.itemMarginBottom = 0; + this.itemMarginTop = 0; + this.itemX = 0; + this.itemY = 0; + this.lastItemY = 0; + this.lastLineHeight = 0; + this.legendHeight = 0; + this.legendWidth = 0; + this.maxItemWidth = 0; + this.maxLegendWidth = 0; + this.offsetWidth = 0; + this.options = {}; + this.padding = 0; + this.pages = []; + this.proximate = false; + this.scrollGroup = void 0; + this.symbolHeight = 0; + this.symbolWidth = 0; + this.titleHeight = 0; + this.totalItemWidth = 0; + this.widthOption = 0; + this.chart = chart; + this.init(chart, options); + } /* * * - * (c) 2010-2020 Torstein Honsi + * Functions * - * License: www.highcharts.com/license + * */ + /** + * Initialize the legend. * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @private + * @function Highcharts.Legend#init * - * */ - var correctFloat = U.correctFloat, - defined = U.defined, - destroyObjectProperties = U.destroyObjectProperties, - format = U.format, - isNumber = U.isNumber, - pick = U.pick; + * @param {Highcharts.Chart} chart + * The chart instance. + * + * @param {Highcharts.LegendOptions} options + * Legend options. + */ + Legend.prototype.init = function (chart, options) { + /** + * Chart of this legend. + * + * @readonly + * @name Highcharts.Legend#chart + * @type {Highcharts.Chart} + */ + this.chart = chart; + this.setOptions(options); + if (options.enabled) { + // Render it + this.render(); + // move checkboxes + addEvent(this.chart, "endResize", function () { + this.legend.positionCheckboxes(); + }); + if (this.proximate) { + this.unchartrender = addEvent(this.chart, "render", function () { + this.legend.proximatePositions(); + this.legend.positionItems(); + }); + } else if (this.unchartrender) { + this.unchartrender(); + } + } + }; /** - * Stack of data points + * @private + * @function Highcharts.Legend#setOptions + * @param {Highcharts.LegendOptions} options + */ + Legend.prototype.setOptions = function (options) { + var padding = pick(options.padding, 8); + /** + * Legend options. + * + * @readonly + * @name Highcharts.Legend#options + * @type {Highcharts.LegendOptions} + */ + this.options = options; + if (!this.chart.styledMode) { + this.itemStyle = options.itemStyle; + this.itemHiddenStyle = merge( + this.itemStyle, + options.itemHiddenStyle + ); + } + this.itemMarginTop = options.itemMarginTop || 0; + this.itemMarginBottom = options.itemMarginBottom || 0; + this.padding = padding; + this.initialItemY = padding - 5; // 5 is pixels above the text + this.symbolWidth = pick(options.symbolWidth, 16); + this.pages = []; + this.proximate = + options.layout === "proximate" && !this.chart.inverted; + this.baseline = void 0; // #12705: baseline has to be reset on every update + }; + /** + * Update the legend with new options. Equivalent to running `chart.update` + * with a legend configuration option. * - * @product highcharts + * @sample highcharts/legend/legend-update/ + * Legend update * - * @interface Highcharts.StackItemObject - */ /** - * Alignment settings - * @name Highcharts.StackItemObject#alignOptions - * @type {Highcharts.AlignObject} - */ /** - * Related axis - * @name Highcharts.StackItemObject#axis - * @type {Highcharts.Axis} - */ /** - * Cumulative value of the stacked data points - * @name Highcharts.StackItemObject#cumulative - * @type {number} - */ /** - * True if on the negative side - * @name Highcharts.StackItemObject#isNegative - * @type {boolean} - */ /** - * Related SVG element - * @name Highcharts.StackItemObject#label - * @type {Highcharts.SVGElement} - */ /** - * Related stack options - * @name Highcharts.StackItemObject#options - * @type {Highcharts.YAxisStackLabelsOptions} - */ /** - * Total value of the stacked data points - * @name Highcharts.StackItemObject#total - * @type {number} - */ /** - * Shared x value of the stack - * @name Highcharts.StackItemObject#x - * @type {number} - */ - ''; // detached doclets above - var Series = H.Series; - /* eslint-disable no-invalid-this, valid-jsdoc */ + * @function Highcharts.Legend#update + * + * @param {Highcharts.LegendOptions} options + * Legend options. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart after the axis 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. Whether to redraw the chart. + * + * @fires Highcharts.Legends#event:afterUpdate + */ + Legend.prototype.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(); + } + fireEvent(this, "afterUpdate"); + }; /** - * The class for stacks. Each stack, on a specific X value and either negative - * or positive, has its own stack item. + * Set the colors for the legend item. * * @private - * @class - * @name Highcharts.StackItem - * @param {Highcharts.Axis} axis - * @param {Highcharts.YAxisStackLabelsOptions} options - * @param {boolean} isNegative - * @param {number} x - * @param {Highcharts.OptionsStackingValue} [stackOption] - */ - var StackItem = /** @class */ (function () { - function StackItem(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 = 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 = {}; - this.hasValidPoints = false; - // 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: options.y, - x: options.x - }; - this.textAlign = options.textAlign || - (inverted ? (isNegative ? 'right' : 'left') : 'center'); + * @function Highcharts.Legend#colorizeItem + * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item + * A Series or Point instance + * @param {boolean} [visible=false] + * Dimmed or colored + * + * @todo + * Make events official: Fires the event `afterColorizeItem`. + */ + Legend.prototype.colorizeItem = function (item, visible) { + item.legendGroup[visible ? "removeClass" : "addClass"]( + "highcharts-legend-item-hidden" + ); + if (!this.chart.styledMode) { + 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 + }); } - /** - * @private - * @function Highcharts.StackItem#destroy - */ - StackItem.prototype.destroy = function () { - destroyObjectProperties(this, this.axis); - }; - /** - * Renders the stack total label and adds it to the stack label group. - * - * @private - * @function Highcharts.StackItem#render - * @param {Highcharts.SVGElement} group - */ - StackItem.prototype.render = function (group) { - var chart = this.axis.chart, - options = this.options, - formatOption = options.format, - attr = {}, - str = formatOption ? // format the text in the label - format(formatOption, - this, - chart) : - options.formatter.call(this); - // 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' }); - } - else { - // Create new label - this.label = chart.renderer - .label(str, null, null, options.shape, null, null, options.useHTML, false, 'stack-labels'); - attr = { - r: options.borderRadius || 0, - text: str, - rotation: options.rotation, - padding: pick(options.padding, 5), - visibility: 'hidden' // hidden until setOffset is called - }; - if (!chart.styledMode) { - attr.fill = options.backgroundColor; - attr.stroke = options.borderColor; - attr['stroke-width'] = options.borderWidth; - this.label.css(options.style); - } - this.label.attr(attr); - if (!this.label.added) { - this.label.add(group); // add to the labels-group - } - } - // Rank it higher than data labels (#8742) - this.label.labelrank = chart.plotHeight; - }; - /** - * Sets the offset that the stack has from the x value and repositions the - * label. - * - * @private - * @function Highcarts.StackItem#setOffset - * @param {number} xOffset - * @param {number} xWidth - * @param {number} [boxBottom] - * @param {number} [boxTop] - * @param {number} [defaultX] - */ - StackItem.prototype.setOffset = function (xOffset, xWidth, boxBottom, boxTop, defaultX) { - var stackItem = this, - axis = stackItem.axis, - chart = axis.chart, - // stack value translated mapped to chart coordinates - y = axis.translate(axis.stacking.usePercentage ? - 100 : - (boxTop ? - boxTop : - stackItem.total), 0, 0, 0, 1), - yZero = axis.translate(boxBottom ? boxBottom : 0), // stack origin - // stack height: - h = defined(y) && Math.abs(y - yZero), - // x position: - x = pick(defaultX, - chart.xAxis[0].translate(stackItem.x)) + - xOffset, - stackBox = defined(y) && stackItem.getStackBox(chart, - stackItem, - x, - y, - xWidth, - h, - axis), - label = stackItem.label, - isNegative = stackItem.isNegative, - isJustify = pick(stackItem.options.overflow, 'justify') === 'justify', - textAlign = stackItem.textAlign, - visible; - if (label && stackBox) { - var bBox = label.getBBox(), - padding = label.padding, - boxOffsetX, - boxOffsetY; - if (textAlign === 'left') { - boxOffsetX = chart.inverted ? -padding : padding; - } - else if (textAlign === 'right') { - boxOffsetX = bBox.width; - } - else { - if (chart.inverted && textAlign === 'center') { - boxOffsetX = bBox.width / 2; - } - else { - boxOffsetX = chart.inverted ? - (isNegative ? bBox.width + padding : -padding) : bBox.width / 2; - } - } - boxOffsetY = chart.inverted ? - bBox.height / 2 : (isNegative ? -padding : bBox.height); - // Reset alignOptions property after justify #12337 - stackItem.alignOptions.x = pick(stackItem.options.x, 0); - stackItem.alignOptions.y = pick(stackItem.options.y, 0); - // Set the stackBox position - stackBox.x -= boxOffsetX; - stackBox.y -= boxOffsetY; - // Align the label to the box - label.align(stackItem.alignOptions, null, stackBox); - // Check if label is inside the plotArea #12294 - if (chart.isInsidePlot(label.alignAttr.x + boxOffsetX - stackItem.alignOptions.x, label.alignAttr.y + boxOffsetY - stackItem.alignOptions.y)) { - label.show(); - } - else { - // Move label away to avoid the overlapping issues - label.alignAttr.y = -9999; - isJustify = false; - } - if (isJustify) { - // Justify stackLabel into the stackBox - Series.prototype.justifyDataLabel.call(this.axis, label, stackItem.alignOptions, label.alignAttr, bBox, stackBox); - } - label.attr({ - x: label.alignAttr.x, - y: label.alignAttr.y - }); - if (pick(!isJustify && stackItem.options.crop, true)) { - visible = - isNumber(label.x) && - isNumber(label.y) && - chart.isInsidePlot(label.x - padding + label.width, label.y) && - chart.isInsidePlot(label.x + padding, label.y); - if (!visible) { - label.hide(); - } - } - } - }; - /** - * @private - * @function Highcharts.StackItem#getStackBox - * - * @param {Highcharts.Chart} chart - * - * @param {Highcharts.StackItem} stackItem - * - * @param {number} x - * - * @param {number} y - * - * @param {number} xWidth - * - * @param {number} h - * - * @param {Highcharts.Axis} axis - * - * @return {Highcharts.BBoxObject} - */ - StackItem.prototype.getStackBox = function (chart, stackItem, x, y, xWidth, h, axis) { - var reversed = stackItem.axis.reversed, - inverted = chart.inverted, - axisPos = axis.height + axis.pos - - (inverted ? chart.plotLeft : chart.plotTop), - neg = (stackItem.isNegative && !reversed) || - (!stackItem.isNegative && reversed); // #4056 - return { - x: inverted ? (neg ? y - axis.right : y - h + axis.pos - chart.plotLeft) : - x + chart.xAxis[0].transB - chart.plotLeft, - y: inverted ? - axis.height - x - xWidth : - (neg ? - (axisPos - y - h) : - axisPos - y), - width: inverted ? h : xWidth, - height: inverted ? xWidth : h - }; - }; - return StackItem; - }()); + if (legendLine) { + legendLine.attr({ stroke: symbolColor }); + } + if (legendSymbol) { + // Apply marker options + if (markerOptions && legendSymbol.isMarker) { + // #585 + symbolAttr = item.pointAttribs(); + if (!visible) { + // #6769 + symbolAttr.stroke = symbolAttr.fill = hiddenColor; + } + } + legendSymbol.attr(symbolAttr); + } + } + fireEvent(this, "afterColorizeItem", { + item: item, + visible: visible, + }); + }; + /** + * @private + * @function Highcharts.Legend#positionItems + */ + Legend.prototype.positionItems = function () { + // Now that the legend width and height are established, put the items + // in the final position + this.allItems.forEach(this.positionItem, this); + if (!this.chart.isResizing) { + this.positionCheckboxes(); + } + }; + /** + * Position the legend item. + * + * @private + * @function Highcharts.Legend#positionItem + * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item + * The item to position + */ + Legend.prototype.positionItem = function (item) { + var _this = this; + 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) { + var attribs = { + translateX: ltr + ? itemX + : legend.legendWidth - itemX - 2 * symbolPadding - 4, + translateY: itemY, + }; + var complete = function () { + fireEvent(_this, "afterPositionItem", { item: item }); + }; + if (defined(legendGroup.translateY)) { + legendGroup.animate(attribs, void 0, complete); + } else { + legendGroup.attr(attribs); + complete(); + } + } + if (checkbox) { + checkbox.x = itemX; + checkbox.y = itemY; + } + }; /** - * Generate stacks for each series and calculate stacks total values + * Destroy a single legend item, used internally on removing series items. * * @private - * @function Highcharts.Chart#getStacks - */ - Chart.prototype.getStacks = function () { - var chart = this, - inverted = chart.inverted; - // reset stacks for each yAxis - chart.yAxis.forEach(function (axis) { - if (axis.stacking && axis.stacking.stacks && axis.hasVisibleSeries) { - axis.stacking.oldStacks = axis.stacking.stacks; - } - }); - chart.series.forEach(function (series) { - var xAxisOptions = series.xAxis && series.xAxis.options || {}; - if (series.options.stacking && - (series.visible === true || - chart.options.chart.ignoreHiddenSeries === false)) { - series.stackKey = [ - series.type, - pick(series.options.stack, ''), - inverted ? xAxisOptions.top : xAxisOptions.left, - inverted ? xAxisOptions.height : xAxisOptions.width - ].join(','); - } - }); + * @function Highcharts.Legend#destroyItem + * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item + * The item to remove + */ + Legend.prototype.destroyItem = function (item) { + var checkbox = item.checkbox; + // destroy SVG elements + ["legendItem", "legendLine", "legendSymbol", "legendGroup"].forEach( + function (key) { + if (item[key]) { + item[key] = item[key].destroy(); + } + } + ); + if (checkbox) { + discardElement(item.checkbox); + } }; - // Stacking methods defined on the Axis prototype - StackingAxis.compose(Axis); - // Stacking methods defined for Series prototype /** - * Set grouped points in a stack-like object. When `centerInCategory` is true, - * and `stacking` is not enabled, we need a pseudo (horizontal) stack in order - * to handle grouping of points within the same category. + * Destroy the legend. Used internally. To reflow objects, `chart.redraw` + * must be called after destruction. * * @private - * @function Highcharts.Series#setStackedPoints - * @return {void} - */ - Series.prototype.setGroupedPoints = function () { - if (this.options.centerInCategory && - (this.is('column') || this.is('columnrange')) && - // With stacking enabled, we already have stacks that we can compute - // from - !this.options.stacking && - // With only one series, we don't need to consider centerInCategory - this.chart.series.length > 1) { - Series.prototype.setStackedPoints.call(this, 'group'); + * @function Highcharts.Legend#destroy + */ + Legend.prototype.destroy = function () { + /** + * @private + * @param {string} key + * @return {void} + */ + function destroyItems(key) { + if (this[key]) { + this[key] = this[key].destroy(); } + } + // Destroy items + this.getAllItems().forEach(function (item) { + ["legendItem", "legendGroup"].forEach(destroyItems, item); + }); + // Destroy legend elements + [ + "clipRect", + "up", + "down", + "pager", + "nav", + "box", + "title", + "group", + ].forEach(destroyItems, this); + this.display = null; // Reset in .render on update. + }; + /** + * Position the checkboxes after the width is determined. + * + * @private + * @function Highcharts.Legend#positionCheckboxes + */ + Legend.prototype.positionCheckboxes = function () { + var alignAttr = this.group && this.group.alignAttr, + translateY, + clipHeight = this.clipHeight || this.legendHeight, + titleHeight = this.titleHeight; + if (alignAttr) { + translateY = alignAttr.translateY; + this.allItems.forEach(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: + this.proximate || + (top > translateY - 6 && top < translateY + clipHeight - 6) + ? "" + : "none", + }); + } + }, this); + } }; /** - * Adds series' points value to corresponding stack + * Render the legend title on top of the legend. * * @private - * @function Highcharts.Series#setStackedPoints + * @function Highcharts.Legend#renderTitle + */ + Legend.prototype.renderTitle = function () { + var options = this.options, + padding = this.padding, + titleOptions = options.title, + titleHeight = 0, + bBox; + if (titleOptions.text) { + if (!this.title) { + /** + * SVG element of the legend title. + * + * @readonly + * @name Highcharts.Legend#title + * @type {Highcharts.SVGElement} + */ + this.title = this.chart.renderer + .label( + titleOptions.text, + padding - 3, + padding - 4, + null, + null, + null, + options.useHTML, + null, + "legend-title" + ) + .attr({ zIndex: 1 }); + if (!this.chart.styledMode) { + this.title.css(titleOptions.style); + } + this.title.add(this.group); + } + // Set the max title width (#7253) + if (!titleOptions.width) { + this.title.css({ + width: this.maxLegendWidth + "px", + }); + } + 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. + * + * @function Highcharts.Legend#setText + * @param {Highcharts.Point|Highcharts.Series} item + * The item for which to update the text in the legend. */ - Series.prototype.setStackedPoints = function (stackingParam) { - var stacking = stackingParam || this.options.stacking; - if (!stacking || - (this.visible !== true && - this.chart.options.chart.ignoreHiddenSeries !== false)) { - return; + Legend.prototype.setText = function (item) { + var options = this.options; + item.legendItem.attr({ + text: options.labelFormat + ? format(options.labelFormat, item, this.chart) + : options.labelFormatter.call(item), + }); + }; + /** + * Render a single specific legend item. Called internally from the `render` + * function. + * + * @private + * @function Highcharts.Legend#renderItem + * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item + * The item to render. + */ + Legend.prototype.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, + itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, + ltr = !options.rtl, + bBox, + 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, + 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 + ); + if (!chart.styledMode) { + // merge to prevent modifying original (#1021) + li.css(merge(item.visible ? itemStyle : itemHiddenStyle)); } - 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, stackKey = stackingParam ? series.type + "," + stacking : series.stackKey, negKey = '-' + stackKey, negStacks = series.negStacks, yAxis = series.yAxis, stacks = yAxis.stacking.stacks, oldStacks = yAxis.stacking.oldStacks, stackIndicator, isNegative, stack, other, key, pointKey, i, x, y; - yAxis.stacking.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 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.stacking.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 if (stacking === 'group') { - // In this stack, the total is the number of valid points - if (y !== null) { - stack.total = (stack.total || 0) + 1; - } - } - else { - stack.total = correctFloat(stack.total + (y || 0)); - } - if (stacking === 'group') { - // This point's index within the stack, pushed to stack.points[1] - stack.cumulative = (stack.total || 1) - 1; - } - else { - stack.cumulative = - pick(stack.cumulative, stackThreshold) + (y || 0); - } - if (y !== null) { - stack.points[pointKey].push(stack.cumulative); - stackedYData[i] = stack.cumulative; - stack.hasValidPoints = true; - } + li.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) { + legend.fontMetrics = renderer.fontMetrics( + chart.styledMode ? 12 : itemStyle.fontSize, + li + ); + legend.baseline = legend.fontMetrics.f + 3 + legend.itemMarginTop; + li.attr("y", legend.baseline); } - if (stacking === 'percent') { - yAxis.stacking.usePercentage = true; + // 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); } - if (stacking !== 'group') { - this.stackedYData = stackedYData; // To be used in getExtremes + } + // Add the HTML checkbox on top + if (showCheckbox && !item.checkbox && legend.createCheckboxForItem) { + legend.createCheckboxForItem(item); + } + // Colorize the items + legend.colorizeItem(item, item.visible); + // Take care of max width and text overflow (#6659) + if (chart.styledMode || !itemStyle.width) { + li.css({ + width: + (options.itemWidth || + legend.widthOption || + chart.spacingBox.width) - + itemExtraWidth + + "px", + }); + } + // Always update the text + legend.setText(item); + // calculate the positions for the next line + bBox = li.getBBox(); + item.itemWidth = item.checkboxOffset = + options.itemWidth || + item.legendItemWidth || + bBox.width + itemExtraWidth; + legend.maxItemWidth = Math.max(legend.maxItemWidth, item.itemWidth); + legend.totalItemWidth += item.itemWidth; + legend.itemHeight = item.itemHeight = Math.round( + item.legendItemHeight || bBox.height || legend.symbolHeight + ); + }; + /** + * Get the position of the item in the layout. We now know the + * maxItemWidth from the previous loop. + * + * @private + * @function Highcharts.Legend#layoutItem + * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item + */ + Legend.prototype.layoutItem = function (item) { + var options = this.options, + padding = this.padding, + horizontal = options.layout === "horizontal", + itemHeight = item.itemHeight, + itemMarginBottom = this.itemMarginBottom, + itemMarginTop = this.itemMarginTop, + itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, + maxLegendWidth = this.maxLegendWidth, + itemWidth = + options.alignColumns && this.totalItemWidth > maxLegendWidth + ? this.maxItemWidth + : item.itemWidth; + // If the item exceeds the width, start a new line + if (horizontal && this.itemX - padding + itemWidth > maxLegendWidth) { + this.itemX = padding; + if (this.lastLineHeight) { + // Not for the first line (#10167) + this.itemY += + itemMarginTop + this.lastLineHeight + itemMarginBottom; } - // Reset old stacks - yAxis.stacking.oldStacks = {}; + this.lastLineHeight = 0; // reset for next line (#915, #3976) + } + // Set the edge positions + this.lastItemY = itemMarginTop + this.itemY + itemMarginBottom; + this.lastLineHeight = Math.max( + // #915 + itemHeight, + this.lastLineHeight + ); + // cache the position of the newly generated or reordered items + item._legendItemPos = [this.itemX, this.itemY]; + // advance + if (horizontal) { + this.itemX += itemWidth; + } else { + this.itemY += itemMarginTop + itemHeight + itemMarginBottom; + this.lastLineHeight = itemHeight; + } + // the width of the widest item + this.offsetWidth = + this.widthOption || + Math.max( + (horizontal + ? this.itemX - + padding - + (item.checkbox + ? // decrease by itemDistance only when no checkbox #4853 + 0 + : itemDistance) + : itemWidth) + padding, + this.offsetWidth + ); }; /** - * Iterate over all stacks and compute the absolute values to percent + * Get all items, which is one item per series for most series and one + * item per point for pie series and its derivatives. Fires the event + * `afterGetAllItems`. * * @private - * @function Highcharts.Series#modifyStacks - */ - Series.prototype.modifyStacks = function () { - var series = this, - yAxis = series.yAxis, - stackKey = series.stackKey, - stacks = yAxis.stacking.stacks, - processedXData = series.processedXData, - stackIndicator, - stacking = series.options.stacking; - if (series[stacking + 'Stacker']) { // Modifier function exists - [stackKey, '-' + stackKey].forEach(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); - } - } - }); + * @function Highcharts.Legend#getAllItems + * @return {Array<(Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series)>} + * The current items in the legend. + * @fires Highcharts.Legend#event:afterGetAllItems + */ + Legend.prototype.getAllItems = function () { + var allItems = []; + this.chart.series.forEach(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) ? void 0 : false, + true + ) + ) { + // Use points or series for the legend item depending on + // legendType + allItems = allItems.concat( + series.legendItems || + (seriesOptions.legendType === "point" ? series.data : series) + ); } + }); + fireEvent(this, "afterGetAllItems", { allItems: allItems }); + return allItems; }; /** - * Modifier function for percent stacks. Blows up the stack to 100%. + * Get a short, three letter string reflecting the alignment and layout. * * @private - * @function Highcharts.Series#percentStacker - * @param {Array<number>} pointExtremes - * @param {Highcharts.StackItem} stack - * @param {number} i + * @function Highcharts.Legend#getAlignment + * @return {string} + * The alignment, empty string if floating + */ + Legend.prototype.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) + if (this.proximate) { + return options.align.charAt(0) + "tv"; + } + 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 + * @function Highcharts.Legend#adjustMargins + * @param {Array<number>} margin + * @param {Array<number>} spacing + */ + Legend.prototype.adjustMargins = function (margin, spacing) { + var chart = this.chart, + options = this.options, + alignment = this.getAlignment(); + if (alignment) { + [ + /(lth|ct|rth)/, + /(rtv|rm|rbv)/, + /(rbh|cb|lbh)/, + /(lbv|lm|ltv)/, + ].forEach(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] + + (chart.titleOffset[side] || 0) + ); + } + }); + } + }; + /** + * @private + * @function Highcharts.Legend#proximatePositions + */ + Legend.prototype.proximatePositions = function () { + var chart = this.chart, + boxes = [], + alignLeft = this.options.align === "left"; + this.allItems.forEach(function (item) { + var lastPoint, + height, + useFirstPoint = alignLeft, + target, + top; + if (item.yAxis) { + if (item.xAxis.options.reversed) { + useFirstPoint = !useFirstPoint; + } + if (item.points) { + lastPoint = find( + useFirstPoint ? item.points : item.points.slice(0).reverse(), + function (item) { + return isNumber(item.plotY); + } + ); + } + height = + this.itemMarginTop + + item.legendItem.getBBox().height + + this.itemMarginBottom; + top = item.yAxis.top - chart.plotTop; + if (item.visible) { + target = lastPoint ? lastPoint.plotY : item.yAxis.height; + target += top - 0.3 * height; + } else { + target = top + item.yAxis.height; + } + boxes.push({ + target: target, + size: height, + item: item, + }); + } + }, this); + H.distribute(boxes, chart.plotHeight); + boxes.forEach(function (box) { + box.item._legendItemPos[1] = + chart.plotTop - chart.spacing[0] + box.pos; + }); + }; + /** + * 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. + * + * @private + * @function Highcharts.Legend#render + */ + Legend.prototype.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, + allowedWidth; + legend.itemX = padding; + legend.itemY = legend.initialItemY; + legend.offsetWidth = 0; + legend.lastItemY = 0; + legend.widthOption = relativeLength( + options.width, + chart.spacingBox.width - padding + ); + // Compute how wide the legend is allowed to be + allowedWidth = chart.spacingBox.width - 2 * padding - options.x; + if ( + ["rm", "lm"].indexOf(legend.getAlignment().substring(0, 2)) > -1 + ) { + allowedWidth /= 2; + } + legend.maxLegendWidth = legend.widthOption || allowedWidth; + if (!legendGroup) { + /** + * SVG group of the legend. + * + * @readonly + * @name Highcharts.Legend#group + * @type {Highcharts.SVGElement} + */ + 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(); + } + /** + * All items for the legend, which is an array of series for most series + * and an array of points for pie series and its derivatives. + * + * @readonly + * @name Highcharts.Legend#allItems + * @type {Array<(Highcharts.Point|Highcharts.Series)>} + */ + legend.allItems = allItems; + legend.display = display = !!allItems.length; + // Render the items. First we run a loop to set the text and properties + // and read all the bounding boxes. The next loop computes the item + // positions based on the bounding boxes. + legend.lastLineHeight = 0; + legend.maxItemWidth = 0; + legend.totalItemWidth = 0; + legend.itemHeight = 0; + allItems.forEach(legend.renderItem, legend); + allItems.forEach(legend.layoutItem, legend); + // Get the box + legendWidth = (legend.widthOption || legend.offsetWidth) + padding; + legendHeight = + legend.lastItemY + legend.lastLineHeight + legend.titleHeight; + legendHeight = legend.handleOverflow(legendHeight); + legendHeight += padding; + // Draw the border and/or background + if (!box) { + /** + * SVG element of the legend box. + * + * @readonly + * @name Highcharts.Legend#box + * @type {Highcharts.SVGElement} + */ + legend.box = box = renderer + .rect() + .addClass("highcharts-legend-box") + .attr({ + r: options.borderRadius, + }) + .add(legendGroup); + box.isNew = true; + } + // Presentational + if (!chart.styledMode) { + 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( + {}, + { + x: 0, + y: 0, + width: legendWidth, + height: legendHeight, + }, + box.strokeWidth() + ) + ); + box.isNew = false; + } + // hide the border if no items + box[display ? "show" : "hide"](); + // Open for responsiveness + if (chart.styledMode && legendGroup.getStyle("display") === "none") { + legendWidth = legendHeight = 0; + } + legend.legendWidth = legendWidth; + legend.legendHeight = legendHeight; + if (display) { + legend.align(); + } + if (!this.proximate) { + this.positionItems(); + } + fireEvent(this, "afterRender"); + }; + /** + * Align the legend to chart's box. + * + * @private + * @function Highcharts.align + * @param {Highcharts.BBoxObject} alignTo + * @return {void} */ - 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]; + Legend.prototype.align = function (alignTo) { + if (alignTo === void 0) { + alignTo = this.chart.spacingBox; + } + var chart = this.chart, + options = this.options; + // If aligning to the top and the layout is horizontal, adjust for + // the title (#7428) + var y = alignTo.y; + if ( + /(lth|ct|rth)/.test(this.getAlignment()) && + chart.titleOffset[0] > 0 + ) { + y += chart.titleOffset[0]; + } else if ( + /(lbh|cb|rbh)/.test(this.getAlignment()) && + chart.titleOffset[2] > 0 + ) { + y -= chart.titleOffset[2]; + } + if (y !== alignTo.y) { + alignTo = merge(alignTo, { y: y }); + } + this.group.align( + merge(options, { + width: this.legendWidth, + height: this.legendHeight, + verticalAlign: this.proximate ? "top" : options.verticalAlign, + }), + true, + alignTo + ); }; /** - * Get stack indicator, according to it's x-value, to determine points with the - * same x-value + * Set up the overflow handling by adding navigation with up and down arrows + * below the legend. * * @private - * @function Highcharts.Series#getStackIndicator - * @param {Highcharts.StackItemIndicatorObject|undefined} stackIndicator - * @param {number} x - * @param {number} index - * @param {string} [key] - * @return {Highcharts.StackItemIndicatorObject} - */ - 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 - }; + * @function Highcharts.Legend#handleOverflow + * @param {number} legendHeight + * @return {number} + */ + Legend.prototype.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"; + } + }, + addTracker = function (key) { + legend[key] = renderer + .circle(0, 0, arrowSize * 1.3) + .translate(arrowSize / 2, arrowSize / 2) + .add(nav); + if (!chart.styledMode) { + legend[key].attr("fill", "rgba(0,0,0,0.0001)"); + } + return legend[key]; + }; + // 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) + allItems.forEach(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 && + y !== lastY // #2617 + ) { + 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); } - else { - (stackIndicator).index++; + 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) + .add(nav); + addTracker("upTracker").on("click", function () { + legend.scroll(-1, animation); + }); + this.pager = renderer + .text("", 15, 10) + .addClass("highcharts-legend-navigation"); + if (!chart.styledMode) { + this.pager.css(navOptions.style); + } + this.pager.add(nav); + this.down = renderer + .symbol("triangle-down", 0, 0, arrowSize, arrowSize) + .add(nav); + addTracker("downTracker").on("click", function () { + legend.scroll(1, animation); + }); } - stackIndicator.key = - [index, x, stackIndicator.index].join(','); - return stackIndicator; + // 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; }; - H.StackItem = StackItem; - - return H.StackItem; - }); - _registerModule(_modules, 'Core/Dynamics.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Axis/Axis.js'], _modules['Core/Series/Series.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Series/LineSeries.js'], _modules['Core/Options.js'], _modules['Core/Series/Point.js'], _modules['Core/Time.js'], _modules['Core/Utilities.js']], function (A, Axis, BaseSeries, Chart, H, LineSeries, O, Point, Time, U) { - /* * + /** + * Scroll the legend by a number of pages. * - * (c) 2010-2020 Torstein Honsi + * @private + * @function Highcharts.Legend#scroll * - * License: www.highcharts.com/license + * @param {number} scrollBy + * The number of pages to scroll. * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] + * Whether and how to apply animation. * - * */ - var animate = A.animate, - setAnimation = A.setAnimation; - var seriesTypes = BaseSeries.seriesTypes; - var time = O.time; - var addEvent = U.addEvent, - createElement = U.createElement, - css = U.css, - defined = U.defined, - erase = U.erase, - error = U.error, - extend = U.extend, - fireEvent = U.fireEvent, - isArray = U.isArray, - isNumber = U.isNumber, - isObject = U.isObject, - isString = U.isString, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick, - relativeLength = U.relativeLength, - splat = U.splat; - /* eslint-disable valid-jsdoc */ - /** - * Remove settings that have not changed, to avoid unnecessary rendering or - * computing (#9197). - * @private + * @return {void} */ - H.cleanRecursively = function (newer, older) { - var result = {}; - objectEach(newer, function (val, key) { - var ob; - // Dive into objects (except DOM nodes) - if (isObject(newer[key], true) && - !newer.nodeType && // #10044 - older[key]) { - ob = H.cleanRecursively(newer[key], older[key]); - if (Object.keys(ob).length) { - result[key] = ob; - } - // Arrays, primitives and DOM nodes are copied directly - } - else if (isObject(newer[key]) || - newer[key] !== older[key]) { - result[key] = newer[key]; - } + Legend.prototype.scroll = function (scrollBy, animation) { + var _this = this; + var chart = this.chart, + 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 (typeof animation !== "undefined") { + setAnimation(animation, chart); + } + this.nav.attr({ + translateX: padding, + translateY: clipHeight + this.padding + 7 + this.titleHeight, + visibility: "visible", }); - return result; - }; - // 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. - * - * @sample highcharts/members/chart-addseries/ - * Add a series from a button - * @sample stock/members/chart-addseries/ - * Add a series in Highstock - * - * @function Highcharts.Chart#addSeries - * - * @param {Highcharts.SeriesOptionsType} options - * The config options for the series. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart after adding. - * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] - * Whether to apply animation, and optionally animation - * configuration. - * - * @return {Highcharts.Series} - * The newly created series object. - * - * @fires Highcharts.Chart#event:addSeries - * @fires Highcharts.Chart#event:afterAddSeries - */ - addSeries: function (options, redraw, animation) { - var series, - chart = this; - if (options) { // <- not necessary - redraw = pick(redraw, true); // defaults to true - fireEvent(chart, 'addSeries', { options: options }, function () { - series = chart.initSeries(options); - chart.isDirtyLegend = true; - chart.linkSeries(); - if (series.enabledDataSorting) { - // We need to call `setData` after `linkSeries` - series.setData(options.data, false); - } - fireEvent(chart, 'afterAddSeries', { series: series }); - 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. - * - * @sample highcharts/members/chart-addaxis/ - * Add and remove axes - * - * @function Highcharts.Chart#addAxis - * - * @param {Highcharts.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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] - * Whether and how to apply animation in the redraw. - * - * @return {Highcharts.Axis} - * The newly generated Axis object. - */ - addAxis: function (options, isX, redraw, animation) { - return this.createAxis(isX ? 'xAxis' : 'yAxis', { axis: options, redraw: redraw, animation: animation }); - }, - /** - * Add a color 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. - * - * @sample highcharts/members/chart-addaxis/ - * Add and remove axes - * - * @function Highcharts.Chart#addColorAxis - * - * @param {Highcharts.ColorAxisOptions} options - * The axis options. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart after adding. - * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] - * Whether and how to apply animation in the redraw. - * - * @return {Highcharts.ColorAxis} - * The newly generated Axis object. - */ - addColorAxis: function (options, redraw, animation) { - return this.createAxis('colorAxis', { axis: options, redraw: redraw, animation: animation }); - }, - /** - * Factory for creating different axis types. - * - * @private - * @function Highcharts.Chart#createAxis - * - * @param {string} type - * An axis type. - * - * @param {...Array<*>} arguments - * All arguments for the constructor. - * - * @return {Highcharts.Axis | Highcharts.ColorAxis} - * The newly generated Axis object. - */ - createAxis: function (type, options) { - var chartOptions = this.options, - isColorAxis = type === 'colorAxis', - axisOptions = options.axis, - redraw = options.redraw, - animation = options.animation, - userOptions = merge(axisOptions, { - index: this[type].length, - isX: type === 'xAxis' - }), - axis; - if (isColorAxis) { - axis = new H.ColorAxis(this, userOptions); - } - else { - axis = new Axis(this, userOptions); - } - // Push the new axis options to the chart options - chartOptions[type] = splat(chartOptions[type] || {}); - chartOptions[type].push(userOptions); - if (isColorAxis) { - this.isDirtyLegend = true; - // Clear before 'bindAxes' (#11924) - this.axes.forEach(function (axis) { - axis.series = []; - }); - this.series.forEach(function (series) { - series.bindAxes(); - series.isDirtyData = true; - }); - } - 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}. - * - * @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 - * - * @function Highcharts.Chart#showLoading - * - * @param {string} [str] - * An optional text to show in the loading label instead of the - * default one. The default text is set in - * [lang.loading](https://api.highcharts.com/highcharts/lang.loading). - */ - 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 = - pick(str, options.lang.loading, ''); - if (!chart.styledMode) { - // 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 - * - * @function Highcharts.Chart#hideLoading - */ - hideLoading: function () { - var options = this.options, - loadingDiv = this.loadingDiv; - if (loadingDiv) { - loadingDiv.className = - 'highcharts-loading highcharts-loading-hidden'; - if (!this.styledMode) { - 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', - 'borderRadius', - 'plotBackgroundColor', - 'plotBackgroundImage', - 'plotBorderColor', - 'plotBorderWidth', - 'plotShadow', - 'shadow' - ], - /** - * These properties require a full reflow of chart elements, best - * implemented through running `Chart.setSize` internally (#8190). - * @type {Array} - */ - propsRequireReflow: [ - 'margin', - 'marginTop', - 'marginRight', - 'marginBottom', - 'marginLeft', - 'spacing', - 'spacingTop', - 'spacingRight', - 'spacingBottom', - 'spacingLeft' - ], - /** - * 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' - ], - /** - * These collections (arrays) implement update() methods with support for - * one-to-one option. - */ - collectionsWithUpdate: [ - 'xAxis', - 'yAxis', - 'zAxis', - 'series' - ], - /** - * 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 - * [xAxis](https://api.highcharts.com/highcharts/xAxis), - * [yAxis](https://api.highcharts.com/highcharts/yAxis) or - * [series](https://api.highcharts.com/highcharts/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. - * - * Note that when changing series data, `chart.update` may mutate the passed - * data options. - * - * See also the - * [responsive option set](https://api.highcharts.com/highcharts/responsive). - * Switching between `responsive.rules` basically runs `chart.update` under - * the hood. - * - * @sample highcharts/members/chart-update/ - * Update chart geometry - * - * @function Highcharts.Chart#update - * - * @param {Highcharts.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`, `yAxis` and `annotations` - * 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. - * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] - * Whether to apply animation, and optionally animation - * configuration. - * - * @fires Highcharts.Chart#event:update - * @fires Highcharts.Chart#event:afterUpdate - */ - update: function (options, redraw, oneToOne, animation) { - var chart = this, - adders = { - credits: 'addCredits', - title: 'setTitle', - subtitle: 'setSubtitle', - caption: 'setCaption' - }, - optionsChart, - updateAllAxes, - updateAllSeries, - newWidth, - newHeight, - runSetSize, - isResponsiveOptions = options.isResponsiveOptions, - itemsForRemoval = []; - fireEvent(chart, 'update', { options: options }); - // If there are responsive rules in action, undo the responsive rules - // before we apply the updated options and replay the responsive rules - // on top from the chart.redraw function (#9617). - if (!isResponsiveOptions) { - chart.setResponsive(false, true); - } - options = H.cleanRecursively(options, chart.options); - merge(true, chart.userOptions, options); - // If the top-level chart option is present, some special updates are - // required - optionsChart = options.chart; - if (optionsChart) { - merge(true, chart.options.chart, optionsChart); - // Setter function - if ('className' in optionsChart) { - chart.setClassName(optionsChart.className); - } - if ('reflow' in optionsChart) { - chart.setReflow(optionsChart.reflow); - } - if ('inverted' in optionsChart || - 'polar' in optionsChart || - 'type' 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 (chart.propsRequireUpdateSeries.indexOf('chart.' + key) !== - -1) { - updateAllSeries = true; - } - // Only dirty box - if (chart.propsRequireDirtyBox.indexOf(key) !== -1) { - chart.isDirtyBox = true; - } - // Chart setSize - if (chart.propsRequireReflow.indexOf(key) !== -1) { - if (isResponsiveOptions) { - chart.isDirtyBox = true; - } - else { - runSetSize = true; - } - } - }); - if (!chart.styledMode && 'style' in optionsChart) { - chart.renderer.setStyle(optionsChart.style); - } - } - // Moved up, because tooltip needs updated plotOptions (#6218) - if (!chart.styledMode && options.colors) { - this.options.colors = options.colors; - } - if (options.time) { - // Maintaining legacy global time. If the chart is instanciated - // first with global time, then updated with time options, we need - // to create a new Time instance to avoid mutating the global time - // (#10536). - if (this.time === time) { - this.time = new Time(options.time); - } - // If we're updating, the time class is different from other chart - // classes (chart.legend, chart.tooltip etc) in that it doesn't know - // about the chart. The other chart[something].update functions also - // set the chart.options[something]. For the time class however we - // need to update the chart options separately. #14230. - merge(true, chart.options.time, options.time); - } - // 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); - // Else, just merge the options. For nodes like loading, noData, - // plotOptions - } - else if (key !== 'color' && - chart.collectionsWithUpdate.indexOf(key) === -1) { - merge(true, chart.options[key], options[key]); - } - if (key !== 'chart' && - chart.propsRequireUpdateSeries.indexOf(key) !== -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. - this.collectionsWithUpdate.forEach(function (coll) { - var indexMap; - if (options[coll]) { - // In stock charts, the navigator series are also part of the - // chart.series array, but those series should not be handled - // here (#8196). - if (coll === 'series') { - indexMap = []; - chart[coll].forEach(function (s, i) { - if (!s.options.isInternal) { - indexMap.push(pick(s.options.index, i)); - } - }); - } - splat(options[coll]).forEach(function (newOptions, i) { - var hasId = defined(newOptions.id); - var item; - // Match by id - if (hasId) { - item = chart.get(newOptions.id); - } - // No match by id found, match by index instead - if (!item) { - item = chart[coll][indexMap ? indexMap[i] : i]; - // Check if we grabbed an item with an exising but - // different id (#13541) - if (item && hasId && defined(item.options.id)) { - item = void 0; - } - } - 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 && chart.collectionsWithInit[coll]) { - chart.collectionsWithInit[coll][0].apply(chart, - // [newOptions, ...extraArguments, redraw=false] - [ - newOptions - ].concat( - // Not all initializers require extra args - chart.collectionsWithInit[coll][1] || []).concat([ - false - ])).touched = true; - } - }); - // Add items for removal - if (oneToOne) { - chart[coll].forEach(function (item) { - if (!item.touched && !item.options.isInternal) { - itemsForRemoval.push(item); - } - else { - delete item.touched; - } - }); - } - } - }); - itemsForRemoval.forEach(function (item) { - if (item.remove) { - item.remove(false); - } - }); - if (updateAllAxes) { - chart.axes.forEach(function (axis) { - axis.update({}, false); - }); - } - // Certain options require the whole series structure to be thrown away - // and rebuilt - if (updateAllSeries) { - chart.getSeriesOrderByLinks().forEach(function (series) { - // Avoid removed navigator series - if (series.chart) { - series.update({}, false); - } - }, this); - } - // Update size. Redraw is forced. - newWidth = optionsChart && optionsChart.width; - newHeight = optionsChart && optionsChart.height; - if (isString(newHeight)) { - newHeight = relativeLength(newHeight, newWidth || chart.chartWidth); - } - if ( - // In this case, run chart.setSize with newWidth and newHeight which - // are undefined, only for reflowing chart elements because margin - // or spacing has been set (#8190) - runSetSize || - // In this case, the size is actually set - (isNumber(newWidth) && newWidth !== chart.chartWidth) || - (isNumber(newHeight) && newHeight !== chart.chartHeight)) { - chart.setSize(newWidth, newHeight, animation); - } - else if (pick(redraw, true)) { - chart.redraw(animation); - } - fireEvent(chart, 'afterUpdate', { - options: options, - redraw: redraw, - animation: animation - }); - }, - /** - * Shortcut to set the subtitle options. This can also be done from {@link - * Chart#update} or {@link Chart#setTitle}. - * - * @function Highcharts.Chart#setSubtitle - * - * @param {Highcharts.SubtitleOptions} options - * New subtitle options. The subtitle text itself is set by the - * `options.text` property. - */ - setSubtitle: function (options, redraw) { - this.applyDescription('subtitle', options); - this.layOutTitles(redraw); - }, - /** - * Set the caption options. This can also be done from {@link - * Chart#update}. - * - * @function Highcharts.Chart#setCaption - * - * @param {Highcharts.CaptionOptions} options - * New caption options. The caption text itself is set by the - * `options.text` property. - */ - setCaption: function (options, redraw) { - this.applyDescription('caption', options); - this.layOutTitles(redraw); + [this.up, this.upTracker].forEach(function (elem) { + elem.attr({ + class: + currentPage === 1 + ? "highcharts-legend-nav-inactive" + : "highcharts-legend-nav-active", + }); + }); + pager.attr({ + text: currentPage + "/" + pageCount, + }); + [this.down, this.downTracker].forEach(function (elem) { + elem.attr({ + // adjust to text width + x: 18 + this.pager.getBBox().width, + class: + currentPage === pageCount + ? "highcharts-legend-nav-inactive" + : "highcharts-legend-nav-active", + }); + }, this); + if (!chart.styledMode) { + this.up.attr({ + fill: + currentPage === 1 + ? navOptions.inactiveColor + : navOptions.activeColor, + }); + this.upTracker.css({ + cursor: currentPage === 1 ? "default" : "pointer", + }); + this.down.attr({ + fill: + currentPage === pageCount + ? navOptions.inactiveColor + : navOptions.activeColor, + }); + this.downTracker.css({ + cursor: currentPage === pageCount ? "default" : "pointer", + }); } + this.scrollOffset = -pages[currentPage - 1] + this.initialItemY; + this.scrollGroup.animate({ + translateY: this.scrollOffset, + }); + this.currentPage = currentPage; + this.positionCheckboxes(); + // Fire event after scroll animation is complete + var animOptions = animObject( + pick(animation, chart.renderer.globalAnimation, true) + ); + syncTimeout(function () { + fireEvent(_this, "afterScroll", { currentPage: currentPage }); + }, animOptions.duration); + } + }; + return Legend; + })(); + // 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 && win.navigator.userAgent) || + isFirefox + ) { + wrap(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 + if (!legend.bubbleLegend) { + setTimeout(runPositionItem); + } }); + } + H.Legend = Legend; + + return H.Legend; + } + ); + _registerModule( + _modules, + "Core/Series/Point.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Globals.js"], + _modules["Core/Utilities.js"], + ], + function (A, H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var animObject = A.animObject; + var defined = U.defined, + erase = U.erase, + extend = U.extend, + fireEvent = U.fireEvent, + format = U.format, + getNestedProperty = U.getNestedProperty, + isArray = U.isArray, + isNumber = U.isNumber, + isObject = U.isObject, + syncTimeout = U.syncTimeout, + pick = U.pick, + removeEvent = U.removeEvent, + uniqueKey = U.uniqueKey; + /** + * Function callback when a series point is clicked. Return false to cancel the + * action. + * + * @callback Highcharts.PointClickCallbackFunction + * + * @param {Highcharts.Point} this + * The point where the event occured. + * + * @param {Highcharts.PointClickEventObject} event + * Event arguments. + */ + /** + * Common information for a click event on a series point. + * + * @interface Highcharts.PointClickEventObject + * @extends Highcharts.PointerEventObject + */ /** + * Clicked point. + * @name Highcharts.PointClickEventObject#point + * @type {Highcharts.Point} + */ + /** + * Configuration hash for the data label and tooltip formatters. + * + * @interface Highcharts.PointLabelObject + */ /** + * The point's current color. + * @name Highcharts.PointLabelObject#color + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} + */ /** + * 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 Highcharts.PointLabelObject#colorIndex + * @type {number} + */ /** + * The name of the related point. + * @name Highcharts.PointLabelObject#key + * @type {string|undefined} + */ /** + * The percentage for related points in a stacked series or pies. + * @name Highcharts.PointLabelObject#percentage + * @type {number} + */ /** + * The related point. The point name, if defined, is available through + * `this.point.name`. + * @name Highcharts.PointLabelObject#point + * @type {Highcharts.Point} + */ /** + * The related series. The series name is available through `this.series.name`. + * @name Highcharts.PointLabelObject#series + * @type {Highcharts.Series} + */ /** + * The total of values in either a stack for stacked series, or a pie in a pie + * series. + * @name Highcharts.PointLabelObject#total + * @type {number|undefined} + */ /** + * For categorized axes this property holds the category name for the point. For + * other axes it holds the X value. + * @name Highcharts.PointLabelObject#x + * @type {number|string|undefined} + */ /** + * The y value of the point. + * @name Highcharts.PointLabelObject#y + * @type {number|undefined} + */ + /** + * Gets fired when the mouse leaves the area close to the point. + * + * @callback Highcharts.PointMouseOutCallbackFunction + * + * @param {Highcharts.Point} this + * Point where the event occured. + * + * @param {global.PointerEvent} event + * Event that occured. + */ + /** + * Gets fired when the mouse enters the area close to the point. + * + * @callback Highcharts.PointMouseOverCallbackFunction + * + * @param {Highcharts.Point} this + * Point where the event occured. + * + * @param {global.Event} event + * Event that occured. + */ + /** + * The generic point options for all series. + * + * In TypeScript you have to extend `PointOptionsObject` with an additional + * declaration to allow custom data options: + * + * ``` + * declare interface PointOptionsObject { + * customProperty: string; + * } + * ``` + * + * @interface Highcharts.PointOptionsObject + */ + /** + * Possible option types for a data point. Use `null` to indicate a gap. + * + * @typedef {number|string|Highcharts.PointOptionsObject|Array<(number|string|null)>|null} Highcharts.PointOptionsType + */ + /** + * Gets fired when the point is removed using the `.remove()` method. + * + * @callback Highcharts.PointRemoveCallbackFunction + * + * @param {Highcharts.Point} this + * Point where the event occured. + * + * @param {global.Event} event + * Event that occured. + */ + /** + * Possible key values for the point state options. + * + * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.PointStateValue + */ + /** + * Gets fired when the point is updated programmatically through the `.update()` + * method. + * + * @callback Highcharts.PointUpdateCallbackFunction + * + * @param {Highcharts.Point} this + * Point where the event occured. + * + * @param {Highcharts.PointUpdateEventObject} event + * Event that occured. + */ + /** + * Information about the update event. + * + * @interface Highcharts.PointUpdateEventObject + * @extends global.Event + */ /** + * Options data of the update event. + * @name Highcharts.PointUpdateEventObject#options + * @type {Highcharts.PointOptionsType} + */ + (""); // detach doclet above + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * 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 instantiate points are through {@link + * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}. + * + * @class + * @name Highcharts.Point + */ + var Point = /** @class */ (function () { + function Point() { + /* * + * + * Properties + * + * */ + /** + * For categorized axes this property holds the category name for the + * point. For other axes it holds the X value. + * + * @name Highcharts.Point#category + * @type {string} + */ + this.category = void 0; + /** + * 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 Highcharts.Point#colorIndex + * @type {number} + */ + this.colorIndex = void 0; + this.formatPrefix = "point"; + this.id = void 0; + this.isNull = false; + /** + * 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 Highcharts.Point#name + * @type {string} + */ + this.name = void 0; + /** + * The point's options as applied in the initial configuration, or + * extended through `Point.update`. + * + * In TypeScript you have to extend `PointOptionsObject` via an + * additional interface to allow custom data options: + * + * ``` + * declare interface PointOptionsObject { + * customProperty: string; + * } + * ``` + * + * @name Highcharts.Point#options + * @type {Highcharts.PointOptionsObject} + */ + this.options = void 0; + /** + * The percentage for points in a stacked series or pies. + * + * @name Highcharts.Point#percentage + * @type {number|undefined} + */ + this.percentage = void 0; + this.selected = false; + /** + * The series object associated with the point. + * + * @name Highcharts.Point#series + * @type {Highcharts.Series} + */ + this.series = void 0; + /** + * The total of values in either a stack for stacked series, or a pie in a + * pie series. + * + * @name Highcharts.Point#total + * @type {number|undefined} + */ + this.total = void 0; + /** + * For certain series types, like pie charts, where individual points can + * be shown or hidden. + * + * @name Highcharts.Point#visible + * @type {boolean} + * @default true + */ + this.visible = true; + this.x = void 0; + } + /* * + * + * Functions + * + * */ + /** + * Animate SVG elements associated with the point. + * + * @private + * @function Highcharts.Point#animateBeforeDestroy + */ + Point.prototype.animateBeforeDestroy = function () { + var point = this, + animateParams = { x: point.startXPos, opacity: 0 }, + isDataLabel, + graphicalProps = point.getGraphicalProps(); + graphicalProps.singular.forEach(function (prop) { + isDataLabel = prop === "dataLabel"; + point[prop] = point[prop].animate( + isDataLabel + ? { + x: point[prop].startXPos, + y: point[prop].startYPos, + opacity: 0, + } + : animateParams + ); + }); + graphicalProps.plural.forEach(function (plural) { + point[plural].forEach(function (item) { + if (item.element) { + item.animate( + extend( + { x: point.startXPos }, + item.startYPos + ? { + x: item.startXPos, + y: item.startYPos, + } + : {} + ) + ); + } + }); + }); + }; + /** + * Apply the options containing the x and y data and possible some extra + * properties. Called on point init or from point.update. + * + * @private + * @function Highcharts.Point#applyOptions + * + * @param {Highcharts.PointOptionsType} options + * The point options as defined in series.data. + * + * @param {number} [x] + * Optionally, the x value. + * + * @return {Highcharts.Point} + * The Point instance. + */ + Point.prototype.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; + } + if (options.dataLabels) { + delete point.dataLabels; + } + /** + * The y value of the point. + * @name Highcharts.Point#y + * @type {number|undefined} + */ + // For higher dimension series types. For instance, for ranges, point.y + // is mapped to point.low. + if (pointValKey) { + point.y = Point.prototype.getNestedProperty.call( + point, + pointValKey + ); + } + point.isNull = pick( + point.isValid && !point.isValid(), + point.x === null || !isNumber(point.y) + ); // #3571, check for NaN + point.formatPrefix = point.isNull ? "null" : "point"; // #9233, #10874 + // The point is initially selected by options (#5777) + if (point.selected) { + point.state = "select"; + } + /** + * The x value of the point. + * @name Highcharts.Point#x + * @type {number} + */ + // 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 && + typeof x === "undefined" && + series.xAxis && + series.xAxis.hasNames + ) { + point.x = series.xAxis.nameToX(point); + } + if (typeof point.x === "undefined" && series) { + if (typeof x === "undefined") { + point.x = series.autoIncrement(point); + } else { + point.x = x; + } + } + return point; + }; /** - * These collections (arrays) implement `Chart.addSomethig` method used in - * chart.update() to create new object in the collection. Equivalent for - * deleting is resolved by simple `Somethig.remove()`. + * Destroy a point to clear memory. Its reference still stays in + * `series.data`. * - * Note: We need to define these references after initializers are bound to - * chart's prototype. + * @private + * @function Highcharts.Point#destroy */ - Chart.prototype.collectionsWithInit = { - // collectionName: [ initializingMethod, [extraArguments] ] - xAxis: [Chart.prototype.addAxis, [true]], - yAxis: [Chart.prototype.addAxis, [false]], - series: [Chart.prototype.addSeries] + Point.prototype.destroy = function () { + var point = this, + series = point.series, + chart = series.chart, + dataSorting = series.options.dataSorting, + hoverPoints = chart.hoverPoints, + globalAnimation = point.series.chart.renderer.globalAnimation, + animation = animObject(globalAnimation), + prop; + /** + * Allow to call after animation. + * @private + */ + function destroyPoint() { + // Remove all events and elements + if (point.graphic || point.dataLabel || point.dataLabels) { + removeEvent(point); + point.destroyElements(); + } + for (prop in point) { + // eslint-disable-line guard-for-in + point[prop] = null; + } + } + if (point.legendItem) { + // pies have legend items + chart.legend.destroyItem(point); + } + if (hoverPoints) { + point.setState(); + erase(hoverPoints, point); + if (!hoverPoints.length) { + chart.hoverPoints = null; + } + } + if (point === chart.hoverPoint) { + point.onMouseOut(); + } + // Remove properties after animation + if (!dataSorting || !dataSorting.enabled) { + destroyPoint(); + } else { + this.animateBeforeDestroy(); + syncTimeout(destroyPoint, animation.duration); + } + chart.pointCount--; }; - // 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. - * - * @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 - * - * @function Highcharts.Point#update - * - * @param {Highcharts.PointOptionsType} 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 marin 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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] - * Whether to apply animation, and optionally animation - * configuration. - * - * @return {void} - * - * @fires Highcharts.Point#event:update - */ - 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); - /** - * @private - */ - function update() { - point.applyOptions(options); - // Update visuals, #4146 - // Handle dummy graphic elements for a11y, #12718 - var hasDummyGraphic = graphic && point.hasDummyGraphic; - var shouldDestroyGraphic = point.y === null ? !hasDummyGraphic : hasDummyGraphic; - if (graphic && shouldDestroyGraphic) { - point.graphic = graphic.destroy(); - delete point.hasDummyGraphic; - } - 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 && - typeof options.marker.symbol !== 'undefined') { - point.graphic = graphic.destroy(); - } - } - if (options && options.dataLabels && point.dataLabel) { - point.dataLabel = point.dataLabel.destroy(); // #2468 - } - 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 : - pick(options, seriesOptions.data[i]); - // 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 - * - * @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 - * - * @function Highcharts.Point#remove - * - * @param {boolean} [redraw=true] - * 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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=false] - * Whether to apply animation, and optionally animation - * configuration. - * - * @return {void} - */ - remove: function (redraw, animation) { - this.series.removePoint(this.series.data.indexOf(this), redraw, animation); + /** + * Destroy SVG elements associated with the point. + * + * @private + * @function Highcharts.Point#destroyElements + * @param {Highcharts.Dictionary<number>} [kinds] + */ + Point.prototype.destroyElements = function (kinds) { + var point = this, + props = point.getGraphicalProps(kinds); + props.singular.forEach(function (prop) { + point[prop] = point[prop].destroy(); + }); + props.plural.forEach(function (plural) { + point[plural].forEach(function (item) { + if (item.element) { + item.destroy(); + } + }); + delete point[plural]; + }); + }; + /** + * Fire an event on the Point object. + * + * @private + * @function Highcharts.Point#firePointEvent + * + * @param {string} eventType + * Type of the event. + * + * @param {Highcharts.Dictionary<any>|Event} [eventArgs] + * Additional event arguments. + * + * @param {Highcharts.EventCallbackFunction<Highcharts.Point>|Function} [defaultFunction] + * Default event handler. + * + * @fires Highcharts.Point#event:* + */ + Point.prototype.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]) + ) { + point.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) { + // #2911 + point.select( + null, + event.ctrlKey || event.metaKey || event.shiftKey + ); + } + }; + } + fireEvent(point, eventType, eventArgs, defaultFunction); + }; + /** + * Get the CSS class names for individual points. Used internally where the + * returned value is set on every point. + * + * @function Highcharts.Point#getClassName + * + * @return {string} + * The class names. + */ + Point.prototype.getClassName = function () { + var point = this; + return ( + "highcharts-point" + + (point.selected ? " highcharts-point-select" : "") + + (point.negative ? " highcharts-negative" : "") + + (point.isNull ? " highcharts-null-point" : "") + + (typeof point.colorIndex !== "undefined" + ? " highcharts-color-" + point.colorIndex + : "") + + (point.options.className ? " " + point.options.className : "") + + (point.zone && point.zone.className + ? " " + point.zone.className.replace("highcharts-negative", "") + : "") + ); + }; + /** + * Get props of all existing graphical point elements. + * + * @private + * @function Highcharts.Point#getGraphicalProps + * @param {Highcharts.Dictionary<number>} [kinds] + * @return {Highcharts.PointGraphicalProps} + */ + Point.prototype.getGraphicalProps = function (kinds) { + var point = this, + props = [], + prop, + i, + graphicalProps = { singular: [], plural: [] }; + kinds = kinds || { graphic: 1, dataLabel: 1 }; + if (kinds.graphic) { + props.push("graphic", "shadowGroup"); + } + if (kinds.dataLabel) { + props.push("dataLabel", "dataLabelUpper", "connector"); + } + i = props.length; + while (i--) { + prop = props[i]; + if (point[prop]) { + graphicalProps.singular.push(prop); } - }); - // Extend the series prototype for dynamic methods - extend(LineSeries.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. - * - * @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 - * - * @function Highcharts.Series#addPoint - * - * @param {Highcharts.PointOptionsType} 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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] - * Whether to apply animation, and optionally animation - * configuration. - * - * @param {boolean} [withEvent=true] - * Used internally, whether to fire the series `addPoint` event. - * - * @return {void} - * - * @fires Highcharts.Series#event:addPoint - */ - addPoint: function (options, redraw, shift, animation, withEvent) { - 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, - xData = series.xData, - isInTheMiddle, - 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--; - } - } - // Insert undefined item - series.updateParallelArrays(point, 'splice', i, 0, 0); - // Update it - series.updateParallelArrays(point, i); - 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(); - } - } - // Fire event - if (withEvent !== false) { - fireEvent(series, 'addPoint', { point: point }); - } - // 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. - * - * @sample highcharts/members/series-removepoint/ - * Remove cropped point - * - * @function Highcharts.Series#removePoint - * - * @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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] - * Whether and optionally how the series should be animated. - * - * @return {void} - * - * @fires Highcharts.Point#event:remove - */ - 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. - * - * @sample highcharts/members/series-remove/ - * Remove first series from a button - * - * @function Highcharts.Series#remove - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart or wait for an explicit call to - * {@link Highcharts.Chart#redraw}. - * - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] - * Whether to apply animation, and optionally animation - * configuration. - * - * @param {boolean} [withEvent=true] - * Used internally, whether to fire the series `remove` event. - * - * @return {void} - * - * @fires Highcharts.Series#event:remove - */ - remove: function (redraw, animation, withEvent, keepEvents) { - var series = this, - chart = series.chart; - /** - * @private - */ - function remove() { - // Destroy elements - series.destroy(keepEvents); - series.remove = null; // Prevent from doing again (#9097) - // 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 initialized from scratch. Therefore, this method is - * more performance expensive than some other utility methods like {@link - * Series#setData} or {@link Series#setVisible}. - * - * Note that `Series.update` may mutate the passed `data` options. - * - * @sample highcharts/members/series-update/ - * Updating series options - * @sample maps/members/series-update/ - * Update series options in Highmaps - * - * @function Highcharts.Series#update - * - * @param {Highcharts.SeriesOptionsType} 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. - * - * @return {void} - * - * @fires Highcharts.Series#event:update - * @fires Highcharts.Series#event:afterUpdate - */ - update: function (options, redraw) { - options = H.cleanRecursively(options, this.userOptions); - fireEvent(this, 'update', { options: options }); - 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, - seriesOptions, - initialType = series.initialType || series.type, - plotOptions = chart.options.plotOptions, - newType = (options.type || - oldOptions.type || - chart.options.chart.type), - keepPoints = !( - // Indicators, histograms etc recalculate the data. It should be - // possible to omit this. - this.hasDerivedData || - // New type requires new point classes - (newType && newType !== this.type) || - // New options affecting how the data points are built - typeof options.pointStart !== 'undefined' || - typeof options.pointInterval !== 'undefined' || - // Changes to data grouping requires new points in new group - series.hasOptionChanged('dataGrouping') || - series.hasOptionChanged('pointStart') || - series.hasOptionChanged('pointInterval') || - series.hasOptionChanged('pointIntervalUnit') || - series.hasOptionChanged('keys')), - initialSeriesProto = seriesTypes[initialType].prototype, - n, - groups = [ - 'group', - 'markerGroup', - 'dataLabelsGroup', - 'transformGroup' - ], - preserve = [ - 'eventOptions', - '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 }, - kinds = {}; - if (keepPoints) { - preserve.push('data', 'isDirtyData', 'points', 'processedXData', 'processedYData', 'xIncrement', 'cropped', '_hasPointMarkers', '_hasPointLabels', - // Map specific, consider moving it to series-specific preserve- - // properties (#10617) - 'mapMap', 'mapData', 'minY', 'maxY', 'minX', 'maxX'); - if (options.visible !== false) { - preserve.push('area', 'graph'); - } - series.parallelArrays.forEach(function (key) { - preserve.push(key + 'Data'); - }); - if (options.data) { - // setData uses dataSorting options so we need to update them - // earlier - if (options.dataSorting) { - extend(series.options.dataSorting, options.dataSorting); - } - this.setData(options.data, false); - } - } - // Do the merge, with some forced options - options = merge(oldOptions, animation, { - // When oldOptions.index is null it should't be cleared. - // Otherwise navigator series will have wrong indexes (#10193). - index: typeof oldOptions.index === 'undefined' ? - series.index : oldOptions.index, - pointStart: pick( - // when updating from blank (#7933) - plotOptions && plotOptions.series && plotOptions.series.pointStart, oldOptions.pointStart, - // when updating after addPoint - series.xData[0]) - }, (!keepPoints && { data: series.options.data }), options); - // Merge does not merge arrays, but replaces them. Since points were - // updated, `series.options.data` has correct merged options, use it: - if (keepPoints && options.data) { - options.data = series.options.data; - } - // Make sure preserved properties are not destroyed (#3094) - preserve = groups.concat(preserve); - preserve.forEach(function (prop) { - preserve[prop] = series[prop]; - delete series[prop]; - }); - // Destroy the series and delete all properties. Reinsert all - // methods and properties from the new type prototype (#2270, - // #3719). - series.remove(false, null, false, true); - for (n in initialSeriesProto) { // eslint-disable-line guard-for-in - series[n] = void 0; - } - if (seriesTypes[newType || initialType]) { - extend(series, seriesTypes[newType || initialType].prototype); - } - else { - error(17, true, chart, { missingModuleFor: (newType || initialType) }); - } - // Re-register groups (#3094) and other preserved properties - preserve.forEach(function (prop) { - series[prop] = preserve[prop]; - }); - series.init(chart, options); - // Remove particular elements of the points. Check `series.options` - // because we need to consider the options being set on plotOptions as - // well. - if (keepPoints && this.points) { - seriesOptions = series.options; - // What kind of elements to destroy - if (seriesOptions.visible === false) { - kinds.graphic = 1; - kinds.dataLabel = 1; - } - else if (!series._hasPointLabels) { - var marker = seriesOptions.marker, - dataLabels = seriesOptions.dataLabels; - if (marker && (marker.enabled === false || - 'symbol' in marker // #10870 - )) { - kinds.graphic = 1; - } - if (dataLabels && - dataLabels.enabled === false) { - kinds.dataLabel = 1; - } - } - this.points.forEach(function (point) { - if (point && point.series) { - point.resolveColor(); - // Destroy elements in order to recreate based on updated - // series options. - if (Object.keys(kinds).length) { - point.destroyElements(kinds); - } - if (seriesOptions.showInLegend === false && - point.legendItem) { - chart.legend.destroyItem(point); - } - } - }, this); - } - series.initialType = initialType; - chart.linkSeries(); // Links are lost in series.remove (#3028) - fireEvent(this, 'afterUpdate'); - if (pick(redraw, true)) { - chart.redraw(keepPoints ? void 0 : false); - } - }, - /** - * Used from within series.update - * - * @private - * @function Highcharts.Series#setName - * - * @param {string} name - * - * @return {void} - */ - setName: function (name) { - this.name = this.options.name = this.userOptions.name = name; - this.chart.isDirtyLegend = true; - }, - /** - * Check if the option has changed. - * - * @private - * @function Highcharts.Series#hasOptionChanged - * - * @param {string} option - * - * @return {boolean} - */ - hasOptionChanged: function (optionName) { - var chart = this.chart, - option = this.options[optionName], - plotOptions = chart.options.plotOptions, - oldOption = this.userOptions[optionName]; - if (oldOption) { - return option !== oldOption; - } - return option !== - pick(plotOptions && plotOptions[this.type] && plotOptions[this.type][optionName], plotOptions && plotOptions.series && plotOptions.series[optionName], option); + } + ["dataLabel", "connector"].forEach(function (prop) { + var plural = prop + "s"; + if (kinds[prop] && point[plural]) { + graphicalProps.plural.push(plural); } - }); - // 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. - * - * @sample highcharts/members/axis-update/ - * Axis update demo - * - * @function Highcharts.Axis#update - * - * @param {Highcharts.AxisOptions} options - * The new options that will be merged in with existing options on - * the axis. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart after the axis 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. - * - * @return {void} - */ - update: function (options, redraw) { - var chart = this.chart, - newEvents = ((options && options.events) || {}); - options = merge(this.userOptions, options); - // Color Axis is not an array, - // This change is applied in the ColorAxis wrapper - if (chart.options[this.coll].indexOf) { - // Don't use this.options.index, - // StockChart has Axes in navigator too - chart.options[this.coll][chart.options[this.coll].indexOf(this.userOptions)] = options; - } - // Remove old events, if no new exist (#8161) - objectEach(chart.options[this.coll].events, function (fn, ev) { - if (typeof newEvents[ev] === 'undefined') { - newEvents[ev] = void 0; - } - }); - this.destroy(true); - this.init(chart, extend(options, { events: newEvents })); - chart.isDirtyBox = true; - if (pick(redraw, true)) { - chart.redraw(); - } - }, - /** - * Remove the axis from the chart. - * - * @sample highcharts/members/chart-addaxis/ - * Add and remove axes - * - * @function Highcharts.Axis#remove - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart following the remove. - * - * @return {void} - */ - 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]; - } - chart[key].forEach(function (axis, i) { - // Re-index, #1706, #8075 - axis.options.index = axis.userOptions.index = i; - }); - this.destroy(); - chart.isDirtyBox = true; - if (pick(redraw, true)) { - chart.redraw(); - } - }, - /** - * Update the axis title by options after render time. - * - * @sample highcharts/members/axis-settitle/ - * Set a new Y axis title - * - * @function Highcharts.Axis#setTitle - * - * @param {Highcharts.AxisTitleOptions} titleOptions - * The additional title options. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart after setting the title. - * - * @return {void} - */ - setTitle: function (titleOptions, redraw) { - this.update({ title: titleOptions }, redraw); - }, - /** - * Set new axis categories and optionally redraw. - * - * @sample highcharts/members/axis-setcategories/ - * Set categories by click on a button - * - * @function Highcharts.Axis#setCategories - * - * @param {Array<string>} categories - * The new categories. - * - * @param {boolean} [redraw=true] - * Whether to redraw the chart. - * - * @return {void} - */ - setCategories: function (categories, redraw) { - this.update({ categories: categories }, redraw); + }); + return graphicalProps; + }; + /** + * Return the configuration hash needed for the data label and tooltip + * formatters. + * + * @function Highcharts.Point#getLabelConfig + * + * @return {Highcharts.PointLabelObject} + * Abstract object used in formatters and formats. + */ + Point.prototype.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, + }; + }; + /** + * Returns the value of the point property for a given value. + * @private + */ + Point.prototype.getNestedProperty = function (key) { + if (!key) { + return; + } + if (key.indexOf("custom.") === 0) { + return getNestedProperty(key, this.options); + } + return this[key]; + }; + /** + * In a series with `zones`, return the zone that the point belongs to. + * + * @function Highcharts.Point#getZone + * + * @return {Highcharts.SeriesZonesOptionsObject} + * The zone item. + */ + Point.prototype.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]; + } + // For resetting or reusing the point (#8100) + if (!this.nonZonedColor) { + this.nonZonedColor = this.color; + } + if (zone && zone.color && !this.options.color) { + this.color = zone.color; + } else { + this.color = this.nonZonedColor; + } + return zone; + }; + /** + * Utility to check if point has new shape type. Used in column series and + * all others that are based on column series. + * + * @return boolean|undefined + */ + Point.prototype.hasNewShapeType = function () { + var point = this; + var oldShapeType = + point.graphic && + (point.graphic.symbolName || point.graphic.element.nodeName); + return oldShapeType !== this.shapeType; + }; + /** + * Initialize the point. Called internally based on the `series.data` + * option. + * + * @function Highcharts.Point#init + * + * @param {Highcharts.Series} series + * The series object containing this point. + * + * @param {Highcharts.PointOptionsType} options + * The data in either number, array or object format. + * + * @param {number} [x] + * Optionally, the X value of the point. + * + * @return {Highcharts.Point} + * The Point instance. + * + * @fires Highcharts.Point#event:afterInit + */ + Point.prototype.init = function (series, options, x) { + this.series = series; + this.applyOptions(options, x); + // Add a unique ID to the point if none is assigned + this.id = defined(this.id) ? this.id : uniqueKey(); + this.resolveColor(); + series.chart.pointCount++; + fireEvent(this, "afterInit"); + return this; + }; + /** + * Transform number or array configs into objects. Also called for object + * configs. 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 }`. + * + * @function Highcharts.Point#optionsToObject + * + * @param {Highcharts.PointOptionsType} options + * The input option. + * + * @return {Highcharts.Dictionary<*>} + * Transformed options. + */ + Point.prototype.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) { + // Skip undefined positions for keys + if (!keys || typeof options[i] !== "undefined") { + if (pointArrayMap[j].indexOf(".") > 0) { + // Handle nested keys, e.g. ['color.pattern.image'] + // Avoid function call unless necessary. + Point.prototype.setNestedProperty( + ret, + options[i], + pointArrayMap[j] + ); + } else { + 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; + }; + /** + * @private + * @function Highcharts.Point#resolveColor + * @return {void} + */ + Point.prototype.resolveColor = function () { + var series = this.series, + colors, + optionsChart = series.chart.options.chart, + colorCount = optionsChart.colorCount, + styledMode = series.chart.styledMode, + colorIndex; + // remove points nonZonedColor for later recalculation + delete this.nonZonedColor; + /** + * The point's current color. + * + * @name Highcharts.Point#color + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} + */ + if (!styledMode && !this.options.color) { + this.color = series.color; // #3445 + } + if (series.options.colorByPoint) { + if (!styledMode) { + colors = series.options.colors || series.chart.options.colors; + this.color = this.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; + } + this.colorIndex = pick(this.colorIndex, colorIndex); + }; + /** + * Set a value in an object, on the property defined by key. The key + * supports nested properties using dot notation. The function modifies the + * input object and does not make a copy. + * + * @function Highcharts.Point#setNestedProperty<T> + * + * @param {T} object + * The object to set the value on. + * + * @param {*} value + * The value to set. + * + * @param {string} key + * Key to the property to set. + * + * @return {T} + * The modified object. + */ + Point.prototype.setNestedProperty = function (object, value, key) { + var nestedKeys = key.split("."); + nestedKeys.reduce(function (result, key, i, arr) { + var isLastKey = arr.length - 1 === i; + result[key] = isLastKey + ? value + : isObject(result[key], true) + ? result[key] + : {}; + return result[key]; + }, object); + return object; + }; + /** + * Extendable method for formatting each point's tooltip line. + * + * @function Highcharts.Point#tooltipFormatter + * + * @param {string} pointFormat + * The point format. + * + * @return {string} + * A string to be concatenated in to the common tooltip text. + */ + Point.prototype.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 || ""; + // Replace default point style with class name + if (series.chart.styledMode) { + pointFormat = series.chart.tooltip.styledModeFormat(pointFormat); + } + // Loop over the point array map and replace unformatted values with + // sprintf formatting markup + (series.pointArrayMap || ["y"]).forEach(function (key) { + key = "{point." + key; // without the closing bracket + if (valuePrefix || valueSuffix) { + pointFormat = pointFormat.replace( + RegExp(key + "}", "g"), + valuePrefix + key + "}" + valueSuffix + ); } - }); + pointFormat = pointFormat.replace( + RegExp(key + "}", "g"), + key + ":,." + valueDecimals + "f}" + ); + }); + return format( + pointFormat, + { + point: this, + series: this.series, + }, + series.chart + ); + }; + return Point; + })(); + H.Point = Point; - }); - _registerModule(_modules, 'Series/AreaSeries.js', [_modules['Core/Series/Series.js'], _modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/Utilities.js']], function (BaseSeries, Color, H, LegendSymbolMixin, U) { + return Point; + } + ); + _registerModule( + _modules, + "Core/Series/Series.js", + [ + _modules["Core/Globals.js"], + _modules["Core/Series/Point.js"], + _modules["Core/Utilities.js"], + ], + function (H, Point, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var error = U.error, + extendClass = U.extendClass, + fireEvent = U.fireEvent, + getOptions = U.getOptions, + isObject = U.isObject, + merge = U.merge, + objectEach = U.objectEach; + /** + * @class + * @name Highcharts.Series + */ + var BaseSeries = /** @class */ (function () { /* * * - * (c) 2010-2020 Torstein Honsi + * Constructor * - * License: www.highcharts.com/license + * */ + function BaseSeries(chart, options) { + var mergedOptions = merge(BaseSeries.defaultOptions, options); + this.chart = chart; + this._i = chart.series.length; + chart.series.push(this); + this.options = mergedOptions; + this.userOptions = merge(options); + } + /* * * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * Static Functions * * */ - var color = Color.parse; - var objectEach = U.objectEach, - pick = U.pick; - var Series = H.Series; + BaseSeries.addSeries = function (seriesName, seriesType) { + BaseSeries.seriesTypes[seriesName] = seriesType; + }; + BaseSeries.cleanRecursively = function (toClean, reference) { + var clean = {}; + objectEach(toClean, function (_val, key) { + var ob; + // Dive into objects (except DOM nodes) + if ( + isObject(toClean[key], true) && + !toClean.nodeType && // #10044 + reference[key] + ) { + ob = BaseSeries.cleanRecursively(toClean[key], reference[key]); + if (Object.keys(ob).length) { + clean[key] = ob; + } + // Arrays, primitives and DOM nodes are copied directly + } else if ( + isObject(toClean[key]) || + toClean[key] !== reference[key] + ) { + clean[key] = toClean[key]; + } + }); + return clean; + }; + // eslint-disable-next-line valid-jsdoc /** - * Area series type. - * + * Internal function to initialize an individual series. * @private - * @class - * @name Highcharts.seriesTypes.area - * - * @augments Highcharts.Series */ - BaseSeries.seriesType('area', 'line', + BaseSeries.getSeries = function (chart, options) { + if (options === void 0) { + options = {}; + } + var optionsChart = chart.options.chart, + type = + options.type || + optionsChart.type || + optionsChart.defaultSeriesType || + "", + Series = BaseSeries.seriesTypes[type]; + // No such series type + if (!Series) { + error(17, true, chart, { missingModuleFor: type }); + } + return new Series(chart, options); + }; /** - * The area series type. - * - * @sample {highcharts} highcharts/demo/area-basic/ - * Area chart - * @sample {highstock} stock/demo/area/ - * Area chart + * Factory to create new series prototypes. * - * @extends plotOptions.line - * @excluding useOhlcData - * @product highcharts highstock - * @optionparent plotOptions.area - */ - { - /** - * @see [fillColor](#plotOptions.area.fillColor) - * @see [fillOpacity](#plotOptions.area.fillOpacity) - * - * @apioption plotOptions.area.color - */ - /** - * Fill color or gradient for the area. When `null`, the series' `color` - * is used with the series' `fillOpacity`. - * - * In styled mode, the fill color can be set with the `.highcharts-area` - * class name. - * - * @see [color](#plotOptions.area.color) - * @see [fillOpacity](#plotOptions.area.fillOpacity) - * - * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/ - * Null by default - * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/ - * Gradient - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @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. - * - * In styled mode, the fill opacity can be set with the - * `.highcharts-area` class name. - * - * @see [color](#plotOptions.area.color) - * @see [fillColor](#plotOptions.area.fillColor) - * - * @sample {highcharts} highcharts/plotoptions/area-fillopacity/ - * Automatic fill color and fill opacity of 0.1 - * - * @type {number} - * @default {highcharts} 0.75 - * @default {highstock} 0.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`. - * - * In styled mode, the line stroke can be set with the - * `.highcharts-graph` class name. - * - * @sample {highcharts} highcharts/plotoptions/area-linecolor/ - * Dark gray line - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @product highcharts highstock - * @apioption plotOptions.area.lineColor - */ - /** - * A separate color for the negative part of the area. - * - * In styled mode, a negative color is set with the - * `.highcharts-negative` class name. - * - * @see [negativeColor](#plotOptions.area.negativeColor) - * - * @sample {highcharts} highcharts/css/series-negative-color/ - * Negative color in styled mode - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @since 3.0 - * @product highcharts - * @apioption plotOptions.area.negativeFillColor - */ - /** - * Whether the whole area or just the line should respond to mouseover - * tooltips and other mouse or touch events. - * - * @sample {highcharts|highstock} highcharts/plotoptions/area-trackbyarea/ - * Display the tooltip when the area is hovered - * - * @type {boolean} - * @default false - * @since 1.1.6 - * @product highcharts highstock - * @apioption plotOptions.area.trackByArea - */ - /** - * The Y axis value to serve as the base for the area, for - * distinguishing between values above and below a threshold. The area - * between the graph and the threshold is filled. - * - * * If a number is given, the Y axis will scale to the threshold. - * * If `null`, the scaling behaves like a line series with fill between - * the graph and the Y axis minimum. - * * If `Infinity` or `-Infinity`, the area between the graph and the - * corresponding Y axis extreme is filled (since v6.1.0). - * - * @sample {highcharts} highcharts/plotoptions/area-threshold/ - * A threshold of 100 - * @sample {highcharts} highcharts/plotoptions/area-threshold-infinity/ - * A threshold of Infinity - * - * @type {number|null} - * @since 2.0 - * @product highcharts highstock - */ - threshold: 0 - }, - /* eslint-disable valid-jsdoc */ - /** - * @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. - * @private - */ - getStackPoints: function (points) { - var series = this, - segment = [], - keys = [], - xAxis = this.xAxis, - yAxis = this.yAxis, - stack = yAxis.stacking.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 = void 0; - // 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) - objectEach(stack, function (stackX, x) { - // nulled after switching between - // grouping and not (#1651, #2336) - if (stackX.total !== null) { - keys.push(x); - } - }); - keys.sort(function (a, b) { - return a - b; - }); - visibleSeries = yAxisSeries.map(function (s) { - return s.visible; - }); - keys.forEach(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. - [-1, 1].forEach(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; - // Can go either up or down, - // depending on reversedStacks - while (i >= 0 && i < seriesLength) { - 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(// #6272 - y, 0, 1, 0, 1); - segment.push({ - isNull: true, - plotX: xAxis.translate(// #6272 - x, 0, 0, 0, 1), - x: x, - plotY: y, - yBottom: y - }); - } - }); - } - return segment; - }, - /** - * @private - */ - 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.stacking.stacks[this.stackKey], threshold = options.threshold, translatedThreshold = Math.round(// #10909 - yAxis.getThreshold(options.threshold)), isNull, yBottom, connectNulls = pick(// #10574 - 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 (typeof 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++) { - // Reset after series.update of stacking property (#12033) - if (!stacking) { - points[i].leftCliff = points[i].rightCliff = - points[i].leftNull = points[i].rightNull = void 0; - } - isNull = points[i].isNull; - plotX = pick(points[i].rectPlotX, points[i].plotX); - yBottom = stacking ? points[i].yBottom : translatedThreshold; - if (!isNull || connectNulls) { - if (!connectNulls) { - addDummyPoints(i, i - 1, 'left'); - } - // Skip null point when stacking is false and connectNulls - // true - if (!(isNull && !stacking && connectNulls)) { - 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); - var firstBottomPoint = bottomPath[0]; - if (firstBottomPoint && firstBottomPoint[0] === 'M') { - bottomPath[0] = ['L', firstBottomPoint[1], firstBottomPoint[2]]; - } - areaPath = topPath.concat(bottomPath); - // TODO: don't set leftCliff and rightCliff when connectNulls? - graphPath = getGraphPath - .call(this, graphPoints, false, 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. - * @private - */ - 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 - zones.forEach(function (zone, - i) { - props.push([ - 'zone-area-' + i, - 'highcharts-area highcharts-zone-area-' + i + ' ' + - zone.className, - zone.color || series.color, - zone.fillColor || options.fillColor - ]); - }); - props.forEach(function (prop) { - var areaKey = prop[0], - area = series[areaKey], - verb = area ? 'animate' : 'attr', - attribs = {}; - // Create or update the area - if (area) { // update - area.endX = series.preventGraphAnimation ? - null : - areaPath.xMap; - area.animate({ d: areaPath }); - } - else { // create - attribs.zIndex = 0; // #1069 - area = series[areaKey] = series.chart.renderer - .path(areaPath) - .addClass(prop[1]) - .add(series.group); - area.isArea = true; - } - if (!series.chart.styledMode) { - attribs.fill = pick(prop[3], color(prop[2]) - .setOpacity(pick(options.fillOpacity, 0.75)) - .get()); - } - area[verb](attribs); - area.startX = areaPath.xMap; - area.shiftUnit = options.step ? 2 : 1; - }); - }, - drawLegendSymbol: LegendSymbolMixin.drawRectangle - }); - /* eslint-enable valid-jsdoc */ - /** - * A `area` series. If the [type](#series.area.type) option is not - * specified, it is inherited from [chart.type](#chart.type). + * @function Highcharts.seriesType * - * @extends series,plotOptions.area - * @excluding dataParser, dataURL, useOhlcData - * @product highcharts highstock - * @apioption series.area - */ - /** - * @see [fillColor](#series.area.fillColor) - * @see [fillOpacity](#series.area.fillOpacity) - * - * @apioption series.area.color - */ - /** - * 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 following snippet shows only a - * few settings, see the complete options set 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" - * }] - * ``` - * - * @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 - * - * @type {Array<number|Array<(number|string),(number|null)>|null|*>} - * @extends series.line.data - * @product highcharts highstock - * @apioption series.area.data - */ - /** - * @see [color](#series.area.color) - * @see [fillOpacity](#series.area.fillOpacity) + * @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 {Highcharts.SeriesOptionsType|Highcharts.Dictionary<*>} options + * The additional default options that are merged with the parent's options. + * + * @param {Highcharts.Dictionary<*>} [props] + * The properties (functions and primitives) to set on the new prototype. + * + * @param {Highcharts.Dictionary<*>} [pointProps] + * Members for a series-specific extension of the {@link Point} prototype if + * needed. + * + * @return {Highcharts.Series} + * The newly created prototype as extended from {@link Series} or its + * derivatives. + */ + // docs: add to API + extending Highcharts + BaseSeries.seriesType = function ( + type, + parent, + options, + seriesProto, + pointProto + ) { + var defaultOptions = getOptions().plotOptions || {}, + seriesTypes = BaseSeries.seriesTypes; + parent = parent || ""; + // Merge the options + defaultOptions[type] = merge(defaultOptions[parent], options); + // Create the class + BaseSeries.addSeries( + type, + extendClass(seriesTypes[parent] || function () {}, seriesProto) + ); + seriesTypes[type].prototype.type = type; + // Create the point class if needed + if (pointProto) { + seriesTypes[type].prototype.pointClass = extendClass( + Point, + pointProto + ); + } + return seriesTypes[type]; + }; + BaseSeries.prototype.update = function (newOptions, redraw) { + if (redraw === void 0) { + redraw = true; + } + var series = this; + newOptions = BaseSeries.cleanRecursively( + newOptions, + this.userOptions + ); + var newType = newOptions.type; + if (typeof newType !== "undefined" && newType !== series.type) { + series = BaseSeries.getSeries(series.chart, newOptions); + } + fireEvent(series, "update", { newOptions: newOptions }); + series.userOptions = merge(newOptions); + fireEvent(series, "afterUpdate", { newOptions: newOptions }); + if (redraw) { + series.chart.redraw(); + } + return series; + }; + /* * * - * @apioption series.area.fillColor - */ - /** - * @see [color](#series.area.color) - * @see [fillColor](#series.area.fillColor) + * Static Properties * - * @default {highcharts} 0.75 - * @default {highstock} 0.75 - * @apioption series.area.fillOpacity - */ - ''; // adds doclets above to transpilat + * */ + BaseSeries.defaultOptions = { + type: "base", + }; + BaseSeries.seriesTypes = {}; + return BaseSeries; + })(); + BaseSeries.prototype.pointClass = Point; + // backwards compatibility + H.seriesType = BaseSeries.seriesType; + H.seriesTypes = BaseSeries.seriesTypes; + /* * + * + * Export + * + * */ - }); - _registerModule(_modules, 'Series/SplineSeries.js', [_modules['Core/Series/Series.js'], _modules['Core/Utilities.js']], function (BaseSeries, U) { + return BaseSeries; + } + ); + _registerModule( + _modules, + "Core/Chart/Chart.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Axis/Axis.js"], + _modules["Core/Series/Series.js"], + _modules["Core/Globals.js"], + _modules["Core/Legend.js"], + _modules["Core/MSPointer.js"], + _modules["Core/Options.js"], + _modules["Core/Pointer.js"], + _modules["Core/Time.js"], + _modules["Core/Utilities.js"], + ], + function (A, Axis, BaseSeries, H, Legend, MSPointer, O, Pointer, Time, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var animate = A.animate, + animObject = A.animObject, + setAnimation = A.setAnimation; + var charts = H.charts, + doc = H.doc, + win = H.win; + var defaultOptions = O.defaultOptions; + var addEvent = U.addEvent, + attr = U.attr, + createElement = U.createElement, + css = U.css, + defined = U.defined, + discardElement = U.discardElement, + erase = U.erase, + error = U.error, + extend = U.extend, + find = U.find, + fireEvent = U.fireEvent, + getStyle = U.getStyle, + isArray = U.isArray, + isFunction = U.isFunction, + isNumber = U.isNumber, + isObject = U.isObject, + isString = U.isString, + merge = U.merge, + numberFormat = U.numberFormat, + objectEach = U.objectEach, + pick = U.pick, + pInt = U.pInt, + relativeLength = U.relativeLength, + removeEvent = U.removeEvent, + splat = U.splat, + syncTimeout = U.syncTimeout, + uniqueKey = U.uniqueKey; + /** + * Callback for chart constructors. + * + * @callback Highcharts.ChartCallbackFunction + * + * @param {Highcharts.Chart} chart + * Created chart. + */ + /** + * Format a number and return a string based on input settings. + * + * @callback Highcharts.NumberFormatterCallbackFunction + * + * @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. + * + * @return {string} The formatted number. + */ + /** + * The chart title. The title has an `update` method that allows modifying the + * options directly or indirectly via `chart.update`. + * + * @interface Highcharts.TitleObject + * @extends Highcharts.SVGElement + */ /** + * Modify options for the title. + * + * @function Highcharts.TitleObject#update + * + * @param {Highcharts.TitleOptions} titleOptions + * Options to modify. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart after the title 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. + */ + /** + * The chart subtitle. The subtitle has an `update` method that + * allows modifying the options directly or indirectly via + * `chart.update`. + * + * @interface Highcharts.SubtitleObject + * @extends Highcharts.SVGElement + */ /** + * Modify options for the subtitle. + * + * @function Highcharts.SubtitleObject#update + * + * @param {Highcharts.SubtitleOptions} subtitleOptions + * Options to modify. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart after the subtitle 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. + */ + /** + * The chart caption. The caption has an `update` method that + * allows modifying the options directly or indirectly via + * `chart.update`. + * + * @interface Highcharts.CaptionObject + * @extends Highcharts.SVGElement + */ /** + * Modify options for the caption. + * + * @function Highcharts.CaptionObject#update + * + * @param {Highcharts.CaptionOptions} captionOptions + * Options to modify. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart after the caption 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. + */ + var marginNames = H.marginNames; + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * The Chart class. The recommended constructor is {@link Highcharts#chart}. + * + * @example + * var chart = Highcharts.chart('container', { + * title: { + * text: 'My chart' + * }, + * series: [{ + * data: [1, 3, 2, 4] + * }] + * }) + * + * @class + * @name Highcharts.Chart + * + * @param {string|Highcharts.HTMLDOMElement} [renderTo] + * The DOM element to render to, or its id. + * + * @param {Highcharts.Options} options + * The chart options structure. + * + * @param {Highcharts.ChartCallbackFunction} [callback] + * Function to run when the chart has loaded and and all external images + * are loaded. Defining a + * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) + * handler is equivalent. + */ + var Chart = /** @class */ (function () { + function Chart(a, b, c) { + this.axes = void 0; + this.axisOffset = void 0; + this.bounds = void 0; + this.chartHeight = void 0; + this.chartWidth = void 0; + this.clipBox = void 0; + this.colorCounter = void 0; + this.container = void 0; + this.index = void 0; + this.isResizing = void 0; + this.labelCollectors = void 0; + this.legend = void 0; + this.margin = void 0; + this.numberFormatter = void 0; + this.options = void 0; + this.plotBox = void 0; + this.plotHeight = void 0; + this.plotLeft = void 0; + this.plotTop = void 0; + this.plotWidth = void 0; + this.pointCount = void 0; + this.pointer = void 0; + this.renderer = void 0; + this.renderTo = void 0; + this.series = void 0; + this.spacing = void 0; + this.spacingBox = void 0; + this.symbolCounter = void 0; + this.time = void 0; + this.titleOffset = void 0; + this.userOptions = void 0; + this.xAxis = void 0; + this.yAxis = void 0; + this.getArgs(a, b, c); + } /* * * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * Functions * * */ - var pick = U.pick; /** - * Spline series type. + * Handle the arguments passed to the constructor. * * @private - * @class - * @name Highcharts.seriesTypes.spline - * - * @augments Highcarts.Series - */ - BaseSeries.seriesType('spline', 'line', + * @function Highcharts.Chart#getArgs + * + * @param {...Array<*>} arguments + * All arguments for the constructor. + * + * @fires Highcharts.Chart#event:init + * @fires Highcharts.Chart#event:afterInit + */ + Chart.prototype.getArgs = function (a, b, c) { + // Remove the optional first argument, renderTo, and + // set it on this. + if (isString(a) || a.nodeName) { + this.renderTo = a; + this.init(b, c); + } else { + this.init(a, b); + } + }; /** - * A spline series is a special type of line series, where the segments - * between the data points are smoothed. + * Overridable function that initializes the chart. The constructor's + * arguments are passed on directly. * - * @sample {highcharts} highcharts/demo/spline-irregular-time/ - * Spline chart - * @sample {highstock} stock/demo/spline/ - * Spline chart + * @function Highcharts.Chart#init * - * @extends plotOptions.series - * @excluding step, boostThreshold, boostBlending - * @product highcharts highstock - * @optionparent plotOptions.spline - */ - {}, - /** - * @lends seriesTypes.spline.prototype - */ - { - /* eslint-disable valid-jsdoc */ + * @param {Highcharts.Options} userOptions + * Custom options. + * + * @param {Function} [callback] + * Function to run when the chart has loaded and and all external + * images are loaded. + * + * @return {void} + * + * @fires Highcharts.Chart#event:init + * @fires Highcharts.Chart#event:afterInit + */ + Chart.prototype.init = function (userOptions, callback) { + // Handle regular options + var options, + // skip merging data points to increase performance + seriesOptions = userOptions.series, + userPlotOptions = userOptions.plotOptions || {}; + // Fire the event with a default function + fireEvent(this, "init", { args: arguments }, function () { + userOptions.series = null; + options = merge(defaultOptions, userOptions); // do the merge + var optionsChart = options.chart || {}; + // Override (by copy of user options) or clear tooltip options + // in chart.options.plotOptions (#6218) + objectEach(options.plotOptions, function (typeOptions, type) { + if (isObject(typeOptions)) { + // #8766 + typeOptions.tooltip = + (userPlotOptions[type] && // override by copy: + merge(userPlotOptions[type].tooltip)) || + void 0; // 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; + // set back the series data + options.series = userOptions.series = seriesOptions; /** - * Get the spline segment from a given point's previous neighbour to the - * given point. + * The original options given to the constructor or a chart factory + * like {@link Highcharts.chart} and {@link Highcharts.stockChart}. * - * @private - * @function Highcharts.seriesTypes.spline#getPointSpline + * @name Highcharts.Chart#userOptions + * @type {Highcharts.Options} + */ + this.userOptions = userOptions; + var chartEvents = optionsChart.events; + this.margin = []; + this.spacing = []; + // Pixel data bounds for touch zoom + this.bounds = { h: {}, v: {} }; + // 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 after merging + * {@link #defaultOptions} and {@link #userOptions}. It contains + * members for the sub elements like series, legend, tooltip etc. + * + * @name Highcharts.Chart#options + * @type {Highcharts.Options} + */ + this.options = options; + /** + * All the axes in the chart. * - * @param {Array<Highcharts.Point>} + * @see Highcharts.Chart.xAxis + * @see Highcharts.Chart.yAxis * - * @param {Highcharts.Point} point + * @name Highcharts.Chart#axes + * @type {Array<Highcharts.Axis>} + */ + this.axes = []; + /** + * All the current series in the chart. * - * @param {number} i + * @name Highcharts.Chart#series + * @type {Array<Highcharts.Series>} + */ + this.series = []; + /** + * 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. * - * @return {Highcharts.SVGPathArray} + * @name Highcharts.Chart#time + * @type {Highcharts.Time} */ - 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 || 0, - plotY = point.plotY || 0, - lastPoint = points[i - 1], - nextPoint = points[i + 1], - leftContX, - leftContY, - rightContX, - rightContY, - ret; - /** - * @private - */ - function doCurve(otherPoint) { - return otherPoint && - !otherPoint.isNull && - otherPoint.doCurve !== false && - // #6387, area splines next to null: - !point.isCliff; - } - // Find control points - if (doCurve(lastPoint) && doCurve(nextPoint)) { - var lastX = lastPoint.plotX || 0, - lastY = lastPoint.plotY || 0, - nextX = nextPoint.plotX || 0, - nextY = nextPoint.plotY || 0, - 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(); + this.time = + userOptions.time && Object.keys(userOptions.time).length + ? new Time(userOptions.time) + : H.time; + /** + * Callback function to override the default function that formats + * all the numbers in the chart. Returns a string with the formatted + * number. + * + * @name Highcharts.Chart#numberFormatter + * @type {Highcharts.NumberFormatterCallbackFunction} + */ + this.numberFormatter = optionsChart.numberFormatter || numberFormat; + /** + * Whether the chart is in styled mode, meaning all presentatinoal + * attributes are avoided. + * + * @name Highcharts.Chart#styledMode + * @type {boolean} + */ + this.styledMode = optionsChart.styledMode; + this.hasCartesianSeries = optionsChart.showAxes; + var chart = this; + /** + * Index position of the chart in the {@link Highcharts#charts} + * property. + * + * @name Highcharts.Chart#index + * @type {number} + * @readonly + */ + chart.index = charts.length; // Add the chart to the global lookup + charts.push(chart); + H.chartCount++; + // Chart event handlers + if (chartEvents) { + objectEach(chartEvents, function (event, eventType) { + if (isFunction(event)) { + addEvent(chart, eventType, event); + } + }); } - // */ - ret = [ - 'C', - pick(lastPoint.rightContX, lastPoint.plotX, 0), - pick(lastPoint.rightContY, lastPoint.plotY, 0), - pick(leftContX, plotX, 0), - pick(leftContY, plotY, 0), - plotX, - plotY - ]; - // reset for updating series later - lastPoint.rightContX = lastPoint.rightContY = void 0; - return ret; + /** + * A collection of the X axes in the chart. + * + * @name Highcharts.Chart#xAxis + * @type {Array<Highcharts.Axis>} + */ + chart.xAxis = []; + /** + * A collection of the Y axes in the chart. + * + * @name Highcharts.Chart#yAxis + * @type {Array<Highcharts.Axis>} + * + * @todo + * Make events official: Fire the event `afterInit`. + */ + chart.yAxis = []; + chart.pointCount = chart.colorCounter = chart.symbolCounter = 0; + // Fire after init but before first render, before axes and series + // have been initialized. + fireEvent(chart, "afterInit"); + chart.firstRender(); + }); + }; + /** + * Internal function to unitialize an individual series. + * + * @private + * @function Highcharts.Chart#initSeries + */ + Chart.prototype.initSeries = function (options) { + var chart = this, + optionsChart = chart.options.chart, + type = + options.type || + optionsChart.type || + optionsChart.defaultSeriesType, + series, + Constr = BaseSeries.seriesTypes[type]; + // No such series type + if (!Constr) { + error(17, true, chart, { missingModuleFor: type }); + } + series = new Constr(chart, options); + if (typeof series.init === "function") { + series.init(this, options); + } + return series; + }; + /** + * Internal function to set data for all series with enabled sorting. + * + * @private + * @function Highcharts.Chart#setSeriesData + */ + Chart.prototype.setSeriesData = function () { + this.getSeriesOrderByLinks().forEach(function (series) { + // We need to set data for series with sorting after series init + if (!series.points && !series.data && series.enabledDataSorting) { + series.setData(series.options.data, false); } - /* eslint-enable valid-jsdoc */ - }); + }); + }; /** - * A `spline` series. If the [type](#series.spline.type) option is - * not specified, it is inherited from [chart.type](#chart.type). + * Sort and return chart series in order depending on the number of linked + * series. * - * @extends series,plotOptions.spline - * @excluding dataParser, dataURL, step, boostThreshold, boostBlending - * @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 following snippet shows only a - * few settings, see the complete options set 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" - * }] - * ``` - * - * @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 - * - * @type {Array<number|Array<(number|string),(number|null)>|null|*>} - * @extends series.line.data - * @product highcharts highstock - * @apioption series.spline.data + * @private + * @function Highcharts.Series#getSeriesOrderByLinks + * @return {Array<Highcharts.Series>} */ - ''; // adds doclets above intro transpilat - - }); - _registerModule(_modules, 'Series/AreaSplineSeries.js', [_modules['Core/Series/Series.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/Options.js']], function (BaseSeries, LegendSymbolMixin, O) { - /* * + Chart.prototype.getSeriesOrderByLinks = function () { + return this.series.concat().sort(function (a, b) { + if (a.linkedSeries.length || b.linkedSeries.length) { + return b.linkedSeries.length - a.linkedSeries.length; + } + return 0; + }); + }; + /** + * 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. * - * (c) 2010-2020 Torstein Honsi + * @private + * @function Highcharts.Series#orderSeries + * @param {number} [fromIndex] + * If this is given, only the series above this index are handled. + */ + Chart.prototype.orderSeries = function (fromIndex) { + var series = this.series, + i = fromIndex || 0; + for (; i < series.length; i++) { + if (series[i]) { + /** + * Contains the series' index in the `Chart.series` array. + * + * @name Highcharts.Series#index + * @type {number} + * @readonly + */ + series[i].index = i; + series[i].name = series[i].getName(); + } + } + }; + /** + * Check whether a given point is within the plot area. * - * License: www.highcharts.com/license + * @function Highcharts.Chart#isInsidePlot * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @param {number} plotX + * Pixel x relative to the plot area. * - * */ - var defaultOptions = O.defaultOptions; - var areaProto = BaseSeries.seriesTypes.area.prototype; - /** - * AreaSpline series type. + * @param {number} plotY + * Pixel y relative to the plot area. * - * @private - * @class - * @name Highcharts.seriesTypes.areaspline + * @param {boolean} [inverted] + * Whether the chart is inverted. * - * @augments Highcharts.Series - */ - BaseSeries.seriesType('areaspline', 'spline', + * @return {boolean} + * Returns true if the given point is inside the plot area. + */ + Chart.prototype.isInsidePlot = function (plotX, plotY, inverted) { + var x = inverted ? plotY : plotX, + y = inverted ? plotX : plotY, + e = { + x: x, + y: y, + isInsidePlot: + x >= 0 && x <= this.plotWidth && y >= 0 && y <= this.plotHeight, + }; + fireEvent(this, "afterIsInsidePlot", e); + return e.isInsidePlot; + }; /** - * The area spline series is an area series where the graph between the - * points is smoothed into a spline. + * 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. + * + * @function Highcharts.Chart#redraw + * + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] + * If or how to apply animation to the redraw. + * + * @fires Highcharts.Chart#event:afterSetExtremes + * @fires Highcharts.Chart#event:beforeRedraw + * @fires Highcharts.Chart#event:predraw + * @fires Highcharts.Chart#event:redraw + * @fires Highcharts.Chart#event:render + * @fires Highcharts.Chart#event:updatedData + */ + Chart.prototype.redraw = function (animation) { + fireEvent(this, "beforeRedraw"); + var chart = this, + axes = chart.axes, + series = chart.series, + pointer = chart.pointer, + legend = chart.legend, + legendUserOptions = chart.userOptions.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); + } + // Set the global animation. When chart.hasRendered is not true, the + // redraw call comes from a responsive rule and animation should not + // occur. + setAnimation(chart.hasRendered ? animation : false, 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 + series.forEach(function (serie) { + if (serie.isDirty) { + if (serie.options.legendType === "point") { + if (typeof serie.updateTotals === "function") { + serie.updateTotals(); + } + redrawLegend = true; + } else if ( + legendUserOptions && + (legendUserOptions.labelFormatter || + legendUserOptions.labelFormat) + ) { + redrawLegend = true; // #2165 + } + } + if (serie.isDirtyData) { + fireEvent(serie, "updatedData"); + } + }); + // handle added or removed series + if (redrawLegend && legend && legend.options.enabled) { + // draw legend graphics + legend.render(); + chart.isDirtyLegend = false; + } + // reset stacks + if (hasStackedSeries) { + chart.getStacks(); + } + if (hasCartesianSeries) { + // set axes scales + axes.forEach(function (axis) { + // Don't do setScale again if we're only resizing. Regression + // #13507. But we need it after chart.update (responsive), as + // axis is initialized again (#12137). + if (!chart.isResizing || !isNumber(axis.min)) { + axis.updateNames(); + axis.setScale(); + } + }); + } + chart.getMargins(); // #3098 + if (hasCartesianSeries) { + // If one axis is dirty, all axes must be redrawn (#792, #2169) + axes.forEach(function (axis) { + if (axis.isDirty) { + isDirtyBox = true; + } + }); + // redraw axes + axes.forEach(function (axis) { + // Fire 'afterSetExtremes' only if extremes are set + var key = axis.min + "," + axis.max; + if (axis.extKey !== key) { + // #821, #4452 + axis.extKey = key; + // prevent a recursive call to chart.redraw() (#1119) + afterRedraw.push(function () { + 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 + series.forEach(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 + afterRedraw.forEach(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. * - * @sample {highcharts} highcharts/demo/areaspline/ - * Area spline chart - * @sample {highstock} stock/demo/areaspline/ - * Area spline chart + * @sample highcharts/plotoptions/series-id/ + * Get series by id * - * @extends plotOptions.area - * @excluding step, boostThreshold, boostBlending - * @product highcharts highstock - * @apioption plotOptions.areaspline - */ - /** - * @see [fillColor](#plotOptions.areaspline.fillColor) - * @see [fillOpacity](#plotOptions.areaspline.fillOpacity) + * @function Highcharts.Chart#get * - * @apioption plotOptions.areaspline.color - */ - /** - * @see [color](#plotOptions.areaspline.color) - * @see [fillOpacity](#plotOptions.areaspline.fillOpacity) + * @param {string} id + * The id as given in the configuration options. * - * @apioption plotOptions.areaspline.fillColor + * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined} + * The retrieved item. */ + Chart.prototype.get = function (id) { + var ret, + series = this.series, + i; + /** + * @private + * @param {Highcharts.Axis|Highcharts.Series} item + * @return {boolean} + */ + 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; + }; /** - * @see [color](#plotOptions.areaspline.color) - * @see [fillColor](#plotOptions.areaspline.fillColor) + * Create the Axis instances based on the config options. * - * @default {highcharts} 0.75 - * @default {highstock} 0.75 - * @apioption plotOptions.areaspline.fillOpacity - */ - defaultOptions.plotOptions.area, { - getStackPoints: areaProto.getStackPoints, - getGraphPath: areaProto.getGraphPath, - drawGraph: areaProto.drawGraph, - drawLegendSymbol: LegendSymbolMixin.drawRectangle - }); + * @private + * @function Highcharts.Chart#getAxes + * @fires Highcharts.Chart#event:afterGetAxes + * @fires Highcharts.Chart#event:getAxes + */ + Chart.prototype.getAxes = function () { + var chart = this, + options = this.options, + xAxisOptions = (options.xAxis = splat(options.xAxis || {})), + yAxisOptions = (options.yAxis = splat(options.yAxis || {})), + optionsArray; + fireEvent(this, "getAxes"); + // make sure the options are arrays and add some members + xAxisOptions.forEach(function (axis, i) { + axis.index = i; + axis.isX = true; + }); + yAxisOptions.forEach(function (axis, i) { + axis.index = i; + }); + // concatenate all axis options into one array + optionsArray = xAxisOptions.concat(yAxisOptions); + optionsArray.forEach(function (axisOptions) { + new Axis(chart, axisOptions); // eslint-disable-line no-new + }); + fireEvent(this, "afterGetAxes"); + }; /** - * A `areaspline` series. If the [type](#series.areaspline.type) option - * is not specified, it is inherited from [chart.type](#chart.type). + * 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. * + * @sample highcharts/plotoptions/series-allowpointselect-line/ + * Get selected points * - * @extends series,plotOptions.areaspline - * @excluding dataParser, dataURL, step, boostThreshold, boostBlending - * @product highcharts highstock - * @apioption series.areaspline - */ - /** - * @see [fillColor](#series.areaspline.fillColor) - * @see [fillOpacity](#series.areaspline.fillOpacity) - * - * @apioption series.areaspline.color - */ - /** - * 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 following snippet shows only a - * few settings, see the complete options set 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" - * }] - * ``` - * - * @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 - * - * @type {Array<number|Array<(number|string),(number|null)>|null|*>} - * @extends series.line.data - * @product highcharts highstock - * @apioption series.areaspline.data - */ - /** - * @see [color](#series.areaspline.color) - * @see [fillOpacity](#series.areaspline.fillOpacity) + * @function Highcharts.Chart#getSelectedPoints * - * @apioption series.areaspline.fillColor + * @return {Array<Highcharts.Point>} + * The currently selected points. */ + Chart.prototype.getSelectedPoints = function () { + var points = []; + this.series.forEach(function (serie) { + // For one-to-one points inspect series.data in order to retrieve + // points outside the visible range (#6445). For grouped data, + // inspect the generated series.points. + points = points.concat( + serie.getPointsCollection().filter(function (point) { + return pick(point.selectedStaging, point.selected); + }) + ); + }); + return points; + }; /** - * @see [color](#series.areaspline.color) - * @see [fillColor](#series.areaspline.fillColor) + * 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 + * [series.showCheckBox](https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox) + * is true. * - * @default {highcharts} 0.75 - * @default {highstock} 0.75 - * @apioption series.areaspline.fillOpacity - */ - ''; // adds doclets above into transpilat - - }); - _registerModule(_modules, 'Series/ColumnSeries.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Series/Series.js'], _modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Mixins/LegendSymbol.js'], _modules['Series/LineSeries.js'], _modules['Core/Utilities.js']], function (A, BaseSeries, Color, H, LegendSymbolMixin, LineSeries, U) { - /* * + * @sample highcharts/members/chart-getselectedseries/ + * Get selected series * - * (c) 2010-2020 Torstein Honsi + * @function Highcharts.Chart#getSelectedSeries * - * License: www.highcharts.com/license + * @return {Array<Highcharts.Series>} + * The currently selected series. + */ + Chart.prototype.getSelectedSeries = function () { + return this.series.filter(function (serie) { + return serie.selected; + }); + }; + /** + * Set a new title or subtitle for the chart. * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @sample highcharts/members/chart-settitle/ + * Set title text and styles * - * */ - var animObject = A.animObject; - var color = Color.parse; - var noop = H.noop; - var clamp = U.clamp, - defined = U.defined, - extend = U.extend, - isArray = U.isArray, - isNumber = U.isNumber, - merge = U.merge, - pick = U.pick, - objectEach = U.objectEach; - /** - * Adjusted width and x offset of the columns for grouping. + * @function Highcharts.Chart#setTitle * - * @private - * @interface Highcharts.ColumnMetricsObject - */ /** - * Width of the columns. - * @name Highcharts.ColumnMetricsObject#width - * @type {number} - */ /** - * Offset of the columns. - * @name Highcharts.ColumnMetricsObject#offset - * @type {number} - */ - ''; // detach doclets above - /** - * The column series type. + * @param {Highcharts.TitleOptions} [titleOptions] + * New title options. The title text itself is set by the + * `titleOptions.text` property. * - * @private - * @class - * @name Highcharts.seriesTypes.column + * @param {Highcharts.SubtitleOptions} [subtitleOptions] + * New subtitle options. The subtitle text itself is set by the + * `subtitleOptions.text` property. * - * @augments Highcharts.Series + * @param {boolean} [redraw] + * Whether to redraw the chart or wait for a later call to + * `chart.redraw()`. */ - var ColumnSeries = BaseSeries.seriesType('column', 'line', - /** - * 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 - * @excluding connectEnds, connectNulls, gapSize, gapUnit, linecap, - * lineWidth, marker, step, useOhlcData - * @product highcharts highstock - * @optionparent plotOptions.column - */ - { - /** - * The corner radius of the border surrounding each column or bar. - * - * @sample {highcharts} highcharts/plotoptions/column-borderradius/ - * Rounded columns - * - * @product highcharts highstock gantt - * - * @private - */ - borderRadius: 0, - /** - * When using automatic point colors pulled from the global - * [colors](colors) or series-specific - * [plotOptions.column.colors](series.colors) collections, this option - * determines whether the chart should receive one color per series or - * one color per point. - * - * In styled mode, the `colors` or `series.colors` arrays are not - * supported, and instead this option gives the points individual color - * class names on the form `highcharts-color-{n}`. - * - * @see [series colors](#plotOptions.column.colors) - * - * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/ - * False by default - * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/ - * True - * - * @type {boolean} - * @default false - * @since 2.0 - * @product highcharts highstock gantt - * @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<Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject>} - * @since 3.0 - * @product highcharts highstock gantt - * @apioption plotOptions.column.colors - */ - /** - * When `true`, the columns will center in the category, ignoring null - * or missing points. When `false`, space will be reserved for null or - * missing points. - * - * @sample {highcharts} highcharts/series-column/centerincategory/ - * Center in category - * - * @since 8.0.1 - * @product highcharts highstock gantt - * - * @private - */ - centerInCategory: false, - /** - * Padding between each value groups, in x axis units. - * - * @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 - * - * @product highcharts highstock gantt - * - * @private - */ - 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. - * - * @sample {highcharts} highcharts/plotoptions/column-grouping-false/ - * Grouping disabled - * @sample {highstock} highcharts/plotoptions/column-grouping-false/ - * Grouping disabled - * - * @type {boolean} - * @default true - * @since 2.3.0 - * @product highcharts highstock gantt - * @apioption plotOptions.column.grouping - */ - /** - * @ignore-option - * @private - */ - marker: null, - /** - * 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. - * - * @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 - * - * @type {number} - * @since 4.1.8 - * @product highcharts highstock gantt - * @apioption plotOptions.column.maxPointWidth - */ - /** - * Padding between each column or bar, in x axis units. - * - * @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 - * - * @product highcharts highstock gantt - * - * @private - */ - pointPadding: 0.1, - /** - * A pixel value specifying a fixed width for each column or bar point. - * When `null`, the width is calculated from the `pointPadding` and - * `groupPadding`. The width effects the dimension that is not based on - * the point value. For column series it is the hoizontal length and for - * bar series it is the vertical length. - * - * @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 - * - * @type {number} - * @since 1.2.5 - * @product highcharts highstock gantt - * @apioption plotOptions.column.pointWidth - */ - /** - * A pixel value specifying a fixed width for the column or bar. - * Overrides pointWidth on the series. - * - * @see [series.pointWidth](#plotOptions.column.pointWidth) - * - * @type {number} - * @default undefined - * @since 7.0.0 - * @product highcharts highstock gantt - * @apioption series.column.data.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. - * - * @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 - * - * @product highcharts highstock gantt - * - * @private - */ - 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. - * - * @product highcharts highstock gantt - * - * @private - */ - 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. - * - * This option is set by default to 1 if data sorting is enabled. - * - * @sample {highcharts} highcharts/plotoptions/column-pointrange/ - * Set the point range to one day on a data set with one week - * between the points - * - * @type {number|null} - * @since 2.3 - * @product highcharts highstock gantt - * - * @private - */ - 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 gantt - */ - hover: { - /** @ignore-option */ - halo: false, - /** - * A specific border color for the hovered point. Defaults to - * inherit the normal state border color. - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @product highcharts gantt - * @apioption plotOptions.column.states.hover.borderColor - */ - /** - * A specific color for the hovered point. - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @product highcharts gantt - * @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 gantt - */ - brightness: 0.1 - }, - /** - * Options for the selected point. These settings override the - * normal state options when a point is selected. - * - * @extends plotOptions.series.states.select - * @excluding halo, lineWidth, lineWidthPlus, marker - * @product highcharts highstock gantt - */ - select: { - /** - * A specific color for the selected point. - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @default #cccccc - * @product highcharts highstock gantt - */ - color: '#cccccc', - /** - * A specific border color for the selected point. - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @default #000000 - * @product highcharts highstock gantt - */ - borderColor: '#000000' - } - }, - dataLabels: { - align: void 0, - verticalAlign: void 0, - /** - * The y position offset of the label relative to the point in - * pixels. - * - * @type {number} - */ - y: void 0 - }, - // false doesn't work well: https://jsfiddle.net/highcharts/hz8fopan/14/ - /** - * @ignore-option - * @private - */ - 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|null} - * @since 2.0 - * @product highcharts - * - * @private - */ - threshold: 0, - /** - * The width of the border surrounding each column or bar. Defaults to - * `1` when there is room for a border, but to `0` when the columns are - * so dense that a border would cover the next column. - * - * In styled mode, the stroke width can be set with the - * `.highcharts-point` rule. - * - * @sample {highcharts} highcharts/plotoptions/column-borderwidth/ - * 2px black border - * - * @type {number} - * @default undefined - * @product highcharts highstock gantt - * @apioption plotOptions.column.borderWidth - */ - /** - * The color of the border surrounding each column or bar. - * - * In styled mode, the border stroke can be set with the - * `.highcharts-point` rule. - * - * @sample {highcharts} highcharts/plotoptions/column-bordercolor/ - * Dark gray border - * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @default #ffffff - * @product highcharts highstock gantt - * - * @private - */ - borderColor: '#ffffff' - }, - /** - * @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, - /* eslint-disable valid-jsdoc */ - /** - * Initialize the series. Extends the basic Series.init method by - * marking other series of the same type as dirty. - * - * @private - * @function Highcharts.seriesTypes.column#init - * @return {void} - */ - init: function () { - LineSeries.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) { - chart.series.forEach(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. - * - * @private - * @function Highcharts.seriesTypes.column#getColumnMetrics - * @return {Highcharts.ColumnMetricsObject} - */ - getColumnMetrics: function () { - var series = this, - options = series.options, - xAxis = series.xAxis, - yAxis = series.yAxis, - reversedStacks = xAxis.options.reversedStacks, - // Keep backward compatibility: reversed xAxis had reversed - // stacks - reverseStacks = (xAxis.reversed && !reversedStacks) || - (!xAxis.reversed && reversedStacks), - 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 { - series.chart.series.forEach(function (otherSeries) { - var otherYAxis = otherSeries.yAxis, - otherOptions = otherSeries.options, - 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 && otherOptions.stacking !== 'group') { - stackKey = otherSeries.stackKey; - if (typeof 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.ordinal && xAxis.ordinal.slope) || - 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) + (reverseStacks ? 1 : 0), - pointXOffset = pointPadding + - (groupPadding + - colIndex * pointOffsetWidth - - (categoryWidth / 2)) * (reverseStacks ? -1 : 1); - // Save it for reading in linked series (Error bars particularly) - series.columnMetrics = { - width: pointWidth, - offset: pointXOffset, - paddedWidth: pointOffsetWidth, - columnCount: columnCount - }; - return series.columnMetrics; - }, - /** - * Make the columns crisp. The edges are rounded to the nearest full - * pixel. - * - * @private - * @function Highcharts.seriesTypes.column#crispCol - * @param {number} x - * @param {number} y - * @param {number} w - * @param {number} h - * @return {Highcharts.BBoxObject} - */ - 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 + Chart.prototype.setTitle = function ( + titleOptions, + subtitleOptions, + redraw + ) { + this.applyDescription("title", titleOptions); + this.applyDescription("subtitle", subtitleOptions); + // The initial call also adds the caption. On update, chart.update will + // relay to Chart.setCaption. + this.applyDescription("caption", void 0); + this.layOutTitles(redraw); + }; + /** + * Apply a title, subtitle or caption for the chart + * + * @private + * @function Highcharts.Chart#applyDescription + * @param name {string} + * Either title, subtitle or caption + * @param {Highcharts.TitleOptions|Highcharts.SubtitleOptions|Highcharts.CaptionOptions|undefined} explicitOptions + * The options to set, will be merged with default options. + */ + Chart.prototype.applyDescription = function (name, explicitOptions) { + var chart = this; + // Default style + var style = + name === "title" + ? { + color: "#333333", + fontSize: this.options.isStock ? "16px" : "18px", // #2944 + } + : { + color: "#666666", }; - }, - /** - * Adjust for missing columns, according to the `centerInCategory` - * option. Missing columns are either single points or stacks where the - * point or points are either missing or null. - * - * @private - * @function Highcharts.seriesTypes.column#adjustForMissingColumns - * @param {number} x - * The x coordinate of the column, left side - * @param {number} pointWidth - * The pointWidth, already computed upstream - * @param {Highcharts.ColumnPoint} point - * The point instance - * @param {Highcharts.ColumnMetricsObject} metrics - * The series-wide column metrics - * @return {number} - * The adjusted x position, or the original if not adjusted - */ - adjustForMissingColumns: function (x, pointWidth, point, metrics) { - var _this = this; - var stacking = this.options.stacking; - if (!point.isNull && metrics.columnCount > 1) { - var indexInCategory_1 = 0; - var totalInCategory_1 = 0; - // Loop over all the stacks on the Y axis. When stacking is - // enabled, these are real point stacks. When stacking is not - // enabled, but `centerInCategory` is true, there is one stack - // handling the grouping of points in each category. This is - // done in the `setGroupedPoints` function. - objectEach(this.yAxis.stacking && this.yAxis.stacking.stacks, function (stack) { - if (typeof point.x === 'number') { - var stackItem = stack[point.x.toString()]; - if (stackItem) { - var pointValues = stackItem.points[_this.index], - total = stackItem.total; - // If true `stacking` is enabled, count the - // total number of non-null stacks in the - // category, and note which index this point is - // within those stacks. - if (stacking) { - if (pointValues) { - indexInCategory_1 = totalInCategory_1; - } - if (stackItem.hasValidPoints) { - totalInCategory_1++; - } - // If `stacking` is not enabled, look for the - // index and total of the `group` stack. - } - else if (isArray(pointValues)) { - indexInCategory_1 = pointValues[1]; - totalInCategory_1 = total || 0; - } - } - } - }); - // Compute the adjusted x position - var boxWidth = (totalInCategory_1 - 1) * metrics.paddedWidth + - pointWidth; - x = (point.plotX || 0) + boxWidth / 2 - pointWidth - - indexInCategory_1 * metrics.paddedWidth; - } - return x; - }, - /** - * Translate each point to the plot area coordinate system and find - * shape positions - * - * @private - * @function Highcharts.seriesTypes.column#translate - */ - 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 - ), - xAxis = series.xAxis, - yAxis = series.yAxis, - threshold = options.threshold, - translatedThreshold = series.translatedThreshold = - yAxis.getThreshold(threshold), - minPointLength = pick(options.minPointLength, 5), - metrics = series.getColumnMetrics(), - seriesPointWidth = metrics.width, - // postprocessed for border width - seriesBarW = series.barW = - Math.max(seriesPointWidth, 1 + 2 * borderWidth), - seriesXOffset = series.pointXOffset = metrics.offset, - dataMin = series.dataMin, - dataMax = series.dataMax; - 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); - } - LineSeries.prototype.translate.apply(series); - // Record the new values - series.points.forEach(function (point) { - var yBottom = pick(point.yBottom, - translatedThreshold), - safeDistance = 999 + Math.abs(yBottom), - pointWidth = seriesPointWidth, - plotX = point.plotX || 0, - // Don't draw too far outside plot area (#1303, #2241, - // #4264) - plotY = clamp(point.plotY, -safeDistance, - yAxis.len + safeDistance), - barX = plotX + seriesXOffset, - 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 (isNumber(threshold) && - isNumber(dataMax) && - point.y === threshold && - dataMax <= threshold && - // and if there's room for it (#7311) - (yAxis.min || 0) < threshold && - // if all points are the same value (i.e zero) not draw - // as negative points (#10646) - dataMin !== dataMax) { - up = !up; - } - // If stacked... - barY = (Math.abs(barY - translatedThreshold) > minPointLength ? - // ...keep position - yBottom - minPointLength : - // #1485, #4051 - translatedThreshold - - (up ? minPointLength : 0)); - } - // Handle point.options.pointWidth - // @todo Handle grouping/stacking too. Calculate offset properly - if (defined(point.options.pointWidth)) { - pointWidth = barW = - Math.ceil(point.options.pointWidth); - barX -= Math.round((pointWidth - seriesPointWidth) / 2); - } - // Adjust for null or missing points - if (options.centerInCategory) { - barX = series.adjustForMissingColumns(barX, pointWidth, point, metrics); - } - // 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, - xAxis.len + xAxis.pos - chart.plotTop - (plotX || 0) - seriesXOffset - barW / 2, - barH - ] : - [barX + barW / 2, plotY + yAxis.pos - - chart.plotTop, barH]; - // Register shape type and arguments to be used in drawPoints - // Allow shapeType defined on pointClass level - point.shapeType = - series.pointClass.prototype.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, + // Merge default options with explicit options + var options = (this.options[name] = merge( + // Default styles + !this.styledMode && { style: style }, + this.options[name], + explicitOptions + )); + var elem = this[name]; + if (elem && explicitOptions) { + this[name] = elem = elem.destroy(); // remove old + } + if (options && !elem) { + elem = this.renderer + .text(options.text, 0, 0, options.useHTML) + .attr({ + align: options.align, + class: "highcharts-" + name, + zIndex: options.zIndex || 4, + }) + .add(); + // Update methods, shortcut to Chart.setTitle, Chart.setSubtitle and + // Chart.setCaption + elem.update = function (updateOptions) { + var fn = { + title: "setTitle", + subtitle: "setSubtitle", + caption: "setCaption", + }[name]; + chart[fn](updateOptions); + }; + // Presentational + if (!this.styledMode) { + elem.css(options.style); + } /** - * Use a solid rectangle like the area series types - * - * @private - * @function Highcharts.seriesTypes.column#drawLegendSymbol + * The chart title. The title has an `update` method that allows + * modifying the options directly or indirectly via + * `chart.update`. * - * @param {Highcharts.Legend} legend - * The legend object + * @sample highcharts/members/title-update/ + * Updating titles * - * @param {Highcharts.Series|Highcharts.Point} item - * The series (this) or point + * @name Highcharts.Chart#title + * @type {Highcharts.TitleObject} */ - drawLegendSymbol: LegendSymbolMixin.drawRectangle, /** - * Columns have no graph + * The chart subtitle. The subtitle has an `update` method that + * allows modifying the options directly or indirectly via + * `chart.update`. * - * @private - * @function Highcharts.seriesTypes.column#drawGraph - */ - drawGraph: function () { - this.group[this.dense ? 'addClass' : 'removeClass']('highcharts-dense-data'); - }, - /** - * Get presentational attributes - * - * @private - * @function Highcharts.seriesTypes.column#pointAttribs - * - * @param {Highcharts.ColumnPoint} point - * - * @param {string} state - * - * @return {Highcharts.SVGAttributes} + * @name Highcharts.Chart#subtitle + * @type {Highcharts.SubtitleObject} */ - 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, - // set to fill when borderColor null: - stroke = ((point && point[strokeOption]) || - options[strokeOption] || - this.color || - fill), strokeWidth = (point && point[strokeWidthOption]) || - options[strokeWidthOption] || - this[strokeWidthOption] || 0, dashstyle = (point && point.options.dashStyle) || options.dashStyle, opacity = pick(point && point.opacity, options.opacity, 1), 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), added support for colorAxis (#10670) - fill = (point.options.color || - (zone && (zone.color || point.nonZonedColor)) || - this.color); - if (zone) { - stroke = zone.borderColor || stroke; - dashstyle = zone.dashStyle || dashstyle; - strokeWidth = zone.borderWidth || strokeWidth; - } - } - // Select or hover states - if (state && point) { - stateOptions = merge(options.states[state], - // #6401 - point.options.states && - point.options.states[state] || - {}); - brightness = stateOptions.brightness; - fill = - stateOptions.color || (typeof brightness !== 'undefined' && - color(fill) - .brighten(stateOptions.brightness) - .get()) || fill; - stroke = stateOptions[strokeOption] || stroke; - strokeWidth = - stateOptions[strokeWidthOption] || strokeWidth; - dashstyle = stateOptions.dashStyle || dashstyle; - opacity = pick(stateOptions.opacity, opacity); - } - ret = { - fill: fill, - stroke: stroke, - 'stroke-width': strokeWidth, - opacity: opacity + this[name] = elem; + } + }; + /** + * Internal function to lay out the chart title, subtitle and caption, and + * cache the full offset height for use in `getMargins`. The result is + * stored in `this.titleOffset`. + * + * @private + * @function Highcharts.Chart#layOutTitles + * + * @param {boolean} [redraw=true] + * @fires Highcharts.Chart#event:afterLayOutTitles + */ + Chart.prototype.layOutTitles = function (redraw) { + var titleOffset = [0, 0, 0], + requiresDirtyBox, + renderer = this.renderer, + spacingBox = this.spacingBox; + // Lay out the title and the subtitle respectively + ["title", "subtitle", "caption"].forEach(function (key) { + var title = this[key], + titleOptions = this.options[key], + verticalAlign = titleOptions.verticalAlign || "top", + offset = + key === "title" + ? -3 + : // Floating subtitle (#6574) + verticalAlign === "top" + ? titleOffset[0] + 2 + : 0, + titleSize, + height; + if (title) { + if (!this.styledMode) { + titleSize = titleOptions.style.fontSize; + } + titleSize = renderer.fontMetrics(titleSize, title).b; + title.css({ + width: + (titleOptions.width || + spacingBox.width + (titleOptions.widthAdjust || 0)) + "px", + }); + // Skip the cache for HTML (#3481, #11666) + height = Math.round(title.getBBox(titleOptions.useHTML).height); + title.align( + extend( + { + y: + verticalAlign === "bottom" + ? titleSize + : offset + titleSize, + height: height, + }, + titleOptions + ), + false, + "spacingBox" + ); + if (!titleOptions.floating) { + if (verticalAlign === "top") { + titleOffset[0] = Math.ceil(titleOffset[0] + height); + } else if (verticalAlign === "bottom") { + titleOffset[2] = Math.ceil(titleOffset[2] + height); + } + } + } + }, this); + // Handle title.margin and caption.margin + if ( + titleOffset[0] && + (this.options.title.verticalAlign || "top") === "top" + ) { + titleOffset[0] += this.options.title.margin; + } + if ( + titleOffset[2] && + this.options.caption.verticalAlign === "bottom" + ) { + titleOffset[2] += this.options.caption.margin; + } + requiresDirtyBox = + !this.titleOffset || + this.titleOffset.join(",") !== titleOffset.join(","); + // Used in getMargins + this.titleOffset = titleOffset; + fireEvent(this, "afterLayOutTitles"); + if (!this.isDirtyBox && requiresDirtyBox) { + this.isDirtyBox = this.isDirtyLegend = 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}. + * + * @private + * @function Highcharts.Chart#getChartSize + */ + Chart.prototype.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 = getStyle(renderTo, "width"); + } + if (!defined(heightOption)) { + chart.containerHeight = getStyle(renderTo, "height"); + } + /** + * The current pixel width of the chart. + * + * @name Highcharts.Chart#chartWidth + * @type {number} + */ + chart.chartWidth = Math.max( + // #1393 + 0, + widthOption || chart.containerWidth || 600 // #1460 + ); + /** + * The current pixel height of the chart. + * + * @name Highcharts.Chart#chartHeight + * @type {number} + */ + chart.chartHeight = Math.max( + 0, + 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 + * @function Highcharts.Chart#temporaryDisplay + * + * @param {boolean} [revert] + * Revert to the saved original styles. + */ + Chart.prototype.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 ( + getStyle(node, "display", false) === "none" || + node.hcOricDetached + ) { + node.hcOrigStyle = { + display: node.style.display, + height: node.style.height, + overflow: node.style.overflow, }; - 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. - * - * @private - * @function Highcharts.seriesTypes.column#drawPoints - */ - drawPoints: function () { - var series = this, - chart = this.chart, - options = series.options, - renderer = chart.renderer, - animationLimit = options.animationLimit || 250, - shapeArgs; - // draw the columns - series.points.forEach(function (point) { - var plotY = point.plotY, - graphic = point.graphic, - hasGraphic = !!graphic, - verb = graphic && chart.pointCount < animationLimit ? - 'animate' : 'attr'; - if (isNumber(plotY) && point.y !== null) { - shapeArgs = point.shapeArgs; - // When updating a series between 2d and 3d or cartesian and - // polar, the shape type changes. - if (graphic && point.hasNewShapeType()) { - graphic = graphic.destroy(); - } - // Set starting position for point sliding animation. - if (series.enabledDataSorting) { - point.startXPos = series.xAxis.reversed ? - -(shapeArgs ? shapeArgs.width : 0) : - series.xAxis.width; - } - if (!graphic) { - point.graphic = graphic = - renderer[point.shapeType](shapeArgs) - .add(point.group || series.group); - if (graphic && - series.enabledDataSorting && - chart.hasRendered && - chart.pointCount < animationLimit) { - graphic.attr({ - x: point.startXPos - }); - hasGraphic = true; - verb = 'animate'; - } - } - if (graphic && hasGraphic) { // update - graphic[verb](merge(shapeArgs)); - } - // Border radius is not stylable (#6900) - if (options.borderRadius) { - graphic[verb]({ - r: options.borderRadius - }); - } - // Presentational - if (!chart.styledMode) { - graphic[verb](series.pointAttribs(point, (point.selected && 'select'))) - .shadow(point.allowShadow !== false && options.shadow, null, options.stacking && !options.borderRadius); - } - graphic.addClass(point.getClassName(), true); - } - else if (graphic) { - point.graphic = graphic.destroy(); // #1269 - } - }); + tempStyle = { + display: "block", + overflow: "hidden", + }; + if (node !== this.renderTo) { + tempStyle.height = 0; + } + 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) { + 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`. + * + * @function Highcharts.Chart#setClassName + * + * @param {string} [className] + * The additional class name. + */ + Chart.prototype.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 + * @function Highcharts.Chart#afterGetContainer + * @fires Highcharts.Chart#event:afterGetContainer + */ + Chart.prototype.getContainer = function () { + var chart = this, + container, + options = chart.options, + optionsChart = options.chart, + chartWidth, + chartHeight, + renderTo = chart.renderTo, + indexAttrName = "data-highcharts-chart", + oldChartIndex, + Ren, + containerId = 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) { + error(13, true, chart); + } + // 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; + // Allow table cells and flex-boxes to shrink without the chart blocking + // them out (#6427) + css(renderTo, { overflow: "hidden" }); + // Create the inner container + if (!chart.styledMode) { + containerStyle = extend( + { + position: "relative", + // needed for context menu (avoidscrollbars) and content + // overflow in IE + overflow: "hidden", + width: chartWidth + "px", + height: chartHeight + "px", + textAlign: "left", + lineHeight: "normal", + zIndex: 0, + "-webkit-tap-highlight-color": "rgba(0,0,0,0)", + userSelect: "none", // #13503 + }, + optionsChart.style + ); + } + /** + * The containing HTML element of the chart. The container is + * dynamically inserted into the element given as the `renderTo` + * parameter in the {@link Highcharts#chart} constructor. + * + * @name Highcharts.Chart#container + * @type {Highcharts.HTMLDOMElement} + */ + container = createElement( + "div", + { + id: containerId, }, - /** - * Animate the column heights one by one from zero. - * - * @private - * @function Highcharts.seriesTypes.column#animate - * - * @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 (init) { - attr.scaleY = 0.001; - translatedThreshold = clamp(yAxis.toPixels(options.threshold), yAxis.pos, yAxis.pos + yAxis.len); - if (inverted) { - attr.translateX = translatedThreshold - yAxis.len; - } - else { - attr.translateY = translatedThreshold; - } - // apply finnal clipping (used in Highstock) (#7083) - // animation is done by scaleY, so cliping is for panes - if (series.clipBox) { - series.setClip(); - } - 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) { - if (series.group) { - attr[translateProp] = translateStart + - fx.pos * (yAxis.pos - translateStart); - series.group.attr(attr); - } - } - })); + 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. + * + * @name Highcharts.Chart#renderer + * @type {Highcharts.SVGRenderer} + */ + chart.renderer = new Ren( + container, + chartWidth, + chartHeight, + null, + optionsChart.forExport, + options.exporting && options.exporting.allowHTML, + chart.styledMode + ); + // Set the initial animation from the options + setAnimation(void 0, chart); + chart.setClassName(optionsChart.className); + if (!chart.styledMode) { + chart.renderer.setStyle(optionsChart.style); + } else { + // Initialize definitions + for (key in options.defs) { + // eslint-disable-line guard-for-in + this.renderer.definition(options.defs[key]); + } + } + // Add a reference to the charts index + chart.renderer.chartIndex = chart.index; + fireEvent(this, "afterGetContainer"); + }; + /** + * 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 + * @function Highcharts.Chart#getMargins + * @fires Highcharts.Chart#event:getMargins + */ + Chart.prototype.getMargins = function (skipAxes) { + var _a = this, + spacing = _a.spacing, + margin = _a.margin, + titleOffset = _a.titleOffset; + this.resetMargins(); + // Adjust for title and subtitle + if (titleOffset[0] && !defined(margin[0])) { + this.plotTop = Math.max(this.plotTop, titleOffset[0] + spacing[0]); + } + if (titleOffset[2] && !defined(margin[2])) { + this.marginBottom = Math.max( + this.marginBottom, + titleOffset[2] + spacing[2] + ); + } + // Adjust for legend + if (this.legend && this.legend.display) { + this.legend.adjustMargins(margin, spacing); + } + fireEvent(this, "getMargins"); + if (!skipAxes) { + this.getAxisMargins(); + } + }; + /** + * @private + * @function Highcharts.Chart#getAxisMargins + */ + Chart.prototype.getAxisMargins = function () { + var chart = this, + // [top, right, bottom, left] + axisOffset = (chart.axisOffset = [0, 0, 0, 0]), + colorAxis = chart.colorAxis, + margin = chart.margin, + getOffset = function (axes) { + axes.forEach(function (axis) { + if (axis.visible) { + axis.getOffset(); + } + }); + }; + // pre-render axes to get labels offset width + if (chart.hasCartesianSeries) { + getOffset(chart.axes); + } else if (colorAxis && colorAxis.length) { + getOffset(colorAxis); + } + // Add the axis offsets + marginNames.forEach(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 [chart.reflow](https://api.highcharts.com/highcharts/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. + * + * @sample highcharts/members/chart-reflow/ + * Resize div and reflow + * @sample highcharts/chart/events-container/ + * Pop up and reflow + * + * @function Highcharts.Chart#reflow + * + * @param {global.Event} [e] + * Event arguments. Used primarily when the function is called + * internally as a response to window resize. + */ + Chart.prototype.reflow = function (e) { + var chart = this, + optionsChart = chart.options.chart, + renderTo = chart.renderTo, + hasUserSize = + defined(optionsChart.width) && defined(optionsChart.height), + width = optionsChart.width || getStyle(renderTo, "width"), + height = optionsChart.height || 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 + ) { + U.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(void 0, void 0, false); + } + }, + e ? 100 : 0 + ); + } + chart.containerWidth = width; + chart.containerHeight = height; + } + }; + /** + * Toggle the event handlers necessary for auto resizing, depending on the + * `chart.reflow` option. + * + * @private + * @function Highcharts.Chart#setReflow + */ + Chart.prototype.setReflow = function (reflow) { + var chart = this; + if (reflow !== false && !this.unbindReflow) { + this.unbindReflow = addEvent(win, "resize", function (e) { + // a removed event listener still runs in Edge and IE if the + // listener was removed while the event runs, so check if the + // chart is not destroyed (#11609) + if (chart.options) { + chart.reflow(e); + } + }); + addEvent(this, "destroy", this.unbindReflow); + } else if (reflow === false && this.unbindReflow) { + // Unbind and unset + this.unbindReflow = this.unbindReflow(); + } + // 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. + * + * @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 + * + * @function Highcharts.Chart#setSize + * + * @param {number|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|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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] + * Whether and how to apply animation. + * + * @return {void} + * + * @fires Highcharts.Chart#event:endResize + * @fires Highcharts.Chart#event:resize + */ + Chart.prototype.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 + setAnimation(animation, chart); + globalAnimation = renderer.globalAnimation; + chart.oldChartHeight = chart.chartHeight; + chart.oldChartWidth = chart.chartWidth; + if (typeof width !== "undefined") { + chart.options.chart.width = width; + } + if (typeof height !== "undefined") { + chart.options.chart.height = height; + } + chart.getChartSize(); + // Resize the container with the global animation applied if enabled + // (#2503) + if (!chart.styledMode) { + (globalAnimation ? animate : css)( + chart.container, + { + width: chart.chartWidth + "px", + height: chart.chartHeight + "px", + }, + globalAnimation + ); + } + chart.setChartSize(true); + renderer.setSize( + chart.chartWidth, + chart.chartHeight, + globalAnimation + ); + // handle axes + chart.axes.forEach(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(globalAnimation); + 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 + * @function Highcharts.Chart#setChartSize + * @fires Highcharts.Chart#event:afterSetChartSize + */ + Chart.prototype.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 Highcharts.Chart#plotLeft + * @type {number} + */ + chart.plotLeft = plotLeft = Math.round(chart.plotLeft); + /** + * The current top position of the plot area in pixels. + * + * @name Highcharts.Chart#plotTop + * @type {number} + */ + chart.plotTop = plotTop = Math.round(chart.plotTop); + /** + * The current width of the plot area in pixels. + * + * @name Highcharts.Chart#plotWidth + * @type {number} + */ + chart.plotWidth = plotWidth = Math.max( + 0, + Math.round(chartWidth - plotLeft - chart.marginRight) + ); + /** + * The current height of the plot area in pixels. + * + * @name Highcharts.Chart#plotHeight + * @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) { + chart.axes.forEach(function (axis) { + axis.setAxisSize(); + axis.setAxisTranslation(); + }); + } + fireEvent(chart, "afterSetChartSize", { skipAxes: skipAxes }); + }; + /** + * Initial margins before auto size margins are applied. + * + * @private + * @function Highcharts.Chart#resetMargins + */ + Chart.prototype.resetMargins = function () { + fireEvent(this, "resetMargins"); + var chart = this, + chartOptions = chart.options.chart; + // Create margin and spacing array + ["margin", "spacing"].forEach(function splashArrays(target) { + var value = chartOptions[target], + values = isObject(value) ? value : [value, value, value, value]; + ["Top", "Right", "Bottom", "Left"].forEach(function ( + sideName, + side + ) { + chart[target][side] = pick( + chartOptions[target + sideName], + values[side] + ); + }); + }); + // Set margin names like chart.plotTop, chart.plotLeft, + // chart.marginRight, chart.marginBottom. + marginNames.forEach(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 + * @function Highcharts.Chart#drawChartBox + * @fires Highcharts.Chart#event:afterDrawChartBox + */ + Chart.prototype.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, + styledMode = chart.styledMode, + 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"; + } + if (!styledMode) { + // 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); + } else { + chartBorderWidth = mgn = chartBackground.strokeWidth(); + } + 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); + if (!styledMode) { + // 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 { + if (plotBackgroundImage !== plotBGImage.attr("href")) { + plotBGImage.attr("href", plotBackgroundImage); + } + 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(); + } + if (!styledMode) { + // 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; + fireEvent(this, "afterDrawChartBox"); + }; + /** + * 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 + * @function Highcharts.Chart#propFromSeries + * @return {void} + */ + Chart.prototype.propFromSeries = function () { + var chart = this, + optionsChart = chart.options.chart, + klass, + seriesOptions = chart.options.series, + i, + value; + /** + * The flag is set to `true` if a series of the chart is inverted. + * + * @name Highcharts.Chart#inverted + * @type {boolean|undefined} + */ + ["inverted", "angular", "polar"].forEach(function (key) { + // The default series type's class + klass = + BaseSeries.seriesTypes[ + optionsChart.type || optionsChart.defaultSeriesType + ]; + // Get the value from available chart-wide properties + value = + // It is set in the options: + optionsChart[key] || + // The default series class: + (klass && klass.prototype[key]); + // requires it + // 4. Check if any the chart's series require it + i = seriesOptions && seriesOptions.length; + while (!value && i--) { + klass = BaseSeries.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 + * @function Highcharts.Chart#linkSeries + * @fires Highcharts.Chart#event:afterLinkSeries + */ + Chart.prototype.linkSeries = function () { + var chart = this, + chartSeries = chart.series; + // Reset links + chartSeries.forEach(function (series) { + series.linkedSeries.length = 0; + }); + // Apply new links + chartSeries.forEach(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; + if (linkedTo.enabledDataSorting) { + series.setDataSortingOptions(); + } + series.visible = pick( + series.options.visible, + linkedTo.options.visible, + series.visible + ); // #3879 + } + } + }); + fireEvent(this, "afterLinkSeries"); + }; + /** + * Render series for the chart. + * + * @private + * @function Highcharts.Chart#renderSeries + */ + Chart.prototype.renderSeries = function () { + this.series.forEach(function (serie) { + serie.translate(); + serie.render(); + }); + }; + /** + * Render labels for the chart. + * + * @private + * @function Highcharts.Chart#renderLabels + */ + Chart.prototype.renderLabels = function () { + var chart = this, + labels = chart.options.labels; + if (labels.items) { + labels.items.forEach(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 + * @function Highcharts.Chart#render + */ + Chart.prototype.render = function () { + var chart = this, + axes = chart.axes, + colorAxis = chart.colorAxis, + renderer = chart.renderer, + options = chart.options, + correction = 0, // correction for X axis labels + tempWidth, + tempHeight, + redoHorizontal, + redoVertical, + renderAxes = function (axes) { + axes.forEach(function (axis) { + if (axis.visible) { + axis.render(); + } + }); + }; + // Title + chart.setTitle(); + /** + * The overview of the chart's series. + * + * @name Highcharts.Chart#legend + * @type {Highcharts.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; + axes.some(function (axis) { + if ( + axis.horiz && + axis.visible && + axis.options.labels.enabled && + axis.series.length + ) { + // 21 is the most common correction for X axis labels + correction = 21; + return true; + } + }); + // use Math.max to prevent negative plotHeight + chart.plotHeight = Math.max(chart.plotHeight - correction, 0); + tempHeight = chart.plotHeight; + // Get margins by pre-rendering axes + axes.forEach(function (axis) { + axis.setScale(); + }); + chart.getAxisMargins(); + // If the plot area size has changed significantly, calculate tick + // positions again + redoHorizontal = tempWidth / chart.plotWidth > 1.1; + // Height is more sensitive, use lower threshold + redoVertical = tempHeight / chart.plotHeight > 1.05; + if (redoHorizontal || redoVertical) { + axes.forEach(function (axis) { + if ( + (axis.horiz && redoHorizontal) || + (!axis.horiz && redoVertical) + ) { + // update to reflect the new margins + axis.setTickInterval(true); + } + }); + chart.getMargins(); // second pass to check for new labels + } + // Draw the borders and backgrounds + chart.drawChartBox(); + // Axes + if (chart.hasCartesianSeries) { + renderAxes(axes); + } else if (colorAxis && colorAxis.length) { + renderAxes(colorAxis); + } + // 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(); + } + // Handle scaling + chart.updateContainerScaling(); + // Set flag + chart.hasRendered = true; + }; + /** + * Set a new credits label for the chart. + * + * @sample highcharts/credits/credits-update/ + * Add and update credits + * + * @function Highcharts.Chart#addCredits + * + * @param {Highcharts.CreditsOptions} [credits] + * A configuration object for the new credits. + */ + Chart.prototype.addCredits = function (credits) { + var chart = this, + creds = merge(true, this.options.credits, credits); + if (creds.enabled && !this.credits) { /** - * Remove this series from the chart + * The chart's credits label. The label has an `update` method that + * allows setting new options as per the + * [credits options set](https://api.highcharts.com/highcharts/credits). * - * @private - * @function Highcharts.seriesTypes.column#remove + * @name Highcharts.Chart#credits + * @type {Highcharts.SVGElement} */ - 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) { - chart.series.forEach(function (otherSeries) { - if (otherSeries.type === series.type) { - otherSeries.isDirty = true; - } - }); + this.credits = this.renderer + .text(creds.text + (this.mapCredits || ""), 0, 0) + .addClass("highcharts-credits") + .on("click", function () { + if (creds.href) { + win.location.href = creds.href; } - LineSeries.prototype.remove.apply(series, arguments); + }) + .attr({ + align: creds.position.align, + zIndex: 8, + }); + if (!chart.styledMode) { + this.credits.css(creds.style); + } + this.credits.add().align(creds.position); + // Dynamically update + this.credits.update = function (options) { + chart.credits = chart.credits.destroy(); + chart.addCredits(options); + }; + } + }; + /** + * Handle scaling, #11329 - when there is scaling/transform on the container + * or on a parent element, we need to take this into account. We calculate + * the scaling once here and it is picked up where we need to use it + * (Pointer, Tooltip). + * + * @private + * @function Highcharts.Chart#updateContainerScaling + */ + Chart.prototype.updateContainerScaling = function () { + var container = this.container; + // #13342 - tooltip was not visible in Chrome, when chart + // updates height. + if ( + container.offsetWidth > 2 && // #13342 + container.offsetHeight > 2 && // #13342 + container.getBoundingClientRect + ) { + var bb = container.getBoundingClientRect(), + scaleX = bb.width / container.offsetWidth, + scaleY = bb.height / container.offsetHeight; + if (scaleX !== 1 || scaleY !== 1) { + this.containerScaling = { scaleX: scaleX, scaleY: scaleY }; + } else { + delete this.containerScaling; + } + } + }; + /** + * 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 + * + * @function Highcharts.Chart#destroy + * + * @fires Highcharts.Chart#event:destroy + */ + Chart.prototype.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) { + erase(charts, chart); // #6569 + } else { + charts[chart.index] = void 0; + } + 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: + [ + "title", + "subtitle", + "chartBackground", + "plotBackground", + "plotBGImage", + "plotBorder", + "seriesGroup", + "clipRect", + "credits", + "pointer", + "rangeSelector", + "legend", + "resetZoomButton", + "tooltip", + "renderer", + ].forEach(function (name) { + var prop = chart[name]; + if (prop && prop.destroy) { + chart[name] = prop.destroy(); + } + }); + // Remove container and all SVG, check container as it can break in IE + // when destroyed before finished loading + if (container) { + container.innerHTML = ""; + removeEvent(container); + if (parentNode) { + discardElement(container); + } + } + // clean it all up + objectEach(chart, function (val, key) { + delete chart[key]; + }); + }; + /** + * Prepare for first rendering after all data are loaded. + * + * @private + * @function Highcharts.Chart#firstRender + * @fires Highcharts.Chart#event:beforeRender + */ + Chart.prototype.firstRender = function () { + var chart = this, + options = chart.options; + // Hook for oldIE to check whether the chart is ready to render + if (chart.isReadyToRender && !chart.isReadyToRender()) { + return; + } + // Create the container + chart.getContainer(); + chart.resetMargins(); + chart.setChartSize(); + // Set the common chart properties (mainly invert) from the given series + chart.propFromSeries(); + // get axes + chart.getAxes(); + // Initialize the series + (isArray(options.series) ? options.series : []).forEach( + // #9680 + function (serieOptions) { + chart.initSeries(serieOptions); + } + ); + chart.linkSeries(); + chart.setSeriesData(); + // 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) { + if (!H.hasTouch && (win.PointerEvent || win.MSPointerEvent)) { + chart.pointer = new MSPointer(chart, options); + } else { + /** + * The Pointer that keeps track of mouse and touch interaction. + * + * @memberof Highcharts.Chart + * @name pointer + * @type {Highcharts.Pointer} + * @instance + */ + chart.pointer = new Pointer(chart, options); + } + } + chart.render(); + // Fire the load event if there are no external images + if (!chart.renderer.imgCount && !chart.hasLoaded) { + 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 + * @function Highcharts.Chart#onload + * @fires Highcharts.Chart#event:load + * @fires Highcharts.Chart#event:render + */ + Chart.prototype.onload = function () { + // Run callbacks, first the ones registered by modules, then user's one + this.callbacks.concat([this.callback]).forEach(function (fn) { + // Chart destroyed in its own callback (#3600) + if (fn && typeof this.index !== "undefined") { + 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.setReflow(this.options.chart.reflow); + } + // Don't run again + this.hasLoaded = true; + }; + return Chart; + })(); + // Hook for adding callbacks in modules + Chart.prototype.callbacks = []; + /** + * Factory function for basic charts. + * + * @example + * // Render a chart in to div#container + * var chart = Highcharts.chart('container', { + * title: { + * text: 'My chart' + * }, + * series: [{ + * data: [1, 3, 2, 4] + * }] + * }); + * + * @function Highcharts.chart + * + * @param {string|Highcharts.HTMLDOMElement} [renderTo] + * The DOM element to render to, or its id. + * + * @param {Highcharts.Options} options + * The chart options structure. + * + * @param {Highcharts.ChartCallbackFunction} [callback] + * Function to run when the chart has loaded and and all external images + * are loaded. Defining a + * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) + * handler is equivalent. + * + * @return {Highcharts.Chart} + * Returns the Chart object. + */ + function chart(a, b, c) { + return new Chart(a, b, c); + } + H.chart = chart; + H.Chart = Chart; + + return Chart; + } + ); + _registerModule( + _modules, + "Extensions/ScrollablePlotArea.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Chart/Chart.js"], + _modules["Core/Globals.js"], + _modules["Core/Utilities.js"], + ], + function (A, Chart, H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * Highcharts feature to make the Y axis stay fixed when scrolling the chart + * horizontally on mobile devices. Supports left and right side axes. + */ + /* + WIP on vertical scrollable plot area (#9378). To do: + - Bottom axis positioning + - Test with Gantt + - Look for size optimizing the code + - API and demos + */ + var stop = A.stop; + var addEvent = U.addEvent, + createElement = U.createElement, + pick = U.pick; + /** + * Options for a scrollable plot area. This feature provides a minimum size for + * the plot area of the chart. If the size gets smaller than this, typically + * on mobile devices, a native browser scrollbar is presented. This scrollbar + * provides smooth scrolling for the contents of the plot area, whereas the + * title, legend and unaffected axes are fixed. + * + * Since v7.1.2, a scrollable plot area can be defined for either horizontal or + * vertical scrolling, depending on whether the `minWidth` or `minHeight` + * option is set. + * + * @sample highcharts/chart/scrollable-plotarea + * Scrollable plot area + * @sample highcharts/chart/scrollable-plotarea-vertical + * Vertically scrollable plot area + * @sample {gantt} highcharts/chart/scrollable-plotarea-vertical + * Gantt chart with vertically scrollable plot area + * + * @since 6.1.0 + * @product highcharts gantt + * @apioption chart.scrollablePlotArea + */ + /** + * The minimum height for the plot area. If it gets smaller than this, the plot + * area will become scrollable. + * + * @type {number} + * @apioption chart.scrollablePlotArea.minHeight + */ + /** + * The minimum width for the plot area. If it gets smaller than this, the plot + * area will become scrollable. + * + * @type {number} + * @apioption chart.scrollablePlotArea.minWidth + */ + /** + * The initial scrolling position of the scrollable plot area. Ranges from 0 to + * 1, where 0 aligns the plot area to the left and 1 aligns it to the right. + * Typically we would use 1 if the chart has right aligned Y axes. + * + * @type {number} + * @apioption chart.scrollablePlotArea.scrollPositionX + */ + /** + * The initial scrolling position of the scrollable plot area. Ranges from 0 to + * 1, where 0 aligns the plot area to the top and 1 aligns it to the bottom. + * + * @type {number} + * @apioption chart.scrollablePlotArea.scrollPositionY + */ + /** + * The opacity of mask applied on one of the sides of the plot + * area. + * + * @sample {highcharts} highcharts/chart/scrollable-plotarea-opacity + * Disabled opacity for the mask + * + * @type {number} + * @default 0.85 + * @since 7.1.1 + * @apioption chart.scrollablePlotArea.opacity + */ + (""); // detach API doclets + /* eslint-disable no-invalid-this, valid-jsdoc */ + addEvent(Chart, "afterSetChartSize", function (e) { + var scrollablePlotArea = this.options.chart.scrollablePlotArea, + scrollableMinWidth = + scrollablePlotArea && scrollablePlotArea.minWidth, + scrollableMinHeight = + scrollablePlotArea && scrollablePlotArea.minHeight, + scrollablePixelsX, + scrollablePixelsY, + corrections; + if (!this.renderer.forExport) { + // The amount of pixels to scroll, the difference between chart + // width and scrollable width + if (scrollableMinWidth) { + this.scrollablePixelsX = scrollablePixelsX = Math.max( + 0, + scrollableMinWidth - this.chartWidth + ); + if (scrollablePixelsX) { + this.plotWidth += scrollablePixelsX; + if (this.inverted) { + this.clipBox.height += scrollablePixelsX; + this.plotBox.height += scrollablePixelsX; + } else { + this.clipBox.width += scrollablePixelsX; + this.plotBox.width += scrollablePixelsX; + } + corrections = { + // Corrections for right side + 1: { name: "right", value: scrollablePixelsX }, + }; + } + // Currently we can only do either X or Y + } else if (scrollableMinHeight) { + this.scrollablePixelsY = scrollablePixelsY = Math.max( + 0, + scrollableMinHeight - this.chartHeight + ); + if (scrollablePixelsY) { + this.plotHeight += scrollablePixelsY; + if (this.inverted) { + this.clipBox.width += scrollablePixelsY; + this.plotBox.width += scrollablePixelsY; + } else { + this.clipBox.height += scrollablePixelsY; + this.plotBox.height += scrollablePixelsY; + } + corrections = { + 2: { name: "bottom", value: scrollablePixelsY }, + }; + } + } + if (corrections && !e.skipAxes) { + this.axes.forEach(function (axis) { + // For right and bottom axes, only fix the plot line length + if (corrections[axis.side]) { + // Get the plot lines right in getPlotLinePath, + // temporarily set it to the adjusted plot width. + axis.getPlotLinePath = function () { + var marginName = corrections[axis.side].name, + correctionValue = corrections[axis.side].value, + // axis.right or axis.bottom + margin = this[marginName], + path; + // Temporarily adjust + this[marginName] = margin - correctionValue; + path = H.Axis.prototype.getPlotLinePath.apply( + this, + arguments + ); + // Reset + this[marginName] = margin; + return path; + }; + } else { + // Apply the corrected plotWidth + axis.setAxisSize(); + axis.setAxisTranslation(); + } + }); + } + } + }); + addEvent(Chart, "render", function () { + if (this.scrollablePixelsX || this.scrollablePixelsY) { + if (this.setUpScrolling) { + this.setUpScrolling(); + } + this.applyFixed(); + } else if (this.fixedDiv) { + // Has been in scrollable mode + this.applyFixed(); + } + }); + /** + * @private + * @function Highcharts.Chart#setUpScrolling + * @return {void} + */ + Chart.prototype.setUpScrolling = function () { + var _this = this; + var attribs = { + WebkitOverflowScrolling: "touch", + overflowX: "hidden", + overflowY: "hidden", + }; + if (this.scrollablePixelsX) { + attribs.overflowX = "auto"; + } + if (this.scrollablePixelsY) { + attribs.overflowY = "auto"; + } + // Insert a container with position relative + // that scrolling and fixed container renders to (#10555) + this.scrollingParent = createElement( + "div", + { + className: "highcharts-scrolling-parent", + }, + { + position: "relative", + }, + this.renderTo + ); + // Add the necessary divs to provide scrolling + this.scrollingContainer = createElement( + "div", + { + className: "highcharts-scrolling", + }, + attribs, + this.scrollingParent + ); + // On scroll, reset the chart position because it applies to the scrolled + // container + addEvent(this.scrollingContainer, "scroll", function () { + if (_this.pointer) { + delete _this.pointer.chartPosition; + } + }); + this.innerContainer = createElement( + "div", + { + className: "highcharts-inner-container", + }, + null, + this.scrollingContainer + ); + // Now move the container inside + this.innerContainer.appendChild(this.container); + // Don't run again + this.setUpScrolling = null; + }; + /** + * These elements are moved over to the fixed renderer and stay fixed when the + * user scrolls the chart + * @private + */ + Chart.prototype.moveFixedElements = function () { + var container = this.container, + fixedRenderer = this.fixedRenderer, + fixedSelectors = [ + ".highcharts-contextbutton", + ".highcharts-credits", + ".highcharts-legend", + ".highcharts-legend-checkbox", + ".highcharts-navigator-series", + ".highcharts-navigator-xaxis", + ".highcharts-navigator-yaxis", + ".highcharts-navigator", + ".highcharts-reset-zoom", + ".highcharts-scrollbar", + ".highcharts-subtitle", + ".highcharts-title", + ], + axisClass; + if (this.scrollablePixelsX && !this.inverted) { + axisClass = ".highcharts-yaxis"; + } else if (this.scrollablePixelsX && this.inverted) { + axisClass = ".highcharts-xaxis"; + } else if (this.scrollablePixelsY && !this.inverted) { + axisClass = ".highcharts-xaxis"; + } else if (this.scrollablePixelsY && this.inverted) { + axisClass = ".highcharts-yaxis"; + } + fixedSelectors.push(axisClass, axisClass + "-labels"); + fixedSelectors.forEach(function (className) { + [].forEach.call( + container.querySelectorAll(className), + function (elem) { + (elem.namespaceURI === fixedRenderer.SVG_NS + ? fixedRenderer.box + : fixedRenderer.box.parentNode + ).appendChild(elem); + elem.style.pointerEvents = "auto"; } + ); + }); + }; + /** + * @private + * @function Highcharts.Chart#applyFixed + * @return {void} + */ + Chart.prototype.applyFixed = function () { + var _a, _b; + var fixedRenderer, + scrollableWidth, + scrollableHeight, + firstTime = !this.fixedDiv, + scrollableOptions = this.options.chart.scrollablePlotArea; + // First render + if (firstTime) { + this.fixedDiv = createElement( + "div", + { + className: "highcharts-fixed", + }, + { + position: "absolute", + overflow: "hidden", + pointerEvents: "none", + zIndex: 2, + top: 0, + }, + null, + true + ); + (_a = this.scrollingContainer) === null || _a === void 0 + ? void 0 + : _a.parentNode.insertBefore( + this.fixedDiv, + this.scrollingContainer + ); + this.renderTo.style.overflow = "visible"; + this.fixedRenderer = fixedRenderer = new H.Renderer( + this.fixedDiv, + this.chartWidth, + this.chartHeight, + (_b = this.options.chart) === null || _b === void 0 + ? void 0 + : _b.style + ); + // Mask + this.scrollableMask = fixedRenderer + .path() + .attr({ + fill: this.options.chart.backgroundColor || "#fff", + "fill-opacity": pick(scrollableOptions.opacity, 0.85), + zIndex: -1, + }) + .addClass("highcharts-scrollable-mask") + .add(); + this.moveFixedElements(); + addEvent(this, "afterShowResetZoom", this.moveFixedElements); + addEvent(this, "afterLayOutTitles", this.moveFixedElements); + } else { + // Set the size of the fixed renderer to the visible width + this.fixedRenderer.setSize(this.chartWidth, this.chartHeight); + } + // Increase the size of the scrollable renderer and background + scrollableWidth = this.chartWidth + (this.scrollablePixelsX || 0); + scrollableHeight = this.chartHeight + (this.scrollablePixelsY || 0); + stop(this.container); + this.container.style.width = scrollableWidth + "px"; + this.container.style.height = scrollableHeight + "px"; + this.renderer.boxWrapper.attr({ + width: scrollableWidth, + height: scrollableHeight, + viewBox: [0, 0, scrollableWidth, scrollableHeight].join(" "), }); - /* eslint-enable valid-jsdoc */ - /** - * A `column` series. If the [type](#series.column.type) option is - * not specified, it is inherited from [chart.type](#chart.type). + this.chartBackground.attr({ + width: scrollableWidth, + height: scrollableHeight, + }); + this.scrollingContainer.style.height = this.chartHeight + "px"; + // Set scroll position + if (firstTime) { + if (scrollableOptions.scrollPositionX) { + this.scrollingContainer.scrollLeft = + this.scrollablePixelsX * scrollableOptions.scrollPositionX; + } + if (scrollableOptions.scrollPositionY) { + this.scrollingContainer.scrollTop = + this.scrollablePixelsY * scrollableOptions.scrollPositionY; + } + } + // Mask behind the left and right side + var axisOffset = this.axisOffset, + maskTop = this.plotTop - axisOffset[0] - 1, + maskLeft = this.plotLeft - axisOffset[3] - 1, + maskBottom = this.plotTop + this.plotHeight + axisOffset[2] + 1, + maskRight = this.plotLeft + this.plotWidth + axisOffset[1] + 1, + maskPlotRight = + this.plotLeft + this.plotWidth - (this.scrollablePixelsX || 0), + maskPlotBottom = + this.plotTop + this.plotHeight - (this.scrollablePixelsY || 0), + d; + if (this.scrollablePixelsX) { + d = [ + // Left side + ["M", 0, maskTop], + ["L", this.plotLeft - 1, maskTop], + ["L", this.plotLeft - 1, maskBottom], + ["L", 0, maskBottom], + ["Z"], + // Right side + ["M", maskPlotRight, maskTop], + ["L", this.chartWidth, maskTop], + ["L", this.chartWidth, maskBottom], + ["L", maskPlotRight, maskBottom], + ["Z"], + ]; + } else if (this.scrollablePixelsY) { + d = [ + // Top side + ["M", maskLeft, 0], + ["L", maskLeft, this.plotTop - 1], + ["L", maskRight, this.plotTop - 1], + ["L", maskRight, 0], + ["Z"], + // Bottom side + ["M", maskLeft, maskPlotBottom], + ["L", maskLeft, this.chartHeight], + ["L", maskRight, this.chartHeight], + ["L", maskRight, maskPlotBottom], + ["Z"], + ]; + } else { + d = [["M", 0, 0]]; + } + if (this.redrawTrigger !== "adjustHeight") { + this.scrollableMask.attr({ d: d }); + } + }; + } + ); + _registerModule( + _modules, + "Core/Axis/StackingAxis.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Utilities.js"], + ], + function (A, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var getDeferredAnimation = A.getDeferredAnimation; + var addEvent = U.addEvent, + destroyObjectProperties = U.destroyObjectProperties, + fireEvent = U.fireEvent, + objectEach = U.objectEach, + pick = U.pick; + /* eslint-disable valid-jsdoc */ + /** + * Adds stacking support to axes. + * @private + * @class + */ + var StackingAxisAdditions = /** @class */ (function () { + /* * * - * @extends series,plotOptions.column - * @excluding connectNulls, dataParser, dataURL, gapSize, gapUnit, linecap, - * lineWidth, marker, connectEnds, step - * @product highcharts highstock - * @apioption series.column - */ - /** - * 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 following snippet shows only a - * few settings, see the complete options set 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" - * }] - * ``` - * - * @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 - * - * @type {Array<number|Array<(number|string),(number|null)>|null|*>} - * @extends series.line.data - * @excluding marker - * @product highcharts highstock - * @apioption series.column.data - */ - /** - * The color of the border surrounding the column or bar. + * Constructors * - * In styled mode, the border stroke can be set with the `.highcharts-point` - * rule. + * */ + function StackingAxisAdditions(axis) { + this.oldStacks = {}; + this.stacks = {}; + this.stacksTouched = 0; + this.axis = axis; + } + /* * * - * @sample {highcharts} highcharts/plotoptions/column-bordercolor/ - * Dark gray border + * Functions * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @product highcharts highstock - * @apioption series.column.data.borderColor + * */ + /** + * Build the stacks from top down + * @private */ + StackingAxisAdditions.prototype.buildStacks = function () { + var stacking = this; + var axis = stacking.axis; + var axisSeries = axis.series; + var reversedStacks = pick(axis.options.reversedStacks, true); + var len = axisSeries.length; + var actualSeries, i; + if (!axis.isXAxis) { + stacking.usePercentage = false; + i = len; + while (i--) { + actualSeries = axisSeries[reversedStacks ? i : len - i - 1]; + actualSeries.setStackedPoints(); + actualSeries.setGroupedPoints(); + } + // Loop up again to compute percent and stream stack + for (i = 0; i < len; i++) { + axisSeries[i].modifyStacks(); + } + fireEvent(axis, "afterBuildStacks"); + } + }; /** - * The width of the border surrounding the column or bar. - * - * In styled mode, the stroke width can be set with the `.highcharts-point` - * rule. - * - * @sample {highcharts} highcharts/plotoptions/column-borderwidth/ - * 2px black border - * - * @type {number} - * @product highcharts highstock - * @apioption series.column.data.borderWidth + * @private */ + StackingAxisAdditions.prototype.cleanStacks = function () { + var stacking = this; + var axis = stacking.axis; + var stacks; + if (!axis.isXAxis) { + if (stacking.oldStacks) { + stacks = stacking.stacks = stacking.oldStacks; + } + // reset stacks + objectEach(stacks, function (type) { + objectEach(type, function (stack) { + stack.cumulative = stack.total; + }); + }); + } + }; /** - * A name for the dash style to use for the column or bar. Overrides - * dashStyle on the series. - * - * In styled mode, the stroke dash-array can be set with the same classes as - * listed under [data.color](#series.column.data.color). - * - * @see [series.pointWidth](#plotOptions.column.dashStyle) - * - * @type {Highcharts.DashStyleValue} - * @apioption series.column.data.dashStyle + * Set all the stacks to initial states and destroy unused ones. + * @private */ + StackingAxisAdditions.prototype.resetStacks = function () { + var stacking = this; + var axis = stacking.axis; + var stacks = stacking.stacks; + if (!axis.isXAxis) { + objectEach(stacks, function (type) { + objectEach(type, function (stack, key) { + // Clean up memory after point deletion (#1044, #4320) + if (stack.touched < stacking.stacksTouched) { + stack.destroy(); + delete type[key]; + // Reset stacks + } else { + stack.total = null; + stack.cumulative = null; + } + }); + }); + } + }; /** - * A pixel value specifying a fixed width for the column or bar. Overrides - * pointWidth on the series. The width effects the dimension that is not based - * on the point value. + * @private + */ + StackingAxisAdditions.prototype.renderStackTotals = function () { + var stacking = this; + var axis = stacking.axis; + var chart = axis.chart; + var renderer = chart.renderer; + var stacks = stacking.stacks; + var stackLabelsAnim = axis.options.stackLabels.animation; + var animationConfig = getDeferredAnimation(chart, stackLabelsAnim); + var stackTotalGroup = (stacking.stackTotalGroup = + stacking.stackTotalGroup || + renderer + .g("stack-labels") + .attr({ + visibility: "visible", + zIndex: 6, + opacity: 0, + }) + .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); + }); + }); + stackTotalGroup.animate( + { + opacity: 1, + }, + animationConfig + ); + }; + return StackingAxisAdditions; + })(); + /** + * Axis with stacking support. + * @private + * @class + */ + var StackingAxis = /** @class */ (function () { + function StackingAxis() {} + /* * * - * @see [series.pointWidth](#plotOptions.column.pointWidth) + * Static Functions * - * @type {number} - * @apioption series.column.data.pointWidth + * */ + /** + * Extends axis with stacking support. + * @private */ + StackingAxis.compose = function (AxisClass) { + var axisProto = AxisClass.prototype; + addEvent(AxisClass, "init", StackingAxis.onInit); + addEvent(AxisClass, "destroy", StackingAxis.onDestroy); + }; /** - * @excluding halo, lineWidth, lineWidthPlus, marker - * @product highcharts highstock - * @apioption series.column.states.hover + * @private */ + StackingAxis.onDestroy = function () { + var stacking = this.stacking; + if (!stacking) { + return; + } + var stacks = stacking.stacks; + // Destroy each stack total + objectEach(stacks, function (stack, stackKey) { + destroyObjectProperties(stack); + stacks[stackKey] = null; + }); + if (stacking && stacking.stackTotalGroup) { + stacking.stackTotalGroup.destroy(); + } + }; /** - * @excluding halo, lineWidth, lineWidthPlus, marker - * @product highcharts highstock - * @apioption series.column.states.select + * @private */ - ''; // includes above doclets in transpilat + StackingAxis.onInit = function () { + var axis = this; + if (!axis.stacking) { + axis.stacking = new StackingAxisAdditions(axis); + } + }; + return StackingAxis; + })(); - return ColumnSeries; - }); - _registerModule(_modules, 'Series/BarSeries.js', [_modules['Core/Series/Series.js']], function (Series) { - /* * - * - * (c) 2010-2020 Torstein Honsi + return StackingAxis; + } + ); + _registerModule( + _modules, + "Mixins/LegendSymbol.js", + [_modules["Core/Globals.js"], _modules["Core/Utilities.js"]], + function (H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var merge = U.merge, + pick = U.pick; + /* eslint-disable valid-jsdoc */ + /** + * Legend symbol mixin. + * + * @private + * @mixin Highcharts.LegendSymbolMixin + */ + var LegendSymbolMixin = (H.LegendSymbolMixin = { + /** + * Get the series' symbol in the legend * - * License: www.highcharts.com/license + * @private + * @function Highcharts.LegendSymbolMixin.drawRectangle * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @param {Highcharts.Legend} legend + * The legend object * - * */ - /** - * Bar series type. + * @param {Highcharts.Point|Highcharts.Series} 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. * * @private - * @class - * @name Highcharts.seriesTypes.bar + * @function Highcharts.LegendSymbolMixin.drawLineMarker * - * @augments Highcharts.Series + * @param {Highcharts.Legend} legend + * The legend object. */ - Series.seriesType('bar', 'column', + 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 + if (!this.chart.styledMode) { + 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 && symbolWidth) { + // 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; + } + }, + }); + + return LegendSymbolMixin; + } + ); + _registerModule( + _modules, + "Core/Series/CartesianSeries.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Series/Series.js"], + _modules["Core/Globals.js"], + _modules["Mixins/LegendSymbol.js"], + _modules["Core/Options.js"], + _modules["Core/Series/Point.js"], + _modules["Core/Renderer/SVG/SVGElement.js"], + _modules["Core/Utilities.js"], + ], + function (A, BaseSeries, H, LegendSymbolMixin, O, Point, SVGElement, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var animObject = A.animObject; + var defaultOptions = O.defaultOptions; + var addEvent = U.addEvent, + arrayMax = U.arrayMax, + arrayMin = U.arrayMin, + clamp = U.clamp, + correctFloat = U.correctFloat, + defined = U.defined, + erase = U.erase, + error = U.error, + extend = U.extend, + find = U.find, + fireEvent = U.fireEvent, + getNestedProperty = U.getNestedProperty, + isArray = U.isArray, + isFunction = U.isFunction, + isNumber = U.isNumber, + isString = U.isString, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick, + removeEvent = U.removeEvent, + splat = U.splat, + syncTimeout = U.syncTimeout; + /** + * This is a placeholder type of the possible series options for + * [Highcharts](../highcharts/series), [Highstock](../highstock/series), + * [Highmaps](../highmaps/series), and [Gantt](../gantt/series). + * + * In TypeScript is this dynamically generated to reference all possible types + * of series options. + * + * @ignore-declaration + * @typedef {Highcharts.SeriesOptions|Highcharts.Dictionary<*>} Highcharts.SeriesOptionsType + */ + /** + * Options for `dataSorting`. + * + * @interface Highcharts.DataSortingOptionsObject + * @since 8.0.0 + */ /** + * Enable or disable data sorting for the series. + * @name Highcharts.DataSortingOptionsObject#enabled + * @type {boolean|undefined} + */ /** + * Whether to allow matching points by name in an update. + * @name Highcharts.DataSortingOptionsObject#matchByName + * @type {boolean|undefined} + */ /** + * Determines what data value should be used to sort by. + * @name Highcharts.DataSortingOptionsObject#sortKey + * @type {string|undefined} + */ + /** + * Function callback when a series has been animated. + * + * @callback Highcharts.SeriesAfterAnimateCallbackFunction + * + * @param {Highcharts.Series} this + * The series where the event occured. + * + * @param {Highcharts.SeriesAfterAnimateEventObject} event + * Event arguments. + */ + /** + * Event information regarding completed animation of a series. + * + * @interface Highcharts.SeriesAfterAnimateEventObject + */ /** + * Animated series. + * @name Highcharts.SeriesAfterAnimateEventObject#target + * @type {Highcharts.Series} + */ /** + * Event type. + * @name Highcharts.SeriesAfterAnimateEventObject#type + * @type {"afterAnimate"} + */ + /** + * Function callback when the checkbox next to the series' name in the legend is + * clicked. + * + * @callback Highcharts.SeriesCheckboxClickCallbackFunction + * + * @param {Highcharts.Series} this + * The series where the event occured. + * + * @param {Highcharts.SeriesCheckboxClickEventObject} event + * Event arguments. + */ + /** + * Event information regarding check of a series box. + * + * @interface Highcharts.SeriesCheckboxClickEventObject + */ /** + * Whether the box has been checked. + * @name Highcharts.SeriesCheckboxClickEventObject#checked + * @type {boolean} + */ /** + * Related series. + * @name Highcharts.SeriesCheckboxClickEventObject#item + * @type {Highcharts.Series} + */ /** + * Related series. + * @name Highcharts.SeriesCheckboxClickEventObject#target + * @type {Highcharts.Series} + */ /** + * Event type. + * @name Highcharts.SeriesCheckboxClickEventObject#type + * @type {"checkboxClick"} + */ + /** + * Function callback when a series is clicked. Return false to cancel toogle + * actions. + * + * @callback Highcharts.SeriesClickCallbackFunction + * + * @param {Highcharts.Series} this + * The series where the event occured. + * + * @param {Highcharts.SeriesClickEventObject} event + * Event arguments. + */ + /** + * Common information for a click event on a series. + * + * @interface Highcharts.SeriesClickEventObject + * @extends global.Event + */ /** + * Nearest point on the graph. + * @name Highcharts.SeriesClickEventObject#point + * @type {Highcharts.Point} + */ + /** + * Gets fired when the series is hidden after chart generation time, either by + * clicking the legend item or by calling `.hide()`. + * + * @callback Highcharts.SeriesHideCallbackFunction + * + * @param {Highcharts.Series} this + * The series where the event occured. + * + * @param {global.Event} event + * The event that occured. + */ + /** + * The SVG value used for the `stroke-linecap` and `stroke-linejoin` of a line + * graph. + * + * @typedef {"butt"|"round"|"square"|string} Highcharts.SeriesLinecapValue + */ + /** + * Gets fired when the legend item belonging to the series is clicked. The + * default action is to toggle the visibility of the series. This can be + * prevented by returning `false` or calling `event.preventDefault()`. + * + * @callback Highcharts.SeriesLegendItemClickCallbackFunction + * + * @param {Highcharts.Series} this + * The series where the event occured. + * + * @param {Highcharts.SeriesLegendItemClickEventObject} event + * The event that occured. + */ + /** + * Information about the event. + * + * @interface Highcharts.SeriesLegendItemClickEventObject + */ /** + * Related browser event. + * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent + * @type {global.PointerEvent} + */ /** + * Prevent the default action of toggle the visibility of the series. + * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault + * @type {Function} + */ /** + * Related series. + * @name Highcharts.SeriesCheckboxClickEventObject#target + * @type {Highcharts.Series} + */ /** + * Event type. + * @name Highcharts.SeriesCheckboxClickEventObject#type + * @type {"checkboxClick"} + */ + /** + * Gets fired when the mouse leaves the graph. + * + * @callback Highcharts.SeriesMouseOutCallbackFunction + * + * @param {Highcharts.Series} this + * Series where the event occured. + * + * @param {global.PointerEvent} event + * Event that occured. + */ + /** + * Gets fired when the mouse enters the graph. + * + * @callback Highcharts.SeriesMouseOverCallbackFunction + * + * @param {Highcharts.Series} this + * Series where the event occured. + * + * @param {global.PointerEvent} event + * Event that occured. + */ + /** + * Translation and scale for the plot area of a series. + * + * @interface Highcharts.SeriesPlotBoxObject + */ /** + * @name Highcharts.SeriesPlotBoxObject#scaleX + * @type {number} + */ /** + * @name Highcharts.SeriesPlotBoxObject#scaleY + * @type {number} + */ /** + * @name Highcharts.SeriesPlotBoxObject#translateX + * @type {number} + */ /** + * @name Highcharts.SeriesPlotBoxObject#translateY + * @type {number} + */ + /** + * Gets fired when the series is shown after chart generation time, either by + * clicking the legend item or by calling `.show()`. + * + * @callback Highcharts.SeriesShowCallbackFunction + * + * @param {Highcharts.Series} this + * Series where the event occured. + * + * @param {global.Event} event + * Event that occured. + */ + /** + * Possible key values for the series state options. + * + * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.SeriesStateValue + */ + (""); // detach doclets above + var seriesTypes = BaseSeries.seriesTypes, + win = H.win; + /** + * This is the base series prototype that all other series types inherit from. + * A new series is initialized either through the + * [series](https://api.highcharts.com/highcharts/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 + * [plotOptions.series](https://api.highcharts.com/highcharts/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 + * arguments 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 + * @name Highcharts.Series + * + * @param {Highcharts.Chart} chart + * The chart instance. + * + * @param {Highcharts.SeriesOptionsType|object} options + * The series options. + */ /** + * The line series is the base type and is therefor the series base prototype. + * + * @private + * @class + * @name Highcharts.seriesTypes.line + * + * @augments Highcharts.Series + */ + var CartesianSeries = BaseSeries.seriesType( + "line", /** - * A bar series is a special type of column series where the columns are - * horizontal. - * - * @sample highcharts/demo/bar-basic/ - * Bar chart + * Series options for specific data and the data itself. In TypeScript you + * have to cast the series options to specific series types, + to get all + * possible options for a series. + * + * @example + * // TypeScript example + * Highcharts.chart('container', { + * series: [{ + * color: '#06C', + * data: [[0, 1], + [2, 3]] + * } as Highcharts.SeriesLineOptions ] + * }); * - * @extends plotOptions.column - * @product highcharts - * @apioption plotOptions.bar - */ - /** - * @ignore + * @type {Array<*>} + * @apioption series */ - null, { - inverted: true - }); /** - * A `bar` series. If the [type](#series.bar.type) option is not specified, - * it is inherited from [chart.type](#chart.type). + * An id for the series. This can be used after render time to get a pointer + * to the series object through `chart.get()`. * - * @extends series,plotOptions.bar - * @excluding connectNulls, dashStyle, dataParser, dataURL, gapSize, gapUnit, - * linecap, lineWidth, marker, connectEnds, step - * @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 following snippet shows only a - * few settings, see the complete options set 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" - * }] - * ``` - * - * @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 - * - * @type {Array<number|Array<(number|string),(number|null)>|null|*>} - * @extends series.column.data - * @product highcharts - * @apioption series.bar.data + * @sample {highcharts} highcharts/plotoptions/series-id/ + * Get series by id + * + * @type {string} + * @since 1.2.0 + * @apioption series.id */ /** - * @excluding halo,lineWidth,lineWidthPlus,marker - * @product highcharts highstock - * @apioption series.bar.states.hover + * 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} + * @since 2.3.0 + * @apioption series.index */ /** - * @excluding halo,lineWidth,lineWidthPlus,marker - * @product highcharts highstock - * @apioption series.bar.states.select - */ - ''; // gets doclets above into transpilat - - }); - _registerModule(_modules, 'Series/ScatterSeries.js', [_modules['Core/Series/Series.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (BaseSeries, H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * The sequential index of the series in the legend. * - * */ - var addEvent = U.addEvent; - var Series = H.Series; - /** - * Scatter series type. + * @see [legend.reversed](#legend.reversed), + * [yAxis.reversedStacks](#yAxis.reversedStacks) * - * @private - * @class - * @name Highcharts.seriesTypes.scatter + * @sample {highcharts|highstock} highcharts/series/legendindex/ + * Legend in opposite order * - * @augments Highcharts.Series + * @type {number} + * @apioption series.legendIndex */ - BaseSeries.seriesType('scatter', 'line', /** - * A scatter plot uses cartesian coordinates to display values for two - * variables for a set of data. + * The name of the series as shown in the legend, tooltip etc. * - * @sample {highcharts} highcharts/demo/scatter/ - * Scatter plot + * @sample {highcharts} highcharts/series/name/ + * Series name + * @sample {highmaps} maps/demo/category-map/ + * Series name * - * @extends plotOptions.line - * @excluding cropThreshold, pointPlacement, shadow, useOhlcData - * @product highcharts highstock - * @optionparent plotOptions.scatter + * @type {string} + * @apioption series.name */ - { - /** - * The width of the line connecting the data points. - * - * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/ - * 0 by default - * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/ - * 1px - * - * @product highcharts highstock - */ - lineWidth: 0, - findNearestPointBy: 'xy', - /** - * Apply a jitter effect for the rendered markers. When plotting - * discrete values, a little random noise may help telling the points - * apart. The jitter setting applies a random displacement of up to `n` - * axis units in either direction. So for example on a horizontal X - * axis, setting the `jitter.x` to 0.24 will render the point in a - * random position between 0.24 units to the left and 0.24 units to the - * right of the true axis position. On a category axis, setting it to - * 0.5 will fill up the bin and make the data appear continuous. - * - * When rendered on top of a box plot or a column series, a jitter value - * of 0.24 will correspond to the underlying series' default - * [groupPadding]( - * https://api.highcharts.com/highcharts/plotOptions.column.groupPadding) - * and [pointPadding]( - * https://api.highcharts.com/highcharts/plotOptions.column.pointPadding) - * settings. - * - * @sample {highcharts} highcharts/series-scatter/jitter - * Jitter on a scatter plot - * - * @sample {highcharts} highcharts/series-scatter/jitter-boxplot - * Jittered scatter plot on top of a box plot - * - * @product highcharts highstock - * @since 7.0.2 - */ - jitter: { - /** - * The maximal X offset for the random jitter effect. - */ - x: 0, - /** - * The maximal Y offset for the random jitter effect. - */ - y: 0 - }, - 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](#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: '<span style="color:{point.color}">\u25CF</span> ' + - '<span style="font-size: 10px"> {series.name}</span><br/>', - pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>' - } - // Prototype members - }, { - sorted: false, - requireSorting: false, - noSharedTooltip: true, - trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'], - takeOrdinalPosition: false, - /* eslint-disable valid-jsdoc */ - /** - * @private - * @function Highcharts.seriesTypes.scatter#drawGraph - */ - drawGraph: function () { - if (this.options.lineWidth || - // In case we have a graph from before and we update the line - // width to 0 (#13816) - (this.options.lineWidth === 0 && - this.graph && - this.graph.strokeWidth())) { - Series.prototype.drawGraph.call(this); - } - }, - // Optionally add the jitter effect - applyJitter: function () { - var series = this, - jitter = this.options.jitter, - len = this.points.length; - /** - * Return a repeatable, pseudo-random number based on an integer - * seed. - * @private - */ - function unrandom(seed) { - var rand = Math.sin(seed) * 10000; - return rand - Math.floor(rand); - } - if (jitter) { - this.points.forEach(function (point, i) { - ['x', 'y'].forEach(function (dim, j) { - var axis, - plotProp = 'plot' + dim.toUpperCase(), - min, - max, - translatedJitter; - if (jitter[dim] && !point.isNull) { - axis = series[dim + 'Axis']; - translatedJitter = - jitter[dim] * axis.transA; - if (axis && !axis.isLog) { - // Identify the outer bounds of the jitter range - min = Math.max(0, point[plotProp] - translatedJitter); - max = Math.min(axis.len, point[plotProp] + translatedJitter); - // Find a random position within this range - point[plotProp] = min + - (max - min) * unrandom(i + j * len); - // Update clientX for the tooltip k-d-tree - if (dim === 'x') { - point.clientX = point.plotX; - } - } - } - }); - }); - } - } - /* eslint-enable valid-jsdoc */ - }); - /* eslint-disable no-invalid-this */ - addEvent(Series, 'afterTranslate', function () { - if (this.applyJitter) { - this.applyJitter(); - } - }); - /* eslint-enable no-invalid-this */ /** - * A `scatter` series. If the [type](#series.scatter.type) option is - * not specified, it is inherited from [chart.type](#chart.type). - * - * @extends series,plotOptions.scatter - * @excluding cropThreshold, dataParser, dataURL, useOhlcData - * @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 following snippet shows only a - * few settings, see the complete options set 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" - * }] - * ``` - * - * @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 - * - * @type {Array<number|Array<(number|string),(number|null)>|null|*>} - * @extends series.line.data - * @product highcharts highstock - * @apioption series.scatter.data - */ - ''; // adds doclets above to transpilat - - }); - _registerModule(_modules, 'Mixins/CenteredSeries.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * This option allows grouping series in a stacked chart. The stack option + * can be a string or anything else, as long as the grouped series' stack + * options match each other after conversion into a string. * - * */ - /** - * @private - * @interface Highcharts.RadianAngles - */ /** - * @name Highcharts.RadianAngles#end - * @type {number} - */ /** - * @name Highcharts.RadianAngles#start - * @type {number} - */ - var isNumber = U.isNumber, - pick = U.pick, - relativeLength = U.relativeLength; - var deg2rad = H.deg2rad; - /* eslint-disable valid-jsdoc */ - /** - * @private - * @mixin Highcharts.CenteredSeriesMixin - */ - var centeredSeriesMixin = 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. - * - * @private - * @function Highcharts.CenteredSeriesMixin.getCenter - * - * @return {Array<number>} - */ - 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, - smallestSize = Math.min(plotWidth, - plotHeight), - size = options.size, - innerSize = options.innerSize || 0, - positions, - i, - value; - if (typeof size === 'string') { - size = parseFloat(size); - } - if (typeof innerSize === 'string') { - innerSize = parseFloat(innerSize); - } - positions = [ - pick(centerOption[0], '50%'), - pick(centerOption[1], '50%'), - // Prevent from negative values - pick(size && size < 0 ? void 0 : options.size, '100%'), - pick(innerSize && innerSize < 0 ? void 0 : options.innerSize || 0, '0%') - ]; - // No need for inner size in angular (gauges) series but still required - // for pie series - if (chart.angular && !(this instanceof H.Series)) { - positions[3] = 0; - } - 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. - * - * @private - * @function Highcharts.CenteredSeriesMixin.getStartAndEndRadians - * - * @param {number} [start] - * Start angle in degrees. - * - * @param {number} [end] - * Start angle in degrees. - * - * @return {Highcharts.RadianAngles} - * Returns an object containing start and end angles as radians. - */ - getStartAndEndRadians: function (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) - }; - } - }; - - return centeredSeriesMixin; - }); - _registerModule(_modules, 'Series/PieSeries.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Series/Series.js'], _modules['Mixins/CenteredSeries.js'], _modules['Core/Globals.js'], _modules['Mixins/LegendSymbol.js'], _modules['Series/LineSeries.js'], _modules['Core/Series/Point.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Utilities.js']], function (A, BaseSeries, CenteredSeriesMixin, H, LegendSymbolMixin, LineSeries, Point, SVGRenderer, U) { - /* * + * @sample {highcharts} highcharts/series/stack/ + * Stacked and grouped columns * - * (c) 2010-2020 Torstein Honsi + * @type {number|string} + * @since 2.1 + * @product highcharts highstock + * @apioption series.stack + */ + /** + * 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. * - * License: www.highcharts.com/license + * @sample {highcharts} highcharts/series/type/ + * Line and column in the same chart + * @sample highcharts/series/type-dynamic/ + * Dynamic types with button selector + * @sample {highmaps} maps/demo/mapline-mappoint/ + * Multiple types in the same map * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @type {string} + * @apioption series.type + */ + /** + * When using dual or multiple x axes, this number defines which xAxis the + * particular series is connected to. It refers to either the + * {@link #xAxis.id|axis id} + * or the index of the axis in the xAxis array, with 0 being the first. * - * */ - var setAnimation = A.setAnimation; - var getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians; - var noop = H.noop; - var addEvent = U.addEvent, - clamp = U.clamp, - defined = U.defined, - fireEvent = U.fireEvent, - isNumber = U.isNumber, - merge = U.merge, - pick = U.pick, - relativeLength = U.relativeLength; - /** - * Pie series type. + * @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 + * {@link #yAxis.id|axis id} + * or the index of the axis in the yAxis array, with 0 being the first. * - * @private - * @class - * @name Highcharts.seriesTypes.pie + * @sample {highcharts} highcharts/series/yaxis/ + * Apply the column series to the secondary Y axis * - * @augments Highcharts.Series + * @type {number|string} + * @default 0 + * @product highcharts highstock + * @apioption series.yAxis */ - BaseSeries.seriesType('pie', 'line', /** - * A pie chart is a circular graphic which is divided into slices to - * illustrate numerical proportion. + * Define the visual z index of the series. * - * @sample highcharts/demo/pie-basic/ - * Pie chart + * @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 * - * @extends plotOptions.line - * @excluding animationLimit, boostThreshold, connectEnds, connectNulls, - * cropThreshold, dashStyle, dataSorting, dragDrop, - * findNearestPointBy, getExtremesFromAll, label, lineWidth, - * marker, negativeColor, pointInterval, pointIntervalUnit, - * pointPlacement, pointStart, softThreshold, stacking, step, - * threshold, turboThreshold, zoneAxis, zones, dataSorting, - * boostBlending - * @product highcharts - * @optionparent plotOptions.pie + * @type {number} + * @product highcharts highstock + * @apioption series.zIndex + */ + void 0, + /** + * General options for all series types. + * + * @optionparent plotOptions.series */ { - /** - * @excluding legendItemClick - * @apioption plotOptions.pie.events + /** + * 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. + * + * @type {Highcharts.SeriesLinecapValue} + * @default round + * @since 3.0.7 + * @apioption plotOptions.line.linecap + */ + /** + * Pixel width of the graph line. + * + * @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 + * + * @product highcharts highstock + * + * @private + */ + 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 does + * not 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). + * + * The selected points can be handled by point select and unselect + * events, or collectively by the [getSelectedPoints + * ](/class-reference/Highcharts.Chart#getSelectedPoints) function. + * + * And alternative way of selecting points is through dragging. + * + * @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 {highcharts} highcharts/chart/events-selection-points/ + * Select a range of points through a drag selection + * @sample {highmaps} maps/plotoptions/series-allowpointselect/ + * Map area + * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/ + * Map bubble + * + * @since 1.2.0 + * + * @private + */ + allowPointSelect: false, + /** + * When true, each point or 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. + * + * @sample {highcharts} highcharts/plotoptions/column-crisp-false/ + * Crisp is false + * + * @since 5.0.10 + * @product highcharts highstock gantt + * + * @private + */ + crisp: true, + /** + * 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. + * + * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/ + * Show select box + * + * @since 1.2.0 + * + * @private + */ + 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: + * + * - `defer`: The animation delay time in milliseconds. + * + * - `duration`: The duration of the animation in milliseconds. + * + * - `easing`: Can be a string reference to an easing function set on + * the `Math` object or a function. See the _Custom easing function_ + * demo below. + * + * Due to poor performance, animation is disabled in old IE browsers + * for several chart types. + * + * @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 + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + * @default {highcharts} true + * @default {highstock} true + * @default {highmaps} false + * + * @private + */ + animation: { + /** @internal */ + duration: 1000, + }, + /** + * @default 0 + * @type {number} + * @since 8.2.0 + * @apioption plotOptions.series.animation.defer + */ + /** + * An additional class name to apply to the series' graphical elements. + * This option does not replace default class names of the graphical + * element. + * + * @type {string} + * @since 5.0.0 + * @apioption plotOptions.series.className + */ + /** + * Disable this option to allow series rendering in the whole plotting + * area. + * + * **Note:** Clipping should be always enabled when + * [chart.zoomType](#chart.zoomType) is set + * + * @sample {highcharts} highcharts/plotoptions/series-clip/ + * Disabled clipping + * + * @default true + * @type {boolean} + * @since 3.0.0 + * @apioption plotOptions.series.clip + */ + /** + * 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. + * + * @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 {highcharts} highcharts/series/infographic/ + * Pattern fill + * @sample {highmaps} maps/demo/category-map/ + * Category map by multiple series + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @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. + * + * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/ + * False by default + * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/ + * True + * + * @type {boolean} + * @default false + * @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. + * + * 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 + * + * @type {string|Highcharts.CursorValue} + * @apioption plotOptions.series.cursor + */ + /** + * A reserved subspace to store options and values for customized + * functionality. Here you can add additional data for your own event + * callbacks and formatter callbacks. + * + * @sample {highcharts} highcharts/point/custom/ + * Point and series with custom data + * + * @type {Highcharts.Dictionary<*>} + * @apioption plotOptions.series.custom + */ + /** + * Name of the dash style to use for the graph, or for some series types + * the outline of each shape. + * + * In styled mode, the + * [stroke dash-array](https://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 + * + * @type {Highcharts.DashStyleValue} + * @default Solid + * @since 2.1 + * @apioption plotOptions.series.dashStyle + */ + /** + * A description of the series to add to the screen reader information + * about the series. + * + * @type {string} + * @since 5.0.0 + * @requires modules/accessibility + * @apioption plotOptions.series.description + */ + /** + * Options for the series data sorting. + * + * @type {Highcharts.DataSortingOptionsObject} + * @since 8.0.0 + * @product highcharts highstock + * @apioption plotOptions.series.dataSorting + */ + /** + * Enable or disable data sorting for the series. Use [xAxis.reversed]( + * #xAxis.reversed) to change the sorting order. + * + * @sample {highcharts} highcharts/datasorting/animation/ + * Data sorting in scatter-3d + * @sample {highcharts} highcharts/datasorting/labels-animation/ + * Axis labels animation + * @sample {highcharts} highcharts/datasorting/dependent-sorting/ + * Dependent series sorting + * @sample {highcharts} highcharts/datasorting/independent-sorting/ + * Independent series sorting + * + * @type {boolean} + * @since 8.0.0 + * @apioption plotOptions.series.dataSorting.enabled + */ + /** + * Whether to allow matching points by name in an update. If this option + * is disabled, points will be matched by order. + * + * @sample {highcharts} highcharts/datasorting/match-by-name/ + * Enabled match by name + * + * @type {boolean} + * @since 8.0.0 + * @apioption plotOptions.series.dataSorting.matchByName + */ + /** + * Determines what data value should be used to sort by. + * + * @sample {highcharts} highcharts/datasorting/sort-key/ + * Sort key as `z` value + * + * @type {string} + * @since 8.0.0 + * @default y + * @apioption plotOptions.series.dataSorting.sortKey + */ + /** + * 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. + * + * @sample {highcharts} highcharts/plotoptions/series-enablemousetracking-false/ + * No mouse tracking + * @sample {highmaps} maps/plotoptions/series-enablemousetracking-false/ + * No mouse tracking + * + * @type {boolean} + * @default true + * @apioption plotOptions.series.enableMouseTracking + */ + /** + * 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 gantt + * @apioption plotOptions.series.getExtremesFromAll + */ + /** + * 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. + * + * @see [series.data](#series.line.data) + * + * @sample {highcharts|highstock} highcharts/series/data-keys/ + * An extended data array with keys + * @sample {highcharts|highstock} highcharts/series/data-nested-keys/ + * Nested keys used to access object properties + * + * @type {Array<string>} + * @since 4.1.6 + * @apioption plotOptions.series.keys + */ + /** + * The line cap used for line ends and line joins on the graph. + * + * @type {Highcharts.SeriesLinecapValue} + * @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. + * + * If master series uses data sorting and linked series does not have + * its own sorting definition, the linked series will be sorted in the + * same order as the master one. + * + * @sample {highcharts|highstock} highcharts/demo/arearange-line/ + * Linked series + * + * @type {string} + * @since 3.0 + * @product highcharts highstock gantt + * @apioption plotOptions.series.linkedTo + */ + /** + * 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. + * + * @see [navigator.series](#navigator.series) + * + * @type {Highcharts.PlotSeriesOptions} + * @since 5.0.0 + * @product highstock + * @apioption plotOptions.series.navigatorOptions + */ + /** + * The color for the parts of the graph or points that are below the + * [threshold](#plotOptions.series.threshold). Note that `zones` takes + * precedence over the negative color. Using `negativeColor` is + * equivalent to applying a zone with value of 0. + * + * @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 + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @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. + * + * Please note that this options applies to the _series data_, not the + * interval of the axis ticks, which is independent. + * + * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/ + * Datetime X axis + * @sample {highstock} stock/plotoptions/pointinterval-pointstart/ + * Using pointStart and pointInterval + * + * @type {number} + * @default 1 + * @product highcharts highstock gantt + * @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. + * + * Please note that this options applies to the _series data_, not the + * interval of the axis ticks, which is independent. + * + * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/ + * One point a month + * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/ + * One point a month + * + * @type {string} + * @since 4.1.0 + * @product highcharts highstock gantt + * @validvalue ["day", "month", "year"] + * @apioption plotOptions.series.pointIntervalUnit + */ + /** + * Possible values: `"on"`, `"between"`, `number`. + * + * 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. + * + * For the `xrange` series type and gantt charts, if the Y axis is a + * category axis, the `pointPlacement` applies to the Y axis rather than + * the (typically datetime) X axis. + * + * Defaults to `undefined` in cartesian charts, `"between"` in polar + * charts. + * + * @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 + * @sample {highcharts|highstock} maps/plotoptions/heatmap-pointplacement/ + * Placement in heatmap + * + * @type {string|number} + * @since 2.3.0 + * @product highcharts highstock gantt + * @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. + * + * @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 + * + * @type {number} + * @default 0 + * @product highcharts highstock gantt + * @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. + * + * @sample {highcharts} highcharts/plotoptions/series-selected/ + * One out of two series selected + * + * @type {boolean} + * @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`. + * + * @sample {highcharts} highcharts/plotoptions/series-shadow/ + * Shadow enabled + * + * @type {boolean|Highcharts.ShadowOptionsObject} + * @default false + * @apioption plotOptions.series.shadow + */ + /** + * Whether to display this particular series or series type in the + * legend. Standalone series are shown in legend by default, and linked + * series are not. Since v7.2.0 it is possible to show series that use + * colorAxis by setting this option to `true`. + * + * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ + * One series in the legend, one hidden + * + * @type {boolean} + * @apioption plotOptions.series.showInLegend + */ + /** + * Whether or not to show the series in the navigator. Takes precedence + * over [navigator.baseSeries](#navigator.baseSeries) if defined. + * + * @type {boolean} + * @since 5.0.0 + * @product highstock + * @apioption plotOptions.series.showInNavigator + */ + /** + * 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 + */ + /** + * Whether to stack the values of each series on top of each other. + * Possible values are `undefined` to disable, `"normal"` to stack by + * value or `"percent"`. + * + * When stacking is enabled, data must be sorted + * in ascending X order. + * + * Some stacking options are related to specific series types. In the + * streamgraph series type, the stacking option is set to `"stream"`. + * The second one is `"overlap"`, which only applies to waterfall + * series. + * + * @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 {highcharts} highcharts/plotoptions/series-waterfall-with-normal-stacking + * Waterfall with normal stacking + * @sample {highcharts} highcharts/plotoptions/series-waterfall-with-overlap-stacking + * Waterfall with overlap stacking + * @sample {highstock} stock/plotoptions/stacking/ + * Area + * + * @type {string} + * @product highcharts highstock + * @validvalue ["normal", "overlap", "percent", "stream"] + * @apioption plotOptions.series.stacking + */ + /** + * Whether to apply steps to the line. Possible values are `left`, + * `center` and `right`. + * + * @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 + * + * @type {string} + * @since 1.2.5 + * @product highcharts highstock + * @validvalue ["left", "center", "right"] + * @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). + * + * @see [softThreshold](#plotOptions.series.softThreshold). + * + * @type {number} + * @default 0 + * @since 3.0 + * @product highcharts highstock + * @apioption plotOptions.series.threshold + */ + /** + * Set the initial visibility of the series. + * + * @sample {highcharts} highcharts/plotoptions/series-visible/ + * Two series, one hidden and one visible + * @sample {highstock} stock/plotoptions/series-visibility/ + * Hidden series + * + * @type {boolean} + * @default true + * @apioption plotOptions.series.visible + */ + /** + * Defines the Axis on which the zones are applied. + * + * @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 + * + * @type {string} + * @default y + * @since 4.1.0 + * @product highcharts highstock + * @apioption plotOptions.series.zoneAxis + */ + /** + * 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. + * + * @declare Highcharts.SeriesEventsOptionsObject + * + * @private + */ + events: {}, + /** + * Fires after the series has finished its initial animation, or in case + * animation is disabled, immediately as the series is displayed. + * + * @sample {highcharts} highcharts/plotoptions/series-events-afteranimate/ + * Show label after animate + * @sample {highstock} highcharts/plotoptions/series-events-afteranimate/ + * Show label after animate + * + * @type {Highcharts.SeriesAfterAnimateCallbackFunction} + * @since 4.0 + * @product highcharts highstock gantt + * @context Highcharts.Series + * @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. + * + * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/ + * Alert checkbox status + * + * @type {Highcharts.SeriesCheckboxClickCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Series + * @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. + * + * @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 + * + * @type {Highcharts.SeriesClickCallbackFunction} + * @context Highcharts.Series + * @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()`. + * + * @sample {highcharts} highcharts/plotoptions/series-events-hide/ + * Alert when the series is hidden by clicking the legend item + * + * @type {Highcharts.SeriesHideCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Series + * @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()`. + * + * @sample {highcharts} highcharts/plotoptions/series-events-legenditemclick/ + * Confirm hiding and showing + * + * @type {Highcharts.SeriesLegendItemClickCallbackFunction} + * @context Highcharts.Series + * @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. + * + * @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 + * + * @type {Highcharts.SeriesMouseOutCallbackFunction} + * @context Highcharts.Series + * @apioption plotOptions.series.events.mouseOut + */ + /** + * Fires when the mouse enters the graph. One parameter, `event`, is + * passed to the function, containing common event information. + * + * @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 + * + * @type {Highcharts.SeriesMouseOverCallbackFunction} + * @context Highcharts.Series + * @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()`. + * + * @sample {highcharts} highcharts/plotoptions/series-events-show/ + * Alert when the series is shown by clicking the legend item. + * + * @type {Highcharts.SeriesShowCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Series + * @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. + * + * @declare Highcharts.PointMarkerOptionsObject + * + * @private + */ + marker: { + /** + * Enable or disable the point marker. If `undefined`, the markers + * are hidden when the data is dense, and shown for more widespread + * data points. + * + * @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 + * + * @type {boolean} + * @default {highcharts} undefined + * @default {highstock} false + * @apioption plotOptions.series.marker.enabled */ /** - * 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. + * The threshold for how dense the point markers should be before + * they are hidden, given that `enabled` is not defined. The number + * indicates the horizontal distance between the two closest points + * in the series, as multiples of the `marker.radius`. In other + * words, the default value of 2 means points are hidden if + * overlapping horizontally. * - * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/ - * Alert checkbox status + * @sample highcharts/plotoptions/series-marker-enabledthreshold + * A higher threshold * - * @type {Function} - * @since 1.2.0 - * @product highcharts - * @context Highcharts.Point - * @apioption plotOptions.pie.events.checkboxClick + * @since 6.0.5 */ + enabledThreshold: 2, /** - * 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()`. + * The fill color of the point marker. When `undefined`, the series' + * or point's color is used. * - * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/ - * Confirm toggle visibility + * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ + * White fill * - * @type {Highcharts.PointLegendItemClickCallbackFunction} - * @since 1.2.0 - * @product highcharts - * @apioption plotOptions.pie.point.events.legendItemClick + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @apioption plotOptions.series.marker.fillColor */ /** - * 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%"]`. - * - * @sample {highcharts} highcharts/plotoptions/pie-center/ - * Centered at 100, 100 - * - * @type {Array<(number|string|null),(number|string|null)>} - * @default [null, null] - * @product highcharts + * Image markers only. Set the image width explicitly. When using + * this option, a `width` must also be set. * - * @private + * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/ + * Fixed width and height + * @sample {highstock} highcharts/plotoptions/series-marker-width-height/ + * Fixed width and height + * + * @type {number} + * @since 4.0.4 + * @apioption plotOptions.series.marker.height */ - center: [null, null], /** - * The color of the pie series. A pie series is represented as an empty - * circle if the total sum of its values is 0. Use this property to - * define the color of its border. + * The color of the point marker's outline. When `undefined`, the + * series' or point's color is used. * - * 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. + * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ + * Inherit from series color (undefined) * - * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/ - * Empty pie series + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + */ + lineColor: "#ffffff", + /** + * The width of the point marker's outline. * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @default #cccccc - * @apioption plotOptions.pie.color + * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ + * 2px blue marker */ + lineWidth: 0, /** - * @product highcharts + * The radius of the point marker. * - * @private + * @sample {highcharts} highcharts/plotoptions/series-marker-radius/ + * Bigger markers + * + * @default {highstock} 2 */ - clip: false, + radius: 4, /** - * @ignore-option + * A predefined shape or symbol for the marker. When undefined, the + * symbol is pulled from options.symbols. Other possible values are + * `'circle'`, `'square'`,`'diamond'`, `'triangle'` and + * `'triangle-down'`. * - * @private + * 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. + * + * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/ + * Predefined, graphic and custom markers + * @sample {highstock} highcharts/plotoptions/series-marker-symbol/ + * Predefined, graphic and custom markers + * + * @type {string} + * @apioption plotOptions.series.marker.symbol */ - colorByPoint: true, /** - * A series specific or series type specific color set to use instead - * of the global [colors](#colors). + * Image markers only. Set the image width explicitly. When using + * this option, a `height` must also be set. * - * @sample {highcharts} highcharts/demo/pie-monochrome/ - * Set default colors for all pies + * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/ + * Fixed width and height + * @sample {highstock} highcharts/plotoptions/series-marker-width-height/ + * Fixed width and height * - * @type {Array<Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject>} - * @since 3.0 - * @product highcharts - * @apioption plotOptions.pie.colors + * @type {number} + * @since 4.0.4 + * @apioption plotOptions.series.marker.width */ /** - * @declare Highcharts.SeriesPieDataLabelsOptionsObject - * @extends plotOptions.series.dataLabels - * @excluding align, allowOverlap, inside, staggerLines, step - * @private + * States for a single point marker. + * + * @declare Highcharts.PointStatesOptionsObject */ - dataLabels: { + states: { + /** + * The normal state of a single point marker. Currently only + * used for setting animation when returning to normal state + * from hover. + * + * @declare Highcharts.PointStatesNormalOptionsObject + */ + normal: { + /** + * Animation when returning to normal state after hovering. + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + */ + animation: true, + }, + /** + * The hover state for a single point marker. + * + * @declare Highcharts.PointStatesHoverOptionsObject + */ + hover: { + /** + * Animation when hovering over the marker. + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + */ + animation: { + /** @internal */ + duration: 50, + }, /** - * Alignment method for data labels. Possible values are: - * - * - `toPlotEdges`: Each label touches the nearest vertical edge of - * the plot area. - * - * - `connectors`: Connectors have the same x position and the - * widest label of each half (left & right) touches the nearest - * vertical edge of the plot area. - * - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-connectors/ - * alignTo: connectors - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-plotedges/ - * alignTo: plotEdges + * Enable or disable the point marker. * - * @type {string} - * @since 7.0.0 - * @product highcharts - * @apioption plotOptions.pie.dataLabels.alignTo + * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-enabled/ + * Disabled hover state */ - allowOverlap: true, + enabled: true, /** - * 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. - * - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ - * Blue connectors - * @sample {highcharts} highcharts/css/pie-point/ - * Styled connectors + * The fill color of the marker in hover state. When + * `undefined`, the series' or point's fillColor for normal + * state is used. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @since 2.1 - * @product highcharts - * @apioption plotOptions.pie.dataLabels.connectorColor + * @apioption plotOptions.series.marker.states.hover.fillColor */ /** - * The distance from the data label to the connector. Note that - * data labels also have a default `padding`, so in order for the - * connector to touch the text, the `padding` must also be 0. + * The color of the point marker's outline. When + * `undefined`, the series' or point's lineColor for normal + * state is used. * - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ - * No padding + * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linecolor/ + * White fill color, black line color * - * @since 2.1 - * @product highcharts + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @apioption plotOptions.series.marker.states.hover.lineColor */ - connectorPadding: 5, /** - * Specifies the method that is used to generate the connector path. - * Highcharts provides 3 built-in connector shapes: `'fixedOffset'` - * (default), `'straight'` and `'crookedLine'`. Using - * `'crookedLine'` has the most sense (in most of the cases) when - * `'alignTo'` is set. + * The width of the point marker's outline. When + * `undefined`, the series' or point's lineWidth for normal + * state is used. * - * Users can provide their own method by passing a function instead - * of a String. 3 arguments are passed to the callback: + * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linewidth/ + * 3px line width * - * - Object that holds the information about the coordinates of the - * label (`x` & `y` properties) and how the label is located in - * relation to the pie (`alignment` property). `alignment` can by - * one of the following: - * `'left'` (pie on the left side of the data label), - * `'right'` (pie on the right side of the data label) or - * `'center'` (data label overlaps the pie). - * - * - Object that holds the information about the position of the - * connector. Its `touchingSliceAt` porperty tells the position - * of the place where the connector touches the slice. - * - * - Data label options - * - * The function has to return an SVG path definition in array form - * (see the example). + * @type {number} + * @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. * - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-string/ - * connectorShape is a String - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-function/ - * connectorShape is a function + * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/ + * 10px radius * - * @type {string|Function} - * @since 7.0.0 - * @product highcharts + * @type {number} + * @apioption plotOptions.series.marker.states.hover.radius */ - connectorShape: 'fixedOffset', /** - * 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. + * The number of pixels to increase the radius of the + * hovered point. * - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ - * Disable the connector - * @sample {highcharts} highcharts/css/pie-point/ - * Styled connectors + * @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 * - * @type {number} - * @default 1 - * @since 2.1 - * @product highcharts - * @apioption plotOptions.pie.dataLabels.connectorWidth + * @since 4.0.3 */ + radiusPlus: 2, /** - * Works only if `connectorShape` is `'crookedLine'`. It defines how - * far from the vertical plot edge the coonnector path should be - * crooked. + * The additional line width for a hovered point. * - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-crookdistance/ - * crookDistance set to 90% + * @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 * - * @since 7.0.0 - * @product highcharts + * @since 4.0.3 */ - crookDistance: '70%', + 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. + * + * @declare Highcharts.PointStatesSelectOptionsObject + */ + select: { /** - * The distance of the data label from the pie's edge. Negative - * numbers put the data label on top of the pie slices. Can also be - * defined as a percentage of pie's radius. Connectors are only - * shown for data labels outside the pie. + * Enable or disable visible feedback for selection. * - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ - * Data labels on top of the pie + * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/ + * Disabled select state * - * @type {number|string} - * @since 2.1 - * @product highcharts + * @type {boolean} + * @default true + * @apioption plotOptions.series.marker.states.select.enabled */ - distance: 30, - enabled: true, /** - * A - * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) - * for the data label. Available variables are the same as for - * `formatter`. + * The radius of the point marker. In hover state, it + * defaults to the normal state's radius + 2. * - * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ - * Add a unit + * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/ + * 10px radius for selected points * - * @type {string} - * @default undefined - * @since 3.0 - * @apioption plotOptions.pie.dataLabels.format + * @type {number} + * @apioption plotOptions.series.marker.states.select.radius */ - // eslint-disable-next-line valid-jsdoc /** - * Callback JavaScript function to format the data label. Note that - * if a `format` is defined, the format takes precedence and the - * formatter is ignored. + * The fill color of the point marker. * - * @type {Highcharts.DataLabelsFormatterCallbackFunction} - * @default function () { return this.point.isNull ? void 0 : this.point.name; } + * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/ + * Solid red discs for selected points + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ - formatter: function () { - return this.point.isNull ? void 0 : this.point.name; - }, + fillColor: "#cccccc", /** - * Whether to render the connector as a soft arc or a line with - * sharp break. Works only if `connectorShape` equals to - * `fixedOffset`. + * The color of the point marker's outline. When + * `undefined`, the series' or point's color is used. * - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ - * Soft - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ - * Non soft + * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/ + * Red line color for selected points * - * @since 2.1.7 - * @product highcharts + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ - softConnector: true, + lineColor: "#000000", /** - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow - * Long labels truncated with an ellipsis - * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap - * Long labels are wrapped + * The width of the point marker's outline. * - * @type {Highcharts.CSSObject} - * @apioption plotOptions.pie.dataLabels.style + * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/ + * 3px line width for selected points */ - x: 0 + lineWidth: 2, + }, }, + }, + /** + * Properties for each single point. + * + * @declare Highcharts.PlotSeriesPointOptions + * + * @private + */ + point: { + /** + * 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. + * + * @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 + * + * @type {Highcharts.PointClickCallbackFunction} + * @context Highcharts.Point + * @apioption plotOptions.series.point.events.click + */ /** - * If the total sum of the pie's values is 0, the series is represented - * as an empty circle . The `fillColor` option defines the color of that - * circle. Use [pie.borderWidth](#plotOptions.pie.borderWidth) to set - * the border thickness. + * Fires when the mouse leaves the area close to the point. One + * parameter, `event`, is passed to the function, containing common + * event information. * - * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/ - * Empty pie series + * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ + * Show values in the chart's corner on mouse over * - * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @private + * @type {Highcharts.PointMouseOutCallbackFunction} + * @context Highcharts.Point + * @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. + * + * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ + * Show values in the chart's corner on mouse over + * + * @type {Highcharts.PointMouseOverCallbackFunction} + * @context Highcharts.Point + * @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. + * + * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/ + * Remove point and confirm + * + * @type {Highcharts.PointRemoveCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Point + * @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. + * + * @sample {highcharts} highcharts/plotoptions/series-point-events-select/ + * Report the last selected point + * @sample {highmaps} maps/plotoptions/series-allowpointselect/ + * Report select and unselect + * + * @type {Highcharts.PointSelectCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Point + * @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. + * + * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/ + * Report the last unselected point + * @sample {highmaps} maps/plotoptions/series-allowpointselect/ + * Report select and unselect + * + * @type {Highcharts.PointUnselectCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Point + * @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. + * + * @sample {highcharts} highcharts/plotoptions/series-point-events-update/ + * Confirm point updating + * + * @type {Highcharts.PointUpdateCallbackFunction} + * @since 1.2.0 + * @context Highcharts.Point + * @apioption plotOptions.series.point.events.update */ - fillColor: void 0, /** - * The end angle of the pie in degrees where 0 is top and 90 is right. - * Defaults to `startAngle` plus 360. + * Events for each single point. * - * @sample {highcharts} highcharts/demo/pie-semi-circle/ - * Semi-circle donut + * @declare Highcharts.PointEventsOptionsObject + */ + events: {}, + }, + /** + * Options for the series data labels, appearing next to each data + * point. + * + * Since v6.2.0, multiple data labels can be applied to each single + * point by defining them as an array of configs. + * + * In styled mode, the data labels can be styled with the + * `.highcharts-data-label-box` and `.highcharts-data-label` class names + * ([see example](https://www.highcharts.com/samples/highcharts/css/series-datalabels)). + * + * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled + * Data labels enabled + * @sample {highcharts} highcharts/plotoptions/series-datalabels-multiple + * Multiple data labels on a bar series + * @sample {highcharts} highcharts/css/series-datalabels + * Style mode example + * + * @type {*|Array<*>} + * @product highcharts highstock highmaps gantt + * + * @private + */ + dataLabels: { + /** + * Enable or disable the initial animation when a series is + * displayed for the `dataLabels`. The animation can also be set as + * a configuration object. Please note that this option only + * applies to the initial animation. + * For other animations, see [chart.animation](#chart.animation) + * and the animation parameter under the API methods. + * The following properties are supported: + * + * - `defer`: The animation delay time in milliseconds. + * + * @sample {highcharts} highcharts/plotoptions/animation-defer/ + * Animation defer settings + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + * @since 8.2.0 + * @apioption plotOptions.series.dataLabels.animation + */ + animation: {}, + /** + * The animation delay time in milliseconds. + * Set to `0` renders dataLabel immediately. + * As `undefined` inherits defer time from the [series.animation.defer](#plotOptions.series.animation.defer). * * @type {number} - * @since 1.3.6 - * @product highcharts - * @apioption plotOptions.pie.endAngle + * @since 8.2.0 + * @apioption plotOptions.series.dataLabels.animation.defer */ /** - * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries), - * this option tells whether the series shall be redrawn as if the - * hidden point were `null`. + * 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`. * - * The default value changed from `false` to `true` with Highcharts - * 3.0. + * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/ + * Left aligned + * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/ + * Data labels inside the bar * - * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ - * True, the hiddden point is ignored + * @type {Highcharts.AlignValue|null} + */ + 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. * - * @since 2.3.0 - * @product highcharts + * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/ + * Don't allow overlap * - * @private + * @type {boolean} + * @default false + * @since 4.1.0 + * @apioption plotOptions.series.dataLabels.allowOverlap */ - ignoreHiddenPoint: true, /** - * @ignore-option + * The background color or gradient for the data label. * - * @private + * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ + * Data labels box options + * @sample {highmaps} maps/plotoptions/series-datalabels-box/ + * Data labels box options + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @since 2.2.1 + * @apioption plotOptions.series.dataLabels.backgroundColor */ - inactiveOtherPoints: 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. + * The border color for the data label. Defaults to `undefined`. * + * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ + * Data labels box options * - * Note: in Highcharts < 4.1.2, the percentage was relative to the plot - * area, not the pie size. + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @since 2.2.1 + * @apioption plotOptions.series.dataLabels.borderColor + */ + /** + * The border radius in pixels for the data label. * - * @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 + * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ + * Data labels box options + * @sample {highmaps} maps/plotoptions/series-datalabels-box/ + * Data labels box options * - * @type {number|string} + * @type {number} * @default 0 - * @since 2.0 - * @product highcharts - * @apioption plotOptions.pie.innerSize + * @since 2.2.1 + * @apioption plotOptions.series.dataLabels.borderRadius */ /** - * @ignore-option + * The border width in pixels for the data label. * - * @private + * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ + * Data labels box options + * + * @type {number} + * @default 0 + * @since 2.2.1 + * @apioption plotOptions.series.dataLabels.borderWidth */ - legendType: 'point', /** - * @ignore-option + * 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. * - * @private + * @sample {highcharts} highcharts/css/data-label-contrast/ + * Contrast text shadow + * @sample {highcharts} highcharts/css/series-datalabels/ + * Styling by CSS + * + * @type {string} + * @since 5.0.0 + * @apioption plotOptions.series.dataLabels.className */ - marker: null, /** - * 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. + * The text color for the data labels. Defaults to `undefined`. 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 {number|string} - * @default 80 - * @since 3.0 - * @product highcharts - * @apioption plotOptions.pie.minSize + * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/ + * Red data labels + * @sample {highmaps} maps/demo/color-axis/ + * White data labels + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @apioption plotOptions.series.dataLabels.color */ /** - * 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. - * [slicedOffset](#plotOptions.pie.slicedOffset) is also included in the - * default size calculation. 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%"`. - * - * @sample {highcharts} highcharts/plotoptions/pie-size/ - * Smaller pie - * - * @type {number|string|null} - * @product highcharts + * 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. * - * @private + * @type {boolean} + * @default true + * @since 2.3.3 + * @apioption plotOptions.series.dataLabels.crop */ - 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. + * Whether to defer displaying the data labels until the initial + * series animation has finished. Setting to `false` renders the + * data label immediately. If set to `true` inherits the defer + * time set in [plotOptions.series.animation](#plotOptions.series.animation). * - * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ - * One series in the legend, one hidden + * @sample highcharts/plotoptions/animation-defer + * Set defer time * - * @product highcharts + * @since 4.0.0 + * @product highcharts highstock gantt + */ + defer: true, + /** + * Enable or disable the data labels. * - * @private + * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/ + * Data labels enabled + * @sample {highmaps} maps/demo/color-axis/ + * Data labels enabled + * + * @type {boolean} + * @default false + * @apioption plotOptions.series.dataLabels.enabled */ - showInLegend: false, /** - * If a point is sliced, moved out from the center, how many pixels - * should it be moved?. + * A declarative filter to control of 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 + * `undefined` to disable a single data label. * - * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ - * 20px offset + * @example + * filter: { + * property: 'percentage', + * operator: '>', + * value: 4 + * } * - * @product highcharts + * @sample {highcharts} highcharts/demo/pie-monochrome + * Data labels filtered by percentage * - * @private + * @declare Highcharts.DataLabelsFilterOptionsObject + * @since 6.0.3 + * @apioption plotOptions.series.dataLabels.filter + */ + /** + * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`, + * `==`, and `===`. + * + * @type {string} + * @validvalue [">", "<", ">=", "<=", "==", "==="] + * @apioption plotOptions.series.dataLabels.filter.operator */ - slicedOffset: 10, /** - * The start angle of the pie slices in degrees where 0 is top and 90 - * right. + * The point property to filter by. Point options are passed + * directly to properties, additionally there are `y` value, + * `percentage` and others listed under {@link Highcharts.Point} + * members. * - * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ - * Start from right + * @type {string} + * @apioption plotOptions.series.dataLabels.filter.property + */ + /** + * The value to compare against. * * @type {number} - * @default 0 - * @since 2.3.4 - * @product highcharts - * @apioption plotOptions.pie.startAngle + * @apioption plotOptions.series.dataLabels.filter.value */ /** - * 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. + * A + * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) + * for the data label. Available variables are the same as for + * `formatter`. + * + * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ + * Add a unit + * @sample {highmaps} maps/plotoptions/series-datalabels-format/ + * Formatted value in the data label + * + * @type {string} + * @default y + * @default point.value + * @since 3.0 + * @apioption plotOptions.series.dataLabels.format + */ + // eslint-disable-next-line valid-jsdoc + /** + * Callback JavaScript function to format the data label. Note that + * if a `format` is defined, the format takes precedence and the + * formatter is ignored. * - * @product highcharts + * @sample {highmaps} maps/plotoptions/series-datalabels-format/ + * Formatted value * - * @private + * @type {Highcharts.DataLabelsFormatterCallbackFunction} */ - stickyTracking: false, - tooltip: { - followPointer: true + formatter: function () { + var numberFormatter = this.series.chart.numberFormatter; + return typeof this.y !== "number" + ? "" + : numberFormatter(this.y, -1); }, /** - * 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. - * - * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/ - * Black border + * 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 {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} - * @default #ffffff - * @product highcharts - * - * @private + * @type {boolean} + * @since 3.0 + * @apioption plotOptions.series.dataLabels.inside */ - 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. - * - * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/ - * 3px border + * Format for points with the value of null. Works analogously to + * [format](#plotOptions.series.dataLabels.format). `nullFormat` can + * be applied only to series which support displaying null points. * - * @product highcharts + * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ + * Format data label and tooltip for null point. * - * @private - */ - borderWidth: 1, - /** - * @ignore-options - * @private + * @type {boolean|string} + * @since 7.1.0 + * @apioption plotOptions.series.dataLabels.nullFormat */ - lineWidth: void 0, - states: { - /** - * @extends plotOptions.series.states.hover - * @excluding marker, lineWidth, lineWidthPlus - * @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. - * - * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/ - * Brightened by 0.5 - * - * @product highcharts - */ - brightness: 0.1 - } - } - }, - /* eslint-disable valid-jsdoc */ - /** - * @lends seriesTypes.pie.prototype - */ - { - isCartesian: false, - requireSorting: false, - directTouch: true, - noSharedTooltip: true, - trackerGroups: ['group', 'dataLabelsGroup'], - axisTypes: [], - pointAttribs: BaseSeries.seriesTypes.column.prototype.pointAttribs, /** - * Animate the pies in + * Callback JavaScript function that defines formatting for points + * with the value of null. Works analogously to + * [formatter](#plotOptions.series.dataLabels.formatter). + * `nullPointFormatter` can be applied only to series which support + * displaying null points. * - * @private - * @function Highcharts.seriesTypes.pie#animate + * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ + * Format data label and tooltip for null point. * - * @param {boolean} [init=false] + * @type {Highcharts.DataLabelsFormatterCallbackFunction} + * @since 7.1.0 + * @apioption plotOptions.series.dataLabels.nullFormatter */ - animate: function (init) { - var series = this, - points = series.points, - startAngleRad = series.startAngleRad; - if (!init) { - points.forEach(function (point) { - var graphic = point.graphic, - args = point.shapeArgs; - if (graphic && args) { - // start values - graphic.attr({ - // animate from inner radius (#779) - r: pick(point.startR, (series.center && series.center[3] / 2)), - start: startAngleRad, - end: startAngleRad - }); - // animate - graphic.animate({ - r: args.r, - start: args.start, - end: args.end - }, series.options.animation); - } - }); - } - }, - // Define hasData function for non-cartesian series. - // Returns true if the series has points at all. - hasData: function () { - return !!this.processedXData.length; // != 0 - }, /** - * Recompute total chart sum and update percentages of points. + * 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 `"allow"`. * - * @private - * @function Highcharts.seriesTypes.pie#updateTotals - * @return {void} + * @type {Highcharts.DataLabelsOverflowValue} + * @default justify + * @since 3.0.6 + * @apioption plotOptions.series.dataLabels.overflow */ - 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 + * When either the `borderWidth` or the `backgroundColor` is set, + * this is the padding within the box. * - * @private - * @function Highcharts.seriesTypes.pie#generatePoints - * @return {void} + * @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 */ - generatePoints: function () { - LineSeries.prototype.generatePoints.call(this); - this.updateTotals(); - }, + padding: 5, /** - * 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. - * @private + * Aligns data labels relative to points. If `center` alignment is + * not possible, it defaults to `right`. + * + * @type {Highcharts.AlignValue} + * @default center + * @apioption plotOptions.series.dataLabels.position */ - getX: function (y, left, point) { - var center = this.center, - // Variable pie has individual radius - radius = this.radii ? - this.radii[point.index] : - center[2] / 2, - angle, - x; - angle = Math.asin(clamp((y - center[1]) / (radius + point.labelDistance), -1, 1)); - x = center[0] + - (left ? -1 : 1) * - (Math.cos(angle) * (radius + point.labelDistance)) + - (point.labelDistance > 0 ? - (left ? -1 : 1) * this.options.dataLabels.padding : - 0); - return x; - }, /** - * Do translation for pie slices + * Text rotation in degrees. Note that due to a more complex + * structure, backgrounds, borders and padding will be lost on a + * rotated data label. * - * @private - * @function Highcharts.seriesTypes.pie#translate - * @param {Array<number>} [positions] - * @return {void} + * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ + * Vertical labels + * + * @type {number} + * @default 0 + * @apioption plotOptions.series.dataLabels.rotation */ - 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, - // the x component of the radius vector for a given point - radiusX, - 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(); - } - // Calculate the geometry for each point - for (i = 0; i < len; i++) { - point = points[i]; - // 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 - }; - // Used for distance calculation for specific point. - point.labelDistance = pick((point.options.dataLabels && - point.options.dataLabels.distance), labelDistance); - // Compute point.labelDistance if it's defined as percentage - // of slice radius (#8854) - point.labelDistance = relativeLength(point.labelDistance, point.shapeArgs.r); - // Saved for later dataLabels distance calculation. - series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance); - // 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.labelPosition = { - natural: { - // initial position of the data label - it's utilized for - // finding the final position for the label - x: positions[0] + radiusX + Math.cos(angle) * - point.labelDistance, - y: positions[1] + radiusY + Math.sin(angle) * - point.labelDistance - }, - 'final': { - // used for generating connector path - - // initialized later in drawDataLabels function - // x: undefined, - // y: undefined - }, - // left - pie on the left side of the data label - // right - pie on the right side of the data label - // center - data label overlaps the pie - alignment: point.labelDistance < 0 ? - 'center' : point.half ? 'right' : 'left', - connectorPosition: { - breakAt: { - x: positions[0] + radiusX + Math.cos(angle) * - finalConnectorOffset, - y: positions[1] + radiusY + Math.sin(angle) * - finalConnectorOffset - }, - touchingSliceAt: { - x: positions[0] + radiusX, - y: positions[1] + radiusY - } - } - }; - } - fireEvent(series, 'afterTranslate'); - }, /** - * Called internally to draw auxiliary graph in pie-like series in - * situtation when the default graph is not sufficient enough to present - * the data well. Auxiliary graph is saved in the same object as - * regular graph. + * 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`. * - * @private - * @function Highcharts.seriesTypes.pie#drawEmpty + * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ + * Data labels box options + * + * @type {boolean|Highcharts.ShadowOptionsObject} + * @default false + * @since 2.2.1 + * @apioption plotOptions.series.dataLabels.shadow */ - drawEmpty: function () { - var centerX, - centerY, - start = this.startAngleRad, - end = this.endAngleRad, - options = this.options; - // Draw auxiliary graph if there're no visible points. - if (this.total === 0 && this.center) { - centerX = this.center[0]; - centerY = this.center[1]; - if (!this.graph) { - this.graph = this.chart.renderer - .arc(centerX, centerY, this.center[1] / 2, 0, start, end) - .addClass('highcharts-empty-series') - .add(this.group); - } - this.graph.attr({ - d: SVGRenderer.prototype.symbols.arc(centerX, centerY, this.center[2] / 2, 0, { - start: start, - end: end, - innerR: this.center[3] / 2 - }) - }); - if (!this.chart.styledMode) { - this.graph.attr({ - 'stroke-width': options.borderWidth, - fill: options.fillColor || 'none', - stroke: options.color || - '#cccccc' - }); - } - } - else if (this.graph) { // Destroy the graph object. - this.graph = this.graph.destroy(); - } - }, /** - * Draw the data points + * The name of a symbol to use for the border around the label. + * Symbols are predefined functions on the Renderer object. * - * @private - * @function Highcharts.seriesTypes.pie#drawPoints - * @return {void} - */ - redrawPoints: function () { - var series = this, - chart = series.chart, - renderer = chart.renderer, - groupTranslation, - graphic, - pointAttr, - shapeArgs, - shadow = series.options.shadow; - this.drawEmpty(); - if (shadow && !series.shadowGroup && !chart.styledMode) { - series.shadowGroup = renderer.g('shadow') - .attr({ zIndex: -1 }) - .add(series.group); - } - // draw the slices - series.points.forEach(function (point) { - var animateTo = {}; - graphic = point.graphic; - if (!point.isNull && graphic) { - shapeArgs = point.shapeArgs; - // If the point is sliced, use special translation, else use - // plot area translation - groupTranslation = point.getTranslate(); - if (!chart.styledMode) { - // 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 (!point.delayedRendering) { - graphic - .setRadialReference(series.center); - if (!chart.styledMode) { - merge(true, animateTo, pointAttr); - } - merge(true, animateTo, shapeArgs, groupTranslation); - graphic.animate(animateTo); - } - else { - graphic - .setRadialReference(series.center) - .attr(shapeArgs) - .attr(groupTranslation); - if (!chart.styledMode) { - graphic - .attr(pointAttr) - .attr({ 'stroke-linejoin': 'round' }) - .shadow(shadow, shadowGroup); - } - point.delayedRendering = false; - } - graphic.attr({ - visibility: point.visible ? 'inherit' : 'hidden' - }); - graphic.addClass(point.getClassName()); - } - else if (graphic) { - point.graphic = graphic.destroy(); - } - }); + * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/ + * A callout for annotations + * + * @type {string} + * @default square + * @since 4.1.2 + * @apioption plotOptions.series.dataLabels.shape + */ + /** + * 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. + * + * For some series types, where each point has an extent, like for + * example tree maps, the data label may overflow the point. There + * are two strategies for handling overflow. By default, the text + * will wrap to multiple lines. The other strategy is to set + * `style.textOverflow` to `ellipsis`, which will keep the text on + * one line plus it will break inside long words. + * + * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/ + * Bold labels + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow/ + * Long labels truncated with an ellipsis in a pie + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap/ + * Long labels are wrapped in a pie + * @sample {highmaps} maps/demo/color-axis/ + * Bold labels + * + * @type {Highcharts.CSSObject} + * @since 4.1.0 + * @apioption plotOptions.series.dataLabels.style + */ + style: { + /** @internal */ + fontSize: "11px", + /** @internal */ + fontWeight: "bold", + /** @internal */ + color: "contrast", + /** @internal */ + textOutline: "1px contrast", }, /** - * Slices in pie chart are initialized in DOM, but it's shapes and - * animations are normally run in `drawPoints()`. - * @private + * Options for a label text which should follow marker's shape. + * Border and background are disabled for a label that follows a + * path. + * + * **Note:** Only SVG-based renderer supports this option. Setting + * `useHTML` to true will disable this option. + * + * @declare Highcharts.DataLabelsTextPathOptionsObject + * @since 7.1.0 + * @apioption plotOptions.series.dataLabels.textPath */ - drawPoints: function () { - var renderer = this.chart.renderer; - this.points.forEach(function (point) { - // When updating a series between 2d and 3d or cartesian and - // polar, the shape type changes. - if (point.graphic && point.hasNewShapeType()) { - point.graphic = point.graphic.destroy(); - } - if (!point.graphic) { - point.graphic = renderer[point.shapeType](point.shapeArgs) - .add(point.series.group); - point.delayedRendering = true; - } - }); - }, /** - * @private - * @deprecated - * @function Highcharts.seriesTypes.pie#searchPoint + * Presentation attributes for the text path. + * + * @type {Highcharts.SVGAttributes} + * @since 7.1.0 + * @apioption plotOptions.series.dataLabels.textPath.attributes */ - searchPoint: noop, /** - * Utility for sorting data labels + * Enable or disable `textPath` option for link's or marker's data + * labels. * - * @private - * @function Highcharts.seriesTypes.pie#sortByAngle - * @param {Array<Highcharts.Point>} points - * @param {number} sign - * @return {void} + * @type {boolean} + * @since 7.1.0 + * @apioption plotOptions.series.dataLabels.textPath.enabled */ - sortByAngle: function (points, sign) { - points.sort(function (a, b) { - return ((typeof a.angle !== 'undefined') && - (b.angle - a.angle) * sign); - }); - }, /** - * Use a simple symbol from LegendSymbolMixin. + * Whether to + * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) + * to render the labels. * - * @private - * @borrows Highcharts.LegendSymbolMixin.drawRectangle as Highcharts.seriesTypes.pie#drawLegendSymbol + * @type {boolean} + * @default false + * @apioption plotOptions.series.dataLabels.useHTML */ - drawLegendSymbol: LegendSymbolMixin.drawRectangle, /** - * Use the getCenter method from drawLegendSymbol. + * 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. * - * @private - * @borrows Highcharts.CenteredSeriesMixin.getCenter as Highcharts.seriesTypes.pie#getCenter + * @type {Highcharts.VerticalAlignValue|null} + * @since 2.3.3 */ - getCenter: CenteredSeriesMixin.getCenter, + verticalAlign: "bottom", /** - * Pies don't have point marker symbols. + * The x position offset of the label relative to the point in + * pixels. * - * @deprecated - * @private - * @function Highcharts.seriesTypes.pie#getSymbol + * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ + * Vertical and positioned + * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/ + * Data labels inside the bar */ - getSymbol: noop, + x: 0, /** - * @private - * @type {null} + * 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.z */ - drawGraph: null - }, - /** - * @lends seriesTypes.pie.prototype.pointClass.prototype - */ - { /** - * Initialize the pie slice + * The y position offset of the label relative to the point in + * pixels. * - * @private - * @function Highcharts.seriesTypes.pie#pointClass#init - * @return {Highcharts.Point} - */ - 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; + * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ + * Vertical and positioned + */ + y: 0, + }, + /** + * 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. + * + * @since 2.2 + * @product highcharts highstock + * + * @private + */ + cropThreshold: 300, + /** + * Opacity of a series parts: line, fill (e.g. area) and dataLabels. + * + * @see [states.inactive.opacity](#plotOptions.series.states.inactive.opacity) + * + * @since 7.1.0 + * + * @private + */ + opacity: 1, + /** + * 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. + * + * @product highstock + * + * @private + */ + 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. + * + * @since 4.1.9 + * @product highcharts highstock + * + * @private + */ + softThreshold: true, + /** + * @declare Highcharts.SeriesStatesOptionsObject + * + * @private + */ + states: { + /** + * The normal state of a series, or for point items in column, pie + * and similar series. Currently only used for setting animation + * when returning to normal state from hover. + * + * @declare Highcharts.SeriesStatesNormalOptionsObject + */ + normal: { + /** + * Animation when returning to normal state after hovering. + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + */ + animation: true, }, /** - * Negative points are not valid (#1530, #3623, #5322) - * - * @private - * @function Highcharts.seriesTypes.pie#pointClass#isValid - * @return {boolean} - */ - isValid: function () { - return isNumber(this.y) && this.y >= 0; + * Options for the hovered series. These settings override the + * normal state options when a series is moused over or touched. + * + * @declare Highcharts.SeriesStatesHoverOptionsObject + */ + hover: { + /** + * Enable separate styles for the hovered series to visualize + * that the user hovers either the series itself or the legend. + * + * @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 + * + * @type {boolean} + * @default true + * @since 1.2 + * @apioption plotOptions.series.states.hover.enabled + */ + /** + * Animation setting for hovering the graph in line-type series. + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + * @since 5.0.8 + * @product highcharts highstock + */ + animation: { + /** + * The duration of the hover animation in milliseconds. By + * default the hover state animates quickly in, and slowly + * back to normal. + * + * @internal + */ + duration: 50, + }, + /** + * Pixel width of the graph line. By default this property is + * undefined, and the `lineWidthPlus` property dictates how much + * to increase the linewidth from normal state. + * + * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/ + * 5px line on hover + * + * @type {number} + * @product highcharts highstock + * @apioption plotOptions.series.states.hover.lineWidth + */ + /** + * The additional line width for the graph of a hovered series. + * + * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ + * 5 pixels wider + * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ + * 5 pixels wider + * + * @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). + * + * @deprecated + * + * @extends plotOptions.series.marker + * @excluding states + * @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 `null`. + * + * In styled mode, the halo is styled with the + * `.highcharts-halo` class, with colors inherited from + * `.highcharts-color-{n}`. + * + * @sample {highcharts} highcharts/plotoptions/halo/ + * Halo options + * @sample {highstock} highcharts/plotoptions/halo/ + * Halo options + * + * @declare Highcharts.SeriesStatesHoverHaloOptionsObject + * @type {null|*} + * @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 {Highcharts.SVGAttributes} + * @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. + * + * @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. + * + * @since 4.0 + * @product highcharts highstock + */ + opacity: 0.25, + }, }, /** - * Toggle the visibility of the pie slice + * Specific options for point in selected states, after being + * selected by + * [allowPointSelect](#plotOptions.series.allowPointSelect) + * or programmatically. * - * @private - * @function Highcharts.seriesTypes.pie#pointClass#setVisible - * @param {boolean} vis - * Whether to show the slice or not. If undefined, the visibility - * is toggled. - * @param {boolean} [redraw=false] - * @return {void} + * @sample maps/plotoptions/series-allowpointselect/ + * Allow point select demo + * + * @declare Highcharts.SeriesStatesSelectOptionsObject + * @extends plotOptions.series.states.hover + * @excluding brightness */ - 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 = - typeof vis === 'undefined' ? !point.visible : vis; - // update userOptions.data - series.options.data[series.data.indexOf(point)] = - point.options; - // Show and hide associated elements. This is performed - // regardless of redraw or not, because chart.redraw only - // handles full series. - ['graphic', 'dataLabel', 'connector', 'shadowGroup'].forEach(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(''); + select: { + animation: { + /** @internal */ + duration: 0, + }, + }, + /** + * The opposite state of a hover for series. + * + * @sample highcharts/plotoptions/series-states-inactive-disabled + * Disabled inactive state + * + * @declare Highcharts.SeriesStatesInactiveOptionsObject + */ + inactive: { + /** + * Enable or disable the inactive state for a series + * + * @sample highcharts/plotoptions/series-states-inactive-disabled + * Disabled inactive state + * + * @type {boolean} + * @default true + * @apioption plotOptions.series.states.inactive.enabled + */ + /** + * The animation for entering the inactive state. + * + * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} + */ + animation: { + /** @internal */ + duration: 50, + }, + /** + * Opacity of series elements (dataLabels, line, area). + * + * @type {number} + */ + opacity: 0.2, + }, + }, + /** + * 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. + * + * **Note:** The boost module will force this option because of + * technical limitations. + * + * @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 + * + * @private + */ + 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. + * + * @declare Highcharts.SeriesTooltipOptionsObject + * @since 2.3 + * @extends tooltip + * @excluding animation, backgroundColor, borderColor, borderRadius, + * borderWidth, className, crosshairs, enabled, formatter, + * headerShape, hideDelay, outside, padding, positioner, + * shadow, shape, shared, snap, split, stickOnContact, + * style, useHTML + * @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. + * + * Note: + * In boost mode turbo threshold is forced. Only array of numbers or + * two dimensional arrays are allowed. + * + * @since 2.2 + * @product highcharts highstock gantt + * + * @private + */ + 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. The zone definitions have to be in ascending order regarding + * to the value. + * + * In styled mode, the color zones are styled with the + * `.highcharts-zone-{n}` class, or custom classed from the `className` + * option + * ([view live demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-zones/)). + * + * @see [zoneAxis](#plotOptions.series.zoneAxis) + * + * @sample {highcharts} highcharts/series/color-zones-simple/ + * Color zones + * @sample {highstock} highcharts/series/color-zones-simple/ + * Color zones + * + * @declare Highcharts.SeriesZonesOptionsObject + * @type {Array<*>} + * @since 4.1.0 + * @product highcharts highstock + * @apioption plotOptions.series.zones + */ + /** + * Styled mode only. A custom class name for the zone. + * + * @sample highcharts/css/color-zones/ + * Zones styled by class name + * + * @type {string} + * @since 5.0.0 + * @apioption plotOptions.series.zones.className + */ + /** + * Defines the color of the series. + * + * @see [series color](#plotOptions.series.color) + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @since 4.1.0 + * @product highcharts highstock + * @apioption plotOptions.series.zones.color + */ + /** + * A name for the dash style to use for the graph. + * + * @see [plotOptions.series.dashStyle](#plotOptions.series.dashStyle) + * + * @sample {highcharts|highstock} highcharts/series/color-zones-dashstyle-dot/ + * Dashed line indicates prognosis + * + * @type {Highcharts.DashStyleValue} + * @since 4.1.0 + * @product highcharts highstock + * @apioption plotOptions.series.zones.dashStyle + */ + /** + * Defines the fill color for the series (in area type series) + * + * @see [fillColor](#plotOptions.area.fillColor) + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @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} + * @since 4.1.0 + * @product highcharts highstock + * @apioption plotOptions.series.zones.value + */ + /** + * When using dual or multiple color axes, this number defines which + * colorAxis the particular series is connected to. It refers to + * either the + * {@link #colorAxis.id|axis id} + * or the index of the axis in the colorAxis array, with 0 being the + * first. Set this option to false to prevent a series from connecting + * to the default color axis. + * + * Since v7.2.0 the option can also be an axis id or an axis index + * instead of a boolean flag. + * + * @sample highcharts/coloraxis/coloraxis-with-pie/ + * Color axis with pie series + * @sample highcharts/coloraxis/multiple-coloraxis/ + * Multiple color axis + * + * @type {number|string|boolean} + * @default 0 + * @product highcharts highstock highmaps + * @apioption plotOptions.series.colorAxis + */ + /** + * Determines what data value should be used to calculate point color + * if `colorAxis` is used. Requires to set `min` and `max` if some + * custom point property is used or if approximation for data grouping + * is set to `'sum'`. + * + * @sample highcharts/coloraxis/custom-color-key/ + * Custom color key + * @sample highcharts/coloraxis/changed-default-color-key/ + * Changed default color key + * + * @type {string} + * @default y + * @since 7.2.0 + * @product highcharts highstock highmaps + * @apioption plotOptions.series.colorKey + */ + /** + * 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. + * + * @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 + * @validvalue ["x", "xy"] + * + * @private + */ + findNearestPointBy: "x", + }, + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** @lends Highcharts.Series.prototype */ + { + axisTypes: ["xAxis", "yAxis"], + coll: "series", + colorCounter: 0, + cropShoulder: 1, + directTouch: false, + isCartesian: true, + // each point's x and y values are stored in this.xData and this.yData + parallelArrays: ["x", "y"], + pointClass: Point, + requireSorting: true, + sorted: true, + init: function (chart, options) { + fireEvent(this, "init", { options: options }); + var series = this, + events, + chartSeries = chart.series, + lastSeries; + // A lookup over those events that are added by _options_ (not + // programmatically). These are updated through Series.update() + // (#10861). + this.eventOptions = this.eventOptions || {}; + // The 'eventsToUnbind' property moved from prototype into the + // Series init to avoid reference to the same array between + // the different series and charts. #12959, #13937 + this.eventsToUnbind = []; + /** + * Read only. The chart that the series belongs to. + * + * @name Highcharts.Series#chart + * @type {Highcharts.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 Highcharts.Series#type + * @type {string} + */ + /** + * Read only. The series' current options. To update, use + * {@link Series#update}. + * + * @name Highcharts.Series#options + * @type {Highcharts.SeriesOptionsType} + */ + 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 Highcharts.Series#name + * @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 Highcharts.Series#visible + * @type {boolean} + */ + visible: options.visible !== false, + /** + * Read only. The series' selected state as set by {@link + * Highcharts.Series#select}. + * + * @name Highcharts.Series#selected + * @type {boolean} + */ + selected: options.selected === true, // false by default + }); + // Register event listeners + events = options.events; + objectEach(events, function (event, eventType) { + if (isFunction(event)) { + // If event does not exist, or is changed by Series.update + if (series.eventOptions[eventType] !== event) { + // Remove existing if set by option + if (isFunction(series.eventOptions[eventType])) { + removeEvent( + series, + eventType, + series.eventOptions[eventType] + ); + } + series.eventOptions[eventType] = event; + 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(); + // Initialize the parallel data arrays + series.parallelArrays.forEach(function (key) { + if (!series[key + "Data"]) { + series[key + "Data"] = []; + } + }); + // 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; + series.opacity = series.options.opacity; + // Insert the series and re-order all series above the insertion + // point. + chart.orderSeries(this.insert(chartSeries)); + // Set options for series with sorting and set data later. + if (options.dataSorting && options.dataSorting.enabled) { + series.setDataSortingOptions(); + } else if (!series.points && !series.data) { + series.setData(options.data, false); + } + fireEvent(this, "afterInit"); + }, + /** + * Check whether the series item is itself or inherits from a certain + * series type. + * + * @function Highcharts.Series#is + * @param {string} type The type of series to check for, can be either + * featured or custom series types. For example `column`, `pie`, + * `ohlc` etc. + * + * @return {boolean} + * True if this item is or inherits from the given type. + */ + is: function (type) { + return seriesTypes[type] && this instanceof seriesTypes[type]; + }, + /** + * 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 + * @function Highcharts.Series#insert + * @param {Array<Highcharts.Series>} collection + * A collection of series, like `chart.series` or `xAxis.series`. + * @return {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 + * @function Highcharts.Series#bindAxes + * @return {void} + * @exception 18 + */ + bindAxes: function () { + var series = this, + seriesOptions = series.options, + chart = series.chart, + axisOptions; + fireEvent(this, "bindAxes", null, function () { + // repeat for xAxis and yAxis + (series.axisTypes || []).forEach(function (AXIS) { + // loop through the chart's axis objects + chart[AXIS].forEach(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 || + (typeof seriesOptions[AXIS] !== "undefined" && + seriesOptions[AXIS] === axisOptions.id) || + (typeof 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 Highcharts.Series#xAxis + * @type {Highcharts.Axis} + */ + /** + * Read only. The unique yAxis object associated + * with the series. + * + * @name Highcharts.Series#yAxis + * @type {Highcharts.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) { + error(18, true, chart); + } + }); + }); + fireEvent(this, "afterBindAxes"); + }, + /** + * 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 + * @function Highcharts.Series#updateParallelArrays + * @param {Highcharts.Point} point + * @param {number|string} i + * @return {void} + */ + 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) + ); + }; + series.parallelArrays.forEach(fn); + }, + /** + * Define hasData functions for series. These return true if there + * are data points on this series within the plot area. + * + * @private + * @function Highcharts.Series#hasData + * @return {boolean} + */ + hasData: function () { + return ( + (this.visible && + typeof this.dataMax !== "undefined" && + typeof this.dataMin !== "undefined") || // #3703 + (this.visible && this.yData && this.yData.length > 0) // #9758 + ); + }, + /** + * 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 + * @function Highcharts.Series#autoIncrement + * @return {number} + */ + autoIncrement: function () { + var options = this.options, + xIncrement = this.xIncrement, + date, + pointInterval, + pointIntervalUnit = options.pointIntervalUnit, + 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") { + time.set("Date", date, time.get("Date", date) + pointInterval); + } else if (pointIntervalUnit === "month") { + time.set( + "Month", + date, + time.get("Month", date) + pointInterval + ); + } else if (pointIntervalUnit === "year") { + time.set( + "FullYear", + date, + time.get("FullYear", date) + pointInterval + ); + } + pointInterval = date.getTime() - xIncrement; + } + this.xIncrement = xIncrement + pointInterval; + return xIncrement; + }, + /** + * Internal function to set properties for series if data sorting is + * enabled. + * + * @private + * @function Highcharts.Series#setDataSortingOptions + * @return {void} + */ + setDataSortingOptions: function () { + var options = this.options; + extend(this, { + requireSorting: false, + sorted: false, + enabledDataSorting: true, + allowDG: false, + }); + // To allow unsorted data for column series. + if (!defined(options.pointRange)) { + options.pointRange = 1; + } + }, + /** + * Set the series options by merging from the options tree. Called + * internally on initializing and updating series. This function will + * not redraw the series. For API usage, use {@link Series#update}. + * @private + * @function Highcharts.Series#setOptions + * @param {Highcharts.SeriesOptionsType} itemOptions + * The series options. + * @return {Highcharts.SeriesOptionsType} + * @fires Highcharts.Series#event:afterSetOptions + */ + setOptions: function (itemOptions) { + var chart = this.chart, + chartOptions = chart.options, + plotOptions = chartOptions.plotOptions, + userOptions = chart.userOptions || {}, + seriesUserOptions = merge(itemOptions), + options, + zones, + zone, + styledMode = chart.styledMode, + e = { + plotOptions: plotOptions, + userOptions: seriesUserOptions, + }; + fireEvent(this, "setOptions", e); + // These may be modified by the event + var typeOptions = e.plotOptions[this.type], + userPlotOptions = userOptions.plotOptions || {}; + // use copy to prevent undetected changes (#9762) + /** + * Contains series options by the user without defaults. + * @name Highcharts.Series#userOptions + * @type {Highcharts.SeriesOptionsType} + */ + this.userOptions = e.userOptions; + options = merge( + typeOptions, + plotOptions.series, + // #3881, chart instance plotOptions[type] should trump + // plotOptions.series + userOptions.plotOptions && userOptions.plotOptions[this.type], + seriesUserOptions + ); + // 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 + seriesUserOptions.tooltip // 7 + ); + // When shared tooltip, stickyTracking is true by default, + // unless user says otherwise. + this.stickyTracking = pick( + seriesUserOptions.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 + ) { + zone = { + value: + options[this.zoneAxis + "Threshold"] || + options.threshold || + 0, + className: "highcharts-negative", + }; + if (!styledMode) { + zone.color = options.negativeColor; + zone.fillColor = options.negativeFillColor; + } + zones.push(zone); + } + if (zones.length) { + // Push one extra zone for the rest + if (defined(zones[zones.length - 1].value)) { + zones.push( + styledMode + ? {} + : { + color: this.color, + fillColor: this.fillColor, + } + ); + } + } + fireEvent(this, "afterSetOptions", { options: options }); + return options; + }, + /** + * Return series name in "Series {Number}" format or the one defined by + * a user. This method can be simply overridden as series name format + * can vary (e.g. technical indicators). + * + * @function Highcharts.Series#getName + * @return {string} + * The series name. + */ + getName: function () { + // #4119 + return pick(this.options.name, "Series " + (this.index + 1)); + }, + /** + * @private + * @function Highcharts.Series#getCyclic + * @param {string} prop + * @param {*} [value] + * @param {Highcharts.Dictionary<any>} [defaults] + * @return {void} + */ + 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 (typeof i !== "undefined") { + this[indexName] = i; + } + this[prop] = value; + }, + /** + * Get the series' color based on either the options or pulled from + * global options. + * + * @private + * @function Highcharts.Series#getColor + * @return {void} + */ + getColor: function () { + if (this.chart.styledMode) { + this.getCyclic("color"); + } else 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 || + defaultOptions.plotOptions[this.type].color, + this.chart.options.colors + ); + } + }, + /** + * Get all points' instances created for this series. + * + * @private + * @function Highcharts.Series#getPointsCollection + * @return {Array<Highcharts.Point>} + */ + getPointsCollection: function () { + return (this.hasGroupedData ? this.points : this.data) || []; + }, + /** + * Get the series' symbol based on either the options or pulled from + * global options. + * + * @private + * @function Highcharts.Series#getSymbol + * @return {void} + */ + getSymbol: function () { + var seriesMarkerOption = this.options.marker; + this.getCyclic( + "symbol", + seriesMarkerOption.symbol, + this.chart.options.symbols + ); + }, + /** + * Finds the index of an existing point that matches the given point + * options. + * + * @private + * @function Highcharts.Series#findPointIndex + * @param {Highcharts.PointOptionsObject} optionsObject + * The options of the point. + * @param {number} fromIndex + * The index to start searching from, used for optimizing + * series with required sorting. + * @returns {number|undefined} + * Returns the index of a matching point, or undefined if no + * match is found. + */ + findPointIndex: function (optionsObject, fromIndex) { + var id = optionsObject.id, + x = optionsObject.x, + oldData = this.points, + matchingPoint, + matchedById, + pointIndex, + matchKey, + dataSorting = this.options.dataSorting; + if (id) { + matchingPoint = this.chart.get(id); + } else if (this.linkedParent || this.enabledDataSorting) { + matchKey = + dataSorting && dataSorting.matchByName ? "name" : "index"; + matchingPoint = find(oldData, function (oldPoint) { + return ( + !oldPoint.touched && + oldPoint[matchKey] === optionsObject[matchKey] + ); + }); + // Add unmatched point as a new point + if (!matchingPoint) { + return void 0; + } + } + if (matchingPoint) { + pointIndex = matchingPoint && matchingPoint.index; + if (typeof pointIndex !== "undefined") { + matchedById = true; + } + } + // Search for the same X in the existing data set + if (typeof pointIndex === "undefined" && isNumber(x)) { + pointIndex = this.xData.indexOf(x, fromIndex); + } + // Reduce pointIndex if data is cropped + if ( + pointIndex !== -1 && + typeof pointIndex !== "undefined" && + this.cropped + ) { + pointIndex = + pointIndex >= this.cropStart + ? pointIndex - this.cropStart + : pointIndex; + } + if ( + !matchedById && + oldData[pointIndex] && + oldData[pointIndex].touched + ) { + pointIndex = void 0; + } + return pointIndex; + }, + /** + * @private + * @borrows LegendSymbolMixin.drawLineMarker as Highcharts.Series#drawLegendSymbol + */ + drawLegendSymbol: LegendSymbolMixin.drawLineMarker, + /** + * Internal function called from setData. If the point count is the same + * as is was, or if there are overlapping X values, just run + * Point.update which is cheaper, allows animation, and keeps references + * to points. This also allows adding or removing points if the X-es + * don't match. + * + * @private + * @function Highcharts.Series#updateData + * + * @param {Array<Highcharts.PointOptionsType>} data + * + * @return {boolean} + */ + updateData: function (data, animation) { + var options = this.options, + dataSorting = options.dataSorting, + oldData = this.points, + pointsToAdd = [], + hasUpdatedByKey, + i, + point, + lastIndex, + requireSorting = this.requireSorting, + equalLength = data.length === oldData.length, + succeeded = true; + this.xIncrement = null; + // Iterate the new data + data.forEach(function (pointOptions, i) { + var id, + x, + pointIndex, + optionsObject = + (defined(pointOptions) && + this.pointClass.prototype.optionsToObject.call( + { series: this }, + pointOptions + )) || + {}; + // Get the x of the new data point + x = optionsObject.x; + id = optionsObject.id; + if (id || isNumber(x)) { + pointIndex = this.findPointIndex(optionsObject, lastIndex); + // Matching X not found + // or used already due to ununique x values (#8995), + // add point (but later) + if (pointIndex === -1 || typeof pointIndex === "undefined") { + pointsToAdd.push(pointOptions); + // Matching X found, update + } else if ( + oldData[pointIndex] && + pointOptions !== options.data[pointIndex] + ) { + oldData[pointIndex].update(pointOptions, false, null, false); + // Mark it touched, below we will remove all points that + // are not touched. + oldData[pointIndex].touched = true; + // Speed optimize by only searching after last known + // index. Performs ~20% bettor on large data sets. + if (requireSorting) { + lastIndex = pointIndex + 1; + } + // Point exists, no changes, don't remove it + } else if (oldData[pointIndex]) { + oldData[pointIndex].touched = true; + } + // If the length is equal and some of the nodes had a + // match in the same position, we don't want to remove + // non-matches. + if ( + !equalLength || + i !== pointIndex || + (dataSorting && dataSorting.enabled) || + this.hasDerivedData + ) { + hasUpdatedByKey = true; + } + } else { + // Gather all points that are not matched + pointsToAdd.push(pointOptions); + } + }, this); + // Remove points that don't exist in the updated data set + if (hasUpdatedByKey) { + i = oldData.length; + while (i--) { + point = oldData[i]; + if (point && !point.touched && point.remove) { + point.remove(false, animation); + } + } + // If we did not find keys (ids or x-values), and the length is the + // same, update one-to-one + } else if (equalLength && (!dataSorting || !dataSorting.enabled)) { + data.forEach(function (point, i) { + // .update doesn't exist on a linked, hidden series (#3709) + // (#10187) + if (oldData[i].update && point !== oldData[i].y) { + oldData[i].update(point, false, null, false); + } + }); + // Don't add new points since those configs are used above + pointsToAdd.length = 0; + // Did not succeed in updating data + } else { + succeeded = false; + } + oldData.forEach(function (point) { + if (point) { + point.touched = false; + } + }); + if (!succeeded) { + return false; + } + // Add new points + pointsToAdd.forEach(function (point) { + this.addPoint(point, false, null, null, false); + }, this); + if (this.xIncrement === null && this.xData && this.xData.length) { + this.xIncrement = arrayMax(this.xData); + this.autoIncrement(); + } + return true; + }, + /** + * 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. + * + * @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 + * + * @function Highcharts.Series#setData + * + * @param {Array<Highcharts.PointOptionsType>} data + * Takes an array of data in the same format as described under + * `series.{type}.data` for the given series type, for example a + * line series would take data in the form described under + * [series.line.data](https://api.highcharts.com/highcharts/series.line.data). + * + * @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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [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 this is true, points will be updated instead of replaced + * whenever possible. This occurs a) when the updated data is the + * same length as the existing data, b) when points are matched + * by their id's, or c) when points can be matched by X values. + * This allows updating with animation and performs better. In + * this case, the original array is not passed by reference. Set + * `false` to prevent. + * + * @return {void} + */ + setData: function (data, redraw, animation, updatePoints) { + var series = this, + oldData = series.points, + oldDataLength = (oldData && oldData.length) || 0, + dataLength, + options = series.options, + chart = series.chart, + dataSorting = options.dataSorting, + firstPoint = null, + xAxis = series.xAxis, + i, + turboThreshold = options.turboThreshold, + pt, + xData = this.xData, + yData = this.yData, + pointArrayMap = series.pointArrayMap, + valueCount = pointArrayMap && pointArrayMap.length, + keys = options.keys, + indexOfX = 0, + indexOfY = 1, + updatedData; + data = data || []; + dataLength = data.length; + redraw = pick(redraw, true); + if (dataSorting && dataSorting.enabled) { + data = this.sortData(data); + } + // First try to run Point.update which is cheaper, allows animation, + // and keeps references to points. + if ( + updatePoints !== false && + dataLength && + oldDataLength && + !series.cropped && + !series.hasGroupedData && + series.visible && + // Soft updating has no benefit in boost, and causes JS error + // (#8355) + !series.isSeriesBoosting + ) { + updatedData = this.updateData(data, animation); + } + if (!updatedData) { + // Reset properties + series.xIncrement = null; + series.colorCounter = 0; // for series with colorByPoint (#1547) + // Update parallel arrays + this.parallelArrays.forEach(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) { + firstPoint = series.getFirstValidPoint(data); + 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] + if (keys) { + indexOfX = keys.indexOf("x"); + indexOfY = keys.indexOf("y"); + indexOfX = indexOfX >= 0 ? indexOfX : 0; + indexOfY = indexOfY >= 0 ? indexOfY : 1; } - // Handle ignore hidden slices - if (ignoreHiddenPoint) { - series.isDirty = true; + for (i = 0; i < dataLength; i++) { + pt = data[i]; + xData[i] = pt[indexOfX]; + yData[i] = pt[indexOfY]; } - if (redraw) { - chart.redraw(); + } + } else { + // Highcharts expects configs to be numbers or arrays in + // turbo mode + error(12, false, chart); + } + } else { + for (i = 0; i < dataLength; i++) { + // stray commas in oldIE: + if (typeof data[i] !== "undefined") { + 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])) { + error(14, true, chart); + } + 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 sort series data + * + * @private + * @function Highcharts.Series#sortData + * @param {Array<Highcharts.PointOptionsType>} data + * Force data grouping. + * @return {Array<Highcharts.PointOptionsObject>} + */ + sortData: function (data) { + var series = this, + options = series.options, + dataSorting = options.dataSorting, + sortKey = dataSorting.sortKey || "y", + sortedData, + getPointOptionsObject = function (series, pointOptions) { + return ( + (defined(pointOptions) && + series.pointClass.prototype.optionsToObject.call( + { + series: series, + }, + pointOptions + )) || + {} + ); + }; + data.forEach(function (pointOptions, i) { + data[i] = getPointOptionsObject(series, pointOptions); + data[i].index = i; + }, this); + // Sorting + sortedData = data.concat().sort(function (a, b) { + var aValue = getNestedProperty(sortKey, a); + var bValue = getNestedProperty(sortKey, b); + return bValue < aValue ? -1 : bValue > aValue ? 1 : 0; + }); + // Set x value depending on the position in the array + sortedData.forEach(function (point, i) { + point.x = i; + }, this); + // Set the same x for linked series points if they don't have their + // own sorting + if (series.linkedSeries) { + series.linkedSeries.forEach(function (linkedSeries) { + var options = linkedSeries.options, + seriesData = options.data; + if ( + (!options.dataSorting || !options.dataSorting.enabled) && + seriesData + ) { + seriesData.forEach(function (pointOptions, i) { + seriesData[i] = getPointOptionsObject( + linkedSeries, + pointOptions + ); + if (data[i]) { + seriesData[i].x = data[i].x; + seriesData[i].index = i; } + }); + linkedSeries.setData(seriesData, false); } - }, - /** - * Set or toggle whether the slice is cut out from the pie - * - * @private - * @function Highcharts.seriesTypes.pie#pointClass#slice - * @param {boolean} sliced - * When undefined, the slice state is toggled. - * @param {boolean} redraw - * Whether to redraw the chart. True by default. - * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} - * Animation options. - * @return {void} - */ - 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); + }); + } + return data; + }, + /** + * 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. + * + * @private + * @function Highcharts.Series#getProcessedData + * @param {boolean} [forceExtremesFromAll] + * Force getting extremes of a total series data range. + * @return {Highcharts.SeriesProcessedDataObject} + */ + getProcessedData: function (forceExtremesFromAll) { + var series = this, + // copied during slice operation: + processedXData = series.xData, + 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 = + forceExtremesFromAll || + series.getExtremesFromAll || + options.getExtremesFromAll, // #4599 + isCartesian = series.isCartesian, + xExtremes, + val2lin = xAxis && xAxis.val2lin, + isLog = !!(xAxis && xAxis.logarithmic), + throwOnUnsorted = series.requireSorting, + min, + max; + if (xAxis) { + // corrected for log axis (#3053) + xExtremes = xAxis.getExtremes(); + 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 ( + series.yData && + (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 && + (typeof 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) { + error(15, false, series.chart); + throwOnUnsorted = false; // Only once + } + } + return { + xData: processedXData, + yData: processedYData, + cropped: cropped, + cropStart: cropStart, + closestPointRange: closestPointRange, + }; + }, + /** + * Internal function to apply processed data. + * In Highstock, this function is extended to provide data grouping. + * + * @private + * @function Highcharts.Series#processData + * @param {boolean} [force] + * Force data grouping. + * @return {boolean|undefined} + */ + processData: function (force) { + var series = this, + xAxis = series.xAxis, + processedData; + // 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 ( + series.isCartesian && + !series.isDirty && + !xAxis.isDirty && + !series.yAxis.isDirty && + !force + ) { + return false; + } + processedData = series.getProcessedData(); + // Record the properties + series.cropped = processedData.cropped; // undefined or true + series.cropStart = processedData.cropStart; + series.processedXData = processedData.xData; + series.processedYData = processedData.yData; + series.closestPointRange = series.basePointRange = + processedData.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 + * @function Highcharts.Series#cropData + * @param {Array<number>} xData + * @param {Array<number>} yData + * @param {number} min + * @param {number} max + * @param {number} [cropShoulder] + * @return {Highcharts.SeriesCropDataObject} + */ + cropData: function (xData, yData, min, max, cropShoulder) { + var dataLength = xData.length, + cropStart = 0, + cropEnd = dataLength, + i, + j; + // line-type series need one point outside + cropShoulder = pick(cropShoulder, this.cropShoulder); + // 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 + * @function Highcharts.Series#generatePoints + */ + 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]; + // #970: + if (!point && typeof dataOptions[cursor] !== "undefined") { + 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])) + ); /** - * Pie series only. Whether to display a slice offset from the - * center. - * @name Highcharts.Point#sliced - * @type {boolean|undefined} + * 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. + * + * @product highstock + * + * @name Highcharts.Point#dataGroup + * @type {Highcharts.DataGroupingInfoObject|undefined} */ - // if called without an argument, toggle - point.sliced = point.options.sliced = sliced = - defined(sliced) ? sliced : !point.sliced; - // update userOptions.data - series.options.data[series.data.indexOf(point)] = - point.options; - if (point.graphic) { - point.graphic.animate(this.getTranslate()); - } - if (point.shadowGroup) { - point.shadowGroup.animate(this.getTranslate()); + point.dataGroup = series.groupMap[i]; + if (point.dataGroup.options) { + point.options = point.dataGroup.options; + extend(point, point.dataGroup.options); + // Collision of props and options (#9770) + delete point.dataLabels; } - }, + } + if (point) { + // #6279 + /** + * Contains the point's index in the `Series.points` array. + * + * @name Highcharts.Point#index + * @type {number} + * @readonly + */ + 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 = void 0; // #1003 + } + } + } /** - * @private - * @function Highcharts.seriesTypes.pie#pointClass#getTranslate - * @return {Highcharts.TranslationAttributes} - */ - getTranslate: function () { - return this.sliced ? this.slicedTranslation : { - translateX: 0, - translateY: 0 + * Read only. An array containing those values converted to points. + * In case the series data length exceeds the `cropThreshold`, or if + * the data is grouped, `series.data` doesn't contain all the + * points. Also, in case a series is hidden, the `data` array may be + * empty. To access raw values, `series.options.data` will always be + * up to date. `Series.data` only contains the points that have been + * created on demand. To modify the data, use + * {@link Highcharts.Series#setData} or + * {@link Highcharts.Point#update}. + * + * @see Series.points + * + * @name Highcharts.Series#data + * @type {Array<Highcharts.Point>} + */ + 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 Highcharts.Series#points + * @type {Array<Highcharts.Point>} + */ + series.points = points; + fireEvent(this, "afterGeneratePoints"); + }, + /** + * Get current X extremes for the visible data. + * + * @private + * @function Highcharts.Series#getXExtremes + * + * @param {Array<number>} xData + * The data to inspect. Defaults to the current data within the + * visible range. + * @return {Highcharts.RangeObject} + */ + getXExtremes: function (xData) { + return { + min: arrayMin(xData), + max: arrayMax(xData), + }; + }, + /** + * Calculate Y extremes for the visible data. The result is returned + * as an object with `dataMin` and `dataMax` properties. + * + * @private + * @function Highcharts.Series#getExtremes + * @param {Array<number>} [yData] + * The data to inspect. Defaults to the current data within the + * visible range. + * @param {boolean} [forceExtremesFromAll] + * Force getting extremes of a total series data range. + * @return {Highcharts.DataExtremesObject} + */ + getExtremes: function (yData, forceExtremesFromAll) { + var xAxis = this.xAxis, + yAxis = this.yAxis, + xData = this.processedXData || this.xData, + yDataLength, + activeYData = [], + activeCounter = 0, + // #2117, need to compensate for log X axis + xExtremes, + xMin = 0, + xMax = 0, + validValue, + withinRange, + // Handle X outside the viewed area. This does not work with + // non-sorted data like scatter (#7639). + shoulder = this.requireSorting ? this.cropShoulder : 0, + positiveValuesOnly = yAxis ? yAxis.positiveValuesOnly : false, + x, + y, + i, + j; + yData = yData || this.stackedYData || this.processedYData || []; + yDataLength = yData.length; + if (xAxis) { + xExtremes = xAxis.getExtremes(); + xMin = xExtremes.min; + xMax = xExtremes.max; + } + 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) || isArray(y)) && + (y.length || y > 0 || !positiveValuesOnly); + withinRange = + forceExtremesFromAll || + this.getExtremesFromAll || + this.options.getExtremesFromAll || + this.cropped || + !xAxis || // for colorAxis support + ((xData[i + shoulder] || x) >= xMin && + (xData[i - shoulder] || x) <= xMax); + if (validValue && withinRange) { + j = y.length; + if (j) { + // array, like ohlc or range data + while (j--) { + if (isNumber(y[j])) { + // #7380, #11513 + activeYData[activeCounter++] = y[j]; + } + } + } else { + activeYData[activeCounter++] = y; + } + } + } + var dataExtremes = { + dataMin: arrayMin(activeYData), + dataMax: arrayMax(activeYData), + }; + fireEvent(this, "afterGetExtremes", { dataExtremes: dataExtremes }); + return dataExtremes; + }, + /** + * Set the current data extremes as `dataMin` and `dataMax` on the + * Series item. Use this only when the series properties should be + * updated. + * + * @private + * @function Highcharts.Series#applyExtremes + * @return {void} + */ + applyExtremes: function () { + var dataExtremes = this.getExtremes(); + /** + * Contains the minimum value of the series' data point. Some series + * types like `networkgraph` do not support this property as they + * lack a `y`-value. + * @name Highcharts.Series#dataMin + * @type {number|undefined} + * @readonly + */ + this.dataMin = dataExtremes.dataMin; + /** + * Contains the maximum value of the series' data point. Some series + * types like `networkgraph` do not support this property as they + * lack a `y`-value. + * @name Highcharts.Series#dataMax + * @type {number|undefined} + * @readonly + */ + this.dataMax = dataExtremes.dataMax; + return dataExtremes; + }, + /** + * Find and return the first non null point in the data + * + * @private + * @function Highcharts.Series.getFirstValidPoint + * @param {Array<Highcharts.PointOptionsType>} data + * Array of options for points + * + * @return {Highcharts.PointOptionsType} + */ + getFirstValidPoint: function (data) { + var firstPoint = null, + dataLength = data.length, + i = 0; + while (firstPoint === null && i < dataLength) { + firstPoint = data[i]; + i++; + } + return firstPoint; + }, + /** + * 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. + * + * @function Highcharts.Series#translate + * @return {void} + * @fires Highcharts.Series#events:translate + */ + 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, + enabledDataSorting = series.enabledDataSorting, + yAxis = series.yAxis, + points = series.points, + dataLength = points.length, + hasModifyValue = !!series.modifyValue, + i, + pointPlacement = series.pointPlacementToXValue(), // #7860 + dynamicallyPlaced = Boolean(pointPlacement), + threshold = options.threshold, + stackThreshold = options.startFromThreshold ? threshold : 0, + plotX, + lastPlotX, + stackIndicator, + zoneAxis = this.zoneAxis || "y", + 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). + * @private + */ + function limitedRange(val) { + return clamp(val, -1e5, 1e5); + } + // 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.stacking && + yAxis.stacking.stacks[ + (series.negStacks && + yValue < (stackThreshold ? 0 : threshold) + ? "-" + : "") + series.stackKey + ], + pointStack, + stackValues; + if ( + (yAxis.positiveValuesOnly && + !yAxis.validatePositiveValue(yValue)) || + (xAxis.positiveValuesOnly && + !xAxis.validatePositiveValue(xValue)) + ) { + 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 && stack && stack[xValue]) { + stackIndicator = series.getStackIndicator( + stackIndicator, + xValue, + series.index + ); + if (!point.isNull) { + pointStack = stack[xValue]; + stackValues = pointStack.points[stackIndicator.key]; + } + } + if (isArray(stackValues)) { + yBottom = stackValues[0]; + yValue = stackValues[1]; + if ( + yBottom === stackThreshold && + stackIndicator.key === stack[xValue].base + ) { + yBottom = pick(isNumber(threshold) && threshold, yAxis.min); + } + // #1200, #1232 + if (yAxis.positiveValuesOnly && yBottom <= 0) { + yBottom = null; + } + point.total = point.stackTotal = pointStack.total; + point.percentage = + pointStack.total && (point.y / pointStack.total) * 100; + point.stackY = yValue; + // Place the stack label + // in case of variwide series (where widths of points are + // different in most cases), stack labels are positioned + // wrongly, so the call of the setOffset is omited here and + // labels are correctly positioned later, at the end of the + // variwide's translate function (#10962) + if (!series.irregularWidths) { + 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 + // #3201 + point.plotY = + typeof yValue === "number" && yValue !== Infinity + ? limitedRange(yAxis.translate(yValue, 0, 1, 0, 1)) + : void 0; + point.isInside = this.isPointInside(point); + // Set client related positions for mouse tracking + point.clientX = dynamicallyPlaced + ? correctFloat( + xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement) + ) + : plotX; // #1514, #5383, #5518 + // Negative points. For bubble charts, this means negative z + // values (#9728) + point.negative = + point[zoneAxis] < + (options[zoneAxis + "Threshold"] || threshold || 0); + // some API data + point.category = + categories && typeof categories[point.x] !== "undefined" + ? categories[point.x] + : point.x; + // Determine auto enabling of markers (#3635, #5099) + if (!point.isNull && point.visible !== false) { + if (typeof lastPlotX !== "undefined") { + closestPointRangePx = Math.min( + closestPointRangePx, + Math.abs(plotX - lastPlotX) + ); + } + lastPlotX = plotX; + } + // Find point zone + point.zone = this.zones.length && point.getZone(); + // Animate new points with data sorting + if (!point.graphic && series.group && enabledDataSorting) { + point.isNew = true; + } + } + series.closestPointRangePx = closestPointRangePx; + fireEvent(this, "afterTranslate"); + }, + /** + * Return the series points with null points filtered out. + * + * @function Highcharts.Series#getValidPoints + * + * @param {Array<Highcharts.Point>} [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. + * + * @param {boolean} [allowNull=false] + * Whether to allow null points to pass as valid points. + * + * @return {Array<Highcharts.Point>} + * The valid points. + */ + getValidPoints: function (points, insideOnly, allowNull) { + var chart = this.chart; + // #3916, #5029, #5085 + return (points || this.points || []).filter(function isValidPoint( + point + ) { + if ( + insideOnly && + !chart.isInsidePlot(point.plotX, point.plotY, chart.inverted) + ) { + return false; + } + return point.visible !== false && (allowNull || !point.isNull); + }); + }, + /** + * Get the clipping for the series. Could be called for a series to + * initiate animating the clip or to set the final clip (only width + * and x). + * + * @private + * @function Highcharts.Series#getClip + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] + * Initialize the animation. + * @param {boolean} [finalBox] + * Final size for the clip - end state for the animation. + * @return {Highcharts.Dictionary<number>} + */ + getClipBox: function (animation, finalBox) { + var series = this, + options = series.options, + chart = series.chart, + inverted = chart.inverted, + xAxis = series.xAxis, + yAxis = xAxis && series.yAxis, + clipBox, + scrollablePlotAreaOptions = + chart.options.chart.scrollablePlotArea || {}; + if (animation && options.clip === false && yAxis) { + // support for not clipped series animation (#10450) + clipBox = inverted + ? { + y: -chart.chartWidth + yAxis.len + yAxis.pos, + height: chart.chartWidth, + width: chart.chartHeight, + x: -chart.chartHeight + xAxis.len + xAxis.pos, + } + : { + y: -yAxis.pos, + height: chart.chartHeight, + width: chart.chartWidth, + x: -xAxis.pos, + }; + // x and width will be changed later when setting for animation + // initial state in Series.setClip + } else { + clipBox = series.clipBox || chart.clipBox; + if (finalBox) { + clipBox.width = chart.plotSizeX; + clipBox.x = + (chart.scrollablePixelsX || 0) * + (scrollablePlotAreaOptions.scrollPositionX || 0); + } + } + return !finalBox + ? clipBox + : { + width: clipBox.width, + x: clipBox.x, }; - }, - /** - * @private - * @function Highcharts.seriesTypes.pie#pointClass#haloPath - * @param {number} size - * @return {Highcharts.SVGPathArray} - */ - 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: shapeArgs.r - 1, - start: shapeArgs.start, - end: shapeArgs.end - }); - }, - connectorShapes: { - // only one available before v7.0.0 - fixedOffset: function (labelPosition, connectorPosition, options) { - var breakAt = connectorPosition.breakAt, - touchingSliceAt = connectorPosition.touchingSliceAt, - lineSegment = options.softConnector ? [ - 'C', - // 1st control point (of the curve) - labelPosition.x + - // 5 gives the connector a little horizontal bend - (labelPosition.alignment === 'left' ? -5 : 5), - labelPosition.y, - 2 * breakAt.x - touchingSliceAt.x, - 2 * breakAt.y - touchingSliceAt.y, - breakAt.x, - breakAt.y // - ] : [ - 'L', - breakAt.x, - breakAt.y - ]; - // assemble the path - return ([ - ['M', labelPosition.x, labelPosition.y], - lineSegment, - ['L', touchingSliceAt.x, touchingSliceAt.y] - ]); - }, - straight: function (labelPosition, connectorPosition) { - var touchingSliceAt = connectorPosition.touchingSliceAt; - // direct line to the slice - return [ - ['M', labelPosition.x, labelPosition.y], - ['L', touchingSliceAt.x, touchingSliceAt.y] - ]; - }, - crookedLine: function (labelPosition, connectorPosition, options) { - var touchingSliceAt = connectorPosition.touchingSliceAt, - series = this.series, - pieCenterX = series.center[0], - plotWidth = series.chart.plotWidth, - plotLeft = series.chart.plotLeft, - alignment = labelPosition.alignment, - radius = this.shapeArgs.r, - crookDistance = relativeLength(// % to fraction - options.crookDistance, 1), - crookX = alignment === 'left' ? - pieCenterX + radius + (plotWidth + plotLeft - - pieCenterX - radius) * (1 - crookDistance) : - plotLeft + (pieCenterX - radius) * crookDistance, - segmentWithCrook = [ - 'L', - crookX, - labelPosition.y - ], - useCrook = true; - // crookedLine formula doesn't make sense if the path overlaps - // the label - use straight line instead in that case - if (alignment === 'left' ? - (crookX > labelPosition.x || crookX < touchingSliceAt.x) : - (crookX < labelPosition.x || crookX > touchingSliceAt.x)) { - useCrook = false; - } - // assemble the path - var path = [ - ['M', - labelPosition.x, - labelPosition.y] - ]; - if (useCrook) { - path.push(segmentWithCrook); - } - path.push(['L', touchingSliceAt.x, touchingSliceAt.y]); - return path; - } - }, - /** - * Extendable method for getting the path of the connector between the - * data label and the pie slice. - */ - getConnectorPath: function () { - var labelPosition = this.labelPosition, - options = this.series.options.dataLabels, - connectorShape = options.connectorShape, - predefinedShapes = this.connectorShapes; - // find out whether to use the predefined shape - if (predefinedShapes[connectorShape]) { - connectorShape = predefinedShapes[connectorShape]; + }, + /** + * 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 + * @function Highcharts.Series#setClip + * @param {boolean|Highcharts.AnimationOptionsObject} [animation] + */ + setClip: function (animation) { + var chart = this.chart, + options = this.options, + renderer = chart.renderer, + inverted = chart.inverted, + seriesClipBox = this.clipBox, + clipBox = this.getClipBox(animation), + 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 (animation) { + clipBox.width = 0; + if (inverted) { + clipBox.x = + chart.plotHeight + + (options.clip !== false ? 0 : chart.plotTop); + } + } + // 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) { + chart[sharedClipKey + "m"] = markerClipRect = renderer.clipRect( + // include the width of the first marker + inverted ? chart.plotSizeX + 99 : -99, + 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 }; + // When the series is rendered again before starting animating, in + // compliance to a responsive rule + } else if (!chart.hasLoaded) { + clipRect.attr(clipBox); + } + if (animation) { + if (!clipRect.count[this.index]) { + clipRect.count[this.index] = true; + clipRect.count.length += 1; + } + } + if (options.clip !== false || animation) { + 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. + * + * @function Highcharts.Series#animate + * + * @param {boolean} [init] + * Initialize the animation. + */ + animate: function (init) { + var series = this, + chart = series.chart, + animation = animObject(series.options.animation), + clipRect, + sharedClipKey, + finalBox; + // Initialize the animation. Set up the clipping rectangle. + if (!chart.hasRendered) { + if (init) { + series.setClip(animation); + // Run the animation + } else { + sharedClipKey = this.sharedClipKey; + clipRect = chart[sharedClipKey]; + finalBox = series.getClipBox(animation, true); + if (clipRect) { + clipRect.animate(finalBox, animation); + } + if (chart[sharedClipKey + "m"]) { + chart[sharedClipKey + "m"].animate( + { + width: finalBox.width + 99, + x: finalBox.x - (chart.inverted ? 0 : 99), + }, + animation + ); } - return connectorShape.call(this, { - // pass simplified label position object for user's convenience - x: labelPosition.final.x, - y: labelPosition.final.y, - alignment: labelPosition.alignment - }, labelPosition.connectorPosition, options); + } } - } - /* eslint-enable valid-jsdoc */ - ); - /** - * A `pie` series. If the [type](#series.pie.type) option is not specified, - * it is inherited from [chart.type](#chart.type). - * - * @extends series,plotOptions.pie - * @excluding cropThreshold, dataParser, dataURL, stack, xAxis, yAxis, - * dataSorting, step, boostThreshold, boostBlending - * @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 following snippet shows only a - * few settings, see the complete options set 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" - * }] - * ``` - * - * @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 - * - * @type {Array<number|Array<string,(number|null)>|null|*>} - * @extends series.line.data - * @excluding marker, x - * @product highcharts - * @apioption series.pie.data - */ - /** - * @type {Highcharts.SeriesPieDataLabelsOptionsObject} - * @product highcharts - * @apioption series.pie.data.dataLabels - */ - /** - * 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. - * - * @sample {highcharts} highcharts/point/sliced/ - * One sliced point - * - * @type {boolean} - * @product highcharts - * @apioption series.pie.data.sliced - */ - /** - * @excluding legendItemClick - * @product highcharts - * @apioption series.pie.events - */ - ''; // placeholder for transpiled doclets above - - }); - _registerModule(_modules, 'Core/Series/DataLabels.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Series/CartesianSeries.js'], _modules['Core/Utilities.js']], function (A, H, CartesianSeries, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var getDeferredAnimation = A.getDeferredAnimation; - var noop = H.noop, - seriesTypes = H.seriesTypes; - var arrayMax = U.arrayMax, - clamp = U.clamp, - defined = U.defined, - extend = U.extend, - fireEvent = U.fireEvent, - format = U.format, - isArray = U.isArray, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick, - relativeLength = U.relativeLength, - splat = U.splat, - stableSort = U.stableSort; - /** - * Callback JavaScript function to format the data label as a string. Note that - * if a `format` is defined, the format takes precedence and the formatter is - * ignored. - * - * @callback Highcharts.DataLabelsFormatterCallbackFunction - * - * @param {Highcharts.PointLabelObject} this - * Data label context to format - * - * @param {Highcharts.DataLabelsOptions} options - * [API options](/highcharts/plotOptions.series.dataLabels) of the data label - * - * @return {number|string|null|undefined} - * Formatted data label text - */ - /** - * Values for handling data labels that flow outside the plot area. - * - * @typedef {"allow"|"justify"} Highcharts.DataLabelsOverflowValue - */ - ''; // detach doclets above - /* eslint-disable valid-jsdoc */ - /** - * 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. - * - * @private - * @function Highcharts.distribute - * @param {Highcharts.DataLabelsBoxArray} boxes - * @param {number} len - * @param {number} [maxDistance] - * @return {void} - */ - H.distribute = function (boxes, len, maxDistance) { - var i, - overlapping = true, - origBoxes = boxes, // Original array will be altered with added .pos - restBoxes = [], // The outranked overshoot - box, - target, - total = 0, - reducedLen = origBoxes.reducedLen || len; - /** - * @private - */ - function sortByTarget(a, b) { - return a.target - b.target; + }, + /** + * This runs after animation to land on the final plot clipping. + * + * @private + * @function Highcharts.Series#afterAnimate + * @fires Highcharts.Series#event:afterAnimate + */ + 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. + * + * @function Highcharts.Series#drawPoints + */ + drawPoints: function () { + var series = this, + points = series.points, + chart = series.chart, + i, + point, + graphic, + verb, + options = series.options, + seriesMarkerOptions = options.marker, + pointMarkerOptions, + hasPointMarker, + markerGroup = series[series.specialGroup] || series.markerGroup, + xAxis = series.xAxis, + markerAttribs, + globallyEnabled = pick( + seriesMarkerOptions.enabled, + !xAxis || xAxis.isRadial ? true : null, + // Use larger or equal as radius is null in bubbles (#6321) + series.closestPointRangePx >= + seriesMarkerOptions.enabledThreshold * + seriesMarkerOptions.radius + ); + if ( + seriesMarkerOptions.enabled !== false || + series._hasPointMarkers + ) { + for (i = 0; i < points.length; i++) { + point = points[i]; + graphic = point.graphic; + verb = graphic ? "animate" : "attr"; + pointMarkerOptions = point.marker || {}; + hasPointMarker = !!point.marker; + var shouldDrawMarker = + ((globallyEnabled && + typeof pointMarkerOptions.enabled === "undefined") || + pointMarkerOptions.enabled) && + !point.isNull && + point.visible !== false; + // only draw the point if y is defined + if (shouldDrawMarker) { + // Shortcuts + var symbol = pick(pointMarkerOptions.symbol, series.symbol); + markerAttribs = series.markerAttribs( + point, + point.selected && "select" + ); + // Set starting position for point sliding animation. + if (series.enabledDataSorting) { + point.startXPos = xAxis.reversed + ? -markerAttribs.width + : xAxis.width; + } + var isInside = point.isInside !== false; + if (graphic) { + // update + // Since the marker group isn't clipped, each + // individual marker must be toggled + graphic[isInside ? "show" : "hide"](isInside).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. + * + * @name Point#graphic + * @type {SVGElement} + */ + point.graphic = graphic = chart.renderer + .symbol( + symbol, + markerAttribs.x, + markerAttribs.y, + markerAttribs.width, + markerAttribs.height, + hasPointMarker + ? pointMarkerOptions + : seriesMarkerOptions + ) + .add(markerGroup); + // Sliding animation for new points + if (series.enabledDataSorting && chart.hasRendered) { + graphic.attr({ + x: point.startXPos, + }); + verb = "animate"; + } + } + if (graphic && verb === "animate") { + // update + // Since the marker group isn't clipped, each + // individual marker must be toggled + graphic[isInside ? "show" : "hide"](isInside).animate( + markerAttribs + ); + } + // Presentational attributes + if (graphic && !chart.styledMode) { + graphic[verb]( + 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 + * + * @function Highcharts.Series#markerAttribs + * + * @param {Highcharts.Point} point + * The Point to inspect. + * + * @param {string} [state] + * The state, can be either `hover`, `select` or undefined. + * + * @return {Highcharts.SVGAttributes} + * A hash containing those attributes that are not settable from + * CSS. + */ + markerAttribs: function (point, state) { + var seriesOptions = this.options, + seriesMarkerOptions = seriesOptions.marker, + seriesStateOptions, + pointMarkerOptions = point.marker || {}, + symbol = pointMarkerOptions.symbol || seriesMarkerOptions.symbol, + 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) + ); + } + point.hasImage = symbol && symbol.indexOf("url") === 0; + if (point.hasImage) { + radius = 0; // and subsequently width and height is not set + } + attribs = { + // Math.floor for #1843: + x: seriesOptions.crisp + ? Math.floor(point.plotX) - radius + : point.plotX - radius, + 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. + * + * @private + * @function Highcharts.Series#pointAttribs + * + * @param {Highcharts.Point} [point] + * The point instance to inspect. + * + * @param {string} [state] + * The point state, can be either `hover`, `select` or 'normal'. + * If undefined, normal state is assumed. + * + * @return {Highcharts.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, + opacity = 1; + color = pointColorOption || zoneColor || pointColor || color; + fill = + pointMarkerOptions.fillColor || + seriesMarkerOptions.fillColor || + color; + stroke = + pointMarkerOptions.lineColor || + seriesMarkerOptions.lineColor || + color; + // Handle hover and select states + state = state || "normal"; + 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; + opacity = pick( + pointStateOptions.opacity, + seriesStateOptions.opacity, + opacity + ); + } + return { + stroke: stroke, + "stroke-width": strokeWidth, + fill: fill, + opacity: opacity, + }; + }, + /** + * Clear DOM objects and free up memory. + * + * @private + * @function Highcharts.Series#destroy + * @param {boolean} [keepEventsForUpdate] + * @return {void} + * @fires Highcharts.Series#event:destroy + */ + destroy: function (keepEventsForUpdate) { + 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 events + this.removeEvents(keepEventsForUpdate); + // erase from axes + (series.axisTypes || []).forEach(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); } - // If the total size exceeds the len, remove those boxes with the lowest - // rank - i = boxes.length; + // destroy all points with their elements + i = data.length; while (i--) { - total += boxes[i].size; + point = data[i]; + if (point && point.destroy) { + point.destroy(); + } } - // Sort by rank, then slice away overshoot - if (total > reducedLen) { - stableSort(boxes, function (a, b) { - return (b.rank || 0) - (a.rank || 0); - }); - i = 0; - total = 0; - while (total <= reducedLen) { - total += boxes[i].size; - i++; - } - restBoxes = boxes.splice(i - 1, boxes.length); + series.points = null; + // Clear the animation timeout if we are destroying the series + // during initial animation + U.clearTimeout(series.animationTimeout); + // Destroy all SVGElements associated to the series + objectEach(series, function (val, prop) { + // Survive provides a hook for not destroying + if (val instanceof SVGElement && !val.survive) { + // issue 134 workaround + destroy = issue134 && prop === "group" ? "hide" : "destroy"; + val[destroy](); + } + }); + // remove from hoverSeries + if (chart.hoverSeries === series) { + chart.hoverSeries = null; } - // Order by target - stableSort(boxes, sortByTarget); - // So far we have been mutating the original array. Now - // create a copy with target arrays - boxes = boxes.map(function (box) { - return { - size: box.size, - targets: [box.target], - align: pick(box.align, 0.5) - }; + erase(chart.series, series); + chart.orderSeries(); + // clear all members + objectEach(series, function (val, prop) { + if (!keepEventsForUpdate || prop !== "hcEvents") { + delete series[prop]; + } }); - 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 = clamp(target - box.size * box.align, 0, 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; - } - } + }, + /** + * Get the graph path. + * + * @private + * @function Highcharts.Series#getGraphPath + * @param {Array<Highcharts.Point>} points + * @param {boolean} [nullsAsZeroes] + * @param {boolean} [connectCliffs] + * @return {Highcharts.SVGPathArray} + */ + 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(); } - // Add the rest (hidden boxes) - origBoxes.push.apply(origBoxes, restBoxes); - // Now the composite boxes are placed, we need to put the original boxes - // within them - i = 0; - boxes.some(function (box) { - var posInCompositeBox = 0; - if (box.targets.some(function () { - origBoxes[i].pos = box.pos + posInCompositeBox; - // If the distance between the position and the target exceeds - // maxDistance, abort the loop and decrease the length in increments - // of 10% to recursively reduce the number of visible boxes by - // rank. Once all boxes are within the maxDistance, we're good. - if (typeof maxDistance !== 'undefined' && - Math.abs(origBoxes[i].pos - origBoxes[i].target) > maxDistance) { - // Reset the positions that are already set - origBoxes.slice(0, i + 1).forEach(function (box) { - delete box.pos; - }); - // Try with a smaller length - origBoxes.reducedLen = - (origBoxes.reducedLen || len) - (len * 0.1); - // Recurse - if (origBoxes.reducedLen > len * 0.1) { - H.distribute(origBoxes, len, maxDistance); - } - // Exceeded maxDistance => abort - return true; - } - posInCompositeBox += origBoxes[i].size; - i++; - })) { - // Exceeded maxDistance => abort - return true; - } + // 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) + points = this.getValidPoints( + points, + false, + !(options.connectNulls && !nullsAsZeroes && !connectCliffs) + ); + // Build the line + points.forEach(function (point, i) { + var plotX = point.plotX, + plotY = point.plotY, + lastPoint = points[i - 1], + // the path to this point from the previous + pathToPoint; + 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]]; + // Generate the spline as defined in the SplineSeries object + } else if (series.getPointSpline) { + 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); + if (step === 2) { + // step = center (#8073) + xMap.push(point.x); + } + } + graphPath.push.apply(graphPath, pathToPoint); + gap = false; + } }); - // Add the rest (hidden) boxes and sort by target - stableSort(origBoxes, sortByTarget); - }; - /** - * Draw the data labels - * - * @private - * @function Highcharts.Series#drawDataLabels - * @return {void} - * @fires Highcharts.Series#event:afterDrawDataLabels - */ - CartesianSeries.prototype.drawDataLabels = function () { + 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. + * + * @function Highcharts.Series#drawGraph + */ + drawGraph: function () { + var series = this, + options = this.options, + graphPath = (this.gappedPath || this.getGraphPath).call(this), + styledMode = this.chart.styledMode, + props = [["graph", "highcharts-graph"]]; + // Presentational properties + if (!styledMode) { + props[0].push( + options.lineColor || this.color || "#cccccc", // when colorByPoint = true + options.dashStyle + ); + } + props = series.getZonesGraphs(props); + // Draw the graph + props.forEach(function (prop, i) { + var graphKey = prop[0], + graph = series[graphKey], + verb = graph ? "animate" : "attr", + attribs; + if (graph) { + graph.endX = series.preventGraphAnimation + ? null + : graphPath.xMap; + graph.animate({ d: graphPath }); + } else if (graphPath.length) { + // #1487 + /** + * SVG element of area-based charts. Can be used for styling + * purposes. If zones are configured, this element will be + * hidden and replaced by multiple zone areas, accessible + * via `series['zone-area-x']` (where x is a number, + * starting with 0). + * + * @name Highcharts.Series#area + * @type {Highcharts.SVGElement|undefined} + */ + /** + * SVG element of line-based charts. Can be used for styling + * purposes. If zones are configured, this element will be + * hidden and replaced by multiple zone lines, accessible + * via `series['zone-graph-x']` (where x is a number, + * starting with 0). + * + * @name Highcharts.Series#graph + * @type {Highcharts.SVGElement|undefined} + */ + series[graphKey] = graph = series.chart.renderer + .path(graphPath) + .addClass(prop[1]) + .attr({ zIndex: 1 }) // #1069 + .add(series.group); + } + if (graph && !styledMode) { + attribs = { + stroke: prop[2], + "stroke-width": options.lineWidth, + // Polygon series use filled graph + fill: (series.fillGraph && series.color) || "none", + }; + if (prop[3]) { + attribs.dashstyle = prop[3]; + } else if (options.linecap !== "square") { + attribs["stroke-linecap"] = attribs["stroke-linejoin"] = + "round"; + } + graph[verb](attribs) + // Add shadow to normal series (0) or to first + // zone (1) #3932 + .shadow(i < 2 && options.shadow); + } + // Helpers for animation + if (graph) { + graph.startX = graphPath.xMap; + graph.isArea = graphPath.isArea; // For arearange animation + } + }); + }, + /** + * Get zones properties for building graphs. Extendable by series with + * multiple lines within one series. + * + * @private + * @function Highcharts.Series#getZonesGraphs + * + * @param {Array<Array<string>>} props + * + * @return {Array<Array<string>>} + */ + getZonesGraphs: function (props) { + // Add the zone properties if any + this.zones.forEach(function (zone, i) { + var propset = [ + "zone-graph-" + i, + "highcharts-graph highcharts-zone-graph-" + + i + + " " + + (zone.className || ""), + ]; + if (!this.chart.styledMode) { + propset.push( + zone.color || this.color, + zone.dashStyle || this.options.dashStyle + ); + } + props.push(propset); + }, this); + return props; + }, + /** + * Clip the graphs into zones for colors and styling. + * + * @private + * @function Highcharts.Series#applyZones + * @return {void} + */ + 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, + zoneArea, + zoneGraph; + if ( + zones.length && + (graph || area) && + axis && + typeof 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 + // unless it is not waterfall series, then use showLine property + // to set lines between columns to be visible (#7862) + if (graph && !this.showLine) { + graph.hide(); + } + if (area) { + area.hide(); + } + // Create the clips + extremes = axis.getExtremes(); + zones.forEach(function (threshold, i) { + translatedFrom = reversed + ? horiz + ? chart.plotWidth + : 0 + : horiz + ? 0 + : axis.toPixels(extremes.min) || 0; + translatedFrom = clamp( + pick(translatedTo, translatedFrom), + 0, + chartSizeMax + ); + translatedTo = clamp( + Math.round( + axis.toPixels(pick(threshold.value, extremes.max), true) || + 0 + ), + 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); + } + // when no data, graph zone is not applied and after setData + // clip was ignored. As a result, it should be applied each + // time. + zoneArea = series["zone-area-" + i]; + zoneGraph = series["zone-graph-" + i]; + if (graph && zoneGraph) { + zoneGraph.clip(clips[i]); + } + if (area && zoneArea) { + zoneArea.clip(clips[i]); + } + // if this zone extends out of the axis, ignore the others + ignoreZones = threshold.value > extremes.max; + // Clear translatedTo for indicators + if (series.resetZones && translatedTo === 0) { + translatedTo = void 0; + } + }); + this.clips = clips; + } else if (series.visible) { + // If zones were removed, restore graph and area + if (graph) { + graph.show(true); + } + if (area) { + area.show(true); + } + } + }, + /** + * Initialize and perform group inversion on series.group and + * series.markerGroup. + * + * @private + * @function Highcharts.Series#invertGroups + * @param {boolean} [inverted] + * @return {void} + */ + invertGroups: function (inverted) { var series = this, - chart = series.chart, - seriesOptions = series.options, - seriesDlOptions = seriesOptions.dataLabels, - points = series.points, - pointOptions, - hasRendered = series.hasRendered || 0, - dataLabelsGroup, - dataLabelAnim = seriesDlOptions.animation, - animationConfig = seriesDlOptions.defer ? - getDeferredAnimation(chart, - dataLabelAnim, - series) : - { defer: 0, - duration: 0 }, - renderer = chart.renderer; + chart = series.chart; /** - * Handle the dataLabels.filter option. * @private */ - 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; + function setInvert() { + ["group", "markerGroup"].forEach(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; + // If inverted polar, don't invert series group + series[groupName].invert( + series.isRadialSeries ? false : inverted + ); + } + }); + } + // Pie, go away (#1736) + if (!series.xAxis) { + return; + } + // A fixed size is needed for inversion to work + series.eventsToUnbind.push(addEvent(chart, "resize", setInvert)); + // Do it now + setInvert(); + // 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 + * @function Highcharts.Series#plotGroup + * @param {string} prop + * @param {string} name + * @param {string} visibility + * @param {number} [zIndex] + * @param {Highcharts.SVGElement} [parent] + * @return {Highcharts.SVGElement} + */ + plotGroup: function (prop, name, visibility, zIndex, parent) { + var group = this[prop], + isNew = !group, + attrs = { + visibility: visibility, + zIndex: zIndex || 0.1, // IE8 and pointer logic use this + }; + // Avoid setting undefined opacity, or in styled mode + if ( + typeof this.opacity !== "undefined" && + !this.chart.styledMode && + this.state !== "inactive" // #13719 + ) { + attrs.opacity = this.opacity; + } + // Generate it on first call + if (isNew) { + this[prop] = group = this.chart.renderer.g().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(attrs)[isNew ? "attr" : "animate"](this.getPlotBox()); + return group; + }, + /** + * Get the translation and scale for the plot area of this series. + * + * @function Highcharts.Series#getPlotBox + * + * @return {Highcharts.SeriesPlotBoxObject} + */ + 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, + scaleY: 1, + }; + }, + /** + * Removes the event handlers attached previously with addEvents. + * + * @private + * @function Highcharts.Series#removeEvents + * @param {boolean} [keepEventsForUpdate] + * @return {void} + */ + removeEvents: function (keepEventsForUpdate) { + var series = this; + if (!keepEventsForUpdate) { + // remove all events + removeEvent(series); + } else if (series.eventsToUnbind.length) { + // remove only internal events for proper update + // #12355 - solves problem with multiple destroy events + series.eventsToUnbind.forEach(function (unbind) { + unbind(); + }); + series.eventsToUnbind.length = 0; + } + }, + /** + * 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. + * + * @function Highcharts.Series#render + * + * @return {void} + * + * @fires Highcharts.Series#event:afterRender + */ + render: function () { + var series = this, + chart = series.chart, + group, + options = series.options, + animOptions = animObject(options.animation), + // Animation doesn't work in IE8 quirks when the group div is + // hidden, and looks bad in other oldIE + animDuration = + !series.finishedAnimating && + chart.renderer.isSVG && + animOptions.duration, + visibility = series.visible ? "inherit" : "hidden", // #2597 + zIndex = options.zIndex, + hasRendered = series.hasRendered, + chartSeriesGroup = chart.seriesGroup, + inverted = chart.inverted; + fireEvent(this, "render"); + // 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) { + series.animate(true); + } + // SVGRenderer needs to know this before drawing elements (#1089, + // #1795) + group.inverted = + series.isCartesian || series.invertable ? inverted : false; + // Draw the graph if any + if (series.drawGraph) { + series.drawGraph(); + series.applyZones(); + } + // Draw the points + if (series.visible) { + series.drawPoints(); + } + /* series.points.forEach(function (point) { + if (point.redraw) { + point.redraw(); } - return false; - } - return true; + }); */ + // Draw the data labels + if (series.drawDataLabels) { + series.drawDataLabels(); + } + // In pie charts, slices are added to the DOM, but actual rendering + // is postponed until labels reserved their space + if (series.redrawPoints) { + series.redrawPoints(); } + // 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) { + 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) { + // Additional time if defer is defined before afterAnimate + // will be triggered + if (animDuration && animOptions.defer) { + animDuration += animOptions.defer; + } + series.animationTimeout = syncTimeout(function () { + series.afterAnimate(); + }, animDuration || 0); + } + // Means data is in accordance with what you see + series.isDirty = false; + // (See #322) series.isDirty = series.isDirtyData = false; // means + // data is in accordance with what you see + series.hasRendered = true; + fireEvent(series, "afterRender"); + }, + /** + * Redraw the series. This function is called internally from + * `chart.redraw` and normally shouldn't be called directly. + * + * @private + * @function Highcharts.Series#redraw + * @return {void} + */ + 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"], + /** + * @private + * @function Highcharts.Series#searchPoint + * @param {Highcharts.PointerEventObject} e + * @param {boolean} [compareX] + * @return {Highcharts.Point} + */ + 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, + e + ); + }, + /** + * 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 + * @function Highcharts.Series#buildKDTree + * @param {Highcharts.PointerEventObject} [e] + * @return {void} + */ + buildKDTree: function (e) { + // 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; /** - * Merge two objects that can be arrays. If one of them is an array, the - * other is merged into each element. If both are arrays, each element is - * merged by index. If neither are arrays, we use normal merge. + * Internal function * @private */ - function mergeArrays(one, two) { - var res = [], - i; - if (isArray(one) && !isArray(two)) { - res = one.map(function (el) { - return merge(el, two); - }); - } - else if (isArray(two) && !isArray(one)) { - res = two.map(function (el) { - return merge(one, el); - }); - } - else if (!isArray(one) && !isArray(two)) { - res = merge(one, two); - } - else { - i = Math.max(one.length, two.length); - while (i--) { - res[i] = merge(one[i], two[i]); - } - } - return res; - } - // Merge in plotOptions.dataLabels for series - seriesDlOptions = mergeArrays(mergeArrays(chart.options.plotOptions && - chart.options.plotOptions.series && - chart.options.plotOptions.series.dataLabels, chart.options.plotOptions && - chart.options.plotOptions[series.type] && - chart.options.plotOptions[series.type].dataLabels), seriesDlOptions); - fireEvent(this, 'drawDataLabels'); - if (isArray(seriesDlOptions) || - seriesDlOptions.enabled || - series._hasPointLabels) { - // Create a separate group for the data labels to avoid rotation - dataLabelsGroup = series.plotGroup('dataLabelsGroup', 'data-labels', !hasRendered ? 'hidden' : 'inherit', // #5133, #10220 - seriesDlOptions.zIndex || 6); - dataLabelsGroup.attr({ opacity: +hasRendered }); // #3300 - if (!hasRendered) { - var group = series.dataLabelsGroup; - if (group) { - if (series.visible) { // #2597, #3023, #3024 - dataLabelsGroup.show(true); - } - group[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, animationConfig); - } - } - // Make the labels for each point - points.forEach(function (point) { - // Merge in series options for the point. - // @note dataLabelAttribs (like pointAttribs) would eradicate - // the need for dlOptions, and simplify the section below. - pointOptions = splat(mergeArrays(seriesDlOptions, point.dlOptions || // dlOptions is used in treemaps - (point.options && point.options.dataLabels))); - // Handle each individual data label for this point - pointOptions.forEach(function (labelOptions, i) { - // Options for one datalabel - var labelEnabled = (labelOptions.enabled && - // #2282, #4641, #7112, #10049 - (!point.isNull || point.dataLabelOnNull) && - applyFilter(point, - labelOptions)), - labelConfig, - formatString, - labelText, - style, - rotation, - attr, - dataLabel = point.dataLabels ? point.dataLabels[i] : - point.dataLabel, - connector = point.connectors ? point.connectors[i] : - point.connector, - labelDistance = pick(labelOptions.distance, - point.labelDistance), - isNew = !dataLabel; - if (labelEnabled) { - // Create individual options structure that can be extended - // without affecting others - labelConfig = point.getLabelConfig(); - formatString = pick(labelOptions[point.formatPrefix + 'Format'], labelOptions.format); - labelText = defined(formatString) ? - format(formatString, labelConfig, chart) : - (labelOptions[point.formatPrefix + 'Formatter'] || - labelOptions.formatter).call(labelConfig, labelOptions); - style = labelOptions.style; - rotation = labelOptions.rotation; - if (!chart.styledMode) { - // Determine the color - style.color = pick(labelOptions.color, style.color, series.color, '#000000'); - // Get automated contrast color - if (style.color === 'contrast') { - point.contrastColor = renderer.getContrast((point.color || series.color)); - style.color = (!defined(labelDistance) && - labelOptions.inside) || - labelDistance < 0 || - !!seriesOptions.stacking ? - point.contrastColor : - '#000000'; - } - else { - delete point.contrastColor; - } - if (seriesOptions.cursor) { - style.cursor = seriesOptions.cursor; - } - } - attr = { - r: labelOptions.borderRadius || 0, - rotation: rotation, - padding: labelOptions.padding, - zIndex: 1 - }; - if (!chart.styledMode) { - attr.fill = labelOptions.backgroundColor; - attr.stroke = labelOptions.borderColor; - attr['stroke-width'] = labelOptions.borderWidth; - } - // Remove unused attributes (#947) - objectEach(attr, function (val, name) { - if (typeof val === 'undefined') { - delete attr[name]; - } - }); - } - // If the point is outside the plot area, destroy it. #678, #820 - if (dataLabel && (!labelEnabled || !defined(labelText))) { - point.dataLabel = - point.dataLabel && point.dataLabel.destroy(); - if (point.dataLabels) { - // Remove point.dataLabels if this was the last one - if (point.dataLabels.length === 1) { - delete point.dataLabels; - } - else { - delete point.dataLabels[i]; - } - } - if (!i) { - delete point.dataLabel; - } - if (connector) { - point.connector = point.connector.destroy(); - if (point.connectors) { - // Remove point.connectors if this was the last one - if (point.connectors.length === 1) { - delete point.connectors; - } - else { - delete point.connectors[i]; - } - } - } - // Individual labels are disabled if the are explicitly disabled - // in the point options, or if they fall outside the plot area. - } - else if (labelEnabled && defined(labelText)) { - if (!dataLabel) { - // Create new label element - point.dataLabels = point.dataLabels || []; - dataLabel = point.dataLabels[i] = rotation ? - // Labels don't rotate, use text element - renderer.text(labelText, 0, -9999, labelOptions.useHTML) - .addClass('highcharts-data-label') : - // We can use label - renderer.label(labelText, 0, -9999, labelOptions.shape, null, null, labelOptions.useHTML, null, 'data-label'); - // Store for backwards compatibility - if (!i) { - point.dataLabel = dataLabel; - } - dataLabel.addClass(' highcharts-data-label-color-' + point.colorIndex + - ' ' + (labelOptions.className || '') + - ( // #3398 - labelOptions.useHTML ? - ' highcharts-tracker' : - '')); - } - else { - // Use old element and just update text - attr.text = labelText; - } - // Store data label options for later access - dataLabel.options = labelOptions; - dataLabel.attr(attr); - if (!chart.styledMode) { - // Styles must be applied before add in order to read - // text bounding box - dataLabel.css(style).shadow(labelOptions.shadow); - } - if (!dataLabel.added) { - dataLabel.add(dataLabelsGroup); - } - if (labelOptions.textPath && !labelOptions.useHTML) { - dataLabel.setTextPath((point.getDataLabelPath && - point.getDataLabelPath(dataLabel)) || point.graphic, labelOptions.textPath); - if (point.dataLabelPath && - !labelOptions.textPath.enabled) { - // clean the DOM - point.dataLabelPath = point.dataLabelPath.destroy(); - } - } - // Now the data label is created and placed at 0,0, so we - // need to align it - series.alignDataLabel(point, dataLabel, labelOptions, null, isNew); - } - }); + 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 + ), + }; + } } - fireEvent(this, 'afterDrawDataLabels'); - }; - /** - * Align each individual data label. - * - * @private - * @function Highcharts.Series#alignDataLabel - * @param {Highcharts.Point} point - * @param {Highcharts.SVGElement} dataLabel - * @param {Highcharts.DataLabelsOptions} options - * @param {Highcharts.BBoxObject} alignTo - * @param {boolean} [isNew] - * @return {void} - */ - CartesianSeries.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { + /** + * Start the recursive build process with a clone of the points + * array and null points filtered out. (#3873) + * @private + */ + 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. Also if touchstart, we + // may be dealing with click events on mobile, so don't delay + // (#6817). + syncTimeout( + startRecursive, + series.options.kdNow || (e && e.type === "touchstart") ? 0 : 1 + ); + }, + /** + * @private + * @function Highcharts.Series#searchKDTree + * @param {Highcharts.KDPointSearchObject} point + * @param {boolean} [compareX] + * @param {Highcharts.PointerEventObject} [e] + * @return {Highcharts.Point|undefined} + */ + searchKDTree: function (point, compareX, e) { var series = this, - chart = this.chart, - inverted = this.isCartesian && chart.inverted, - enabledDataSorting = this.enabledDataSorting, - plotX = pick(point.dlBox && point.dlBox.centerX, - point.plotX, -9999), - plotY = pick(point.plotY, -9999), - bBox = dataLabel.getBBox(), - baseline, - rotation = options.rotation, - normRotation, - negRotation, - align = options.align, - rotCorr, // rotation correction - isInsidePlot = chart.isInsidePlot(plotX, - Math.round(plotY), - inverted), - // Math.round for rounding errors (#2683), alignTo to allow column - // labels (#2700) - alignAttr, // the final position; - justify = pick(options.overflow, (enabledDataSorting ? 'none' : 'justify')) === 'justify', visible = this.visible && - point.visible !== false && - (point.series.forceDL || - (enabledDataSorting && !justify) || - isInsidePlot || - ( - // If the data label is inside the align box, it is enough - // that parts of the align box is inside the plot area - // (#12370) - options.inside && alignTo && chart.isInsidePlot(plotX, inverted ? - alignTo.x + 1 : - alignTo.y + alignTo.height - 1, inverted))), setStartPos = function (alignOptions) { - if (enabledDataSorting && series.xAxis && !justify) { - series.setDataLabelStartPos(point, dataLabel, isNew, isInsidePlot, alignOptions); - } - }; - if (visible) { - baseline = chart.renderer.fontMetrics(chart.styledMode ? void 0 : options.style.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 || 0) + - alignTo.width / 2 + - rotCorr.x), - y: (alignTo.y + - (options.y || 0) + - { top: 0, middle: 0.5, bottom: 1 }[options.verticalAlign] * - alignTo.height) - }; - setStartPos(alignAttr); // data sorting - dataLabel[isNew ? 'attr' : 'animate'](alignAttr) - .attr({ - 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; - } - dataLabel.placed = true; - dataLabel.alignAttr = alignAttr; - } - else { - setStartPos(alignTo); // data sorting - dataLabel.align(options, null, alignTo); - alignAttr = dataLabel.alignAttr; - } - // Handle justify or crop - if (justify && alignTo.height >= 0) { // #8830 - 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 - }); - } + 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. + * @private + */ + 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; } - // To use alignAttr property in hideOverlappingLabels - if (isNew && enabledDataSorting) { - dataLabel.placed = false; + /** + * @private + */ + 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(e); } - // Show or hide based on the final aligned position - if (!visible && (!enabledDataSorting || justify)) { - dataLabel.hide(true); - dataLabel.placed = false; // don't animate back in + if (this.kdTree) { + return _search(point, this.kdTree, kdDimensions, kdDimensions); } + }, + /** + * @private + * @function Highcharts.Series#pointPlacementToXValue + * @return {number} + */ + pointPlacementToXValue: function () { + var _a = this, + _b = _a.options, + pointPlacement = _b.pointPlacement, + pointRange = _b.pointRange, + axis = _a.xAxis; + var factor = pointPlacement; + // Point placement is relative to each series pointRange (#5889) + if (factor === "between") { + factor = axis.reversed ? -0.5 : 0.5; // #11955 + } + return isNumber(factor) + ? factor * pick(pointRange, axis.pointRange) + : 0; + }, + /** + * @private + * @function Highcharts.Series#isPointInside + * @param {Highcharts.Point} point + * @return {boolean} + */ + isPointInside: function (point) { + var isInside = + typeof point.plotY !== "undefined" && + typeof point.plotX !== "undefined" && + point.plotY >= 0 && + point.plotY <= this.yAxis.len && // #3519 + point.plotX >= 0 && + point.plotX <= this.xAxis.len; + return isInside; + }, + } + ); // 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 + */ + /** + * 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. + * + * @type {Highcharts.SeriesLinecapValue} + * @default round + * @since 3.0.7 + * @apioption plotOptions.line.linecap + */ + /** + * A `line` series. If the [type](#series.line.type) option is not + * specified, it is inherited from [chart.type](#chart.type). + * + * @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 following snippet shows only a + * few settings, see the complete options set 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" + * }] + * ``` + * + * **Note:** In TypeScript you have to extend `PointOptionsObject` with an + * additional declaration to allow custom data types: + * ```ts + * declare module `highcharts` { + * interface PointOptionsObject { + * custom: Record<string, (boolean|number|string)>; + * } + * } + * ``` + * + * @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 + * + * @declare Highcharts.PointOptionsObject + * @type {Array<number|Array<(number|string),(number|null)>|null|*>} + * @apioption series.line.data + */ + /** + * An additional, individual class name for the data point's graphic + * representation. + * + * @type {string} + * @since 5.0.0 + * @product highcharts gantt + * @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`. + * + * @sample {highcharts} highcharts/point/color/ + * Mark the highest point + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @product highcharts highstock gantt + * @apioption series.line.data.color + */ + /** + * A specific color index to use for the point, so its graphic representations + * are given the class name `highcharts-color-{n}`. In styled mode this will + * change the color of the graphic. In non-styled mode, the color by is set by + * the `fill` attribute, so the change in class name won't have a visual effect + * by default. + * + * @type {number} + * @since 5.0.0 + * @product highcharts gantt + * @apioption series.line.data.colorIndex + */ + /** + * A reserved subspace to store options and values for customized functionality. + * Here you can add additional data for your own event callbacks and formatter + * callbacks. + * + * @sample {highcharts} highcharts/point/custom/ + * Point and series with custom data + * + * @type {Highcharts.Dictionary<*>} + * @apioption series.line.data.custom + */ + /** + * Individual data label for each point. The options are the same as + * the ones for [plotOptions.series.dataLabels]( + * #plotOptions.series.dataLabels). + * + * @sample highcharts/point/datalabels/ + * Show a label for the last value + * + * @declare Highcharts.DataLabelsOptions + * @extends plotOptions.line.dataLabels + * @product highcharts highstock gantt + * @apioption series.line.data.dataLabels + */ + /** + * A description of the point to add to the screen reader information + * about the point. + * + * @type {string} + * @since 5.0.0 + * @requires modules/accessibility + * @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()`. + * + * @sample {highcharts} highcharts/point/id/ + * Remove an id'd point + * + * @type {string} + * @since 1.2.0 + * @product highcharts highstock gantt + * @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, dataLabels, etc. + * + * @see [xAxis.uniqueNames](#xAxis.uniqueNames) + * + * @sample {highcharts} highcharts/series/data-array-of-objects/ + * Point names + * + * @type {string} + * @apioption series.line.data.name + */ + /** + * Whether the data point is selected initially. + * + * @type {boolean} + * @default false + * @product highcharts highstock gantt + * @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|null} + * @product highcharts highstock + * @apioption series.line.data.y + */ + /** + * The individual point events. + * + * @extends plotOptions.series.point.events + * @product highcharts highstock gantt + * @apioption series.line.data.events + */ + /** + * Options for the point markers of line-like series. + * + * @declare Highcharts.PointMarkerOptionsObject + * @extends plotOptions.series.marker + * @product highcharts highstock + * @apioption series.line.data.marker + */ + (""); // include precedent doclets in transpilat + + return CartesianSeries; + } + ); + _registerModule( + _modules, + "Series/LineSeries.js", + [_modules["Core/Series/CartesianSeries.js"], _modules["Core/Globals.js"]], + function (CartesianSeries, H) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + H.Series = CartesianSeries; // backwards compatibility + + return H.Series; + } + ); + _registerModule( + _modules, + "Extensions/Stacking.js", + [ + _modules["Core/Axis/Axis.js"], + _modules["Core/Chart/Chart.js"], + _modules["Core/Globals.js"], + _modules["Core/Axis/StackingAxis.js"], + _modules["Core/Utilities.js"], + ], + function (Axis, Chart, H, StackingAxis, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var correctFloat = U.correctFloat, + defined = U.defined, + destroyObjectProperties = U.destroyObjectProperties, + format = U.format, + isNumber = U.isNumber, + pick = U.pick; + /** + * Stack of data points + * + * @product highcharts + * + * @interface Highcharts.StackItemObject + */ /** + * Alignment settings + * @name Highcharts.StackItemObject#alignOptions + * @type {Highcharts.AlignObject} + */ /** + * Related axis + * @name Highcharts.StackItemObject#axis + * @type {Highcharts.Axis} + */ /** + * Cumulative value of the stacked data points + * @name Highcharts.StackItemObject#cumulative + * @type {number} + */ /** + * True if on the negative side + * @name Highcharts.StackItemObject#isNegative + * @type {boolean} + */ /** + * Related SVG element + * @name Highcharts.StackItemObject#label + * @type {Highcharts.SVGElement} + */ /** + * Related stack options + * @name Highcharts.StackItemObject#options + * @type {Highcharts.YAxisStackLabelsOptions} + */ /** + * Total value of the stacked data points + * @name Highcharts.StackItemObject#total + * @type {number} + */ /** + * Shared x value of the stack + * @name Highcharts.StackItemObject#x + * @type {number} + */ + (""); // detached doclets above + var Series = H.Series; + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * The class for stacks. Each stack, on a specific X value and either negative + * or positive, has its own stack item. + * + * @private + * @class + * @name Highcharts.StackItem + * @param {Highcharts.Axis} axis + * @param {Highcharts.YAxisStackLabelsOptions} options + * @param {boolean} isNegative + * @param {number} x + * @param {Highcharts.OptionsStackingValue} [stackOption] + */ + var StackItem = /** @class */ (function () { + function StackItem(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 = 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 = {}; + this.hasValidPoints = false; + // 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: options.y, + x: options.x, + }; + this.textAlign = + options.textAlign || + (inverted ? (isNegative ? "right" : "left") : "center"); + } + /** + * @private + * @function Highcharts.StackItem#destroy + */ + StackItem.prototype.destroy = function () { + destroyObjectProperties(this, this.axis); }; /** - * Set starting position for data label sorting animation. + * Renders the stack total label and adds it to the stack label group. * * @private - * @function Highcharts.Series#setDataLabelStartPos - * @param {Highcharts.SVGElement} dataLabel - * @param {Highcharts.ColumnPoint} point - * @param {boolean | undefined} [isNew] - * @param {boolean} [isInside] - * @param {Highcharts.AlignObject} [alignOptions] - * - * @return {void} - */ - CartesianSeries.prototype.setDataLabelStartPos = function (point, dataLabel, isNew, isInside, alignOptions) { - var chart = this.chart, - inverted = chart.inverted, - xAxis = this.xAxis, - reversed = xAxis.reversed, - labelCenter = inverted ? dataLabel.height / 2 : dataLabel.width / 2, - pointWidth = point.pointWidth, - halfWidth = pointWidth ? pointWidth / 2 : 0, - startXPos, - startYPos; - startXPos = inverted ? - alignOptions.x : - (reversed ? - -labelCenter - halfWidth : - xAxis.width - labelCenter + halfWidth); - startYPos = inverted ? - (reversed ? - this.yAxis.height - labelCenter + halfWidth : - -labelCenter - halfWidth) : alignOptions.y; - dataLabel.startXPos = startXPos; - dataLabel.startYPos = startYPos; - // We need to handle visibility in case of sorting point outside plot area - if (!isInside) { - dataLabel - .attr({ opacity: 1 }) - .animate({ opacity: 0 }, void 0, dataLabel.hide); - } - else if (dataLabel.visibility === 'hidden') { - dataLabel.show(); - dataLabel - .attr({ opacity: 0 }) - .animate({ opacity: 1 }); - } - // Save start position on first render, but do not change position - if (!chart.hasRendered) { - return; + * @function Highcharts.StackItem#render + * @param {Highcharts.SVGElement} group + */ + StackItem.prototype.render = function (group) { + var chart = this.axis.chart, + options = this.options, + formatOption = options.format, + attr = {}, + str = formatOption // format the text in the label + ? format(formatOption, this, chart) + : options.formatter.call(this); + // 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" }); + } else { + // Create new label + this.label = chart.renderer.label( + str, + null, + null, + options.shape, + null, + null, + options.useHTML, + false, + "stack-labels" + ); + attr = { + r: options.borderRadius || 0, + text: str, + rotation: options.rotation, + padding: pick(options.padding, 5), + visibility: "hidden", // hidden until setOffset is called + }; + if (!chart.styledMode) { + attr.fill = options.backgroundColor; + attr.stroke = options.borderColor; + attr["stroke-width"] = options.borderWidth; + this.label.css(options.style); } - // Set start position - if (isNew) { - dataLabel.attr({ x: dataLabel.startXPos, y: dataLabel.startYPos }); + this.label.attr(attr); + if (!this.label.added) { + this.label.add(group); // add to the labels-group } - dataLabel.placed = true; + } + // Rank it higher than data labels (#8742) + this.label.labelrank = chart.plotHeight; }; /** - * If data labels fall partly outside the plot area, align them back in, in a - * way that doesn't hide the point. + * Sets the offset that the stack has from the x value and repositions the + * label. * * @private - * @function Highcharts.Series#justifyDataLabel - * @param {Highcharts.SVGElement} dataLabel - * @param {Highcharts.DataLabelsOptions} options - * @param {Highcharts.SVGAttributes} alignAttr - * @param {Highcharts.BBoxObject} bBox - * @param {Highcharts.BBoxObject} [alignTo] - * @param {boolean} [isNew] - * @return {boolean|undefined} - */ - CartesianSeries.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); - var _a = options.x, - x = _a === void 0 ? 0 : _a, - _b = options.y, - y = _b === void 0 ? 0 : _b; - // Off left - off = alignAttr.x + padding; - if (off < 0) { - if (align === 'right' && x >= 0) { - options.align = 'left'; - options.inside = true; - } - else { - x -= off; - } - justified = true; - } - // Off right - off = alignAttr.x + bBox.width - padding; - if (off > chart.plotWidth) { - if (align === 'left' && x <= 0) { - options.align = 'right'; - options.inside = true; - } - else { - x += chart.plotWidth - off; - } - justified = true; - } - // Off top - off = alignAttr.y + padding; - if (off < 0) { - if (verticalAlign === 'bottom' && y >= 0) { - options.verticalAlign = 'top'; - options.inside = true; - } - else { - y -= off; - } - justified = true; - } - // Off bottom - off = alignAttr.y + bBox.height - padding; - if (off > chart.plotHeight) { - if (verticalAlign === 'top' && y <= 0) { - options.verticalAlign = 'bottom'; - options.inside = true; - } - else { - y += chart.plotHeight - off; - } - justified = true; + * @function Highcarts.StackItem#setOffset + * @param {number} xOffset + * @param {number} xWidth + * @param {number} [boxBottom] + * @param {number} [boxTop] + * @param {number} [defaultX] + */ + StackItem.prototype.setOffset = function ( + xOffset, + xWidth, + boxBottom, + boxTop, + defaultX + ) { + var stackItem = this, + axis = stackItem.axis, + chart = axis.chart, + // stack value translated mapped to chart coordinates + y = axis.translate( + axis.stacking.usePercentage + ? 100 + : boxTop + ? boxTop + : stackItem.total, + 0, + 0, + 0, + 1 + ), + yZero = axis.translate(boxBottom ? boxBottom : 0), // stack origin + // stack height: + h = defined(y) && Math.abs(y - yZero), + // x position: + x = pick(defaultX, chart.xAxis[0].translate(stackItem.x)) + xOffset, + stackBox = + defined(y) && + stackItem.getStackBox(chart, stackItem, x, y, xWidth, h, axis), + label = stackItem.label, + isNegative = stackItem.isNegative, + isJustify = + pick(stackItem.options.overflow, "justify") === "justify", + textAlign = stackItem.textAlign, + visible; + if (label && stackBox) { + var bBox = label.getBBox(), + padding = label.padding, + boxOffsetX, + boxOffsetY; + if (textAlign === "left") { + boxOffsetX = chart.inverted ? -padding : padding; + } else if (textAlign === "right") { + boxOffsetX = bBox.width; + } else { + if (chart.inverted && textAlign === "center") { + boxOffsetX = bBox.width / 2; + } else { + boxOffsetX = chart.inverted + ? isNegative + ? bBox.width + padding + : -padding + : bBox.width / 2; + } + } + boxOffsetY = chart.inverted + ? bBox.height / 2 + : isNegative + ? -padding + : bBox.height; + // Reset alignOptions property after justify #12337 + stackItem.alignOptions.x = pick(stackItem.options.x, 0); + stackItem.alignOptions.y = pick(stackItem.options.y, 0); + // Set the stackBox position + stackBox.x -= boxOffsetX; + stackBox.y -= boxOffsetY; + // Align the label to the box + label.align(stackItem.alignOptions, null, stackBox); + // Check if label is inside the plotArea #12294 + if ( + chart.isInsidePlot( + label.alignAttr.x + boxOffsetX - stackItem.alignOptions.x, + label.alignAttr.y + boxOffsetY - stackItem.alignOptions.y + ) + ) { + label.show(); + } else { + // Move label away to avoid the overlapping issues + label.alignAttr.y = -9999; + isJustify = false; } - if (justified) { - options.x = x; - options.y = y; - dataLabel.placed = !isNew; - dataLabel.align(options, void 0, alignTo); + if (isJustify) { + // Justify stackLabel into the stackBox + Series.prototype.justifyDataLabel.call( + this.axis, + label, + stackItem.alignOptions, + label.alignAttr, + bBox, + stackBox + ); + } + label.attr({ + x: label.alignAttr.x, + y: label.alignAttr.y, + }); + if (pick(!isJustify && stackItem.options.crop, true)) { + visible = + isNumber(label.x) && + isNumber(label.y) && + chart.isInsidePlot(label.x - padding + label.width, label.y) && + chart.isInsidePlot(label.x + padding, label.y); + if (!visible) { + label.hide(); + } } - return justified; + } }; - if (seriesTypes.pie) { - seriesTypes.pie.prototype.dataLabelPositioners = { - // Based on the value computed in Highcharts' distribute algorithm. - radialDistributionY: function (point) { - return point.top + point.distributeBox.pos; - }, - // 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 - // Based on the value computed in Highcharts' distribute algorithm. - radialDistributionX: function (series, point, y, naturalY) { - return series.getX(y < point.top + 2 || y > point.bottom - 2 ? - naturalY : - y, point.half, point); - }, - // dataLabels.distance determines the x position of the label - justify: function (point, radius, seriesCenter) { - return seriesCenter[0] + (point.half ? -1 : 1) * - (radius + point.labelDistance); - }, - // Left edges of the left-half labels touch the left edge of the plot - // area. Right edges of the right-half labels touch the right edge of - // the plot area. - alignToPlotEdges: function (dataLabel, half, plotWidth, plotLeft) { - var dataLabelWidth = dataLabel.getBBox().width; - return half ? dataLabelWidth + plotLeft : - plotWidth - dataLabelWidth - plotLeft; - }, - // Connectors of each side end in the same x position. Labels are - // aligned to them. Left edge of the widest left-half label touches the - // left edge of the plot area. Right edge of the widest right-half label - // touches the right edge of the plot area. - alignToConnectors: function (points, half, plotWidth, plotLeft) { - var maxDataLabelWidth = 0, - dataLabelWidth; - // find widest data label - points.forEach(function (point) { - dataLabelWidth = point.dataLabel.getBBox().width; - if (dataLabelWidth > maxDataLabelWidth) { - maxDataLabelWidth = dataLabelWidth; - } - }); - return half ? maxDataLabelWidth + plotLeft : - plotWidth - maxDataLabelWidth - plotLeft; - } - }; - /** - * Override the base drawDataLabels method by pie specific functionality - * - * @private - * @function Highcharts.seriesTypes.pie#drawDataLabels - * @return {void} - */ - seriesTypes.pie.prototype.drawDataLabels = function () { - var series = this, - data = series.data, - point, - chart = series.chart, - options = series.options.dataLabels || {}, - connectorPadding = options.connectorPadding, - connectorWidth, - plotWidth = chart.plotWidth, - plotHeight = chart.plotHeight, - plotLeft = chart.plotLeft, - maxWidth = Math.round(chart.chartWidth / 3), - connector, - seriesCenter = series.center, - radius = seriesCenter[2] / 2, - centerY = seriesCenter[1], - dataLabel, - dataLabelWidth, - // labelPos, - labelPosition, - labelHeight, - // divide the points into right and left halves for anti collision - halves = [ - [], - [] // left - ], - x, - y, - visibility, - j, - overflow = [0, 0, 0, 0], // top, right, bottom, left - dataLabelPositioners = series.dataLabelPositioners, - pointDataLabelsOptions; - // get out if not enabled - if (!series.visible || - (!options.enabled && - !series._hasPointLabels)) { - return; - } - // Reset all labels that have been shortened - data.forEach(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 - CartesianSeries.prototype.drawDataLabels.apply(series); - data.forEach(function (point) { - if (point.dataLabel) { - if (point.visible) { // #407, #2510 - // Arrange points for detection collision - halves[point.half].push(point); - // Reset positions (#4905) - point.dataLabel._pos = null; - // Avoid long labels squeezing the pie size too far down - if (!defined(options.style.width) && - !defined(point.options.dataLabels && - point.options.dataLabels.style && - point.options.dataLabels.style.width)) { - if (point.dataLabel.getBBox().width > maxWidth) { - point.dataLabel.css({ - // Use a fraction of the maxWidth to avoid - // wrapping close to the end of the string. - width: Math.round(maxWidth * 0.7) + 'px' - }); - point.dataLabel.shortened = true; - } - } - } - else { - point.dataLabel = point.dataLabel.destroy(); - // Workaround to make pies destroy multiple datalabels - // correctly. This logic needs rewriting to support multiple - // datalabels fully. - if (point.dataLabels && point.dataLabels.length === 1) { - delete point.dataLabels; - } - } - } - }); - /* Loop over the points in each half, starting from the top and bottom - * of the pie to detect overlapping labels. - */ - halves.forEach(function (points, i) { - var top, - bottom, - length = points.length, - positions = [], - naturalY, - sideOverflow, - size, - distributionLength; - 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); - points.forEach(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.distributeBox = { - target: point.labelPosition.natural.y - - point.top + size / 2, - size: size, - rank: point.y - }; - positions.push(point.distributeBox); - } - }); - distributionLength = bottom + size - top; - H.distribute(positions, distributionLength, distributionLength / 5); - } - // Now the used slots are sorted, fill them up sequentially - for (j = 0; j < length; j++) { - point = points[j]; - // labelPos = point.labelPos; - labelPosition = point.labelPosition; - dataLabel = point.dataLabel; - visibility = point.visible === false ? 'hidden' : 'inherit'; - naturalY = labelPosition.natural.y; - y = naturalY; - if (positions && defined(point.distributeBox)) { - if (typeof point.distributeBox.pos === 'undefined') { - visibility = 'hidden'; - } - else { - labelHeight = point.distributeBox.size; - // Find label's y position - y = dataLabelPositioners - .radialDistributionY(point); - } - } - // It is needed to delete point.positionIndex for - // dynamically added points etc. - delete point.positionIndex; // @todo unused - // Find label's x position - // justify is undocumented in the API - preserve support for it - if (options.justify) { - x = dataLabelPositioners.justify(point, radius, seriesCenter); - } - else { - switch (options.alignTo) { - case 'connectors': - x = dataLabelPositioners.alignToConnectors(points, i, plotWidth, plotLeft); - break; - case 'plotEdges': - x = dataLabelPositioners.alignToPlotEdges(dataLabel, i, plotWidth, plotLeft); - break; - default: - x = dataLabelPositioners.radialDistributionX(series, point, y, naturalY); - } - } - // Record the placement and visibility - dataLabel._attr = { - visibility: visibility, - align: labelPosition.alignment - }; - pointDataLabelsOptions = point.options.dataLabels || {}; - dataLabel._pos = { - x: (x + - pick(pointDataLabelsOptions.x, options.x) + // (#12985) - ({ - left: connectorPadding, - right: -connectorPadding - }[labelPosition.alignment] || 0)), - // 10 is for the baseline (label vs text) - y: (y + - pick(pointDataLabelsOptions.y, options.y) - // (#12985) - 10) - }; - // labelPos.x = x; - // labelPos.y = y; - labelPosition.final.x = x; - labelPosition.final.y = y; - // Detect overflowing data labels - if (pick(options.crop, true)) { - dataLabelWidth = dataLabel.getBBox().width; - sideOverflow = null; - // Overflow left - if (x - dataLabelWidth < connectorPadding && - i === 1 // left half - ) { - sideOverflow = Math.round(dataLabelWidth - x + connectorPadding); - overflow[3] = Math.max(sideOverflow, overflow[3]); - // Overflow right - } - else if (x + dataLabelWidth > plotWidth - connectorPadding && - i === 0 // right half - ) { - 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(); - this.points.forEach(function (point) { - // #8864: every connector can have individual options - pointDataLabelsOptions = - merge(options, point.options.dataLabels); - connectorWidth = - pick(pointDataLabelsOptions.connectorWidth, 1); - // Draw the connector - if (connectorWidth) { - 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 + - (point.className ? - ' ' + point.className : - '')) - .add(series.dataLabelsGroup); - if (!chart.styledMode) { - connector.attr({ - 'stroke-width': connectorWidth, - 'stroke': (pointDataLabelsOptions.connectorColor || - point.color || - '#666666') - }); - } - } - connector[isNew ? 'attr' : 'animate']({ - d: point.getConnectorPath() - }); - 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. - * - * @private - * @function Highcharts.seriesTypes.pie#connectorPath - * - * @param {*} labelPos - * - * @return {Highcharts.SVGPathArray} - */ - // TODO: depracated - remove it - /* - 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. - * - * @private - * @function Highcharts.seriesTypes.pie#placeDataLabels - * @return {void} - */ - seriesTypes.pie.prototype.placeDataLabels = function () { - this.points.forEach(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 = - Math.max(dataLabel.getBBox().width - - dataLabel.sideOverflow, 0); - dataLabel.css({ - width: dataLabel._attr.width + 'px', - textOverflow: ((this.options.dataLabels.style || {}) - .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 }); - } - } - // Clear for update - delete point.distributeBox; - }, 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. - * - * @private - * @function Highcharts.seriesTypes.pie#verifyDataLabelOverflow - * @param {Array<number>} overflow - * @return {boolean} - */ - 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 = clamp(newSize, minSize, center[2] - Math.max(overflow[0], overflow[2])); - } - else { // Auto center - newSize = clamp(newSize, minSize, - // vertical overflow - center[2] - overflow[0] - overflow[2]); - // 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. - * - * @private - * @function Highcharts.seriesTypes.column#alignDataLabel - * @param {Highcharts.Point} point - * @param {Highcharts.SVGElement} dataLabel - * @param {Highcharts.DataLabelsOptions} options - * @param {Highcharts.BBoxObject} alignTo - * @param {boolean} [isNew] - * @return {void} - */ - 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; - } - // If parts of the box overshoots outside the plot area, modify the - // box to center the label inside - overshoot = alignTo.y + alignTo.height - series.yAxis.len; - if (overshoot > 0 && overshoot < alignTo.height) { - 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 - CartesianSeries.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); - // If label was justified and we have contrast, set it: - if (options.inside && point.contrastColor) { - dataLabel.css({ - color: point.contrastColor - }); - } - }; - } - - }); - _registerModule(_modules, 'Extensions/OverlappingDataLabels.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Utilities.js']], function (Chart, U) { - /* * + /** + * @private + * @function Highcharts.StackItem#getStackBox + * + * @param {Highcharts.Chart} chart * - * Highcharts module to hide overlapping data labels. - * This module is included in Highcharts. + * @param {Highcharts.StackItem} stackItem * - * (c) 2009-2020 Torstein Honsi + * @param {number} x * - * License: www.highcharts.com/license + * @param {number} y * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * @param {number} xWidth * - * */ - var addEvent = U.addEvent, - fireEvent = U.fireEvent, - isArray = U.isArray, - isNumber = U.isNumber, - objectEach = U.objectEach, - pick = U.pick; - /** - * Internal type - * @private - */ - /* eslint-disable no-invalid-this */ - // 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, 'render', function collectAndHide() { - var labels = []; - // Consider external label collectors - (this.labelCollectors || []).forEach(function (collector) { - labels = labels.concat(collector()); - }); - (this.yAxis || []).forEach(function (yAxis) { - if (yAxis.stacking && - yAxis.options.stackLabels && - !yAxis.options.stackLabels.allowOverlap) { - objectEach(yAxis.stacking.stacks, function (stack) { - objectEach(stack, function (stackItem) { - labels.push(stackItem.label); - }); - }); + * @param {number} h + * + * @param {Highcharts.Axis} axis + * + * @return {Highcharts.BBoxObject} + */ + StackItem.prototype.getStackBox = function ( + chart, + stackItem, + x, + y, + xWidth, + h, + axis + ) { + var reversed = stackItem.axis.reversed, + inverted = chart.inverted, + axisPos = + axis.height + + axis.pos - + (inverted ? chart.plotLeft : chart.plotTop), + neg = + (stackItem.isNegative && !reversed) || + (!stackItem.isNegative && reversed); // #4056 + return { + x: inverted + ? neg + ? y - axis.right + : y - h + axis.pos - chart.plotLeft + : x + chart.xAxis[0].transB - chart.plotLeft, + y: inverted + ? axis.height - x - xWidth + : neg + ? axisPos - y - h + : axisPos - y, + width: inverted ? h : xWidth, + height: inverted ? xWidth : h, + }; + }; + return StackItem; + })(); + /** + * Generate stacks for each series and calculate stacks total values + * + * @private + * @function Highcharts.Chart#getStacks + */ + Chart.prototype.getStacks = function () { + var chart = this, + inverted = chart.inverted; + // reset stacks for each yAxis + chart.yAxis.forEach(function (axis) { + if (axis.stacking && axis.stacking.stacks && axis.hasVisibleSeries) { + axis.stacking.oldStacks = axis.stacking.stacks; + } + }); + chart.series.forEach(function (series) { + var xAxisOptions = (series.xAxis && series.xAxis.options) || {}; + if ( + series.options.stacking && + (series.visible === true || + chart.options.chart.ignoreHiddenSeries === false) + ) { + series.stackKey = [ + series.type, + pick(series.options.stack, ""), + inverted ? xAxisOptions.top : xAxisOptions.left, + inverted ? xAxisOptions.height : xAxisOptions.width, + ].join(","); + } + }); + }; + // Stacking methods defined on the Axis prototype + StackingAxis.compose(Axis); + // Stacking methods defined for Series prototype + /** + * Set grouped points in a stack-like object. When `centerInCategory` is true, + * and `stacking` is not enabled, we need a pseudo (horizontal) stack in order + * to handle grouping of points within the same category. + * + * @private + * @function Highcharts.Series#setStackedPoints + * @return {void} + */ + Series.prototype.setGroupedPoints = function () { + if ( + this.options.centerInCategory && + (this.is("column") || this.is("columnrange")) && + // With stacking enabled, we already have stacks that we can compute + // from + !this.options.stacking && + // With only one series, we don't need to consider centerInCategory + this.chart.series.length > 1 + ) { + Series.prototype.setStackedPoints.call(this, "group"); + } + }; + /** + * Adds series' points value to corresponding stack + * + * @private + * @function Highcharts.Series#setStackedPoints + */ + Series.prototype.setStackedPoints = function (stackingParam) { + var stacking = stackingParam || this.options.stacking; + if ( + !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, + stackKey = stackingParam + ? series.type + "," + stacking + : series.stackKey, + negKey = "-" + stackKey, + negStacks = series.negStacks, + yAxis = series.yAxis, + stacks = yAxis.stacking.stacks, + oldStacks = yAxis.stacking.oldStacks, + stackIndicator, + isNegative, + stack, + other, + key, + pointKey, + i, + x, + y; + yAxis.stacking.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 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.stacking.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 if (stacking === "group") { + // In this stack, the total is the number of valid points + if (y !== null) { + stack.total = (stack.total || 0) + 1; + } + } else { + stack.total = correctFloat(stack.total + (y || 0)); + } + if (stacking === "group") { + // This point's index within the stack, pushed to stack.points[1] + stack.cumulative = (stack.total || 1) - 1; + } else { + stack.cumulative = + pick(stack.cumulative, stackThreshold) + (y || 0); + } + if (y !== null) { + stack.points[pointKey].push(stack.cumulative); + stackedYData[i] = stack.cumulative; + stack.hasValidPoints = true; + } + } + if (stacking === "percent") { + yAxis.stacking.usePercentage = true; + } + if (stacking !== "group") { + this.stackedYData = stackedYData; // To be used in getExtremes + } + // Reset old stacks + yAxis.stacking.oldStacks = {}; + }; + /** + * Iterate over all stacks and compute the absolute values to percent + * + * @private + * @function Highcharts.Series#modifyStacks + */ + Series.prototype.modifyStacks = function () { + var series = this, + yAxis = series.yAxis, + stackKey = series.stackKey, + stacks = yAxis.stacking.stacks, + processedXData = series.processedXData, + stackIndicator, + stacking = series.options.stacking; + if (series[stacking + "Stacker"]) { + // Modifier function exists + [stackKey, "-" + stackKey].forEach(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%. + * + * @private + * @function Highcharts.Series#percentStacker + * @param {Array<number>} pointExtremes + * @param {Highcharts.StackItem} stack + * @param {number} i + */ + 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 + * + * @private + * @function Highcharts.Series#getStackIndicator + * @param {Highcharts.StackItemIndicatorObject|undefined} stackIndicator + * @param {number} x + * @param {number} index + * @param {string} [key] + * @return {Highcharts.StackItemIndicatorObject} + */ + 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; + }; + H.StackItem = StackItem; + + return H.StackItem; + } + ); + _registerModule( + _modules, + "Core/Dynamics.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Axis/Axis.js"], + _modules["Core/Series/Series.js"], + _modules["Core/Chart/Chart.js"], + _modules["Core/Globals.js"], + _modules["Series/LineSeries.js"], + _modules["Core/Options.js"], + _modules["Core/Series/Point.js"], + _modules["Core/Time.js"], + _modules["Core/Utilities.js"], + ], + function (A, Axis, BaseSeries, Chart, H, LineSeries, O, Point, Time, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var animate = A.animate, + setAnimation = A.setAnimation; + var seriesTypes = BaseSeries.seriesTypes; + var time = O.time; + var addEvent = U.addEvent, + createElement = U.createElement, + css = U.css, + defined = U.defined, + erase = U.erase, + error = U.error, + extend = U.extend, + fireEvent = U.fireEvent, + isArray = U.isArray, + isNumber = U.isNumber, + isObject = U.isObject, + isString = U.isString, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick, + relativeLength = U.relativeLength, + splat = U.splat; + /* eslint-disable valid-jsdoc */ + /** + * Remove settings that have not changed, to avoid unnecessary rendering or + * computing (#9197). + * @private + */ + H.cleanRecursively = function (newer, older) { + var result = {}; + objectEach(newer, function (val, key) { + var ob; + // Dive into objects (except DOM nodes) + if ( + isObject(newer[key], true) && + !newer.nodeType && // #10044 + older[key] + ) { + ob = H.cleanRecursively(newer[key], older[key]); + if (Object.keys(ob).length) { + result[key] = ob; + } + // Arrays, primitives and DOM nodes are copied directly + } else if (isObject(newer[key]) || newer[key] !== older[key]) { + result[key] = newer[key]; + } + }); + return result; + }; + // 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. + * + * @sample highcharts/members/chart-addseries/ + * Add a series from a button + * @sample stock/members/chart-addseries/ + * Add a series in Highstock + * + * @function Highcharts.Chart#addSeries + * + * @param {Highcharts.SeriesOptionsType} options + * The config options for the series. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart after adding. + * + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] + * Whether to apply animation, and optionally animation + * configuration. + * + * @return {Highcharts.Series} + * The newly created series object. + * + * @fires Highcharts.Chart#event:addSeries + * @fires Highcharts.Chart#event:afterAddSeries + */ + addSeries: function (options, redraw, animation) { + var series, + chart = this; + if (options) { + // <- not necessary + redraw = pick(redraw, true); // defaults to true + fireEvent(chart, "addSeries", { options: options }, function () { + series = chart.initSeries(options); + chart.isDirtyLegend = true; + chart.linkSeries(); + if (series.enabledDataSorting) { + // We need to call `setData` after `linkSeries` + series.setData(options.data, false); } - }); - (this.series || []).forEach(function (series) { - var dlOptions = series.options.dataLabels; - if (series.visible && - !(dlOptions.enabled === false && !series._hasPointLabels)) { // #3866 - (series.nodes || series.points).forEach(function (point) { - if (point.visible) { - var dataLabels = (isArray(point.dataLabels) ? - point.dataLabels : - (point.dataLabel ? [point.dataLabel] : [])); - dataLabels.forEach(function (label) { - var options = label.options; - label.labelrank = pick(options.labelrank, point.labelrank, point.shapeArgs && point.shapeArgs.height); // #4118 - if (!options.allowOverlap) { - labels.push(label); - } - }); - } - }); + fireEvent(chart, "afterAddSeries", { series: series }); + 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. + * + * @sample highcharts/members/chart-addaxis/ + * Add and remove axes + * + * @function Highcharts.Chart#addAxis + * + * @param {Highcharts.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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] + * Whether and how to apply animation in the redraw. + * + * @return {Highcharts.Axis} + * The newly generated Axis object. + */ + addAxis: function (options, isX, redraw, animation) { + return this.createAxis(isX ? "xAxis" : "yAxis", { + axis: options, + redraw: redraw, + animation: animation, }); - this.hideOverlappingLabels(labels); - }); - /** - * Hide overlapping labels. Labels are moved and faded in and out on zoom to - * provide a smooth visual imression. - * - * @private - * @function Highcharts.Chart#hideOverlappingLabels - * @param {Array<Highcharts.SVGElement>} labels - * Rendered data labels - * @requires modules/overlapping-datalabels - */ - Chart.prototype.hideOverlappingLabels = function (labels) { + }, + /** + * Add a color 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. + * + * @sample highcharts/members/chart-addaxis/ + * Add and remove axes + * + * @function Highcharts.Chart#addColorAxis + * + * @param {Highcharts.ColorAxisOptions} options + * The axis options. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart after adding. + * + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] + * Whether and how to apply animation in the redraw. + * + * @return {Highcharts.ColorAxis} + * The newly generated Axis object. + */ + addColorAxis: function (options, redraw, animation) { + return this.createAxis("colorAxis", { + axis: options, + redraw: redraw, + animation: animation, + }); + }, + /** + * Factory for creating different axis types. + * + * @private + * @function Highcharts.Chart#createAxis + * + * @param {string} type + * An axis type. + * + * @param {...Array<*>} arguments + * All arguments for the constructor. + * + * @return {Highcharts.Axis | Highcharts.ColorAxis} + * The newly generated Axis object. + */ + createAxis: function (type, options) { + var chartOptions = this.options, + isColorAxis = type === "colorAxis", + axisOptions = options.axis, + redraw = options.redraw, + animation = options.animation, + userOptions = merge(axisOptions, { + index: this[type].length, + isX: type === "xAxis", + }), + axis; + if (isColorAxis) { + axis = new H.ColorAxis(this, userOptions); + } else { + axis = new Axis(this, userOptions); + } + // Push the new axis options to the chart options + chartOptions[type] = splat(chartOptions[type] || {}); + chartOptions[type].push(userOptions); + if (isColorAxis) { + this.isDirtyLegend = true; + // Clear before 'bindAxes' (#11924) + this.axes.forEach(function (axis) { + axis.series = []; + }); + this.series.forEach(function (series) { + series.bindAxes(); + series.isDirtyData = true; + }); + } + 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}. + * + * @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 + * + * @function Highcharts.Chart#showLoading + * + * @param {string} [str] + * An optional text to show in the loading label instead of the + * default one. The default text is set in + * [lang.loading](https://api.highcharts.com/highcharts/lang.loading). + */ + showLoading: function (str) { var chart = this, - len = labels.length, - ren = chart.renderer, - label, - i, - j, - label1, - label2, - box1, - box2, - isLabelAffected = false, - isIntersectRect = function (box1, - box2) { - return !(box2.x >= box1.x + box1.width || - box2.x + box2.width <= box1.x || - box2.y >= box1.y + box1.height || - box2.y + box2.height <= box1.y); - }, - // Get the box with its position inside the chart, as opposed to getBBox - // that only reports the position relative to the parent. - getAbsoluteBox = function (label) { - var pos, - parent, - bBox, - // Substract the padding if no background or border (#4333) - padding = label.box ? 0 : (label.padding || 0), - lineHeightCorrection = 0, - xOffset = 0, - boxWidth, - alignValue; - if (label && - (!label.alignAttr || label.placed)) { - pos = label.alignAttr || { - x: label.attr('x'), - y: label.attr('y') - }; - parent = label.parentGroup; - // Get width and height if pure text nodes (stack labels) - if (!label.width) { - bBox = label.getBBox(); - label.width = bBox.width; - label.height = bBox.height; - // Labels positions are computed from top left corner, so - // we need to substract the text height from text nodes too. - lineHeightCorrection = ren - .fontMetrics(null, label.element).h; - } - boxWidth = label.width - 2 * padding; - alignValue = { - left: '0', - center: '0.5', - right: '1' - }[label.alignValue]; - if (alignValue) { - xOffset = +alignValue * boxWidth; - } - else if (isNumber(label.x) && Math.round(label.x) !== label.translateX) { - xOffset = label.x - label.translateX; - } - return { - x: pos.x + (parent.translateX || 0) + padding - - (xOffset || 0), - y: pos.y + (parent.translateY || 0) + padding - - lineHeightCorrection, - width: label.width - 2 * padding, - height: label.height - 2 * padding - }; + 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 = pick(str, options.lang.loading, ""); + if (!chart.styledMode) { + // 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 + * + * @function Highcharts.Chart#hideLoading + */ + hideLoading: function () { + var options = this.options, + loadingDiv = this.loadingDiv; + if (loadingDiv) { + loadingDiv.className = + "highcharts-loading highcharts-loading-hidden"; + if (!this.styledMode) { + 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", + "borderRadius", + "plotBackgroundColor", + "plotBackgroundImage", + "plotBorderColor", + "plotBorderWidth", + "plotShadow", + "shadow", + ], + /** + * These properties require a full reflow of chart elements, best + * implemented through running `Chart.setSize` internally (#8190). + * @type {Array} + */ + propsRequireReflow: [ + "margin", + "marginTop", + "marginRight", + "marginBottom", + "marginLeft", + "spacing", + "spacingTop", + "spacingRight", + "spacingBottom", + "spacingLeft", + ], + /** + * 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", + ], + /** + * These collections (arrays) implement update() methods with support for + * one-to-one option. + */ + collectionsWithUpdate: ["xAxis", "yAxis", "zAxis", "series"], + /** + * 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 + * [xAxis](https://api.highcharts.com/highcharts/xAxis), + * [yAxis](https://api.highcharts.com/highcharts/yAxis) or + * [series](https://api.highcharts.com/highcharts/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. + * + * Note that when changing series data, `chart.update` may mutate the passed + * data options. + * + * See also the + * [responsive option set](https://api.highcharts.com/highcharts/responsive). + * Switching between `responsive.rules` basically runs `chart.update` under + * the hood. + * + * @sample highcharts/members/chart-update/ + * Update chart geometry + * + * @function Highcharts.Chart#update + * + * @param {Highcharts.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`, `yAxis` and `annotations` + * 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. + * + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] + * Whether to apply animation, and optionally animation + * configuration. + * + * @fires Highcharts.Chart#event:update + * @fires Highcharts.Chart#event:afterUpdate + */ + update: function (options, redraw, oneToOne, animation) { + var chart = this, + adders = { + credits: "addCredits", + title: "setTitle", + subtitle: "setSubtitle", + caption: "setCaption", + }, + optionsChart, + updateAllAxes, + updateAllSeries, + newWidth, + newHeight, + runSetSize, + isResponsiveOptions = options.isResponsiveOptions, + itemsForRemoval = []; + fireEvent(chart, "update", { options: options }); + // If there are responsive rules in action, undo the responsive rules + // before we apply the updated options and replay the responsive rules + // on top from the chart.redraw function (#9617). + if (!isResponsiveOptions) { + chart.setResponsive(false, true); + } + options = H.cleanRecursively(options, chart.options); + merge(true, chart.userOptions, options); + // If the top-level chart option is present, some special updates are + // required + optionsChart = options.chart; + if (optionsChart) { + merge(true, chart.options.chart, optionsChart); + // Setter function + if ("className" in optionsChart) { + chart.setClassName(optionsChart.className); + } + if ("reflow" in optionsChart) { + chart.setReflow(optionsChart.reflow); + } + if ( + "inverted" in optionsChart || + "polar" in optionsChart || + "type" 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 ( + chart.propsRequireUpdateSeries.indexOf("chart." + key) !== -1 + ) { + updateAllSeries = true; } - }; - for (i = 0; i < len; i++) { - label = labels[i]; - if (label) { - // Mark with initial opacity - label.oldOpacity = label.opacity; - label.newOpacity = 1; - label.absoluteBox = getAbsoluteBox(label); + // Only dirty box + if (chart.propsRequireDirtyBox.indexOf(key) !== -1) { + chart.isDirtyBox = true; } + // Chart setSize + if (chart.propsRequireReflow.indexOf(key) !== -1) { + if (isResponsiveOptions) { + chart.isDirtyBox = true; + } else { + runSetSize = true; + } + } + }); + if (!chart.styledMode && "style" in optionsChart) { + chart.renderer.setStyle(optionsChart.style); + } + } + // Moved up, because tooltip needs updated plotOptions (#6218) + if (!chart.styledMode && options.colors) { + this.options.colors = options.colors; } - // 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); + if (options.time) { + // Maintaining legacy global time. If the chart is instanciated + // first with global time, then updated with time options, we need + // to create a new Time instance to avoid mutating the global time + // (#10536). + if (this.time === time) { + this.time = new Time(options.time); + } + // If we're updating, the time class is different from other chart + // classes (chart.legend, chart.tooltip etc) in that it doesn't know + // about the chart. The other chart[something].update functions also + // set the chart.options[something]. For the time class however we + // need to update the chart options separately. #14230. + merge(true, chart.options.time, options.time); + } + // 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); + // Else, just merge the options. For nodes like loading, noData, + // plotOptions + } else if ( + key !== "color" && + chart.collectionsWithUpdate.indexOf(key) === -1 + ) { + merge(true, chart.options[key], options[key]); + } + if ( + key !== "chart" && + chart.propsRequireUpdateSeries.indexOf(key) !== -1 + ) { + updateAllSeries = true; + } }); - // Detect overlapping labels - for (i = 0; i < len; i++) { - label1 = labels[i]; - box1 = label1 && label1.absoluteBox; - for (j = i + 1; j < len; ++j) { - label2 = labels[j]; - box2 = label2 && label2.absoluteBox; - if (box1 && - box2 && - label1 !== label2 && // #6465, polar chart with connectEnds - label1.newOpacity !== 0 && - label2.newOpacity !== 0) { - if (isIntersectRect(box1, box2)) { - (label1.labelrank < label2.labelrank ? label1 : label2) - .newOpacity = 0; - } + // 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. + this.collectionsWithUpdate.forEach(function (coll) { + var indexMap; + if (options[coll]) { + // In stock charts, the navigator series are also part of the + // chart.series array, but those series should not be handled + // here (#8196). + if (coll === "series") { + indexMap = []; + chart[coll].forEach(function (s, i) { + if (!s.options.isInternal) { + indexMap.push(pick(s.options.index, i)); + } + }); + } + splat(options[coll]).forEach(function (newOptions, i) { + var hasId = defined(newOptions.id); + var item; + // Match by id + if (hasId) { + item = chart.get(newOptions.id); + } + // No match by id found, match by index instead + if (!item) { + item = chart[coll][indexMap ? indexMap[i] : i]; + // Check if we grabbed an item with an exising but + // different id (#13541) + if (item && hasId && defined(item.options.id)) { + item = void 0; + } + } + 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 && chart.collectionsWithInit[coll]) { + chart.collectionsWithInit[coll][0].apply( + chart, + // [newOptions, ...extraArguments, redraw=false] + [newOptions] + .concat( + // Not all initializers require extra args + chart.collectionsWithInit[coll][1] || [] + ) + .concat([false]) + ).touched = true; + } + }); + // Add items for removal + if (oneToOne) { + chart[coll].forEach(function (item) { + if (!item.touched && !item.options.isInternal) { + itemsForRemoval.push(item); + } else { + delete item.touched; } + }); } + } + }); + itemsForRemoval.forEach(function (item) { + if (item.remove) { + item.remove(false); + } + }); + if (updateAllAxes) { + chart.axes.forEach(function (axis) { + axis.update({}, false); + }); } - // Hide or show - labels.forEach(function (label) { - var complete, - newOpacity; - if (label) { - newOpacity = label.newOpacity; - if (label.oldOpacity !== newOpacity) { - // Make sure the label is completely hidden to avoid catching - // clicks (#4362) - if (label.alignAttr && label.placed) { // data labels - label[newOpacity ? 'removeClass' : 'addClass']('highcharts-data-label-hidden'); - complete = function () { - if (!chart.styledMode) { - label.css({ pointerEvents: newOpacity ? 'auto' : 'none' }); - } - label.visibility = newOpacity ? 'inherit' : 'hidden'; - }; - isLabelAffected = true; - // Animate or set the opacity - label.alignAttr.opacity = newOpacity; - label[label.isOld ? 'animate' : 'attr'](label.alignAttr, null, complete); - fireEvent(chart, 'afterHideOverlappingLabel'); - } - else { // other labels, tick labels - label.attr({ - opacity: newOpacity - }); - } - } - label.isOld = true; + // Certain options require the whole series structure to be thrown away + // and rebuilt + if (updateAllSeries) { + chart.getSeriesOrderByLinks().forEach(function (series) { + // Avoid removed navigator series + if (series.chart) { + series.update({}, false); + } + }, this); + } + // Update size. Redraw is forced. + newWidth = optionsChart && optionsChart.width; + newHeight = optionsChart && optionsChart.height; + if (isString(newHeight)) { + newHeight = relativeLength( + newHeight, + newWidth || chart.chartWidth + ); + } + if ( + // In this case, run chart.setSize with newWidth and newHeight which + // are undefined, only for reflowing chart elements because margin + // or spacing has been set (#8190) + runSetSize || + // In this case, the size is actually set + (isNumber(newWidth) && newWidth !== chart.chartWidth) || + (isNumber(newHeight) && newHeight !== chart.chartHeight) + ) { + chart.setSize(newWidth, newHeight, animation); + } else if (pick(redraw, true)) { + chart.redraw(animation); + } + fireEvent(chart, "afterUpdate", { + options: options, + redraw: redraw, + animation: animation, + }); + }, + /** + * Shortcut to set the subtitle options. This can also be done from {@link + * Chart#update} or {@link Chart#setTitle}. + * + * @function Highcharts.Chart#setSubtitle + * + * @param {Highcharts.SubtitleOptions} options + * New subtitle options. The subtitle text itself is set by the + * `options.text` property. + */ + setSubtitle: function (options, redraw) { + this.applyDescription("subtitle", options); + this.layOutTitles(redraw); + }, + /** + * Set the caption options. This can also be done from {@link + * Chart#update}. + * + * @function Highcharts.Chart#setCaption + * + * @param {Highcharts.CaptionOptions} options + * New caption options. The caption text itself is set by the + * `options.text` property. + */ + setCaption: function (options, redraw) { + this.applyDescription("caption", options); + this.layOutTitles(redraw); + }, + } + ); + /** + * These collections (arrays) implement `Chart.addSomethig` method used in + * chart.update() to create new object in the collection. Equivalent for + * deleting is resolved by simple `Somethig.remove()`. + * + * Note: We need to define these references after initializers are bound to + * chart's prototype. + */ + Chart.prototype.collectionsWithInit = { + // collectionName: [ initializingMethod, [extraArguments] ] + xAxis: [Chart.prototype.addAxis, [true]], + yAxis: [Chart.prototype.addAxis, [false]], + series: [Chart.prototype.addSeries], + }; + // 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. + * + * @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 + * + * @function Highcharts.Point#update + * + * @param {Highcharts.PointOptionsType} 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 marin 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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] + * Whether to apply animation, and optionally animation + * configuration. + * + * @return {void} + * + * @fires Highcharts.Point#event:update + */ + 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); + /** + * @private + */ + function update() { + point.applyOptions(options); + // Update visuals, #4146 + // Handle dummy graphic elements for a11y, #12718 + var hasDummyGraphic = graphic && point.hasDummyGraphic; + var shouldDestroyGraphic = + point.y === null ? !hasDummyGraphic : hasDummyGraphic; + if (graphic && shouldDestroyGraphic) { + point.graphic = graphic.destroy(); + delete point.hasDummyGraphic; + } + 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 && + typeof options.marker.symbol !== "undefined" + ) { + point.graphic = graphic.destroy(); + } + } + if (options && options.dataLabels && point.dataLabel) { + point.dataLabel = point.dataLabel.destroy(); // #2468 + } + 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 + : pick(options, seriesOptions.data[i]); + // 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 + * + * @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 + * + * @function Highcharts.Point#remove + * + * @param {boolean} [redraw=true] + * 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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=false] + * Whether to apply animation, and optionally animation + * configuration. + * + * @return {void} + */ + remove: function (redraw, animation) { + this.series.removePoint( + this.series.data.indexOf(this), + redraw, + animation + ); + }, + } + ); + // Extend the series prototype for dynamic methods + extend( + LineSeries.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. + * + * @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 + * + * @function Highcharts.Series#addPoint + * + * @param {Highcharts.PointOptionsType} 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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] + * Whether to apply animation, and optionally animation + * configuration. + * + * @param {boolean} [withEvent=true] + * Used internally, whether to fire the series `addPoint` event. + * + * @return {void} + * + * @fires Highcharts.Series#event:addPoint + */ + addPoint: function (options, redraw, shift, animation, withEvent) { + 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, + xData = series.xData, + isInTheMiddle, + 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--; + } + } + // Insert undefined item + series.updateParallelArrays(point, "splice", i, 0, 0); + // Update it + series.updateParallelArrays(point, i); + 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(); + } + } + // Fire event + if (withEvent !== false) { + fireEvent(series, "addPoint", { point: point }); + } + // 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. + * + * @sample highcharts/members/series-removepoint/ + * Remove cropped point + * + * @function Highcharts.Series#removePoint + * + * @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 {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] + * Whether and optionally how the series should be animated. + * + * @return {void} + * + * @fires Highcharts.Point#event:remove + */ + 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. + * + * @sample highcharts/members/series-remove/ + * Remove first series from a button + * + * @function Highcharts.Series#remove + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart or wait for an explicit call to + * {@link Highcharts.Chart#redraw}. + * + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] + * Whether to apply animation, and optionally animation + * configuration. + * + * @param {boolean} [withEvent=true] + * Used internally, whether to fire the series `remove` event. + * + * @return {void} + * + * @fires Highcharts.Series#event:remove + */ + remove: function (redraw, animation, withEvent, keepEvents) { + var series = this, + chart = series.chart; + /** + * @private + */ + function remove() { + // Destroy elements + series.destroy(keepEvents); + series.remove = null; // Prevent from doing again (#9097) + // 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 initialized from scratch. Therefore, this method is + * more performance expensive than some other utility methods like {@link + * Series#setData} or {@link Series#setVisible}. + * + * Note that `Series.update` may mutate the passed `data` options. + * + * @sample highcharts/members/series-update/ + * Updating series options + * @sample maps/members/series-update/ + * Update series options in Highmaps + * + * @function Highcharts.Series#update + * + * @param {Highcharts.SeriesOptionsType} 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. + * + * @return {void} + * + * @fires Highcharts.Series#event:update + * @fires Highcharts.Series#event:afterUpdate + */ + update: function (options, redraw) { + options = H.cleanRecursively(options, this.userOptions); + fireEvent(this, "update", { options: options }); + 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, + seriesOptions, + initialType = series.initialType || series.type, + plotOptions = chart.options.plotOptions, + newType = + options.type || oldOptions.type || chart.options.chart.type, + keepPoints = !( + // Indicators, histograms etc recalculate the data. It should be + // possible to omit this. + ( + this.hasDerivedData || + // New type requires new point classes + (newType && newType !== this.type) || + // New options affecting how the data points are built + typeof options.pointStart !== "undefined" || + typeof options.pointInterval !== "undefined" || + // Changes to data grouping requires new points in new group + series.hasOptionChanged("dataGrouping") || + series.hasOptionChanged("pointStart") || + series.hasOptionChanged("pointInterval") || + series.hasOptionChanged("pointIntervalUnit") || + series.hasOptionChanged("keys") + ) + ), + initialSeriesProto = seriesTypes[initialType].prototype, + n, + groups = [ + "group", + "markerGroup", + "dataLabelsGroup", + "transformGroup", + ], + preserve = ["eventOptions", "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 }, + kinds = {}; + if (keepPoints) { + preserve.push( + "data", + "isDirtyData", + "points", + "processedXData", + "processedYData", + "xIncrement", + "cropped", + "_hasPointMarkers", + "_hasPointLabels", + // Map specific, consider moving it to series-specific preserve- + // properties (#10617) + "mapMap", + "mapData", + "minY", + "maxY", + "minX", + "maxX" + ); + if (options.visible !== false) { + preserve.push("area", "graph"); + } + series.parallelArrays.forEach(function (key) { + preserve.push(key + "Data"); + }); + if (options.data) { + // setData uses dataSorting options so we need to update them + // earlier + if (options.dataSorting) { + extend(series.options.dataSorting, options.dataSorting); + } + this.setData(options.data, false); + } + } + // Do the merge, with some forced options + options = merge( + oldOptions, + animation, + { + // When oldOptions.index is null it should't be cleared. + // Otherwise navigator series will have wrong indexes (#10193). + index: + typeof oldOptions.index === "undefined" + ? series.index + : oldOptions.index, + pointStart: pick( + // when updating from blank (#7933) + plotOptions && + plotOptions.series && + plotOptions.series.pointStart, + oldOptions.pointStart, + // when updating after addPoint + series.xData[0] + ), + }, + !keepPoints && { data: series.options.data }, + options + ); + // Merge does not merge arrays, but replaces them. Since points were + // updated, `series.options.data` has correct merged options, use it: + if (keepPoints && options.data) { + options.data = series.options.data; + } + // Make sure preserved properties are not destroyed (#3094) + preserve = groups.concat(preserve); + preserve.forEach(function (prop) { + preserve[prop] = series[prop]; + delete series[prop]; }); - if (isLabelAffected) { - fireEvent(chart, 'afterHideAllOverlappingLabels'); + // Destroy the series and delete all properties. Reinsert all + // methods and properties from the new type prototype (#2270, + // #3719). + series.remove(false, null, false, true); + for (n in initialSeriesProto) { + // eslint-disable-line guard-for-in + series[n] = void 0; } - }; - - }); - _registerModule(_modules, 'Core/Interaction.js', [_modules['Core/Series/Series.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Legend.js'], _modules['Series/LineSeries.js'], _modules['Core/Options.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js']], function (BaseSeries, Chart, H, Legend, LineSeries, O, Point, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! - * - * */ - var seriesTypes = BaseSeries.seriesTypes; - var hasTouch = H.hasTouch, - svg = H.svg; - var defaultOptions = O.defaultOptions; - var addEvent = U.addEvent, - createElement = U.createElement, - css = U.css, - defined = U.defined, - extend = U.extend, - fireEvent = U.fireEvent, - isArray = U.isArray, - isFunction = U.isFunction, - isNumber = U.isNumber, - isObject = U.isObject, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick; - /** - * @interface Highcharts.PointEventsOptionsObject - */ /** - * 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. - * @name Highcharts.PointEventsOptionsObject#select - * @type {Highcharts.PointSelectCallbackFunction|undefined} - */ /** - * 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. - * @name Highcharts.PointEventsOptionsObject#unselect - * @type {Highcharts.PointUnselectCallbackFunction|undefined} - */ - /** - * Information about the select/unselect event. - * - * @interface Highcharts.PointInteractionEventObject - * @extends global.Event - */ /** - * @name Highcharts.PointInteractionEventObject#accumulate - * @type {boolean} - */ + if (seriesTypes[newType || initialType]) { + extend(series, seriesTypes[newType || initialType].prototype); + } else { + error(17, true, chart, { + missingModuleFor: newType || initialType, + }); + } + // Re-register groups (#3094) and other preserved properties + preserve.forEach(function (prop) { + series[prop] = preserve[prop]; + }); + series.init(chart, options); + // Remove particular elements of the points. Check `series.options` + // because we need to consider the options being set on plotOptions as + // well. + if (keepPoints && this.points) { + seriesOptions = series.options; + // What kind of elements to destroy + if (seriesOptions.visible === false) { + kinds.graphic = 1; + kinds.dataLabel = 1; + } else if (!series._hasPointLabels) { + var marker = seriesOptions.marker, + dataLabels = seriesOptions.dataLabels; + if ( + marker && + (marker.enabled === false || "symbol" in marker) // #10870 + ) { + kinds.graphic = 1; + } + if (dataLabels && dataLabels.enabled === false) { + kinds.dataLabel = 1; + } + } + this.points.forEach(function (point) { + if (point && point.series) { + point.resolveColor(); + // Destroy elements in order to recreate based on updated + // series options. + if (Object.keys(kinds).length) { + point.destroyElements(kinds); + } + if ( + seriesOptions.showInLegend === false && + point.legendItem + ) { + chart.legend.destroyItem(point); + } + } + }, this); + } + series.initialType = initialType; + chart.linkSeries(); // Links are lost in series.remove (#3028) + fireEvent(this, "afterUpdate"); + if (pick(redraw, true)) { + chart.redraw(keepPoints ? void 0 : false); + } + }, + /** + * Used from within series.update + * + * @private + * @function Highcharts.Series#setName + * + * @param {string} name + * + * @return {void} + */ + setName: function (name) { + this.name = this.options.name = this.userOptions.name = name; + this.chart.isDirtyLegend = true; + }, + /** + * Check if the option has changed. + * + * @private + * @function Highcharts.Series#hasOptionChanged + * + * @param {string} option + * + * @return {boolean} + */ + hasOptionChanged: function (optionName) { + var chart = this.chart, + option = this.options[optionName], + plotOptions = chart.options.plotOptions, + oldOption = this.userOptions[optionName]; + if (oldOption) { + return option !== oldOption; + } + return ( + option !== + pick( + plotOptions && + plotOptions[this.type] && + plotOptions[this.type][optionName], + plotOptions && + plotOptions.series && + plotOptions.series[optionName], + option + ) + ); + }, + } + ); + // 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. + * + * @sample highcharts/members/axis-update/ + * Axis update demo + * + * @function Highcharts.Axis#update + * + * @param {Highcharts.AxisOptions} options + * The new options that will be merged in with existing options on + * the axis. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart after the axis 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. + * + * @return {void} + */ + update: function (options, redraw) { + var chart = this.chart, + newEvents = (options && options.events) || {}; + options = merge(this.userOptions, options); + // Color Axis is not an array, + // This change is applied in the ColorAxis wrapper + if (chart.options[this.coll].indexOf) { + // Don't use this.options.index, + // StockChart has Axes in navigator too + chart.options[this.coll][ + chart.options[this.coll].indexOf(this.userOptions) + ] = options; + } + // Remove old events, if no new exist (#8161) + objectEach(chart.options[this.coll].events, function (fn, ev) { + if (typeof newEvents[ev] === "undefined") { + newEvents[ev] = void 0; + } + }); + this.destroy(true); + this.init(chart, extend(options, { events: newEvents })); + chart.isDirtyBox = true; + if (pick(redraw, true)) { + chart.redraw(); + } + }, + /** + * Remove the axis from the chart. + * + * @sample highcharts/members/chart-addaxis/ + * Add and remove axes + * + * @function Highcharts.Axis#remove + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart following the remove. + * + * @return {void} + */ + 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]; + } + chart[key].forEach(function (axis, i) { + // Re-index, #1706, #8075 + axis.options.index = axis.userOptions.index = i; + }); + this.destroy(); + chart.isDirtyBox = true; + if (pick(redraw, true)) { + chart.redraw(); + } + }, + /** + * Update the axis title by options after render time. + * + * @sample highcharts/members/axis-settitle/ + * Set a new Y axis title + * + * @function Highcharts.Axis#setTitle + * + * @param {Highcharts.AxisTitleOptions} titleOptions + * The additional title options. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart after setting the title. + * + * @return {void} + */ + setTitle: function (titleOptions, redraw) { + this.update({ title: titleOptions }, redraw); + }, + /** + * Set new axis categories and optionally redraw. + * + * @sample highcharts/members/axis-setcategories/ + * Set categories by click on a button + * + * @function Highcharts.Axis#setCategories + * + * @param {Array<string>} categories + * The new categories. + * + * @param {boolean} [redraw=true] + * Whether to redraw the chart. + * + * @return {void} + */ + setCategories: function (categories, redraw) { + this.update({ categories: categories }, redraw); + }, + } + ); + } + ); + _registerModule( + _modules, + "Series/AreaSeries.js", + [ + _modules["Core/Series/Series.js"], + _modules["Core/Color/Color.js"], + _modules["Core/Globals.js"], + _modules["Mixins/LegendSymbol.js"], + _modules["Core/Utilities.js"], + ], + function (BaseSeries, Color, H, LegendSymbolMixin, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var color = Color.parse; + var objectEach = U.objectEach, + pick = U.pick; + var Series = H.Series; + /** + * Area series type. + * + * @private + * @class + * @name Highcharts.seriesTypes.area + * + * @augments Highcharts.Series + */ + BaseSeries.seriesType( + "area", + "line", /** - * Gets fired when the point is selected either programmatically or following a - * click on the point. - * - * @callback Highcharts.PointSelectCallbackFunction + * The area series type. * - * @param {Highcharts.Point} this - * Point where the event occured. + * @sample {highcharts} highcharts/demo/area-basic/ + * Area chart + * @sample {highstock} stock/demo/area/ + * Area chart * - * @param {Highcharts.PointInteractionEventObject} event - * Event that occured. + * @extends plotOptions.line + * @excluding useOhlcData + * @product highcharts highstock + * @optionparent plotOptions.area */ + { + /** + * @see [fillColor](#plotOptions.area.fillColor) + * @see [fillOpacity](#plotOptions.area.fillOpacity) + * + * @apioption plotOptions.area.color + */ + /** + * Fill color or gradient for the area. When `null`, the series' `color` + * is used with the series' `fillOpacity`. + * + * In styled mode, the fill color can be set with the `.highcharts-area` + * class name. + * + * @see [color](#plotOptions.area.color) + * @see [fillOpacity](#plotOptions.area.fillOpacity) + * + * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/ + * Null by default + * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/ + * Gradient + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @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. + * + * In styled mode, the fill opacity can be set with the + * `.highcharts-area` class name. + * + * @see [color](#plotOptions.area.color) + * @see [fillColor](#plotOptions.area.fillColor) + * + * @sample {highcharts} highcharts/plotoptions/area-fillopacity/ + * Automatic fill color and fill opacity of 0.1 + * + * @type {number} + * @default {highcharts} 0.75 + * @default {highstock} 0.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`. + * + * In styled mode, the line stroke can be set with the + * `.highcharts-graph` class name. + * + * @sample {highcharts} highcharts/plotoptions/area-linecolor/ + * Dark gray line + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @product highcharts highstock + * @apioption plotOptions.area.lineColor + */ + /** + * A separate color for the negative part of the area. + * + * In styled mode, a negative color is set with the + * `.highcharts-negative` class name. + * + * @see [negativeColor](#plotOptions.area.negativeColor) + * + * @sample {highcharts} highcharts/css/series-negative-color/ + * Negative color in styled mode + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @since 3.0 + * @product highcharts + * @apioption plotOptions.area.negativeFillColor + */ + /** + * Whether the whole area or just the line should respond to mouseover + * tooltips and other mouse or touch events. + * + * @sample {highcharts|highstock} highcharts/plotoptions/area-trackbyarea/ + * Display the tooltip when the area is hovered + * + * @type {boolean} + * @default false + * @since 1.1.6 + * @product highcharts highstock + * @apioption plotOptions.area.trackByArea + */ + /** + * The Y axis value to serve as the base for the area, for + * distinguishing between values above and below a threshold. The area + * between the graph and the threshold is filled. + * + * * If a number is given, the Y axis will scale to the threshold. + * * If `null`, the scaling behaves like a line series with fill between + * the graph and the Y axis minimum. + * * If `Infinity` or `-Infinity`, the area between the graph and the + * corresponding Y axis extreme is filled (since v6.1.0). + * + * @sample {highcharts} highcharts/plotoptions/area-threshold/ + * A threshold of 100 + * @sample {highcharts} highcharts/plotoptions/area-threshold-infinity/ + * A threshold of Infinity + * + * @type {number|null} + * @since 2.0 + * @product highcharts highstock + */ + threshold: 0, + }, + /* eslint-disable valid-jsdoc */ /** - * Fires when the point is unselected either programmatically or following a - * click on the point. - * - * @callback Highcharts.PointUnselectCallbackFunction + * @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. + * @private + */ + getStackPoints: function (points) { + var series = this, + segment = [], + keys = [], + xAxis = this.xAxis, + yAxis = this.yAxis, + stack = yAxis.stacking.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 = void 0; + // 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) + objectEach(stack, function (stackX, x) { + // nulled after switching between + // grouping and not (#1651, #2336) + if (stackX.total !== null) { + keys.push(x); + } + }); + keys.sort(function (a, b) { + return a - b; + }); + visibleSeries = yAxisSeries.map(function (s) { + return s.visible; + }); + keys.forEach(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. + [-1, 1].forEach(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; + // Can go either up or down, + // depending on reversedStacks + while (i >= 0 && i < seriesLength) { + 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( + // #6272 + y, + 0, + 1, + 0, + 1 + ); + segment.push({ + isNull: true, + plotX: xAxis.translate( + // #6272 + x, + 0, + 0, + 0, + 1 + ), + x: x, + plotY: y, + yBottom: y, + }); + } + }); + } + return segment; + }, + /** + * @private + */ + 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.stacking.stacks[this.stackKey], + threshold = options.threshold, + translatedThreshold = Math.round( + // #10909 + yAxis.getThreshold(options.threshold) + ), + isNull, + yBottom, + connectNulls = pick( + // #10574 + 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 (typeof 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++) { + // Reset after series.update of stacking property (#12033) + if (!stacking) { + points[i].leftCliff = + points[i].rightCliff = + points[i].leftNull = + points[i].rightNull = + void 0; + } + isNull = points[i].isNull; + plotX = pick(points[i].rectPlotX, points[i].plotX); + yBottom = stacking ? points[i].yBottom : translatedThreshold; + if (!isNull || connectNulls) { + if (!connectNulls) { + addDummyPoints(i, i - 1, "left"); + } + // Skip null point when stacking is false and connectNulls + // true + if (!(isNull && !stacking && connectNulls)) { + 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); + var firstBottomPoint = bottomPath[0]; + if (firstBottomPoint && firstBottomPoint[0] === "M") { + bottomPath[0] = ["L", firstBottomPoint[1], firstBottomPoint[2]]; + } + areaPath = topPath.concat(bottomPath); + // TODO: don't set leftCliff and rightCliff when connectNulls? + graphPath = getGraphPath.call( + this, + graphPoints, + false, + 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. + * @private + */ + 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 + zones.forEach(function (zone, i) { + props.push([ + "zone-area-" + i, + "highcharts-area highcharts-zone-area-" + + i + + " " + + zone.className, + zone.color || series.color, + zone.fillColor || options.fillColor, + ]); + }); + props.forEach(function (prop) { + var areaKey = prop[0], + area = series[areaKey], + verb = area ? "animate" : "attr", + attribs = {}; + // Create or update the area + if (area) { + // update + area.endX = series.preventGraphAnimation ? null : areaPath.xMap; + area.animate({ d: areaPath }); + } else { + // create + attribs.zIndex = 0; // #1069 + area = series[areaKey] = series.chart.renderer + .path(areaPath) + .addClass(prop[1]) + .add(series.group); + area.isArea = true; + } + if (!series.chart.styledMode) { + attribs.fill = pick( + prop[3], + color(prop[2]) + .setOpacity(pick(options.fillOpacity, 0.75)) + .get() + ); + } + area[verb](attribs); + area.startX = areaPath.xMap; + area.shiftUnit = options.step ? 2 : 1; + }); + }, + drawLegendSymbol: LegendSymbolMixin.drawRectangle, + } + ); + /* eslint-enable valid-jsdoc */ + /** + * A `area` series. If the [type](#series.area.type) option is not + * specified, it is inherited from [chart.type](#chart.type). + * + * @extends series,plotOptions.area + * @excluding dataParser, dataURL, useOhlcData + * @product highcharts highstock + * @apioption series.area + */ + /** + * @see [fillColor](#series.area.fillColor) + * @see [fillOpacity](#series.area.fillOpacity) + * + * @apioption series.area.color + */ + /** + * 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 following snippet shows only a + * few settings, see the complete options set 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" + * }] + * ``` + * + * @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 + * + * @type {Array<number|Array<(number|string),(number|null)>|null|*>} + * @extends series.line.data + * @product highcharts highstock + * @apioption series.area.data + */ + /** + * @see [color](#series.area.color) + * @see [fillOpacity](#series.area.fillOpacity) + * + * @apioption series.area.fillColor + */ + /** + * @see [color](#series.area.color) + * @see [fillColor](#series.area.fillColor) + * + * @default {highcharts} 0.75 + * @default {highstock} 0.75 + * @apioption series.area.fillOpacity + */ + (""); // adds doclets above to transpilat + } + ); + _registerModule( + _modules, + "Series/SplineSeries.js", + [_modules["Core/Series/Series.js"], _modules["Core/Utilities.js"]], + function (BaseSeries, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var pick = U.pick; + /** + * Spline series type. + * + * @private + * @class + * @name Highcharts.seriesTypes.spline + * + * @augments Highcarts.Series + */ + BaseSeries.seriesType( + "spline", + "line", + /** + * A spline series is a special type of line series, where the segments + * between the data points are smoothed. * - * @param {Highcharts.Point} this - * Point where the event occured. + * @sample {highcharts} highcharts/demo/spline-irregular-time/ + * Spline chart + * @sample {highstock} stock/demo/spline/ + * Spline chart * - * @param {Highcharts.PointInteractionEventObject} event - * Event that occured. + * @extends plotOptions.series + * @excluding step, boostThreshold, boostBlending + * @product highcharts highstock + * @optionparent plotOptions.spline */ - ''; // detach doclets above - /* eslint-disable valid-jsdoc */ + {}, /** - * TrackerMixin for points and graphs. - * - * @private - * @mixin Highcharts.TrackerMixin + * @lends seriesTypes.spline.prototype */ - var TrackerMixin = H.TrackerMixin = { - /** - * Draw the tracker for a point. - * - * @private - * @function Highcharts.TrackerMixin.drawTrackerPoint - * @param {Highcharts.Series} this - * @fires Highcharts.Series#event:afterDrawTracker - */ - 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 (typeof point !== 'undefined') { - pointer.isDirectTouch = true; - point.onMouseOver(e); - } - }, dataLabels; - // Add reference to the point - series.points.forEach(function (point) { - dataLabels = (isArray(point.dataLabels) ? - point.dataLabels : - (point.dataLabel ? [point.dataLabel] : [])); - if (point.graphic) { - point.graphic.element.point = point; - } - dataLabels.forEach(function (dataLabel) { - if (dataLabel.div) { - dataLabel.div.point = point; - } - else { - dataLabel.element.point = point; - } - }); - }); - // Add the event listeners, we need to do this only once - if (!series._hasTracking) { - series.trackerGroups.forEach(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 (!chart.styledMode && series.options.cursor) { - series[key] - .css(css) - .css({ cursor: series.options.cursor }); - } - } - }); - series._hasTracking = true; - } - fireEvent(this, 'afterDrawTracker'); - }, - /** - * 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. - * - * @private - * @function Highcharts.TrackerMixin.drawTrackerGraph - * @param {Highcharts.Series} this - * @fires Highcharts.Series#event:afterDrawTracker - */ - 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 (e) { - 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) + ')'; - // Draw the tracker - if (tracker) { - tracker.attr({ d: trackerPath }); - } - else if (series.graph) { // create - series.tracker = renderer.path(trackerPath) - .attr({ - visibility: series.visible ? 'visible' : 'hidden', - zIndex: 2 + { + /* eslint-disable valid-jsdoc */ + /** + * Get the spline segment from a given point's previous neighbour to the + * given point. + * + * @private + * @function Highcharts.seriesTypes.spline#getPointSpline + * + * @param {Array<Highcharts.Point>} + * + * @param {Highcharts.Point} point + * + * @param {number} i + * + * @return {Highcharts.SVGPathArray} + */ + 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 || 0, + plotY = point.plotY || 0, + lastPoint = points[i - 1], + nextPoint = points[i + 1], + leftContX, + leftContY, + rightContX, + rightContY, + ret; + /** + * @private + */ + function doCurve(otherPoint) { + return ( + otherPoint && + !otherPoint.isNull && + otherPoint.doCurve !== false && + // #6387, area splines next to null: + !point.isCliff + ); + } + // Find control points + if (doCurve(lastPoint) && doCurve(nextPoint)) { + var lastX = lastPoint.plotX || 0, + lastY = lastPoint.plotY || 0, + nextX = nextPoint.plotX || 0, + nextY = nextPoint.plotY || 0, + 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 }) - .addClass(trackByArea ? - 'highcharts-tracker-area' : - 'highcharts-tracker-line') - .add(series.group); - if (!chart.styledMode) { - series.tracker.attr({ - 'stroke-linecap': 'round', - 'stroke-linejoin': 'round', - stroke: TRACKER_FILL, - fill: trackByArea ? TRACKER_FILL : 'none', - 'stroke-width': series.graph.strokeWidth() + - (trackByArea ? 0 : 2 * snap) - }); - } - // 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. - [series.tracker, series.markerGroup].forEach(function (tracker) { - tracker.addClass('highcharts-tracker') - .on('mouseover', onMouseOver) - .on('mouseout', function (e) { - pointer.onTrackerMouseOut(e); - }); - if (options.cursor && !chart.styledMode) { - tracker.css({ cursor: options.cursor }); - } - if (hasTouch) { - tracker.on('touchstart', onMouseOver); - } - }); - } - fireEvent(this, 'afterDrawTracker'); + .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(); } - }; - /* End TrackerMixin */ - // Add tracking event listener to the series group, so the point graphics - // themselves act as trackers - if (seriesTypes.column) { - /** - * @private - * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.column#drawTracker - */ - seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - if (seriesTypes.pie) { - /** - * @private - * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.pie#drawTracker - */ - seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - if (seriesTypes.scatter) { - /** - * @private - * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.scatter#drawTracker - */ - seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint; - } - // Extend Legend for item events. - extend(Legend.prototype, { - /** - * @private - * @function Highcharts.Legend#setItemEvents - * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item - * @param {Highcharts.SVGElement} legendItem - * @param {boolean} [useHTML=false] - * @fires Highcharts.Point#event:legendItemClick - * @fires Highcharts.Series#event:legendItemClick - */ - setItemEvents: function (item, legendItem, useHTML) { - var legend = this, - boxWrapper = legend.chart.renderer.boxWrapper, - isPoint = item instanceof Point, - activeClass = 'highcharts-legend-' + - (isPoint ? 'point' : 'series') + '-active', - styledMode = legend.chart.styledMode, - // When `useHTML`, the symbol is rendered in other group, so - // we need to apply events listeners to both places - legendItems = useHTML ? - [legendItem, - item.legendSymbol] : - [item.legendGroup]; - // Set the events on the item group, or in case of useHTML, the item - // itself (#1249) - legendItems.forEach(function (element) { - if (element) { - element - .on('mouseover', function () { - if (item.visible) { - legend.allItems.forEach(function (inactiveItem) { - if (item !== inactiveItem) { - inactiveItem.setState('inactive', !isPoint); - } - }); - } - item.setState('hover'); - // A CSS class to dim or hide other than the hovered - // series. - // Works only if hovered series is visible (#10071). - if (item.visible) { - boxWrapper.addClass(activeClass); - } - if (!styledMode) { - legendItem.css(legend.options.itemHoverStyle); - } - }) - .on('mouseout', function () { - if (!legend.chart.styledMode) { - legendItem.css(merge(item.visible ? - legend.itemStyle : - legend.itemHiddenStyle)); - } - legend.allItems.forEach(function (inactiveItem) { - if (item !== inactiveItem) { - inactiveItem.setState('', !isPoint); - } - }); - // 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(); - } - // Reset inactive state - legend.allItems.forEach(function (inactiveItem) { - if (item !== inactiveItem) { - inactiveItem.setState(item.visible ? 'inactive' : '', !isPoint); - } - }); - }; - // 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); - } - }); - } - }); - }, - /** - * @private - * @function Highcharts.Legend#createCheckboxForItem - * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item - * @fires Highcharts.Series#event:checkboxClick - */ - createCheckboxForItem: function (item) { - var legend = this; - item.checkbox = createElement('input', { - type: 'checkbox', - className: 'highcharts-legend-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', { - checked: target.checked, - item: item - }, function () { - item.select(); - }); - }); + 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(); } - }); - // Extend the Chart object with interaction - extend(Chart.prototype, /** @lends Chart.prototype */ { - /** - * Display the zoom button, so users can reset zoom to the default view - * settings. - * - * @function Highcharts.Chart#showResetZoom - * - * @fires Highcharts.Chart#event:afterShowResetZoom - * @fires Highcharts.Chart#event:beforeShowResetZoom + // */ + ret = [ + "C", + pick(lastPoint.rightContX, lastPoint.plotX, 0), + pick(lastPoint.rightContY, lastPoint.plotY, 0), + pick(leftContX, plotX, 0), + pick(leftContY, plotY, 0), + plotX, + plotY, + ]; + // reset for updating series later + lastPoint.rightContX = lastPoint.rightContY = void 0; + return ret; + }, + /* eslint-enable valid-jsdoc */ + } + ); + /** + * A `spline` series. If the [type](#series.spline.type) option is + * not specified, it is inherited from [chart.type](#chart.type). + * + * @extends series,plotOptions.spline + * @excluding dataParser, dataURL, step, boostThreshold, boostBlending + * @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 following snippet shows only a + * few settings, see the complete options set 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" + * }] + * ``` + * + * @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 + * + * @type {Array<number|Array<(number|string),(number|null)>|null|*>} + * @extends series.line.data + * @product highcharts highstock + * @apioption series.spline.data + */ + (""); // adds doclets above intro transpilat + } + ); + _registerModule( + _modules, + "Series/AreaSplineSeries.js", + [ + _modules["Core/Series/Series.js"], + _modules["Mixins/LegendSymbol.js"], + _modules["Core/Options.js"], + ], + function (BaseSeries, LegendSymbolMixin, O) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var defaultOptions = O.defaultOptions; + var areaProto = BaseSeries.seriesTypes.area.prototype; + /** + * AreaSpline series type. + * + * @private + * @class + * @name Highcharts.seriesTypes.areaspline + * + * @augments Highcharts.Series + */ + BaseSeries.seriesType( + "areaspline", + "spline", + /** + * The area spline series is an area series where the graph between the + * points is smoothed into a spline. + * + * @sample {highcharts} highcharts/demo/areaspline/ + * Area spline chart + * @sample {highstock} stock/demo/areaspline/ + * Area spline chart + * + * @extends plotOptions.area + * @excluding step, boostThreshold, boostBlending + * @product highcharts highstock + * @apioption plotOptions.areaspline + */ + /** + * @see [fillColor](#plotOptions.areaspline.fillColor) + * @see [fillOpacity](#plotOptions.areaspline.fillOpacity) + * + * @apioption plotOptions.areaspline.color + */ + /** + * @see [color](#plotOptions.areaspline.color) + * @see [fillOpacity](#plotOptions.areaspline.fillOpacity) + * + * @apioption plotOptions.areaspline.fillColor + */ + /** + * @see [color](#plotOptions.areaspline.color) + * @see [fillColor](#plotOptions.areaspline.fillColor) + * + * @default {highcharts} 0.75 + * @default {highstock} 0.75 + * @apioption plotOptions.areaspline.fillOpacity + */ + defaultOptions.plotOptions.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). + * + * + * @extends series,plotOptions.areaspline + * @excluding dataParser, dataURL, step, boostThreshold, boostBlending + * @product highcharts highstock + * @apioption series.areaspline + */ + /** + * @see [fillColor](#series.areaspline.fillColor) + * @see [fillOpacity](#series.areaspline.fillOpacity) + * + * @apioption series.areaspline.color + */ + /** + * 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 following snippet shows only a + * few settings, see the complete options set 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" + * }] + * ``` + * + * @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 + * + * @type {Array<number|Array<(number|string),(number|null)>|null|*>} + * @extends series.line.data + * @product highcharts highstock + * @apioption series.areaspline.data + */ + /** + * @see [color](#series.areaspline.color) + * @see [fillOpacity](#series.areaspline.fillOpacity) + * + * @apioption series.areaspline.fillColor + */ + /** + * @see [color](#series.areaspline.color) + * @see [fillColor](#series.areaspline.fillColor) + * + * @default {highcharts} 0.75 + * @default {highstock} 0.75 + * @apioption series.areaspline.fillOpacity + */ + (""); // adds doclets above into transpilat + } + ); + _registerModule( + _modules, + "Series/ColumnSeries.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Series/Series.js"], + _modules["Core/Color/Color.js"], + _modules["Core/Globals.js"], + _modules["Mixins/LegendSymbol.js"], + _modules["Series/LineSeries.js"], + _modules["Core/Utilities.js"], + ], + function (A, BaseSeries, Color, H, LegendSymbolMixin, LineSeries, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var animObject = A.animObject; + var color = Color.parse; + var noop = H.noop; + var clamp = U.clamp, + defined = U.defined, + extend = U.extend, + isArray = U.isArray, + isNumber = U.isNumber, + merge = U.merge, + pick = U.pick, + objectEach = U.objectEach; + /** + * Adjusted width and x offset of the columns for grouping. + * + * @private + * @interface Highcharts.ColumnMetricsObject + */ /** + * Width of the columns. + * @name Highcharts.ColumnMetricsObject#width + * @type {number} + */ /** + * Offset of the columns. + * @name Highcharts.ColumnMetricsObject#offset + * @type {number} + */ + (""); // detach doclets above + /** + * The column series type. + * + * @private + * @class + * @name Highcharts.seriesTypes.column + * + * @augments Highcharts.Series + */ + var ColumnSeries = BaseSeries.seriesType( + "column", + "line", + /** + * 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 + * @excluding connectEnds, connectNulls, gapSize, gapUnit, linecap, + * lineWidth, marker, step, useOhlcData + * @product highcharts highstock + * @optionparent plotOptions.column + */ + { + /** + * The corner radius of the border surrounding each column or bar. + * + * @sample {highcharts} highcharts/plotoptions/column-borderradius/ + * Rounded columns + * + * @product highcharts highstock gantt + * + * @private + */ + borderRadius: 0, + /** + * When using automatic point colors pulled from the global + * [colors](colors) or series-specific + * [plotOptions.column.colors](series.colors) collections, this option + * determines whether the chart should receive one color per series or + * one color per point. + * + * In styled mode, the `colors` or `series.colors` arrays are not + * supported, and instead this option gives the points individual color + * class names on the form `highcharts-color-{n}`. + * + * @see [series colors](#plotOptions.column.colors) + * + * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/ + * False by default + * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/ + * True + * + * @type {boolean} + * @default false + * @since 2.0 + * @product highcharts highstock gantt + * @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<Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject>} + * @since 3.0 + * @product highcharts highstock gantt + * @apioption plotOptions.column.colors + */ + /** + * When `true`, the columns will center in the category, ignoring null + * or missing points. When `false`, space will be reserved for null or + * missing points. + * + * @sample {highcharts} highcharts/series-column/centerincategory/ + * Center in category + * + * @since 8.0.1 + * @product highcharts highstock gantt + * + * @private + */ + centerInCategory: false, + /** + * Padding between each value groups, in x axis units. + * + * @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 + * + * @product highcharts highstock gantt + * + * @private + */ + 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. + * + * @sample {highcharts} highcharts/plotoptions/column-grouping-false/ + * Grouping disabled + * @sample {highstock} highcharts/plotoptions/column-grouping-false/ + * Grouping disabled + * + * @type {boolean} + * @default true + * @since 2.3.0 + * @product highcharts highstock gantt + * @apioption plotOptions.column.grouping + */ + /** + * @ignore-option + * @private + */ + marker: null, + /** + * 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. + * + * @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 + * + * @type {number} + * @since 4.1.8 + * @product highcharts highstock gantt + * @apioption plotOptions.column.maxPointWidth + */ + /** + * Padding between each column or bar, in x axis units. + * + * @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 + * + * @product highcharts highstock gantt + * + * @private + */ + pointPadding: 0.1, + /** + * A pixel value specifying a fixed width for each column or bar point. + * When `null`, the width is calculated from the `pointPadding` and + * `groupPadding`. The width effects the dimension that is not based on + * the point value. For column series it is the hoizontal length and for + * bar series it is the vertical length. + * + * @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 + * + * @type {number} + * @since 1.2.5 + * @product highcharts highstock gantt + * @apioption plotOptions.column.pointWidth + */ + /** + * A pixel value specifying a fixed width for the column or bar. + * Overrides pointWidth on the series. + * + * @see [series.pointWidth](#plotOptions.column.pointWidth) + * + * @type {number} + * @default undefined + * @since 7.0.0 + * @product highcharts highstock gantt + * @apioption series.column.data.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. + * + * @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 + * + * @product highcharts highstock gantt + * + * @private + */ + 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. + * + * @product highcharts highstock gantt + * + * @private + */ + 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. + * + * This option is set by default to 1 if data sorting is enabled. + * + * @sample {highcharts} highcharts/plotoptions/column-pointrange/ + * Set the point range to one day on a data set with one week + * between the points + * + * @type {number|null} + * @since 2.3 + * @product highcharts highstock gantt + * + * @private + */ + 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 gantt */ - showResetZoom: function () { - var chart = this, - lang = defaultOptions.lang, - btnOptions = chart.options.chart.resetZoomButton, - theme = btnOptions.theme, - states = theme.states, - alignTo = (btnOptions.relativeTo === 'chart' || - btnOptions.relativeTo === 'spaceBox' ? - null : - 'plotBox'); - /** - * @private - */ - function zoomOut() { - chart.zoomOut(); - } - fireEvent(this, 'beforeShowResetZoom', null, function () { - chart.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); - }); - fireEvent(this, 'afterShowResetZoom'); + hover: { + /** @ignore-option */ + halo: false, + /** + * A specific border color for the hovered point. Defaults to + * inherit the normal state border color. + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @product highcharts gantt + * @apioption plotOptions.column.states.hover.borderColor + */ + /** + * A specific color for the hovered point. + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @product highcharts gantt + * @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 gantt + */ + brightness: 0.1, }, /** - * Zoom the chart out after a user has zoomed in. See also - * [Axis.setExtremes](/class-reference/Highcharts.Axis#setExtremes). - * - * @function Highcharts.Chart#zoomOut + * Options for the selected point. These settings override the + * normal state options when a point is selected. * - * @fires Highcharts.Chart#event:selection + * @extends plotOptions.series.states.select + * @excluding halo, lineWidth, lineWidthPlus, marker + * @product highcharts highstock gantt */ - zoomOut: function () { - fireEvent(this, 'selection', { resetSelection: true }, this.zoom); + select: { + /** + * A specific color for the selected point. + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @default #cccccc + * @product highcharts highstock gantt + */ + color: "#cccccc", + /** + * A specific border color for the selected point. + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @default #000000 + * @product highcharts highstock gantt + */ + borderColor: "#000000", }, + }, + dataLabels: { + align: void 0, + verticalAlign: void 0, /** - * Zoom into a given portion of the chart given by axis coordinates. + * The y position offset of the label relative to the point in + * pixels. * - * @private - * @function Highcharts.Chart#zoom - * @param {Highcharts.SelectEventObject} event - */ - zoom: function (event) { - var chart = this, - hasZoomed, - pointer = chart.pointer, - displayButton = false, - mouseDownPos = chart.inverted ? pointer.mouseDownX : pointer.mouseDownY, - resetZoomButton; - // If zoom is called with no arguments, reset the axes - if (!event || event.resetSelection) { - chart.axes.forEach(function (axis) { - hasZoomed = axis.zoom(); - }); - pointer.initiated = false; // #6804 - } - else { // else, zoom in on all axes - event.xAxis.concat(event.yAxis).forEach(function (axisData) { - var axis = axisData.axis, - axisStartPos = chart.inverted ? axis.left : axis.top, - axisEndPos = chart.inverted ? - axisStartPos + axis.width : axisStartPos + axis.height, - isXAxis = axis.isXAxis, - isWithinPane = false; - // Check if zoomed area is within the pane (#1289). - // In case of multiple panes only one pane should be zoomed. - if ((!isXAxis && - mouseDownPos >= axisStartPos && - mouseDownPos <= axisEndPos) || - isXAxis || - !defined(mouseDownPos)) { - isWithinPane = true; - } - // don't zoom more than minRange - if (pointer[isXAxis ? 'zoomX' : 'zoomY'] && isWithinPane) { - hasZoomed = axis.zoom(axisData.min, axisData.max); - if (axis.displayBtn) { - displayButton = true; - } - } + * @type {number} + */ + y: void 0, + }, + // false doesn't work well: https://jsfiddle.net/highcharts/hz8fopan/14/ + /** + * @ignore-option + * @private + */ + 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|null} + * @since 2.0 + * @product highcharts + * + * @private + */ + threshold: 0, + /** + * The width of the border surrounding each column or bar. Defaults to + * `1` when there is room for a border, but to `0` when the columns are + * so dense that a border would cover the next column. + * + * In styled mode, the stroke width can be set with the + * `.highcharts-point` rule. + * + * @sample {highcharts} highcharts/plotoptions/column-borderwidth/ + * 2px black border + * + * @type {number} + * @default undefined + * @product highcharts highstock gantt + * @apioption plotOptions.column.borderWidth + */ + /** + * The color of the border surrounding each column or bar. + * + * In styled mode, the border stroke can be set with the + * `.highcharts-point` rule. + * + * @sample {highcharts} highcharts/plotoptions/column-bordercolor/ + * Dark gray border + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @default #ffffff + * @product highcharts highstock gantt + * + * @private + */ + borderColor: "#ffffff", + }, + /** + * @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, + /* eslint-disable valid-jsdoc */ + /** + * Initialize the series. Extends the basic Series.init method by + * marking other series of the same type as dirty. + * + * @private + * @function Highcharts.seriesTypes.column#init + * @return {void} + */ + init: function () { + LineSeries.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) { + chart.series.forEach(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. + * + * @private + * @function Highcharts.seriesTypes.column#getColumnMetrics + * @return {Highcharts.ColumnMetricsObject} + */ + getColumnMetrics: function () { + var series = this, + options = series.options, + xAxis = series.xAxis, + yAxis = series.yAxis, + reversedStacks = xAxis.options.reversedStacks, + // Keep backward compatibility: reversed xAxis had reversed + // stacks + reverseStacks = + (xAxis.reversed && !reversedStacks) || + (!xAxis.reversed && reversedStacks), + 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 { + series.chart.series.forEach(function (otherSeries) { + var otherYAxis = otherSeries.yAxis, + otherOptions = otherSeries.options, + 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 && + otherOptions.stacking !== "group" + ) { + stackKey = otherSeries.stackKey; + if (typeof 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.ordinal && xAxis.ordinal.slope) || + 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) + (reverseStacks ? 1 : 0), + pointXOffset = + pointPadding + + (groupPadding + + colIndex * pointOffsetWidth - + categoryWidth / 2) * + (reverseStacks ? -1 : 1); + // Save it for reading in linked series (Error bars particularly) + series.columnMetrics = { + width: pointWidth, + offset: pointXOffset, + paddedWidth: pointOffsetWidth, + columnCount: columnCount, + }; + return series.columnMetrics; + }, + /** + * Make the columns crisp. The edges are rounded to the nearest full + * pixel. + * + * @private + * @function Highcharts.seriesTypes.column#crispCol + * @param {number} x + * @param {number} y + * @param {number} w + * @param {number} h + * @return {Highcharts.BBoxObject} + */ + 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, + }; + }, + /** + * Adjust for missing columns, according to the `centerInCategory` + * option. Missing columns are either single points or stacks where the + * point or points are either missing or null. + * + * @private + * @function Highcharts.seriesTypes.column#adjustForMissingColumns + * @param {number} x + * The x coordinate of the column, left side + * @param {number} pointWidth + * The pointWidth, already computed upstream + * @param {Highcharts.ColumnPoint} point + * The point instance + * @param {Highcharts.ColumnMetricsObject} metrics + * The series-wide column metrics + * @return {number} + * The adjusted x position, or the original if not adjusted + */ + adjustForMissingColumns: function (x, pointWidth, point, metrics) { + var _this = this; + var stacking = this.options.stacking; + if (!point.isNull && metrics.columnCount > 1) { + var indexInCategory_1 = 0; + var totalInCategory_1 = 0; + // Loop over all the stacks on the Y axis. When stacking is + // enabled, these are real point stacks. When stacking is not + // enabled, but `centerInCategory` is true, there is one stack + // handling the grouping of points in each category. This is + // done in the `setGroupedPoints` function. + objectEach( + this.yAxis.stacking && this.yAxis.stacking.stacks, + function (stack) { + if (typeof point.x === "number") { + var stackItem = stack[point.x.toString()]; + if (stackItem) { + var pointValues = stackItem.points[_this.index], + total = stackItem.total; + // If true `stacking` is enabled, count the + // total number of non-null stacks in the + // category, and note which index this point is + // within those stacks. + if (stacking) { + if (pointValues) { + indexInCategory_1 = totalInCategory_1; + } + if (stackItem.hasValidPoints) { + totalInCategory_1++; + } + // If `stacking` is not enabled, look for the + // index and total of the `group` stack. + } else if (isArray(pointValues)) { + indexInCategory_1 = pointValues[1]; + totalInCategory_1 = total || 0; + } + } + } + } + ); + // Compute the adjusted x position + var boxWidth = + (totalInCategory_1 - 1) * metrics.paddedWidth + pointWidth; + x = + (point.plotX || 0) + + boxWidth / 2 - + pointWidth - + indexInCategory_1 * metrics.paddedWidth; + } + return x; + }, + /** + * Translate each point to the plot area coordinate system and find + * shape positions + * + * @private + * @function Highcharts.seriesTypes.column#translate + */ + 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 + )), + xAxis = series.xAxis, + yAxis = series.yAxis, + threshold = options.threshold, + translatedThreshold = (series.translatedThreshold = + yAxis.getThreshold(threshold)), + minPointLength = pick(options.minPointLength, 5), + metrics = series.getColumnMetrics(), + seriesPointWidth = metrics.width, + // postprocessed for border width + seriesBarW = (series.barW = Math.max( + seriesPointWidth, + 1 + 2 * borderWidth + )), + seriesXOffset = (series.pointXOffset = metrics.offset), + dataMin = series.dataMin, + dataMax = series.dataMax; + 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); + } + LineSeries.prototype.translate.apply(series); + // Record the new values + series.points.forEach(function (point) { + var yBottom = pick(point.yBottom, translatedThreshold), + safeDistance = 999 + Math.abs(yBottom), + pointWidth = seriesPointWidth, + plotX = point.plotX || 0, + // Don't draw too far outside plot area (#1303, #2241, + // #4264) + plotY = clamp( + point.plotY, + -safeDistance, + yAxis.len + safeDistance + ), + barX = plotX + seriesXOffset, + 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 ( + isNumber(threshold) && + isNumber(dataMax) && + point.y === threshold && + dataMax <= threshold && + // and if there's room for it (#7311) + (yAxis.min || 0) < threshold && + // if all points are the same value (i.e zero) not draw + // as negative points (#10646) + dataMin !== dataMax + ) { + up = !up; + } + // If stacked... + barY = + Math.abs(barY - translatedThreshold) > minPointLength + ? // ...keep position + yBottom - minPointLength + : // #1485, #4051 + translatedThreshold - (up ? minPointLength : 0); + } + // Handle point.options.pointWidth + // @todo Handle grouping/stacking too. Calculate offset properly + if (defined(point.options.pointWidth)) { + pointWidth = barW = Math.ceil(point.options.pointWidth); + barX -= Math.round((pointWidth - seriesPointWidth) / 2); + } + // Adjust for null or missing points + if (options.centerInCategory) { + barX = series.adjustForMissingColumns( + barX, + pointWidth, + point, + metrics + ); + } + // 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, + xAxis.len + + xAxis.pos - + chart.plotTop - + (plotX || 0) - + seriesXOffset - + barW / 2, + barH, + ] + : [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH]; + // Register shape type and arguments to be used in drawPoints + // Allow shapeType defined on pointClass level + point.shapeType = series.pointClass.prototype.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 + * + * @private + * @function Highcharts.seriesTypes.column#drawLegendSymbol + * + * @param {Highcharts.Legend} legend + * The legend object + * + * @param {Highcharts.Series|Highcharts.Point} item + * The series (this) or point + */ + drawLegendSymbol: LegendSymbolMixin.drawRectangle, + /** + * Columns have no graph + * + * @private + * @function Highcharts.seriesTypes.column#drawGraph + */ + drawGraph: function () { + this.group[this.dense ? "addClass" : "removeClass"]( + "highcharts-dense-data" + ); + }, + /** + * Get presentational attributes + * + * @private + * @function Highcharts.seriesTypes.column#pointAttribs + * + * @param {Highcharts.ColumnPoint} point + * + * @param {string} state + * + * @return {Highcharts.SVGAttributes} + */ + 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, + // set to fill when borderColor null: + stroke = + (point && point[strokeOption]) || + options[strokeOption] || + this.color || + fill, + strokeWidth = + (point && point[strokeWidthOption]) || + options[strokeWidthOption] || + this[strokeWidthOption] || + 0, + dashstyle = + (point && point.options.dashStyle) || options.dashStyle, + opacity = pick(point && point.opacity, options.opacity, 1), + 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), added support for colorAxis (#10670) + fill = + point.options.color || + (zone && (zone.color || point.nonZonedColor)) || + this.color; + if (zone) { + stroke = zone.borderColor || stroke; + dashstyle = zone.dashStyle || dashstyle; + strokeWidth = zone.borderWidth || strokeWidth; + } + } + // Select or hover states + if (state && point) { + stateOptions = merge( + options.states[state], + // #6401 + (point.options.states && point.options.states[state]) || {} + ); + brightness = stateOptions.brightness; + fill = + stateOptions.color || + (typeof brightness !== "undefined" && + color(fill).brighten(stateOptions.brightness).get()) || + fill; + stroke = stateOptions[strokeOption] || stroke; + strokeWidth = stateOptions[strokeWidthOption] || strokeWidth; + dashstyle = stateOptions.dashStyle || dashstyle; + opacity = pick(stateOptions.opacity, opacity); + } + ret = { + fill: fill, + stroke: stroke, + "stroke-width": strokeWidth, + opacity: opacity, + }; + 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. + * + * @private + * @function Highcharts.seriesTypes.column#drawPoints + */ + drawPoints: function () { + var series = this, + chart = this.chart, + options = series.options, + renderer = chart.renderer, + animationLimit = options.animationLimit || 250, + shapeArgs; + // draw the columns + series.points.forEach(function (point) { + var plotY = point.plotY, + graphic = point.graphic, + hasGraphic = !!graphic, + verb = + graphic && chart.pointCount < animationLimit + ? "animate" + : "attr"; + if (isNumber(plotY) && point.y !== null) { + shapeArgs = point.shapeArgs; + // When updating a series between 2d and 3d or cartesian and + // polar, the shape type changes. + if (graphic && point.hasNewShapeType()) { + graphic = graphic.destroy(); + } + // Set starting position for point sliding animation. + if (series.enabledDataSorting) { + point.startXPos = series.xAxis.reversed + ? -(shapeArgs ? shapeArgs.width : 0) + : series.xAxis.width; + } + if (!graphic) { + point.graphic = graphic = renderer[point.shapeType]( + shapeArgs + ).add(point.group || series.group); + if ( + graphic && + series.enabledDataSorting && + chart.hasRendered && + chart.pointCount < animationLimit + ) { + graphic.attr({ + x: point.startXPos, }); + hasGraphic = true; + verb = "animate"; + } } - // Show or hide the Reset zoom button - resetZoomButton = chart.resetZoomButton; - if (displayButton && !resetZoomButton) { - chart.showResetZoom(); - } - else if (!displayButton && isObject(resetZoomButton)) { - chart.resetZoomButton = resetZoomButton.destroy(); + if (graphic && hasGraphic) { + // update + graphic[verb](merge(shapeArgs)); } - // Redraw - if (hasZoomed) { - chart.redraw(pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100)); + // Border radius is not stylable (#6900) + if (options.borderRadius) { + graphic[verb]({ + r: options.borderRadius, + }); } - }, + // Presentational + if (!chart.styledMode) { + graphic[verb]( + series.pointAttribs(point, point.selected && "select") + ).shadow( + point.allowShadow !== false && 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. + * + * @private + * @function Highcharts.seriesTypes.column#animate + * + * @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 (init) { + attr.scaleY = 0.001; + translatedThreshold = clamp( + yAxis.toPixels(options.threshold), + yAxis.pos, + yAxis.pos + yAxis.len + ); + if (inverted) { + attr.translateX = translatedThreshold - yAxis.len; + } else { + attr.translateY = translatedThreshold; + } + // apply finnal clipping (used in Highstock) (#7083) + // animation is done by scaleY, so cliping is for panes + if (series.clipBox) { + series.setClip(); + } + 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) { + if (series.group) { + attr[translateProp] = + translateStart + fx.pos * (yAxis.pos - translateStart); + series.group.attr(attr); + } + }, + }) + ); + } + }, + /** + * Remove this series from the chart + * + * @private + * @function Highcharts.seriesTypes.column#remove + */ + 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) { + chart.series.forEach(function (otherSeries) { + if (otherSeries.type === series.type) { + otherSeries.isDirty = true; + } + }); + } + LineSeries.prototype.remove.apply(series, arguments); + }, + } + ); + /* eslint-enable valid-jsdoc */ + /** + * A `column` series. If the [type](#series.column.type) option is + * not specified, it is inherited from [chart.type](#chart.type). + * + * @extends series,plotOptions.column + * @excluding connectNulls, dataParser, dataURL, gapSize, gapUnit, linecap, + * lineWidth, marker, connectEnds, step + * @product highcharts highstock + * @apioption series.column + */ + /** + * 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 following snippet shows only a + * few settings, see the complete options set 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" + * }] + * ``` + * + * @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 + * + * @type {Array<number|Array<(number|string),(number|null)>|null|*>} + * @extends series.line.data + * @excluding marker + * @product highcharts highstock + * @apioption series.column.data + */ + /** + * The color of the border surrounding the column or bar. + * + * In styled mode, the border stroke can be set with the `.highcharts-point` + * rule. + * + * @sample {highcharts} highcharts/plotoptions/column-bordercolor/ + * Dark gray border + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @product highcharts highstock + * @apioption series.column.data.borderColor + */ + /** + * The width of the border surrounding the column or bar. + * + * In styled mode, the stroke width can be set with the `.highcharts-point` + * rule. + * + * @sample {highcharts} highcharts/plotoptions/column-borderwidth/ + * 2px black border + * + * @type {number} + * @product highcharts highstock + * @apioption series.column.data.borderWidth + */ + /** + * A name for the dash style to use for the column or bar. Overrides + * dashStyle on the series. + * + * In styled mode, the stroke dash-array can be set with the same classes as + * listed under [data.color](#series.column.data.color). + * + * @see [series.pointWidth](#plotOptions.column.dashStyle) + * + * @type {Highcharts.DashStyleValue} + * @apioption series.column.data.dashStyle + */ + /** + * A pixel value specifying a fixed width for the column or bar. Overrides + * pointWidth on the series. The width effects the dimension that is not based + * on the point value. + * + * @see [series.pointWidth](#plotOptions.column.pointWidth) + * + * @type {number} + * @apioption series.column.data.pointWidth + */ + /** + * @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 + */ + (""); // includes above doclets in transpilat + + return ColumnSeries; + } + ); + _registerModule( + _modules, + "Series/BarSeries.js", + [_modules["Core/Series/Series.js"]], + function (Series) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /** + * Bar series type. + * + * @private + * @class + * @name Highcharts.seriesTypes.bar + * + * @augments Highcharts.Series + */ + Series.seriesType( + "bar", + "column", + /** + * 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 + * @apioption plotOptions.bar + */ + /** + * @ignore + */ + null, + { + inverted: true, + } + ); + /** + * A `bar` series. If the [type](#series.bar.type) option is not specified, + * it is inherited from [chart.type](#chart.type). + * + * @extends series,plotOptions.bar + * @excluding connectNulls, dashStyle, dataParser, dataURL, gapSize, gapUnit, + * linecap, lineWidth, marker, connectEnds, step + * @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 following snippet shows only a + * few settings, see the complete options set 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" + * }] + * ``` + * + * @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 + * + * @type {Array<number|Array<(number|string),(number|null)>|null|*>} + * @extends series.column.data + * @product highcharts + * @apioption series.bar.data + */ + /** + * @excluding halo,lineWidth,lineWidthPlus,marker + * @product highcharts highstock + * @apioption series.bar.states.hover + */ + /** + * @excluding halo,lineWidth,lineWidthPlus,marker + * @product highcharts highstock + * @apioption series.bar.states.select + */ + (""); // gets doclets above into transpilat + } + ); + _registerModule( + _modules, + "Series/ScatterSeries.js", + [ + _modules["Core/Series/Series.js"], + _modules["Core/Globals.js"], + _modules["Core/Utilities.js"], + ], + function (BaseSeries, H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var addEvent = U.addEvent; + var Series = H.Series; + /** + * Scatter series type. + * + * @private + * @class + * @name Highcharts.seriesTypes.scatter + * + * @augments Highcharts.Series + */ + BaseSeries.seriesType( + "scatter", + "line", + /** + * 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 + * @excluding cropThreshold, pointPlacement, shadow, useOhlcData + * @product highcharts highstock + * @optionparent plotOptions.scatter + */ + { + /** + * The width of the line connecting the data points. + * + * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/ + * 0 by default + * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/ + * 1px + * + * @product highcharts highstock + */ + lineWidth: 0, + findNearestPointBy: "xy", + /** + * Apply a jitter effect for the rendered markers. When plotting + * discrete values, a little random noise may help telling the points + * apart. The jitter setting applies a random displacement of up to `n` + * axis units in either direction. So for example on a horizontal X + * axis, setting the `jitter.x` to 0.24 will render the point in a + * random position between 0.24 units to the left and 0.24 units to the + * right of the true axis position. On a category axis, setting it to + * 0.5 will fill up the bin and make the data appear continuous. + * + * When rendered on top of a box plot or a column series, a jitter value + * of 0.24 will correspond to the underlying series' default + * [groupPadding]( + * https://api.highcharts.com/highcharts/plotOptions.column.groupPadding) + * and [pointPadding]( + * https://api.highcharts.com/highcharts/plotOptions.column.pointPadding) + * settings. + * + * @sample {highcharts} highcharts/series-scatter/jitter + * Jitter on a scatter plot + * + * @sample {highcharts} highcharts/series-scatter/jitter-boxplot + * Jittered scatter plot on top of a box plot + * + * @product highcharts highstock + * @since 7.0.2 + */ + jitter: { + /** + * The maximal X offset for the random jitter effect. + */ + x: 0, + /** + * The maximal Y offset for the random jitter effect. + */ + y: 0, + }, + 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](#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: + '<span style="color:{point.color}">\u25CF</span> ' + + '<span style="font-size: 10px"> {series.name}</span><br/>', + pointFormat: "x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>", + }, + // Prototype members + }, + { + sorted: false, + requireSorting: false, + noSharedTooltip: true, + trackerGroups: ["group", "markerGroup", "dataLabelsGroup"], + takeOrdinalPosition: false, + /* eslint-disable valid-jsdoc */ + /** + * @private + * @function Highcharts.seriesTypes.scatter#drawGraph + */ + drawGraph: function () { + if ( + this.options.lineWidth || + // In case we have a graph from before and we update the line + // width to 0 (#13816) + (this.options.lineWidth === 0 && + this.graph && + this.graph.strokeWidth()) + ) { + Series.prototype.drawGraph.call(this); + } + }, + // Optionally add the jitter effect + applyJitter: function () { + var series = this, + jitter = this.options.jitter, + len = this.points.length; /** - * 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. - * + * Return a repeatable, pseudo-random number based on an integer + * seed. * @private - * @function Highcharts.Chart#pan - * @param {Highcharts.PointerEventObject} e - * @param {string} panning */ - pan: function (e, panning) { - var chart = this, - hoverPoints = chart.hoverPoints, - panningOptions, - chartOptions = chart.options.chart, - hasMapNavigation = chart.options.mapNavigation && - chart.options.mapNavigation.enabled, - doRedraw, - type; - if (typeof panning === 'object') { - panningOptions = panning; - } - else { - panningOptions = { - enabled: panning, - type: 'x' - }; - } - if (chartOptions && chartOptions.panning) { - chartOptions.panning = panningOptions; - } - type = panningOptions.type; - fireEvent(this, 'pan', { originalEvent: e }, function () { - // remove active points for shared tooltip - if (hoverPoints) { - hoverPoints.forEach(function (point) { - point.setState(); - }); - } - // panning axis mapping - var xy = [1]; // x - if (type === 'xy') { - xy = [1, 0]; - } - else if (type === 'y') { - xy = [0]; - } - xy.forEach(function (isX) { - 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, pointRangeDirection = (axis.reversed && !chart.inverted) || - (!axis.reversed && chart.inverted) ? - -1 : - 1, extremes = axis.getExtremes(), panMin = axis.toValue(startPos - mousePos, true) + - halfPointRange * pointRangeDirection, panMax = axis.toValue(startPos + axis.len - mousePos, true) - - halfPointRange * pointRangeDirection, flipped = panMax < panMin, newMin = flipped ? panMax : panMin, newMax = flipped ? panMin : panMax, hasVerticalPanning = axis.hasVerticalPanning(), paddedMin, paddedMax, spill, panningState = axis.panningState; - // General calculations of panning state. - // This is related to using vertical panning. (#11315). - axis.series.forEach(function (series) { - if (hasVerticalPanning && - !isX && (!panningState || panningState.isDirty)) { - var processedData = series.getProcessedData(true), - dataExtremes = series.getExtremes(processedData.yData, - true); - if (!panningState) { - panningState = { - startMin: Number.MAX_VALUE, - startMax: -Number.MAX_VALUE - }; - } - if (isNumber(dataExtremes.dataMin) && - isNumber(dataExtremes.dataMax)) { - panningState.startMin = Math.min(dataExtremes.dataMin, panningState.startMin); - panningState.startMax = Math.max(dataExtremes.dataMax, panningState.startMax); - } - } - }); - paddedMin = Math.min(pick(panningState === null || panningState === void 0 ? void 0 : panningState.startMin, extremes.dataMin), halfPointRange ? - extremes.min : - axis.toValue(axis.toPixels(extremes.min) - - axis.minPixelPadding)); - paddedMax = Math.max(pick(panningState === null || panningState === void 0 ? void 0 : panningState.startMax, extremes.dataMax), halfPointRange ? - extremes.max : - axis.toValue(axis.toPixels(extremes.max) + - axis.minPixelPadding)); - axis.panningState = panningState; - // It is not necessary to calculate extremes on ordinal axis, - // because they are already calculated, so we don't want to - // override them. - if (!axis.isOrdinal) { - // 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 && - newMin >= paddedMin && - newMax <= paddedMax) { - axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' }); - if (!chart.resetZoomButton && - !hasMapNavigation && - // Show reset zoom button only when both newMin and - // newMax values are between padded axis range. - newMin !== paddedMin && - newMax !== paddedMax && - type.match('y')) { - chart.showResetZoom(); - axis.displayBtn = false; - } - doRedraw = true; - } - // set new reference for next run: - chart[mouseDown] = mousePos; - } - }); - if (doRedraw) { - chart.redraw(false); - } - css(chart.container, { cursor: 'move' }); - }); + function unrandom(seed) { + var rand = Math.sin(seed) * 10000; + return rand - Math.floor(rand); } - }); - // Extend the Point object with interaction - extend(Point.prototype, /** @lends Highcharts.Point.prototype */ { - /** - * Toggle the selection status of a 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 - * - * @function Highcharts.Point#select - * - * @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 - * [allowPointSelect](https://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect) - * is `true`, selected points are accumulated on Control, Shift or Cmd - * clicking the point. - * - * @fires Highcharts.Point#event:select - * @fires Highcharts.Point#event:unselect - */ - select: function (selected, accumulate) { - var point = this, - series = point.series, - chart = series.chart; - selected = pick(selected, !point.selected); - this.selectedStaging = 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 - * - * @name Highcharts.Point#selected - * @type {boolean} - */ - point.selected = point.options.selected = selected; - series.options.data[series.data.indexOf(point)] = - point.options; - point.setState(selected && 'select'); - // unselect all other points unless Ctrl or Cmd + click - if (!accumulate) { - chart.getSelectedPoints().forEach(function (loopPoint) { - var loopSeries = loopPoint.series; - if (loopPoint.selected && loopPoint !== point) { - loopPoint.selected = loopPoint.options.selected = - false; - loopSeries.options.data[loopSeries.data.indexOf(loopPoint)] = loopPoint.options; - // Programatically selecting a point should restore - // normal state, but when click happened on other - // point, set inactive state to match other points - loopPoint.setState(chart.hoverPoints && - loopSeries.options.inactiveOtherPoints ? - 'inactive' : ''); - loopPoint.firePointEvent('unselect'); - } - }); - } + if (jitter) { + this.points.forEach(function (point, i) { + ["x", "y"].forEach(function (dim, j) { + var axis, + plotProp = "plot" + dim.toUpperCase(), + min, + max, + translatedJitter; + if (jitter[dim] && !point.isNull) { + axis = series[dim + "Axis"]; + translatedJitter = jitter[dim] * axis.transA; + if (axis && !axis.isLog) { + // Identify the outer bounds of the jitter range + min = Math.max(0, point[plotProp] - translatedJitter); + max = Math.min( + axis.len, + point[plotProp] + translatedJitter + ); + // Find a random position within this range + point[plotProp] = + min + (max - min) * unrandom(i + j * len); + // Update clientX for the tooltip k-d-tree + if (dim === "x") { + point.clientX = point.plotX; + } + } + } }); - delete this.selectedStaging; - }, - /** - * Runs on mouse over the point. Called internally from mouse and touch - * events. - * - * @function Highcharts.Point#onMouseOver - * - * @param {Highcharts.PointerEventObject} [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. - * - * @function Highcharts.Point#onMouseOut - * @fires Highcharts.Point#event:mouseOut - */ - onMouseOut: function () { - var point = this, - chart = point.series.chart; - point.firePointEvent('mouseOut'); - if (!point.series.options.inactiveOtherPoints) { - (chart.hoverPoints || []).forEach(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 - * @function Highcharts.Point#importEvents - */ - importEvents: function () { - if (!this.hasImportedEvents) { - var point = this, - options = merge(point.series.options.point, - point.options), - events = options.events; - point.events = events; - objectEach(events, function (event, eventType) { - if (isFunction(event)) { - addEvent(point, eventType, event); - } - }); - this.hasImportedEvents = true; - } - }, - /** - * Set the point's state. - * - * @function Highcharts.Point#setState - * - * @param {Highcharts.PointStateValue|""} [state] - * The new state, can be one of `'hover'`, `'select'`, `'inactive'`, - * or `''` (an empty string), `'normal'` or `undefined` to set to - * normal state. - * @param {boolean} [move] - * State for animation. - * - * @fires Highcharts.Point#event:afterSetState - */ - setState: function (state, move) { - var point = this, - series = point.series, - previousState = point.state, - stateOptions = (series.options.states[state || 'normal'] || - {}), - markerOptions = (defaultOptions.plotOptions[series.type].marker && - series.options.marker), - normalDisabled = (markerOptions && markerOptions.enabled === false), - markerStateOptions = ((markerOptions && - markerOptions.states && - markerOptions.states[state || 'normal']) || {}), - stateDisabled = markerStateOptions.enabled === false, - stateMarkerGraphic = series.stateMarkerGraphic, - pointMarker = point.marker || {}, - chart = series.chart, - halo = series.halo, - haloOptions, - markerAttribs, - pointAttribs, - pointAttribsAnimation, - 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; - } - point.state = state; - if (hasMarkers) { - markerAttribs = series.markerAttribs(point, state); - } - // Apply hover styles to the existing point - if (point.graphic) { - if (previousState) { - point.graphic.removeClass('highcharts-point-' + previousState); - } - if (state) { - point.graphic.addClass('highcharts-point-' + state); - } - if (!chart.styledMode) { - pointAttribs = series.pointAttribs(point, state); - pointAttribsAnimation = pick(chart.options.chart.animation, stateOptions.animation); - // Some inactive points (e.g. slices in pie) should apply - // oppacity also for it's labels - if (series.options.inactiveOtherPoints && pointAttribs.opacity) { - (point.dataLabels || []).forEach(function (label) { - if (label) { - label.animate({ - opacity: pointAttribs.opacity - }, pointAttribsAnimation); - } - }); - if (point.connector) { - point.connector.animate({ - opacity: pointAttribs.opacity - }, pointAttribsAnimation); - } - } - point.graphic.animate(pointAttribs, pointAttribsAnimation); - } - if (markerAttribs) { - point.graphic.animate(markerAttribs, pick( - // Turn off globally: - chart.options.chart.animation, 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 (markerAttribs) { - 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']({ - x: markerAttribs.x, - y: markerAttribs.y - }); - } - } - if (!chart.styledMode && stateMarkerGraphic) { - stateMarkerGraphic.attr(series.pointAttribs(point, state)); - } - } - if (stateMarkerGraphic) { - stateMarkerGraphic[state && point.isInside ? 'show' : 'hide'](); // #2450 - stateMarkerGraphic.element.point = point; // #4310 - } - } - // Show me your halo - haloOptions = stateOptions.halo; - var markerGraphic = (point.graphic || stateMarkerGraphic); - var markerVisibility = (markerGraphic && markerGraphic.visibility || 'inherit'); - if (haloOptions && - haloOptions.size && - markerGraphic && - markerVisibility !== 'hidden' && - !point.isCluster) { - if (!halo) { - series.halo = halo = chart.renderer.path() - // #5818, #5903, #6705 - .add(markerGraphic.parentGroup); - } - halo.show()[move ? 'animate' : 'attr']({ - d: point.haloPath(haloOptions.size) - }); - halo.attr({ - 'class': 'highcharts-halo highcharts-color-' + - pick(point.colorIndex, series.colorIndex) + - (point.className ? ' ' + point.className : ''), - 'visibility': markerVisibility, - 'zIndex': -1 // #4929, #8276 - }); - halo.point = point; // #6055 - if (!chart.styledMode) { - halo.attr(extend({ - 'fill': point.color || series.color, - 'fill-opacity': haloOptions.opacity - }, 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) }, null, - // Hide after unhovering. The `complete` callback runs in the - // halo's context (#7681). - halo.hide); - } - fireEvent(point, 'afterSetState'); - }, + }); + } + }, + /* eslint-enable valid-jsdoc */ + } + ); + /* eslint-disable no-invalid-this */ + addEvent(Series, "afterTranslate", function () { + if (this.applyJitter) { + this.applyJitter(); + } + }); + /* eslint-enable no-invalid-this */ + /** + * A `scatter` series. If the [type](#series.scatter.type) option is + * not specified, it is inherited from [chart.type](#chart.type). + * + * @extends series,plotOptions.scatter + * @excluding cropThreshold, dataParser, dataURL, useOhlcData + * @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 following snippet shows only a + * few settings, see the complete options set 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" + * }] + * ``` + * + * @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 + * + * @type {Array<number|Array<(number|string),(number|null)>|null|*>} + * @extends series.line.data + * @product highcharts highstock + * @apioption series.scatter.data + */ + (""); // adds doclets above to transpilat + } + ); + _registerModule( + _modules, + "Mixins/CenteredSeries.js", + [_modules["Core/Globals.js"], _modules["Core/Utilities.js"]], + function (H, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + /** + * @private + * @interface Highcharts.RadianAngles + */ /** + * @name Highcharts.RadianAngles#end + * @type {number} + */ /** + * @name Highcharts.RadianAngles#start + * @type {number} + */ + var isNumber = U.isNumber, + pick = U.pick, + relativeLength = U.relativeLength; + var deg2rad = H.deg2rad; + /* eslint-disable valid-jsdoc */ + /** + * @private + * @mixin Highcharts.CenteredSeriesMixin + */ + var centeredSeriesMixin = (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. + * + * @private + * @function Highcharts.CenteredSeriesMixin.getCenter + * + * @return {Array<number>} + */ + 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, + smallestSize = Math.min(plotWidth, plotHeight), + size = options.size, + innerSize = options.innerSize || 0, + positions, + i, + value; + if (typeof size === "string") { + size = parseFloat(size); + } + if (typeof innerSize === "string") { + innerSize = parseFloat(innerSize); + } + positions = [ + pick(centerOption[0], "50%"), + pick(centerOption[1], "50%"), + // Prevent from negative values + pick(size && size < 0 ? void 0 : options.size, "100%"), + pick( + innerSize && innerSize < 0 ? void 0 : options.innerSize || 0, + "0%" + ), + ]; + // No need for inner size in angular (gauges) series but still required + // for pie series + if (chart.angular && !(this instanceof H.Series)) { + positions[3] = 0; + } + 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. + * + * @private + * @function Highcharts.CenteredSeriesMixin.getStartAndEndRadians + * + * @param {number} [start] + * Start angle in degrees. + * + * @param {number} [end] + * Start angle in degrees. + * + * @return {Highcharts.RadianAngles} + * Returns an object containing start and end angles as radians. + */ + getStartAndEndRadians: function (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), + }; + }, + }); + + return centeredSeriesMixin; + } + ); + _registerModule( + _modules, + "Series/PieSeries.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Series/Series.js"], + _modules["Mixins/CenteredSeries.js"], + _modules["Core/Globals.js"], + _modules["Mixins/LegendSymbol.js"], + _modules["Series/LineSeries.js"], + _modules["Core/Series/Point.js"], + _modules["Core/Renderer/SVG/SVGRenderer.js"], + _modules["Core/Utilities.js"], + ], + function ( + A, + BaseSeries, + CenteredSeriesMixin, + H, + LegendSymbolMixin, + LineSeries, + Point, + SVGRenderer, + U + ) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var setAnimation = A.setAnimation; + var getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians; + var noop = H.noop; + var addEvent = U.addEvent, + clamp = U.clamp, + defined = U.defined, + fireEvent = U.fireEvent, + isNumber = U.isNumber, + merge = U.merge, + pick = U.pick, + relativeLength = U.relativeLength; + /** + * Pie series type. + * + * @private + * @class + * @name Highcharts.seriesTypes.pie + * + * @augments Highcharts.Series + */ + BaseSeries.seriesType( + "pie", + "line", + /** + * 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, dataSorting, dragDrop, + * findNearestPointBy, getExtremesFromAll, label, lineWidth, + * marker, negativeColor, pointInterval, pointIntervalUnit, + * pointPlacement, pointStart, softThreshold, stacking, step, + * threshold, turboThreshold, zoneAxis, zones, dataSorting, + * boostBlending + * @product highcharts + * @optionparent plotOptions.pie + */ + { + /** + * @excluding legendItemClick + * @apioption plotOptions.pie.events + */ + /** + * 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. + * + * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/ + * Alert checkbox status + * + * @type {Function} + * @since 1.2.0 + * @product highcharts + * @context Highcharts.Point + * @apioption plotOptions.pie.events.checkboxClick + */ + /** + * 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()`. + * + * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/ + * Confirm toggle visibility + * + * @type {Highcharts.PointLegendItemClickCallbackFunction} + * @since 1.2.0 + * @product highcharts + * @apioption plotOptions.pie.point.events.legendItemClick + */ + /** + * 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%"]`. + * + * @sample {highcharts} highcharts/plotoptions/pie-center/ + * Centered at 100, 100 + * + * @type {Array<(number|string|null),(number|string|null)>} + * @default [null, null] + * @product highcharts + * + * @private + */ + center: [null, null], + /** + * The color of the pie series. A pie series is represented as an empty + * circle if the total sum of its values is 0. Use this property to + * define the color of its border. + * + * 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. + * + * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/ + * Empty pie series + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @default #cccccc + * @apioption plotOptions.pie.color + */ + /** + * @product highcharts + * + * @private + */ + clip: false, + /** + * @ignore-option + * + * @private + */ + colorByPoint: true, + /** + * A series specific or series type specific color set to use instead + * of the global [colors](#colors). + * + * @sample {highcharts} highcharts/demo/pie-monochrome/ + * Set default colors for all pies + * + * @type {Array<Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject>} + * @since 3.0 + * @product highcharts + * @apioption plotOptions.pie.colors + */ + /** + * @declare Highcharts.SeriesPieDataLabelsOptionsObject + * @extends plotOptions.series.dataLabels + * @excluding align, allowOverlap, inside, staggerLines, step + * @private + */ + dataLabels: { + /** + * Alignment method for data labels. Possible values are: + * + * - `toPlotEdges`: Each label touches the nearest vertical edge of + * the plot area. + * + * - `connectors`: Connectors have the same x position and the + * widest label of each half (left & right) touches the nearest + * vertical edge of the plot area. + * + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-connectors/ + * alignTo: connectors + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-plotedges/ + * alignTo: plotEdges + * + * @type {string} + * @since 7.0.0 + * @product highcharts + * @apioption plotOptions.pie.dataLabels.alignTo + */ + allowOverlap: true, /** - * Get the path definition for the halo, which is usually a shadow-like - * circle around the currently hovered point. + * The color of the line connecting the data label to the pie slice. + * The default color is the same as the point's color. * - * @function Highcharts.Point#haloPath + * In styled mode, the connector stroke is given in the + * `.highcharts-data-label-connector` class. * - * @param {number} size - * The radius of the circular halo. + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ + * Blue connectors + * @sample {highcharts} highcharts/css/pie-point/ + * Styled connectors * - * @return {Highcharts.SVGPathArray} - * The path definition. + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @since 2.1 + * @product highcharts + * @apioption plotOptions.pie.dataLabels.connectorColor */ - 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(LineSeries.prototype, /** @lends Highcharts.Series.prototype */ { /** - * Runs on mouse over the series graphical items. + * The distance from the data label to the connector. Note that + * data labels also have a default `padding`, so in order for the + * connector to touch the text, the `padding` must also be 0. * - * @function Highcharts.Series#onMouseOver - * @fires Highcharts.Series#event:mouseOver + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ + * No padding + * + * @since 2.1 + * @product highcharts */ - onMouseOver: function () { - var series = this, - chart = series.chart, - hoverSeries = chart.hoverSeries, - pointer = chart.pointer; - pointer.setHoverChartIndex(); - // 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'); - /** - * Contains the original hovered series. - * - * @name Highcharts.Chart#hoverSeries - * @type {Highcharts.Series|null} - */ - chart.hoverSeries = series; - }, + connectorPadding: 5, /** - * Runs on mouse out of the series graphical items. + * Specifies the method that is used to generate the connector path. + * Highcharts provides 3 built-in connector shapes: `'fixedOffset'` + * (default), `'straight'` and `'crookedLine'`. Using + * `'crookedLine'` has the most sense (in most of the cases) when + * `'alignTo'` is set. * - * @function Highcharts.Series#onMouseOut + * Users can provide their own method by passing a function instead + * of a String. 3 arguments are passed to the callback: * - * @fires Highcharts.Series#event:mouseOut - */ - onMouseOut: function () { - // trigger the event only if listeners exist - var series = this, - options = series.options, - chart = series.chart, - tooltip = chart.tooltip, - hoverPoint = chart.hoverPoint; - // #182, set to null before the mouseOut event fires - chart.hoverSeries = null; - // 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(); - } - // Reset all inactive states - chart.series.forEach(function (s) { - s.setState('', true); - }); - }, - /** - * Set the state of the series. Called internally on mouse interaction - * operations, but it can also be called directly to visually - * highlight a series. - * - * @function Highcharts.Series#setState - * - * @param {Highcharts.SeriesStateValue|""} [state] - * The new state, can be either `'hover'`, `'inactive'`, `'select'`, - * or `''` (an empty string), `'normal'` or `undefined` to set to - * normal state. - * @param {boolean} [inherit] - * Determines if state should be inherited by points too. + * - Object that holds the information about the coordinates of the + * label (`x` & `y` properties) and how the label is located in + * relation to the pie (`alignment` property). `alignment` can by + * one of the following: + * `'left'` (pie on the left side of the data label), + * `'right'` (pie on the right side of the data label) or + * `'center'` (data label overlaps the pie). + * + * - Object that holds the information about the position of the + * connector. Its `touchingSliceAt` porperty tells the position + * of the place where the connector touches the slice. + * + * - Data label options + * + * The function has to return an SVG path definition in array form + * (see the example). + * + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-string/ + * connectorShape is a String + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-function/ + * connectorShape is a function + * + * @type {string|Function} + * @since 7.0.0 + * @product highcharts */ - setState: function (state, inherit) { - var series = this, - options = series.options, - graph = series.graph, - inactiveOtherPoints = options.inactiveOtherPoints, - stateOptions = options.states, - lineWidth = options.lineWidth, - opacity = options.opacity, - // By default a quick animation to hover/inactive, - // slower to un-hover - stateAnimation = pick((stateOptions[state || 'normal'] && - stateOptions[state || 'normal'].animation), - series.chart.options.chart.animation), - attribs, - i = 0; - state = state || ''; - if (series.state !== state) { - // Toggle class names - [ - series.group, - series.markerGroup, - series.dataLabelsGroup - ].forEach(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 (!series.chart.styledMode) { - if (stateOptions[state] && - stateOptions[state].enabled === false) { - return; - } - if (state) { - lineWidth = (stateOptions[state].lineWidth || - lineWidth + (stateOptions[state].lineWidthPlus || 0)); // #4035 - opacity = pick(stateOptions[state].opacity, opacity); - } - if (graph && !graph.dashstyle) { - attribs = { - 'stroke-width': lineWidth - }; - // Animate the graph stroke-width. - graph.animate(attribs, stateAnimation); - while (series['zone-graph-' + i]) { - series['zone-graph-' + i].attr(attribs); - i = i + 1; - } - } - // For some types (pie, networkgraph, sankey) opacity is - // resolved on a point level - if (!inactiveOtherPoints) { - [ - series.group, - series.markerGroup, - series.dataLabelsGroup, - series.labelBySeries - ].forEach(function (group) { - if (group) { - group.animate({ - opacity: opacity - }, stateAnimation); - } - }); - } - } - } - // Don't loop over points on a series that doesn't apply inactive state - // to siblings markers (e.g. line, column) - if (inherit && inactiveOtherPoints && series.points) { - series.setAllPointsToState(state); - } - }, + connectorShape: "fixedOffset", /** - * Set the state for all points in the series. + * The width of the line connecting the data label to the pie slice. * - * @function Highcharts.Series#setAllPointsToState + * In styled mode, the connector stroke width is given in the + * `.highcharts-data-label-connector` class. * - * @private + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ + * Disable the connector + * @sample {highcharts} highcharts/css/pie-point/ + * Styled connectors * - * @param {string} [state] - * Can be either `hover` or undefined to set to normal state. + * @type {number} + * @default 1 + * @since 2.1 + * @product highcharts + * @apioption plotOptions.pie.dataLabels.connectorWidth */ - setAllPointsToState: function (state) { - this.points.forEach(function (point) { - if (point.setState) { - point.setState(state); - } - }); - }, /** - * Show or hide the series. + * Works only if `connectorShape` is `'crookedLine'`. It defines how + * far from the vertical plot edge the coonnector path should be + * crooked. * - * @function Highcharts.Series#setVisible + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-crookdistance/ + * crookDistance set to 90% * - * @param {boolean} [visible] - * True to show the series, false to hide. If undefined, the visibility is - * toggled. + * @since 7.0.0 + * @product highcharts + */ + crookDistance: "70%", + /** + * The distance of the data label from the pie's edge. Negative + * numbers put the data label on top of the pie slices. Can also be + * defined as a percentage of pie's radius. Connectors are only + * shown for data labels outside the pie. * - * @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. + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ + * Data labels on top of the pie * - * @fires Highcharts.Series#event:hide - * @fires Highcharts.Series#event:show + * @type {number|string} + * @since 2.1 + * @product highcharts */ - 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 = - typeof vis === 'undefined' ? !oldVisibility : vis; // #5618 - showOrHide = vis ? 'show' : 'hide'; - // show or hide elements - [ - 'group', - 'dataLabelsGroup', - 'markerGroup', - 'tracker', - 'tt' - ].forEach(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) { - chart.series.forEach(function (otherSeries) { - if (otherSeries.options.stacking && otherSeries.visible) { - otherSeries.isDirty = true; - } - }); - } - // show or hide linked series - series.linkedSeries.forEach(function (otherSeries) { - otherSeries.setVisible(vis, false); - }); - if (ignoreHiddenSeries) { - chart.isDirtyBox = true; - } - fireEvent(series, showOrHide); - if (redraw !== false) { - chart.redraw(); - } - }, + distance: 30, + enabled: true, /** - * Show the series if hidden. + * A + * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) + * for the data label. Available variables are the same as for + * `formatter`. * - * @sample highcharts/members/series-hide/ - * Toggle visibility from a button + * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ + * Add a unit * - * @function Highcharts.Series#show - * @fires Highcharts.Series#event:show + * @type {string} + * @default undefined + * @since 3.0 + * @apioption plotOptions.pie.dataLabels.format */ - show: function () { - this.setVisible(true); - }, + // eslint-disable-next-line valid-jsdoc /** - * Hide the series if visible. If the - * [chart.ignoreHiddenSeries](https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries) - * option is true, the chart is redrawn without this series. - * - * @sample highcharts/members/series-hide/ - * Toggle visibility from a button + * Callback JavaScript function to format the data label. Note that + * if a `format` is defined, the format takes precedence and the + * formatter is ignored. * - * @function Highcharts.Series#hide - * @fires Highcharts.Series#event:hide + * @type {Highcharts.DataLabelsFormatterCallbackFunction} + * @default function () { return this.point.isNull ? void 0 : this.point.name; } */ - hide: function () { - this.setVisible(false); + formatter: function () { + return this.point.isNull ? void 0 : this.point.name; }, /** - * 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. + * Whether to render the connector as a soft arc or a line with + * sharp break. Works only if `connectorShape` equals to + * `fixedOffset`. * - * @sample highcharts/members/series-select/ - * Select a series from a button + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ + * Soft + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ + * Non soft * - * @function Highcharts.Series#select - * - * @param {boolean} [selected] - * True to select the series, false to unselect. If undefined, the selection - * state is toggled. + * @since 2.1.7 + * @product highcharts + */ + softConnector: true, + /** + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow + * Long labels truncated with an ellipsis + * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap + * Long labels are wrapped * - * @fires Highcharts.Series#event:select - * @fires Highcharts.Series#event:unselect + * @type {Highcharts.CSSObject} + * @apioption plotOptions.pie.dataLabels.style + */ + x: 0, + }, + /** + * If the total sum of the pie's values is 0, the series is represented + * as an empty circle . The `fillColor` option defines the color of that + * circle. Use [pie.borderWidth](#plotOptions.pie.borderWidth) to set + * the border thickness. + * + * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/ + * Empty pie series + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @private + */ + fillColor: void 0, + /** + * The end angle of the pie in degrees where 0 is top and 90 is right. + * Defaults to `startAngle` plus 360. + * + * @sample {highcharts} highcharts/demo/pie-semi-circle/ + * Semi-circle donut + * + * @type {number} + * @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. + * + * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ + * True, the hiddden point is ignored + * + * @since 2.3.0 + * @product highcharts + * + * @private + */ + ignoreHiddenPoint: true, + /** + * @ignore-option + * + * @private + */ + inactiveOtherPoints: 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. + * + * @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 + * + * @type {number|string} + * @default 0 + * @since 2.0 + * @product highcharts + * @apioption plotOptions.pie.innerSize + */ + /** + * @ignore-option + * + * @private + */ + legendType: "point", + /** + * @ignore-option + * + * @private + */ + marker: null, + /** + * 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|string} + * @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. + * [slicedOffset](#plotOptions.pie.slicedOffset) is also included in the + * default size calculation. 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%"`. + * + * @sample {highcharts} highcharts/plotoptions/pie-size/ + * Smaller pie + * + * @type {number|string|null} + * @product highcharts + * + * @private + */ + 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. + * + * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ + * One series in the legend, one hidden + * + * @product highcharts + * + * @private + */ + showInLegend: false, + /** + * If a point is sliced, moved out from the center, how many pixels + * should it be moved?. + * + * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ + * 20px offset + * + * @product highcharts + * + * @private + */ + slicedOffset: 10, + /** + * The start angle of the pie slices in degrees where 0 is top and 90 + * right. + * + * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ + * Start from right + * + * @type {number} + * @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 + * + * @private + */ + 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. + * + * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/ + * Black border + * + * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} + * @default #ffffff + * @product highcharts + * + * @private + */ + 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. + * + * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/ + * 3px border + * + * @product highcharts + * + * @private + */ + borderWidth: 1, + /** + * @ignore-options + * @private + */ + lineWidth: void 0, + states: { + /** + * @extends plotOptions.series.states.hover + * @excluding marker, lineWidth, lineWidthPlus + * @product highcharts */ - select: function (selected) { - var series = this; - series.selected = - selected = - this.options.selected = (typeof selected === 'undefined' ? - !series.selected : - selected); - if (series.checkbox) { - series.checkbox.checked = selected; + 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. + * + * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/ + * Brightened by 0.5 + * + * @product highcharts + */ + brightness: 0.1, + }, + }, + }, + /* eslint-disable valid-jsdoc */ + /** + * @lends seriesTypes.pie.prototype + */ + { + isCartesian: false, + requireSorting: false, + directTouch: true, + noSharedTooltip: true, + trackerGroups: ["group", "dataLabelsGroup"], + axisTypes: [], + pointAttribs: BaseSeries.seriesTypes.column.prototype.pointAttribs, + /** + * Animate the pies in + * + * @private + * @function Highcharts.seriesTypes.pie#animate + * + * @param {boolean} [init=false] + */ + animate: function (init) { + var series = this, + points = series.points, + startAngleRad = series.startAngleRad; + if (!init) { + points.forEach(function (point) { + var graphic = point.graphic, + args = point.shapeArgs; + if (graphic && args) { + // start values + graphic.attr({ + // animate from inner radius (#779) + r: pick( + point.startR, + series.center && series.center[3] / 2 + ), + start: startAngleRad, + end: startAngleRad, + }); + // animate + graphic.animate( + { + r: args.r, + start: args.start, + end: args.end, + }, + series.options.animation + ); } - fireEvent(series, selected ? 'select' : 'unselect'); + }); + } + }, + // Define hasData function for non-cartesian series. + // Returns true if the series has points at all. + hasData: function () { + return !!this.processedXData.length; // != 0 + }, + /** + * Recompute total chart sum and update percentages of points. + * + * @private + * @function Highcharts.seriesTypes.pie#updateTotals + * @return {void} + */ + 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 + * + * @private + * @function Highcharts.seriesTypes.pie#generatePoints + * @return {void} + */ + generatePoints: function () { + LineSeries.prototype.generatePoints.call(this); + this.updateTotals(); + }, + /** + * 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. + * @private + */ + getX: function (y, left, point) { + var center = this.center, + // Variable pie has individual radius + radius = this.radii ? this.radii[point.index] : center[2] / 2, + angle, + x; + angle = Math.asin( + clamp((y - center[1]) / (radius + point.labelDistance), -1, 1) + ); + x = + center[0] + + (left ? -1 : 1) * + (Math.cos(angle) * (radius + point.labelDistance)) + + (point.labelDistance > 0 + ? (left ? -1 : 1) * this.options.dataLabels.padding + : 0); + return x; + }, + /** + * Do translation for pie slices + * + * @private + * @function Highcharts.seriesTypes.pie#translate + * @param {Array<number>} [positions] + * @return {void} + */ + 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, + // the x component of the radius vector for a given point + radiusX, + 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(); + } + // Calculate the geometry for each point + for (i = 0; i < len; i++) { + point = points[i]; + // 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, + }; + // Used for distance calculation for specific point. + point.labelDistance = pick( + point.options.dataLabels && point.options.dataLabels.distance, + labelDistance + ); + // Compute point.labelDistance if it's defined as percentage + // of slice radius (#8854) + point.labelDistance = relativeLength( + point.labelDistance, + point.shapeArgs.r + ); + // Saved for later dataLabels distance calculation. + series.maxLabelDistance = Math.max( + series.maxLabelDistance || 0, + point.labelDistance + ); + // 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.labelPosition = { + natural: { + // initial position of the data label - it's utilized for + // finding the final position for the label + x: + positions[0] + + radiusX + + Math.cos(angle) * point.labelDistance, + y: + positions[1] + + radiusY + + Math.sin(angle) * point.labelDistance, + }, + final: { + // used for generating connector path - + // initialized later in drawDataLabels function + // x: undefined, + // y: undefined + }, + // left - pie on the left side of the data label + // right - pie on the right side of the data label + // center - data label overlaps the pie + alignment: + point.labelDistance < 0 + ? "center" + : point.half + ? "right" + : "left", + connectorPosition: { + breakAt: { + x: + positions[0] + + radiusX + + Math.cos(angle) * finalConnectorOffset, + y: + positions[1] + + radiusY + + Math.sin(angle) * finalConnectorOffset, + }, + touchingSliceAt: { + x: positions[0] + radiusX, + y: positions[1] + radiusY, + }, + }, + }; + } + fireEvent(series, "afterTranslate"); + }, + /** + * Called internally to draw auxiliary graph in pie-like series in + * situtation when the default graph is not sufficient enough to present + * the data well. Auxiliary graph is saved in the same object as + * regular graph. + * + * @private + * @function Highcharts.seriesTypes.pie#drawEmpty + */ + drawEmpty: function () { + var centerX, + centerY, + start = this.startAngleRad, + end = this.endAngleRad, + options = this.options; + // Draw auxiliary graph if there're no visible points. + if (this.total === 0 && this.center) { + centerX = this.center[0]; + centerY = this.center[1]; + if (!this.graph) { + this.graph = this.chart.renderer + .arc(centerX, centerY, this.center[1] / 2, 0, start, end) + .addClass("highcharts-empty-series") + .add(this.group); + } + this.graph.attr({ + d: SVGRenderer.prototype.symbols.arc( + centerX, + centerY, + this.center[2] / 2, + 0, + { + start: start, + end: end, + innerR: this.center[3] / 2, + } + ), + }); + if (!this.chart.styledMode) { + this.graph.attr({ + "stroke-width": options.borderWidth, + fill: options.fillColor || "none", + stroke: options.color || "#cccccc", + }); + } + } else if (this.graph) { + // Destroy the graph object. + this.graph = this.graph.destroy(); + } + }, + /** + * Draw the data points + * + * @private + * @function Highcharts.seriesTypes.pie#drawPoints + * @return {void} + */ + redrawPoints: function () { + var series = this, + chart = series.chart, + renderer = chart.renderer, + groupTranslation, + graphic, + pointAttr, + shapeArgs, + shadow = series.options.shadow; + this.drawEmpty(); + if (shadow && !series.shadowGroup && !chart.styledMode) { + series.shadowGroup = renderer + .g("shadow") + .attr({ zIndex: -1 }) + .add(series.group); + } + // draw the slices + series.points.forEach(function (point) { + var animateTo = {}; + graphic = point.graphic; + if (!point.isNull && graphic) { + shapeArgs = point.shapeArgs; + // If the point is sliced, use special translation, else use + // plot area translation + groupTranslation = point.getTranslate(); + if (!chart.styledMode) { + // 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 (!point.delayedRendering) { + graphic.setRadialReference(series.center); + if (!chart.styledMode) { + merge(true, animateTo, pointAttr); + } + merge(true, animateTo, shapeArgs, groupTranslation); + graphic.animate(animateTo); + } else { + graphic + .setRadialReference(series.center) + .attr(shapeArgs) + .attr(groupTranslation); + if (!chart.styledMode) { + graphic + .attr(pointAttr) + .attr({ "stroke-linejoin": "round" }) + .shadow(shadow, shadowGroup); + } + point.delayedRendering = false; + } + graphic.attr({ + visibility: point.visible ? "inherit" : "hidden", + }); + graphic.addClass(point.getClassName()); + } else if (graphic) { + point.graphic = graphic.destroy(); + } + }); + }, + /** + * Slices in pie chart are initialized in DOM, but it's shapes and + * animations are normally run in `drawPoints()`. + * @private + */ + drawPoints: function () { + var renderer = this.chart.renderer; + this.points.forEach(function (point) { + // When updating a series between 2d and 3d or cartesian and + // polar, the shape type changes. + if (point.graphic && point.hasNewShapeType()) { + point.graphic = point.graphic.destroy(); + } + if (!point.graphic) { + point.graphic = renderer[point.shapeType](point.shapeArgs).add( + point.series.group + ); + point.delayedRendering = true; + } + }); + }, + /** + * @private + * @deprecated + * @function Highcharts.seriesTypes.pie#searchPoint + */ + searchPoint: noop, + /** + * Utility for sorting data labels + * + * @private + * @function Highcharts.seriesTypes.pie#sortByAngle + * @param {Array<Highcharts.Point>} points + * @param {number} sign + * @return {void} + */ + sortByAngle: function (points, sign) { + points.sort(function (a, b) { + return ( + typeof a.angle !== "undefined" && (b.angle - a.angle) * sign + ); + }); + }, + /** + * Use a simple symbol from LegendSymbolMixin. + * + * @private + * @borrows Highcharts.LegendSymbolMixin.drawRectangle as Highcharts.seriesTypes.pie#drawLegendSymbol + */ + drawLegendSymbol: LegendSymbolMixin.drawRectangle, + /** + * Use the getCenter method from drawLegendSymbol. + * + * @private + * @borrows Highcharts.CenteredSeriesMixin.getCenter as Highcharts.seriesTypes.pie#getCenter + */ + getCenter: CenteredSeriesMixin.getCenter, + /** + * Pies don't have point marker symbols. + * + * @deprecated + * @private + * @function Highcharts.seriesTypes.pie#getSymbol + */ + getSymbol: noop, + /** + * @private + * @type {null} + */ + drawGraph: null, + }, + /** + * @lends seriesTypes.pie.prototype.pointClass.prototype + */ + { + /** + * Initialize the pie slice + * + * @private + * @function Highcharts.seriesTypes.pie#pointClass#init + * @return {Highcharts.Point} + */ + 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) + * + * @private + * @function Highcharts.seriesTypes.pie#pointClass#isValid + * @return {boolean} + */ + isValid: function () { + return isNumber(this.y) && this.y >= 0; + }, + /** + * Toggle the visibility of the pie slice + * + * @private + * @function Highcharts.seriesTypes.pie#pointClass#setVisible + * @param {boolean} vis + * Whether to show the slice or not. If undefined, the visibility + * is toggled. + * @param {boolean} [redraw=false] + * @return {void} + */ + 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 = + typeof vis === "undefined" ? !point.visible : vis; + // update userOptions.data + series.options.data[series.data.indexOf(point)] = point.options; + // Show and hide associated elements. This is performed + // regardless of redraw or not, because chart.redraw only + // handles full series. + ["graphic", "dataLabel", "connector", "shadowGroup"].forEach( + 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 + * + * @private + * @function Highcharts.seriesTypes.pie#pointClass#slice + * @param {boolean} sliced + * When undefined, the slice state is toggled. + * @param {boolean} redraw + * Whether to redraw the chart. True by default. + * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} + * Animation options. + * @return {void} + */ + 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); + /** + * Pie series only. Whether to display a slice offset from the + * center. + * @name Highcharts.Point#sliced + * @type {boolean|undefined} + */ + // if called without an argument, toggle + point.sliced = + point.options.sliced = + sliced = + defined(sliced) ? sliced : !point.sliced; + // update userOptions.data + series.options.data[series.data.indexOf(point)] = point.options; + if (point.graphic) { + point.graphic.animate(this.getTranslate()); + } + if (point.shadowGroup) { + point.shadowGroup.animate(this.getTranslate()); + } + }, + /** + * @private + * @function Highcharts.seriesTypes.pie#pointClass#getTranslate + * @return {Highcharts.TranslationAttributes} + */ + getTranslate: function () { + return this.sliced + ? this.slicedTranslation + : { + translateX: 0, + translateY: 0, + }; + }, + /** + * @private + * @function Highcharts.seriesTypes.pie#pointClass#haloPath + * @param {number} size + * @return {Highcharts.SVGPathArray} + */ + 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: shapeArgs.r - 1, + start: shapeArgs.start, + end: shapeArgs.end, + } + ); + }, + connectorShapes: { + // only one available before v7.0.0 + fixedOffset: function (labelPosition, connectorPosition, options) { + var breakAt = connectorPosition.breakAt, + touchingSliceAt = connectorPosition.touchingSliceAt, + lineSegment = options.softConnector + ? [ + "C", + // 1st control point (of the curve) + labelPosition.x + + // 5 gives the connector a little horizontal bend + (labelPosition.alignment === "left" ? -5 : 5), + labelPosition.y, + 2 * breakAt.x - touchingSliceAt.x, + 2 * breakAt.y - touchingSliceAt.y, + breakAt.x, + breakAt.y, // + ] + : ["L", breakAt.x, breakAt.y]; + // assemble the path + return [ + ["M", labelPosition.x, labelPosition.y], + lineSegment, + ["L", touchingSliceAt.x, touchingSliceAt.y], + ]; }, - /** - * @private - * @borrows Highcharts.TrackerMixin.drawTrackerGraph as Highcharts.Series#drawTracker - */ - drawTracker: TrackerMixin.drawTrackerGraph + straight: function (labelPosition, connectorPosition) { + var touchingSliceAt = connectorPosition.touchingSliceAt; + // direct line to the slice + return [ + ["M", labelPosition.x, labelPosition.y], + ["L", touchingSliceAt.x, touchingSliceAt.y], + ]; + }, + crookedLine: function (labelPosition, connectorPosition, options) { + var touchingSliceAt = connectorPosition.touchingSliceAt, + series = this.series, + pieCenterX = series.center[0], + plotWidth = series.chart.plotWidth, + plotLeft = series.chart.plotLeft, + alignment = labelPosition.alignment, + radius = this.shapeArgs.r, + crookDistance = relativeLength( + // % to fraction + options.crookDistance, + 1 + ), + crookX = + alignment === "left" + ? pieCenterX + + radius + + (plotWidth + plotLeft - pieCenterX - radius) * + (1 - crookDistance) + : plotLeft + (pieCenterX - radius) * crookDistance, + segmentWithCrook = ["L", crookX, labelPosition.y], + useCrook = true; + // crookedLine formula doesn't make sense if the path overlaps + // the label - use straight line instead in that case + if ( + alignment === "left" + ? crookX > labelPosition.x || crookX < touchingSliceAt.x + : crookX < labelPosition.x || crookX > touchingSliceAt.x + ) { + useCrook = false; + } + // assemble the path + var path = [["M", labelPosition.x, labelPosition.y]]; + if (useCrook) { + path.push(segmentWithCrook); + } + path.push(["L", touchingSliceAt.x, touchingSliceAt.y]); + return path; + }, + }, + /** + * Extendable method for getting the path of the connector between the + * data label and the pie slice. + */ + getConnectorPath: function () { + var labelPosition = this.labelPosition, + options = this.series.options.dataLabels, + connectorShape = options.connectorShape, + predefinedShapes = this.connectorShapes; + // find out whether to use the predefined shape + if (predefinedShapes[connectorShape]) { + connectorShape = predefinedShapes[connectorShape]; + } + return connectorShape.call( + this, + { + // pass simplified label position object for user's convenience + x: labelPosition.final.x, + y: labelPosition.final.y, + alignment: labelPosition.alignment, + }, + labelPosition.connectorPosition, + options + ); + }, + } + /* eslint-enable valid-jsdoc */ + ); + /** + * A `pie` series. If the [type](#series.pie.type) option is not specified, + * it is inherited from [chart.type](#chart.type). + * + * @extends series,plotOptions.pie + * @excluding cropThreshold, dataParser, dataURL, stack, xAxis, yAxis, + * dataSorting, step, boostThreshold, boostBlending + * @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 following snippet shows only a + * few settings, see the complete options set 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" + * }] + * ``` + * + * @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 + * + * @type {Array<number|Array<string,(number|null)>|null|*>} + * @extends series.line.data + * @excluding marker, x + * @product highcharts + * @apioption series.pie.data + */ + /** + * @type {Highcharts.SeriesPieDataLabelsOptionsObject} + * @product highcharts + * @apioption series.pie.data.dataLabels + */ + /** + * 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. + * + * @sample {highcharts} highcharts/point/sliced/ + * One sliced point + * + * @type {boolean} + * @product highcharts + * @apioption series.pie.data.sliced + */ + /** + * @excluding legendItemClick + * @product highcharts + * @apioption series.pie.events + */ + (""); // placeholder for transpiled doclets above + } + ); + _registerModule( + _modules, + "Core/Series/DataLabels.js", + [ + _modules["Core/Animation/AnimationUtilities.js"], + _modules["Core/Globals.js"], + _modules["Core/Series/CartesianSeries.js"], + _modules["Core/Utilities.js"], + ], + function (A, H, CartesianSeries, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var getDeferredAnimation = A.getDeferredAnimation; + var noop = H.noop, + seriesTypes = H.seriesTypes; + var arrayMax = U.arrayMax, + clamp = U.clamp, + defined = U.defined, + extend = U.extend, + fireEvent = U.fireEvent, + format = U.format, + isArray = U.isArray, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick, + relativeLength = U.relativeLength, + splat = U.splat, + stableSort = U.stableSort; + /** + * Callback JavaScript function to format the data label as a string. Note that + * if a `format` is defined, the format takes precedence and the formatter is + * ignored. + * + * @callback Highcharts.DataLabelsFormatterCallbackFunction + * + * @param {Highcharts.PointLabelObject} this + * Data label context to format + * + * @param {Highcharts.DataLabelsOptions} options + * [API options](/highcharts/plotOptions.series.dataLabels) of the data label + * + * @return {number|string|null|undefined} + * Formatted data label text + */ + /** + * Values for handling data labels that flow outside the plot area. + * + * @typedef {"allow"|"justify"} Highcharts.DataLabelsOverflowValue + */ + (""); // detach doclets above + /* eslint-disable valid-jsdoc */ + /** + * 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. + * + * @private + * @function Highcharts.distribute + * @param {Highcharts.DataLabelsBoxArray} boxes + * @param {number} len + * @param {number} [maxDistance] + * @return {void} + */ + H.distribute = function (boxes, len, maxDistance) { + var i, + overlapping = true, + origBoxes = boxes, // Original array will be altered with added .pos + restBoxes = [], // The outranked overshoot + box, + target, + total = 0, + reducedLen = origBoxes.reducedLen || len; + /** + * @private + */ + 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 > reducedLen) { + stableSort(boxes, function (a, b) { + return (b.rank || 0) - (a.rank || 0); + }); + i = 0; + total = 0; + while (total <= reducedLen) { + 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 = boxes.map(function (box) { + return { + size: box.size, + targets: [box.target], + align: pick(box.align, 0.5), + }; }); - - }); - _registerModule(_modules, 'Core/Responsive.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Utilities.js']], function (Chart, U) { - /* * - * - * (c) 2010-2020 Torstein Honsi - * - * License: www.highcharts.com/license - * - * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + 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 = clamp(target - box.size * box.align, 0, 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; + } + } + } + // Add the rest (hidden boxes) + origBoxes.push.apply(origBoxes, restBoxes); + // Now the composite boxes are placed, we need to put the original boxes + // within them + i = 0; + boxes.some(function (box) { + var posInCompositeBox = 0; + if ( + box.targets.some(function () { + origBoxes[i].pos = box.pos + posInCompositeBox; + // If the distance between the position and the target exceeds + // maxDistance, abort the loop and decrease the length in increments + // of 10% to recursively reduce the number of visible boxes by + // rank. Once all boxes are within the maxDistance, we're good. + if ( + typeof maxDistance !== "undefined" && + Math.abs(origBoxes[i].pos - origBoxes[i].target) > maxDistance + ) { + // Reset the positions that are already set + origBoxes.slice(0, i + 1).forEach(function (box) { + delete box.pos; + }); + // Try with a smaller length + origBoxes.reducedLen = + (origBoxes.reducedLen || len) - len * 0.1; + // Recurse + if (origBoxes.reducedLen > len * 0.1) { + H.distribute(origBoxes, len, maxDistance); + } + // Exceeded maxDistance => abort + return true; + } + posInCompositeBox += origBoxes[i].size; + i++; + }) + ) { + // Exceeded maxDistance => abort + return true; + } + }); + // Add the rest (hidden) boxes and sort by target + stableSort(origBoxes, sortByTarget); + }; + /** + * Draw the data labels + * + * @private + * @function Highcharts.Series#drawDataLabels + * @return {void} + * @fires Highcharts.Series#event:afterDrawDataLabels + */ + CartesianSeries.prototype.drawDataLabels = function () { + var series = this, + chart = series.chart, + seriesOptions = series.options, + seriesDlOptions = seriesOptions.dataLabels, + points = series.points, + pointOptions, + hasRendered = series.hasRendered || 0, + dataLabelsGroup, + dataLabelAnim = seriesDlOptions.animation, + animationConfig = seriesDlOptions.defer + ? getDeferredAnimation(chart, dataLabelAnim, series) + : { defer: 0, duration: 0 }, + renderer = chart.renderer; + /** + * Handle the dataLabels.filter option. + * @private + */ + 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; + } + /** + * Merge two objects that can be arrays. If one of them is an array, the + * other is merged into each element. If both are arrays, each element is + * merged by index. If neither are arrays, we use normal merge. + * @private + */ + function mergeArrays(one, two) { + var res = [], + i; + if (isArray(one) && !isArray(two)) { + res = one.map(function (el) { + return merge(el, two); + }); + } else if (isArray(two) && !isArray(one)) { + res = two.map(function (el) { + return merge(one, el); + }); + } else if (!isArray(one) && !isArray(two)) { + res = merge(one, two); + } else { + i = Math.max(one.length, two.length); + while (i--) { + res[i] = merge(one[i], two[i]); + } + } + return res; + } + // Merge in plotOptions.dataLabels for series + seriesDlOptions = mergeArrays( + mergeArrays( + chart.options.plotOptions && + chart.options.plotOptions.series && + chart.options.plotOptions.series.dataLabels, + chart.options.plotOptions && + chart.options.plotOptions[series.type] && + chart.options.plotOptions[series.type].dataLabels + ), + seriesDlOptions + ); + fireEvent(this, "drawDataLabels"); + if ( + isArray(seriesDlOptions) || + seriesDlOptions.enabled || + series._hasPointLabels + ) { + // Create a separate group for the data labels to avoid rotation + dataLabelsGroup = series.plotGroup( + "dataLabelsGroup", + "data-labels", + !hasRendered ? "hidden" : "inherit", // #5133, #10220 + seriesDlOptions.zIndex || 6 + ); + dataLabelsGroup.attr({ opacity: +hasRendered }); // #3300 + if (!hasRendered) { + var group = series.dataLabelsGroup; + if (group) { + if (series.visible) { + // #2597, #3023, #3024 + dataLabelsGroup.show(true); + } + group[seriesOptions.animation ? "animate" : "attr"]( + { opacity: 1 }, + animationConfig + ); + } + } + // Make the labels for each point + points.forEach(function (point) { + // Merge in series options for the point. + // @note dataLabelAttribs (like pointAttribs) would eradicate + // the need for dlOptions, and simplify the section below. + pointOptions = splat( + mergeArrays( + seriesDlOptions, + point.dlOptions || // dlOptions is used in treemaps + (point.options && point.options.dataLabels) + ) + ); + // Handle each individual data label for this point + pointOptions.forEach(function (labelOptions, i) { + // Options for one datalabel + var labelEnabled = + labelOptions.enabled && + // #2282, #4641, #7112, #10049 + (!point.isNull || point.dataLabelOnNull) && + applyFilter(point, labelOptions), + labelConfig, + formatString, + labelText, + style, + rotation, + attr, + dataLabel = point.dataLabels + ? point.dataLabels[i] + : point.dataLabel, + connector = point.connectors + ? point.connectors[i] + : point.connector, + labelDistance = pick( + labelOptions.distance, + point.labelDistance + ), + isNew = !dataLabel; + if (labelEnabled) { + // Create individual options structure that can be extended + // without affecting others + labelConfig = point.getLabelConfig(); + formatString = pick( + labelOptions[point.formatPrefix + "Format"], + labelOptions.format + ); + labelText = defined(formatString) + ? format(formatString, labelConfig, chart) + : ( + labelOptions[point.formatPrefix + "Formatter"] || + labelOptions.formatter + ).call(labelConfig, labelOptions); + style = labelOptions.style; + rotation = labelOptions.rotation; + if (!chart.styledMode) { + // Determine the color + style.color = pick( + labelOptions.color, + style.color, + series.color, + "#000000" + ); + // Get automated contrast color + if (style.color === "contrast") { + point.contrastColor = renderer.getContrast( + point.color || series.color + ); + style.color = + (!defined(labelDistance) && labelOptions.inside) || + labelDistance < 0 || + !!seriesOptions.stacking + ? point.contrastColor + : "#000000"; + } else { + delete point.contrastColor; + } + if (seriesOptions.cursor) { + style.cursor = seriesOptions.cursor; + } + } + attr = { + r: labelOptions.borderRadius || 0, + rotation: rotation, + padding: labelOptions.padding, + zIndex: 1, + }; + if (!chart.styledMode) { + attr.fill = labelOptions.backgroundColor; + attr.stroke = labelOptions.borderColor; + attr["stroke-width"] = labelOptions.borderWidth; + } + // Remove unused attributes (#947) + objectEach(attr, function (val, name) { + if (typeof val === "undefined") { + delete attr[name]; + } + }); + } + // If the point is outside the plot area, destroy it. #678, #820 + if (dataLabel && (!labelEnabled || !defined(labelText))) { + point.dataLabel = point.dataLabel && point.dataLabel.destroy(); + if (point.dataLabels) { + // Remove point.dataLabels if this was the last one + if (point.dataLabels.length === 1) { + delete point.dataLabels; + } else { + delete point.dataLabels[i]; + } + } + if (!i) { + delete point.dataLabel; + } + if (connector) { + point.connector = point.connector.destroy(); + if (point.connectors) { + // Remove point.connectors if this was the last one + if (point.connectors.length === 1) { + delete point.connectors; + } else { + delete point.connectors[i]; + } + } + } + // Individual labels are disabled if the are explicitly disabled + // in the point options, or if they fall outside the plot area. + } else if (labelEnabled && defined(labelText)) { + if (!dataLabel) { + // Create new label element + point.dataLabels = point.dataLabels || []; + dataLabel = point.dataLabels[i] = rotation + ? // Labels don't rotate, use text element + renderer + .text(labelText, 0, -9999, labelOptions.useHTML) + .addClass("highcharts-data-label") + : // We can use label + renderer.label( + labelText, + 0, + -9999, + labelOptions.shape, + null, + null, + labelOptions.useHTML, + null, + "data-label" + ); + // Store for backwards compatibility + if (!i) { + point.dataLabel = dataLabel; + } + dataLabel.addClass( + " highcharts-data-label-color-" + + point.colorIndex + + " " + + (labelOptions.className || "") + // #3398 + (labelOptions.useHTML ? " highcharts-tracker" : "") + ); + } else { + // Use old element and just update text + attr.text = labelText; + } + // Store data label options for later access + dataLabel.options = labelOptions; + dataLabel.attr(attr); + if (!chart.styledMode) { + // Styles must be applied before add in order to read + // text bounding box + dataLabel.css(style).shadow(labelOptions.shadow); + } + if (!dataLabel.added) { + dataLabel.add(dataLabelsGroup); + } + if (labelOptions.textPath && !labelOptions.useHTML) { + dataLabel.setTextPath( + (point.getDataLabelPath && + point.getDataLabelPath(dataLabel)) || + point.graphic, + labelOptions.textPath + ); + if (point.dataLabelPath && !labelOptions.textPath.enabled) { + // clean the DOM + point.dataLabelPath = point.dataLabelPath.destroy(); + } + } + // Now the data label is created and placed at 0,0, so we + // need to align it + series.alignDataLabel( + point, + dataLabel, + labelOptions, + null, + isNew + ); + } + }); + }); + } + fireEvent(this, "afterDrawDataLabels"); + }; + /** + * Align each individual data label. + * + * @private + * @function Highcharts.Series#alignDataLabel + * @param {Highcharts.Point} point + * @param {Highcharts.SVGElement} dataLabel + * @param {Highcharts.DataLabelsOptions} options + * @param {Highcharts.BBoxObject} alignTo + * @param {boolean} [isNew] + * @return {void} + */ + CartesianSeries.prototype.alignDataLabel = function ( + point, + dataLabel, + options, + alignTo, + isNew + ) { + var series = this, + chart = this.chart, + inverted = this.isCartesian && chart.inverted, + enabledDataSorting = this.enabledDataSorting, + plotX = pick(point.dlBox && point.dlBox.centerX, point.plotX, -9999), + plotY = pick(point.plotY, -9999), + bBox = dataLabel.getBBox(), + baseline, + rotation = options.rotation, + normRotation, + negRotation, + align = options.align, + rotCorr, // rotation correction + isInsidePlot = chart.isInsidePlot(plotX, Math.round(plotY), inverted), + // Math.round for rounding errors (#2683), alignTo to allow column + // labels (#2700) + alignAttr, // the final position; + justify = + pick(options.overflow, enabledDataSorting ? "none" : "justify") === + "justify", + visible = + this.visible && + point.visible !== false && + (point.series.forceDL || + (enabledDataSorting && !justify) || + isInsidePlot || + // If the data label is inside the align box, it is enough + // that parts of the align box is inside the plot area + // (#12370) + (options.inside && + alignTo && + chart.isInsidePlot( + plotX, + inverted ? alignTo.x + 1 : alignTo.y + alignTo.height - 1, + inverted + ))), + setStartPos = function (alignOptions) { + if (enabledDataSorting && series.xAxis && !justify) { + series.setDataLabelStartPos( + point, + dataLabel, + isNew, + isInsidePlot, + alignOptions + ); + } + }; + if (visible) { + baseline = chart.renderer.fontMetrics( + chart.styledMode ? void 0 : options.style.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 || 0) + alignTo.width / 2 + rotCorr.x, + y: + alignTo.y + + (options.y || 0) + + { top: 0, middle: 0.5, bottom: 1 }[options.verticalAlign] * + alignTo.height, + }; + setStartPos(alignAttr); // data sorting + dataLabel[isNew ? "attr" : "animate"](alignAttr).attr({ + 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; + } + dataLabel.placed = true; + dataLabel.alignAttr = alignAttr; + } else { + setStartPos(alignTo); // data sorting + dataLabel.align(options, null, alignTo); + alignAttr = dataLabel.alignAttr; + } + // Handle justify or crop + if (justify && alignTo.height >= 0) { + // #8830 + 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, + }); + } + } + // To use alignAttr property in hideOverlappingLabels + if (isNew && enabledDataSorting) { + dataLabel.placed = false; + } + // Show or hide based on the final aligned position + if (!visible && (!enabledDataSorting || justify)) { + dataLabel.hide(true); + dataLabel.placed = false; // don't animate back in + } + }; + /** + * Set starting position for data label sorting animation. + * + * @private + * @function Highcharts.Series#setDataLabelStartPos + * @param {Highcharts.SVGElement} dataLabel + * @param {Highcharts.ColumnPoint} point + * @param {boolean | undefined} [isNew] + * @param {boolean} [isInside] + * @param {Highcharts.AlignObject} [alignOptions] + * + * @return {void} + */ + CartesianSeries.prototype.setDataLabelStartPos = function ( + point, + dataLabel, + isNew, + isInside, + alignOptions + ) { + var chart = this.chart, + inverted = chart.inverted, + xAxis = this.xAxis, + reversed = xAxis.reversed, + labelCenter = inverted ? dataLabel.height / 2 : dataLabel.width / 2, + pointWidth = point.pointWidth, + halfWidth = pointWidth ? pointWidth / 2 : 0, + startXPos, + startYPos; + startXPos = inverted + ? alignOptions.x + : reversed + ? -labelCenter - halfWidth + : xAxis.width - labelCenter + halfWidth; + startYPos = inverted + ? reversed + ? this.yAxis.height - labelCenter + halfWidth + : -labelCenter - halfWidth + : alignOptions.y; + dataLabel.startXPos = startXPos; + dataLabel.startYPos = startYPos; + // We need to handle visibility in case of sorting point outside plot area + if (!isInside) { + dataLabel + .attr({ opacity: 1 }) + .animate({ opacity: 0 }, void 0, dataLabel.hide); + } else if (dataLabel.visibility === "hidden") { + dataLabel.show(); + dataLabel.attr({ opacity: 0 }).animate({ opacity: 1 }); + } + // Save start position on first render, but do not change position + if (!chart.hasRendered) { + return; + } + // Set start position + if (isNew) { + dataLabel.attr({ x: dataLabel.startXPos, y: dataLabel.startYPos }); + } + dataLabel.placed = true; + }; + /** + * If data labels fall partly outside the plot area, align them back in, in a + * way that doesn't hide the point. + * + * @private + * @function Highcharts.Series#justifyDataLabel + * @param {Highcharts.SVGElement} dataLabel + * @param {Highcharts.DataLabelsOptions} options + * @param {Highcharts.SVGAttributes} alignAttr + * @param {Highcharts.BBoxObject} bBox + * @param {Highcharts.BBoxObject} [alignTo] + * @param {boolean} [isNew] + * @return {boolean|undefined} + */ + CartesianSeries.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; + var _a = options.x, + x = _a === void 0 ? 0 : _a, + _b = options.y, + y = _b === void 0 ? 0 : _b; + // Off left + off = alignAttr.x + padding; + if (off < 0) { + if (align === "right" && x >= 0) { + options.align = "left"; + options.inside = true; + } else { + x -= off; + } + justified = true; + } + // Off right + off = alignAttr.x + bBox.width - padding; + if (off > chart.plotWidth) { + if (align === "left" && x <= 0) { + options.align = "right"; + options.inside = true; + } else { + x += chart.plotWidth - off; + } + justified = true; + } + // Off top + off = alignAttr.y + padding; + if (off < 0) { + if (verticalAlign === "bottom" && y >= 0) { + options.verticalAlign = "top"; + options.inside = true; + } else { + y -= off; + } + justified = true; + } + // Off bottom + off = alignAttr.y + bBox.height - padding; + if (off > chart.plotHeight) { + if (verticalAlign === "top" && y <= 0) { + options.verticalAlign = "bottom"; + options.inside = true; + } else { + y += chart.plotHeight - off; + } + justified = true; + } + if (justified) { + options.x = x; + options.y = y; + dataLabel.placed = !isNew; + dataLabel.align(options, void 0, alignTo); + } + return justified; + }; + if (seriesTypes.pie) { + seriesTypes.pie.prototype.dataLabelPositioners = { + // Based on the value computed in Highcharts' distribute algorithm. + radialDistributionY: function (point) { + return point.top + point.distributeBox.pos; + }, + // 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 + // Based on the value computed in Highcharts' distribute algorithm. + radialDistributionX: function (series, point, y, naturalY) { + return series.getX( + y < point.top + 2 || y > point.bottom - 2 ? naturalY : y, + point.half, + point + ); + }, + // dataLabels.distance determines the x position of the label + justify: function (point, radius, seriesCenter) { + return ( + seriesCenter[0] + + (point.half ? -1 : 1) * (radius + point.labelDistance) + ); + }, + // Left edges of the left-half labels touch the left edge of the plot + // area. Right edges of the right-half labels touch the right edge of + // the plot area. + alignToPlotEdges: function (dataLabel, half, plotWidth, plotLeft) { + var dataLabelWidth = dataLabel.getBBox().width; + return half + ? dataLabelWidth + plotLeft + : plotWidth - dataLabelWidth - plotLeft; + }, + // Connectors of each side end in the same x position. Labels are + // aligned to them. Left edge of the widest left-half label touches the + // left edge of the plot area. Right edge of the widest right-half label + // touches the right edge of the plot area. + alignToConnectors: function (points, half, plotWidth, plotLeft) { + var maxDataLabelWidth = 0, + dataLabelWidth; + // find widest data label + points.forEach(function (point) { + dataLabelWidth = point.dataLabel.getBBox().width; + if (dataLabelWidth > maxDataLabelWidth) { + maxDataLabelWidth = dataLabelWidth; + } + }); + return half + ? maxDataLabelWidth + plotLeft + : plotWidth - maxDataLabelWidth - plotLeft; + }, + }; + /** + * Override the base drawDataLabels method by pie specific functionality * - * */ - var find = U.find, - isArray = U.isArray, - isObject = U.isObject, - merge = U.merge, - objectEach = U.objectEach, - pick = U.pick, - splat = U.splat, - uniqueKey = U.uniqueKey; + * @private + * @function Highcharts.seriesTypes.pie#drawDataLabels + * @return {void} + */ + seriesTypes.pie.prototype.drawDataLabels = function () { + var series = this, + data = series.data, + point, + chart = series.chart, + options = series.options.dataLabels || {}, + connectorPadding = options.connectorPadding, + connectorWidth, + plotWidth = chart.plotWidth, + plotHeight = chart.plotHeight, + plotLeft = chart.plotLeft, + maxWidth = Math.round(chart.chartWidth / 3), + connector, + seriesCenter = series.center, + radius = seriesCenter[2] / 2, + centerY = seriesCenter[1], + dataLabel, + dataLabelWidth, + // labelPos, + labelPosition, + labelHeight, + // divide the points into right and left halves for anti collision + halves = [ + [], + [], // left + ], + x, + y, + visibility, + j, + overflow = [0, 0, 0, 0], // top, right, bottom, left + dataLabelPositioners = series.dataLabelPositioners, + pointDataLabelsOptions; + // get out if not enabled + if ( + !series.visible || + (!options.enabled && !series._hasPointLabels) + ) { + return; + } + // Reset all labels that have been shortened + data.forEach(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 + CartesianSeries.prototype.drawDataLabels.apply(series); + data.forEach(function (point) { + if (point.dataLabel) { + if (point.visible) { + // #407, #2510 + // Arrange points for detection collision + halves[point.half].push(point); + // Reset positions (#4905) + point.dataLabel._pos = null; + // Avoid long labels squeezing the pie size too far down + if ( + !defined(options.style.width) && + !defined( + point.options.dataLabels && + point.options.dataLabels.style && + point.options.dataLabels.style.width + ) + ) { + if (point.dataLabel.getBBox().width > maxWidth) { + point.dataLabel.css({ + // Use a fraction of the maxWidth to avoid + // wrapping close to the end of the string. + width: Math.round(maxWidth * 0.7) + "px", + }); + point.dataLabel.shortened = true; + } + } + } else { + point.dataLabel = point.dataLabel.destroy(); + // Workaround to make pies destroy multiple datalabels + // correctly. This logic needs rewriting to support multiple + // datalabels fully. + if (point.dataLabels && point.dataLabels.length === 1) { + delete point.dataLabels; + } + } + } + }); + /* Loop over the points in each half, starting from the top and bottom + * of the pie to detect overlapping labels. + */ + halves.forEach(function (points, i) { + var top, + bottom, + length = points.length, + positions = [], + naturalY, + sideOverflow, + size, + distributionLength; + 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 + ); + points.forEach(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.distributeBox = { + target: + point.labelPosition.natural.y - point.top + size / 2, + size: size, + rank: point.y, + }; + positions.push(point.distributeBox); + } + }); + distributionLength = bottom + size - top; + H.distribute( + positions, + distributionLength, + distributionLength / 5 + ); + } + // Now the used slots are sorted, fill them up sequentially + for (j = 0; j < length; j++) { + point = points[j]; + // labelPos = point.labelPos; + labelPosition = point.labelPosition; + dataLabel = point.dataLabel; + visibility = point.visible === false ? "hidden" : "inherit"; + naturalY = labelPosition.natural.y; + y = naturalY; + if (positions && defined(point.distributeBox)) { + if (typeof point.distributeBox.pos === "undefined") { + visibility = "hidden"; + } else { + labelHeight = point.distributeBox.size; + // Find label's y position + y = dataLabelPositioners.radialDistributionY(point); + } + } + // It is needed to delete point.positionIndex for + // dynamically added points etc. + delete point.positionIndex; // @todo unused + // Find label's x position + // justify is undocumented in the API - preserve support for it + if (options.justify) { + x = dataLabelPositioners.justify(point, radius, seriesCenter); + } else { + switch (options.alignTo) { + case "connectors": + x = dataLabelPositioners.alignToConnectors( + points, + i, + plotWidth, + plotLeft + ); + break; + case "plotEdges": + x = dataLabelPositioners.alignToPlotEdges( + dataLabel, + i, + plotWidth, + plotLeft + ); + break; + default: + x = dataLabelPositioners.radialDistributionX( + series, + point, + y, + naturalY + ); + } + } + // Record the placement and visibility + dataLabel._attr = { + visibility: visibility, + align: labelPosition.alignment, + }; + pointDataLabelsOptions = point.options.dataLabels || {}; + dataLabel._pos = { + x: + x + + pick(pointDataLabelsOptions.x, options.x) + // (#12985) + ({ + left: connectorPadding, + right: -connectorPadding, + }[labelPosition.alignment] || 0), + // 10 is for the baseline (label vs text) + y: + y + + pick(pointDataLabelsOptions.y, options.y) - // (#12985) + 10, + }; + // labelPos.x = x; + // labelPos.y = y; + labelPosition.final.x = x; + labelPosition.final.y = y; + // Detect overflowing data labels + if (pick(options.crop, true)) { + dataLabelWidth = dataLabel.getBBox().width; + sideOverflow = null; + // Overflow left + if ( + x - dataLabelWidth < connectorPadding && + i === 1 // left half + ) { + sideOverflow = Math.round( + dataLabelWidth - x + connectorPadding + ); + overflow[3] = Math.max(sideOverflow, overflow[3]); + // Overflow right + } else if ( + x + dataLabelWidth > plotWidth - connectorPadding && + i === 0 // right half + ) { + 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(); + this.points.forEach(function (point) { + // #8864: every connector can have individual options + pointDataLabelsOptions = merge(options, point.options.dataLabels); + connectorWidth = pick(pointDataLabelsOptions.connectorWidth, 1); + // Draw the connector + if (connectorWidth) { + 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 + + (point.className ? " " + point.className : "") + ) + .add(series.dataLabelsGroup); + if (!chart.styledMode) { + connector.attr({ + "stroke-width": connectorWidth, + stroke: + pointDataLabelsOptions.connectorColor || + point.color || + "#666666", + }); + } + } + connector[isNew ? "attr" : "animate"]({ + d: point.getConnectorPath(), + }); + connector.attr("visibility", visibility); + } else if (connector) { + point.connector = connector.destroy(); + } + } + }); + } + }; /** - * A callback function to gain complete control on when the responsive rule - * applies. + * Extendable method for getting the path of the connector between the data + * label and the pie slice. * - * @callback Highcharts.ResponsiveCallbackFunction + * @private + * @function Highcharts.seriesTypes.pie#connectorPath * - * @param {Highcharts.Chart} this - * Chart context. + * @param {*} labelPos * - * @return {boolean} - * Return `true` if it applies. + * @return {Highcharts.SVGPathArray} */ + // TODO: depracated - remove it + /* + 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 + ]; + }; + */ /** - * Allows setting a set of rules to apply for different screen or chart - * sizes. Each rule specifies additional chart options. + * Perform the final placement of the data labels after we have verified + * that they fall within the plot area. * - * @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 + * @private + * @function Highcharts.seriesTypes.pie#placeDataLabels + * @return {void} */ + seriesTypes.pie.prototype.placeDataLabels = function () { + this.points.forEach(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 = Math.max( + dataLabel.getBBox().width - dataLabel.sideOverflow, + 0 + ); + dataLabel.css({ + width: dataLabel._attr.width + "px", + textOverflow: + (this.options.dataLabels.style || {}).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 }); + } + } + // Clear for update + delete point.distributeBox; + }, this); + }; + seriesTypes.pie.prototype.alignDataLabel = noop; /** - * A set of rules for responsive settings. The rules are executed from - * the top down. + * 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. * - * @sample {highcharts} highcharts/responsive/axis/ - * Axis changes - * @sample {highstock} highcharts/responsive/axis/ - * Axis changes - * @sample {highmaps} highcharts/responsive/axis/ - * Axis changes - * - * @type {Array<*>} - * @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. - * - * @sample {highstock} stock/demo/responsive/ - * Stock chart - * @sample highcharts/responsive/axis/ - * Axis - * @sample highcharts/responsive/legend/ - * Legend - * @sample highcharts/responsive/classname/ - * Class name - * - * @type {Highcharts.Options} - * @since 5.0.0 - * @apioption responsive.rules.chartOptions + * @private + * @function Highcharts.seriesTypes.pie#verifyDataLabelOverflow + * @param {Array<number>} overflow + * @return {boolean} */ + 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 = clamp( + newSize, + minSize, + center[2] - Math.max(overflow[0], overflow[2]) + ); + } else { + // Auto center + newSize = clamp( + newSize, + minSize, + // vertical overflow + center[2] - overflow[0] - overflow[2] + ); + // 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) { /** - * Under which conditions the rule applies. + * Override the basic data label alignment by adjusting for the position of + * the column. * - * @since 5.0.0 - * @apioption responsive.rules.condition + * @private + * @function Highcharts.seriesTypes.column#alignDataLabel + * @param {Highcharts.Point} point + * @param {Highcharts.SVGElement} dataLabel + * @param {Highcharts.DataLabelsOptions} options + * @param {Highcharts.BBoxObject} alignTo + * @param {boolean} [isNew] + * @return {void} */ - /** - * 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, for example the document - * size or other elements. + 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; + } + // If parts of the box overshoots outside the plot area, modify the + // box to center the label inside + overshoot = alignTo.y + alignTo.height - series.yAxis.len; + if (overshoot > 0 && overshoot < alignTo.height) { + 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 + CartesianSeries.prototype.alignDataLabel.call( + this, + point, + dataLabel, + options, + alignTo, + isNew + ); + // If label was justified and we have contrast, set it: + if (options.inside && point.contrastColor) { + dataLabel.css({ + color: point.contrastColor, + }); + } + }; + } + } + ); + _registerModule( + _modules, + "Extensions/OverlappingDataLabels.js", + [_modules["Core/Chart/Chart.js"], _modules["Core/Utilities.js"]], + function (Chart, U) { + /* * + * + * Highcharts module to hide overlapping data labels. + * This module is included in Highcharts. + * + * (c) 2009-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var addEvent = U.addEvent, + fireEvent = U.fireEvent, + isArray = U.isArray, + isNumber = U.isNumber, + objectEach = U.objectEach, + pick = U.pick; + /** + * Internal type + * @private + */ + /* eslint-disable no-invalid-this */ + // 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, "render", function collectAndHide() { + var labels = []; + // Consider external label collectors + (this.labelCollectors || []).forEach(function (collector) { + labels = labels.concat(collector()); + }); + (this.yAxis || []).forEach(function (yAxis) { + if ( + yAxis.stacking && + yAxis.options.stackLabels && + !yAxis.options.stackLabels.allowOverlap + ) { + objectEach(yAxis.stacking.stacks, function (stack) { + objectEach(stack, function (stackItem) { + labels.push(stackItem.label); + }); + }); + } + }); + (this.series || []).forEach(function (series) { + var dlOptions = series.options.dataLabels; + if ( + series.visible && + !(dlOptions.enabled === false && !series._hasPointLabels) + ) { + // #3866 + (series.nodes || series.points).forEach(function (point) { + if (point.visible) { + var dataLabels = isArray(point.dataLabels) + ? point.dataLabels + : point.dataLabel + ? [point.dataLabel] + : []; + dataLabels.forEach(function (label) { + var options = label.options; + label.labelrank = pick( + options.labelrank, + point.labelrank, + point.shapeArgs && point.shapeArgs.height + ); // #4118 + if (!options.allowOverlap) { + labels.push(label); + } + }); + } + }); + } + }); + this.hideOverlappingLabels(labels); + }); + /** + * Hide overlapping labels. Labels are moved and faded in and out on zoom to + * provide a smooth visual imression. + * + * @private + * @function Highcharts.Chart#hideOverlappingLabels + * @param {Array<Highcharts.SVGElement>} labels + * Rendered data labels + * @requires modules/overlapping-datalabels + */ + Chart.prototype.hideOverlappingLabels = function (labels) { + var chart = this, + len = labels.length, + ren = chart.renderer, + label, + i, + j, + label1, + label2, + box1, + box2, + isLabelAffected = false, + isIntersectRect = function (box1, box2) { + return !( + box2.x >= box1.x + box1.width || + box2.x + box2.width <= box1.x || + box2.y >= box1.y + box1.height || + box2.y + box2.height <= box1.y + ); + }, + // Get the box with its position inside the chart, as opposed to getBBox + // that only reports the position relative to the parent. + getAbsoluteBox = function (label) { + var pos, + parent, + bBox, + // Substract the padding if no background or border (#4333) + padding = label.box ? 0 : label.padding || 0, + lineHeightCorrection = 0, + xOffset = 0, + boxWidth, + alignValue; + if (label && (!label.alignAttr || label.placed)) { + pos = label.alignAttr || { + x: label.attr("x"), + y: label.attr("y"), + }; + parent = label.parentGroup; + // Get width and height if pure text nodes (stack labels) + if (!label.width) { + bBox = label.getBBox(); + label.width = bBox.width; + label.height = bBox.height; + // Labels positions are computed from top left corner, so + // we need to substract the text height from text nodes too. + lineHeightCorrection = ren.fontMetrics(null, label.element).h; + } + boxWidth = label.width - 2 * padding; + alignValue = { + left: "0", + center: "0.5", + right: "1", + }[label.alignValue]; + if (alignValue) { + xOffset = +alignValue * boxWidth; + } else if ( + isNumber(label.x) && + Math.round(label.x) !== label.translateX + ) { + xOffset = label.x - label.translateX; + } + return { + x: pos.x + (parent.translateX || 0) + padding - (xOffset || 0), + y: + pos.y + + (parent.translateY || 0) + + padding - + lineHeightCorrection, + width: label.width - 2 * padding, + height: label.height - 2 * padding, + }; + } + }; + for (i = 0; i < len; i++) { + label = labels[i]; + if (label) { + // Mark with initial opacity + label.oldOpacity = label.opacity; + label.newOpacity = 1; + label.absoluteBox = getAbsoluteBox(label); + } + } + // 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]; + box1 = label1 && label1.absoluteBox; + for (j = i + 1; j < len; ++j) { + label2 = labels[j]; + box2 = label2 && label2.absoluteBox; + if ( + box1 && + box2 && + label1 !== label2 && // #6465, polar chart with connectEnds + label1.newOpacity !== 0 && + label2.newOpacity !== 0 + ) { + if (isIntersectRect(box1, box2)) { + (label1.labelrank < label2.labelrank + ? label1 + : label2 + ).newOpacity = 0; + } + } + } + } + // Hide or show + labels.forEach(function (label) { + var complete, newOpacity; + if (label) { + newOpacity = label.newOpacity; + if (label.oldOpacity !== newOpacity) { + // Make sure the label is completely hidden to avoid catching + // clicks (#4362) + if (label.alignAttr && label.placed) { + // data labels + label[newOpacity ? "removeClass" : "addClass"]( + "highcharts-data-label-hidden" + ); + complete = function () { + if (!chart.styledMode) { + label.css({ pointerEvents: newOpacity ? "auto" : "none" }); + } + label.visibility = newOpacity ? "inherit" : "hidden"; + }; + isLabelAffected = true; + // Animate or set the opacity + label.alignAttr.opacity = newOpacity; + label[label.isOld ? "animate" : "attr"]( + label.alignAttr, + null, + complete + ); + fireEvent(chart, "afterHideOverlappingLabel"); + } else { + // other labels, tick labels + label.attr({ + opacity: newOpacity, + }); + } + } + label.isOld = true; + } + }); + if (isLabelAffected) { + fireEvent(chart, "afterHideAllOverlappingLabels"); + } + }; + } + ); + _registerModule( + _modules, + "Core/Interaction.js", + [ + _modules["Core/Series/Series.js"], + _modules["Core/Chart/Chart.js"], + _modules["Core/Globals.js"], + _modules["Core/Legend.js"], + _modules["Series/LineSeries.js"], + _modules["Core/Options.js"], + _modules["Core/Series/Point.js"], + _modules["Core/Utilities.js"], + ], + function (BaseSeries, Chart, H, Legend, LineSeries, O, Point, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var seriesTypes = BaseSeries.seriesTypes; + var hasTouch = H.hasTouch, + svg = H.svg; + var defaultOptions = O.defaultOptions; + var addEvent = U.addEvent, + createElement = U.createElement, + css = U.css, + defined = U.defined, + extend = U.extend, + fireEvent = U.fireEvent, + isArray = U.isArray, + isFunction = U.isFunction, + isNumber = U.isNumber, + isObject = U.isObject, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick; + /** + * @interface Highcharts.PointEventsOptionsObject + */ /** + * 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. + * @name Highcharts.PointEventsOptionsObject#select + * @type {Highcharts.PointSelectCallbackFunction|undefined} + */ /** + * 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. + * @name Highcharts.PointEventsOptionsObject#unselect + * @type {Highcharts.PointUnselectCallbackFunction|undefined} + */ + /** + * Information about the select/unselect event. + * + * @interface Highcharts.PointInteractionEventObject + * @extends global.Event + */ /** + * @name Highcharts.PointInteractionEventObject#accumulate + * @type {boolean} + */ + /** + * Gets fired when the point is selected either programmatically or following a + * click on the point. + * + * @callback Highcharts.PointSelectCallbackFunction + * + * @param {Highcharts.Point} this + * Point where the event occured. + * + * @param {Highcharts.PointInteractionEventObject} event + * Event that occured. + */ + /** + * Fires when the point is unselected either programmatically or following a + * click on the point. + * + * @callback Highcharts.PointUnselectCallbackFunction + * + * @param {Highcharts.Point} this + * Point where the event occured. + * + * @param {Highcharts.PointInteractionEventObject} event + * Event that occured. + */ + (""); // detach doclets above + /* eslint-disable valid-jsdoc */ + /** + * TrackerMixin for points and graphs. + * + * @private + * @mixin Highcharts.TrackerMixin + */ + var TrackerMixin = (H.TrackerMixin = { + /** + * Draw the tracker for a point. * - * @type {Highcharts.ResponsiveCallbackFunction} - * @since 5.0.0 - * @context Highcharts.Chart - * @apioption responsive.rules.condition.callback + * @private + * @function Highcharts.TrackerMixin.drawTrackerPoint + * @param {Highcharts.Series} this + * @fires Highcharts.Series#event:afterDrawTracker */ + 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 (typeof point !== "undefined") { + pointer.isDirectTouch = true; + point.onMouseOver(e); + } + }, + dataLabels; + // Add reference to the point + series.points.forEach(function (point) { + dataLabels = isArray(point.dataLabels) + ? point.dataLabels + : point.dataLabel + ? [point.dataLabel] + : []; + if (point.graphic) { + point.graphic.element.point = point; + } + dataLabels.forEach(function (dataLabel) { + if (dataLabel.div) { + dataLabel.div.point = point; + } else { + dataLabel.element.point = point; + } + }); + }); + // Add the event listeners, we need to do this only once + if (!series._hasTracking) { + series.trackerGroups.forEach(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 (!chart.styledMode && series.options.cursor) { + series[key].css(css).css({ cursor: series.options.cursor }); + } + } + }); + series._hasTracking = true; + } + fireEvent(this, "afterDrawTracker"); + }, /** - * The responsive rule applies if the chart height is less than this. + * 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. * - * @type {number} - * @since 5.0.0 - * @apioption responsive.rules.condition.maxHeight - */ + * @private + * @function Highcharts.TrackerMixin.drawTrackerGraph + * @param {Highcharts.Series} this + * @fires Highcharts.Series#event:afterDrawTracker + */ + 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 (e) { + 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) + ")"; + // Draw the tracker + if (tracker) { + tracker.attr({ d: trackerPath }); + } else if (series.graph) { + // create + series.tracker = renderer + .path(trackerPath) + .attr({ + visibility: series.visible ? "visible" : "hidden", + zIndex: 2, + }) + .addClass( + trackByArea + ? "highcharts-tracker-area" + : "highcharts-tracker-line" + ) + .add(series.group); + if (!chart.styledMode) { + series.tracker.attr({ + "stroke-linecap": "round", + "stroke-linejoin": "round", + stroke: TRACKER_FILL, + fill: trackByArea ? TRACKER_FILL : "none", + "stroke-width": + series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap), + }); + } + // 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. + [series.tracker, series.markerGroup].forEach(function (tracker) { + tracker + .addClass("highcharts-tracker") + .on("mouseover", onMouseOver) + .on("mouseout", function (e) { + pointer.onTrackerMouseOut(e); + }); + if (options.cursor && !chart.styledMode) { + tracker.css({ cursor: options.cursor }); + } + if (hasTouch) { + tracker.on("touchstart", onMouseOver); + } + }); + } + fireEvent(this, "afterDrawTracker"); + }, + }); + /* End TrackerMixin */ + // Add tracking event listener to the series group, so the point graphics + // themselves act as trackers + if (seriesTypes.column) { /** - * The responsive rule applies if the chart width is less than this. - * - * @sample highcharts/responsive/axis/ - * Max width is 500 - * - * @type {number} - * @since 5.0.0 - * @apioption responsive.rules.condition.maxWidth + * @private + * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.column#drawTracker */ + seriesTypes.column.prototype.drawTracker = + TrackerMixin.drawTrackerPoint; + } + if (seriesTypes.pie) { /** - * 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 + * @private + * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.pie#drawTracker */ + seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint; + } + if (seriesTypes.scatter) { /** - * 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 + * @private + * @borrows Highcharts.TrackerMixin.drawTrackerPoint as Highcharts.seriesTypes.scatter#drawTracker */ - /* eslint-disable no-invalid-this, valid-jsdoc */ + seriesTypes.scatter.prototype.drawTracker = + TrackerMixin.drawTrackerPoint; + } + // Extend Legend for item events. + extend(Legend.prototype, { /** - * Update the chart based on the current chart/document size and options for - * responsiveness. - * * @private - * @function Highcharts.Chart#setResponsive - * @param {boolean} [redraw=true] - * @param {boolean} [reset=false] - * Reset by un-applying all rules. Chart.update resets all rules before applying - * updated options. - */ - Chart.prototype.setResponsive = function (redraw, reset) { - var options = this.options.responsive, - ruleIds = [], - currentResponsive = this.currentResponsive, - currentRuleIds, - undoOptions; - if (!reset && options && options.rules) { - options.rules.forEach(function (rule) { - if (typeof rule._id === 'undefined') { - rule._id = uniqueKey(); - } - this.matchResponsiveRule(rule, ruleIds /* , redraw */); - }, this); - } - // Merge matching rules - var mergedOptions = merge.apply(0, - ruleIds.map(function (ruleId) { - return find(options.rules, - function (rule) { - return rule._id === ruleId; - }).chartOptions; - })); - mergedOptions.isResponsiveOptions = true; - // Stringified key for the rules that currently apply. - ruleIds = (ruleIds.toString() || void 0); - 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, true); - } - if (ruleIds) { - // Get undo-options for matching rules - undoOptions = this.currentOptions(mergedOptions); - undoOptions.isResponsiveOptions = true; - this.currentResponsive = { - ruleIds: ruleIds, - mergedOptions: mergedOptions, - undoOptions: undoOptions + * @function Highcharts.Legend#setItemEvents + * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item + * @param {Highcharts.SVGElement} legendItem + * @param {boolean} [useHTML=false] + * @fires Highcharts.Point#event:legendItemClick + * @fires Highcharts.Series#event:legendItemClick + */ + setItemEvents: function (item, legendItem, useHTML) { + var legend = this, + boxWrapper = legend.chart.renderer.boxWrapper, + isPoint = item instanceof Point, + activeClass = + "highcharts-legend-" + (isPoint ? "point" : "series") + "-active", + styledMode = legend.chart.styledMode, + // When `useHTML`, the symbol is rendered in other group, so + // we need to apply events listeners to both places + legendItems = useHTML + ? [legendItem, item.legendSymbol] + : [item.legendGroup]; + // Set the events on the item group, or in case of useHTML, the item + // itself (#1249) + legendItems.forEach(function (element) { + if (element) { + element + .on("mouseover", function () { + if (item.visible) { + legend.allItems.forEach(function (inactiveItem) { + if (item !== inactiveItem) { + inactiveItem.setState("inactive", !isPoint); + } + }); + } + item.setState("hover"); + // A CSS class to dim or hide other than the hovered + // series. + // Works only if hovered series is visible (#10071). + if (item.visible) { + boxWrapper.addClass(activeClass); + } + if (!styledMode) { + legendItem.css(legend.options.itemHoverStyle); + } + }) + .on("mouseout", function () { + if (!legend.chart.styledMode) { + legendItem.css( + merge( + item.visible ? legend.itemStyle : legend.itemHiddenStyle + ) + ); + } + legend.allItems.forEach(function (inactiveItem) { + if (item !== inactiveItem) { + inactiveItem.setState("", !isPoint); + } + }); + // 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(); + } + // Reset inactive state + legend.allItems.forEach(function (inactiveItem) { + if (item !== inactiveItem) { + inactiveItem.setState( + item.visible ? "inactive" : "", + !isPoint + ); + } + }); }; - this.update(mergedOptions, redraw, true); - } - else { - this.currentResponsive = void 0; - } - } - }; - /** - * Handle a single responsiveness rule. - * - * @private - * @function Highcharts.Chart#matchResponsiveRule - * @param {Highcharts.ResponsiveRulesOptions} rule - * @param {Array<string>} matches - */ - 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); + // 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 + ); + } + }); } - }; + }); + }, /** - * 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?). The matching of items in collections - * bears resemblance to the oneToOne matching in Chart.update. Probably we can - * refactor out that matching and reuse it in both functions. - * * @private - * @function Highcharts.Chart#currentOptions - * @param {Highcharts.Options} options - * @return {Highcharts.Options} - */ - Chart.prototype.currentOptions = function (options) { + * @function Highcharts.Legend#createCheckboxForItem + * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item + * @fires Highcharts.Series#event:checkboxClick + */ + createCheckboxForItem: function (item) { + var legend = this; + item.checkbox = createElement( + "input", + { + type: "checkbox", + className: "highcharts-legend-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", + { + checked: target.checked, + item: item, + }, + function () { + item.select(); + } + ); + }); + }, + }); + // Extend the Chart object with interaction + extend( + Chart.prototype, + /** @lends Chart.prototype */ { + /** + * Display the zoom button, so users can reset zoom to the default view + * settings. + * + * @function Highcharts.Chart#showResetZoom + * + * @fires Highcharts.Chart#event:afterShowResetZoom + * @fires Highcharts.Chart#event:beforeShowResetZoom + */ + showResetZoom: function () { var chart = this, - ret = {}; + lang = defaultOptions.lang, + btnOptions = chart.options.chart.resetZoomButton, + theme = btnOptions.theme, + states = theme.states, + alignTo = + btnOptions.relativeTo === "chart" || + btnOptions.relativeTo === "spaceBox" + ? null + : "plotBox"; /** - * Recurse over a set of options and its current values, - * and store the current values in the ret object. + * @private */ - function getCurrent(options, curr, ret, depth) { - var i; - objectEach(options, function (val, key) { - if (!depth && - chart.collectionsWithUpdate.indexOf(key) > -1) { - val = splat(val); - ret[key] = []; - // Iterate over collections like series, xAxis or yAxis and map - // the items by index. - for (i = 0; i < Math.max(val.length, curr[key].length); i++) { - // Item exists in current data (#6347) - if (curr[key][i]) { - // If the item is missing from the new data, we need to - // save the whole config structure. Like when - // responsively updating from a dual axis layout to a - // single axis and back (#13544). - if (val[i] === void 0) { - ret[key][i] = curr[key][i]; - // Otherwise, proceed - } - else { - 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 if (typeof curr[key] === 'undefined') { // #10286 - ret[key] = null; - } - else { - ret[key] = curr[key]; - } + function zoomOut() { + chart.zoomOut(); + } + fireEvent(this, "beforeShowResetZoom", null, function () { + chart.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); + }); + fireEvent(this, "afterShowResetZoom"); + }, + /** + * Zoom the chart out after a user has zoomed in. See also + * [Axis.setExtremes](/class-reference/Highcharts.Axis#setExtremes). + * + * @function Highcharts.Chart#zoomOut + * + * @fires Highcharts.Chart#event:selection + */ + zoomOut: function () { + fireEvent(this, "selection", { resetSelection: true }, this.zoom); + }, + /** + * Zoom into a given portion of the chart given by axis coordinates. + * + * @private + * @function Highcharts.Chart#zoom + * @param {Highcharts.SelectEventObject} event + */ + zoom: function (event) { + var chart = this, + hasZoomed, + pointer = chart.pointer, + displayButton = false, + mouseDownPos = chart.inverted + ? pointer.mouseDownX + : pointer.mouseDownY, + resetZoomButton; + // If zoom is called with no arguments, reset the axes + if (!event || event.resetSelection) { + chart.axes.forEach(function (axis) { + hasZoomed = axis.zoom(); + }); + pointer.initiated = false; // #6804 + } else { + // else, zoom in on all axes + event.xAxis.concat(event.yAxis).forEach(function (axisData) { + var axis = axisData.axis, + axisStartPos = chart.inverted ? axis.left : axis.top, + axisEndPos = chart.inverted + ? axisStartPos + axis.width + : axisStartPos + axis.height, + isXAxis = axis.isXAxis, + isWithinPane = false; + // Check if zoomed area is within the pane (#1289). + // In case of multiple panes only one pane should be zoomed. + if ( + (!isXAxis && + mouseDownPos >= axisStartPos && + mouseDownPos <= axisEndPos) || + isXAxis || + !defined(mouseDownPos) + ) { + isWithinPane = true; + } + // don't zoom more than minRange + if (pointer[isXAxis ? "zoomX" : "zoomY"] && isWithinPane) { + 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 + ) + ); + } + }, + /** + * 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 + * @function Highcharts.Chart#pan + * @param {Highcharts.PointerEventObject} e + * @param {string} panning + */ + pan: function (e, panning) { + var chart = this, + hoverPoints = chart.hoverPoints, + panningOptions, + chartOptions = chart.options.chart, + hasMapNavigation = + chart.options.mapNavigation && + chart.options.mapNavigation.enabled, + doRedraw, + type; + if (typeof panning === "object") { + panningOptions = panning; + } else { + panningOptions = { + enabled: panning, + type: "x", + }; + } + if (chartOptions && chartOptions.panning) { + chartOptions.panning = panningOptions; + } + type = panningOptions.type; + fireEvent(this, "pan", { originalEvent: e }, function () { + // remove active points for shared tooltip + if (hoverPoints) { + hoverPoints.forEach(function (point) { + point.setState(); }); + } + // panning axis mapping + var xy = [1]; // x + if (type === "xy") { + xy = [1, 0]; + } else if (type === "y") { + xy = [0]; + } + xy.forEach(function (isX) { + 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, + pointRangeDirection = + (axis.reversed && !chart.inverted) || + (!axis.reversed && chart.inverted) + ? -1 + : 1, + extremes = axis.getExtremes(), + panMin = + axis.toValue(startPos - mousePos, true) + + halfPointRange * pointRangeDirection, + panMax = + axis.toValue(startPos + axis.len - mousePos, true) - + halfPointRange * pointRangeDirection, + flipped = panMax < panMin, + newMin = flipped ? panMax : panMin, + newMax = flipped ? panMin : panMax, + hasVerticalPanning = axis.hasVerticalPanning(), + paddedMin, + paddedMax, + spill, + panningState = axis.panningState; + // General calculations of panning state. + // This is related to using vertical panning. (#11315). + axis.series.forEach(function (series) { + if ( + hasVerticalPanning && + !isX && + (!panningState || panningState.isDirty) + ) { + var processedData = series.getProcessedData(true), + dataExtremes = series.getExtremes( + processedData.yData, + true + ); + if (!panningState) { + panningState = { + startMin: Number.MAX_VALUE, + startMax: -Number.MAX_VALUE, + }; + } + if ( + isNumber(dataExtremes.dataMin) && + isNumber(dataExtremes.dataMax) + ) { + panningState.startMin = Math.min( + dataExtremes.dataMin, + panningState.startMin + ); + panningState.startMax = Math.max( + dataExtremes.dataMax, + panningState.startMax + ); + } + } + }); + paddedMin = Math.min( + pick( + panningState === null || panningState === void 0 + ? void 0 + : panningState.startMin, + extremes.dataMin + ), + halfPointRange + ? extremes.min + : axis.toValue( + axis.toPixels(extremes.min) - axis.minPixelPadding + ) + ); + paddedMax = Math.max( + pick( + panningState === null || panningState === void 0 + ? void 0 + : panningState.startMax, + extremes.dataMax + ), + halfPointRange + ? extremes.max + : axis.toValue( + axis.toPixels(extremes.max) + axis.minPixelPadding + ) + ); + axis.panningState = panningState; + // It is not necessary to calculate extremes on ordinal axis, + // because they are already calculated, so we don't want to + // override them. + if (!axis.isOrdinal) { + // 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 && + newMin >= paddedMin && + newMax <= paddedMax + ) { + axis.setExtremes(newMin, newMax, false, false, { + trigger: "pan", + }); + if ( + !chart.resetZoomButton && + !hasMapNavigation && + // Show reset zoom button only when both newMin and + // newMax values are between padded axis range. + newMin !== paddedMin && + newMax !== paddedMax && + type.match("y") + ) { + chart.showResetZoom(); + axis.displayBtn = false; + } + doRedraw = true; + } + // set new reference for next run: + chart[mouseDown] = mousePos; + } + }); + 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. + * + * @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 + * + * @function Highcharts.Point#select + * + * @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 + * [allowPointSelect](https://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect) + * is `true`, selected points are accumulated on Control, Shift or Cmd + * clicking the point. + * + * @fires Highcharts.Point#event:select + * @fires Highcharts.Point#event:unselect + */ + select: function (selected, accumulate) { + var point = this, + series = point.series, + chart = series.chart; + selected = pick(selected, !point.selected); + this.selectedStaging = 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 + * + * @name Highcharts.Point#selected + * @type {boolean} + */ + point.selected = point.options.selected = selected; + series.options.data[series.data.indexOf(point)] = point.options; + point.setState(selected && "select"); + // unselect all other points unless Ctrl or Cmd + click + if (!accumulate) { + chart.getSelectedPoints().forEach(function (loopPoint) { + var loopSeries = loopPoint.series; + if (loopPoint.selected && loopPoint !== point) { + loopPoint.selected = loopPoint.options.selected = false; + loopSeries.options.data[ + loopSeries.data.indexOf(loopPoint) + ] = loopPoint.options; + // Programatically selecting a point should restore + // normal state, but when click happened on other + // point, set inactive state to match other points + loopPoint.setState( + chart.hoverPoints && + loopSeries.options.inactiveOtherPoints + ? "inactive" + : "" + ); + loopPoint.firePointEvent("unselect"); + } + }); + } + } + ); + delete this.selectedStaging; + }, + /** + * Runs on mouse over the point. Called internally from mouse and touch + * events. + * + * @function Highcharts.Point#onMouseOver + * + * @param {Highcharts.PointerEventObject} [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. + * + * @function Highcharts.Point#onMouseOut + * @fires Highcharts.Point#event:mouseOut + */ + onMouseOut: function () { + var point = this, + chart = point.series.chart; + point.firePointEvent("mouseOut"); + if (!point.series.options.inactiveOtherPoints) { + (chart.hoverPoints || []).forEach(function (p) { + p.setState(); + }); } - getCurrent(options, this.options, ret, 0); - return ret; - }; - - }); - _registerModule(_modules, 'masters/highcharts.src.js', [_modules['Core/Globals.js']], function (Highcharts) { - - - return Highcharts; - }); - _modules['masters/highcharts.src.js']._modules = _modules; - return _modules['masters/highcharts.src.js']; -})); \ No newline at end of file + 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 + * @function Highcharts.Point#importEvents + */ + importEvents: function () { + if (!this.hasImportedEvents) { + var point = this, + options = merge(point.series.options.point, point.options), + events = options.events; + point.events = events; + objectEach(events, function (event, eventType) { + if (isFunction(event)) { + addEvent(point, eventType, event); + } + }); + this.hasImportedEvents = true; + } + }, + /** + * Set the point's state. + * + * @function Highcharts.Point#setState + * + * @param {Highcharts.PointStateValue|""} [state] + * The new state, can be one of `'hover'`, `'select'`, `'inactive'`, + * or `''` (an empty string), `'normal'` or `undefined` to set to + * normal state. + * @param {boolean} [move] + * State for animation. + * + * @fires Highcharts.Point#event:afterSetState + */ + setState: function (state, move) { + var point = this, + series = point.series, + previousState = point.state, + stateOptions = series.options.states[state || "normal"] || {}, + markerOptions = + defaultOptions.plotOptions[series.type].marker && + series.options.marker, + normalDisabled = markerOptions && markerOptions.enabled === false, + markerStateOptions = + (markerOptions && + markerOptions.states && + markerOptions.states[state || "normal"]) || + {}, + stateDisabled = markerStateOptions.enabled === false, + stateMarkerGraphic = series.stateMarkerGraphic, + pointMarker = point.marker || {}, + chart = series.chart, + halo = series.halo, + haloOptions, + markerAttribs, + pointAttribs, + pointAttribsAnimation, + 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; + } + point.state = state; + if (hasMarkers) { + markerAttribs = series.markerAttribs(point, state); + } + // Apply hover styles to the existing point + if (point.graphic) { + if (previousState) { + point.graphic.removeClass("highcharts-point-" + previousState); + } + if (state) { + point.graphic.addClass("highcharts-point-" + state); + } + if (!chart.styledMode) { + pointAttribs = series.pointAttribs(point, state); + pointAttribsAnimation = pick( + chart.options.chart.animation, + stateOptions.animation + ); + // Some inactive points (e.g. slices in pie) should apply + // oppacity also for it's labels + if ( + series.options.inactiveOtherPoints && + pointAttribs.opacity + ) { + (point.dataLabels || []).forEach(function (label) { + if (label) { + label.animate( + { + opacity: pointAttribs.opacity, + }, + pointAttribsAnimation + ); + } + }); + if (point.connector) { + point.connector.animate( + { + opacity: pointAttribs.opacity, + }, + pointAttribsAnimation + ); + } + } + point.graphic.animate(pointAttribs, pointAttribsAnimation); + } + if (markerAttribs) { + point.graphic.animate( + markerAttribs, + pick( + // Turn off globally: + chart.options.chart.animation, + 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 (markerAttribs) { + 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"]({ + x: markerAttribs.x, + y: markerAttribs.y, + }); + } + } + if (!chart.styledMode && stateMarkerGraphic) { + stateMarkerGraphic.attr(series.pointAttribs(point, state)); + } + } + if (stateMarkerGraphic) { + stateMarkerGraphic[state && point.isInside ? "show" : "hide"](); // #2450 + stateMarkerGraphic.element.point = point; // #4310 + } + } + // Show me your halo + haloOptions = stateOptions.halo; + var markerGraphic = point.graphic || stateMarkerGraphic; + var markerVisibility = + (markerGraphic && markerGraphic.visibility) || "inherit"; + if ( + haloOptions && + haloOptions.size && + markerGraphic && + markerVisibility !== "hidden" && + !point.isCluster + ) { + if (!halo) { + series.halo = halo = chart.renderer + .path() + // #5818, #5903, #6705 + .add(markerGraphic.parentGroup); + } + halo.show()[move ? "animate" : "attr"]({ + d: point.haloPath(haloOptions.size), + }); + halo.attr({ + class: + "highcharts-halo highcharts-color-" + + pick(point.colorIndex, series.colorIndex) + + (point.className ? " " + point.className : ""), + visibility: markerVisibility, + zIndex: -1, // #4929, #8276 + }); + halo.point = point; // #6055 + if (!chart.styledMode) { + halo.attr( + extend( + { + fill: point.color || series.color, + "fill-opacity": haloOptions.opacity, + }, + 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) }, + null, + // Hide after unhovering. The `complete` callback runs in the + // halo's context (#7681). + halo.hide + ); + } + fireEvent(point, "afterSetState"); + }, + /** + * Get the path definition for the halo, which is usually a shadow-like + * circle around the currently hovered point. + * + * @function Highcharts.Point#haloPath + * + * @param {number} size + * The radius of the circular halo. + * + * @return {Highcharts.SVGPathArray} + * 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( + LineSeries.prototype, + /** @lends Highcharts.Series.prototype */ { + /** + * Runs on mouse over the series graphical items. + * + * @function Highcharts.Series#onMouseOver + * @fires Highcharts.Series#event:mouseOver + */ + onMouseOver: function () { + var series = this, + chart = series.chart, + hoverSeries = chart.hoverSeries, + pointer = chart.pointer; + pointer.setHoverChartIndex(); + // 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"); + /** + * Contains the original hovered series. + * + * @name Highcharts.Chart#hoverSeries + * @type {Highcharts.Series|null} + */ + chart.hoverSeries = series; + }, + /** + * Runs on mouse out of the series graphical items. + * + * @function Highcharts.Series#onMouseOut + * + * @fires Highcharts.Series#event:mouseOut + */ + onMouseOut: function () { + // trigger the event only if listeners exist + var series = this, + options = series.options, + chart = series.chart, + tooltip = chart.tooltip, + hoverPoint = chart.hoverPoint; + // #182, set to null before the mouseOut event fires + chart.hoverSeries = null; + // 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(); + } + // Reset all inactive states + chart.series.forEach(function (s) { + s.setState("", true); + }); + }, + /** + * Set the state of the series. Called internally on mouse interaction + * operations, but it can also be called directly to visually + * highlight a series. + * + * @function Highcharts.Series#setState + * + * @param {Highcharts.SeriesStateValue|""} [state] + * The new state, can be either `'hover'`, `'inactive'`, `'select'`, + * or `''` (an empty string), `'normal'` or `undefined` to set to + * normal state. + * @param {boolean} [inherit] + * Determines if state should be inherited by points too. + */ + setState: function (state, inherit) { + var series = this, + options = series.options, + graph = series.graph, + inactiveOtherPoints = options.inactiveOtherPoints, + stateOptions = options.states, + lineWidth = options.lineWidth, + opacity = options.opacity, + // By default a quick animation to hover/inactive, + // slower to un-hover + stateAnimation = pick( + stateOptions[state || "normal"] && + stateOptions[state || "normal"].animation, + series.chart.options.chart.animation + ), + attribs, + i = 0; + state = state || ""; + if (series.state !== state) { + // Toggle class names + [ + series.group, + series.markerGroup, + series.dataLabelsGroup, + ].forEach(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 (!series.chart.styledMode) { + if ( + stateOptions[state] && + stateOptions[state].enabled === false + ) { + return; + } + if (state) { + lineWidth = + stateOptions[state].lineWidth || + lineWidth + (stateOptions[state].lineWidthPlus || 0); // #4035 + opacity = pick(stateOptions[state].opacity, opacity); + } + if (graph && !graph.dashstyle) { + attribs = { + "stroke-width": lineWidth, + }; + // Animate the graph stroke-width. + graph.animate(attribs, stateAnimation); + while (series["zone-graph-" + i]) { + series["zone-graph-" + i].attr(attribs); + i = i + 1; + } + } + // For some types (pie, networkgraph, sankey) opacity is + // resolved on a point level + if (!inactiveOtherPoints) { + [ + series.group, + series.markerGroup, + series.dataLabelsGroup, + series.labelBySeries, + ].forEach(function (group) { + if (group) { + group.animate( + { + opacity: opacity, + }, + stateAnimation + ); + } + }); + } + } + } + // Don't loop over points on a series that doesn't apply inactive state + // to siblings markers (e.g. line, column) + if (inherit && inactiveOtherPoints && series.points) { + series.setAllPointsToState(state); + } + }, + /** + * Set the state for all points in the series. + * + * @function Highcharts.Series#setAllPointsToState + * + * @private + * + * @param {string} [state] + * Can be either `hover` or undefined to set to normal state. + */ + setAllPointsToState: function (state) { + this.points.forEach(function (point) { + if (point.setState) { + point.setState(state); + } + }); + }, + /** + * Show or hide the series. + * + * @function Highcharts.Series#setVisible + * + * @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. + * + * @fires Highcharts.Series#event:hide + * @fires Highcharts.Series#event:show + */ + 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 = + typeof vis === "undefined" ? !oldVisibility : vis; // #5618 + showOrHide = vis ? "show" : "hide"; + // show or hide elements + [ + "group", + "dataLabelsGroup", + "markerGroup", + "tracker", + "tt", + ].forEach(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) { + chart.series.forEach(function (otherSeries) { + if (otherSeries.options.stacking && otherSeries.visible) { + otherSeries.isDirty = true; + } + }); + } + // show or hide linked series + series.linkedSeries.forEach(function (otherSeries) { + otherSeries.setVisible(vis, false); + }); + if (ignoreHiddenSeries) { + chart.isDirtyBox = true; + } + fireEvent(series, showOrHide); + if (redraw !== false) { + chart.redraw(); + } + }, + /** + * Show the series if hidden. + * + * @sample highcharts/members/series-hide/ + * Toggle visibility from a button + * + * @function Highcharts.Series#show + * @fires Highcharts.Series#event:show + */ + show: function () { + this.setVisible(true); + }, + /** + * Hide the series if visible. If the + * [chart.ignoreHiddenSeries](https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries) + * option is true, the chart is redrawn without this series. + * + * @sample highcharts/members/series-hide/ + * Toggle visibility from a button + * + * @function Highcharts.Series#hide + * @fires Highcharts.Series#event:hide + */ + 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. + * + * @sample highcharts/members/series-select/ + * Select a series from a button + * + * @function Highcharts.Series#select + * + * @param {boolean} [selected] + * True to select the series, false to unselect. If undefined, the selection + * state is toggled. + * + * @fires Highcharts.Series#event:select + * @fires Highcharts.Series#event:unselect + */ + select: function (selected) { + var series = this; + series.selected = + selected = + this.options.selected = + typeof selected === "undefined" ? !series.selected : selected; + if (series.checkbox) { + series.checkbox.checked = selected; + } + fireEvent(series, selected ? "select" : "unselect"); + }, + /** + * @private + * @borrows Highcharts.TrackerMixin.drawTrackerGraph as Highcharts.Series#drawTracker + */ + drawTracker: TrackerMixin.drawTrackerGraph, + } + ); + } + ); + _registerModule( + _modules, + "Core/Responsive.js", + [_modules["Core/Chart/Chart.js"], _modules["Core/Utilities.js"]], + function (Chart, U) { + /* * + * + * (c) 2010-2020 Torstein Honsi + * + * License: www.highcharts.com/license + * + * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! + * + * */ + var find = U.find, + isArray = U.isArray, + isObject = U.isObject, + merge = U.merge, + objectEach = U.objectEach, + pick = U.pick, + splat = U.splat, + uniqueKey = U.uniqueKey; + /** + * A callback function to gain complete control on when the responsive rule + * applies. + * + * @callback Highcharts.ResponsiveCallbackFunction + * + * @param {Highcharts.Chart} this + * Chart context. + * + * @return {boolean} + * Return `true` if it applies. + */ + /** + * 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. + * + * @sample {highcharts} highcharts/responsive/axis/ + * Axis changes + * @sample {highstock} highcharts/responsive/axis/ + * Axis changes + * @sample {highmaps} highcharts/responsive/axis/ + * Axis changes + * + * @type {Array<*>} + * @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. + * + * @sample {highstock} stock/demo/responsive/ + * Stock chart + * @sample highcharts/responsive/axis/ + * Axis + * @sample highcharts/responsive/legend/ + * Legend + * @sample highcharts/responsive/classname/ + * Class name + * + * @type {Highcharts.Options} + * @since 5.0.0 + * @apioption responsive.rules.chartOptions + */ + /** + * Under which conditions the rule applies. + * + * @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, for example the document + * size or other elements. + * + * @type {Highcharts.ResponsiveCallbackFunction} + * @since 5.0.0 + * @context Highcharts.Chart + * @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. + * + * @sample highcharts/responsive/axis/ + * Max width is 500 + * + * @type {number} + * @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 + */ + /* eslint-disable no-invalid-this, valid-jsdoc */ + /** + * Update the chart based on the current chart/document size and options for + * responsiveness. + * + * @private + * @function Highcharts.Chart#setResponsive + * @param {boolean} [redraw=true] + * @param {boolean} [reset=false] + * Reset by un-applying all rules. Chart.update resets all rules before applying + * updated options. + */ + Chart.prototype.setResponsive = function (redraw, reset) { + var options = this.options.responsive, + ruleIds = [], + currentResponsive = this.currentResponsive, + currentRuleIds, + undoOptions; + if (!reset && options && options.rules) { + options.rules.forEach(function (rule) { + if (typeof rule._id === "undefined") { + rule._id = uniqueKey(); + } + this.matchResponsiveRule(rule, ruleIds /* , redraw */); + }, this); + } + // Merge matching rules + var mergedOptions = merge.apply( + 0, + ruleIds.map(function (ruleId) { + return find(options.rules, function (rule) { + return rule._id === ruleId; + }).chartOptions; + }) + ); + mergedOptions.isResponsiveOptions = true; + // Stringified key for the rules that currently apply. + ruleIds = ruleIds.toString() || void 0; + 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, true); + } + if (ruleIds) { + // Get undo-options for matching rules + undoOptions = this.currentOptions(mergedOptions); + undoOptions.isResponsiveOptions = true; + this.currentResponsive = { + ruleIds: ruleIds, + mergedOptions: mergedOptions, + undoOptions: undoOptions, + }; + this.update(mergedOptions, redraw, true); + } else { + this.currentResponsive = void 0; + } + } + }; + /** + * Handle a single responsiveness rule. + * + * @private + * @function Highcharts.Chart#matchResponsiveRule + * @param {Highcharts.ResponsiveRulesOptions} rule + * @param {Array<string>} matches + */ + 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?). The matching of items in collections + * bears resemblance to the oneToOne matching in Chart.update. Probably we can + * refactor out that matching and reuse it in both functions. + * + * @private + * @function Highcharts.Chart#currentOptions + * @param {Highcharts.Options} options + * @return {Highcharts.Options} + */ + Chart.prototype.currentOptions = function (options) { + var chart = this, + 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; + objectEach(options, function (val, key) { + if (!depth && chart.collectionsWithUpdate.indexOf(key) > -1) { + val = splat(val); + ret[key] = []; + // Iterate over collections like series, xAxis or yAxis and map + // the items by index. + for (i = 0; i < Math.max(val.length, curr[key].length); i++) { + // Item exists in current data (#6347) + if (curr[key][i]) { + // If the item is missing from the new data, we need to + // save the whole config structure. Like when + // responsively updating from a dual axis layout to a + // single axis and back (#13544). + if (val[i] === void 0) { + ret[key][i] = curr[key][i]; + // Otherwise, proceed + } else { + 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 if (typeof curr[key] === "undefined") { + // #10286 + ret[key] = null; + } else { + ret[key] = curr[key]; + } + }); + } + getCurrent(options, this.options, ret, 0); + return ret; + }; + } + ); + _registerModule( + _modules, + "masters/highcharts.src.js", + [_modules["Core/Globals.js"]], + function (Highcharts) { + return Highcharts; + } + ); + _modules["masters/highcharts.src.js"]._modules = _modules; + return _modules["masters/highcharts.src.js"]; +}); diff --git a/notemyprogress/lang/en/local_notemyprogress.php b/notemyprogress/lang/en/local_notemyprogress.php index 23a84b33ac66017a05d991c771b69f267c44262e..fb06870c7d449aa95a06139b3c3ac401cb847132 100644 --- a/notemyprogress/lang/en/local_notemyprogress.php +++ b/notemyprogress/lang/en/local_notemyprogress.php @@ -715,9 +715,10 @@ $string['fml_settings_bddname_description'] = 'This parameter designates the nam $string['fml_settings_bddname_default'] = 'Empty'; /* Gamification */ -$string['studentRanking1']="¿Quieres aparecer en el ranking ? "; -$string['studentRanking2']="/!\ Te verán todos los inscritos en este curso que hayan aceptado. Aceptar es definitivo: "; -$string['studentRanking3']="Acceptar"; +$string['EnableGame']="Disable/Enable :"; +$string['studentRanking1']="Do you want to appear in the Ranking Board ?"; +$string['studentRanking2']=" /!\ All the registered in this course having accepted you will see. Accepting is final:"; +$string['studentRanking3']="Accept"; $string['gamification_denied'] = 'gamification has been desactivated by your teacher'; $string['tg_colon'] = '{$a->a}: {$a->b}'; $string['tg_section_title'] = 'Course gamification settings'; @@ -751,7 +752,7 @@ $string['tg_section_settings_appearance_title'] = 'Title'; $string['tg_section_settings_levels'] = 'Level settings'; $string['tg_section_settings_levels_quantity'] = 'Levels'; $string['tg_section_settings_levels_base_points'] = 'Base Points'; -$string['tg_section_settings_levels_calculated'] = 'Calculated'; +$string['tg_section_settings_levels_calculated'] = 'Automatic'; $string['tg_section_settings_levels_manually'] = 'Manually'; $string['tg_section_settings_levels_name'] = 'Name'; $string['tg_section_settings_levels_required'] = 'Points required'; @@ -759,6 +760,8 @@ $string['tg_section_settings_rules'] = 'Rules setting'; $string['tg_section_settings_add_rule'] = 'Add new rule'; $string['tg_section_settings_earn'] = 'For this event win:'; $string['tg_section_settings_select_event'] = 'Select an event'; +$string['gm_Chart_Title']='Spreading chart'; +$string['gm_Chart_Y']='Students'; $string['tg_timenow'] = 'Just now'; $string['tg_timeseconds'] = '{$a}s ago'; @@ -778,3 +781,9 @@ $string['fml_api_invalid_transaction'] = 'The request is incorrect'; $string['fml_api_invalid_profile'] = 'You cannot do this action, your profile is incorrect'; $string['fml_api_save_successful'] = 'The data has been successfully saved on the server'; $string['fml_api_cancel_action'] = 'You have canceled the action'; +$string['overview']='Overview : '; +$string['game_point_error']='Points are required'; +$string['game_event_error']='Event is required'; +$string['game_name_error']='Name required'; + + diff --git a/notemyprogress/lang/es/local_notemyprogress.php b/notemyprogress/lang/es/local_notemyprogress.php index 48b7d7c101a7f47f52512271899e26eb4334bca8..2269b8bc9dfe1ef99949cd7f8648d6a82fac248e 100644 --- a/notemyprogress/lang/es/local_notemyprogress.php +++ b/notemyprogress/lang/es/local_notemyprogress.php @@ -715,68 +715,73 @@ $string['fml_settings_bddname_label'] = 'Nombre de la base de datos'; $string['fml_settings_bddname_description'] = 'Este parámetro designa el nombre de la base de datos MongoDB en la que se guardará la información.'; $string['fml_settings_bddname_default'] = 'Vacío'; /* Gamification */ -$string['studentRanking1']="Do you want to appear in the Ranking Board ?"; -$string['studentRanking2']=" /!\ All the registered in this course having accepted you will see. Accepting is final:"; -$string['studentRanking3']="Accept"; -$string['gamification_denied'] = 'gamification has been desactivated by your teacher'; +$string['EnableGame']="Desactivar/Activar :"; +$string['studentRanking1']="¿Quieres aparecer en el ranking ? "; +$string['studentRanking2']="/!\ Te verán todos los inscritos en este curso que hayan aceptado. Aceptar es definitivo: "; +$string['studentRanking3']="Acceptar"; +$string['gamification_denied'] = 'La gamificación ha sido desactivada por su maestro'; $string['tg_colon'] = '{$a->a}: {$a->b}'; -$string['tg_section_title'] = 'Course gamification settings'; -$string['tg_section_help_title'] = 'Course gamification settings'; -$string['tg_section_help_description'] = 'You can give the option of gamification to the students of the course.'; -$string['tg_save_warning_title'] = "Are you sure you want to save the changes?"; -$string['tg_save_warning_content'] = "If you change the settings for the gamification when the course has already started, data may be lost..."; -$string['tg_confirm_ok'] = "Save"; -$string['tg_confirm_cancel'] = "Cancel"; -$string['tg_save'] = "Save configuration"; -$string['tg_section_preview'] = 'Preview'; -$string['tg_section_experience'] = 'Experience points'; -$string['tg_section_information'] = 'Information'; -$string['tg_section_ranking'] = 'Ranking/Report'; -$string['tg_section_settings'] = 'Settings'; - -$string['tg_section_points'] = 'points'; -$string['tg_section_description'] = 'Description'; -$string['tg_section_no_description'] = 'No description'; -$string['tg_section_no_name'] = 'No name'; - -$string['tg_section_preview_next_level'] = 'to the next level'; -$string['tg_section_ranking_ranking_text'] = 'Ranking'; -$string['tg_section_ranking_level'] = 'Level'; -$string['tg_section_ranking_student'] = 'Student'; +$string['tg_section_title'] = 'Configuración de gamificación del curso'; +$string['tg_section_help_title'] = 'Configuración de gamificación del curso'; +$string['tg_section_help_description'] = 'Puede dar la opción de gamificación a los estudiantes del curso.'; +$string['tg_save_warning_title'] = "¿Estás seguro de que quieres guardar los cambios?"; +$string['tg_save_warning_content'] = "Si cambia la configuración de la gamificación cuando el curso ya ha comenzado, los datos pueden perderse ..."; +$string['tg_confirm_ok'] = "Guardar"; +$string['tg_confirm_cancel'] = "Cancelar"; +$string['tg_save'] = "Guardar configuración"; +$string['tg_section_preview'] = 'Avance'; +$string['tg_section_experience'] = 'Puntos de experiencia'; +$string['tg_section_information'] = 'Información'; +$string['tg_section_ranking'] = 'Clasificación/informe'; +$string['tg_section_settings'] = 'Ajustes'; + +$string['tg_section_points'] = 'puntos'; +$string['tg_section_description'] = 'Descripción'; +$string['tg_section_no_description'] = 'Sin descripción'; +$string['tg_section_no_name'] = 'Sin nombre'; + +$string['tg_section_preview_next_level'] = 'al siguiente nivel'; +$string['tg_section_ranking_ranking_text'] = 'Clasificación'; +$string['tg_section_ranking_level'] = 'Nivel'; +$string['tg_section_ranking_student'] = 'Alumna'; $string['tg_section_ranking_total'] = 'Total'; -$string['tg_section_ranking_progress'] = 'Progress %'; - -$string['tg_section_settings_appearance'] = 'Appearance'; -$string['tg_section_settings_appearance_title'] = 'Title'; -$string['tg_section_settings_levels'] = 'Level settings'; -$string['tg_section_settings_levels_quantity'] = 'Levels'; -$string['tg_section_settings_levels_base_points'] = 'Base Points'; -$string['tg_section_settings_levels_calculated'] = 'Calculated'; -$string['tg_section_settings_levels_manually'] = 'Manually'; -$string['tg_section_settings_levels_name'] = 'Name'; -$string['tg_section_settings_levels_required'] = 'Points required'; -$string['tg_section_settings_rules'] = 'Rules setting'; -$string['tg_section_settings_add_rule'] = 'Add new rule'; -$string['tg_section_settings_earn'] = 'For this event win:'; -$string['tg_section_settings_select_event'] = 'Select an event'; - -$string['tg_timenow'] = 'Just now'; -$string['tg_timeseconds'] = '{$a}s ago'; -$string['tg_timeminutes'] = '{$a}m ago'; -$string['tg_timehours'] = '{$a}h ago'; -$string['tg_timedays'] = '{$a}d ago'; -$string['tg_timeweeks'] = '{$a}w ago'; +$string['tg_section_ranking_progress'] = 'Progreso %'; + +$string['tg_section_settings_appearance'] = 'Apariencia'; +$string['tg_section_settings_appearance_title'] = 'Título'; +$string['tg_section_settings_levels'] = 'Configuración de nivel'; +$string['tg_section_settings_levels_quantity'] = 'Nivel'; +$string['tg_section_settings_levels_base_points'] = 'Puntos base'; +$string['tg_section_settings_levels_calculated'] = 'automático'; +$string['tg_section_settings_levels_manually'] = 'A mano'; +$string['tg_section_settings_levels_name'] = 'Nombre'; +$string['tg_section_settings_levels_required'] = 'Se requieren puntos'; +$string['tg_section_settings_rules'] = 'Establecimiento de reglas'; +$string['tg_section_settings_add_rule'] = 'Agregar nueva regla'; +$string['tg_section_settings_earn'] = 'Para este evento, gane:'; +$string['tg_section_settings_select_event'] = 'Seleccione un evento'; +$string['gm_Chart_Title']='Tabla de esparcimiento'; +$string['gm_Chart_Y']='Alumno'; + +$string['tg_timenow'] = 'Justo ahora'; +$string['tg_timeseconds'] = '{$a}s atrás'; +$string['tg_timeminutes'] = '{$a}m atrás'; +$string['tg_timehours'] = '{$a}h atrás'; +$string['tg_timedays'] = '{$a}d atrás'; +$string['tg_timeweeks'] = '{$a}w atrás'; $string['tg_timewithinayearformat'] = '%b %e'; $string['tg_timeolderyearformat'] = '%b %Y'; /* General Errors */ -$string['fml_api_error_network'] = "An error has occurred in communication with the server."; -$string['fml_api_invalid_data'] = 'Incorrect data'; -$string['fml_api_json_decode_error'] = 'Error Decoding sent data'; -$string['fml_api_invalid_token_error'] = 'The token sent in the transaction is invalid, please refresh the page'; -$string['fml_api_invalid_transaction'] = 'The request is incorrect'; -$string['fml_api_invalid_profile'] = 'You cannot do this action, your profile is incorrect'; -$string['fml_api_save_successful'] = 'The data has been successfully saved on the server'; -$string['fml_api_cancel_action'] = 'You have canceled the action'; - - +$string['fml_api_error_network'] = "Se ha producido un error en la comunicación con el servidor."; +$string['fml_api_invalid_data'] = 'Datos Incorrectos'; +$string['fml_api_json_decode_error'] = 'Error en decodificación de datos enviados'; +$string['fml_api_invalid_token_error'] = 'El token enviado en la transacción no es válido, actualice la página'; +$string['fml_api_invalid_transaction'] = 'La solicitud es incorrecta'; +$string['fml_api_invalid_profile'] = 'No puedes hacer esta acción, tu perfil es incorrecto'; +$string['fml_api_save_successful'] = 'Los datos se han guardado correctamente en el servidor'; +$string['fml_api_cancel_action'] = 'Has cancelado la acción'; +$string['overview']='Visión de conjunto : '; +$string['game_point_error']='Los puntos son requeridos'; +$string['game_event_error']='El evento es requerido'; +$string['game_name_error']='Se requiere el nombre'; diff --git a/notemyprogress/lang/fr/local_notemyprogress.php b/notemyprogress/lang/fr/local_notemyprogress.php index 1e23153d967686d30b4bfac08934d68f79628535..e1430c57874519e1e1d4624de6789f5d4fd860c1 100644 --- a/notemyprogress/lang/fr/local_notemyprogress.php +++ b/notemyprogress/lang/fr/local_notemyprogress.php @@ -720,66 +720,73 @@ $string['fml_settings_bddname_label'] = 'Nom de la base de données'; $string['fml_settings_bddname_description'] = 'Ce paramètre désigne le nom de la base de données MongoDB dans laquelle vont être enregistrées les informations.'; /* Gamification */ +$string['EnableGame']="Désactiver/Activer :"; $string['studentRanking1']="Voulez vous apparaitre dans le ranking board ? "; -$string['studentRanking2']= "/!\ tout les inscrits à ce cours ayant accepté vous verrons) accepter est définitif:"; +$string['studentRanking2']= "/!\ Tout les inscrits à ce cours ayant accepté vous verrons. Accepter est définitif:"; $string['studentRanking3']="Accepter"; -$string['gamification_denied'] = 'gamification has been desactivated by your teacher'; +$string['gamification_denied'] = 'La gamification a été désactivée par votre professeur'; $string['tg_colon'] = '{$a->a}: {$a->b}'; -$string['tg_section_title'] = 'Course gamification settings'; -$string['tg_section_help_title'] = 'Course gamification settings'; -$string['tg_section_help_description'] = 'You can give the option of gamification to the students of the course.'; -$string['tg_save_warning_title'] = "Are you sure you want to save the changes?"; -$string['tg_save_warning_content'] = "If you change the settings for the gamification when the course has already started, data may be lost..."; -$string['tg_confirm_ok'] = "Save"; -$string['tg_confirm_cancel'] = "Cancel"; -$string['tg_save'] = "Save configuration"; +$string['tg_section_title'] = 'Gamification :'; +$string['tg_section_help_title'] = 'Paramètres de gamification des cours'; +$string['tg_section_help_description'] = 'Vous pouvez donner la possibilité de gamification aux étudiants du cours.'; +$string['tg_save_warning_title'] = "Êtes-vous sûr de vouloir enregistrer les modifications?"; +$string['tg_save_warning_content'] = "Si vous modifiez les paramètres de la gamification au début du cours, les données peuvent être perdues ..."; +$string['tg_confirm_ok'] = "Sauver"; +$string['tg_confirm_cancel'] = "Annuler"; +$string['tg_save'] = "Enregistrer la configuration"; $string['tg_section_preview'] = 'Point de vue étudiant'; -$string['tg_section_experience'] = 'Experience points'; +$string['tg_section_experience'] = 'Points d\'experience'; $string['tg_section_information'] = 'Information'; -$string['tg_section_ranking'] = 'Ranking/Report'; -$string['tg_section_settings'] = 'Settings'; +$string['tg_section_ranking'] = 'Classement / rapport'; +$string['tg_section_settings'] = 'Réglages'; $string['tg_section_points'] = 'points'; $string['tg_section_description'] = 'Description'; -$string['tg_section_no_description'] = 'No description'; -$string['tg_section_no_name'] = 'No name'; +$string['tg_section_no_description'] = 'Pas de description'; +$string['tg_section_no_name'] = 'Sans nom'; -$string['tg_section_preview_next_level'] = 'to the next level'; -$string['tg_section_ranking_ranking_text'] = 'Ranking'; -$string['tg_section_ranking_level'] = 'Level'; -$string['tg_section_ranking_student'] = 'Student'; +$string['tg_section_preview_next_level'] = 'au niveau supérieur'; +$string['tg_section_ranking_ranking_text'] = 'Classement'; +$string['tg_section_ranking_level'] = 'Niveau'; +$string['tg_section_ranking_student'] = 'Élève'; $string['tg_section_ranking_total'] = 'Total'; -$string['tg_section_ranking_progress'] = 'Progress %'; - -$string['tg_section_settings_appearance'] = 'Appearance'; -$string['tg_section_settings_appearance_title'] = 'Title'; -$string['tg_section_settings_levels'] = 'Level settings'; -$string['tg_section_settings_levels_quantity'] = 'Levels'; -$string['tg_section_settings_levels_base_points'] = 'Base Points'; -$string['tg_section_settings_levels_calculated'] = 'Calculated'; -$string['tg_section_settings_levels_manually'] = 'Manually'; -$string['tg_section_settings_levels_name'] = 'Name'; -$string['tg_section_settings_levels_required'] = 'Points required'; -$string['tg_section_settings_rules'] = 'Rules setting'; -$string['tg_section_settings_add_rule'] = 'Add new rule'; -$string['tg_section_settings_earn'] = 'For this event win:'; -$string['tg_section_settings_select_event'] = 'Select an event'; - -$string['tg_timenow'] = 'Just now'; -$string['tg_timeseconds'] = '{$a}s ago'; -$string['tg_timeminutes'] = '{$a}m ago'; -$string['tg_timehours'] = '{$a}h ago'; -$string['tg_timedays'] = '{$a}d ago'; -$string['tg_timeweeks'] = '{$a}w ago'; +$string['tg_section_ranking_progress'] ='Progréssion %'; + +$string['tg_section_settings_appearance'] = 'Apparence'; +$string['tg_section_settings_appearance_title'] = 'Titre'; +$string['tg_section_settings_levels'] = 'Paramètres de niveau'; +$string['tg_section_settings_levels_quantity'] = 'Niveaux'; +$string['tg_section_settings_levels_base_points'] = 'Points de base'; +$string['tg_section_settings_levels_calculated'] = 'Automatique'; +$string['tg_section_settings_levels_manually'] = 'Manuellement'; +$string['tg_section_settings_levels_name'] = 'Nom'; +$string['tg_section_settings_levels_required'] = 'Points requis'; +$string['tg_section_settings_rules'] = 'Liste des régles'; +$string['tg_section_settings_add_rule'] = 'Nouvelle règle'; +$string['tg_section_settings_earn'] = 'Pour cet événement, victoire:'; +$string['tg_section_settings_select_event'] = 'Sélectionnez un événement'; +$string['gm_Chart_Title']='Graphique de répartion'; +$string['gm_Chart_Y']='Etudiants'; + +$string['tg_timenow'] = 'Juste maintenant'; +$string['tg_timeseconds'] = 'il y a {$a}s '; +$string['tg_timeminutes'] = 'il y a {$a}m '; +$string['tg_timehours'] = 'il y a {$a}h '; +$string['tg_timedays'] = 'il y a {$a}d '; +$string['tg_timeweeks'] = 'il y a {$a}w '; $string['tg_timewithinayearformat'] = '%b %e'; $string['tg_timeolderyearformat'] = '%b %Y'; /* General Errors */ -$string['fml_api_error_network'] = "An error has occurred in communication with the server."; -$string['fml_api_invalid_data'] = 'Incorrect data'; -$string['fml_api_json_decode_error'] = 'Error Decoding sent data'; -$string['fml_api_invalid_token_error'] = 'The token sent in the transaction is invalid, please refresh the page'; -$string['fml_api_invalid_transaction'] = 'The request is incorrect'; -$string['fml_api_invalid_profile'] = 'You cannot do this action, your profile is incorrect'; -$string['fml_api_save_successful'] = 'The data has been successfully saved on the server'; -$string['fml_api_cancel_action'] = 'You have canceled the action'; +$string['fml_api_error_network'] = "Une erreur s'est produite dans la communication avec le serveur."; +$string['fml_api_invalid_data'] = 'Données incorrectes'; +$string['fml_api_json_decode_error'] = 'Décodage d erreur de données envoyées'; +$string['fml_api_invalid_token_error'] = 'Le jeton envoyé dans la transaction n\'est pas valide, veuillez actualiser la page'; +$string['fml_api_invalid_transaction'] = 'La demande est incorrecte'; +$string['fml_api_invalid_profile'] = 'Vous ne pouvez pas faire cette action, votre profil est incorrect'; +$string['fml_api_save_successful'] = 'Les données ont été enregistrées avec succès sur le serveur'; +$string['fml_api_cancel_action'] = 'Vous avez annulé l\'action'; +$string['overview']='Vue d\'ensemble : '; +$string['game_point_error']='Veuillez saisir les points'; +$string['game_event_error']='Veuillez saisir l\'évènement'; +$string['game_name_error']='Le nom est requis'; \ No newline at end of file diff --git a/notemyprogress/locallib.php b/notemyprogress/locallib.php index d329f48140b9d09945b09b2d69c1809992c11c43..5e207f8df4b71f357b68219ecf43d7d58bfbaeea 100644 --- a/notemyprogress/locallib.php +++ b/notemyprogress/locallib.php @@ -185,16 +185,18 @@ function local_notemyprogress_validate_token($token){ local_notemyprogress_ajax_response(null, $message, false, 403); } function local_notemyprogress_save_gamification_config($courseid, $userid, $rules, $levels, $settings, $url,$enable){ - // \local_notemyprogress\logs::create( - // "setgamification", - // "configweeks", - // "saved", - // "weeks_settings", - // $url, - // 4, - // $userid, - // $courseid - // ); + // \local_notemyprogress\logs::create( + // "setgamification", + // "configweeks", + // "saved", + // "weeks_settings", + // $url, + // 4, + // $userid, + // $courseid + // ); + $logs = new \local_notemyprogress\logs($courseid, $userid); + $logs->addLogsNMP("Saved", "section", "CONFIGURATION_GAMIFICATION", "configuration_gamification", $url, "GamificationSaved"); $configLevels = new \local_notemyprogress\configgamification($courseid, $userid); $configLevels->save_levels($levels, $settings, $rules); diff --git a/notemyprogress/pix/rankImage.jpg b/notemyprogress/pix/rankImage.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d76465c8405a7cbe5825e39e070d0709e5d4b493 Binary files /dev/null and b/notemyprogress/pix/rankImage.jpg differ diff --git a/notemyprogress/student_gamification.php b/notemyprogress/student_gamification.php index 78307ad3d2e2f7bdc6a85b7d193a22719659115a..0ebb905fc2ae58fef0871d6bb6171302f16129ea 100644 --- a/notemyprogress/student_gamification.php +++ b/notemyprogress/student_gamification.php @@ -75,15 +75,17 @@ $content = [ 'studentRanking1'=>get_string("studentRanking1","local_notemyprogress"), 'studentRanking2'=>get_string("studentRanking2","local_notemyprogress"), 'studentRanking3'=>get_string("studentRanking3","local_notemyprogress"), + 'overview'=>get_string("overview","local_notemyprogress"), ], 'levels_data' => $configgamification->get_levels_data(), 'indicators' => $es->get_user_info(), - 'ranking' => $es->get_ranking(), + 'ranking' => $es->get_ranking(1), ]; $templatecontext = [ - 'image' => $OUTPUT->image_url('badge', 'local_notemyprogress') + 'image' => $OUTPUT->image_url('badge', 'local_notemyprogress'), + 'rankImage' => $OUTPUT->image_url('rankImage', 'local_notemyprogress') ]; $maxTimeSql = "SELECT MAX(timecreated) as maximum from {notemyprogress_gamification} where courseid=? "; diff --git a/notemyprogress/templates/gamification.mustache b/notemyprogress/templates/gamification.mustache index 0b750072fdf2c086949f029245db1528e26264b1..c40a8ac215b5265c43df7a50cc3a46dba30f9185 100644 --- a/notemyprogress/templates/gamification.mustache +++ b/notemyprogress/templates/gamification.mustache @@ -5,250 +5,253 @@ :exitbutton="strings.exitbutton" :helpcontents="get_help_content()" :token="token" - @open_help_section_modal="openHelpSectionModalEvent" - > + @open_help_section_modal="openHelpSectionModalEvent"> </pageheader> <template> - <!-- Server message --> - <v-row class="justify-center" v-if="notifications.length > 0"> - <v-col cols="8"> - <v-alert v-for="(value, index, key) in notifications" :key="key" - :type="value.type" border="left" class="text-center" dense text dismissible> - <span v-text="value.message"></span> - </v-alert> - </v-col> - </v-row> - <v-row> - <v-card - class="align-center text-center flex" - height="100" - > - <h4>Disable / Enable : <h4> - <v-switch - :input-value="false" - v-model ="swDisableEnable" - inset - color="red darken-3" - class="pt-800 px-800 justify-center" - @Change = disableEnable(swDisableEnable); - ></v-switch> - </v-card> - <v-row> - <v-card - width = '100%'> - <v-row> - <v-col> - <v-tabs v-model="tab" background-color="transparent"> - <v-tab v-text="strings.settings_level"></v-tab> - <v-tab v-text="strings.preview"></v-tab> - <v-tab v-text="strings.settings"></v-tab> - </v-tabs> - <v-tabs-items v-model="tab"> - <v-tab-item> <!-- Level and settings --> - <v-row class="pt-8 px-8 justify-center"> - <v-row> - <v-col cols="6" sm="4" md="3" lg="5"> - <v-text-field - type="number" - readonly - :label="strings.quantity" - v-model="levelsData.length" - append-outer-icon="mdi-plus-circle" - prepend-icon="mdi-minus-circle" - @click:append-outer="addLevel()" - @click:prepend="removeLevel()" - ></v-text-field> - </v-col> - <v-col cols="6" sm="4" md="3" lg="2"> - <v-text-field :label="strings.base_points" v-model="pointsBase" type="number"> - </v-text-field> - </v-col> - <v-col cols="12" sm="4" md="4" lg="3"> - <v-btn-toggle v-model="setPointsOption" tile group> - <v-btn value="calculated" v-text="strings.calculated"></v-btn> - <v-btn value="manually" v-text="strings.manually"></v-btn> - </v-btn-toggle> - </v-col> - </v-row> - </v-row> - <v-row class="pb-5 px-8"> - <v-row v-for="(level, index) in levelsData" :key="level.lvl" cols="12" sm="6" md="4" lg="3"> - <v-card class="pt-5 align-center text-center"> - <v-col :content="level.lvl" bottom offset-x="20" offset-y="20"> - <v-avatar size="100"> - <v-img src="{{{image}}}"></v-img> - </v-avatar> - </v-col> - <v-card-text class="text--primary text-center"> - <v-text-field :label="strings.name" :rules="[v => !!v || 'Name is required']" required v-model="level.nam" type="text"> - </v-text-field> - <v-text-field :label="strings.description" v-model="level.des" type="text"> - </v-text-field> - <v-text-field :disabled="setPointsOption === 'calculated'" :label="strings.levels_required" v-model="calculatePoints(index)" type="number"> - </v-text-field> - </v-card-text> - </v-card> + <v-container pa-8> + <!-- Server message --> + <v-row class="justify-center" v-if="notifications.length > 0"> + <v-col cols="8"> + <v-alert v-for="(value, index, key) in notifications" + :type="value.type" + class="text-center" dense text dismissible> + <span v-text="value.message"></span> + </v-alert> + </v-col> + </v-row> + <v-card + class="align-center text-center flex" + height="100" + elevation ="1"> + <h4 v-text="strings.enable"><h4> + <v-switch + v-model ="swDisableEnable" + inset + color="red darken-3" + class="pt-800 px-800 justify-center" + @Change = disableEnable(swDisableEnable); + ></v-switch> + </v-card> + <v-row> + <v-col> + <v-tabs v-model="tab" background-color="transparent"> + <v-tab v-text="strings.settings_level"></v-tab> + <v-tab v-text="strings.preview"></v-tab> + <v-tab v-text="strings.settings"></v-tab> + </v-tabs> + <v-tabs-items v-model="tab"> + <v-tab-item> <!-- Level and settings --> + <v-row class="pt-8 px-8 justify-center"> + <v-row> + <v-col cols="6" sm="4" md="3" lg="5"> + <v-text-field + type="number" + readonly + :label="strings.quantity" + v-model="levelsData.length" + append-outer-icon="mdi-plus-circle" + prepend-icon="mdi-minus-circle" + @click:append-outer="addLevel()" + @click:prepend="removeLevel()" + ></v-text-field> + </v-col> + <v-col cols="6" sm="4" md="3" lg="2"> + <v-text-field :label="strings.base_points" v-model="pointsBase" type="number"> + </v-text-field> + </v-col> + <v-col cols="12" sm="4" md="4" lg="3"> + <v-btn-toggle v-model="setPointsOption" tile group> + <v-btn value="calculated" v-text="strings.calculated"></v-btn> + <v-btn value="manually" v-text="strings.manually"></v-btn> + </v-btn-toggle> + </v-col> + </v-row> + </v-row> + <v-row class="pb-5 px-8"> + <v-row v-for="(level, index) in levelsData" cols="12" sm="6" md="4" lg="3"> + <v-card class="pt-5 align-center text-center"> + <v-col :content="level.lvl" bottom offset-x="20" offset-y="20"> + <v-avatar size="100"> + <v-img src="{{{image}}}"></v-img> + </v-avatar> + </v-col> + <v-card-text class="text--primary text-center"> + <v-text-field :label="strings.name" :rules="[v => !!v || strings.nameError]" required v-model="level.nam" type="text"> + </v-text-field> + <v-text-field :label="strings.description" v-model="level.des" type="text"> + </v-text-field> + <v-text-field :disabled="setPointsOption === 'calculated'" :label="strings.levels_required" v-model="calculatePoints(index)" type="number"> + </v-text-field> + </v-card-text> + </v-card> + </v-row> + </v-row> + <v-row class="pb-10 justify-center"> + <v-btn @click="save_changes('levelsEdit')" v-text="strings.save" class="white--text" color="#118AB2"></v-btn> + </v-row> + </v-tab-item> + <v-tab-item> <!-- Student preview tab --> + <v-row class="justify-center"> + <v-col cols="12" sm="11" md="7" lg="6" class="d-flex py-10"> + <v-card class="align-center text-center flex"> + <h4 class="py-5" v-text="settings.tit"></h4> + <p v-if="levelsData[0].nam" v-text="levelsData[0].nam"></p> + <v-badge + :content="levelsData[0].lvl" + bottom + offset-x="20" + offset-y="20" + > + <v-avatar size="100"> + <v-img src="{{{image}}}"></v-img> + </v-avatar> + </v-badge> + <v-card-text class="py-5"> + <small class="d-inline" v-text="levelsData[1].points"></small> + <sup>xp</sup> + <small v-text="strings.to_next_level"></small> + <v-progress-linear + color="#118AB2" + :value="0" + height="8" + > + </v-progress-linear> + <p v-if="levelsData[0].des" class="pt-5" v-text="levelsData[0].des"></p> + <h3 :class="[ !levelsData[0].des ? 'pt-5' : 'pt-0']" + v-text="levelsData[0].points +' '+strings.points"></h3> + </v-card-text> + <v-card-actions> + <p class="py-0 px-2" v-text="settings.des"></p> + </v-card-actions> + </v-card> + </v-col> + </v-row> <!-- Change the preview --> + <v-row class="pt-5 px-8 justify-center"> + <v-col cols="12" md="12"> + <h4 v-text="strings.appearance"></h4> + </v-col> + <v-col cols="12" md="12" class=""> + <v-row class="justify-center"> + <v-col cols="12" xs="10" md="6"> + <v-text-field :label="strings.appearance_title" v-model="settings.tit"></v-text-field> + </v-col> + </v-row> + <v-row class="justify-center"> + <v-col cols="12" xs="10" md="6"> + <v-textarea :label="strings.description" auto-grow v-model="settings.des"></v-textarea> + </v-col> + </v-row> + </v-col> + </v-row> + <v-row class="pb-10 justify-center"> + <v-btn @click="save_changes('studentSideEdit')" v-text="strings.save" class="white--text" color="#118AB2"></v-btn> + </v-row> + </v-tab-item> + <v-tab-item> + <v-row class="pt-5 px-8 justify-center justify-sm-space-between "> + <v-col cols="12" sm="6" md="5" lg="5"> + <h4 v-text="strings.rules"></h4> + </v-col> + <v-col cols="6" sm="5" md="4" lg="4"> + <v-btn + block v-text="strings.add_rule" + @click="addRule()" + class="white--text" + color="#118AB2"> + </v-btn> + </v-col> + </v-row> + <v-row class="pt-5 pb-5 px-8"> + <v-col v-for="(rule, index) in rulesData" cols="12" md="12"> + <v-card class="align-center text-center"> + <v-card-text class="text--primary text-center"> + <v-row class="align-center justify-space-around"> + <v-col cols="12" sm="4" md="3"> + <v-text-field + v-model="rule.points" + :label="strings.earn" + :rules="[v => !!v || strings.pointError]" + :suffix="strings.points" + type="number"> + </v-text-field> + </v-col> + <v-col cols="12" sm="6" md="7"> + <v-select + v-model="rule.rule" + attach + :menu-props="{ top: true, offsetY: true }" + :items="events" + item-text='name' + item-value='event' + :label="strings.select_event" + :rules="[v => !!v || strings.eventError]" + required> + </v-select> + </v-col> + <v-col cols="12" sm="2" md="1"> + <v-btn :disabled="rulesData.length < 3" icon @click="removeRule(index)"> + <v-icon>mdi-delete</v-icon> + </v-btn> + </v-col> + </v-row> + </v-card-text> + </v-card> + </v-col> + + </v-row> + <v-row class="pb-10 justify-center"> + <v-btn @click="save_changes('rulesEdit')" v-text="strings.save" class="white--text" color="#118AB2"></v-btn> </v-row> - </v-row> - <v-row class="pb-10 justify-center"> - <v-btn @click="save_changes" v-text="strings.save" class="white--text" color="#118AB2"></v-btn> - </v-row> - </v-tab-item> - <v-tab-item> <!-- Student preview tab --> - <v-row class="justify-center"> - <v-col cols="12" sm="11" md="7" lg="6" class="d-flex py-10"> - <v-card class="align-center text-center flex"> - <h4 class="py-5" v-text="settings.tit"></h4> - <p v-if="levelsData[0].nam" v-text="levelsData[0].nam"></p> + </v-tab-item> + </v-tabs-items> + </v-col> + + <v-col> + <v-row class="py-5 px-8"> + <v-col cols="12" md="12"> + <v-data-table + :headers="table_headers()" + :items="ranking" + item-key="id" + :items-per-page="7" + > + <template v-slot:item.level="{ item }"> <v-badge - :content="levelsData[0].lvl" - bottom - offset-x="20" - offset-y="20" + inline + bordered + :content="item.level" > - <v-avatar size="100"> - <v-img src="{{{image}}}"></v-img> + <v-avatar size="30"> + <img src="{{{image}}}"> </v-avatar> </v-badge> - <v-card-text class="py-5"> - <small class="d-inline" v-text="levelsData[1].points"></small> - <sup>xp</sup> - <small v-text="strings.to_next_level"></small> - <v-progress-linear - color="#118AB2" - :value="0" - height="8" - > - </v-progress-linear> - <p v-if="levelsData[0].des" class="pt-5" v-text="levelsData[0].des"></p> - <h3 :class="[ !levelsData[0].des ? 'pt-5' : 'pt-0']" - v-text="levelsData[0].points +' '+strings.points"></h3> - </v-card-text> - <v-card-actions> - <p class="py-0 px-2" v-text="settings.des"></p> - </v-card-actions> - </v-card> - </v-col> - </v-row> <!-- Change the preview --> - <v-row class="pt-5 px-8 justify-center"> - <v-col cols="12" md="12"> - <h4 v-text="strings.appearance"></h4> - </v-col> - <v-col cols="12" md="12" class=""> - <v-row class="justify-center"> - <v-col cols="12" xs="10" md="6"> - <v-text-field :label="strings.appearance_title" v-model="settings.tit"></v-text-field> - </v-col> - </v-row> - <v-row class="justify-center"> - <v-col cols="12" xs="10" md="6"> - <v-textarea :label="strings.description" auto-grow v-model="settings.des"></v-textarea> - </v-col> - </v-row> - </v-col> - </v-row> - <v-row class="pb-10 justify-center"> - <v-btn @click="save_changes" v-text="strings.save" class="white--text" color="#118AB2"></v-btn> - </v-row> - </v-tab-item> - <v-tab-item> - <v-row class="pt-5 px-8 justify-center justify-sm-space-between "> - <v-col cols="12" sm="6" md="5" lg="5"> - <h4 v-text="strings.rules"></h4> - </v-col> - <v-col cols="6" sm="5" md="4" lg="4"> - <v-btn - block v-text="strings.add_rule" - @click="addRule()" - class="white--text" - color="#118AB2"> - </v-btn> - </v-col> - </v-row> - <v-row class="pt-5 pb-5 px-8"> - <v-col v-for="(rule, index) in rulesData" :key="rule" cols="12" md="12"> - <v-card class="align-center text-center"> - <v-card-text class="text--primary text-center"> - <v-row class="align-center justify-space-around"> - <v-col cols="12" sm="4" md="3"> - <v-text-field - v-model="rule.points" - :label="strings.earn" - :rules="[v => !!v || 'Los puntos son requeridos']" - :suffix="strings.points" - type="number"> - </v-text-field> - </v-col> - <v-col cols="12" sm="6" md="7"> - <v-select - v-model="rule.rule" - attach - :menu-props="{ top: true, offsetY: true }" - :items="events" - item-text='name' - item-value='event' - :label="strings.select_event" - :rules="[v => !!v || 'El evento es requerido']" - required> - </v-select> - </v-col> - <v-col cols="12" sm="2" md="1"> - <v-btn :disabled="rulesData.length < 3" icon @click="removeRule(index)"> - <v-icon>mdi-delete</v-icon> - </v-btn> - </v-col> - </v-row> - </v-card-text> - </v-card> - </v-col> - - </v-row> - <v-row class="pb-10 justify-center"> - <v-btn @click="save_changes" v-text="strings.save" class="white--text" color="#118AB2"></v-btn> - </v-row> - </v-tab-item> - </v-tabs-items> - </v-col> - - <v-col> - <v-row class="py-5 px-8"> - <v-col cols="12" md="12"> - <v-data-table - :headers="table_headers()" - :items="ranking" - item-key="id" - :items-per-page="7" - > - <template v-slot:item.level="{ item }"> - <v-badge - inline - bordered - :content="item.level" - > - <v-avatar size="30"> - <img src="{{{image}}}"> - </v-avatar> - </v-badge> - </template> - <template v-slot:item.progress_percentage="{ item }"> - <v-progress-linear - color="#118AB2" - :value="item.progress_percentage" - height="8" - > - </v-progress-linear> - </template> - <template v-slot:item.total="{ item }"> - <p class="d-inline" v-text="item.total"></p> - <sup>xp</sup> - </template> - </v-data-table> - </v-col> - </v-row> - - </v-col> - </v-row> - </v-card> + </template> + <template v-slot:item.progress_percentage="{ item }"> + <v-progress-linear + color="#118AB2" + :value="item.progress_percentage" + height="8" + > + </v-progress-linear> + </template> + <template v-slot:item.total="{ item }"> + <p class="d-inline" v-text="item.total"></p> + <sup>xp</sup> + </template> + </v-data-table> + </v-col> + </v-row> + <v-row> + <chart + :container="'week_resourcess'" + :chart="chart_spread()" + :lang="strings.chart" + ></chart> + </v-row> + </v-col> + </v-row> + + </v-container> </template> </v-main> </v-app> \ No newline at end of file diff --git a/notemyprogress/templates/student_gamification.mustache b/notemyprogress/templates/student_gamification.mustache index de5c49cac4441119be99981f0cd49b4954c4260b..e464216de1cc646ed7a5d23197bbb90db37e66a2 100644 --- a/notemyprogress/templates/student_gamification.mustache +++ b/notemyprogress/templates/student_gamification.mustache @@ -10,7 +10,7 @@ <template> <v-card> <v-row> - <v-col cols=12 lg="7"> + <v-col cols=12 lg="6"> <v-row class="justify-center"> <v-col cols="12" sm="11" md="7" lg="6" class="d-flex py-10"> <v-card class="align-center text-center flex"> @@ -38,7 +38,7 @@ <p v-if="levelInfo.des" class="pt-5" v-text="levelInfo.des"></p> <h3 :class="[ !levelInfo.des ? 'pt-5' : 'pt-0']" v-text="indicators.points +' '+strings.points"></h3> - <v-row class="justify-center text-left" v-for="action in activity" :key="action"> + <v-row class="justify-center text-left" v-for="action in activity" > <v-col cols="5"> <small v-text="action.description"></small> </v-col> @@ -49,7 +49,6 @@ <small v-text="action.points + ' '+strings.points"></small> </v-col> </v-row> - <small v-text="rankable"></small> </v-card-text> <v-card-actions> <p class="py-0 px-2" v-text="settings.des"></p> @@ -57,8 +56,32 @@ </v-card> </v-col> </v-row> + <v-col> + <h4 v-text=strings.overview></h4> + <v-row class="py-5 px-8"> + <v-col v-for="(level, index) in levelsData" cols="12" sm="6" md="3" class="d-flex"> + <v-card class="pt-5 align-center text-center flex"> + <v-badge + :content="level.lvl" + bottom + offset-x="20" + offset-y="20" + > + <v-avatar size="100"> + <v-img src="{{{image}}}"></v-img> + </v-avatar> + </v-badge> + <v-card-text class="text--primary text-center"> + <h6 v-if="level.nam" v-text="level.nam"></h6> + <p v-if="level.des" class="pt-4 text-left" v-text="level.des"></p> + <p class="mb-0 pb-0" v-text="level.points+' '+strings.points"></p> + </v-card-text> + </v-card> + </v-col> + </v-row> + </v-col> </v-col> - <v-col cols=12 lg="5"> + <v-col cols=12 lg="6"> <v-row v-if="strings.rankable"class="py-5 px-8"> <v-col cols="12" md="12"> <v-data-table @@ -84,7 +107,6 @@ :value="item.progress_percentage" height="8" > - </v-progress-linear> </template> <template v-slot:item.total="{ item }"> @@ -92,20 +114,22 @@ <sup>xp</sup> </template> </v-data-table> + + + </v-col> </v-row> <v-row v-else> <v-card class="mx-auto" - max-width="100%" + max-width="90%" outlined > <v-list-item three-line> <v-list-item-content> - <v-list-item-title class="text-h5 mb-1"> - <v-text v-text=strings.studentRanking1></v-text> + <v-list-item-title class="text-h5 mb-1" v-text=strings.studentRanking1> </v-list-item-title> - <v-list-item-subtitle><v-text v-text=strings.studentRanking2></v-text> </v-list-item-subtitle> + <v-list-item-subtitle v-text=strings.studentRanking2></v-list-item-subtitle> </v-list-item-content> <v-list-item-avatar @@ -113,7 +137,7 @@ size="80" color="red" - ><v-img src="{{{image}}}"></v-img></v-list-item-avatar> + ><v-img src="{{{rankImage}}}"></v-img></v-list-item-avatar> </v-list-item> <v-card-actions> @@ -122,8 +146,8 @@ rounded text @click=activateRanking() + v-text=strings.studentRanking3 > - <v-text v-text=strings.studentRanking3></v-text> </v-btn> </v-card-actions> </v-card> @@ -131,31 +155,7 @@ </v-col> </v-row> </v-card> - <v-card> - <v-col> - <v-row class="py-5 px-8"> - <v-col v-for="(level, index) in levelsData" :key="level.level" cols="12" sm="6" md="3" class="d-flex"> - <v-card class="pt-5 align-center text-center flex"> - <v-badge - :content="level.lvl" - bottom - offset-x="20" - offset-y="20" - > - <v-avatar size="100"> - <v-img src="{{{image}}}"></v-img> - </v-avatar> - </v-badge> - <v-card-text class="text--primary text-center"> - <h6 v-if="level.nam" v-text="level.nam"></h6> - <p v-if="level.des" class="pt-4 text-left" v-text="level.des"></p> - <p class="mb-0 pb-0" v-text="level.points+' '+strings.points"></p> - </v-card-text> - </v-card> - </v-col> - </v-row> - </v-col> - </v-card> + </template> </v-main> </v-app> \ No newline at end of file