chore: version inicial del codigo fuente del LMS Estafeta
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
.highcharts-popup.highcharts-annotation-toolbar {
|
||||
right: 10%;
|
||||
left: auto;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
padding-right: 40px;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-popup.highcharts-annotation-toolbar button {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.highcharts-popup.highcharts-annotation-toolbar > span {
|
||||
display: block;
|
||||
float: left;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.highcharts-popup {
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
display: none;
|
||||
font-size: 0.876em;
|
||||
max-height: 90%;
|
||||
top: 5%;
|
||||
left: 15%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 75%;
|
||||
min-width: 300px;
|
||||
max-width: 600px;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
-webkit-box-shadow: 0px 0px 8px 0px rgba(61, 61, 61, 0.3);
|
||||
-moz-box-shadow: 0px 0px 8px 0px rgba(61, 61, 61, 0.3);
|
||||
box-shadow: 0px 0px 8px 0px rgba(61, 61, 61, 0.3);
|
||||
}
|
||||
|
||||
.highcharts-popup div, .highcharts-popup span {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.highcharts-popup input, .highcharts-popup label, .highcharts-popup select {
|
||||
clear: both;
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.highcharts-popup input {
|
||||
border: 1px solid #e6e6e6;
|
||||
padding: 5px;
|
||||
width: calc(100% - 12px);
|
||||
}
|
||||
|
||||
.highcharts-popup-lhs-col, .highcharts-popup-rhs-col {
|
||||
padding: 20px;
|
||||
height: calc(100% - 84px);
|
||||
/* 44px - padding, 40px - tabs*/
|
||||
float: left;
|
||||
}
|
||||
|
||||
.highcharts-popup-lhs-col.highcharts-popup-lhs-full {
|
||||
width: calc(100% - 52px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: calc(100% - 140px);
|
||||
border: none;
|
||||
padding: 0px 20px 20px 20px;
|
||||
}
|
||||
|
||||
.highcharts-popup-lhs-col.highcharts-popup-lhs-full + .highcharts-popup-bottom-row {
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
.highcharts-popup-lhs-col {
|
||||
clear: both;
|
||||
width: calc(30% - 44px);
|
||||
border-right: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-popup-bottom-row {
|
||||
float: left;
|
||||
padding: 0px 20px;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.highcharts-popup-rhs-col {
|
||||
width: calc(70% - 40px);
|
||||
}
|
||||
|
||||
.highcharts-popup-rhs-col-wrapper {
|
||||
float: left;
|
||||
width: calc(100% - 20px);
|
||||
padding-right: 20px;
|
||||
height: calc(100% - 40px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.highcharts-popup-rhs-col-wrapper h3 {
|
||||
margin-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper ul.highcharts-indicator-list,
|
||||
.highcharts-indicator-list {
|
||||
float: left;
|
||||
color: #666;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.highcharts-indicator-list li {
|
||||
cursor: pointer;
|
||||
padding: 0px 0px 5px 0px;
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.highcharts-indicator-list li:hover {
|
||||
background-color: #e6ebf5;
|
||||
}
|
||||
|
||||
.highcharts-tab-item {
|
||||
background-color: #f7f7f7;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float: left;
|
||||
padding: 10px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.highcharts-tab-item.highcharts-tab-item-active {
|
||||
background-color: #e6ebf5;
|
||||
}
|
||||
|
||||
.highcharts-tab-item-content {
|
||||
display: none;
|
||||
float: left;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-tab-item-show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.highcharts-popup-close {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
top: 0%;
|
||||
right: 0%;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.highcharts-popup-close:hover,
|
||||
.highcharts-popup button:hover,
|
||||
.highcharts-popup button.highcharts-annotation-edit-button:hover,
|
||||
.highcharts-popup button.highcharts-annotation-remove-button:hover {
|
||||
background-color: #e6ebf5;
|
||||
}
|
||||
|
||||
.highcharts-popup button {
|
||||
float: right;
|
||||
border: none;
|
||||
background: #f7f7f7;
|
||||
color: #666;
|
||||
margin-left: 5px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.highcharts-popup button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.highcharts-tab-disabled {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* annotation edit small popup */
|
||||
.highcharts-popup button.highcharts-annotation-edit-button,
|
||||
.highcharts-popup button.highcharts-annotation-remove-button {
|
||||
width: 20px;
|
||||
height: 40px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.highcharts-popup button.highcharts-annotation-edit-button {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
|
||||
.highcharts-popup button.highcharts-annotation-remove-button {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
|
||||
.highcharts-popup .highcharts-annotation-title {
|
||||
display: block;
|
||||
float: left;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.highcharts-popup .highcharts-popup-main-title {
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
margin: 0px 0px 20px 0px;
|
||||
padding: 8px 0px 6px 20px;
|
||||
}
|
||||
|
||||
.highcharts-indicator-title {
|
||||
float: left;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
// Colors for buttons.
|
||||
$button-background-color: #f7f7f7;
|
||||
$button-hover-color: #e6ebf5;
|
||||
|
||||
|
||||
.highcharts-popup.highcharts-annotation-toolbar {
|
||||
right: 10%;
|
||||
left: auto;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
padding-right: 40px;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-popup.highcharts-annotation-toolbar button {
|
||||
margin-top:0px;
|
||||
}
|
||||
|
||||
.highcharts-popup.highcharts-annotation-toolbar > span {
|
||||
display:block;
|
||||
float:left;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
|
||||
.highcharts-popup {
|
||||
background-color: #fff;
|
||||
color: #666;
|
||||
display: none;
|
||||
font-size: 0.876em;
|
||||
max-height: 90%;
|
||||
top: 5%;
|
||||
left: 15%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 75%;
|
||||
min-width: 300px;
|
||||
max-width: 600px;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
-webkit-box-shadow: 0px 0px 8px 0px rgba(61,61,61,0.3);
|
||||
-moz-box-shadow: 0px 0px 8px 0px rgba(61,61,61,0.3);
|
||||
box-shadow: 0px 0px 8px 0px rgba(61,61,61,0.3);
|
||||
}
|
||||
|
||||
.highcharts-popup div, .highcharts-popup span {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.highcharts-popup input, .highcharts-popup label, .highcharts-popup select {
|
||||
clear: both;
|
||||
float: left;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.highcharts-popup input {
|
||||
border: 1px solid #e6e6e6;
|
||||
padding: 5px;
|
||||
width: calc(100% - 12px);
|
||||
}
|
||||
|
||||
.highcharts-popup-lhs-col, .highcharts-popup-rhs-col {
|
||||
padding: 20px;
|
||||
height: calc(100% - 84px); /* 44px - padding, 40px - tabs*/
|
||||
float: left;
|
||||
}
|
||||
|
||||
.highcharts-popup-lhs-col.highcharts-popup-lhs-full {
|
||||
width: calc(100% - 52px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: calc(100% - 140px);
|
||||
border: none;
|
||||
padding: 0px 20px 20px 20px;
|
||||
}
|
||||
|
||||
.highcharts-popup-lhs-col.highcharts-popup-lhs-full + .highcharts-popup-bottom-row {
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
.highcharts-popup-lhs-col {
|
||||
clear: both;
|
||||
width: calc(30% - 44px);
|
||||
border-right: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-popup-bottom-row {
|
||||
float: left;
|
||||
padding: 0px 20px;
|
||||
width: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.highcharts-popup-rhs-col {
|
||||
width: calc(70% - 40px);
|
||||
}
|
||||
|
||||
.highcharts-popup-rhs-col-wrapper {
|
||||
float: left;
|
||||
width: calc(100% - 20px);
|
||||
padding-right: 20px;
|
||||
height: calc(100% - 40px);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.highcharts-popup-rhs-col-wrapper h3 {
|
||||
margin-top:0px;
|
||||
padding-bottom:0px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper ul.highcharts-indicator-list,
|
||||
.highcharts-indicator-list {
|
||||
float: left;
|
||||
color: #666;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.highcharts-indicator-list li {
|
||||
cursor: pointer;
|
||||
padding: 0px 0px 5px 0px;
|
||||
margin: 0px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.highcharts-indicator-list li:hover {
|
||||
background-color: $button-hover-color;
|
||||
}
|
||||
|
||||
.highcharts-tab-item {
|
||||
background-color: $button-background-color;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
float:left;
|
||||
padding: 10px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.highcharts-tab-item.highcharts-tab-item-active {
|
||||
background-color: $button-hover-color;
|
||||
}
|
||||
|
||||
.highcharts-tab-item-content {
|
||||
display: none;
|
||||
float: left;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border-top: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-tab-item-show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.highcharts-popup-close {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
top: 0%;
|
||||
right: 0%;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.highcharts-popup-close:hover,
|
||||
.highcharts-popup button:hover,
|
||||
.highcharts-popup button.highcharts-annotation-edit-button:hover,
|
||||
.highcharts-popup button.highcharts-annotation-remove-button:hover {
|
||||
background-color: $button-hover-color;
|
||||
}
|
||||
|
||||
.highcharts-popup button {
|
||||
float: right;
|
||||
border: none;
|
||||
background: $button-background-color;
|
||||
color: #666;
|
||||
margin-left:5px;
|
||||
margin-top:12px;
|
||||
}
|
||||
.highcharts-popup button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.highcharts-tab-disabled {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
/* annotation edit small popup */
|
||||
.highcharts-popup button.highcharts-annotation-edit-button,
|
||||
.highcharts-popup button.highcharts-annotation-remove-button {
|
||||
width: 20px;
|
||||
height: 40px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.highcharts-popup button.highcharts-annotation-edit-button {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
|
||||
.highcharts-popup button.highcharts-annotation-remove-button {
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
text-indent: -9999px;
|
||||
}
|
||||
|
||||
.highcharts-popup .highcharts-annotation-title {
|
||||
display: block;
|
||||
float: left;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.highcharts-popup .highcharts-popup-main-title {
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
margin: 0px 0px 20px 0px;
|
||||
padding: 8px 0px 6px 20px;
|
||||
}
|
||||
|
||||
.highcharts-indicator-title {
|
||||
float: left;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
@@ -0,0 +1,950 @@
|
||||
/**
|
||||
* @license Highcharts
|
||||
*
|
||||
* (c) 2009-2016 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*/
|
||||
.highcharts-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
line-height: normal;
|
||||
z-index: 0;
|
||||
/* #1072 */
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.highcharts-root {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.highcharts-root text {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-emphasized {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.highcharts-anchor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-background {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-plot-border, .highcharts-plot-background {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-label-box {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-button-box {
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.highcharts-tracker-line {
|
||||
stroke-linejoin: round;
|
||||
stroke: rgba(192, 192, 192, 0.0001);
|
||||
stroke-width: 22;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-tracker-area {
|
||||
fill: rgba(192, 192, 192, 0.0001);
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
.highcharts-title {
|
||||
fill: #333333;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.highcharts-subtitle {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
/* Axes */
|
||||
.highcharts-axis-line {
|
||||
fill: none;
|
||||
stroke: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-yaxis .highcharts-axis-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-axis-title {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-axis-labels {
|
||||
fill: #666666;
|
||||
cursor: default;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.highcharts-grid-line {
|
||||
fill: none;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-xaxis-grid .highcharts-grid-line {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.highcharts-tick {
|
||||
stroke: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-yaxis .highcharts-tick {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-minor-grid-line {
|
||||
stroke: #f2f2f2;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-thin {
|
||||
stroke-width: 1px;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-category {
|
||||
stroke: #ccd6eb;
|
||||
stroke-opacity: 0.25;
|
||||
}
|
||||
|
||||
/* Credits */
|
||||
.highcharts-credits {
|
||||
cursor: pointer;
|
||||
fill: #999999;
|
||||
font-size: 0.7em;
|
||||
transition: fill 250ms, font-size 250ms;
|
||||
}
|
||||
|
||||
.highcharts-credits:hover {
|
||||
fill: black;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
.highcharts-tooltip {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
transition: stroke 150ms;
|
||||
}
|
||||
|
||||
.highcharts-tooltip text {
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
.highcharts-tooltip .highcharts-header {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.highcharts-tooltip-box {
|
||||
stroke-width: 1px;
|
||||
fill: #f7f7f7;
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
|
||||
.highcharts-tooltip-box .highcharts-label-box {
|
||||
fill: #f7f7f7;
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
|
||||
div.highcharts-tooltip {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.highcharts-selection-marker {
|
||||
fill: #335cad;
|
||||
fill-opacity: 0.25;
|
||||
}
|
||||
|
||||
.highcharts-graph {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.highcharts-empty-series {
|
||||
stroke-width: 1px;
|
||||
fill: none;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-state-hover .highcharts-graph {
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.highcharts-point-inactive {
|
||||
opacity: 0.2;
|
||||
transition: opacity 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-series-inactive {
|
||||
opacity: 0.2;
|
||||
transition: opacity 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-state-hover path {
|
||||
transition: stroke-width 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-state-normal path {
|
||||
transition: stroke-width 250ms;
|
||||
/* slow out */
|
||||
}
|
||||
|
||||
/* Legend hover affects points and series */
|
||||
g.highcharts-series,
|
||||
.highcharts-point,
|
||||
.highcharts-markers,
|
||||
.highcharts-data-labels {
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
|
||||
.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
|
||||
.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
|
||||
.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/* Series options */
|
||||
/* Default colors */
|
||||
.highcharts-color-0 {
|
||||
fill: #7cb5ec;
|
||||
stroke: #7cb5ec;
|
||||
}
|
||||
|
||||
.highcharts-color-1 {
|
||||
fill: #434348;
|
||||
stroke: #434348;
|
||||
}
|
||||
|
||||
.highcharts-color-2 {
|
||||
fill: #90ed7d;
|
||||
stroke: #90ed7d;
|
||||
}
|
||||
|
||||
.highcharts-color-3 {
|
||||
fill: #f7a35c;
|
||||
stroke: #f7a35c;
|
||||
}
|
||||
|
||||
.highcharts-color-4 {
|
||||
fill: #8085e9;
|
||||
stroke: #8085e9;
|
||||
}
|
||||
|
||||
.highcharts-color-5 {
|
||||
fill: #f15c80;
|
||||
stroke: #f15c80;
|
||||
}
|
||||
|
||||
.highcharts-color-6 {
|
||||
fill: #e4d354;
|
||||
stroke: #e4d354;
|
||||
}
|
||||
|
||||
.highcharts-color-7 {
|
||||
fill: #2b908f;
|
||||
stroke: #2b908f;
|
||||
}
|
||||
|
||||
.highcharts-color-8 {
|
||||
fill: #f45b5b;
|
||||
stroke: #f45b5b;
|
||||
}
|
||||
|
||||
.highcharts-color-9 {
|
||||
fill: #91e8e1;
|
||||
stroke: #91e8e1;
|
||||
}
|
||||
|
||||
.highcharts-area {
|
||||
fill-opacity: 0.75;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-markers {
|
||||
stroke-width: 1px;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-a11y-markers-hidden .highcharts-point:not(.highcharts-point-hover):not(.highcharts-a11y-marker-visible),
|
||||
.highcharts-a11y-marker-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.highcharts-point {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-dense-data .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-data-label {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-data-label-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-data-label text, text.highcharts-data-label {
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
.highcharts-data-label-connector {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-data-label-hidden {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.highcharts-halo {
|
||||
fill-opacity: 0.25;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
|
||||
.highcharts-markers .highcharts-point-select {
|
||||
fill: #cccccc;
|
||||
stroke: #000000;
|
||||
}
|
||||
|
||||
.highcharts-column-series rect.highcharts-point {
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-column-series .highcharts-point {
|
||||
transition: fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-column-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-pie-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-pie-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point-select {
|
||||
fill: inherit;
|
||||
stroke: inherit;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point-select {
|
||||
fill: inherit;
|
||||
stroke: inherit;
|
||||
}
|
||||
|
||||
.highcharts-solidgauge-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-point {
|
||||
stroke-width: 1px;
|
||||
stroke: #e6e6e6;
|
||||
transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-point-hover {
|
||||
stroke: #999999;
|
||||
transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-above-level {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node-interactive {
|
||||
fill-opacity: 0.15;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
|
||||
fill-opacity: 0.75;
|
||||
}
|
||||
|
||||
.highcharts-vector-series .highcharts-point {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-windbarb-series .highcharts-point {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-lollipop-stem {
|
||||
stroke: #000000;
|
||||
}
|
||||
|
||||
.highcharts-focus-border {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-legend-item-hidden .highcharts-focus-border {
|
||||
fill: none !important;
|
||||
}
|
||||
|
||||
/* Legend */
|
||||
.highcharts-legend-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-legend-item > text {
|
||||
fill: #333333;
|
||||
font-weight: bold;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-legend-item:hover text {
|
||||
fill: #000000;
|
||||
}
|
||||
|
||||
.highcharts-legend-item-hidden * {
|
||||
fill: #cccccc !important;
|
||||
stroke: #cccccc !important;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-legend-nav-active {
|
||||
fill: #003399;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-legend-nav-inactive {
|
||||
fill: #cccccc;
|
||||
}
|
||||
|
||||
circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive {
|
||||
/* tracker */
|
||||
fill: rgba(192, 192, 192, 0.0001);
|
||||
}
|
||||
|
||||
.highcharts-legend-title-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Bubble legend */
|
||||
.highcharts-bubble-legend-symbol {
|
||||
stroke-width: 2;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-bubble-legend-connectors {
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.highcharts-bubble-legend-labels {
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.highcharts-loading {
|
||||
position: absolute;
|
||||
background-color: #ffffff;
|
||||
opacity: 0.5;
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-loading-hidden {
|
||||
height: 0 !important;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition: opacity 250ms, height 250ms step-end;
|
||||
}
|
||||
|
||||
.highcharts-loading-inner {
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
/* Plot bands and polar pane backgrounds */
|
||||
.highcharts-plot-band, .highcharts-pane {
|
||||
fill: #000000;
|
||||
fill-opacity: 0.05;
|
||||
}
|
||||
|
||||
.highcharts-plot-line {
|
||||
fill: none;
|
||||
stroke: #999999;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
/* Highcharts More and modules */
|
||||
.highcharts-boxplot-box {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-boxplot-median {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-bubble-series .highcharts-point {
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-errorbar-series .highcharts-point {
|
||||
stroke: #000000;
|
||||
}
|
||||
|
||||
.highcharts-gauge-series .highcharts-data-label-box {
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-gauge-series .highcharts-dial {
|
||||
fill: #000000;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-polygon-series .highcharts-graph {
|
||||
fill: inherit;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-waterfall-series .highcharts-graph {
|
||||
stroke: #333333;
|
||||
stroke-dasharray: 1, 3;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-link {
|
||||
transition: fill 250ms, fill-opacity 250ms;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
|
||||
transition: fill 50ms, fill-opacity 50ms;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
.highcharts-venn-series .highcharts-point {
|
||||
fill-opacity: 0.75;
|
||||
stroke: #cccccc;
|
||||
transition: stroke 250ms, fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-venn-series .highcharts-point-hover {
|
||||
fill-opacity: 1;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
/* Highstock */
|
||||
.highcharts-navigator-mask-outside {
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.highcharts-navigator-mask-inside {
|
||||
fill: #6685c2;
|
||||
/* navigator.maskFill option */
|
||||
fill-opacity: 0.25;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.highcharts-navigator-outline {
|
||||
stroke: #cccccc;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-navigator-handle {
|
||||
stroke: #cccccc;
|
||||
fill: #f2f2f2;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series {
|
||||
fill: #335cad;
|
||||
stroke: #335cad;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series .highcharts-graph {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series .highcharts-area {
|
||||
fill-opacity: 0.05;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis .highcharts-axis-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis .highcharts-grid-line {
|
||||
stroke-width: 1px;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis.highcharts-axis-labels {
|
||||
fill: #999999;
|
||||
}
|
||||
|
||||
.highcharts-navigator-yaxis .highcharts-grid-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-thumb {
|
||||
fill: #cccccc;
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-button {
|
||||
fill: #e6e6e6;
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-arrow {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-rifles {
|
||||
stroke: #666666;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-track {
|
||||
fill: #f2f2f2;
|
||||
stroke: #f2f2f2;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-button {
|
||||
fill: #f7f7f7;
|
||||
stroke: #cccccc;
|
||||
cursor: default;
|
||||
stroke-width: 1px;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-button text {
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
.highcharts-button-hover {
|
||||
transition: fill 0ms;
|
||||
fill: #e6e6e6;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-button-hover text {
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
.highcharts-button-pressed {
|
||||
font-weight: bold;
|
||||
fill: #e6ebf5;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-button-pressed text {
|
||||
fill: #333333;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-button-disabled text {
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
.highcharts-range-selector-buttons .highcharts-button {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.highcharts-range-label rect {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-range-label text {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-range-input rect {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-range-input text {
|
||||
fill: #333333;
|
||||
}
|
||||
|
||||
.highcharts-range-input {
|
||||
stroke-width: 1px;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
input.highcharts-range-selector {
|
||||
position: absolute;
|
||||
border: 0;
|
||||
width: 1px;
|
||||
/* Chrome needs a pixel to see it */
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
left: -9em;
|
||||
/* #4798 */
|
||||
}
|
||||
|
||||
.highcharts-crosshair-label text {
|
||||
fill: #ffffff;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-label .highcharts-label-box {
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.highcharts-candlestick-series .highcharts-point {
|
||||
stroke: #000000;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-candlestick-series .highcharts-point-up {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-ohlc-series .highcharts-point-hover {
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point .highcharts-label-box {
|
||||
stroke: #999999;
|
||||
fill: #ffffff;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
|
||||
stroke: #000000;
|
||||
fill: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point text {
|
||||
fill: #000000;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Highmaps */
|
||||
.highcharts-map-series .highcharts-point {
|
||||
transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-map-series .highcharts-point-hover {
|
||||
transition: fill 0ms, fill-opacity 0ms;
|
||||
fill-opacity: 0.5;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-mapline-series .highcharts-point {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-heatmap-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-map-navigation {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.highcharts-coloraxis {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-coloraxis-marker {
|
||||
fill: #999999;
|
||||
}
|
||||
|
||||
.highcharts-null-point {
|
||||
fill: #f7f7f7;
|
||||
}
|
||||
|
||||
/* 3d charts */
|
||||
.highcharts-3d-frame {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
/* Exporting module */
|
||||
.highcharts-contextbutton {
|
||||
fill: #ffffff;
|
||||
/* needed to capture hover */
|
||||
stroke: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.highcharts-contextbutton:hover {
|
||||
fill: #e6e6e6;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-button-symbol {
|
||||
stroke: #666666;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.highcharts-menu {
|
||||
border: 1px solid #999999;
|
||||
background: #ffffff;
|
||||
padding: 5px 0;
|
||||
box-shadow: 3px 3px 10px #888;
|
||||
}
|
||||
|
||||
.highcharts-menu-item {
|
||||
padding: 0.5em 1em;
|
||||
background: none;
|
||||
color: #333333;
|
||||
cursor: pointer;
|
||||
transition: background 250ms, color 250ms;
|
||||
}
|
||||
|
||||
.highcharts-menu-item:hover {
|
||||
background: #335cad;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Drilldown module */
|
||||
.highcharts-drilldown-point {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-drilldown-data-label text,
|
||||
text.highcharts-drilldown-data-label,
|
||||
.highcharts-drilldown-axis-label {
|
||||
cursor: pointer;
|
||||
fill: #003399;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* No-data module */
|
||||
.highcharts-no-data text {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
/* Drag-panes module */
|
||||
.highcharts-axis-resizer {
|
||||
cursor: ns-resize;
|
||||
stroke: black;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
/* Bullet type series */
|
||||
.highcharts-bullet-target {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Lineargauge type series */
|
||||
.highcharts-lineargauge-target {
|
||||
stroke-width: 1px;
|
||||
stroke: #333333;
|
||||
}
|
||||
|
||||
.highcharts-lineargauge-target-line {
|
||||
stroke-width: 1px;
|
||||
stroke: #333333;
|
||||
}
|
||||
|
||||
/* Annotations module */
|
||||
.highcharts-annotation-label-box {
|
||||
stroke-width: 1px;
|
||||
stroke: #000000;
|
||||
fill: #000000;
|
||||
fill-opacity: 0.75;
|
||||
}
|
||||
|
||||
.highcharts-annotation-label text {
|
||||
fill: #e6e6e6;
|
||||
}
|
||||
|
||||
/* Gantt */
|
||||
.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-point-connecting-path {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-grid-axis .highcharts-tick {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-grid-axis .highcharts-axis-line {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
@@ -0,0 +1,841 @@
|
||||
/**
|
||||
* @license Highcharts
|
||||
*
|
||||
* (c) 2009-2016 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*/
|
||||
|
||||
// Colors for data series and points.
|
||||
$colors: #7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1 !default;
|
||||
|
||||
// Chart background, point stroke for markers and columns etc
|
||||
$background-color: #ffffff !default;
|
||||
|
||||
// Neutral colors, grayscale by default. The default colors are defined by mixing the
|
||||
// background-color with neutral, with a weight corresponding to the number in the name.
|
||||
$neutral-color-100: #000000 !default; // Strong text.
|
||||
$neutral-color-80: #333333 !default; // Main text and some strokes.
|
||||
$neutral-color-60: #666666 !default; // Axis labels, axis title, connector fallback.
|
||||
$neutral-color-40: #999999 !default; // Credits text, export menu stroke.
|
||||
$neutral-color-20: #cccccc !default; // Disabled texts, button strokes, crosshair etc.
|
||||
$neutral-color-10: #e6e6e6 !default; // Grid lines etc.
|
||||
$neutral-color-5: #f2f2f2 !default; // Minor grid lines etc.
|
||||
$neutral-color-3: #f7f7f7 !default; // Tooltip backgroud, button fills, map null points.
|
||||
|
||||
// Colored, shades of blue by default
|
||||
$highlight-color-100: #003399 !default; // Drilldown clickable labels, color axis max color.
|
||||
$highlight-color-80: #335cad !default; // Selection marker, menu hover, button hover, chart border, navigator series.
|
||||
$highlight-color-60: #6685c2 !default; // Navigator mask fill.
|
||||
$highlight-color-20: #ccd6eb !default; // Ticks and axis line.
|
||||
$highlight-color-10: #e6ebf5 !default; // Pressed button, color axis min color.
|
||||
|
||||
// Fonts
|
||||
$font-family: "Lucida Grande", "Lucida Sans Unicode", Arial, Helvetica, sans-serif !default;
|
||||
$title-font-size: 1.5em !default;
|
||||
$subtitle-font-size: 1em !default;
|
||||
$legend-font-size: 1em !default;
|
||||
$axis-labels-font-size: 0.9em !default;
|
||||
|
||||
// Tooltip
|
||||
$tooltip-border: 1px !default;
|
||||
$tooltip-background: $neutral-color-3 !default;
|
||||
|
||||
// Axes
|
||||
$xaxis-grid-line: 0px !default;
|
||||
|
||||
// Range-selector
|
||||
$range-selector-button-border: 0px !default;
|
||||
$range-selector-input-text: $neutral-color-80 !default;
|
||||
$range-selector-input-border: $neutral-color-20 !default;
|
||||
|
||||
// Data-labels
|
||||
$data-label-color: $neutral-color-80 !default;
|
||||
|
||||
// Buttons
|
||||
$context-button-background: $background-color !default;
|
||||
|
||||
$highcharts-button-background: $neutral-color-3 !default;
|
||||
$highcharts-button-border: $neutral-color-20 !default;
|
||||
$highcharts-button-text: $neutral-color-80 !default;
|
||||
|
||||
$highcharts-button-pressed-background: $highlight-color-10 !default;
|
||||
$highcharts-button-pressed-border: $neutral-color-20 !default;
|
||||
$highcharts-button-pressed-text: $neutral-color-80 !default;
|
||||
|
||||
$highcharts-button-hover-background: $neutral-color-10 !default;
|
||||
$highcharts-button-hover-border: $neutral-color-20 !default;
|
||||
$highcharts-button-hover-text: $neutral-color-80 !default;
|
||||
|
||||
// Navigator
|
||||
$navigator-series-fill: $highlight-color-80 !default;
|
||||
$navigator-series-border: $highlight-color-80 !default;
|
||||
|
||||
// Scrollbar
|
||||
$scrollbar-track-background: $neutral-color-5 !default;
|
||||
$scrollbar-track-border: $neutral-color-5 !default;
|
||||
|
||||
// Indicators
|
||||
$indicator-positive-line: #06B535;
|
||||
$indicator-negative-line: #F21313;
|
||||
|
||||
.highcharts-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
line-height: normal;
|
||||
z-index: 0; /* #1072 */
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
font-family: $font-family;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
.highcharts-root {
|
||||
display: block;
|
||||
}
|
||||
.highcharts-root text {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
.highcharts-emphasized {
|
||||
font-style: italic;
|
||||
}
|
||||
.highcharts-anchor {
|
||||
cursor: pointer;
|
||||
}
|
||||
.highcharts-background {
|
||||
fill: $background-color;
|
||||
}
|
||||
.highcharts-plot-border, .highcharts-plot-background {
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-label-box {
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-button-box {
|
||||
fill: inherit;
|
||||
}
|
||||
.highcharts-tracker-line {
|
||||
stroke-linejoin: round;
|
||||
stroke: rgba(192, 192, 192, 0.0001);
|
||||
stroke-width: 22;
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-tracker-area {
|
||||
fill: rgba(192, 192, 192, 0.0001);
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
.highcharts-title {
|
||||
fill: $neutral-color-80;
|
||||
font-size: $title-font-size;
|
||||
}
|
||||
.highcharts-subtitle {
|
||||
fill: $neutral-color-60;
|
||||
}
|
||||
|
||||
/* Axes */
|
||||
.highcharts-axis-line {
|
||||
fill: none;
|
||||
stroke: $highlight-color-20;
|
||||
}
|
||||
.highcharts-yaxis .highcharts-axis-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-axis-title {
|
||||
fill: $neutral-color-60;
|
||||
}
|
||||
.highcharts-axis-labels {
|
||||
fill: $neutral-color-60;
|
||||
cursor: default;
|
||||
font-size: $axis-labels-font-size;
|
||||
}
|
||||
.highcharts-grid-line {
|
||||
fill: none;
|
||||
stroke: $neutral-color-10;
|
||||
}
|
||||
.highcharts-xaxis-grid .highcharts-grid-line {
|
||||
stroke-width: $xaxis-grid-line;
|
||||
}
|
||||
.highcharts-tick {
|
||||
stroke: $highlight-color-20;
|
||||
}
|
||||
.highcharts-yaxis .highcharts-tick {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-minor-grid-line {
|
||||
stroke: $neutral-color-5;
|
||||
}
|
||||
.highcharts-crosshair-thin {
|
||||
stroke-width: 1px;
|
||||
stroke: $neutral-color-20;
|
||||
}
|
||||
.highcharts-crosshair-category {
|
||||
stroke: $highlight-color-20;
|
||||
stroke-opacity: 0.25;
|
||||
}
|
||||
|
||||
|
||||
/* Credits */
|
||||
.highcharts-credits {
|
||||
cursor: pointer;
|
||||
fill: $neutral-color-40;
|
||||
font-size: 0.7em;
|
||||
transition: fill 250ms, font-size 250ms;
|
||||
}
|
||||
.highcharts-credits:hover {
|
||||
fill: black;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
.highcharts-tooltip {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
transition: stroke 150ms;
|
||||
}
|
||||
.highcharts-tooltip text {
|
||||
fill: $neutral-color-80;
|
||||
}
|
||||
.highcharts-tooltip .highcharts-header {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.highcharts-tooltip-box {
|
||||
stroke-width: $tooltip-border;
|
||||
fill: $tooltip-background;
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
.highcharts-tooltip-box .highcharts-label-box {
|
||||
fill: $tooltip-background;
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
div.highcharts-tooltip {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.highcharts-selection-marker {
|
||||
fill: $highlight-color-80;
|
||||
fill-opacity: 0.25;
|
||||
}
|
||||
|
||||
.highcharts-graph {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.highcharts-empty-series {
|
||||
stroke-width: 1px;
|
||||
fill: none;
|
||||
stroke: $neutral-color-20;
|
||||
}
|
||||
|
||||
.highcharts-state-hover .highcharts-graph {
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.highcharts-point-inactive {
|
||||
opacity: 0.2;
|
||||
transition: opacity 50ms; /* quick in */
|
||||
}
|
||||
|
||||
.highcharts-series-inactive {
|
||||
opacity: 0.2;
|
||||
transition: opacity 50ms; /* quick in */
|
||||
}
|
||||
|
||||
.highcharts-state-hover path {
|
||||
transition: stroke-width 50ms; /* quick in */
|
||||
}
|
||||
.highcharts-state-normal path {
|
||||
transition: stroke-width 250ms; /* slow out */
|
||||
}
|
||||
/* Legend hover affects points and series */
|
||||
g.highcharts-series,
|
||||
.highcharts-point,
|
||||
.highcharts-markers,
|
||||
.highcharts-data-labels {
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
|
||||
.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
|
||||
.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
|
||||
.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/* Series options */
|
||||
|
||||
/* Default colors */
|
||||
@for $i from 1 through length($colors) {
|
||||
$color: nth($colors, $i);
|
||||
.highcharts-color-#{$i - 1} {
|
||||
fill: $color;
|
||||
stroke: $color;
|
||||
}
|
||||
}
|
||||
|
||||
.highcharts-area {
|
||||
fill-opacity: 0.75;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-markers {
|
||||
stroke-width: 1px;
|
||||
stroke: $background-color;
|
||||
}
|
||||
.highcharts-a11y-markers-hidden .highcharts-point:not(.highcharts-point-hover):not(.highcharts-a11y-marker-visible),
|
||||
.highcharts-a11y-marker-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
.highcharts-point {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-dense-data .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-data-label {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highcharts-data-label-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-data-label text, text.highcharts-data-label {
|
||||
fill: $data-label-color;
|
||||
}
|
||||
.highcharts-data-label-connector {
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-data-label-hidden {
|
||||
pointer-events: none;
|
||||
}
|
||||
.highcharts-halo {
|
||||
fill-opacity: 0.25;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
|
||||
.highcharts-markers .highcharts-point-select {
|
||||
fill: $neutral-color-20;
|
||||
stroke: $neutral-color-100;
|
||||
}
|
||||
.highcharts-column-series rect.highcharts-point {
|
||||
// rect to prevent stroke on 3D columns
|
||||
stroke: $background-color;
|
||||
}
|
||||
.highcharts-column-series .highcharts-point {
|
||||
transition: fill-opacity 250ms;
|
||||
}
|
||||
.highcharts-column-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
.highcharts-pie-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: $background-color;
|
||||
}
|
||||
.highcharts-pie-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
.highcharts-funnel-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: $background-color;
|
||||
}
|
||||
.highcharts-funnel-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
.highcharts-funnel-series .highcharts-point-select {
|
||||
fill: inherit;
|
||||
stroke: inherit;
|
||||
}
|
||||
.highcharts-pyramid-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: $background-color;
|
||||
}
|
||||
.highcharts-pyramid-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
.highcharts-pyramid-series .highcharts-point-select {
|
||||
fill: inherit;
|
||||
stroke: inherit;
|
||||
}
|
||||
.highcharts-solidgauge-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-treemap-series .highcharts-point {
|
||||
stroke-width: 1px;
|
||||
stroke: $neutral-color-10;
|
||||
transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
|
||||
}
|
||||
.highcharts-treemap-series .highcharts-point-hover {
|
||||
stroke: $neutral-color-40;
|
||||
transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-above-level {
|
||||
display: none;
|
||||
}
|
||||
.highcharts-treemap-series .highcharts-internal-node {
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-treemap-series .highcharts-internal-node-interactive {
|
||||
fill-opacity: 0.15;
|
||||
cursor: pointer;
|
||||
}
|
||||
.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
|
||||
fill-opacity: 0.75;
|
||||
}
|
||||
|
||||
.highcharts-vector-series .highcharts-point {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-windbarb-series .highcharts-point {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-lollipop-stem {
|
||||
stroke: $neutral-color-100;
|
||||
}
|
||||
|
||||
.highcharts-focus-border {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-legend-item-hidden .highcharts-focus-border {
|
||||
fill: none !important;
|
||||
}
|
||||
|
||||
/* Legend */
|
||||
.highcharts-legend-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-legend-item > text {
|
||||
fill: $neutral-color-80;
|
||||
font-weight: bold;
|
||||
font-size: $legend-font-size;
|
||||
cursor: pointer;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-legend-item:hover text {
|
||||
fill: $neutral-color-100;
|
||||
}
|
||||
.highcharts-legend-item-hidden * {
|
||||
fill: $neutral-color-20 !important;
|
||||
stroke: $neutral-color-20 !important;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
.highcharts-legend-nav-active {
|
||||
fill: $highlight-color-100;
|
||||
cursor: pointer;
|
||||
}
|
||||
.highcharts-legend-nav-inactive {
|
||||
fill: $neutral-color-20;
|
||||
}
|
||||
circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive { /* tracker */
|
||||
fill: rgba(192, 192, 192, 0.0001);
|
||||
}
|
||||
.highcharts-legend-title-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Bubble legend */
|
||||
.highcharts-bubble-legend-symbol {
|
||||
stroke-width: 2;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
.highcharts-bubble-legend-connectors {
|
||||
stroke-width: 1;
|
||||
}
|
||||
.highcharts-bubble-legend-labels {
|
||||
fill: $neutral-color-80;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.highcharts-loading {
|
||||
position: absolute;
|
||||
background-color: $background-color;
|
||||
opacity: 0.5;
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
.highcharts-loading-hidden {
|
||||
height: 0 !important;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition: opacity 250ms, height 250ms step-end;
|
||||
}
|
||||
.highcharts-loading-inner {
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
/* Plot bands and polar pane backgrounds */
|
||||
.highcharts-plot-band, .highcharts-pane {
|
||||
fill: $neutral-color-100;
|
||||
fill-opacity: 0.05;
|
||||
}
|
||||
.highcharts-plot-line {
|
||||
fill: none;
|
||||
stroke: $neutral-color-40;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
/* Highcharts More and modules */
|
||||
.highcharts-boxplot-box {
|
||||
fill: $background-color;
|
||||
}
|
||||
.highcharts-boxplot-median {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.highcharts-bubble-series .highcharts-point {
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
.highcharts-errorbar-series .highcharts-point {
|
||||
stroke: $neutral-color-100;
|
||||
}
|
||||
.highcharts-gauge-series .highcharts-data-label-box {
|
||||
stroke: $neutral-color-20;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-gauge-series .highcharts-dial {
|
||||
fill: $neutral-color-100;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-polygon-series .highcharts-graph {
|
||||
fill: inherit;
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-waterfall-series .highcharts-graph {
|
||||
stroke: $neutral-color-80;
|
||||
stroke-dasharray: 1, 3;
|
||||
}
|
||||
.highcharts-sankey-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-sankey-series .highcharts-link {
|
||||
transition: fill 250ms, fill-opacity 250ms;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
|
||||
transition: fill 50ms, fill-opacity 50ms;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
.highcharts-venn-series .highcharts-point {
|
||||
fill-opacity: 0.75;
|
||||
stroke: $neutral-color-20;
|
||||
transition: stroke 250ms, fill-opacity 250ms;
|
||||
}
|
||||
.highcharts-venn-series .highcharts-point-hover {
|
||||
fill-opacity: 1;
|
||||
stroke: $neutral-color-20;
|
||||
}
|
||||
|
||||
/* Highstock */
|
||||
.highcharts-navigator-mask-outside {
|
||||
fill-opacity: 0;
|
||||
}
|
||||
.highcharts-navigator-mask-inside {
|
||||
fill: $highlight-color-60; /* navigator.maskFill option */
|
||||
fill-opacity: 0.25;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.highcharts-navigator-outline {
|
||||
stroke: $neutral-color-20;
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-navigator-handle {
|
||||
stroke: $neutral-color-20;
|
||||
fill: $neutral-color-5;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.highcharts-navigator-series {
|
||||
fill: $navigator-series-fill;
|
||||
stroke: $navigator-series-border;
|
||||
}
|
||||
.highcharts-navigator-series .highcharts-graph {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-navigator-series .highcharts-area {
|
||||
fill-opacity: 0.05;
|
||||
}
|
||||
.highcharts-navigator-xaxis .highcharts-axis-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-navigator-xaxis .highcharts-grid-line {
|
||||
stroke-width: 1px;
|
||||
stroke: $neutral-color-10;
|
||||
}
|
||||
.highcharts-navigator-xaxis.highcharts-axis-labels {
|
||||
fill: $neutral-color-40;
|
||||
}
|
||||
.highcharts-navigator-yaxis .highcharts-grid-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-scrollbar-thumb {
|
||||
fill: $neutral-color-20;
|
||||
stroke: $neutral-color-20;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-scrollbar-button {
|
||||
fill: $neutral-color-10;
|
||||
stroke: $neutral-color-20;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-scrollbar-arrow {
|
||||
fill: $neutral-color-60;
|
||||
}
|
||||
.highcharts-scrollbar-rifles {
|
||||
stroke: $neutral-color-60;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-scrollbar-track {
|
||||
fill: $scrollbar-track-background;
|
||||
stroke: $scrollbar-track-border;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-button {
|
||||
fill: $highcharts-button-background;
|
||||
stroke: $highcharts-button-border;
|
||||
cursor: default;
|
||||
stroke-width: 1px;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
.highcharts-button text {
|
||||
fill: $highcharts-button-text;
|
||||
}
|
||||
.highcharts-button-hover {
|
||||
transition: fill 0ms;
|
||||
fill: $highcharts-button-hover-background;
|
||||
stroke: $highcharts-button-hover-border;
|
||||
}
|
||||
.highcharts-button-hover text {
|
||||
fill: $highcharts-button-hover-text;
|
||||
}
|
||||
.highcharts-button-pressed {
|
||||
font-weight: bold;
|
||||
fill: $highcharts-button-pressed-background;
|
||||
stroke: $highcharts-button-pressed-border;
|
||||
}
|
||||
.highcharts-button-pressed text {
|
||||
fill: $highcharts-button-pressed-text;
|
||||
font-weight: bold;
|
||||
}
|
||||
.highcharts-button-disabled text {
|
||||
fill: $highcharts-button-text;
|
||||
}
|
||||
.highcharts-range-selector-buttons .highcharts-button {
|
||||
stroke-width: $range-selector-button-border;
|
||||
}
|
||||
.highcharts-range-label rect {
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-range-label text {
|
||||
fill: $neutral-color-60;
|
||||
}
|
||||
.highcharts-range-input rect {
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-range-input text {
|
||||
fill: $range-selector-input-text;
|
||||
}
|
||||
.highcharts-range-input {
|
||||
stroke-width:1px;
|
||||
stroke: $range-selector-input-border;
|
||||
}
|
||||
input.highcharts-range-selector {
|
||||
position: absolute;
|
||||
border: 0;
|
||||
width: 1px; /* Chrome needs a pixel to see it */
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
left: -9em; /* #4798 */
|
||||
}
|
||||
.highcharts-crosshair-label text {
|
||||
fill: $background-color;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.highcharts-crosshair-label .highcharts-label-box {
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
|
||||
.highcharts-candlestick-series .highcharts-point {
|
||||
stroke: $neutral-color-100;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-candlestick-series .highcharts-point-up {
|
||||
fill: $background-color;
|
||||
}
|
||||
.highcharts-ohlc-series .highcharts-point-hover {
|
||||
stroke-width: 3px;
|
||||
}
|
||||
.highcharts-flags-series .highcharts-point .highcharts-label-box {
|
||||
stroke: $neutral-color-40;
|
||||
fill: $background-color;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
|
||||
stroke: $neutral-color-100;
|
||||
fill: $highlight-color-20;
|
||||
}
|
||||
.highcharts-flags-series .highcharts-point text {
|
||||
fill: $neutral-color-100;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Highmaps */
|
||||
.highcharts-map-series .highcharts-point {
|
||||
transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
|
||||
stroke: $neutral-color-20;
|
||||
}
|
||||
.highcharts-map-series .highcharts-point-hover {
|
||||
transition: fill 0ms, fill-opacity 0ms;
|
||||
fill-opacity: 0.5;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.highcharts-mapline-series .highcharts-point {
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-heatmap-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-map-navigation {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
.highcharts-coloraxis {
|
||||
stroke-width: 0;
|
||||
}
|
||||
.highcharts-coloraxis-marker {
|
||||
fill: $neutral-color-40;
|
||||
}
|
||||
.highcharts-null-point {
|
||||
fill: $neutral-color-3;
|
||||
}
|
||||
|
||||
/* 3d charts */
|
||||
.highcharts-3d-frame {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
/* Exporting module */
|
||||
.highcharts-contextbutton {
|
||||
fill: $context-button-background; /* needed to capture hover */
|
||||
stroke: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
.highcharts-contextbutton:hover {
|
||||
fill: $neutral-color-10;
|
||||
stroke: $neutral-color-10;
|
||||
}
|
||||
.highcharts-button-symbol {
|
||||
stroke: $neutral-color-60;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
.highcharts-menu {
|
||||
border: 1px solid $neutral-color-40;
|
||||
background: $background-color;
|
||||
padding: 5px 0;
|
||||
box-shadow: 3px 3px 10px #888;
|
||||
}
|
||||
.highcharts-menu-item {
|
||||
padding: 0.5em 1em;
|
||||
background: none;
|
||||
color: $neutral-color-80;
|
||||
cursor: pointer;
|
||||
transition: background 250ms, color 250ms;
|
||||
}
|
||||
.highcharts-menu-item:hover {
|
||||
background: $highlight-color-80;
|
||||
color: $background-color;
|
||||
}
|
||||
|
||||
/* Drilldown module */
|
||||
.highcharts-drilldown-point {
|
||||
cursor: pointer;
|
||||
}
|
||||
.highcharts-drilldown-data-label text,
|
||||
text.highcharts-drilldown-data-label,
|
||||
.highcharts-drilldown-axis-label {
|
||||
cursor: pointer;
|
||||
fill: $highlight-color-100;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* No-data module */
|
||||
.highcharts-no-data text {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
fill: $neutral-color-60;
|
||||
}
|
||||
|
||||
/* Drag-panes module */
|
||||
.highcharts-axis-resizer {
|
||||
cursor: ns-resize;
|
||||
stroke: black;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
/* Bullet type series */
|
||||
.highcharts-bullet-target {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Lineargauge type series */
|
||||
.highcharts-lineargauge-target {
|
||||
stroke-width: 1px;
|
||||
stroke: $neutral-color-80;
|
||||
}
|
||||
.highcharts-lineargauge-target-line {
|
||||
stroke-width: 1px;
|
||||
stroke: $neutral-color-80;
|
||||
}
|
||||
|
||||
/* Annotations module */
|
||||
.highcharts-annotation-label-box {
|
||||
stroke-width: 1px;
|
||||
stroke: $neutral-color-100;
|
||||
fill: $neutral-color-100;
|
||||
fill-opacity: 0.75;
|
||||
}
|
||||
.highcharts-annotation-label text {
|
||||
fill: $neutral-color-10;
|
||||
}
|
||||
|
||||
/* Gantt */
|
||||
.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
|
||||
cursor: pointer;
|
||||
}
|
||||
.highcharts-point-connecting-path {
|
||||
fill: none;
|
||||
}
|
||||
.highcharts-grid-axis .highcharts-tick {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
.highcharts-grid-axis .highcharts-axis-line {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
.chart:-webkit-full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart:-moz-full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart:-ms-fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart:fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
float: left;
|
||||
height: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highcharts-draw-mode {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper * {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.highcharts-stocktools-popup {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.highcharts-menu-wrapper {
|
||||
float: left;
|
||||
width: 40px;
|
||||
height: calc(100% - 50px);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-submenu-wrapper {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
background: #fff;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-wrapper {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: 10px;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-wrapper > div {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-down {
|
||||
background-size: cover;
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(90deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(90deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(90deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(90deg);
|
||||
/* Internet Explorer */
|
||||
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-up {
|
||||
background-size: cover;
|
||||
outline: none;
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
/* Chrome/Safari */
|
||||
-moz-user-select: none;
|
||||
/* Firefox */
|
||||
-ms-user-select: none;
|
||||
/* IE10+ */
|
||||
/* Rules below not implemented in browsers yet */
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(-90deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(-90deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(-90deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(-90deg);
|
||||
/* Internet Explorer */
|
||||
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-right {
|
||||
background-repeat: no-repeat;
|
||||
background-position: right bottom;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-left.highcharts-arrow-right {
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(0deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(0deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(0deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(0deg);
|
||||
/* Internet Explorer */
|
||||
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-left {
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(180deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(180deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(180deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(180deg);
|
||||
/* Internet Explorer */
|
||||
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper ul {
|
||||
width: 40px;
|
||||
/* 30px spacing for arrows to scroll */
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
float: left;
|
||||
transition: margin 250ms;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper > ul {
|
||||
width: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li {
|
||||
list-style: none;
|
||||
margin-bottom: 3px;
|
||||
padding: 0px;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn > .highcharts-menu-item-btn {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn.highcharts-active {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn .highcharts-menu-item-btn:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li > span.highcharts-menu-item-btn {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
}
|
||||
|
||||
.highcharts-submenu-wrapper li > span.highcharts-menu-item-btn {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li > span.highcharts-submenu-item-arrow {
|
||||
float: left;
|
||||
width: 10px;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li.highcharts-separator {
|
||||
height: 15px;
|
||||
background-color: transparent;
|
||||
width: 36px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li.highcharts-separator > span.highcharts-menu-item-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li.highcharts-active > span.highcharts-menu-item-btn,
|
||||
.highcharts-bindings-wrapper li > span.highcharts-menu-item-btn:hover,
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-wrapper > div:hover,
|
||||
.highcharts-bindings-wrapper li.highcharts-active,
|
||||
.highcharts-toggle-toolbar:hover {
|
||||
background-color: #e6ebf5;
|
||||
transition: background-color 100ms;
|
||||
}
|
||||
|
||||
.highcharts-toggle-toolbar {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #f7f7f7;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.highcharts-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li:hover, .highcharts-submenu-item-arrow:hover {
|
||||
background-color: #e6ebf5;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-down, .highcharts-bindings-wrapper .highcharts-arrow-up {
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
float: left;
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
// Colors for buttons.
|
||||
$button-background-color: #f7f7f7;
|
||||
$button-hover-color: #e6ebf5;
|
||||
|
||||
.chart:-webkit-full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart:-moz-full-screen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart:-ms-fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart:fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
float: left;
|
||||
height: 400px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highcharts-draw-mode { cursor: crosshair; }
|
||||
|
||||
.highcharts-bindings-wrapper * {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.highcharts-stocktools-popup {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.highcharts-menu-wrapper {
|
||||
float: left;
|
||||
width: 40px;
|
||||
height: calc(100% - 50px);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-submenu-wrapper {
|
||||
display: none;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
background: #fff;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-wrapper {
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
bottom: 10px;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-wrapper > div {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-down {
|
||||
background-size: cover;
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(90deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(90deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(90deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(90deg);
|
||||
/* Internet Explorer */
|
||||
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-up {
|
||||
background-size: cover;
|
||||
outline: none;
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
/* Chrome/Safari */
|
||||
-moz-user-select: none;
|
||||
/* Firefox */
|
||||
-ms-user-select: none;
|
||||
/* IE10+ */
|
||||
/* Rules below not implemented in browsers yet */
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(-90deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(-90deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(-90deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(-90deg);
|
||||
/* Internet Explorer */
|
||||
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-right {
|
||||
background-repeat: no-repeat;
|
||||
background-position: right bottom;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-left.highcharts-arrow-right {
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(0deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(0deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(0deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(0deg);
|
||||
/* Internet Explorer */
|
||||
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-left {
|
||||
/* Safari */
|
||||
-webkit-transform: rotate(180deg);
|
||||
/* Firefox */
|
||||
-moz-transform: rotate(180deg);
|
||||
/* IE */
|
||||
-ms-transform: rotate(180deg);
|
||||
/* Opera */
|
||||
-o-transform: rotate(180deg);
|
||||
/* Internet Explorer */
|
||||
filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper ul {
|
||||
width: 40px;
|
||||
/* 30px spacing for arrows to scroll */
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
float: left;
|
||||
transition: margin 250ms;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper>ul {
|
||||
width: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li {
|
||||
list-style: none;
|
||||
margin-bottom: 3px;
|
||||
padding: 0px;
|
||||
clear: both;
|
||||
width: 100%;
|
||||
height: 40px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
background-color: $button-background-color;
|
||||
}
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn > .highcharts-menu-item-btn {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn.highcharts-active {
|
||||
background: $button-background-color;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-stocktools-toolbar li.highcharts-disabled-btn .highcharts-menu-item-btn:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li>span.highcharts-menu-item-btn {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
}
|
||||
|
||||
.highcharts-submenu-wrapper li>span.highcharts-menu-item-btn {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li>span.highcharts-submenu-item-arrow {
|
||||
float: left;
|
||||
width: 10px;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li.highcharts-separator {
|
||||
height: 15px;
|
||||
background-color: transparent;
|
||||
width: 36px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li.highcharts-separator>span.highcharts-menu-item-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li.highcharts-active>span.highcharts-menu-item-btn,
|
||||
.highcharts-bindings-wrapper li>span.highcharts-menu-item-btn:hover,
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-wrapper > div:hover,
|
||||
.highcharts-bindings-wrapper li.highcharts-active,
|
||||
.highcharts-toggle-toolbar:hover {
|
||||
background-color: $button-hover-color;
|
||||
transition: background-color 100ms;
|
||||
}
|
||||
|
||||
.highcharts-toggle-toolbar {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: $button-background-color;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.highcharts-hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper li:hover, .highcharts-submenu-item-arrow:hover {
|
||||
background-color: $button-hover-color;
|
||||
}
|
||||
|
||||
.highcharts-bindings-wrapper .highcharts-arrow-down, .highcharts-bindings-wrapper .highcharts-arrow-up {
|
||||
width: 50%;
|
||||
height: 20px;
|
||||
float: left;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,122 @@
|
||||
// Global font
|
||||
@import 'https://fonts.googleapis.com/css?family=Unica+One';
|
||||
|
||||
// Chart background, point stroke for markers and columns etc
|
||||
$background-color: #2a2a2b;
|
||||
|
||||
// Colors for data series and points.
|
||||
$colors: #2b908f #90ee7e #f45b5b #7798BF #aaeeee #ff0066 #eeaaee #55BF3B #DF5353 #7798BF #aaeeee;
|
||||
|
||||
// Neutral colors
|
||||
$neutral-color-100: #fff;
|
||||
$neutral-color-80: #E0E0E3;
|
||||
$neutral-color-60: #E0E0E3;
|
||||
$neutral-color-40: #666;
|
||||
$neutral-color-20: #606063;
|
||||
$neutral-color-10: #707073;
|
||||
$neutral-color-5: #505053;
|
||||
|
||||
// Colored, shades
|
||||
$highlight-color-100: #F0F0F3;
|
||||
$highlight-color-60: rgba(255,255,255,0.1);
|
||||
$highlight-color-20: $neutral-color-10;
|
||||
|
||||
// Data-labels
|
||||
$data-label-color: #B0B0B3;
|
||||
|
||||
// Fonts
|
||||
$font-family: 'Unica One', Arial, Helvetica, sans-serif;
|
||||
$title-font-size: 20px;
|
||||
|
||||
// Tooltip
|
||||
$tooltip-background: rgba(0, 0, 0, 0.85);
|
||||
|
||||
// Range-selector
|
||||
$range-selector-input-text: silver;
|
||||
$range-selector-input-border: $neutral-color-5;
|
||||
|
||||
// Buttons
|
||||
$highcharts-button-background: $neutral-color-5;
|
||||
$highcharts-button-text: #ccc;
|
||||
|
||||
$highcharts-button-pressed-background: #000003;
|
||||
$highcharts-button-pressed-text: $neutral-color-100;
|
||||
|
||||
$highcharts-button-hover-background: $neutral-color-10;
|
||||
$highcharts-button-hover-text: $neutral-color-100;
|
||||
|
||||
$context-button-background: $neutral-color-5;
|
||||
|
||||
// Navigator
|
||||
$navigator-series-fill: #7798BF;
|
||||
$navigator-series-border: #A6C7ED;
|
||||
|
||||
// Navigator
|
||||
$scrollbar-track-background: #404043;
|
||||
$scrollbar-track-border: #404043;
|
||||
|
||||
// Titles
|
||||
.highcharts-title, .highcharts-subtitle {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
.highcharts-tooltip text {
|
||||
fill: #F0F0F0
|
||||
}
|
||||
|
||||
// Range-selector
|
||||
.highcharts-range-selector-buttons text {
|
||||
fill: silver;
|
||||
}
|
||||
|
||||
// Axes
|
||||
.highcharts-yaxis-grid {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-axis-labels, .highcharts-axis-title {
|
||||
fill: #E0E0E3;
|
||||
}
|
||||
|
||||
// Navigator
|
||||
.highcharts-navigator .highcharts-navigator-handle {
|
||||
fill: $neutral-color-40;
|
||||
stroke: #aaa;
|
||||
}
|
||||
|
||||
.highcharts-navigator .highcharts-navigator-outline {
|
||||
stroke: #CCC;
|
||||
}
|
||||
|
||||
.highcharts-navigator .highcharts-navigator-xaxis .highcharts-grid-line {
|
||||
stroke: $neutral-color-5;
|
||||
}
|
||||
|
||||
// Scrollbar
|
||||
.highcharts-scrollbar .highcharts-scrollbar-rifles {
|
||||
stroke: $neutral-color-100;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar .highcharts-scrollbar-button {
|
||||
stroke: #606063;
|
||||
fill: #606063;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar .highcharts-scrollbar-arrow {
|
||||
fill: #CCC;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar .highcharts-scrollbar-thumb {
|
||||
fill: #808083;
|
||||
stroke: #808083;
|
||||
}
|
||||
|
||||
// Navigation
|
||||
.highcharts-contextbutton .highcharts-button-symbol {
|
||||
stroke: #DDDDDD;
|
||||
}
|
||||
|
||||
@import '../highcharts';
|
||||
|
||||
|
||||
@@ -0,0 +1,964 @@
|
||||
@import 'https://fonts.googleapis.com/css?family=Dosis:400,600';
|
||||
.highcharts-title, .highcharts-subtitle, .highcharts-yaxis .highcharts-axis-title {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.highcharts-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/**
|
||||
* @license Highcharts
|
||||
*
|
||||
* (c) 2009-2016 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*/
|
||||
.highcharts-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
line-height: normal;
|
||||
z-index: 0;
|
||||
/* #1072 */
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-family: "Dosis", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.highcharts-root {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.highcharts-root text {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-emphasized {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.highcharts-anchor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-background {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-plot-border, .highcharts-plot-background {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-label-box {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-button-box {
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.highcharts-tracker-line {
|
||||
stroke-linejoin: round;
|
||||
stroke: rgba(192, 192, 192, 0.0001);
|
||||
stroke-width: 22;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-tracker-area {
|
||||
fill: rgba(192, 192, 192, 0.0001);
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
.highcharts-title {
|
||||
fill: #000;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.highcharts-subtitle {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
/* Axes */
|
||||
.highcharts-axis-line {
|
||||
fill: none;
|
||||
stroke: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-yaxis .highcharts-axis-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-axis-title {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-axis-labels {
|
||||
fill: #666666;
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.highcharts-grid-line {
|
||||
fill: none;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-xaxis-grid .highcharts-grid-line {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-tick {
|
||||
stroke: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-yaxis .highcharts-tick {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-minor-grid-line {
|
||||
stroke: #f2f2f2;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-thin {
|
||||
stroke-width: 1px;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-category {
|
||||
stroke: #ccd6eb;
|
||||
stroke-opacity: 0.25;
|
||||
}
|
||||
|
||||
/* Credits */
|
||||
.highcharts-credits {
|
||||
cursor: pointer;
|
||||
fill: #999999;
|
||||
font-size: 0.7em;
|
||||
transition: fill 250ms, font-size 250ms;
|
||||
}
|
||||
|
||||
.highcharts-credits:hover {
|
||||
fill: black;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
.highcharts-tooltip {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
transition: stroke 150ms;
|
||||
}
|
||||
|
||||
.highcharts-tooltip text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-tooltip .highcharts-header {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.highcharts-tooltip-box {
|
||||
stroke-width: 0px;
|
||||
fill: rgba(219, 219, 216, 0.8);
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
|
||||
.highcharts-tooltip-box .highcharts-label-box {
|
||||
fill: rgba(219, 219, 216, 0.8);
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
|
||||
div.highcharts-tooltip {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.highcharts-selection-marker {
|
||||
fill: #335cad;
|
||||
fill-opacity: 0.25;
|
||||
}
|
||||
|
||||
.highcharts-graph {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.highcharts-empty-series {
|
||||
stroke-width: 1px;
|
||||
fill: none;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-state-hover .highcharts-graph {
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.highcharts-point-inactive {
|
||||
opacity: 0.2;
|
||||
transition: opacity 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-series-inactive {
|
||||
opacity: 0.2;
|
||||
transition: opacity 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-state-hover path {
|
||||
transition: stroke-width 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-state-normal path {
|
||||
transition: stroke-width 250ms;
|
||||
/* slow out */
|
||||
}
|
||||
|
||||
/* Legend hover affects points and series */
|
||||
g.highcharts-series,
|
||||
.highcharts-point,
|
||||
.highcharts-markers,
|
||||
.highcharts-data-labels {
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
|
||||
.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
|
||||
.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
|
||||
.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/* Series options */
|
||||
/* Default colors */
|
||||
.highcharts-color-0 {
|
||||
fill: #7cb5ec;
|
||||
stroke: #7cb5ec;
|
||||
}
|
||||
|
||||
.highcharts-color-1 {
|
||||
fill: #f7a35c;
|
||||
stroke: #f7a35c;
|
||||
}
|
||||
|
||||
.highcharts-color-2 {
|
||||
fill: #90ee7e;
|
||||
stroke: #90ee7e;
|
||||
}
|
||||
|
||||
.highcharts-color-3 {
|
||||
fill: #7798BF;
|
||||
stroke: #7798BF;
|
||||
}
|
||||
|
||||
.highcharts-color-4 {
|
||||
fill: #aaeeee;
|
||||
stroke: #aaeeee;
|
||||
}
|
||||
|
||||
.highcharts-color-5 {
|
||||
fill: #ff0066;
|
||||
stroke: #ff0066;
|
||||
}
|
||||
|
||||
.highcharts-color-6 {
|
||||
fill: #eeaaee;
|
||||
stroke: #eeaaee;
|
||||
}
|
||||
|
||||
.highcharts-color-7 {
|
||||
fill: #55BF3B;
|
||||
stroke: #55BF3B;
|
||||
}
|
||||
|
||||
.highcharts-color-8 {
|
||||
fill: #DF5353;
|
||||
stroke: #DF5353;
|
||||
}
|
||||
|
||||
.highcharts-color-9 {
|
||||
fill: #7798BF;
|
||||
stroke: #7798BF;
|
||||
}
|
||||
|
||||
.highcharts-color-10 {
|
||||
fill: #aaeeee;
|
||||
stroke: #aaeeee;
|
||||
}
|
||||
|
||||
.highcharts-area {
|
||||
fill-opacity: 0.75;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-markers {
|
||||
stroke-width: 1px;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-a11y-markers-hidden .highcharts-point:not(.highcharts-point-hover):not(.highcharts-a11y-marker-visible),
|
||||
.highcharts-a11y-marker-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.highcharts-point {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-dense-data .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-data-label {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-data-label-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-data-label text, text.highcharts-data-label {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-data-label-connector {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-data-label-hidden {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.highcharts-halo {
|
||||
fill-opacity: 0.25;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
|
||||
.highcharts-markers .highcharts-point-select {
|
||||
fill: #cccccc;
|
||||
stroke: #404048;
|
||||
}
|
||||
|
||||
.highcharts-column-series rect.highcharts-point {
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-column-series .highcharts-point {
|
||||
transition: fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-column-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-pie-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-pie-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point-select {
|
||||
fill: inherit;
|
||||
stroke: inherit;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point-select {
|
||||
fill: inherit;
|
||||
stroke: inherit;
|
||||
}
|
||||
|
||||
.highcharts-solidgauge-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-point {
|
||||
stroke-width: 1px;
|
||||
stroke: #e6e6e6;
|
||||
transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-point-hover {
|
||||
stroke: #999999;
|
||||
transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-above-level {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node-interactive {
|
||||
fill-opacity: 0.15;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
|
||||
fill-opacity: 0.75;
|
||||
}
|
||||
|
||||
.highcharts-vector-series .highcharts-point {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-windbarb-series .highcharts-point {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-lollipop-stem {
|
||||
stroke: #404048;
|
||||
}
|
||||
|
||||
.highcharts-focus-border {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-legend-item-hidden .highcharts-focus-border {
|
||||
fill: none !important;
|
||||
}
|
||||
|
||||
/* Legend */
|
||||
.highcharts-legend-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-legend-item > text {
|
||||
fill: #000;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-legend-item:hover text {
|
||||
fill: #404048;
|
||||
}
|
||||
|
||||
.highcharts-legend-item-hidden * {
|
||||
fill: #cccccc !important;
|
||||
stroke: #cccccc !important;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-legend-nav-active {
|
||||
fill: #003399;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-legend-nav-inactive {
|
||||
fill: #cccccc;
|
||||
}
|
||||
|
||||
circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive {
|
||||
/* tracker */
|
||||
fill: rgba(192, 192, 192, 0.0001);
|
||||
}
|
||||
|
||||
.highcharts-legend-title-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Bubble legend */
|
||||
.highcharts-bubble-legend-symbol {
|
||||
stroke-width: 2;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-bubble-legend-connectors {
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.highcharts-bubble-legend-labels {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.highcharts-loading {
|
||||
position: absolute;
|
||||
background-color: #ffffff;
|
||||
opacity: 0.5;
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-loading-hidden {
|
||||
height: 0 !important;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition: opacity 250ms, height 250ms step-end;
|
||||
}
|
||||
|
||||
.highcharts-loading-inner {
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
/* Plot bands and polar pane backgrounds */
|
||||
.highcharts-plot-band, .highcharts-pane {
|
||||
fill: #404048;
|
||||
fill-opacity: 0.05;
|
||||
}
|
||||
|
||||
.highcharts-plot-line {
|
||||
fill: none;
|
||||
stroke: #999999;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
/* Highcharts More and modules */
|
||||
.highcharts-boxplot-box {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-boxplot-median {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-bubble-series .highcharts-point {
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-errorbar-series .highcharts-point {
|
||||
stroke: #404048;
|
||||
}
|
||||
|
||||
.highcharts-gauge-series .highcharts-data-label-box {
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-gauge-series .highcharts-dial {
|
||||
fill: #404048;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-polygon-series .highcharts-graph {
|
||||
fill: inherit;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-waterfall-series .highcharts-graph {
|
||||
stroke: #000;
|
||||
stroke-dasharray: 1, 3;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-link {
|
||||
transition: fill 250ms, fill-opacity 250ms;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
|
||||
transition: fill 50ms, fill-opacity 50ms;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
.highcharts-venn-series .highcharts-point {
|
||||
fill-opacity: 0.75;
|
||||
stroke: #cccccc;
|
||||
transition: stroke 250ms, fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-venn-series .highcharts-point-hover {
|
||||
fill-opacity: 1;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
/* Highstock */
|
||||
.highcharts-navigator-mask-outside {
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.highcharts-navigator-mask-inside {
|
||||
fill: #6685c2;
|
||||
/* navigator.maskFill option */
|
||||
fill-opacity: 0.25;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.highcharts-navigator-outline {
|
||||
stroke: #cccccc;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-navigator-handle {
|
||||
stroke: #cccccc;
|
||||
fill: #f2f2f2;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series {
|
||||
fill: #335cad;
|
||||
stroke: #335cad;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series .highcharts-graph {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series .highcharts-area {
|
||||
fill-opacity: 0.05;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis .highcharts-axis-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis .highcharts-grid-line {
|
||||
stroke-width: 1px;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis.highcharts-axis-labels {
|
||||
fill: #999999;
|
||||
}
|
||||
|
||||
.highcharts-navigator-yaxis .highcharts-grid-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-thumb {
|
||||
fill: #cccccc;
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-button {
|
||||
fill: #e6e6e6;
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-arrow {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-rifles {
|
||||
stroke: #666666;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-track {
|
||||
fill: #f2f2f2;
|
||||
stroke: #f2f2f2;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-button {
|
||||
fill: #f7f7f7;
|
||||
stroke: #cccccc;
|
||||
cursor: default;
|
||||
stroke-width: 1px;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-button text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-button-hover {
|
||||
transition: fill 0ms;
|
||||
fill: #e6e6e6;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-button-hover text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-button-pressed {
|
||||
font-weight: bold;
|
||||
fill: #e6ebf5;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-button-pressed text {
|
||||
fill: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-button-disabled text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-range-selector-buttons .highcharts-button {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.highcharts-range-label rect {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-range-label text {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-range-input rect {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-range-input text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-range-input {
|
||||
stroke-width: 1px;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
input.highcharts-range-selector {
|
||||
position: absolute;
|
||||
border: 0;
|
||||
width: 1px;
|
||||
/* Chrome needs a pixel to see it */
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
left: -9em;
|
||||
/* #4798 */
|
||||
}
|
||||
|
||||
.highcharts-crosshair-label text {
|
||||
fill: #ffffff;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-label .highcharts-label-box {
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.highcharts-candlestick-series .highcharts-point {
|
||||
stroke: #404048;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-candlestick-series .highcharts-point-up {
|
||||
fill: #ffffff;
|
||||
}
|
||||
|
||||
.highcharts-ohlc-series .highcharts-point-hover {
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point .highcharts-label-box {
|
||||
stroke: #999999;
|
||||
fill: #ffffff;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
|
||||
stroke: #404048;
|
||||
fill: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point text {
|
||||
fill: #404048;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Highmaps */
|
||||
.highcharts-map-series .highcharts-point {
|
||||
transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-map-series .highcharts-point-hover {
|
||||
transition: fill 0ms, fill-opacity 0ms;
|
||||
fill-opacity: 0.5;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-mapline-series .highcharts-point {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-heatmap-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-map-navigation {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.highcharts-coloraxis {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-coloraxis-marker {
|
||||
fill: #999999;
|
||||
}
|
||||
|
||||
.highcharts-null-point {
|
||||
fill: #f7f7f7;
|
||||
}
|
||||
|
||||
/* 3d charts */
|
||||
.highcharts-3d-frame {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
/* Exporting module */
|
||||
.highcharts-contextbutton {
|
||||
fill: #ffffff;
|
||||
/* needed to capture hover */
|
||||
stroke: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.highcharts-contextbutton:hover {
|
||||
fill: #e6e6e6;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-button-symbol {
|
||||
stroke: #666666;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.highcharts-menu {
|
||||
border: 1px solid #999999;
|
||||
background: #ffffff;
|
||||
padding: 5px 0;
|
||||
box-shadow: 3px 3px 10px #888;
|
||||
}
|
||||
|
||||
.highcharts-menu-item {
|
||||
padding: 0.5em 1em;
|
||||
background: none;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
transition: background 250ms, color 250ms;
|
||||
}
|
||||
|
||||
.highcharts-menu-item:hover {
|
||||
background: #335cad;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* Drilldown module */
|
||||
.highcharts-drilldown-point {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-drilldown-data-label text,
|
||||
text.highcharts-drilldown-data-label,
|
||||
.highcharts-drilldown-axis-label {
|
||||
cursor: pointer;
|
||||
fill: #003399;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* No-data module */
|
||||
.highcharts-no-data text {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
/* Drag-panes module */
|
||||
.highcharts-axis-resizer {
|
||||
cursor: ns-resize;
|
||||
stroke: black;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
/* Bullet type series */
|
||||
.highcharts-bullet-target {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Lineargauge type series */
|
||||
.highcharts-lineargauge-target {
|
||||
stroke-width: 1px;
|
||||
stroke: #000;
|
||||
}
|
||||
|
||||
.highcharts-lineargauge-target-line {
|
||||
stroke-width: 1px;
|
||||
stroke: #000;
|
||||
}
|
||||
|
||||
/* Annotations module */
|
||||
.highcharts-annotation-label-box {
|
||||
stroke-width: 1px;
|
||||
stroke: #404048;
|
||||
fill: #404048;
|
||||
fill-opacity: 0.75;
|
||||
}
|
||||
|
||||
.highcharts-annotation-label text {
|
||||
fill: #e6e6e6;
|
||||
}
|
||||
|
||||
/* Gantt */
|
||||
.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-point-connecting-path {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-grid-axis .highcharts-tick {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-grid-axis .highcharts-axis-line {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Global font
|
||||
@import 'https://fonts.googleapis.com/css?family=Dosis:400,600';
|
||||
|
||||
// Colors for data series and points.
|
||||
$colors: #7cb5ec #f7a35c #90ee7e #7798BF #aaeeee #ff0066 #eeaaee #55BF3B #DF5353 #7798BF #aaeeee;
|
||||
|
||||
// Neutral colors
|
||||
$neutral-color-100: #404048;
|
||||
$neutral-color-80: #000;
|
||||
|
||||
// Fonts
|
||||
$font-family: 'Dosis', Arial, Helvetica, sans-serif;
|
||||
$title-font-size: 16px;
|
||||
$legend-font-size: 13px;
|
||||
$axis-labels-font-size: 12px;
|
||||
|
||||
// Tooltip
|
||||
$tooltip-border: 0px;
|
||||
$tooltip-background: rgba(219,219,216,0.8);
|
||||
|
||||
// Axes
|
||||
$xaxis-grid-line: 1px !default;
|
||||
|
||||
// Title
|
||||
.highcharts-title, .highcharts-subtitle, .highcharts-yaxis .highcharts-axis-title {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.highcharts-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@import '../highcharts';
|
||||
@@ -0,0 +1,980 @@
|
||||
@import 'https://fonts.googleapis.com/css?family=Signika:400,700';
|
||||
.highcharts-container {
|
||||
background: url(https://www.highcharts.com/samples/graphics/sand.png);
|
||||
}
|
||||
|
||||
.highcharts-boxplot-box {
|
||||
fill: #505053;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis .highcharts-grid-line {
|
||||
stroke: #D0D0D8;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-track {
|
||||
stroke: #C0C0C8;
|
||||
}
|
||||
|
||||
.highcharts-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-button-box {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
/**
|
||||
* @license Highcharts
|
||||
*
|
||||
* (c) 2009-2016 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*/
|
||||
.highcharts-container {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: left;
|
||||
line-height: normal;
|
||||
z-index: 0;
|
||||
/* #1072 */
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
font-family: "Signika", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.highcharts-root {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.highcharts-root text {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-emphasized {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.highcharts-anchor {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-background {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-plot-border, .highcharts-plot-background {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-label-box {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-button-box {
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.highcharts-tracker-line {
|
||||
stroke-linejoin: round;
|
||||
stroke: rgba(192, 192, 192, 0.0001);
|
||||
stroke-width: 22;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-tracker-area {
|
||||
fill: rgba(192, 192, 192, 0.0001);
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
.highcharts-title {
|
||||
fill: #000;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.highcharts-subtitle {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
/* Axes */
|
||||
.highcharts-axis-line {
|
||||
fill: none;
|
||||
stroke: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-yaxis .highcharts-axis-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-axis-title {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-axis-labels {
|
||||
fill: #666666;
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.highcharts-grid-line {
|
||||
fill: none;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-xaxis-grid .highcharts-grid-line {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.highcharts-tick {
|
||||
stroke: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-yaxis .highcharts-tick {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-minor-grid-line {
|
||||
stroke: #f2f2f2;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-thin {
|
||||
stroke-width: 1px;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-category {
|
||||
stroke: #ccd6eb;
|
||||
stroke-opacity: 0.25;
|
||||
}
|
||||
|
||||
/* Credits */
|
||||
.highcharts-credits {
|
||||
cursor: pointer;
|
||||
fill: #999999;
|
||||
font-size: 0.7em;
|
||||
transition: fill 250ms, font-size 250ms;
|
||||
}
|
||||
|
||||
.highcharts-credits:hover {
|
||||
fill: black;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* Tooltip */
|
||||
.highcharts-tooltip {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
transition: stroke 150ms;
|
||||
}
|
||||
|
||||
.highcharts-tooltip text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-tooltip .highcharts-header {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.highcharts-tooltip-box {
|
||||
stroke-width: 0px;
|
||||
fill: #fff;
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
|
||||
.highcharts-tooltip-box .highcharts-label-box {
|
||||
fill: #fff;
|
||||
fill-opacity: 0.85;
|
||||
}
|
||||
|
||||
div.highcharts-tooltip {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.highcharts-selection-marker {
|
||||
fill: #335cad;
|
||||
fill-opacity: 0.25;
|
||||
}
|
||||
|
||||
.highcharts-graph {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.highcharts-empty-series {
|
||||
stroke-width: 1px;
|
||||
fill: none;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-state-hover .highcharts-graph {
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.highcharts-point-inactive {
|
||||
opacity: 0.2;
|
||||
transition: opacity 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-series-inactive {
|
||||
opacity: 0.2;
|
||||
transition: opacity 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-state-hover path {
|
||||
transition: stroke-width 50ms;
|
||||
/* quick in */
|
||||
}
|
||||
|
||||
.highcharts-state-normal path {
|
||||
transition: stroke-width 250ms;
|
||||
/* slow out */
|
||||
}
|
||||
|
||||
/* Legend hover affects points and series */
|
||||
g.highcharts-series,
|
||||
.highcharts-point,
|
||||
.highcharts-markers,
|
||||
.highcharts-data-labels {
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-legend-series-active g.highcharts-series:not(.highcharts-series-hover),
|
||||
.highcharts-legend-point-active .highcharts-point:not(.highcharts-point-hover),
|
||||
.highcharts-legend-series-active .highcharts-markers:not(.highcharts-series-hover),
|
||||
.highcharts-legend-series-active .highcharts-data-labels:not(.highcharts-series-hover) {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/* Series options */
|
||||
/* Default colors */
|
||||
.highcharts-color-0 {
|
||||
fill: #f45b5b;
|
||||
stroke: #f45b5b;
|
||||
}
|
||||
|
||||
.highcharts-color-1 {
|
||||
fill: #8085e9;
|
||||
stroke: #8085e9;
|
||||
}
|
||||
|
||||
.highcharts-color-2 {
|
||||
fill: #8d4654;
|
||||
stroke: #8d4654;
|
||||
}
|
||||
|
||||
.highcharts-color-3 {
|
||||
fill: #7798BF;
|
||||
stroke: #7798BF;
|
||||
}
|
||||
|
||||
.highcharts-color-4 {
|
||||
fill: #aaeeee;
|
||||
stroke: #aaeeee;
|
||||
}
|
||||
|
||||
.highcharts-color-5 {
|
||||
fill: #ff0066;
|
||||
stroke: #ff0066;
|
||||
}
|
||||
|
||||
.highcharts-color-6 {
|
||||
fill: #eeaaee;
|
||||
stroke: #eeaaee;
|
||||
}
|
||||
|
||||
.highcharts-color-7 {
|
||||
fill: #55BF3B;
|
||||
stroke: #55BF3B;
|
||||
}
|
||||
|
||||
.highcharts-color-8 {
|
||||
fill: #DF5353;
|
||||
stroke: #DF5353;
|
||||
}
|
||||
|
||||
.highcharts-color-9 {
|
||||
fill: #7798BF;
|
||||
stroke: #7798BF;
|
||||
}
|
||||
|
||||
.highcharts-color-10 {
|
||||
fill: #aaeeee;
|
||||
stroke: #aaeeee;
|
||||
}
|
||||
|
||||
.highcharts-area {
|
||||
fill-opacity: 0.75;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-markers {
|
||||
stroke-width: 1px;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.highcharts-a11y-markers-hidden .highcharts-point:not(.highcharts-point-hover):not(.highcharts-a11y-marker-visible),
|
||||
.highcharts-a11y-marker-hidden {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.highcharts-point {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-dense-data .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-data-label {
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-data-label-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-data-label text, text.highcharts-data-label {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-data-label-connector {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-data-label-hidden {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.highcharts-halo {
|
||||
fill-opacity: 0.25;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-series:not(.highcharts-pie-series) .highcharts-point-select,
|
||||
.highcharts-markers .highcharts-point-select {
|
||||
fill: #cccccc;
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
.highcharts-column-series rect.highcharts-point {
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.highcharts-column-series .highcharts-point {
|
||||
transition: fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-column-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-pie-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.highcharts-pie-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-funnel-series .highcharts-point-select {
|
||||
fill: inherit;
|
||||
stroke: inherit;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point {
|
||||
stroke-linejoin: round;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point-hover {
|
||||
fill-opacity: 0.75;
|
||||
transition: fill-opacity 50ms;
|
||||
}
|
||||
|
||||
.highcharts-pyramid-series .highcharts-point-select {
|
||||
fill: inherit;
|
||||
stroke: inherit;
|
||||
}
|
||||
|
||||
.highcharts-solidgauge-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-point {
|
||||
stroke-width: 1px;
|
||||
stroke: #e6e6e6;
|
||||
transition: stroke 250ms, fill 250ms, fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-point-hover {
|
||||
stroke: #999999;
|
||||
transition: stroke 25ms, fill 25ms, fill-opacity 25ms;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-above-level {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node-interactive {
|
||||
fill-opacity: 0.15;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-treemap-series .highcharts-internal-node-interactive:hover {
|
||||
fill-opacity: 0.75;
|
||||
}
|
||||
|
||||
.highcharts-vector-series .highcharts-point {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-windbarb-series .highcharts-point {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-lollipop-stem {
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
.highcharts-focus-border {
|
||||
fill: none;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-legend-item-hidden .highcharts-focus-border {
|
||||
fill: none !important;
|
||||
}
|
||||
|
||||
/* Legend */
|
||||
.highcharts-legend-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-legend-item > text {
|
||||
fill: #000;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-legend-item:hover text {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.highcharts-legend-item-hidden * {
|
||||
fill: #cccccc !important;
|
||||
stroke: #cccccc !important;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-legend-nav-active {
|
||||
fill: #003399;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-legend-nav-inactive {
|
||||
fill: #cccccc;
|
||||
}
|
||||
|
||||
circle.highcharts-legend-nav-active, circle.highcharts-legend-nav-inactive {
|
||||
/* tracker */
|
||||
fill: rgba(192, 192, 192, 0.0001);
|
||||
}
|
||||
|
||||
.highcharts-legend-title-box {
|
||||
fill: none;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Bubble legend */
|
||||
.highcharts-bubble-legend-symbol {
|
||||
stroke-width: 2;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-bubble-legend-connectors {
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.highcharts-bubble-legend-labels {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.highcharts-loading {
|
||||
position: absolute;
|
||||
background-color: none;
|
||||
opacity: 0.5;
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
transition: opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-loading-hidden {
|
||||
height: 0 !important;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
transition: opacity 250ms, height 250ms step-end;
|
||||
}
|
||||
|
||||
.highcharts-loading-inner {
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
top: 45%;
|
||||
}
|
||||
|
||||
/* Plot bands and polar pane backgrounds */
|
||||
.highcharts-plot-band, .highcharts-pane {
|
||||
fill: #fff;
|
||||
fill-opacity: 0.05;
|
||||
}
|
||||
|
||||
.highcharts-plot-line {
|
||||
fill: none;
|
||||
stroke: #999999;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
/* Highcharts More and modules */
|
||||
.highcharts-boxplot-box {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-boxplot-median {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-bubble-series .highcharts-point {
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-errorbar-series .highcharts-point {
|
||||
stroke: #fff;
|
||||
}
|
||||
|
||||
.highcharts-gauge-series .highcharts-data-label-box {
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-gauge-series .highcharts-dial {
|
||||
fill: #fff;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-polygon-series .highcharts-graph {
|
||||
fill: inherit;
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-waterfall-series .highcharts-graph {
|
||||
stroke: #000;
|
||||
stroke-dasharray: 1, 3;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-link {
|
||||
transition: fill 250ms, fill-opacity 250ms;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.highcharts-sankey-series .highcharts-point-hover.highcharts-link {
|
||||
transition: fill 50ms, fill-opacity 50ms;
|
||||
fill-opacity: 1;
|
||||
}
|
||||
|
||||
.highcharts-venn-series .highcharts-point {
|
||||
fill-opacity: 0.75;
|
||||
stroke: #cccccc;
|
||||
transition: stroke 250ms, fill-opacity 250ms;
|
||||
}
|
||||
|
||||
.highcharts-venn-series .highcharts-point-hover {
|
||||
fill-opacity: 1;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
/* Highstock */
|
||||
.highcharts-navigator-mask-outside {
|
||||
fill-opacity: 0;
|
||||
}
|
||||
|
||||
.highcharts-navigator-mask-inside {
|
||||
fill: #6685c2;
|
||||
/* navigator.maskFill option */
|
||||
fill-opacity: 0.25;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.highcharts-navigator-outline {
|
||||
stroke: #cccccc;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-navigator-handle {
|
||||
stroke: #cccccc;
|
||||
fill: #f2f2f2;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series {
|
||||
fill: #f45b5b;
|
||||
stroke: #f45b5b;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series .highcharts-graph {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-navigator-series .highcharts-area {
|
||||
fill-opacity: 0.05;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis .highcharts-axis-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis .highcharts-grid-line {
|
||||
stroke-width: 1px;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-navigator-xaxis.highcharts-axis-labels {
|
||||
fill: #999999;
|
||||
}
|
||||
|
||||
.highcharts-navigator-yaxis .highcharts-grid-line {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-thumb {
|
||||
fill: #cccccc;
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-button {
|
||||
fill: #e6e6e6;
|
||||
stroke: #cccccc;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-arrow {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-rifles {
|
||||
stroke: #666666;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-scrollbar-track {
|
||||
fill: #f2f2f2;
|
||||
stroke: #C0C0C8;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-button {
|
||||
fill: #fff;
|
||||
stroke: #C0C0C8;
|
||||
cursor: default;
|
||||
stroke-width: 1px;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-button text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-button-hover {
|
||||
transition: fill 0ms;
|
||||
fill: #e6e6e6;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-button-hover text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-button-pressed {
|
||||
font-weight: bold;
|
||||
fill: #D0D0D8;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-button-pressed text {
|
||||
fill: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.highcharts-button-disabled text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-range-selector-buttons .highcharts-button {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.highcharts-range-label rect {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-range-label text {
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
.highcharts-range-input rect {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-range-input text {
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
.highcharts-range-input {
|
||||
stroke-width: 1px;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
input.highcharts-range-selector {
|
||||
position: absolute;
|
||||
border: 0;
|
||||
width: 1px;
|
||||
/* Chrome needs a pixel to see it */
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
left: -9em;
|
||||
/* #4798 */
|
||||
}
|
||||
|
||||
.highcharts-crosshair-label text {
|
||||
fill: none;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.highcharts-crosshair-label .highcharts-label-box {
|
||||
fill: inherit;
|
||||
}
|
||||
|
||||
.highcharts-candlestick-series .highcharts-point {
|
||||
stroke: #fff;
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-candlestick-series .highcharts-point-up {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-ohlc-series .highcharts-point-hover {
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point .highcharts-label-box {
|
||||
stroke: #999999;
|
||||
fill: none;
|
||||
transition: fill 250ms;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point-hover .highcharts-label-box {
|
||||
stroke: #fff;
|
||||
fill: #ccd6eb;
|
||||
}
|
||||
|
||||
.highcharts-flags-series .highcharts-point text {
|
||||
fill: #fff;
|
||||
font-size: 0.9em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Highmaps */
|
||||
.highcharts-map-series .highcharts-point {
|
||||
transition: fill 500ms, fill-opacity 500ms, stroke-width 250ms;
|
||||
stroke: #cccccc;
|
||||
}
|
||||
|
||||
.highcharts-map-series .highcharts-point-hover {
|
||||
transition: fill 0ms, fill-opacity 0ms;
|
||||
fill-opacity: 0.5;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.highcharts-mapline-series .highcharts-point {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-heatmap-series .highcharts-point {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-map-navigation {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.highcharts-coloraxis {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
.highcharts-coloraxis-marker {
|
||||
fill: #999999;
|
||||
}
|
||||
|
||||
.highcharts-null-point {
|
||||
fill: #f7f7f7;
|
||||
}
|
||||
|
||||
/* 3d charts */
|
||||
.highcharts-3d-frame {
|
||||
fill: transparent;
|
||||
}
|
||||
|
||||
/* Exporting module */
|
||||
.highcharts-contextbutton {
|
||||
fill: #fff;
|
||||
/* needed to capture hover */
|
||||
stroke: none;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.highcharts-contextbutton:hover {
|
||||
fill: #e6e6e6;
|
||||
stroke: #e6e6e6;
|
||||
}
|
||||
|
||||
.highcharts-button-symbol {
|
||||
stroke: #666666;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.highcharts-menu {
|
||||
border: 1px solid #999999;
|
||||
background: none;
|
||||
padding: 5px 0;
|
||||
box-shadow: 3px 3px 10px #888;
|
||||
}
|
||||
|
||||
.highcharts-menu-item {
|
||||
padding: 0.5em 1em;
|
||||
background: none;
|
||||
color: #000;
|
||||
cursor: pointer;
|
||||
transition: background 250ms, color 250ms;
|
||||
}
|
||||
|
||||
.highcharts-menu-item:hover {
|
||||
background: #335cad;
|
||||
color: none;
|
||||
}
|
||||
|
||||
/* Drilldown module */
|
||||
.highcharts-drilldown-point {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-drilldown-data-label text,
|
||||
text.highcharts-drilldown-data-label,
|
||||
.highcharts-drilldown-axis-label {
|
||||
cursor: pointer;
|
||||
fill: #003399;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* No-data module */
|
||||
.highcharts-no-data text {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
fill: #666666;
|
||||
}
|
||||
|
||||
/* Drag-panes module */
|
||||
.highcharts-axis-resizer {
|
||||
cursor: ns-resize;
|
||||
stroke: black;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
/* Bullet type series */
|
||||
.highcharts-bullet-target {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
/* Lineargauge type series */
|
||||
.highcharts-lineargauge-target {
|
||||
stroke-width: 1px;
|
||||
stroke: #000;
|
||||
}
|
||||
|
||||
.highcharts-lineargauge-target-line {
|
||||
stroke-width: 1px;
|
||||
stroke: #000;
|
||||
}
|
||||
|
||||
/* Annotations module */
|
||||
.highcharts-annotation-label-box {
|
||||
stroke-width: 1px;
|
||||
stroke: #fff;
|
||||
fill: #fff;
|
||||
fill-opacity: 0.75;
|
||||
}
|
||||
|
||||
.highcharts-annotation-label text {
|
||||
fill: #e6e6e6;
|
||||
}
|
||||
|
||||
/* Gantt */
|
||||
.highcharts-treegrid-node-collapsed, .highcharts-treegrid-node-expanded {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.highcharts-point-connecting-path {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.highcharts-grid-axis .highcharts-tick {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
.highcharts-grid-axis .highcharts-axis-line {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// Global font
|
||||
@import 'https://fonts.googleapis.com/css?family=Signika:400,700';
|
||||
|
||||
// Chart background, point stroke for markers and columns etc
|
||||
$background-color: none;
|
||||
|
||||
// Colors for data series and points.
|
||||
$colors: #f45b5b #8085e9 #8d4654 #7798BF #aaeeee #ff0066 #eeaaee #55BF3B #DF5353 #7798BF #aaeeee;
|
||||
|
||||
// Neutral colors
|
||||
$neutral-color-100: #fff;
|
||||
$neutral-color-80: #000;
|
||||
|
||||
// Data-labels
|
||||
$data-label-color: #000;
|
||||
|
||||
// Fonts
|
||||
$font-family: 'Signika', Arial, Helvetica, sans-serif;
|
||||
$title-font-size: 16px;
|
||||
$legend-font-size: 13px;
|
||||
$axis-labels-font-size: 12px;
|
||||
|
||||
// Tooltip
|
||||
$tooltip-border: 0px;
|
||||
$tooltip-background: $neutral-color-100;
|
||||
|
||||
// Buttons
|
||||
$highcharts-button-background: $neutral-color-100;
|
||||
$highcharts-button-border: #C0C0C8;
|
||||
$highcharts-button-text: #000;
|
||||
|
||||
$highcharts-button-pressed-background: #D0D0D8;
|
||||
$highcharts-button-pressed-text: #000;
|
||||
|
||||
$context-button-background: $neutral-color-100;
|
||||
|
||||
// Navigator
|
||||
$navigator-series-fill: #f45b5b;
|
||||
$navigator-series-border: #f45b5b;
|
||||
|
||||
// Scrollbar
|
||||
$scrollbar-track-border: #C0C0C8;
|
||||
|
||||
// General
|
||||
.highcharts-container {
|
||||
background: url(https://www.highcharts.com/samples/graphics/sand.png);
|
||||
}
|
||||
|
||||
// Boxplot
|
||||
.highcharts-boxplot-box {
|
||||
fill: #505053;
|
||||
}
|
||||
|
||||
// Navigator
|
||||
.highcharts-navigator-xaxis .highcharts-grid-line {
|
||||
stroke: #D0D0D8;
|
||||
}
|
||||
|
||||
// Scrollbar
|
||||
.highcharts-scrollbar-track {
|
||||
stroke: #C0C0C8;
|
||||
}
|
||||
|
||||
// Title
|
||||
.highcharts-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
.highcharts-button-box {
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
@import '../highcharts';
|
||||
@@ -0,0 +1,251 @@
|
||||
/* *
|
||||
*
|
||||
* Accessibility module - internationalization support
|
||||
*
|
||||
* (c) 2010-2020 Highsoft AS
|
||||
* Author: Øystein Moseng
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Core/Globals.js';
|
||||
import U from '../Core/Utilities.js';
|
||||
var format = U.format, pick = U.pick;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* String trim that works for IE6-8 as well.
|
||||
*
|
||||
* @private
|
||||
* @function stringTrim
|
||||
*
|
||||
* @param {string} str
|
||||
* The input string
|
||||
*
|
||||
* @return {string}
|
||||
* The trimmed string
|
||||
*/
|
||||
function stringTrim(str) {
|
||||
return str.trim && str.trim() || str.replace(/^\s+|\s+$/g, '');
|
||||
}
|
||||
/**
|
||||
* i18n utility function. Format a single array or plural statement in a format
|
||||
* string. If the statement is not an array or plural statement, returns the
|
||||
* statement within brackets. Invalid array statements return an empty string.
|
||||
*
|
||||
* @private
|
||||
* @function formatExtendedStatement
|
||||
*
|
||||
* @param {string} statement
|
||||
*
|
||||
* @param {Highcharts.Dictionary<*>} ctx
|
||||
* Context to apply to the format string.
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
function formatExtendedStatement(statement, ctx) {
|
||||
var eachStart = statement.indexOf('#each('), pluralStart = statement.indexOf('#plural('), indexStart = statement.indexOf('['), indexEnd = statement.indexOf(']'), arr, result;
|
||||
// Dealing with an each-function?
|
||||
if (eachStart > -1) {
|
||||
var eachEnd = statement.slice(eachStart).indexOf(')') + eachStart, preEach = statement.substring(0, eachStart), postEach = statement.substring(eachEnd + 1), eachStatement = statement.substring(eachStart + 6, eachEnd), eachArguments = eachStatement.split(','), lenArg = Number(eachArguments[1]), len;
|
||||
result = '';
|
||||
arr = ctx[eachArguments[0]];
|
||||
if (arr) {
|
||||
lenArg = isNaN(lenArg) ? arr.length : lenArg;
|
||||
len = lenArg < 0 ?
|
||||
arr.length + lenArg :
|
||||
Math.min(lenArg, arr.length); // Overshoot
|
||||
// Run through the array for the specified length
|
||||
for (var i = 0; i < len; ++i) {
|
||||
result += preEach + arr[i] + postEach;
|
||||
}
|
||||
}
|
||||
return result.length ? result : '';
|
||||
}
|
||||
// Dealing with a plural-function?
|
||||
if (pluralStart > -1) {
|
||||
var pluralEnd = statement.slice(pluralStart).indexOf(')') + pluralStart, pluralStatement = statement.substring(pluralStart + 8, pluralEnd), pluralArguments = pluralStatement.split(','), num = Number(ctx[pluralArguments[0]]);
|
||||
switch (num) {
|
||||
case 0:
|
||||
result = pick(pluralArguments[4], pluralArguments[1]);
|
||||
break;
|
||||
case 1:
|
||||
result = pick(pluralArguments[2], pluralArguments[1]);
|
||||
break;
|
||||
case 2:
|
||||
result = pick(pluralArguments[3], pluralArguments[1]);
|
||||
break;
|
||||
default:
|
||||
result = pluralArguments[1];
|
||||
}
|
||||
return result ? stringTrim(result) : '';
|
||||
}
|
||||
// Array index
|
||||
if (indexStart > -1) {
|
||||
var arrayName = statement.substring(0, indexStart), ix = Number(statement.substring(indexStart + 1, indexEnd)), val;
|
||||
arr = ctx[arrayName];
|
||||
if (!isNaN(ix) && arr) {
|
||||
if (ix < 0) {
|
||||
val = arr[arr.length + ix];
|
||||
// Handle negative overshoot
|
||||
if (typeof val === 'undefined') {
|
||||
val = arr[0];
|
||||
}
|
||||
}
|
||||
else {
|
||||
val = arr[ix];
|
||||
// Handle positive overshoot
|
||||
if (typeof val === 'undefined') {
|
||||
val = arr[arr.length - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
return typeof val !== 'undefined' ? val : '';
|
||||
}
|
||||
// Standard substitution, delegate to format or similar
|
||||
return '{' + statement + '}';
|
||||
}
|
||||
/**
|
||||
* i18n formatting function. Extends Highcharts.format() functionality by also
|
||||
* handling arrays and plural conditionals. Arrays can be indexed as follows:
|
||||
*
|
||||
* - Format: 'This is the first index: {myArray[0]}. The last: {myArray[-1]}.'
|
||||
*
|
||||
* - Context: { myArray: [0, 1, 2, 3, 4, 5] }
|
||||
*
|
||||
* - Result: 'This is the first index: 0. The last: 5.'
|
||||
*
|
||||
*
|
||||
* They can also be iterated using the #each() function. This will repeat the
|
||||
* contents of the bracket expression for each element. Example:
|
||||
*
|
||||
* - Format: 'List contains: {#each(myArray)cm }'
|
||||
*
|
||||
* - Context: { myArray: [0, 1, 2] }
|
||||
*
|
||||
* - Result: 'List contains: 0cm 1cm 2cm '
|
||||
*
|
||||
*
|
||||
* The #each() function optionally takes a length parameter. If positive, this
|
||||
* parameter specifies the max number of elements to iterate through. If
|
||||
* negative, the function will subtract the number from the length of the array.
|
||||
* Use this to stop iterating before the array ends. Example:
|
||||
*
|
||||
* - Format: 'List contains: {#each(myArray, -1) }and {myArray[-1]}.'
|
||||
*
|
||||
* - Context: { myArray: [0, 1, 2, 3] }
|
||||
*
|
||||
* - Result: 'List contains: 0, 1, 2, and 3.'
|
||||
*
|
||||
*
|
||||
* Use the #plural() function to pick a string depending on whether or not a
|
||||
* context object is 1. Arguments are #plural(obj, plural, singular). Example:
|
||||
*
|
||||
* - Format: 'Has {numPoints} {#plural(numPoints, points, point}.'
|
||||
*
|
||||
* - Context: { numPoints: 5 }
|
||||
*
|
||||
* - Result: 'Has 5 points.'
|
||||
*
|
||||
*
|
||||
* Optionally there are additional parameters for dual and none: #plural(obj,
|
||||
* plural, singular, dual, none). Example:
|
||||
*
|
||||
* - Format: 'Has {#plural(numPoints, many points, one point, two points,
|
||||
* none}.'
|
||||
*
|
||||
* - Context: { numPoints: 2 }
|
||||
*
|
||||
* - Result: 'Has two points.'
|
||||
*
|
||||
*
|
||||
* The dual or none parameters will take precedence if they are supplied.
|
||||
*
|
||||
* @requires modules/accessibility
|
||||
*
|
||||
* @function Highcharts.i18nFormat
|
||||
*
|
||||
* @param {string} formatString
|
||||
* The string to format.
|
||||
*
|
||||
* @param {Highcharts.Dictionary<*>} context
|
||||
* Context to apply to the format string.
|
||||
*
|
||||
* @param {Highcharts.Chart} chart
|
||||
* A `Chart` instance with a time object and numberFormatter, passed on
|
||||
* to format().
|
||||
*
|
||||
* @return {string}
|
||||
* The formatted string.
|
||||
*/
|
||||
H.i18nFormat = function (formatString, context, chart) {
|
||||
var getFirstBracketStatement = function (sourceStr, offset) {
|
||||
var str = sourceStr.slice(offset || 0), startBracket = str.indexOf('{'), endBracket = str.indexOf('}');
|
||||
if (startBracket > -1 && endBracket > startBracket) {
|
||||
return {
|
||||
statement: str.substring(startBracket + 1, endBracket),
|
||||
begin: offset + startBracket + 1,
|
||||
end: offset + endBracket
|
||||
};
|
||||
}
|
||||
}, tokens = [], bracketRes, constRes, cursor = 0;
|
||||
// Tokenize format string into bracket statements and constants
|
||||
do {
|
||||
bracketRes = getFirstBracketStatement(formatString, cursor);
|
||||
constRes = formatString.substring(cursor, bracketRes && bracketRes.begin - 1);
|
||||
// If we have constant content before this bracket statement, add it
|
||||
if (constRes.length) {
|
||||
tokens.push({
|
||||
value: constRes,
|
||||
type: 'constant'
|
||||
});
|
||||
}
|
||||
// Add the bracket statement
|
||||
if (bracketRes) {
|
||||
tokens.push({
|
||||
value: bracketRes.statement,
|
||||
type: 'statement'
|
||||
});
|
||||
}
|
||||
cursor = bracketRes ? bracketRes.end + 1 : cursor + 1;
|
||||
} while (bracketRes);
|
||||
// Perform the formatting. The formatArrayStatement function returns the
|
||||
// statement in brackets if it is not an array statement, which means it
|
||||
// gets picked up by format below.
|
||||
tokens.forEach(function (token) {
|
||||
if (token.type === 'statement') {
|
||||
token.value = formatExtendedStatement(token.value, context);
|
||||
}
|
||||
});
|
||||
// Join string back together and pass to format to pick up non-array
|
||||
// statements.
|
||||
return format(tokens.reduce(function (acc, cur) {
|
||||
return acc + cur.value;
|
||||
}, ''), context, chart);
|
||||
};
|
||||
/**
|
||||
* Apply context to a format string from lang options of the chart.
|
||||
*
|
||||
* @requires modules/accessibility
|
||||
*
|
||||
* @function Highcharts.Chart#langFormat
|
||||
*
|
||||
* @param {string} langKey
|
||||
* Key (using dot notation) into lang option structure.
|
||||
*
|
||||
* @param {Highcharts.Dictionary<*>} context
|
||||
* Context to apply to the format string.
|
||||
*
|
||||
* @return {string}
|
||||
* The formatted string.
|
||||
*/
|
||||
H.Chart.prototype.langFormat = function (langKey, context) {
|
||||
var keys = langKey.split('.'), formatString = this.options.lang, i = 0;
|
||||
for (; i < keys.length; ++i) {
|
||||
formatString = formatString && formatString[keys[i]];
|
||||
}
|
||||
return typeof formatString === 'string' ?
|
||||
H.i18nFormat(formatString, context, this) : '';
|
||||
};
|
||||
@@ -0,0 +1,282 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility module for Highcharts
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import ChartUtilities from './Utils/ChartUtilities.js';
|
||||
import H from '../Core/Globals.js';
|
||||
import KeyboardNavigationHandler from './KeyboardNavigationHandler.js';
|
||||
import O from '../Core/Options.js';
|
||||
var defaultOptions = O.defaultOptions;
|
||||
import Point from '../Core/Series/Point.js';
|
||||
import U from '../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, extend = U.extend, fireEvent = U.fireEvent, merge = U.merge;
|
||||
var doc = H.win.document;
|
||||
import AccessibilityComponent from './AccessibilityComponent.js';
|
||||
import KeyboardNavigation from './KeyboardNavigation.js';
|
||||
import LegendComponent from './Components/LegendComponent.js';
|
||||
import MenuComponent from './Components/MenuComponent.js';
|
||||
import SeriesComponent from './Components/SeriesComponent/SeriesComponent.js';
|
||||
import ZoomComponent from './Components/ZoomComponent.js';
|
||||
import RangeSelectorComponent from './Components/RangeSelectorComponent.js';
|
||||
import InfoRegionsComponent from './Components/InfoRegionsComponent.js';
|
||||
import ContainerComponent from './Components/ContainerComponent.js';
|
||||
import whcm from './HighContrastMode.js';
|
||||
import highContrastTheme from './HighContrastTheme.js';
|
||||
import defaultOptionsA11Y from './Options/Options.js';
|
||||
import defaultLangOptions from './Options/LangOptions.js';
|
||||
import copyDeprecatedOptions from './Options/DeprecatedOptions.js';
|
||||
import './A11yI18n.js';
|
||||
import './FocusBorder.js';
|
||||
// Add default options
|
||||
merge(true, defaultOptions, defaultOptionsA11Y, {
|
||||
accessibility: {
|
||||
highContrastTheme: highContrastTheme
|
||||
},
|
||||
lang: defaultLangOptions
|
||||
});
|
||||
// Expose functionality on Highcharts namespace
|
||||
H.A11yChartUtilities = ChartUtilities;
|
||||
H.KeyboardNavigationHandler = KeyboardNavigationHandler;
|
||||
H.AccessibilityComponent = AccessibilityComponent;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* The Accessibility class
|
||||
*
|
||||
* @private
|
||||
* @requires module:modules/accessibility
|
||||
*
|
||||
* @class
|
||||
* @name Highcharts.Accessibility
|
||||
*
|
||||
* @param {Highcharts.Chart} chart
|
||||
* Chart object
|
||||
*/
|
||||
function Accessibility(chart) {
|
||||
this.init(chart);
|
||||
}
|
||||
Accessibility.prototype = {
|
||||
/**
|
||||
* Initialize the accessibility class
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
* Chart object
|
||||
*/
|
||||
init: function (chart) {
|
||||
this.chart = chart;
|
||||
// Abort on old browsers
|
||||
if (!doc.addEventListener || !chart.renderer.isSVG) {
|
||||
chart.renderTo.setAttribute('aria-hidden', true);
|
||||
return;
|
||||
}
|
||||
// Copy over any deprecated options that are used. We could do this on
|
||||
// every update, but it is probably not needed.
|
||||
copyDeprecatedOptions(chart);
|
||||
this.initComponents();
|
||||
this.keyboardNavigation = new KeyboardNavigation(chart, this.components);
|
||||
this.update();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initComponents: function () {
|
||||
var chart = this.chart, a11yOptions = chart.options.accessibility;
|
||||
this.components = {
|
||||
container: new ContainerComponent(),
|
||||
infoRegions: new InfoRegionsComponent(),
|
||||
legend: new LegendComponent(),
|
||||
chartMenu: new MenuComponent(),
|
||||
rangeSelector: new RangeSelectorComponent(),
|
||||
series: new SeriesComponent(),
|
||||
zoom: new ZoomComponent()
|
||||
};
|
||||
if (a11yOptions.customComponents) {
|
||||
extend(this.components, a11yOptions.customComponents);
|
||||
}
|
||||
var components = this.components;
|
||||
this.getComponentOrder().forEach(function (componentName) {
|
||||
components[componentName].initBase(chart);
|
||||
components[componentName].init();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get order to update components in.
|
||||
* @private
|
||||
*/
|
||||
getComponentOrder: function () {
|
||||
if (!this.components) {
|
||||
return []; // For zombie accessibility object on old browsers
|
||||
}
|
||||
if (!this.components.series) {
|
||||
return Object.keys(this.components);
|
||||
}
|
||||
var componentsExceptSeries = Object.keys(this.components)
|
||||
.filter(function (c) { return c !== 'series'; });
|
||||
// Update series first, so that other components can read accessibility
|
||||
// info on points.
|
||||
return ['series'].concat(componentsExceptSeries);
|
||||
},
|
||||
/**
|
||||
* Update all components.
|
||||
*/
|
||||
update: function () {
|
||||
var components = this.components, chart = this.chart, a11yOptions = chart.options.accessibility;
|
||||
fireEvent(chart, 'beforeA11yUpdate');
|
||||
// Update the chart type list as this is used by multiple modules
|
||||
chart.types = this.getChartTypes();
|
||||
// Update markup
|
||||
this.getComponentOrder().forEach(function (componentName) {
|
||||
components[componentName].onChartUpdate();
|
||||
fireEvent(chart, 'afterA11yComponentUpdate', {
|
||||
name: componentName,
|
||||
component: components[componentName]
|
||||
});
|
||||
});
|
||||
// Update keyboard navigation
|
||||
this.keyboardNavigation.update(a11yOptions.keyboardNavigation.order);
|
||||
// Handle high contrast mode
|
||||
if (!chart.highContrastModeActive && // Only do this once
|
||||
whcm.isHighContrastModeActive()) {
|
||||
whcm.setHighContrastTheme(chart);
|
||||
}
|
||||
fireEvent(chart, 'afterA11yUpdate', {
|
||||
accessibility: this
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Destroy all elements.
|
||||
*/
|
||||
destroy: function () {
|
||||
var chart = this.chart || {};
|
||||
// Destroy components
|
||||
var components = this.components;
|
||||
Object.keys(components).forEach(function (componentName) {
|
||||
components[componentName].destroy();
|
||||
components[componentName].destroyBase();
|
||||
});
|
||||
// Kill keyboard nav
|
||||
if (this.keyboardNavigation) {
|
||||
this.keyboardNavigation.destroy();
|
||||
}
|
||||
// Hide container from screen readers if it exists
|
||||
if (chart.renderTo) {
|
||||
chart.renderTo.setAttribute('aria-hidden', true);
|
||||
}
|
||||
// Remove focus border if it exists
|
||||
if (chart.focusElement) {
|
||||
chart.focusElement.removeFocusBorder();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return a list of the types of series we have in the chart.
|
||||
* @private
|
||||
*/
|
||||
getChartTypes: function () {
|
||||
var types = {};
|
||||
this.chart.series.forEach(function (series) {
|
||||
types[series.type] = 1;
|
||||
});
|
||||
return Object.keys(types);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
H.Chart.prototype.updateA11yEnabled = function () {
|
||||
var a11y = this.accessibility, accessibilityOptions = this.options.accessibility;
|
||||
if (accessibilityOptions && accessibilityOptions.enabled) {
|
||||
if (a11y) {
|
||||
a11y.update();
|
||||
}
|
||||
else {
|
||||
this.accessibility = a11y = new Accessibility(this);
|
||||
}
|
||||
}
|
||||
else if (a11y) {
|
||||
// Destroy if after update we have a11y and it is disabled
|
||||
if (a11y.destroy) {
|
||||
a11y.destroy();
|
||||
}
|
||||
delete this.accessibility;
|
||||
}
|
||||
else {
|
||||
// Just hide container
|
||||
this.renderTo.setAttribute('aria-hidden', true);
|
||||
}
|
||||
};
|
||||
// Handle updates to the module and send render updates to components
|
||||
addEvent(H.Chart, 'render', function (e) {
|
||||
// Update/destroy
|
||||
if (this.a11yDirty && this.renderTo) {
|
||||
delete this.a11yDirty;
|
||||
this.updateA11yEnabled();
|
||||
}
|
||||
var a11y = this.accessibility;
|
||||
if (a11y) {
|
||||
a11y.getComponentOrder().forEach(function (componentName) {
|
||||
a11y.components[componentName].onChartRender();
|
||||
});
|
||||
}
|
||||
});
|
||||
// Update with chart/series/point updates
|
||||
addEvent(H.Chart, 'update', function (e) {
|
||||
// Merge new options
|
||||
var newOptions = e.options.accessibility;
|
||||
if (newOptions) {
|
||||
// Handle custom component updating specifically
|
||||
if (newOptions.customComponents) {
|
||||
this.options.accessibility.customComponents =
|
||||
newOptions.customComponents;
|
||||
delete newOptions.customComponents;
|
||||
}
|
||||
merge(true, this.options.accessibility, newOptions);
|
||||
// Recreate from scratch
|
||||
if (this.accessibility && this.accessibility.destroy) {
|
||||
this.accessibility.destroy();
|
||||
delete this.accessibility;
|
||||
}
|
||||
}
|
||||
// Mark dirty for update
|
||||
this.a11yDirty = true;
|
||||
});
|
||||
// Mark dirty for update
|
||||
addEvent(Point, 'update', function () {
|
||||
if (this.series.chart.accessibility) {
|
||||
this.series.chart.a11yDirty = true;
|
||||
}
|
||||
});
|
||||
['addSeries', 'init'].forEach(function (event) {
|
||||
addEvent(H.Chart, event, function () {
|
||||
this.a11yDirty = true;
|
||||
});
|
||||
});
|
||||
['update', 'updatedData', 'remove'].forEach(function (event) {
|
||||
addEvent(H.Series, event, function () {
|
||||
if (this.chart.accessibility) {
|
||||
this.chart.a11yDirty = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
// Direct updates (events happen after render)
|
||||
[
|
||||
'afterDrilldown', 'drillupall'
|
||||
].forEach(function (event) {
|
||||
addEvent(H.Chart, event, function () {
|
||||
if (this.accessibility) {
|
||||
this.accessibility.update();
|
||||
}
|
||||
});
|
||||
});
|
||||
// Destroy with chart
|
||||
addEvent(H.Chart, 'destroy', function () {
|
||||
if (this.accessibility) {
|
||||
this.accessibility.destroy();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,380 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility component class definition
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Core/Globals.js';
|
||||
var win = H.win, doc = win.document;
|
||||
import U from '../Core/Utilities.js';
|
||||
var extend = U.extend, fireEvent = U.fireEvent, merge = U.merge;
|
||||
import HTMLUtilities from './Utils/HTMLUtilities.js';
|
||||
var removeElement = HTMLUtilities.removeElement, getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent;
|
||||
import ChartUtilities from './Utils/ChartUtilities.js';
|
||||
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
|
||||
import EventProvider from './Utils/EventProvider.js';
|
||||
import DOMElementProvider from './Utils/DOMElementProvider.js';
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/** @lends Highcharts.AccessibilityComponent */
|
||||
var functionsToOverrideByDerivedClasses = {
|
||||
/**
|
||||
* Called on component initialization.
|
||||
*/
|
||||
init: function () { },
|
||||
/**
|
||||
* Get keyboard navigation handler for this component.
|
||||
* @return {Highcharts.KeyboardNavigationHandler}
|
||||
*/
|
||||
getKeyboardNavigation: function () { },
|
||||
/**
|
||||
* Called on updates to the chart, including options changes.
|
||||
* Note that this is also called on first render of chart.
|
||||
*/
|
||||
onChartUpdate: function () { },
|
||||
/**
|
||||
* Called on every chart render.
|
||||
*/
|
||||
onChartRender: function () { },
|
||||
/**
|
||||
* Called when accessibility is disabled or chart is destroyed.
|
||||
*/
|
||||
destroy: function () { }
|
||||
};
|
||||
/**
|
||||
* The AccessibilityComponent base class, representing a part of the chart that
|
||||
* has accessibility logic connected to it. This class can be inherited from to
|
||||
* create a custom accessibility component for a chart.
|
||||
*
|
||||
* Components should take care to destroy added elements and unregister event
|
||||
* handlers on destroy. This is handled automatically if using this.addEvent and
|
||||
* this.createElement.
|
||||
*
|
||||
* @sample highcharts/accessibility/custom-component
|
||||
* Custom accessibility component
|
||||
*
|
||||
* @requires module:modules/accessibility
|
||||
* @class
|
||||
* @name Highcharts.AccessibilityComponent
|
||||
*/
|
||||
function AccessibilityComponent() { }
|
||||
/**
|
||||
* @lends Highcharts.AccessibilityComponent
|
||||
*/
|
||||
AccessibilityComponent.prototype = {
|
||||
/**
|
||||
* Initialize the class
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
* Chart object
|
||||
*/
|
||||
initBase: function (chart) {
|
||||
this.chart = chart;
|
||||
this.eventProvider = new EventProvider();
|
||||
this.domElementProvider = new DOMElementProvider();
|
||||
// Key code enum for common keys
|
||||
this.keyCodes = {
|
||||
left: 37,
|
||||
right: 39,
|
||||
up: 38,
|
||||
down: 40,
|
||||
enter: 13,
|
||||
space: 32,
|
||||
esc: 27,
|
||||
tab: 9
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Add an event to an element and keep track of it for later removal.
|
||||
* See EventProvider for details.
|
||||
* @private
|
||||
*/
|
||||
addEvent: function () {
|
||||
return this.eventProvider.addEvent
|
||||
.apply(this.eventProvider, arguments);
|
||||
},
|
||||
/**
|
||||
* Create an element and keep track of it for later removal.
|
||||
* See DOMElementProvider for details.
|
||||
* @private
|
||||
*/
|
||||
createElement: function () {
|
||||
return this.domElementProvider.createElement.apply(this.domElementProvider, arguments);
|
||||
},
|
||||
/**
|
||||
* Fire an event on an element that is either wrapped by Highcharts,
|
||||
* or a DOM element
|
||||
* @private
|
||||
* @param {Highcharts.HTMLElement|Highcharts.HTMLDOMElement|
|
||||
* Highcharts.SVGDOMElement|Highcharts.SVGElement} el
|
||||
* @param {Event} eventObject
|
||||
*/
|
||||
fireEventOnWrappedOrUnwrappedElement: function (el, eventObject) {
|
||||
var type = eventObject.type;
|
||||
if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {
|
||||
if (el.dispatchEvent) {
|
||||
el.dispatchEvent(eventObject);
|
||||
}
|
||||
else {
|
||||
el.fireEvent(type, eventObject);
|
||||
}
|
||||
}
|
||||
else {
|
||||
fireEvent(el, type, eventObject);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Utility function to attempt to fake a click event on an element.
|
||||
* @private
|
||||
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} element
|
||||
*/
|
||||
fakeClickEvent: function (element) {
|
||||
if (element) {
|
||||
var fakeEventObject = getFakeMouseEvent('click');
|
||||
this.fireEventOnWrappedOrUnwrappedElement(element, fakeEventObject);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add a new proxy group to the proxy container. Creates the proxy container
|
||||
* if it does not exist.
|
||||
* @private
|
||||
* @param {Highcharts.HTMLAttributes} [attrs]
|
||||
* The attributes to set on the new group div.
|
||||
* @return {Highcharts.HTMLDOMElement}
|
||||
* The new proxy group element.
|
||||
*/
|
||||
addProxyGroup: function (attrs) {
|
||||
this.createOrUpdateProxyContainer();
|
||||
var groupDiv = this.createElement('div');
|
||||
Object.keys(attrs || {}).forEach(function (prop) {
|
||||
if (attrs[prop] !== null) {
|
||||
groupDiv.setAttribute(prop, attrs[prop]);
|
||||
}
|
||||
});
|
||||
this.chart.a11yProxyContainer.appendChild(groupDiv);
|
||||
return groupDiv;
|
||||
},
|
||||
/**
|
||||
* Creates and updates DOM position of proxy container
|
||||
* @private
|
||||
*/
|
||||
createOrUpdateProxyContainer: function () {
|
||||
var chart = this.chart, rendererSVGEl = chart.renderer.box;
|
||||
chart.a11yProxyContainer = chart.a11yProxyContainer ||
|
||||
this.createProxyContainerElement();
|
||||
if (rendererSVGEl.nextSibling !== chart.a11yProxyContainer) {
|
||||
chart.container.insertBefore(chart.a11yProxyContainer, rendererSVGEl.nextSibling);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {Highcharts.HTMLDOMElement} element
|
||||
*/
|
||||
createProxyContainerElement: function () {
|
||||
var pc = doc.createElement('div');
|
||||
pc.className = 'highcharts-a11y-proxy-container';
|
||||
return pc;
|
||||
},
|
||||
/**
|
||||
* Create an invisible proxy HTML button in the same position as an SVG
|
||||
* element
|
||||
* @private
|
||||
* @param {Highcharts.SVGElement} svgElement
|
||||
* The wrapped svg el to proxy.
|
||||
* @param {Highcharts.HTMLDOMElement} parentGroup
|
||||
* The proxy group element in the proxy container to add this button to.
|
||||
* @param {Highcharts.SVGAttributes} [attributes]
|
||||
* Additional attributes to set.
|
||||
* @param {Highcharts.SVGElement} [posElement]
|
||||
* Element to use for positioning instead of svgElement.
|
||||
* @param {Function} [preClickEvent]
|
||||
* Function to call before click event fires.
|
||||
*
|
||||
* @return {Highcharts.HTMLDOMElement} The proxy button.
|
||||
*/
|
||||
createProxyButton: function (svgElement, parentGroup, attributes, posElement, preClickEvent) {
|
||||
var svgEl = svgElement.element, proxy = this.createElement('button'), attrs = merge({
|
||||
'aria-label': svgEl.getAttribute('aria-label')
|
||||
}, attributes);
|
||||
Object.keys(attrs).forEach(function (prop) {
|
||||
if (attrs[prop] !== null) {
|
||||
proxy.setAttribute(prop, attrs[prop]);
|
||||
}
|
||||
});
|
||||
proxy.className = 'highcharts-a11y-proxy-button';
|
||||
if (preClickEvent) {
|
||||
this.addEvent(proxy, 'click', preClickEvent);
|
||||
}
|
||||
this.setProxyButtonStyle(proxy);
|
||||
this.updateProxyButtonPosition(proxy, posElement || svgElement);
|
||||
this.proxyMouseEventsForButton(svgEl, proxy);
|
||||
// Add to chart div and unhide from screen readers
|
||||
parentGroup.appendChild(proxy);
|
||||
if (!attrs['aria-hidden']) {
|
||||
unhideChartElementFromAT(this.chart, proxy);
|
||||
}
|
||||
return proxy;
|
||||
},
|
||||
/**
|
||||
* Get the position relative to chart container for a wrapped SVG element.
|
||||
* @private
|
||||
* @param {Highcharts.SVGElement} element
|
||||
* The element to calculate position for.
|
||||
* @return {Highcharts.BBoxObject}
|
||||
* Object with x and y props for the position.
|
||||
*/
|
||||
getElementPosition: function (element) {
|
||||
var el = element.element, div = this.chart.renderTo;
|
||||
if (div && el && el.getBoundingClientRect) {
|
||||
var rectEl = el.getBoundingClientRect(), rectDiv = div.getBoundingClientRect();
|
||||
return {
|
||||
x: rectEl.left - rectDiv.left,
|
||||
y: rectEl.top - rectDiv.top,
|
||||
width: rectEl.right - rectEl.left,
|
||||
height: rectEl.bottom - rectEl.top
|
||||
};
|
||||
}
|
||||
return { x: 0, y: 0, width: 1, height: 1 };
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.HTMLElement} button The proxy element.
|
||||
*/
|
||||
setProxyButtonStyle: function (button) {
|
||||
merge(true, button.style, {
|
||||
'border-width': 0,
|
||||
'background-color': 'transparent',
|
||||
cursor: 'pointer',
|
||||
outline: 'none',
|
||||
opacity: 0.001,
|
||||
filter: 'alpha(opacity=1)',
|
||||
'-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)',
|
||||
zIndex: 999,
|
||||
overflow: 'hidden',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
display: 'block',
|
||||
position: 'absolute'
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.HTMLElement} proxy The proxy to update position of.
|
||||
* @param {Highcharts.SVGElement} posElement The element to overlay and take position from.
|
||||
*/
|
||||
updateProxyButtonPosition: function (proxy, posElement) {
|
||||
var bBox = this.getElementPosition(posElement);
|
||||
merge(true, proxy.style, {
|
||||
width: (bBox.width || 1) + 'px',
|
||||
height: (bBox.height || 1) + 'px',
|
||||
left: (bBox.x || 0) + 'px',
|
||||
top: (bBox.y || 0) + 'px'
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.HTMLElement|Highcharts.HTMLDOMElement|
|
||||
* Highcharts.SVGDOMElement|Highcharts.SVGElement} source
|
||||
* @param {Highcharts.HTMLElement} button
|
||||
*/
|
||||
proxyMouseEventsForButton: function (source, button) {
|
||||
var component = this;
|
||||
[
|
||||
'click', 'touchstart', 'touchend', 'touchcancel', 'touchmove',
|
||||
'mouseover', 'mouseenter', 'mouseleave', 'mouseout'
|
||||
].forEach(function (evtType) {
|
||||
var isTouchEvent = evtType.indexOf('touch') === 0;
|
||||
component.addEvent(button, evtType, function (e) {
|
||||
var clonedEvent = isTouchEvent ?
|
||||
component.cloneTouchEvent(e) :
|
||||
component.cloneMouseEvent(e);
|
||||
if (source) {
|
||||
component.fireEventOnWrappedOrUnwrappedElement(source, clonedEvent);
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Utility function to clone a mouse event for re-dispatching.
|
||||
* @private
|
||||
* @param {global.MouseEvent} e The event to clone.
|
||||
* @return {global.MouseEvent} The cloned event
|
||||
*/
|
||||
cloneMouseEvent: function (e) {
|
||||
if (typeof win.MouseEvent === 'function') {
|
||||
return new win.MouseEvent(e.type, e);
|
||||
}
|
||||
// No MouseEvent support, try using initMouseEvent
|
||||
if (doc.createEvent) {
|
||||
var evt = doc.createEvent('MouseEvent');
|
||||
if (evt.initMouseEvent) {
|
||||
evt.initMouseEvent(e.type, e.bubbles, // #10561, #12161
|
||||
e.cancelable, e.view || win, e.detail, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, e.relatedTarget);
|
||||
return evt;
|
||||
}
|
||||
}
|
||||
return getFakeMouseEvent(e.type);
|
||||
},
|
||||
/**
|
||||
* Utility function to clone a touch event for re-dispatching.
|
||||
* @private
|
||||
* @param {global.TouchEvent} e The event to clone.
|
||||
* @return {global.TouchEvent} The cloned event
|
||||
*/
|
||||
cloneTouchEvent: function (e) {
|
||||
var touchListToTouchArray = function (l) {
|
||||
var touchArray = [];
|
||||
for (var i = 0; i < l.length; ++i) {
|
||||
var item = l.item(i);
|
||||
if (item) {
|
||||
touchArray.push(item);
|
||||
}
|
||||
}
|
||||
return touchArray;
|
||||
};
|
||||
if (typeof win.TouchEvent === 'function') {
|
||||
var newEvent = new win.TouchEvent(e.type, {
|
||||
touches: touchListToTouchArray(e.touches),
|
||||
targetTouches: touchListToTouchArray(e.targetTouches),
|
||||
changedTouches: touchListToTouchArray(e.changedTouches),
|
||||
ctrlKey: e.ctrlKey,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
metaKey: e.metaKey,
|
||||
bubbles: e.bubbles,
|
||||
cancelable: e.cancelable,
|
||||
composed: e.composed,
|
||||
detail: e.detail,
|
||||
view: e.view
|
||||
});
|
||||
if (e.defaultPrevented) {
|
||||
newEvent.preventDefault();
|
||||
}
|
||||
return newEvent;
|
||||
}
|
||||
// Fallback to mouse event
|
||||
var fakeEvt = this.cloneMouseEvent(e);
|
||||
fakeEvt.touches = e.touches;
|
||||
fakeEvt.changedTouches = e.changedTouches;
|
||||
fakeEvt.targetTouches = e.targetTouches;
|
||||
return fakeEvt;
|
||||
},
|
||||
/**
|
||||
* Remove traces of the component.
|
||||
* @private
|
||||
*/
|
||||
destroyBase: function () {
|
||||
removeElement(this.chart.a11yProxyContainer);
|
||||
this.domElementProvider.destroyCreatedElements();
|
||||
this.eventProvider.removeAddedEvents();
|
||||
}
|
||||
};
|
||||
extend(AccessibilityComponent.prototype, functionsToOverrideByDerivedClasses);
|
||||
export default AccessibilityComponent;
|
||||
@@ -0,0 +1,134 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2019 Øystein Moseng
|
||||
*
|
||||
* Annotations accessibility code.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import HTMLUtilities from '../Utils/HTMLUtilities.js';
|
||||
var escapeStringForHTML = HTMLUtilities.escapeStringForHTML, stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString;
|
||||
/**
|
||||
* Get list of all annotation labels in the chart.
|
||||
*
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart The chart to get annotation info on.
|
||||
* @return {Array<object>} The labels, or empty array if none.
|
||||
*/
|
||||
function getChartAnnotationLabels(chart) {
|
||||
var annotations = chart.annotations || [];
|
||||
return annotations.reduce(function (acc, cur) {
|
||||
var _a;
|
||||
if (((_a = cur.options) === null || _a === void 0 ? void 0 : _a.visible) !== false) {
|
||||
acc = acc.concat(cur.labels);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
/**
|
||||
* Get the text of an annotation label.
|
||||
*
|
||||
* @private
|
||||
* @param {object} label The annotation label object
|
||||
* @return {string} The text in the label.
|
||||
*/
|
||||
function getLabelText(label) {
|
||||
var _a, _b, _c, _d;
|
||||
var a11yDesc = (_b = (_a = label.options) === null || _a === void 0 ? void 0 : _a.accessibility) === null || _b === void 0 ? void 0 : _b.description;
|
||||
return a11yDesc ? a11yDesc : ((_d = (_c = label.graphic) === null || _c === void 0 ? void 0 : _c.text) === null || _d === void 0 ? void 0 : _d.textStr) || '';
|
||||
}
|
||||
/**
|
||||
* Describe an annotation label.
|
||||
*
|
||||
* @private
|
||||
* @param {object} label The annotation label object to describe
|
||||
* @return {string} The description for the label.
|
||||
*/
|
||||
function getAnnotationLabelDescription(label) {
|
||||
var _a, _b;
|
||||
var a11yDesc = (_b = (_a = label.options) === null || _a === void 0 ? void 0 : _a.accessibility) === null || _b === void 0 ? void 0 : _b.description;
|
||||
if (a11yDesc) {
|
||||
return a11yDesc;
|
||||
}
|
||||
var chart = label.chart;
|
||||
var labelText = getLabelText(label);
|
||||
var points = label.points;
|
||||
var getAriaLabel = function (point) { var _a, _b; return ((_b = (_a = point === null || point === void 0 ? void 0 : point.graphic) === null || _a === void 0 ? void 0 : _a.element) === null || _b === void 0 ? void 0 : _b.getAttribute('aria-label')) || ''; };
|
||||
var getValueDesc = function (point) {
|
||||
var _a;
|
||||
var valDesc = ((_a = point === null || point === void 0 ? void 0 : point.accessibility) === null || _a === void 0 ? void 0 : _a.valueDescription) || getAriaLabel(point);
|
||||
var seriesName = (point === null || point === void 0 ? void 0 : point.series.name) || '';
|
||||
return (seriesName ? seriesName + ', ' : '') + 'data point ' + valDesc;
|
||||
};
|
||||
var pointValueDescriptions = points
|
||||
.filter(function (p) { return !!p.graphic; }) // Filter out mock points
|
||||
.map(getValueDesc)
|
||||
.filter(function (desc) { return !!desc; }); // Filter out points we can't describe
|
||||
var numPoints = pointValueDescriptions.length;
|
||||
var pointsSelector = numPoints > 1 ? 'MultiplePoints' : numPoints ? 'SinglePoint' : 'NoPoints';
|
||||
var langFormatStr = 'accessibility.screenReaderSection.annotations.description' + pointsSelector;
|
||||
var context = {
|
||||
annotationText: labelText,
|
||||
numPoints: numPoints,
|
||||
annotationPoint: pointValueDescriptions[0],
|
||||
additionalAnnotationPoints: pointValueDescriptions.slice(1)
|
||||
};
|
||||
return chart.langFormat(langFormatStr, context);
|
||||
}
|
||||
/**
|
||||
* Return array of HTML strings for each annotation label in the chart.
|
||||
*
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart The chart to get annotation info on.
|
||||
* @return {Array<string>} Array of strings with HTML content for each annotation label.
|
||||
*/
|
||||
function getAnnotationListItems(chart) {
|
||||
var labels = getChartAnnotationLabels(chart);
|
||||
return labels.map(function (label) {
|
||||
var desc = escapeStringForHTML(stripHTMLTagsFromString(getAnnotationLabelDescription(label)));
|
||||
return desc ? "<li>" + desc + "</li>" : '';
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Return the annotation info for a chart as string.
|
||||
*
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart The chart to get annotation info on.
|
||||
* @return {string} String with HTML content or empty string if no annotations.
|
||||
*/
|
||||
function getAnnotationsInfoHTML(chart) {
|
||||
var annotations = chart.annotations;
|
||||
if (!(annotations && annotations.length)) {
|
||||
return '';
|
||||
}
|
||||
var annotationItems = getAnnotationListItems(chart);
|
||||
return "<ul>" + annotationItems.join(' ') + "</ul>";
|
||||
}
|
||||
/**
|
||||
* Return the texts for the annotation(s) connected to a point, or empty array
|
||||
* if none.
|
||||
*
|
||||
* @private
|
||||
* @param {Highcharts.Point} point The data point to get the annotation info from.
|
||||
* @return {Array<string>} Annotation texts
|
||||
*/
|
||||
function getPointAnnotationTexts(point) {
|
||||
var labels = getChartAnnotationLabels(point.series.chart);
|
||||
var pointLabels = labels
|
||||
.filter(function (label) { return label.points.indexOf(point) > -1; });
|
||||
if (!pointLabels.length) {
|
||||
return [];
|
||||
}
|
||||
return pointLabels.map(function (label) { return "" + getLabelText(label); });
|
||||
}
|
||||
var AnnotationsA11y = {
|
||||
getAnnotationsInfoHTML: getAnnotationsInfoHTML,
|
||||
getAnnotationLabelDescription: getAnnotationLabelDescription,
|
||||
getAnnotationListItems: getAnnotationListItems,
|
||||
getPointAnnotationTexts: getPointAnnotationTexts
|
||||
};
|
||||
export default AnnotationsA11y;
|
||||
@@ -0,0 +1,115 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility component for chart container.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
var doc = H.win.document;
|
||||
import U from '../../Core/Utilities.js';
|
||||
var extend = U.extend;
|
||||
import HTMLUtilities from '../Utils/HTMLUtilities.js';
|
||||
var stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
|
||||
import ChartUtilities from '../Utils/ChartUtilities.js';
|
||||
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT, getChartTitle = ChartUtilities.getChartTitle;
|
||||
import AccessibilityComponent from '../AccessibilityComponent.js';
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* The ContainerComponent class
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.ContainerComponent
|
||||
*/
|
||||
var ContainerComponent = function () { };
|
||||
ContainerComponent.prototype = new AccessibilityComponent();
|
||||
extend(ContainerComponent.prototype, /** @lends Highcharts.ContainerComponent */ {
|
||||
/**
|
||||
* Called on first render/updates to the chart, including options changes.
|
||||
*/
|
||||
onChartUpdate: function () {
|
||||
this.handleSVGTitleElement();
|
||||
this.setSVGContainerLabel();
|
||||
this.setGraphicContainerAttrs();
|
||||
this.setRenderToAttrs();
|
||||
this.makeCreditsAccessible();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
handleSVGTitleElement: function () {
|
||||
var chart = this.chart, titleId = 'highcharts-title-' + chart.index, titleContents = stripHTMLTags(chart.langFormat('accessibility.svgContainerTitle', {
|
||||
chartTitle: getChartTitle(chart)
|
||||
}));
|
||||
if (titleContents.length) {
|
||||
var titleElement = this.svgTitleElement =
|
||||
this.svgTitleElement || doc.createElementNS('http://www.w3.org/2000/svg', 'title');
|
||||
titleElement.textContent = titleContents;
|
||||
titleElement.id = titleId;
|
||||
chart.renderTo.insertBefore(titleElement, chart.renderTo.firstChild);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setSVGContainerLabel: function () {
|
||||
var chart = this.chart, svgContainerLabel = stripHTMLTags(chart.langFormat('accessibility.svgContainerLabel', {
|
||||
chartTitle: getChartTitle(chart)
|
||||
}));
|
||||
if (chart.renderer.box && svgContainerLabel.length) {
|
||||
chart.renderer.box.setAttribute('aria-label', svgContainerLabel);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setGraphicContainerAttrs: function () {
|
||||
var chart = this.chart, label = chart.langFormat('accessibility.graphicContainerLabel', {
|
||||
chartTitle: getChartTitle(chart)
|
||||
});
|
||||
if (label.length) {
|
||||
chart.container.setAttribute('aria-label', label);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setRenderToAttrs: function () {
|
||||
var chart = this.chart;
|
||||
if (chart.options.accessibility.landmarkVerbosity !== 'disabled') {
|
||||
chart.renderTo.setAttribute('role', 'region');
|
||||
}
|
||||
else {
|
||||
chart.renderTo.removeAttribute('role');
|
||||
}
|
||||
chart.renderTo.setAttribute('aria-label', chart.langFormat('accessibility.chartContainerLabel', {
|
||||
title: getChartTitle(chart),
|
||||
chart: chart
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
makeCreditsAccessible: function () {
|
||||
var chart = this.chart, credits = chart.credits;
|
||||
if (credits) {
|
||||
if (credits.textStr) {
|
||||
credits.element.setAttribute('aria-label', stripHTMLTags(chart.langFormat('accessibility.credits', { creditsStr: credits.textStr })));
|
||||
}
|
||||
unhideChartElementFromAT(chart, credits.element);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Accessibility disabled/chart destroyed.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.chart.renderTo.setAttribute('aria-hidden', true);
|
||||
}
|
||||
});
|
||||
export default ContainerComponent;
|
||||
@@ -0,0 +1,563 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility component for chart info region and table.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
var doc = H.doc;
|
||||
import U from '../../Core/Utilities.js';
|
||||
var extend = U.extend, format = U.format, pick = U.pick;
|
||||
import AccessibilityComponent from '../AccessibilityComponent.js';
|
||||
import Announcer from '../Utils/Announcer.js';
|
||||
import AnnotationsA11y from './AnnotationsA11y.js';
|
||||
var getAnnotationsInfoHTML = AnnotationsA11y.getAnnotationsInfoHTML;
|
||||
import ChartUtilities from '../Utils/ChartUtilities.js';
|
||||
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT, getChartTitle = ChartUtilities.getChartTitle, getAxisDescription = ChartUtilities.getAxisDescription;
|
||||
import HTMLUtilities from '../Utils/HTMLUtilities.js';
|
||||
var addClass = HTMLUtilities.addClass, setElAttrs = HTMLUtilities.setElAttrs, escapeStringForHTML = HTMLUtilities.escapeStringForHTML, stripHTMLTagsFromString = HTMLUtilities.stripHTMLTagsFromString, getElement = HTMLUtilities.getElement, visuallyHideElement = HTMLUtilities.visuallyHideElement;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getTypeDescForMapChart(chart, formatContext) {
|
||||
return formatContext.mapTitle ?
|
||||
chart.langFormat('accessibility.chartTypes.mapTypeDescription', formatContext) :
|
||||
chart.langFormat('accessibility.chartTypes.unknownMap', formatContext);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getTypeDescForCombinationChart(chart, formatContext) {
|
||||
return chart.langFormat('accessibility.chartTypes.combinationChart', formatContext);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getTypeDescForEmptyChart(chart, formatContext) {
|
||||
return chart.langFormat('accessibility.chartTypes.emptyChart', formatContext);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function buildTypeDescriptionFromSeries(chart, types, context) {
|
||||
var firstType = types[0], typeExplaination = chart.langFormat('accessibility.seriesTypeDescriptions.' + firstType, context), multi = chart.series && chart.series.length < 2 ? 'Single' : 'Multiple';
|
||||
return (chart.langFormat('accessibility.chartTypes.' + firstType + multi, context) ||
|
||||
chart.langFormat('accessibility.chartTypes.default' + multi, context)) + (typeExplaination ? ' ' + typeExplaination : '');
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getTableSummary(chart) {
|
||||
return chart.langFormat('accessibility.table.tableSummary', { chart: chart });
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function stripEmptyHTMLTags(str) {
|
||||
return str.replace(/<(\w+)[^>]*?>\s*<\/\1>/g, '');
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function enableSimpleHTML(str) {
|
||||
return str
|
||||
.replace(/<(h[1-7]|p|div|ul|ol|li)>/g, '<$1>')
|
||||
.replace(/</(h[1-7]|p|div|ul|ol|li|a|button)>/g, '</$1>')
|
||||
.replace(/<(div|a|button) id="([a-zA-Z\-0-9#]*?)">/g, '<$1 id="$2">');
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function stringToSimpleHTML(str) {
|
||||
return stripEmptyHTMLTags(enableSimpleHTML(escapeStringForHTML(str)));
|
||||
}
|
||||
/**
|
||||
* Return simplified explaination of chart type. Some types will not be familiar
|
||||
* to most users, but in those cases we try to add an explaination of the type.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#getTypeDescription
|
||||
* @param {Array<string>} types The series types in this chart.
|
||||
* @return {string} The text description of the chart type.
|
||||
*/
|
||||
H.Chart.prototype.getTypeDescription = function (types) {
|
||||
var firstType = types[0], firstSeries = this.series && this.series[0] || {}, formatContext = {
|
||||
numSeries: this.series.length,
|
||||
numPoints: firstSeries.points && firstSeries.points.length,
|
||||
chart: this,
|
||||
mapTitle: firstSeries.mapTitle
|
||||
};
|
||||
if (!firstType) {
|
||||
return getTypeDescForEmptyChart(this, formatContext);
|
||||
}
|
||||
if (firstType === 'map') {
|
||||
return getTypeDescForMapChart(this, formatContext);
|
||||
}
|
||||
if (this.types.length > 1) {
|
||||
return getTypeDescForCombinationChart(this, formatContext);
|
||||
}
|
||||
return buildTypeDescriptionFromSeries(this, types, formatContext);
|
||||
};
|
||||
/**
|
||||
* The InfoRegionsComponent class
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.InfoRegionsComponent
|
||||
*/
|
||||
var InfoRegionsComponent = function () { };
|
||||
InfoRegionsComponent.prototype = new AccessibilityComponent();
|
||||
extend(InfoRegionsComponent.prototype, /** @lends Highcharts.InfoRegionsComponent */ {
|
||||
/**
|
||||
* Init the component
|
||||
* @private
|
||||
*/
|
||||
init: function () {
|
||||
var chart = this.chart;
|
||||
var component = this;
|
||||
this.initRegionsDefinitions();
|
||||
this.addEvent(chart, 'afterGetTable', function (e) {
|
||||
component.onDataTableCreated(e);
|
||||
});
|
||||
this.addEvent(chart, 'afterViewData', function (tableDiv) {
|
||||
component.dataTableDiv = tableDiv;
|
||||
// Use small delay to give browsers & AT time to register new table
|
||||
setTimeout(function () {
|
||||
component.focusDataTable();
|
||||
}, 300);
|
||||
});
|
||||
this.announcer = new Announcer(chart, 'assertive');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
initRegionsDefinitions: function () {
|
||||
var component = this;
|
||||
this.screenReaderSections = {
|
||||
before: {
|
||||
element: null,
|
||||
buildContent: function (chart) {
|
||||
var formatter = chart.options.accessibility
|
||||
.screenReaderSection.beforeChartFormatter;
|
||||
return formatter ? formatter(chart) :
|
||||
component.defaultBeforeChartFormatter(chart);
|
||||
},
|
||||
insertIntoDOM: function (el, chart) {
|
||||
chart.renderTo.insertBefore(el, chart.renderTo.firstChild);
|
||||
},
|
||||
afterInserted: function () {
|
||||
if (typeof component.sonifyButtonId !== 'undefined') {
|
||||
component.initSonifyButton(component.sonifyButtonId);
|
||||
}
|
||||
if (typeof component.dataTableButtonId !== 'undefined') {
|
||||
component.initDataTableButton(component.dataTableButtonId);
|
||||
}
|
||||
}
|
||||
},
|
||||
after: {
|
||||
element: null,
|
||||
buildContent: function (chart) {
|
||||
var formatter = chart.options.accessibility.screenReaderSection
|
||||
.afterChartFormatter;
|
||||
return formatter ? formatter(chart) :
|
||||
component.defaultAfterChartFormatter();
|
||||
},
|
||||
insertIntoDOM: function (el, chart) {
|
||||
chart.renderTo.insertBefore(el, chart.container.nextSibling);
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Called on chart render. Have to update the sections on render, in order
|
||||
* to get a11y info from series.
|
||||
*/
|
||||
onChartRender: function () {
|
||||
var component = this;
|
||||
this.linkedDescriptionElement = this.getLinkedDescriptionElement();
|
||||
this.setLinkedDescriptionAttrs();
|
||||
Object.keys(this.screenReaderSections).forEach(function (regionKey) {
|
||||
component.updateScreenReaderSection(regionKey);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
getLinkedDescriptionElement: function () {
|
||||
var chartOptions = this.chart.options, linkedDescOption = chartOptions.accessibility.linkedDescription;
|
||||
if (!linkedDescOption) {
|
||||
return;
|
||||
}
|
||||
if (typeof linkedDescOption !== 'string') {
|
||||
return linkedDescOption;
|
||||
}
|
||||
var query = format(linkedDescOption, this.chart), queryMatch = doc.querySelectorAll(query);
|
||||
if (queryMatch.length === 1) {
|
||||
return queryMatch[0];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setLinkedDescriptionAttrs: function () {
|
||||
var el = this.linkedDescriptionElement;
|
||||
if (el) {
|
||||
el.setAttribute('aria-hidden', 'true');
|
||||
addClass(el, 'highcharts-linked-description');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} regionKey The name/key of the region to update
|
||||
*/
|
||||
updateScreenReaderSection: function (regionKey) {
|
||||
var chart = this.chart, region = this.screenReaderSections[regionKey], content = region.buildContent(chart), sectionDiv = region.element = (region.element || this.createElement('div')), hiddenDiv = (sectionDiv.firstChild || this.createElement('div'));
|
||||
this.setScreenReaderSectionAttribs(sectionDiv, regionKey);
|
||||
hiddenDiv.innerHTML = content;
|
||||
sectionDiv.appendChild(hiddenDiv);
|
||||
region.insertIntoDOM(sectionDiv, chart);
|
||||
visuallyHideElement(hiddenDiv);
|
||||
unhideChartElementFromAT(chart, hiddenDiv);
|
||||
if (region.afterInserted) {
|
||||
region.afterInserted();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.HTMLDOMElement} sectionDiv The section element
|
||||
* @param {string} regionKey Name/key of the region we are setting attrs for
|
||||
*/
|
||||
setScreenReaderSectionAttribs: function (sectionDiv, regionKey) {
|
||||
var labelLangKey = ('accessibility.screenReaderSection.' + regionKey + 'RegionLabel'), chart = this.chart, labelText = chart.langFormat(labelLangKey, { chart: chart }), sectionId = 'highcharts-screen-reader-region-' + regionKey + '-' +
|
||||
chart.index;
|
||||
setElAttrs(sectionDiv, {
|
||||
id: sectionId,
|
||||
'aria-label': labelText
|
||||
});
|
||||
// Sections are wrapped to be positioned relatively to chart in case
|
||||
// elements inside are tabbed to.
|
||||
sectionDiv.style.position = 'relative';
|
||||
if (chart.options.accessibility.landmarkVerbosity === 'all' &&
|
||||
labelText) {
|
||||
sectionDiv.setAttribute('role', 'region');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
defaultBeforeChartFormatter: function () {
|
||||
var _a;
|
||||
var chart = this.chart, format = chart.options.accessibility
|
||||
.screenReaderSection.beforeChartFormat, axesDesc = this.getAxesDescription(), shouldHaveSonifyBtn = chart.sonify && ((_a = chart.options.sonification) === null || _a === void 0 ? void 0 : _a.enabled), sonifyButtonId = 'highcharts-a11y-sonify-data-btn-' +
|
||||
chart.index, dataTableButtonId = 'hc-linkto-highcharts-data-table-' +
|
||||
chart.index, annotationsList = getAnnotationsInfoHTML(chart), annotationsTitleStr = chart.langFormat('accessibility.screenReaderSection.annotations.heading', { chart: chart }), context = {
|
||||
chartTitle: getChartTitle(chart),
|
||||
typeDescription: this.getTypeDescriptionText(),
|
||||
chartSubtitle: this.getSubtitleText(),
|
||||
chartLongdesc: this.getLongdescText(),
|
||||
xAxisDescription: axesDesc.xAxis,
|
||||
yAxisDescription: axesDesc.yAxis,
|
||||
playAsSoundButton: shouldHaveSonifyBtn ?
|
||||
this.getSonifyButtonText(sonifyButtonId) : '',
|
||||
viewTableButton: chart.getCSV ?
|
||||
this.getDataTableButtonText(dataTableButtonId) : '',
|
||||
annotationsTitle: annotationsList ? annotationsTitleStr : '',
|
||||
annotationsList: annotationsList
|
||||
}, formattedString = H.i18nFormat(format, context, chart);
|
||||
this.dataTableButtonId = dataTableButtonId;
|
||||
this.sonifyButtonId = sonifyButtonId;
|
||||
return stringToSimpleHTML(formattedString);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
defaultAfterChartFormatter: function () {
|
||||
var chart = this.chart, format = chart.options.accessibility
|
||||
.screenReaderSection.afterChartFormat, context = {
|
||||
endOfChartMarker: this.getEndOfChartMarkerText()
|
||||
}, formattedString = H.i18nFormat(format, context, chart);
|
||||
return stringToSimpleHTML(formattedString);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
getLinkedDescription: function () {
|
||||
var el = this.linkedDescriptionElement, content = el && el.innerHTML || '';
|
||||
return stripHTMLTagsFromString(content);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
getLongdescText: function () {
|
||||
var chartOptions = this.chart.options, captionOptions = chartOptions.caption, captionText = captionOptions && captionOptions.text, linkedDescription = this.getLinkedDescription();
|
||||
return (chartOptions.accessibility.description ||
|
||||
linkedDescription ||
|
||||
captionText ||
|
||||
'');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
getTypeDescriptionText: function () {
|
||||
var chart = this.chart;
|
||||
return chart.types ?
|
||||
chart.options.accessibility.typeDescription ||
|
||||
chart.getTypeDescription(chart.types) : '';
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} buttonId
|
||||
* @return {string}
|
||||
*/
|
||||
getDataTableButtonText: function (buttonId) {
|
||||
var chart = this.chart, buttonText = chart.langFormat('accessibility.table.viewAsDataTableButtonText', { chart: chart, chartTitle: getChartTitle(chart) });
|
||||
return '<button id="' + buttonId + '">' + buttonText + '</button>';
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} buttonId
|
||||
* @return {string}
|
||||
*/
|
||||
getSonifyButtonText: function (buttonId) {
|
||||
var _a;
|
||||
var chart = this.chart;
|
||||
if (((_a = chart.options.sonification) === null || _a === void 0 ? void 0 : _a.enabled) === false) {
|
||||
return '';
|
||||
}
|
||||
var buttonText = chart.langFormat('accessibility.sonification.playAsSoundButtonText', { chart: chart, chartTitle: getChartTitle(chart) });
|
||||
return '<button id="' + buttonId + '">' + buttonText + '</button>';
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
getSubtitleText: function () {
|
||||
var subtitle = (this.chart.options.subtitle);
|
||||
return stripHTMLTagsFromString(subtitle && subtitle.text || '');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
getEndOfChartMarkerText: function () {
|
||||
var chart = this.chart, markerText = chart.langFormat('accessibility.screenReaderSection.endOfChartMarker', { chart: chart }), id = 'highcharts-end-of-chart-marker-' + chart.index;
|
||||
return '<div id="' + id + '">' + markerText + '</div>';
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Dictionary<string>} e
|
||||
*/
|
||||
onDataTableCreated: function (e) {
|
||||
var chart = this.chart;
|
||||
if (chart.options.accessibility.enabled) {
|
||||
if (this.viewDataTableButton) {
|
||||
this.viewDataTableButton.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
e.html = e.html.replace('<table ', '<table tabindex="-1" summary="' + getTableSummary(chart) + '"');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
focusDataTable: function () {
|
||||
var tableDiv = this.dataTableDiv, table = tableDiv && tableDiv.getElementsByTagName('table')[0];
|
||||
if (table && table.focus) {
|
||||
table.focus();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} sonifyButtonId
|
||||
*/
|
||||
initSonifyButton: function (sonifyButtonId) {
|
||||
var _this = this;
|
||||
var el = this.sonifyButton = getElement(sonifyButtonId);
|
||||
var chart = this.chart;
|
||||
var defaultHandler = function (e) {
|
||||
el === null || el === void 0 ? void 0 : el.setAttribute('aria-hidden', 'true');
|
||||
el === null || el === void 0 ? void 0 : el.setAttribute('aria-label', '');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
var announceMsg = chart.langFormat('accessibility.sonification.playAsSoundClickAnnouncement', { chart: chart });
|
||||
_this.announcer.announce(announceMsg);
|
||||
setTimeout(function () {
|
||||
el === null || el === void 0 ? void 0 : el.removeAttribute('aria-hidden');
|
||||
el === null || el === void 0 ? void 0 : el.removeAttribute('aria-label');
|
||||
if (chart.sonify) {
|
||||
chart.sonify();
|
||||
}
|
||||
}, 1000); // Delay to let screen reader speak the button press
|
||||
};
|
||||
if (el && chart) {
|
||||
setElAttrs(el, {
|
||||
tabindex: '-1'
|
||||
});
|
||||
el.onclick = function (e) {
|
||||
var _a;
|
||||
var onPlayAsSoundClick = (_a = chart.options.accessibility) === null || _a === void 0 ? void 0 : _a.screenReaderSection.onPlayAsSoundClick;
|
||||
(onPlayAsSoundClick || defaultHandler).call(this, e, chart);
|
||||
};
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Set attribs and handlers for default viewAsDataTable button if exists.
|
||||
* @private
|
||||
* @param {string} tableButtonId
|
||||
*/
|
||||
initDataTableButton: function (tableButtonId) {
|
||||
var el = this.viewDataTableButton = getElement(tableButtonId), chart = this.chart, tableId = tableButtonId.replace('hc-linkto-', '');
|
||||
if (el) {
|
||||
setElAttrs(el, {
|
||||
tabindex: '-1',
|
||||
'aria-expanded': !!getElement(tableId)
|
||||
});
|
||||
el.onclick = chart.options.accessibility
|
||||
.screenReaderSection.onViewDataTableClick ||
|
||||
function () {
|
||||
chart.viewData();
|
||||
};
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return object with text description of each of the chart's axes.
|
||||
* @private
|
||||
* @return {Highcharts.Dictionary<string>}
|
||||
*/
|
||||
getAxesDescription: function () {
|
||||
var chart = this.chart, shouldDescribeColl = function (collectionKey, defaultCondition) {
|
||||
var axes = chart[collectionKey];
|
||||
return axes.length > 1 || axes[0] &&
|
||||
pick(axes[0].options.accessibility &&
|
||||
axes[0].options.accessibility.enabled, defaultCondition);
|
||||
}, hasNoMap = !!chart.types && chart.types.indexOf('map') < 0, hasCartesian = !!chart.hasCartesianSeries, showXAxes = shouldDescribeColl('xAxis', !chart.angular && hasCartesian && hasNoMap), showYAxes = shouldDescribeColl('yAxis', hasCartesian && hasNoMap), desc = {};
|
||||
if (showXAxes) {
|
||||
desc.xAxis = this.getAxisDescriptionText('xAxis');
|
||||
}
|
||||
if (showYAxes) {
|
||||
desc.yAxis = this.getAxisDescriptionText('yAxis');
|
||||
}
|
||||
return desc;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} collectionKey
|
||||
* @return {string}
|
||||
*/
|
||||
getAxisDescriptionText: function (collectionKey) {
|
||||
var component = this, chart = this.chart, axes = chart[collectionKey];
|
||||
return chart.langFormat('accessibility.axis.' + collectionKey + 'Description' + (axes.length > 1 ? 'Plural' : 'Singular'), {
|
||||
chart: chart,
|
||||
names: axes.map(function (axis) {
|
||||
return getAxisDescription(axis);
|
||||
}),
|
||||
ranges: axes.map(function (axis) {
|
||||
return component.getAxisRangeDescription(axis);
|
||||
}),
|
||||
numAxes: axes.length
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Return string with text description of the axis range.
|
||||
* @private
|
||||
* @param {Highcharts.Axis} axis The axis to get range desc of.
|
||||
* @return {string} A string with the range description for the axis.
|
||||
*/
|
||||
getAxisRangeDescription: function (axis) {
|
||||
var axisOptions = axis.options || {};
|
||||
// Handle overridden range description
|
||||
if (axisOptions.accessibility &&
|
||||
typeof axisOptions.accessibility.rangeDescription !== 'undefined') {
|
||||
return axisOptions.accessibility.rangeDescription;
|
||||
}
|
||||
// Handle category axes
|
||||
if (axis.categories) {
|
||||
return this.getCategoryAxisRangeDesc(axis);
|
||||
}
|
||||
// Use time range, not from-to?
|
||||
if (axis.dateTime && (axis.min === 0 || axis.dataMin === 0)) {
|
||||
return this.getAxisTimeLengthDesc(axis);
|
||||
}
|
||||
// Just use from and to.
|
||||
// We have the range and the unit to use, find the desc format
|
||||
return this.getAxisFromToDescription(axis);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Axis} axis
|
||||
* @return {string}
|
||||
*/
|
||||
getCategoryAxisRangeDesc: function (axis) {
|
||||
var chart = this.chart;
|
||||
if (axis.dataMax && axis.dataMin) {
|
||||
return chart.langFormat('accessibility.axis.rangeCategories', {
|
||||
chart: chart,
|
||||
axis: axis,
|
||||
numCategories: axis.dataMax - axis.dataMin + 1
|
||||
});
|
||||
}
|
||||
return '';
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Axis} axis
|
||||
* @return {string}
|
||||
*/
|
||||
getAxisTimeLengthDesc: function (axis) {
|
||||
var chart = this.chart, range = {}, rangeUnit = 'Seconds';
|
||||
range.Seconds = ((axis.max || 0) - (axis.min || 0)) / 1000;
|
||||
range.Minutes = range.Seconds / 60;
|
||||
range.Hours = range.Minutes / 60;
|
||||
range.Days = range.Hours / 24;
|
||||
['Minutes', 'Hours', 'Days'].forEach(function (unit) {
|
||||
if (range[unit] > 2) {
|
||||
rangeUnit = unit;
|
||||
}
|
||||
});
|
||||
var rangeValue = range[rangeUnit].toFixed(rangeUnit !== 'Seconds' &&
|
||||
rangeUnit !== 'Minutes' ? 1 : 0 // Use decimals for days/hours
|
||||
);
|
||||
// We have the range and the unit to use, find the desc format
|
||||
return chart.langFormat('accessibility.axis.timeRange' + rangeUnit, {
|
||||
chart: chart,
|
||||
axis: axis,
|
||||
range: rangeValue.replace('.0', '')
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Axis} axis
|
||||
* @return {string}
|
||||
*/
|
||||
getAxisFromToDescription: function (axis) {
|
||||
var chart = this.chart, dateRangeFormat = chart.options.accessibility
|
||||
.screenReaderSection.axisRangeDateFormat, format = function (axisKey) {
|
||||
return axis.dateTime ? chart.time.dateFormat(dateRangeFormat, axis[axisKey]) : axis[axisKey];
|
||||
};
|
||||
return chart.langFormat('accessibility.axis.rangeFromTo', {
|
||||
chart: chart,
|
||||
axis: axis,
|
||||
rangeFrom: format('min'),
|
||||
rangeTo: format('max')
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Remove component traces
|
||||
*/
|
||||
destroy: function () {
|
||||
var _a;
|
||||
(_a = this.announcer) === null || _a === void 0 ? void 0 : _a.destroy();
|
||||
}
|
||||
});
|
||||
export default InfoRegionsComponent;
|
||||
@@ -0,0 +1,300 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility component for chart legend.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
import Legend from '../../Core/Legend.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, extend = U.extend, find = U.find, fireEvent = U.fireEvent;
|
||||
import AccessibilityComponent from '../AccessibilityComponent.js';
|
||||
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
|
||||
import HTMLUtilities from '../Utils/HTMLUtilities.js';
|
||||
var stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString, removeElement = HTMLUtilities.removeElement;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function scrollLegendToItem(legend, itemIx) {
|
||||
var itemPage = legend.allItems[itemIx].pageIx, curPage = legend.currentPage;
|
||||
if (typeof itemPage !== 'undefined' && itemPage + 1 !== curPage) {
|
||||
legend.scroll(1 + itemPage - curPage);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function shouldDoLegendA11y(chart) {
|
||||
var items = chart.legend && chart.legend.allItems, legendA11yOptions = (chart.options.legend.accessibility || {});
|
||||
return !!(items && items.length &&
|
||||
!(chart.colorAxis && chart.colorAxis.length) &&
|
||||
legendA11yOptions.enabled !== false);
|
||||
}
|
||||
/**
|
||||
* Highlight legend item by index.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#highlightLegendItem
|
||||
*
|
||||
* @param {number} ix
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
H.Chart.prototype.highlightLegendItem = function (ix) {
|
||||
var items = this.legend.allItems, oldIx = this.highlightedLegendItemIx;
|
||||
if (items[ix]) {
|
||||
if (items[oldIx]) {
|
||||
fireEvent(items[oldIx].legendGroup.element, 'mouseout');
|
||||
}
|
||||
scrollLegendToItem(this.legend, ix);
|
||||
this.setFocusToElement(items[ix].legendItem, items[ix].a11yProxyElement);
|
||||
fireEvent(items[ix].legendGroup.element, 'mouseover');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// Keep track of pressed state for legend items
|
||||
addEvent(Legend, 'afterColorizeItem', function (e) {
|
||||
var chart = this.chart, a11yOptions = chart.options.accessibility, legendItem = e.item;
|
||||
if (a11yOptions.enabled && legendItem && legendItem.a11yProxyElement) {
|
||||
legendItem.a11yProxyElement.setAttribute('aria-pressed', e.visible ? 'false' : 'true');
|
||||
}
|
||||
});
|
||||
/**
|
||||
* The LegendComponent class
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.LegendComponent
|
||||
*/
|
||||
var LegendComponent = function () { };
|
||||
LegendComponent.prototype = new AccessibilityComponent();
|
||||
extend(LegendComponent.prototype, /** @lends Highcharts.LegendComponent */ {
|
||||
/**
|
||||
* Init the component
|
||||
* @private
|
||||
*/
|
||||
init: function () {
|
||||
var component = this;
|
||||
this.proxyElementsList = [];
|
||||
this.recreateProxies();
|
||||
// Note: Chart could create legend dynamically, so events can not be
|
||||
// tied to the component's chart's current legend.
|
||||
this.addEvent(Legend, 'afterScroll', function () {
|
||||
if (this.chart === component.chart) {
|
||||
component.updateProxiesPositions();
|
||||
component.updateLegendItemProxyVisibility();
|
||||
this.chart.highlightLegendItem(component.highlightedLegendItemIx);
|
||||
}
|
||||
});
|
||||
this.addEvent(Legend, 'afterPositionItem', function (e) {
|
||||
if (this.chart === component.chart && this.chart.renderer) {
|
||||
component.updateProxyPositionForItem(e.item);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
updateLegendItemProxyVisibility: function () {
|
||||
var legend = this.chart.legend, items = legend.allItems || [], curPage = legend.currentPage || 1, clipHeight = legend.clipHeight || 0;
|
||||
items.forEach(function (item) {
|
||||
var itemPage = item.pageIx || 0, y = item._legendItemPos ? item._legendItemPos[1] : 0, h = item.legendItem ? Math.round(item.legendItem.getBBox().height) : 0, hide = y + h - legend.pages[itemPage] > clipHeight || itemPage !== curPage - 1;
|
||||
if (item.a11yProxyElement) {
|
||||
item.a11yProxyElement.style.visibility = hide ?
|
||||
'hidden' : 'visible';
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* The legend needs updates on every render, in order to update positioning
|
||||
* of the proxy overlays.
|
||||
*/
|
||||
onChartRender: function () {
|
||||
if (shouldDoLegendA11y(this.chart)) {
|
||||
this.updateProxiesPositions();
|
||||
}
|
||||
else {
|
||||
this.removeProxies();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
updateProxiesPositions: function () {
|
||||
for (var _i = 0, _a = this.proxyElementsList; _i < _a.length; _i++) {
|
||||
var _b = _a[_i], element = _b.element, posElement = _b.posElement;
|
||||
this.updateProxyButtonPosition(element, posElement);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
updateProxyPositionForItem: function (item) {
|
||||
var proxyRef = find(this.proxyElementsList, function (ref) { return ref.item === item; });
|
||||
if (proxyRef) {
|
||||
this.updateProxyButtonPosition(proxyRef.element, proxyRef.posElement);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
recreateProxies: function () {
|
||||
this.removeProxies();
|
||||
if (shouldDoLegendA11y(this.chart)) {
|
||||
this.addLegendProxyGroup();
|
||||
this.proxyLegendItems();
|
||||
this.updateLegendItemProxyVisibility();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
removeProxies: function () {
|
||||
removeElement(this.legendProxyGroup);
|
||||
this.proxyElementsList = [];
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
addLegendProxyGroup: function () {
|
||||
var a11yOptions = this.chart.options.accessibility, groupLabel = this.chart.langFormat('accessibility.legend.legendLabel', {}), groupRole = a11yOptions.landmarkVerbosity === 'all' ?
|
||||
'region' : null;
|
||||
this.legendProxyGroup = this.addProxyGroup({
|
||||
'aria-label': groupLabel,
|
||||
'role': groupRole
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
proxyLegendItems: function () {
|
||||
var component = this, items = (this.chart.legend &&
|
||||
this.chart.legend.allItems || []);
|
||||
items.forEach(function (item) {
|
||||
if (item.legendItem && item.legendItem.element) {
|
||||
component.proxyLegendItem(item);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item
|
||||
*/
|
||||
proxyLegendItem: function (item) {
|
||||
if (!item.legendItem || !item.legendGroup) {
|
||||
return;
|
||||
}
|
||||
var itemLabel = this.chart.langFormat('accessibility.legend.legendItem', {
|
||||
chart: this.chart,
|
||||
itemName: stripHTMLTags(item.name)
|
||||
}), attribs = {
|
||||
tabindex: -1,
|
||||
'aria-pressed': !item.visible,
|
||||
'aria-label': itemLabel
|
||||
},
|
||||
// Considers useHTML
|
||||
proxyPositioningElement = item.legendGroup.div ?
|
||||
item.legendItem : item.legendGroup;
|
||||
item.a11yProxyElement = this.createProxyButton(item.legendItem, this.legendProxyGroup, attribs, proxyPositioningElement);
|
||||
this.proxyElementsList.push({
|
||||
item: item,
|
||||
element: item.a11yProxyElement,
|
||||
posElement: proxyPositioningElement
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get keyboard navigation handler for this component.
|
||||
* @return {Highcharts.KeyboardNavigationHandler}
|
||||
*/
|
||||
getKeyboardNavigation: function () {
|
||||
var keys = this.keyCodes, component = this, chart = this.chart;
|
||||
return new KeyboardNavigationHandler(chart, {
|
||||
keyCodeMap: [
|
||||
[
|
||||
[keys.left, keys.right, keys.up, keys.down],
|
||||
function (keyCode) {
|
||||
return component.onKbdArrowKey(this, keyCode);
|
||||
}
|
||||
],
|
||||
[
|
||||
[keys.enter, keys.space],
|
||||
function () {
|
||||
return component.onKbdClick(this);
|
||||
}
|
||||
]
|
||||
],
|
||||
validate: function () {
|
||||
return component.shouldHaveLegendNavigation();
|
||||
},
|
||||
init: function (direction) {
|
||||
return component.onKbdNavigationInit(direction);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @param {number} keyCode
|
||||
* @return {number}
|
||||
* Response code
|
||||
*/
|
||||
onKbdArrowKey: function (keyboardNavigationHandler, keyCode) {
|
||||
var keys = this.keyCodes, response = keyboardNavigationHandler.response, chart = this.chart, a11yOptions = chart.options.accessibility, numItems = chart.legend.allItems.length, direction = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1;
|
||||
var res = chart.highlightLegendItem(this.highlightedLegendItemIx + direction);
|
||||
if (res) {
|
||||
this.highlightedLegendItemIx += direction;
|
||||
return response.success;
|
||||
}
|
||||
if (numItems > 1 &&
|
||||
a11yOptions.keyboardNavigation.wrapAround) {
|
||||
keyboardNavigationHandler.init(direction);
|
||||
return response.success;
|
||||
}
|
||||
// No wrap, move
|
||||
return response[direction > 0 ? 'next' : 'prev'];
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @return {number}
|
||||
* Response code
|
||||
*/
|
||||
onKbdClick: function (keyboardNavigationHandler) {
|
||||
var legendItem = this.chart.legend.allItems[this.highlightedLegendItemIx];
|
||||
if (legendItem && legendItem.a11yProxyElement) {
|
||||
fireEvent(legendItem.a11yProxyElement, 'click');
|
||||
}
|
||||
return keyboardNavigationHandler.response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @return {boolean|undefined}
|
||||
*/
|
||||
shouldHaveLegendNavigation: function () {
|
||||
var chart = this.chart, legendOptions = chart.options.legend || {}, hasLegend = chart.legend && chart.legend.allItems, hasColorAxis = chart.colorAxis && chart.colorAxis.length, legendA11yOptions = (legendOptions.accessibility || {});
|
||||
return !!(hasLegend &&
|
||||
chart.legend.display &&
|
||||
!hasColorAxis &&
|
||||
legendA11yOptions.enabled &&
|
||||
legendA11yOptions.keyboardNavigation &&
|
||||
legendA11yOptions.keyboardNavigation.enabled);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {number} direction
|
||||
*/
|
||||
onKbdNavigationInit: function (direction) {
|
||||
var chart = this.chart, lastIx = chart.legend.allItems.length - 1, ixToHighlight = direction > 0 ? 0 : lastIx;
|
||||
chart.highlightLegendItem(ixToHighlight);
|
||||
this.highlightedLegendItemIx = ixToHighlight;
|
||||
}
|
||||
});
|
||||
export default LegendComponent;
|
||||
@@ -0,0 +1,349 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility component for exporting menu.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var extend = U.extend;
|
||||
import AccessibilityComponent from '../AccessibilityComponent.js';
|
||||
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
|
||||
import ChartUtilities from '../Utils/ChartUtilities.js';
|
||||
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
|
||||
import HTMLUtilities from '../Utils/HTMLUtilities.js';
|
||||
var removeElement = HTMLUtilities.removeElement, getFakeMouseEvent = HTMLUtilities.getFakeMouseEvent;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Get the wrapped export button element of a chart.
|
||||
*
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
* @returns {Highcharts.SVGElement}
|
||||
*/
|
||||
function getExportMenuButtonElement(chart) {
|
||||
return chart.exportSVGElements && chart.exportSVGElements[0];
|
||||
}
|
||||
/**
|
||||
* Show the export menu and focus the first item (if exists).
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#showExportMenu
|
||||
*/
|
||||
H.Chart.prototype.showExportMenu = function () {
|
||||
var exportButton = getExportMenuButtonElement(this);
|
||||
if (exportButton) {
|
||||
var el = exportButton.element;
|
||||
if (el.onclick) {
|
||||
el.onclick(getFakeMouseEvent('click'));
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Chart#hideExportMenu
|
||||
*/
|
||||
H.Chart.prototype.hideExportMenu = function () {
|
||||
var chart = this, exportList = chart.exportDivElements;
|
||||
if (exportList && chart.exportContextMenu) {
|
||||
// Reset hover states etc.
|
||||
exportList.forEach(function (el) {
|
||||
if (el.className === 'highcharts-menu-item' && el.onmouseout) {
|
||||
el.onmouseout(getFakeMouseEvent('mouseout'));
|
||||
}
|
||||
});
|
||||
chart.highlightedExportItemIx = 0;
|
||||
// Hide the menu div
|
||||
chart.exportContextMenu.hideMenu();
|
||||
// Make sure the chart has focus and can capture keyboard events
|
||||
chart.container.focus();
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Highlight export menu item by index.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#highlightExportItem
|
||||
*
|
||||
* @param {number} ix
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
H.Chart.prototype.highlightExportItem = function (ix) {
|
||||
var listItem = this.exportDivElements && this.exportDivElements[ix], curHighlighted = this.exportDivElements &&
|
||||
this.exportDivElements[this.highlightedExportItemIx], hasSVGFocusSupport;
|
||||
if (listItem &&
|
||||
listItem.tagName === 'LI' &&
|
||||
!(listItem.children && listItem.children.length)) {
|
||||
// Test if we have focus support for SVG elements
|
||||
hasSVGFocusSupport = !!(this.renderTo.getElementsByTagName('g')[0] || {}).focus;
|
||||
// Only focus if we can set focus back to the elements after
|
||||
// destroying the menu (#7422)
|
||||
if (listItem.focus && hasSVGFocusSupport) {
|
||||
listItem.focus();
|
||||
}
|
||||
if (curHighlighted && curHighlighted.onmouseout) {
|
||||
curHighlighted.onmouseout(getFakeMouseEvent('mouseout'));
|
||||
}
|
||||
if (listItem.onmouseover) {
|
||||
listItem.onmouseover(getFakeMouseEvent('mouseover'));
|
||||
}
|
||||
this.highlightedExportItemIx = ix;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* Try to highlight the last valid export menu item.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#highlightLastExportItem
|
||||
* @return {boolean}
|
||||
*/
|
||||
H.Chart.prototype.highlightLastExportItem = function () {
|
||||
var chart = this, i;
|
||||
if (chart.exportDivElements) {
|
||||
i = chart.exportDivElements.length;
|
||||
while (i--) {
|
||||
if (chart.highlightExportItem(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
*/
|
||||
function exportingShouldHaveA11y(chart) {
|
||||
var exportingOpts = chart.options.exporting, exportButton = getExportMenuButtonElement(chart);
|
||||
return !!(exportingOpts &&
|
||||
exportingOpts.enabled !== false &&
|
||||
exportingOpts.accessibility &&
|
||||
exportingOpts.accessibility.enabled &&
|
||||
exportButton &&
|
||||
exportButton.element);
|
||||
}
|
||||
/**
|
||||
* The MenuComponent class
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.MenuComponent
|
||||
*/
|
||||
var MenuComponent = function () { };
|
||||
MenuComponent.prototype = new AccessibilityComponent();
|
||||
extend(MenuComponent.prototype, /** @lends Highcharts.MenuComponent */ {
|
||||
/**
|
||||
* Init the component
|
||||
*/
|
||||
init: function () {
|
||||
var chart = this.chart, component = this;
|
||||
this.addEvent(chart, 'exportMenuShown', function () {
|
||||
component.onMenuShown();
|
||||
});
|
||||
this.addEvent(chart, 'exportMenuHidden', function () {
|
||||
component.onMenuHidden();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onMenuHidden: function () {
|
||||
var menu = this.chart.exportContextMenu;
|
||||
if (menu) {
|
||||
menu.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
this.isExportMenuShown = false;
|
||||
this.setExportButtonExpandedState('false');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onMenuShown: function () {
|
||||
var chart = this.chart, menu = chart.exportContextMenu;
|
||||
if (menu) {
|
||||
this.addAccessibleContextMenuAttribs();
|
||||
unhideChartElementFromAT(chart, menu);
|
||||
}
|
||||
this.isExportMenuShown = true;
|
||||
this.setExportButtonExpandedState('true');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {string} stateStr
|
||||
*/
|
||||
setExportButtonExpandedState: function (stateStr) {
|
||||
var button = this.exportButtonProxy;
|
||||
if (button) {
|
||||
button.setAttribute('aria-expanded', stateStr);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Called on each render of the chart. We need to update positioning of the
|
||||
* proxy overlay.
|
||||
*/
|
||||
onChartRender: function () {
|
||||
var chart = this.chart, a11yOptions = chart.options.accessibility;
|
||||
// Always start with a clean slate
|
||||
removeElement(this.exportProxyGroup);
|
||||
// Set screen reader properties on export menu
|
||||
if (exportingShouldHaveA11y(chart)) {
|
||||
// Proxy button and group
|
||||
this.exportProxyGroup = this.addProxyGroup(
|
||||
// Wrap in a region div if verbosity is high
|
||||
a11yOptions.landmarkVerbosity === 'all' ? {
|
||||
'aria-label': chart.langFormat('accessibility.exporting.exportRegionLabel', { chart: chart }),
|
||||
'role': 'region'
|
||||
} : {});
|
||||
var button = getExportMenuButtonElement(this.chart);
|
||||
this.exportButtonProxy = this.createProxyButton(button, this.exportProxyGroup, {
|
||||
'aria-label': chart.langFormat('accessibility.exporting.menuButtonLabel', { chart: chart }),
|
||||
'aria-expanded': 'false'
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
addAccessibleContextMenuAttribs: function () {
|
||||
var chart = this.chart, exportList = chart.exportDivElements;
|
||||
if (exportList && exportList.length) {
|
||||
// Set tabindex on the menu items to allow focusing by script
|
||||
// Set role to give screen readers a chance to pick up the contents
|
||||
exportList.forEach(function (item) {
|
||||
if (item.tagName === 'LI' &&
|
||||
!(item.children && item.children.length)) {
|
||||
item.setAttribute('tabindex', -1);
|
||||
}
|
||||
else {
|
||||
item.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
});
|
||||
// Set accessibility properties on parent div
|
||||
var parentDiv = exportList[0].parentNode;
|
||||
parentDiv.removeAttribute('aria-hidden');
|
||||
parentDiv.setAttribute('aria-label', chart.langFormat('accessibility.exporting.chartMenuLabel', { chart: chart }));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Get keyboard navigation handler for this component.
|
||||
* @return {Highcharts.KeyboardNavigationHandler}
|
||||
*/
|
||||
getKeyboardNavigation: function () {
|
||||
var keys = this.keyCodes, chart = this.chart, component = this;
|
||||
return new KeyboardNavigationHandler(chart, {
|
||||
keyCodeMap: [
|
||||
// Arrow prev handler
|
||||
[
|
||||
[keys.left, keys.up],
|
||||
function () {
|
||||
return component.onKbdPrevious(this);
|
||||
}
|
||||
],
|
||||
// Arrow next handler
|
||||
[
|
||||
[keys.right, keys.down],
|
||||
function () {
|
||||
return component.onKbdNext(this);
|
||||
}
|
||||
],
|
||||
// Click handler
|
||||
[
|
||||
[keys.enter, keys.space],
|
||||
function () {
|
||||
return component.onKbdClick(this);
|
||||
}
|
||||
]
|
||||
],
|
||||
// Only run exporting navigation if exporting support exists and is
|
||||
// enabled on chart
|
||||
validate: function () {
|
||||
return chart.exportChart &&
|
||||
chart.options.exporting.enabled !== false &&
|
||||
chart.options.exporting.accessibility.enabled !==
|
||||
false;
|
||||
},
|
||||
// Focus export menu button
|
||||
init: function () {
|
||||
var exportBtn = component.exportButtonProxy, exportGroup = chart.exportingGroup;
|
||||
if (exportGroup && exportBtn) {
|
||||
chart.setFocusToElement(exportGroup, exportBtn);
|
||||
}
|
||||
},
|
||||
// Hide the menu
|
||||
terminate: function () {
|
||||
chart.hideExportMenu();
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @return {number}
|
||||
* Response code
|
||||
*/
|
||||
onKbdPrevious: function (keyboardNavigationHandler) {
|
||||
var chart = this.chart, a11yOptions = chart.options.accessibility, response = keyboardNavigationHandler.response, i = chart.highlightedExportItemIx || 0;
|
||||
// Try to highlight prev item in list. Highlighting e.g.
|
||||
// separators will fail.
|
||||
while (i--) {
|
||||
if (chart.highlightExportItem(i)) {
|
||||
return response.success;
|
||||
}
|
||||
}
|
||||
// We failed, so wrap around or move to prev module
|
||||
if (a11yOptions.keyboardNavigation.wrapAround) {
|
||||
chart.highlightLastExportItem();
|
||||
return response.success;
|
||||
}
|
||||
return response.prev;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @return {number}
|
||||
* Response code
|
||||
*/
|
||||
onKbdNext: function (keyboardNavigationHandler) {
|
||||
var chart = this.chart, a11yOptions = chart.options.accessibility, response = keyboardNavigationHandler.response, i = (chart.highlightedExportItemIx || 0) + 1;
|
||||
// Try to highlight next item in list. Highlighting e.g.
|
||||
// separators will fail.
|
||||
for (; i < chart.exportDivElements.length; ++i) {
|
||||
if (chart.highlightExportItem(i)) {
|
||||
return response.success;
|
||||
}
|
||||
}
|
||||
// We failed, so wrap around or move to next module
|
||||
if (a11yOptions.keyboardNavigation.wrapAround) {
|
||||
chart.highlightExportItem(0);
|
||||
return response.success;
|
||||
}
|
||||
return response.next;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @return {number}
|
||||
* Response code
|
||||
*/
|
||||
onKbdClick: function (keyboardNavigationHandler) {
|
||||
var chart = this.chart, curHighlightedItem = chart.exportDivElements[chart.highlightedExportItemIx], exportButtonElement = getExportMenuButtonElement(chart).element;
|
||||
if (this.isExportMenuShown) {
|
||||
this.fakeClickEvent(curHighlightedItem);
|
||||
}
|
||||
else {
|
||||
this.fakeClickEvent(exportButtonElement);
|
||||
chart.highlightExportItem(0);
|
||||
}
|
||||
return keyboardNavigationHandler.response.success;
|
||||
}
|
||||
});
|
||||
export default MenuComponent;
|
||||
@@ -0,0 +1,266 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility component for the range selector.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var extend = U.extend;
|
||||
import AccessibilityComponent from '../AccessibilityComponent.js';
|
||||
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
|
||||
import ChartUtilities from '../Utils/ChartUtilities.js';
|
||||
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
|
||||
import HTMLUtilities from '../Utils/HTMLUtilities.js';
|
||||
var setElAttrs = HTMLUtilities.setElAttrs;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function shouldRunInputNavigation(chart) {
|
||||
var inputVisible = (chart.rangeSelector &&
|
||||
chart.rangeSelector.inputGroup &&
|
||||
chart.rangeSelector.inputGroup.element
|
||||
.getAttribute('visibility') !== 'hidden');
|
||||
return (inputVisible &&
|
||||
chart.options.rangeSelector.inputEnabled !== false &&
|
||||
chart.rangeSelector.minInput &&
|
||||
chart.rangeSelector.maxInput);
|
||||
}
|
||||
/**
|
||||
* Highlight range selector button by index.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#highlightRangeSelectorButton
|
||||
*
|
||||
* @param {number} ix
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
H.Chart.prototype.highlightRangeSelectorButton = function (ix) {
|
||||
var buttons = this.rangeSelector.buttons, curSelectedIx = this.highlightedRangeSelectorItemIx;
|
||||
// Deselect old
|
||||
if (typeof curSelectedIx !== 'undefined' && buttons[curSelectedIx]) {
|
||||
buttons[curSelectedIx].setState(this.oldRangeSelectorItemState || 0);
|
||||
}
|
||||
// Select new
|
||||
this.highlightedRangeSelectorItemIx = ix;
|
||||
if (buttons[ix]) {
|
||||
this.setFocusToElement(buttons[ix].box, buttons[ix].element);
|
||||
this.oldRangeSelectorItemState = buttons[ix].state;
|
||||
buttons[ix].setState(2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* The RangeSelectorComponent class
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.RangeSelectorComponent
|
||||
*/
|
||||
var RangeSelectorComponent = function () { };
|
||||
RangeSelectorComponent.prototype = new AccessibilityComponent();
|
||||
extend(RangeSelectorComponent.prototype, /** @lends Highcharts.RangeSelectorComponent */ {
|
||||
/**
|
||||
* Called on first render/updates to the chart, including options changes.
|
||||
*/
|
||||
onChartUpdate: function () {
|
||||
var chart = this.chart, component = this, rangeSelector = chart.rangeSelector;
|
||||
if (!rangeSelector) {
|
||||
return;
|
||||
}
|
||||
if (rangeSelector.buttons && rangeSelector.buttons.length) {
|
||||
rangeSelector.buttons.forEach(function (button) {
|
||||
unhideChartElementFromAT(chart, button.element);
|
||||
component.setRangeButtonAttrs(button);
|
||||
});
|
||||
}
|
||||
// Make sure input boxes are accessible and focusable
|
||||
if (rangeSelector.maxInput && rangeSelector.minInput) {
|
||||
['minInput', 'maxInput'].forEach(function (key, i) {
|
||||
var input = rangeSelector[key];
|
||||
if (input) {
|
||||
unhideChartElementFromAT(chart, input);
|
||||
component.setRangeInputAttrs(input, 'accessibility.rangeSelector.' + (i ? 'max' : 'min') +
|
||||
'InputLabel');
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.SVGElement} button
|
||||
*/
|
||||
setRangeButtonAttrs: function (button) {
|
||||
var chart = this.chart, label = chart.langFormat('accessibility.rangeSelector.buttonText', {
|
||||
chart: chart,
|
||||
buttonText: button.text && button.text.textStr
|
||||
});
|
||||
setElAttrs(button.element, {
|
||||
tabindex: -1,
|
||||
role: 'button',
|
||||
'aria-label': label
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
setRangeInputAttrs: function (input, langKey) {
|
||||
var chart = this.chart;
|
||||
setElAttrs(input, {
|
||||
tabindex: -1,
|
||||
role: 'textbox',
|
||||
'aria-label': chart.langFormat(langKey, { chart: chart })
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get navigation for the range selector buttons.
|
||||
* @private
|
||||
* @return {Highcharts.KeyboardNavigationHandler} The module object.
|
||||
*/
|
||||
getRangeSelectorButtonNavigation: function () {
|
||||
var chart = this.chart, keys = this.keyCodes, component = this;
|
||||
return new KeyboardNavigationHandler(chart, {
|
||||
keyCodeMap: [
|
||||
[
|
||||
[keys.left, keys.right, keys.up, keys.down],
|
||||
function (keyCode) {
|
||||
return component.onButtonNavKbdArrowKey(this, keyCode);
|
||||
}
|
||||
],
|
||||
[
|
||||
[keys.enter, keys.space],
|
||||
function () {
|
||||
return component.onButtonNavKbdClick(this);
|
||||
}
|
||||
]
|
||||
],
|
||||
validate: function () {
|
||||
var hasRangeSelector = chart.rangeSelector &&
|
||||
chart.rangeSelector.buttons &&
|
||||
chart.rangeSelector.buttons.length;
|
||||
return hasRangeSelector;
|
||||
},
|
||||
init: function (direction) {
|
||||
var lastButtonIx = (chart.rangeSelector.buttons.length - 1);
|
||||
chart.highlightRangeSelectorButton(direction > 0 ? 0 : lastButtonIx);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @param {number} keyCode
|
||||
* @return {number} Response code
|
||||
*/
|
||||
onButtonNavKbdArrowKey: function (keyboardNavigationHandler, keyCode) {
|
||||
var response = keyboardNavigationHandler.response, keys = this.keyCodes, chart = this.chart, wrapAround = chart.options.accessibility
|
||||
.keyboardNavigation.wrapAround, direction = (keyCode === keys.left || keyCode === keys.up) ? -1 : 1, didHighlight = chart.highlightRangeSelectorButton(chart.highlightedRangeSelectorItemIx + direction);
|
||||
if (!didHighlight) {
|
||||
if (wrapAround) {
|
||||
keyboardNavigationHandler.init(direction);
|
||||
return response.success;
|
||||
}
|
||||
return response[direction > 0 ? 'next' : 'prev'];
|
||||
}
|
||||
return response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onButtonNavKbdClick: function (keyboardNavigationHandler) {
|
||||
var response = keyboardNavigationHandler.response, chart = this.chart, wasDisabled = chart.oldRangeSelectorItemState === 3;
|
||||
if (!wasDisabled) {
|
||||
this.fakeClickEvent(chart.rangeSelector.buttons[chart.highlightedRangeSelectorItemIx].element);
|
||||
}
|
||||
return response.success;
|
||||
},
|
||||
/**
|
||||
* Get navigation for the range selector input boxes.
|
||||
* @private
|
||||
* @return {Highcharts.KeyboardNavigationHandler}
|
||||
* The module object.
|
||||
*/
|
||||
getRangeSelectorInputNavigation: function () {
|
||||
var chart = this.chart, keys = this.keyCodes, component = this;
|
||||
return new KeyboardNavigationHandler(chart, {
|
||||
keyCodeMap: [
|
||||
[
|
||||
[
|
||||
keys.tab, keys.up, keys.down
|
||||
],
|
||||
function (keyCode, e) {
|
||||
var direction = (keyCode === keys.tab && e.shiftKey ||
|
||||
keyCode === keys.up) ? -1 : 1;
|
||||
return component.onInputKbdMove(this, direction);
|
||||
}
|
||||
]
|
||||
],
|
||||
validate: function () {
|
||||
return shouldRunInputNavigation(chart);
|
||||
},
|
||||
init: function (direction) {
|
||||
component.onInputNavInit(direction);
|
||||
},
|
||||
terminate: function () {
|
||||
component.onInputNavTerminate();
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @param {number} direction
|
||||
* @return {number} Response code
|
||||
*/
|
||||
onInputKbdMove: function (keyboardNavigationHandler, direction) {
|
||||
var chart = this.chart, response = keyboardNavigationHandler.response, newIx = chart.highlightedInputRangeIx =
|
||||
chart.highlightedInputRangeIx + direction, newIxOutOfRange = newIx > 1 || newIx < 0;
|
||||
if (newIxOutOfRange) {
|
||||
return response[direction > 0 ? 'next' : 'prev'];
|
||||
}
|
||||
chart.rangeSelector[newIx ? 'maxInput' : 'minInput'].focus();
|
||||
return response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {number} direction
|
||||
*/
|
||||
onInputNavInit: function (direction) {
|
||||
var chart = this.chart, buttonIxToHighlight = direction > 0 ? 0 : 1;
|
||||
chart.highlightedInputRangeIx = buttonIxToHighlight;
|
||||
chart.rangeSelector[buttonIxToHighlight ? 'maxInput' : 'minInput'].focus();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onInputNavTerminate: function () {
|
||||
var rangeSel = (this.chart.rangeSelector || {});
|
||||
if (rangeSel.maxInput) {
|
||||
rangeSel.hideInput('max');
|
||||
}
|
||||
if (rangeSel.minInput) {
|
||||
rangeSel.hideInput('min');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Get keyboard navigation handlers for this component.
|
||||
* @return {Array<Highcharts.KeyboardNavigationHandler>}
|
||||
* List of module objects.
|
||||
*/
|
||||
getKeyboardNavigation: function () {
|
||||
return [
|
||||
this.getRangeSelectorButtonNavigation(),
|
||||
this.getRangeSelectorInputNavigation()
|
||||
];
|
||||
}
|
||||
});
|
||||
export default RangeSelectorComponent;
|
||||
@@ -0,0 +1,171 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Handle forcing series markers.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../../Core/Globals.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, merge = U.merge;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function isWithinDescriptionThreshold(series) {
|
||||
var a11yOptions = series.chart.options.accessibility;
|
||||
return series.points.length <
|
||||
a11yOptions.series.pointDescriptionEnabledThreshold ||
|
||||
a11yOptions.series.pointDescriptionEnabledThreshold === false;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function shouldForceMarkers(series) {
|
||||
var chart = series.chart, chartA11yEnabled = chart.options.accessibility.enabled, seriesA11yEnabled = (series.options.accessibility &&
|
||||
series.options.accessibility.enabled) !== false;
|
||||
return chartA11yEnabled && seriesA11yEnabled && isWithinDescriptionThreshold(series);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function hasIndividualPointMarkerOptions(series) {
|
||||
return !!(series._hasPointMarkers && series.points && series.points.length);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function unforceSeriesMarkerOptions(series) {
|
||||
var resetMarkerOptions = series.resetA11yMarkerOptions;
|
||||
if (resetMarkerOptions) {
|
||||
merge(true, series.options, {
|
||||
marker: {
|
||||
enabled: resetMarkerOptions.enabled,
|
||||
states: {
|
||||
normal: {
|
||||
opacity: resetMarkerOptions.states &&
|
||||
resetMarkerOptions.states.normal &&
|
||||
resetMarkerOptions.states.normal.opacity
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function forceZeroOpacityMarkerOptions(options) {
|
||||
merge(true, options, {
|
||||
marker: {
|
||||
enabled: true,
|
||||
states: {
|
||||
normal: {
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getPointMarkerOpacity(pointOptions) {
|
||||
return pointOptions.marker.states &&
|
||||
pointOptions.marker.states.normal &&
|
||||
pointOptions.marker.states.normal.opacity || 1;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function unforcePointMarkerOptions(pointOptions) {
|
||||
merge(true, pointOptions.marker, {
|
||||
states: {
|
||||
normal: {
|
||||
opacity: getPointMarkerOpacity(pointOptions)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function handleForcePointMarkers(series) {
|
||||
var i = series.points.length;
|
||||
while (i--) {
|
||||
var point = series.points[i];
|
||||
var pointOptions = point.options;
|
||||
delete point.hasForcedA11yMarker;
|
||||
if (pointOptions.marker) {
|
||||
if (pointOptions.marker.enabled) {
|
||||
unforcePointMarkerOptions(pointOptions);
|
||||
point.hasForcedA11yMarker = false;
|
||||
}
|
||||
else {
|
||||
forceZeroOpacityMarkerOptions(pointOptions);
|
||||
point.hasForcedA11yMarker = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function addForceMarkersEvents() {
|
||||
/**
|
||||
* Keep track of forcing markers.
|
||||
* @private
|
||||
*/
|
||||
addEvent(H.Series, 'render', function () {
|
||||
var series = this, options = series.options;
|
||||
if (shouldForceMarkers(series)) {
|
||||
if (options.marker && options.marker.enabled === false) {
|
||||
series.a11yMarkersForced = true;
|
||||
forceZeroOpacityMarkerOptions(series.options);
|
||||
}
|
||||
if (hasIndividualPointMarkerOptions(series)) {
|
||||
handleForcePointMarkers(series);
|
||||
}
|
||||
}
|
||||
else if (series.a11yMarkersForced) {
|
||||
delete series.a11yMarkersForced;
|
||||
unforceSeriesMarkerOptions(series);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Keep track of options to reset markers to if no longer forced.
|
||||
* @private
|
||||
*/
|
||||
addEvent(H.Series, 'afterSetOptions', function (e) {
|
||||
this.resetA11yMarkerOptions = merge(e.options.marker || {}, this.userOptions.marker || {});
|
||||
});
|
||||
/**
|
||||
* Process marker graphics after render
|
||||
* @private
|
||||
*/
|
||||
addEvent(H.Series, 'afterRender', function () {
|
||||
var series = this;
|
||||
// For styled mode the rendered graphic does not reflect the style
|
||||
// options, and we need to add/remove classes to achieve the same.
|
||||
if (series.chart.styledMode) {
|
||||
if (series.markerGroup) {
|
||||
series.markerGroup[series.a11yMarkersForced ? 'addClass' : 'removeClass']('highcharts-a11y-markers-hidden');
|
||||
}
|
||||
// Do we need to handle individual points?
|
||||
if (hasIndividualPointMarkerOptions(series)) {
|
||||
series.points.forEach(function (point) {
|
||||
if (point.graphic) {
|
||||
point.graphic[point.hasForcedA11yMarker ? 'addClass' : 'removeClass']('highcharts-a11y-marker-hidden');
|
||||
point.graphic[point.hasForcedA11yMarker === false ? 'addClass' : 'removeClass']('highcharts-a11y-marker-visible');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
export default addForceMarkersEvents;
|
||||
@@ -0,0 +1,254 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Handle announcing new data for a chart.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../../Core/Globals.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var extend = U.extend, defined = U.defined;
|
||||
import ChartUtilities from '../../Utils/ChartUtilities.js';
|
||||
var getChartTitle = ChartUtilities.getChartTitle;
|
||||
import SeriesDescriber from './SeriesDescriber.js';
|
||||
var defaultPointDescriptionFormatter = SeriesDescriber
|
||||
.defaultPointDescriptionFormatter, defaultSeriesDescriptionFormatter = SeriesDescriber
|
||||
.defaultSeriesDescriptionFormatter;
|
||||
import Announcer from '../../Utils/Announcer.js';
|
||||
import EventProvider from '../../Utils/EventProvider.js';
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function chartHasAnnounceEnabled(chart) {
|
||||
return !!chart.options.accessibility.announceNewData.enabled;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function findPointInDataArray(point) {
|
||||
var candidates = point.series.data.filter(function (candidate) {
|
||||
return point.x === candidate.x && point.y === candidate.y;
|
||||
});
|
||||
return candidates.length === 1 ? candidates[0] : point;
|
||||
}
|
||||
/**
|
||||
* Get array of unique series from two arrays
|
||||
* @private
|
||||
*/
|
||||
function getUniqueSeries(arrayA, arrayB) {
|
||||
var uniqueSeries = (arrayA || []).concat(arrayB || [])
|
||||
.reduce(function (acc, cur) {
|
||||
acc[cur.name + cur.index] = cur;
|
||||
return acc;
|
||||
}, {});
|
||||
return Object.keys(uniqueSeries).map(function (ix) {
|
||||
return uniqueSeries[ix];
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var NewDataAnnouncer = function (chart) {
|
||||
this.chart = chart;
|
||||
};
|
||||
extend(NewDataAnnouncer.prototype, {
|
||||
/**
|
||||
* Initialize the new data announcer.
|
||||
* @private
|
||||
*/
|
||||
init: function () {
|
||||
var chart = this.chart;
|
||||
var announceOptions = chart.options.accessibility.announceNewData;
|
||||
var announceType = announceOptions.interruptUser ? 'assertive' : 'polite';
|
||||
this.lastAnnouncementTime = 0;
|
||||
this.dirty = {
|
||||
allSeries: {}
|
||||
};
|
||||
this.eventProvider = new EventProvider();
|
||||
this.announcer = new Announcer(chart, announceType);
|
||||
this.addEventListeners();
|
||||
},
|
||||
/**
|
||||
* Remove traces of announcer.
|
||||
* @private
|
||||
*/
|
||||
destroy: function () {
|
||||
this.eventProvider.removeAddedEvents();
|
||||
this.announcer.destroy();
|
||||
},
|
||||
/**
|
||||
* Add event listeners for the announcer
|
||||
* @private
|
||||
*/
|
||||
addEventListeners: function () {
|
||||
var announcer = this, chart = this.chart, e = this.eventProvider;
|
||||
e.addEvent(chart, 'afterDrilldown', function () {
|
||||
announcer.lastAnnouncementTime = 0;
|
||||
});
|
||||
e.addEvent(H.Series, 'updatedData', function () {
|
||||
announcer.onSeriesUpdatedData(this);
|
||||
});
|
||||
e.addEvent(chart, 'afterAddSeries', function (e) {
|
||||
announcer.onSeriesAdded(e.series);
|
||||
});
|
||||
e.addEvent(H.Series, 'addPoint', function (e) {
|
||||
announcer.onPointAdded(e.point);
|
||||
});
|
||||
e.addEvent(chart, 'redraw', function () {
|
||||
announcer.announceDirtyData();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* On new data in the series, make sure we add it to the dirty list.
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
*/
|
||||
onSeriesUpdatedData: function (series) {
|
||||
var chart = this.chart;
|
||||
if (series.chart === chart && chartHasAnnounceEnabled(chart)) {
|
||||
this.dirty.hasDirty = true;
|
||||
this.dirty.allSeries[series.name + series.index] = series;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* On new data series added, update dirty list.
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
*/
|
||||
onSeriesAdded: function (series) {
|
||||
if (chartHasAnnounceEnabled(this.chart)) {
|
||||
this.dirty.hasDirty = true;
|
||||
this.dirty.allSeries[series.name + series.index] = series;
|
||||
// Add it to newSeries storage unless we already have one
|
||||
this.dirty.newSeries = defined(this.dirty.newSeries) ?
|
||||
void 0 : series;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* On new point added, update dirty list.
|
||||
* @private
|
||||
* @param {Highcharts.Point} point
|
||||
*/
|
||||
onPointAdded: function (point) {
|
||||
var chart = point.series.chart;
|
||||
if (this.chart === chart && chartHasAnnounceEnabled(chart)) {
|
||||
// Add it to newPoint storage unless we already have one
|
||||
this.dirty.newPoint = defined(this.dirty.newPoint) ?
|
||||
void 0 : point;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Gather what we know and announce the data to user.
|
||||
* @private
|
||||
*/
|
||||
announceDirtyData: function () {
|
||||
var chart = this.chart, announcer = this;
|
||||
if (chart.options.accessibility.announceNewData &&
|
||||
this.dirty.hasDirty) {
|
||||
var newPoint = this.dirty.newPoint;
|
||||
// If we have a single new point, see if we can find it in the
|
||||
// data array. Otherwise we can only pass through options to
|
||||
// the description builder, and it is a bit sparse in info.
|
||||
if (newPoint) {
|
||||
newPoint = findPointInDataArray(newPoint);
|
||||
}
|
||||
this.queueAnnouncement(Object.keys(this.dirty.allSeries).map(function (ix) {
|
||||
return announcer.dirty.allSeries[ix];
|
||||
}), this.dirty.newSeries, newPoint);
|
||||
// Reset
|
||||
this.dirty = {
|
||||
allSeries: {}
|
||||
};
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Announce to user that there is new data.
|
||||
* @private
|
||||
* @param {Array<Highcharts.Series>} dirtySeries
|
||||
* Array of series with new data.
|
||||
* @param {Highcharts.Series} [newSeries]
|
||||
* If a single new series was added, a reference to this series.
|
||||
* @param {Highcharts.Point} [newPoint]
|
||||
* If a single point was added, a reference to this point.
|
||||
*/
|
||||
queueAnnouncement: function (dirtySeries, newSeries, newPoint) {
|
||||
var _this = this;
|
||||
var chart = this.chart;
|
||||
var annOptions = chart.options.accessibility.announceNewData;
|
||||
if (annOptions.enabled) {
|
||||
var now = +new Date();
|
||||
var dTime = now - this.lastAnnouncementTime;
|
||||
var time = Math.max(0, annOptions.minAnnounceInterval - dTime);
|
||||
// Add series from previously queued announcement.
|
||||
var allSeries = getUniqueSeries(this.queuedAnnouncement && this.queuedAnnouncement.series, dirtySeries);
|
||||
// Build message and announce
|
||||
var message = this.buildAnnouncementMessage(allSeries, newSeries, newPoint);
|
||||
if (message) {
|
||||
// Is there already one queued?
|
||||
if (this.queuedAnnouncement) {
|
||||
clearTimeout(this.queuedAnnouncementTimer);
|
||||
}
|
||||
// Build the announcement
|
||||
this.queuedAnnouncement = {
|
||||
time: now,
|
||||
message: message,
|
||||
series: allSeries
|
||||
};
|
||||
// Queue the announcement
|
||||
this.queuedAnnouncementTimer = setTimeout(function () {
|
||||
if (_this && _this.announcer) {
|
||||
_this.lastAnnouncementTime = +new Date();
|
||||
_this.announcer.announce(_this.queuedAnnouncement.message);
|
||||
delete _this.queuedAnnouncement;
|
||||
delete _this.queuedAnnouncementTimer;
|
||||
}
|
||||
}, time);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Get announcement message for new data.
|
||||
* @private
|
||||
* @param {Array<Highcharts.Series>} dirtySeries
|
||||
* Array of series with new data.
|
||||
* @param {Highcharts.Series} [newSeries]
|
||||
* If a single new series was added, a reference to this series.
|
||||
* @param {Highcharts.Point} [newPoint]
|
||||
* If a single point was added, a reference to this point.
|
||||
*
|
||||
* @return {string|null}
|
||||
* The announcement message to give to user.
|
||||
*/
|
||||
buildAnnouncementMessage: function (dirtySeries, newSeries, newPoint) {
|
||||
var chart = this.chart, annOptions = chart.options.accessibility.announceNewData;
|
||||
// User supplied formatter?
|
||||
if (annOptions.announcementFormatter) {
|
||||
var formatterRes = annOptions.announcementFormatter(dirtySeries, newSeries, newPoint);
|
||||
if (formatterRes !== false) {
|
||||
return formatterRes.length ? formatterRes : null;
|
||||
}
|
||||
}
|
||||
// Default formatter - use lang options
|
||||
var multiple = H.charts && H.charts.length > 1 ? 'Multiple' : 'Single', langKey = newSeries ? 'newSeriesAnnounce' + multiple :
|
||||
newPoint ? 'newPointAnnounce' + multiple : 'newDataAnnounce', chartTitle = getChartTitle(chart);
|
||||
return chart.langFormat('accessibility.announceNewData.' + langKey, {
|
||||
chartTitle: chartTitle,
|
||||
seriesDesc: newSeries ?
|
||||
defaultSeriesDescriptionFormatter(newSeries) :
|
||||
null,
|
||||
pointDesc: newPoint ?
|
||||
defaultPointDescriptionFormatter(newPoint) :
|
||||
null,
|
||||
point: newPoint,
|
||||
series: newSeries
|
||||
});
|
||||
}
|
||||
});
|
||||
export default NewDataAnnouncer;
|
||||
@@ -0,0 +1,109 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility component for series and points.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../../Core/Globals.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var extend = U.extend;
|
||||
import AccessibilityComponent from '../../AccessibilityComponent.js';
|
||||
import SeriesKeyboardNavigation from './SeriesKeyboardNavigation.js';
|
||||
import NewDataAnnouncer from './NewDataAnnouncer.js';
|
||||
import addForceMarkersEvents from './ForcedMarkers.js';
|
||||
import ChartUtilities from '../../Utils/ChartUtilities.js';
|
||||
var hideSeriesFromAT = ChartUtilities.hideSeriesFromAT;
|
||||
import SeriesDescriber from './SeriesDescriber.js';
|
||||
var describeSeries = SeriesDescriber.describeSeries;
|
||||
import Tooltip from '../../../Core/Tooltip.js';
|
||||
// Expose functionality to users
|
||||
H.SeriesAccessibilityDescriber = SeriesDescriber;
|
||||
// Handle forcing markers
|
||||
addForceMarkersEvents();
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* The SeriesComponent class
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.SeriesComponent
|
||||
*/
|
||||
var SeriesComponent = function () { };
|
||||
SeriesComponent.prototype = new AccessibilityComponent();
|
||||
extend(SeriesComponent.prototype, /** @lends Highcharts.SeriesComponent */ {
|
||||
/**
|
||||
* Init the component.
|
||||
*/
|
||||
init: function () {
|
||||
this.newDataAnnouncer = new NewDataAnnouncer(this.chart);
|
||||
this.newDataAnnouncer.init();
|
||||
this.keyboardNavigation = new SeriesKeyboardNavigation(this.chart, this.keyCodes);
|
||||
this.keyboardNavigation.init();
|
||||
this.hideTooltipFromATWhenShown();
|
||||
this.hideSeriesLabelsFromATWhenShown();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
hideTooltipFromATWhenShown: function () {
|
||||
var component = this;
|
||||
this.addEvent(Tooltip, 'refresh', function () {
|
||||
if (this.chart === component.chart &&
|
||||
this.label &&
|
||||
this.label.element) {
|
||||
this.label.element.setAttribute('aria-hidden', true);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
hideSeriesLabelsFromATWhenShown: function () {
|
||||
this.addEvent(this.chart, 'afterDrawSeriesLabels', function () {
|
||||
this.series.forEach(function (series) {
|
||||
if (series.labelBySeries) {
|
||||
series.labelBySeries.attr('aria-hidden', true);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Called on chart render. It is necessary to do this for render in case
|
||||
* markers change on zoom/pixel density.
|
||||
*/
|
||||
onChartRender: function () {
|
||||
var chart = this.chart;
|
||||
chart.series.forEach(function (series) {
|
||||
var shouldDescribeSeries = (series.options.accessibility &&
|
||||
series.options.accessibility.enabled) !== false &&
|
||||
series.visible;
|
||||
if (shouldDescribeSeries) {
|
||||
describeSeries(series);
|
||||
}
|
||||
else {
|
||||
hideSeriesFromAT(series);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get keyboard navigation handler for this component.
|
||||
* @return {Highcharts.KeyboardNavigationHandler}
|
||||
*/
|
||||
getKeyboardNavigation: function () {
|
||||
return this.keyboardNavigation.getKeyboardNavigationHandler();
|
||||
},
|
||||
/**
|
||||
* Remove traces
|
||||
*/
|
||||
destroy: function () {
|
||||
this.newDataAnnouncer.destroy();
|
||||
this.keyboardNavigation.destroy();
|
||||
}
|
||||
});
|
||||
export default SeriesComponent;
|
||||
@@ -0,0 +1,397 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Place desriptions on a series and its points.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var find = U.find, format = U.format, isNumber = U.isNumber, numberFormat = U.numberFormat, pick = U.pick, defined = U.defined;
|
||||
import AnnotationsA11y from '../AnnotationsA11y.js';
|
||||
var getPointAnnotationTexts = AnnotationsA11y.getPointAnnotationTexts;
|
||||
import HTMLUtilities from '../../Utils/HTMLUtilities.js';
|
||||
var escapeStringForHTML = HTMLUtilities.escapeStringForHTML, reverseChildNodes = HTMLUtilities.reverseChildNodes, stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
|
||||
import ChartUtilities from '../../Utils/ChartUtilities.js';
|
||||
var getAxisDescription = ChartUtilities.getAxisDescription, getSeriesFirstPointElement = ChartUtilities.getSeriesFirstPointElement, getSeriesA11yElement = ChartUtilities.getSeriesA11yElement, unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
|
||||
import Tooltip from '../../../Core/Tooltip.js';
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function findFirstPointWithGraphic(point) {
|
||||
var sourcePointIndex = point.index;
|
||||
if (!point.series || !point.series.data || !defined(sourcePointIndex)) {
|
||||
return null;
|
||||
}
|
||||
return find(point.series.data, function (p) {
|
||||
return !!(p &&
|
||||
typeof p.index !== 'undefined' &&
|
||||
p.index > sourcePointIndex &&
|
||||
p.graphic &&
|
||||
p.graphic.element);
|
||||
}) || null;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function shouldAddDummyPoint(point) {
|
||||
// Note: Sunburst series use isNull for hidden points on drilldown.
|
||||
// Ignore these.
|
||||
var isSunburst = point.series && point.series.is('sunburst'), isNull = point.isNull;
|
||||
return isNull && !isSunburst;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function makeDummyElement(point, pos) {
|
||||
var renderer = point.series.chart.renderer, dummy = renderer.rect(pos.x, pos.y, 1, 1);
|
||||
dummy.attr({
|
||||
'class': 'highcharts-a11y-dummy-point',
|
||||
fill: 'none',
|
||||
opacity: 0,
|
||||
'fill-opacity': 0,
|
||||
'stroke-opacity': 0
|
||||
});
|
||||
return dummy;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Point} point
|
||||
* @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined}
|
||||
*/
|
||||
function addDummyPointElement(point) {
|
||||
var series = point.series, firstPointWithGraphic = findFirstPointWithGraphic(point), firstGraphic = firstPointWithGraphic && firstPointWithGraphic.graphic, parentGroup = firstGraphic ?
|
||||
firstGraphic.parentGroup :
|
||||
series.graph || series.group, dummyPos = firstPointWithGraphic ? {
|
||||
x: pick(point.plotX, firstPointWithGraphic.plotX, 0),
|
||||
y: pick(point.plotY, firstPointWithGraphic.plotY, 0)
|
||||
} : {
|
||||
x: pick(point.plotX, 0),
|
||||
y: pick(point.plotY, 0)
|
||||
}, dummyElement = makeDummyElement(point, dummyPos);
|
||||
if (parentGroup && parentGroup.element) {
|
||||
point.graphic = dummyElement;
|
||||
point.hasDummyGraphic = true;
|
||||
dummyElement.add(parentGroup);
|
||||
// Move to correct pos in DOM
|
||||
parentGroup.element.insertBefore(dummyElement.element, firstGraphic ? firstGraphic.element : null);
|
||||
return dummyElement.element;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* @return {boolean}
|
||||
*/
|
||||
function hasMorePointsThanDescriptionThreshold(series) {
|
||||
var chartA11yOptions = series.chart.options.accessibility, threshold = (chartA11yOptions.series.pointDescriptionEnabledThreshold);
|
||||
return !!(threshold !== false &&
|
||||
series.points &&
|
||||
series.points.length >= threshold);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* @return {boolean}
|
||||
*/
|
||||
function shouldSetScreenReaderPropsOnPoints(series) {
|
||||
var seriesA11yOptions = series.options.accessibility || {};
|
||||
return !hasMorePointsThanDescriptionThreshold(series) &&
|
||||
!seriesA11yOptions.exposeAsGroupOnly;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* @return {boolean}
|
||||
*/
|
||||
function shouldSetKeyboardNavPropsOnPoints(series) {
|
||||
var chartA11yOptions = series.chart.options.accessibility, seriesNavOptions = chartA11yOptions.keyboardNavigation.seriesNavigation;
|
||||
return !!(series.points && (series.points.length <
|
||||
seriesNavOptions.pointNavigationEnabledThreshold ||
|
||||
seriesNavOptions.pointNavigationEnabledThreshold === false));
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* @return {boolean}
|
||||
*/
|
||||
function shouldDescribeSeriesElement(series) {
|
||||
var chart = series.chart, chartOptions = chart.options.chart || {}, chartHas3d = chartOptions.options3d && chartOptions.options3d.enabled, hasMultipleSeries = chart.series.length > 1, describeSingleSeriesOption = chart.options.accessibility.series.describeSingleSeries, exposeAsGroupOnlyOption = (series.options.accessibility || {}).exposeAsGroupOnly, noDescribe3D = chartHas3d && hasMultipleSeries;
|
||||
return !noDescribe3D && (hasMultipleSeries || describeSingleSeriesOption ||
|
||||
exposeAsGroupOnlyOption || hasMorePointsThanDescriptionThreshold(series));
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Point} point
|
||||
* @param {number} value
|
||||
* @return {string}
|
||||
*/
|
||||
function pointNumberToString(point, value) {
|
||||
var chart = point.series.chart, a11yPointOptions = chart.options.accessibility.point || {}, tooltipOptions = point.series.tooltipOptions || {}, lang = chart.options.lang;
|
||||
if (isNumber(value)) {
|
||||
return numberFormat(value, a11yPointOptions.valueDecimals ||
|
||||
tooltipOptions.valueDecimals ||
|
||||
-1, lang.decimalPoint, lang.accessibility.thousandsSep || lang.thousandsSep);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* @return {string}
|
||||
*/
|
||||
function getSeriesDescriptionText(series) {
|
||||
var seriesA11yOptions = series.options.accessibility || {}, descOpt = seriesA11yOptions.description;
|
||||
return descOpt && series.chart.langFormat('accessibility.series.description', {
|
||||
description: descOpt,
|
||||
series: series
|
||||
}) || '';
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.series} series
|
||||
* @param {string} axisCollection
|
||||
* @return {string}
|
||||
*/
|
||||
function getSeriesAxisDescriptionText(series, axisCollection) {
|
||||
var axis = series[axisCollection];
|
||||
return series.chart.langFormat('accessibility.series.' + axisCollection + 'Description', {
|
||||
name: getAxisDescription(axis),
|
||||
series: series
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get accessible time description for a point on a datetime axis.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Point#getTimeDescription
|
||||
* @param {Highcharts.Point} point
|
||||
* @return {string|undefined}
|
||||
* The description as string.
|
||||
*/
|
||||
function getPointA11yTimeDescription(point) {
|
||||
var series = point.series, chart = series.chart, a11yOptions = chart.options.accessibility.point || {}, hasDateXAxis = series.xAxis && series.xAxis.dateTime;
|
||||
if (hasDateXAxis) {
|
||||
var tooltipDateFormat = Tooltip.prototype.getXDateFormat.call({
|
||||
getDateFormat: Tooltip.prototype.getDateFormat,
|
||||
chart: chart
|
||||
}, point, chart.options.tooltip, series.xAxis), dateFormat = a11yOptions.dateFormatter &&
|
||||
a11yOptions.dateFormatter(point) ||
|
||||
a11yOptions.dateFormat ||
|
||||
tooltipDateFormat;
|
||||
return chart.time.dateFormat(dateFormat, point.x, void 0);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Point} point
|
||||
* @return {string}
|
||||
*/
|
||||
function getPointXDescription(point) {
|
||||
var timeDesc = getPointA11yTimeDescription(point), xAxis = point.series.xAxis || {}, pointCategory = xAxis.categories && defined(point.category) &&
|
||||
('' + point.category).replace('<br/>', ' '), canUseId = point.id && point.id.indexOf('highcharts-') < 0, fallback = 'x, ' + point.x;
|
||||
return point.name || timeDesc || pointCategory ||
|
||||
(canUseId ? point.id : fallback);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Point} point
|
||||
* @param {string} prefix
|
||||
* @param {string} suffix
|
||||
* @return {string}
|
||||
*/
|
||||
function getPointArrayMapValueDescription(point, prefix, suffix) {
|
||||
var pre = prefix || '', suf = suffix || '', keyToValStr = function (key) {
|
||||
var num = pointNumberToString(point, pick(point[key], point.options[key]));
|
||||
return key + ': ' + pre + num + suf;
|
||||
}, pointArrayMap = point.series.pointArrayMap;
|
||||
return pointArrayMap.reduce(function (desc, key) {
|
||||
return desc + (desc.length ? ', ' : '') + keyToValStr(key);
|
||||
}, '');
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Point} point
|
||||
* @return {string}
|
||||
*/
|
||||
function getPointValue(point) {
|
||||
var series = point.series, a11yPointOpts = series.chart.options.accessibility.point || {}, tooltipOptions = series.tooltipOptions || {}, valuePrefix = a11yPointOpts.valuePrefix ||
|
||||
tooltipOptions.valuePrefix || '', valueSuffix = a11yPointOpts.valueSuffix ||
|
||||
tooltipOptions.valueSuffix || '', fallbackKey = (typeof point.value !==
|
||||
'undefined' ?
|
||||
'value' : 'y'), fallbackDesc = pointNumberToString(point, point[fallbackKey]);
|
||||
if (point.isNull) {
|
||||
return series.chart.langFormat('accessibility.series.nullPointValue', {
|
||||
point: point
|
||||
});
|
||||
}
|
||||
if (series.pointArrayMap) {
|
||||
return getPointArrayMapValueDescription(point, valuePrefix, valueSuffix);
|
||||
}
|
||||
return valuePrefix + fallbackDesc + valueSuffix;
|
||||
}
|
||||
/**
|
||||
* Return the description for the annotation(s) connected to a point, or empty
|
||||
* string if none.
|
||||
*
|
||||
* @private
|
||||
* @param {Highcharts.Point} point The data point to get the annotation info from.
|
||||
* @return {string} Annotation description
|
||||
*/
|
||||
function getPointAnnotationDescription(point) {
|
||||
var chart = point.series.chart;
|
||||
var langKey = 'accessibility.series.pointAnnotationsDescription';
|
||||
var annotations = getPointAnnotationTexts(point);
|
||||
var context = { point: point, annotations: annotations };
|
||||
return annotations.length ? chart.langFormat(langKey, context) : '';
|
||||
}
|
||||
/**
|
||||
* Return string with information about point.
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
function getPointValueDescription(point) {
|
||||
var series = point.series, chart = series.chart, pointValueDescriptionFormat = chart.options.accessibility
|
||||
.point.valueDescriptionFormat, showXDescription = pick(series.xAxis &&
|
||||
series.xAxis.options.accessibility &&
|
||||
series.xAxis.options.accessibility.enabled, !chart.angular), xDesc = showXDescription ? getPointXDescription(point) : '', context = {
|
||||
point: point,
|
||||
index: defined(point.index) ? (point.index + 1) : '',
|
||||
xDescription: xDesc,
|
||||
value: getPointValue(point),
|
||||
separator: showXDescription ? ', ' : ''
|
||||
};
|
||||
return format(pointValueDescriptionFormat, context, chart);
|
||||
}
|
||||
/**
|
||||
* Return string with information about point.
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
function defaultPointDescriptionFormatter(point) {
|
||||
var series = point.series, chart = series.chart, valText = getPointValueDescription(point), description = point.options && point.options.accessibility &&
|
||||
point.options.accessibility.description, userDescText = description ? ' ' + description : '', seriesNameText = chart.series.length > 1 && series.name ?
|
||||
' ' + series.name + '.' : '', annotationsDesc = getPointAnnotationDescription(point), pointAnnotationsText = annotationsDesc ? ' ' + annotationsDesc : '';
|
||||
point.accessibility = point.accessibility || {};
|
||||
point.accessibility.valueDescription = valText;
|
||||
return valText + userDescText + seriesNameText + pointAnnotationsText;
|
||||
}
|
||||
/**
|
||||
* Set a11y props on a point element
|
||||
* @private
|
||||
* @param {Highcharts.Point} point
|
||||
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} pointElement
|
||||
*/
|
||||
function setPointScreenReaderAttribs(point, pointElement) {
|
||||
var series = point.series, a11yPointOptions = series.chart.options.accessibility.point || {}, seriesA11yOptions = series.options.accessibility || {}, label = escapeStringForHTML(stripHTMLTags(seriesA11yOptions.pointDescriptionFormatter &&
|
||||
seriesA11yOptions.pointDescriptionFormatter(point) ||
|
||||
a11yPointOptions.descriptionFormatter &&
|
||||
a11yPointOptions.descriptionFormatter(point) ||
|
||||
defaultPointDescriptionFormatter(point)));
|
||||
pointElement.setAttribute('role', 'img');
|
||||
pointElement.setAttribute('aria-label', label);
|
||||
}
|
||||
/**
|
||||
* Add accessible info to individual point elements of a series
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
*/
|
||||
function describePointsInSeries(series) {
|
||||
var setScreenReaderProps = shouldSetScreenReaderPropsOnPoints(series), setKeyboardProps = shouldSetKeyboardNavPropsOnPoints(series);
|
||||
if (setScreenReaderProps || setKeyboardProps) {
|
||||
series.points.forEach(function (point) {
|
||||
var pointEl = point.graphic && point.graphic.element ||
|
||||
shouldAddDummyPoint(point) && addDummyPointElement(point);
|
||||
if (pointEl) {
|
||||
// We always set tabindex, as long as we are setting props.
|
||||
// When setting tabindex, also remove default outline to
|
||||
// avoid ugly border on click.
|
||||
pointEl.setAttribute('tabindex', '-1');
|
||||
pointEl.style.outline = '0';
|
||||
if (setScreenReaderProps) {
|
||||
setPointScreenReaderAttribs(point, pointEl);
|
||||
}
|
||||
else {
|
||||
pointEl.setAttribute('aria-hidden', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Return string with information about series.
|
||||
* @private
|
||||
* @return {string}
|
||||
*/
|
||||
function defaultSeriesDescriptionFormatter(series) {
|
||||
var chart = series.chart, chartTypes = chart.types || [], description = getSeriesDescriptionText(series), shouldDescribeAxis = function (coll) {
|
||||
return chart[coll] && chart[coll].length > 1 && series[coll];
|
||||
}, xAxisInfo = getSeriesAxisDescriptionText(series, 'xAxis'), yAxisInfo = getSeriesAxisDescriptionText(series, 'yAxis'), summaryContext = {
|
||||
name: series.name || '',
|
||||
ix: series.index + 1,
|
||||
numSeries: chart.series && chart.series.length,
|
||||
numPoints: series.points && series.points.length,
|
||||
series: series
|
||||
}, combinationSuffix = chartTypes.length > 1 ? 'Combination' : '', summary = chart.langFormat('accessibility.series.summary.' + series.type + combinationSuffix, summaryContext) || chart.langFormat('accessibility.series.summary.default' + combinationSuffix, summaryContext);
|
||||
return summary + (description ? ' ' + description : '') + (shouldDescribeAxis('yAxis') ? ' ' + yAxisInfo : '') + (shouldDescribeAxis('xAxis') ? ' ' + xAxisInfo : '');
|
||||
}
|
||||
/**
|
||||
* Set a11y props on a series element
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} seriesElement
|
||||
*/
|
||||
function describeSeriesElement(series, seriesElement) {
|
||||
var seriesA11yOptions = series.options.accessibility || {}, a11yOptions = series.chart.options.accessibility, landmarkVerbosity = a11yOptions.landmarkVerbosity;
|
||||
// Handle role attribute
|
||||
if (seriesA11yOptions.exposeAsGroupOnly) {
|
||||
seriesElement.setAttribute('role', 'img');
|
||||
}
|
||||
else if (landmarkVerbosity === 'all') {
|
||||
seriesElement.setAttribute('role', 'region');
|
||||
} /* else do not add role */
|
||||
seriesElement.setAttribute('tabindex', '-1');
|
||||
seriesElement.style.outline = '0'; // Don't show browser outline on click, despite tabindex
|
||||
seriesElement.setAttribute('aria-label', escapeStringForHTML(stripHTMLTags(a11yOptions.series.descriptionFormatter &&
|
||||
a11yOptions.series.descriptionFormatter(series) ||
|
||||
defaultSeriesDescriptionFormatter(series))));
|
||||
}
|
||||
/**
|
||||
* Put accessible info on series and points of a series.
|
||||
* @param {Highcharts.Series} series The series to add info on.
|
||||
*/
|
||||
function describeSeries(series) {
|
||||
var chart = series.chart, firstPointEl = getSeriesFirstPointElement(series), seriesEl = getSeriesA11yElement(series), is3d = chart.is3d && chart.is3d();
|
||||
if (seriesEl) {
|
||||
// For some series types the order of elements do not match the
|
||||
// order of points in series. In that case we have to reverse them
|
||||
// in order for AT to read them out in an understandable order.
|
||||
// Due to z-index issues we can not do this for 3D charts.
|
||||
if (seriesEl.lastChild === firstPointEl && !is3d) {
|
||||
reverseChildNodes(seriesEl);
|
||||
}
|
||||
describePointsInSeries(series);
|
||||
unhideChartElementFromAT(chart, seriesEl);
|
||||
if (shouldDescribeSeriesElement(series)) {
|
||||
describeSeriesElement(series, seriesEl);
|
||||
}
|
||||
else {
|
||||
seriesEl.setAttribute('aria-label', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
var SeriesDescriber = {
|
||||
describeSeries: describeSeries,
|
||||
defaultPointDescriptionFormatter: defaultPointDescriptionFormatter,
|
||||
defaultSeriesDescriptionFormatter: defaultSeriesDescriptionFormatter,
|
||||
getPointA11yTimeDescription: getPointA11yTimeDescription,
|
||||
getPointXDescription: getPointXDescription,
|
||||
getPointValue: getPointValue,
|
||||
getPointValueDescription: getPointValueDescription
|
||||
};
|
||||
export default SeriesDescriber;
|
||||
+569
@@ -0,0 +1,569 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Handle keyboard navigation for series.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Chart from '../../../Core/Chart/Chart.js';
|
||||
import H from '../../../Core/Globals.js';
|
||||
import Point from '../../../Core/Series/Point.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var defined = U.defined, extend = U.extend;
|
||||
import KeyboardNavigationHandler from '../../KeyboardNavigationHandler.js';
|
||||
import EventProvider from '../../Utils/EventProvider.js';
|
||||
import ChartUtilities from '../../Utils/ChartUtilities.js';
|
||||
var getPointFromXY = ChartUtilities.getPointFromXY, getSeriesFromName = ChartUtilities.getSeriesFromName, scrollToPoint = ChartUtilities.scrollToPoint;
|
||||
import '../../../Series/ColumnSeries.js';
|
||||
import '../../../Series/PieSeries.js';
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/*
|
||||
* Set for which series types it makes sense to move to the closest point with
|
||||
* up/down arrows, and which series types should just move to next series.
|
||||
*/
|
||||
H.Series.prototype.keyboardMoveVertical = true;
|
||||
['column', 'pie'].forEach(function (type) {
|
||||
if (H.seriesTypes[type]) {
|
||||
H.seriesTypes[type].prototype.keyboardMoveVertical = false;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Get the index of a point in a series. This is needed when using e.g. data
|
||||
* grouping.
|
||||
*
|
||||
* @private
|
||||
* @function getPointIndex
|
||||
*
|
||||
* @param {Highcharts.AccessibilityPoint} point
|
||||
* The point to find index of.
|
||||
*
|
||||
* @return {number|undefined}
|
||||
* The index in the series.points array of the point.
|
||||
*/
|
||||
function getPointIndex(point) {
|
||||
var index = point.index, points = point.series.points, i = points.length;
|
||||
if (points[index] !== point) {
|
||||
while (i--) {
|
||||
if (points[i] === point) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Determine if series navigation should be skipped
|
||||
*
|
||||
* @private
|
||||
* @function isSkipSeries
|
||||
*
|
||||
* @param {Highcharts.Series} series
|
||||
*
|
||||
* @return {boolean|number|undefined}
|
||||
*/
|
||||
function isSkipSeries(series) {
|
||||
var a11yOptions = series.chart.options.accessibility, seriesNavOptions = a11yOptions.keyboardNavigation.seriesNavigation, seriesA11yOptions = series.options.accessibility || {}, seriesKbdNavOptions = seriesA11yOptions.keyboardNavigation;
|
||||
return seriesKbdNavOptions && seriesKbdNavOptions.enabled === false ||
|
||||
seriesA11yOptions.enabled === false ||
|
||||
series.options.enableMouseTracking === false || // #8440
|
||||
!series.visible ||
|
||||
// Skip all points in a series where pointNavigationEnabledThreshold is
|
||||
// reached
|
||||
(seriesNavOptions.pointNavigationEnabledThreshold &&
|
||||
seriesNavOptions.pointNavigationEnabledThreshold <=
|
||||
series.points.length);
|
||||
}
|
||||
/**
|
||||
* Determine if navigation for a point should be skipped
|
||||
*
|
||||
* @private
|
||||
* @function isSkipPoint
|
||||
*
|
||||
* @param {Highcharts.Point} point
|
||||
*
|
||||
* @return {boolean|number|undefined}
|
||||
*/
|
||||
function isSkipPoint(point) {
|
||||
var a11yOptions = point.series.chart.options.accessibility;
|
||||
return point.isNull &&
|
||||
a11yOptions.keyboardNavigation.seriesNavigation.skipNullPoints ||
|
||||
point.visible === false ||
|
||||
isSkipSeries(point.series);
|
||||
}
|
||||
/**
|
||||
* Get the point in a series that is closest (in pixel distance) to a reference
|
||||
* point. Optionally supply weight factors for x and y directions.
|
||||
*
|
||||
* @private
|
||||
* @function getClosestPoint
|
||||
*
|
||||
* @param {Highcharts.Point} point
|
||||
* @param {Highcharts.Series} series
|
||||
* @param {number} [xWeight]
|
||||
* @param {number} [yWeight]
|
||||
*
|
||||
* @return {Highcharts.Point|undefined}
|
||||
*/
|
||||
function getClosestPoint(point, series, xWeight, yWeight) {
|
||||
var minDistance = Infinity, dPoint, minIx, distance, i = series.points.length, hasUndefinedPosition = function (point) {
|
||||
return !(defined(point.plotX) && defined(point.plotY));
|
||||
};
|
||||
if (hasUndefinedPosition(point)) {
|
||||
return;
|
||||
}
|
||||
while (i--) {
|
||||
dPoint = series.points[i];
|
||||
if (hasUndefinedPosition(dPoint)) {
|
||||
continue;
|
||||
}
|
||||
distance = (point.plotX - dPoint.plotX) *
|
||||
(point.plotX - dPoint.plotX) *
|
||||
(xWeight || 1) +
|
||||
(point.plotY - dPoint.plotY) *
|
||||
(point.plotY - dPoint.plotY) *
|
||||
(yWeight || 1);
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
minIx = i;
|
||||
}
|
||||
}
|
||||
return defined(minIx) ? series.points[minIx] : void 0;
|
||||
}
|
||||
/**
|
||||
* Highlights a point (show tooltip and display hover state).
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Point#highlight
|
||||
*
|
||||
* @return {Highcharts.Point}
|
||||
* This highlighted point.
|
||||
*/
|
||||
Point.prototype.highlight = function () {
|
||||
var chart = this.series.chart;
|
||||
if (!this.isNull) {
|
||||
this.onMouseOver(); // Show the hover marker and tooltip
|
||||
}
|
||||
else {
|
||||
if (chart.tooltip) {
|
||||
chart.tooltip.hide(0);
|
||||
}
|
||||
// Don't call blur on the element, as it messes up the chart div's focus
|
||||
}
|
||||
scrollToPoint(this);
|
||||
// We focus only after calling onMouseOver because the state change can
|
||||
// change z-index and mess up the element.
|
||||
if (this.graphic) {
|
||||
chart.setFocusToElement(this.graphic);
|
||||
}
|
||||
chart.highlightedPoint = this;
|
||||
return this;
|
||||
};
|
||||
/**
|
||||
* Function to highlight next/previous point in chart.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#highlightAdjacentPoint
|
||||
*
|
||||
* @param {boolean} next
|
||||
* Flag for the direction.
|
||||
*
|
||||
* @return {Highcharts.Point|boolean}
|
||||
* Returns highlighted point on success, false on failure (no adjacent
|
||||
* point to highlight in chosen direction).
|
||||
*/
|
||||
Chart.prototype.highlightAdjacentPoint = function (next) {
|
||||
var chart = this, series = chart.series, curPoint = chart.highlightedPoint, curPointIndex = curPoint && getPointIndex(curPoint) || 0, curPoints = (curPoint && curPoint.series.points), lastSeries = chart.series && chart.series[chart.series.length - 1], lastPoint = lastSeries && lastSeries.points &&
|
||||
lastSeries.points[lastSeries.points.length - 1], newSeries, newPoint;
|
||||
// If no points, return false
|
||||
if (!series[0] || !series[0].points) {
|
||||
return false;
|
||||
}
|
||||
if (!curPoint) {
|
||||
// No point is highlighted yet. Try first/last point depending on move
|
||||
// direction
|
||||
newPoint = next ? series[0].points[0] : lastPoint;
|
||||
}
|
||||
else {
|
||||
// We have a highlighted point.
|
||||
// Grab next/prev point & series
|
||||
newSeries = series[curPoint.series.index + (next ? 1 : -1)];
|
||||
newPoint = curPoints[curPointIndex + (next ? 1 : -1)];
|
||||
if (!newPoint && newSeries) {
|
||||
// Done with this series, try next one
|
||||
newPoint = newSeries.points[next ? 0 : newSeries.points.length - 1];
|
||||
}
|
||||
// If there is no adjacent point, we return false
|
||||
if (!newPoint) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Recursively skip points
|
||||
if (isSkipPoint(newPoint)) {
|
||||
// If we skip this whole series, move to the end of the series before we
|
||||
// recurse, just to optimize
|
||||
newSeries = newPoint.series;
|
||||
if (isSkipSeries(newSeries)) {
|
||||
chart.highlightedPoint = next ?
|
||||
newSeries.points[newSeries.points.length - 1] :
|
||||
newSeries.points[0];
|
||||
}
|
||||
else {
|
||||
// Otherwise, just move one point
|
||||
chart.highlightedPoint = newPoint;
|
||||
}
|
||||
// Retry
|
||||
return chart.highlightAdjacentPoint(next);
|
||||
}
|
||||
// There is an adjacent point, highlight it
|
||||
return newPoint.highlight();
|
||||
};
|
||||
/**
|
||||
* Highlight first valid point in a series. Returns the point if successfully
|
||||
* highlighted, otherwise false. If there is a highlighted point in the series,
|
||||
* use that as starting point.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Series#highlightFirstValidPoint
|
||||
*
|
||||
* @return {boolean|Highcharts.Point}
|
||||
*/
|
||||
H.Series.prototype.highlightFirstValidPoint = function () {
|
||||
var curPoint = this.chart.highlightedPoint, start = (curPoint && curPoint.series) === this ?
|
||||
getPointIndex(curPoint) :
|
||||
0, points = this.points, len = points.length;
|
||||
if (points && len) {
|
||||
for (var i = start; i < len; ++i) {
|
||||
if (!isSkipPoint(points[i])) {
|
||||
return points[i].highlight();
|
||||
}
|
||||
}
|
||||
for (var j = start; j >= 0; --j) {
|
||||
if (!isSkipPoint(points[j])) {
|
||||
return points[j].highlight();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
/**
|
||||
* Highlight next/previous series in chart. Returns false if no adjacent series
|
||||
* in the direction, otherwise returns new highlighted point.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#highlightAdjacentSeries
|
||||
*
|
||||
* @param {boolean} down
|
||||
*
|
||||
* @return {Highcharts.Point|boolean}
|
||||
*/
|
||||
Chart.prototype.highlightAdjacentSeries = function (down) {
|
||||
var chart = this, newSeries, newPoint, adjacentNewPoint, curPoint = chart.highlightedPoint, lastSeries = chart.series && chart.series[chart.series.length - 1], lastPoint = lastSeries && lastSeries.points &&
|
||||
lastSeries.points[lastSeries.points.length - 1];
|
||||
// If no point is highlighted, highlight the first/last point
|
||||
if (!chart.highlightedPoint) {
|
||||
newSeries = down ? (chart.series && chart.series[0]) : lastSeries;
|
||||
newPoint = down ?
|
||||
(newSeries && newSeries.points && newSeries.points[0]) : lastPoint;
|
||||
return newPoint ? newPoint.highlight() : false;
|
||||
}
|
||||
newSeries = chart.series[curPoint.series.index + (down ? -1 : 1)];
|
||||
if (!newSeries) {
|
||||
return false;
|
||||
}
|
||||
// We have a new series in this direction, find the right point
|
||||
// Weigh xDistance as counting much higher than Y distance
|
||||
newPoint = getClosestPoint(curPoint, newSeries, 4);
|
||||
if (!newPoint) {
|
||||
return false;
|
||||
}
|
||||
// New series and point exists, but we might want to skip it
|
||||
if (isSkipSeries(newSeries)) {
|
||||
// Skip the series
|
||||
newPoint.highlight();
|
||||
adjacentNewPoint = chart.highlightAdjacentSeries(down); // Try recurse
|
||||
if (!adjacentNewPoint) {
|
||||
// Recurse failed
|
||||
curPoint.highlight();
|
||||
return false;
|
||||
}
|
||||
// Recurse succeeded
|
||||
return adjacentNewPoint;
|
||||
}
|
||||
// Highlight the new point or any first valid point back or forwards from it
|
||||
newPoint.highlight();
|
||||
return newPoint.series.highlightFirstValidPoint();
|
||||
};
|
||||
/**
|
||||
* Highlight the closest point vertically.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#highlightAdjacentPointVertical
|
||||
*
|
||||
* @param {boolean} down
|
||||
*
|
||||
* @return {Highcharts.Point|boolean}
|
||||
*/
|
||||
Chart.prototype.highlightAdjacentPointVertical = function (down) {
|
||||
var curPoint = this.highlightedPoint, minDistance = Infinity, bestPoint;
|
||||
if (!defined(curPoint.plotX) || !defined(curPoint.plotY)) {
|
||||
return false;
|
||||
}
|
||||
this.series.forEach(function (series) {
|
||||
if (isSkipSeries(series)) {
|
||||
return;
|
||||
}
|
||||
series.points.forEach(function (point) {
|
||||
if (!defined(point.plotY) || !defined(point.plotX) ||
|
||||
point === curPoint) {
|
||||
return;
|
||||
}
|
||||
var yDistance = point.plotY - curPoint.plotY, width = Math.abs(point.plotX - curPoint.plotX), distance = Math.abs(yDistance) * Math.abs(yDistance) +
|
||||
width * width * 4; // Weigh horizontal distance highly
|
||||
// Reverse distance number if axis is reversed
|
||||
if (series.yAxis && series.yAxis.reversed) {
|
||||
yDistance *= -1;
|
||||
}
|
||||
if (yDistance <= 0 && down || yDistance >= 0 && !down || // Chk dir
|
||||
distance < 5 || // Points in same spot => infinite loop
|
||||
isSkipPoint(point)) {
|
||||
return;
|
||||
}
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
bestPoint = point;
|
||||
}
|
||||
});
|
||||
});
|
||||
return bestPoint ? bestPoint.highlight() : false;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
* @return {Highcharts.Point|boolean}
|
||||
*/
|
||||
function highlightFirstValidPointInChart(chart) {
|
||||
var res = false;
|
||||
delete chart.highlightedPoint;
|
||||
res = chart.series.reduce(function (acc, cur) {
|
||||
return acc || cur.highlightFirstValidPoint();
|
||||
}, false);
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
* @return {Highcharts.Point|boolean}
|
||||
*/
|
||||
function highlightLastValidPointInChart(chart) {
|
||||
var numSeries = chart.series.length, i = numSeries, res = false;
|
||||
while (i--) {
|
||||
chart.highlightedPoint = chart.series[i].points[chart.series[i].points.length - 1];
|
||||
// Highlight first valid point in the series will also
|
||||
// look backwards. It always starts from currently
|
||||
// highlighted point.
|
||||
res = chart.series[i].highlightFirstValidPoint();
|
||||
if (res) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
*/
|
||||
function updateChartFocusAfterDrilling(chart) {
|
||||
highlightFirstValidPointInChart(chart);
|
||||
if (chart.focusElement) {
|
||||
chart.focusElement.removeFocusBorder();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.SeriesKeyboardNavigation
|
||||
*/
|
||||
function SeriesKeyboardNavigation(chart, keyCodes) {
|
||||
this.keyCodes = keyCodes;
|
||||
this.chart = chart;
|
||||
}
|
||||
extend(SeriesKeyboardNavigation.prototype, /** @lends Highcharts.SeriesKeyboardNavigation */ {
|
||||
/**
|
||||
* Init the keyboard navigation
|
||||
*/
|
||||
init: function () {
|
||||
var keyboardNavigation = this, chart = this.chart, e = this.eventProvider = new EventProvider();
|
||||
e.addEvent(H.Series, 'destroy', function () {
|
||||
return keyboardNavigation.onSeriesDestroy(this);
|
||||
});
|
||||
e.addEvent(chart, 'afterDrilldown', function () {
|
||||
updateChartFocusAfterDrilling(this);
|
||||
});
|
||||
e.addEvent(chart, 'drilldown', function (e) {
|
||||
var point = e.point, series = point.series;
|
||||
keyboardNavigation.lastDrilledDownPoint = {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
seriesName: series ? series.name : ''
|
||||
};
|
||||
});
|
||||
e.addEvent(chart, 'drillupall', function () {
|
||||
setTimeout(function () {
|
||||
keyboardNavigation.onDrillupAll();
|
||||
}, 10);
|
||||
});
|
||||
},
|
||||
onDrillupAll: function () {
|
||||
// After drillup we want to find the point that was drilled down to and
|
||||
// highlight it.
|
||||
var last = this.lastDrilledDownPoint, chart = this.chart, series = last && getSeriesFromName(chart, last.seriesName), point;
|
||||
if (last && series && defined(last.x) && defined(last.y)) {
|
||||
point = getPointFromXY(series, last.x, last.y);
|
||||
}
|
||||
// Container focus can be lost on drillup due to deleted elements.
|
||||
if (chart.container) {
|
||||
chart.container.focus();
|
||||
}
|
||||
if (point && point.highlight) {
|
||||
point.highlight();
|
||||
}
|
||||
if (chart.focusElement) {
|
||||
chart.focusElement.removeFocusBorder();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @return {Highcharts.KeyboardNavigationHandler}
|
||||
*/
|
||||
getKeyboardNavigationHandler: function () {
|
||||
var keyboardNavigation = this, keys = this.keyCodes, chart = this.chart, inverted = chart.inverted;
|
||||
return new KeyboardNavigationHandler(chart, {
|
||||
keyCodeMap: [
|
||||
[inverted ? [keys.up, keys.down] : [keys.left, keys.right], function (keyCode) {
|
||||
return keyboardNavigation.onKbdSideways(this, keyCode);
|
||||
}],
|
||||
[inverted ? [keys.left, keys.right] : [keys.up, keys.down], function (keyCode) {
|
||||
return keyboardNavigation.onKbdVertical(this, keyCode);
|
||||
}],
|
||||
[[keys.enter, keys.space], function () {
|
||||
if (chart.highlightedPoint) {
|
||||
chart.highlightedPoint.firePointEvent('click');
|
||||
}
|
||||
return this.response.success;
|
||||
}]
|
||||
],
|
||||
init: function (dir) {
|
||||
return keyboardNavigation.onHandlerInit(this, dir);
|
||||
},
|
||||
terminate: function () {
|
||||
return keyboardNavigation.onHandlerTerminate();
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} handler
|
||||
* @param {number} keyCode
|
||||
* @return {number}
|
||||
* response
|
||||
*/
|
||||
onKbdSideways: function (handler, keyCode) {
|
||||
var keys = this.keyCodes, isNext = keyCode === keys.right || keyCode === keys.down;
|
||||
return this.attemptHighlightAdjacentPoint(handler, isNext);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} handler
|
||||
* @param {number} keyCode
|
||||
* @return {number}
|
||||
* response
|
||||
*/
|
||||
onKbdVertical: function (handler, keyCode) {
|
||||
var chart = this.chart, keys = this.keyCodes, isNext = keyCode === keys.down || keyCode === keys.right, navOptions = chart.options.accessibility.keyboardNavigation
|
||||
.seriesNavigation;
|
||||
// Handle serialized mode, act like left/right
|
||||
if (navOptions.mode && navOptions.mode === 'serialize') {
|
||||
return this.attemptHighlightAdjacentPoint(handler, isNext);
|
||||
}
|
||||
// Normal mode, move between series
|
||||
var highlightMethod = (chart.highlightedPoint &&
|
||||
chart.highlightedPoint.series.keyboardMoveVertical) ?
|
||||
'highlightAdjacentPointVertical' :
|
||||
'highlightAdjacentSeries';
|
||||
chart[highlightMethod](isNext);
|
||||
return handler.response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} handler
|
||||
* @param {number} initDirection
|
||||
* @return {number}
|
||||
* response
|
||||
*/
|
||||
onHandlerInit: function (handler, initDirection) {
|
||||
var chart = this.chart;
|
||||
if (initDirection > 0) {
|
||||
highlightFirstValidPointInChart(chart);
|
||||
}
|
||||
else {
|
||||
highlightLastValidPointInChart(chart);
|
||||
}
|
||||
return handler.response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onHandlerTerminate: function () {
|
||||
var _a, _b;
|
||||
var chart = this.chart;
|
||||
var curPoint = chart.highlightedPoint;
|
||||
(_a = chart.tooltip) === null || _a === void 0 ? void 0 : _a.hide(0);
|
||||
(_b = curPoint === null || curPoint === void 0 ? void 0 : curPoint.onMouseOut) === null || _b === void 0 ? void 0 : _b.call(curPoint);
|
||||
delete chart.highlightedPoint;
|
||||
},
|
||||
/**
|
||||
* Function that attempts to highlight next/prev point. Handles wrap around.
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} handler
|
||||
* @param {boolean} directionIsNext
|
||||
* @return {number}
|
||||
* response
|
||||
*/
|
||||
attemptHighlightAdjacentPoint: function (handler, directionIsNext) {
|
||||
var chart = this.chart, wrapAround = chart.options.accessibility.keyboardNavigation
|
||||
.wrapAround, highlightSuccessful = chart.highlightAdjacentPoint(directionIsNext);
|
||||
if (!highlightSuccessful) {
|
||||
if (wrapAround) {
|
||||
return handler.init(directionIsNext ? 1 : -1);
|
||||
}
|
||||
return handler.response[directionIsNext ? 'next' : 'prev'];
|
||||
}
|
||||
return handler.response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
onSeriesDestroy: function (series) {
|
||||
var chart = this.chart, currentHighlightedPointDestroyed = chart.highlightedPoint &&
|
||||
chart.highlightedPoint.series === series;
|
||||
if (currentHighlightedPointDestroyed) {
|
||||
delete chart.highlightedPoint;
|
||||
if (chart.focusElement) {
|
||||
chart.focusElement.removeFocusBorder();
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
destroy: function () {
|
||||
this.eventProvider.removeAddedEvents();
|
||||
}
|
||||
});
|
||||
export default SeriesKeyboardNavigation;
|
||||
@@ -0,0 +1,288 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Accessibility component for chart zoom.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var extend = U.extend, pick = U.pick;
|
||||
import AccessibilityComponent from '../AccessibilityComponent.js';
|
||||
import KeyboardNavigationHandler from '../KeyboardNavigationHandler.js';
|
||||
import ChartUtilities from '../Utils/ChartUtilities.js';
|
||||
var unhideChartElementFromAT = ChartUtilities.unhideChartElementFromAT;
|
||||
import HTMLUtilities from '../Utils/HTMLUtilities.js';
|
||||
var setElAttrs = HTMLUtilities.setElAttrs, removeElement = HTMLUtilities.removeElement;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function chartHasMapZoom(chart) {
|
||||
return !!(chart.mapZoom &&
|
||||
chart.mapNavButtons &&
|
||||
chart.mapNavButtons.length);
|
||||
}
|
||||
/**
|
||||
* Pan along axis in a direction (1 or -1), optionally with a defined
|
||||
* granularity (number of steps it takes to walk across current view)
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Axis#panStep
|
||||
*
|
||||
* @param {number} direction
|
||||
* @param {number} [granularity]
|
||||
*/
|
||||
H.Axis.prototype.panStep = function (direction, granularity) {
|
||||
var gran = granularity || 3, extremes = this.getExtremes(), step = (extremes.max - extremes.min) / gran * direction, newMax = extremes.max + step, newMin = extremes.min + step, size = newMax - newMin;
|
||||
if (direction < 0 && newMin < extremes.dataMin) {
|
||||
newMin = extremes.dataMin;
|
||||
newMax = newMin + size;
|
||||
}
|
||||
else if (direction > 0 && newMax > extremes.dataMax) {
|
||||
newMax = extremes.dataMax;
|
||||
newMin = newMax - size;
|
||||
}
|
||||
this.setExtremes(newMin, newMax);
|
||||
};
|
||||
/**
|
||||
* The ZoomComponent class
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.ZoomComponent
|
||||
*/
|
||||
var ZoomComponent = function () { };
|
||||
ZoomComponent.prototype = new AccessibilityComponent();
|
||||
extend(ZoomComponent.prototype, /** @lends Highcharts.ZoomComponent */ {
|
||||
/**
|
||||
* Initialize the component
|
||||
*/
|
||||
init: function () {
|
||||
var component = this, chart = this.chart;
|
||||
[
|
||||
'afterShowResetZoom', 'afterDrilldown', 'drillupall'
|
||||
].forEach(function (eventType) {
|
||||
component.addEvent(chart, eventType, function () {
|
||||
component.updateProxyOverlays();
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Called when chart is updated
|
||||
*/
|
||||
onChartUpdate: function () {
|
||||
var chart = this.chart, component = this;
|
||||
// Make map zoom buttons accessible
|
||||
if (chart.mapNavButtons) {
|
||||
chart.mapNavButtons.forEach(function (button, i) {
|
||||
unhideChartElementFromAT(chart, button.element);
|
||||
component.setMapNavButtonAttrs(button.element, 'accessibility.zoom.mapZoom' + (i ? 'Out' : 'In'));
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} button
|
||||
* @param {string} labelFormatKey
|
||||
*/
|
||||
setMapNavButtonAttrs: function (button, labelFormatKey) {
|
||||
var chart = this.chart, label = chart.langFormat(labelFormatKey, { chart: chart });
|
||||
setElAttrs(button, {
|
||||
tabindex: -1,
|
||||
role: 'button',
|
||||
'aria-label': label
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Update the proxy overlays on every new render to ensure positions are
|
||||
* correct.
|
||||
*/
|
||||
onChartRender: function () {
|
||||
this.updateProxyOverlays();
|
||||
},
|
||||
/**
|
||||
* Update proxy overlays, recreating the buttons.
|
||||
*/
|
||||
updateProxyOverlays: function () {
|
||||
var chart = this.chart;
|
||||
// Always start with a clean slate
|
||||
removeElement(this.drillUpProxyGroup);
|
||||
removeElement(this.resetZoomProxyGroup);
|
||||
if (chart.resetZoomButton) {
|
||||
this.recreateProxyButtonAndGroup(chart.resetZoomButton, 'resetZoomProxyButton', 'resetZoomProxyGroup', chart.langFormat('accessibility.zoom.resetZoomButton', { chart: chart }));
|
||||
}
|
||||
if (chart.drillUpButton) {
|
||||
this.recreateProxyButtonAndGroup(chart.drillUpButton, 'drillUpProxyButton', 'drillUpProxyGroup', chart.langFormat('accessibility.drillUpButton', {
|
||||
chart: chart,
|
||||
buttonText: chart.getDrilldownBackText()
|
||||
}));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.SVGElement} buttonEl
|
||||
* @param {string} buttonProp
|
||||
* @param {string} groupProp
|
||||
* @param {string} label
|
||||
*/
|
||||
recreateProxyButtonAndGroup: function (buttonEl, buttonProp, groupProp, label) {
|
||||
removeElement(this[groupProp]);
|
||||
this[groupProp] = this.addProxyGroup();
|
||||
this[buttonProp] = this.createProxyButton(buttonEl, this[groupProp], { 'aria-label': label, tabindex: -1 });
|
||||
},
|
||||
/**
|
||||
* Get keyboard navigation handler for map zoom.
|
||||
* @private
|
||||
* @return {Highcharts.KeyboardNavigationHandler} The module object
|
||||
*/
|
||||
getMapZoomNavigation: function () {
|
||||
var keys = this.keyCodes, chart = this.chart, component = this;
|
||||
return new KeyboardNavigationHandler(chart, {
|
||||
keyCodeMap: [
|
||||
[
|
||||
[keys.up, keys.down, keys.left, keys.right],
|
||||
function (keyCode) {
|
||||
return component.onMapKbdArrow(this, keyCode);
|
||||
}
|
||||
],
|
||||
[
|
||||
[keys.tab],
|
||||
function (_keyCode, e) {
|
||||
return component.onMapKbdTab(this, e);
|
||||
}
|
||||
],
|
||||
[
|
||||
[keys.space, keys.enter],
|
||||
function () {
|
||||
return component.onMapKbdClick(this);
|
||||
}
|
||||
]
|
||||
],
|
||||
validate: function () {
|
||||
return chartHasMapZoom(chart);
|
||||
},
|
||||
init: function (direction) {
|
||||
return component.onMapNavInit(direction);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @param {number} keyCode
|
||||
* @return {number} Response code
|
||||
*/
|
||||
onMapKbdArrow: function (keyboardNavigationHandler, keyCode) {
|
||||
var keys = this.keyCodes, panAxis = (keyCode === keys.up || keyCode === keys.down) ?
|
||||
'yAxis' : 'xAxis', stepDirection = (keyCode === keys.left || keyCode === keys.up) ?
|
||||
-1 : 1;
|
||||
this.chart[panAxis][0].panStep(stepDirection);
|
||||
return keyboardNavigationHandler.response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @param {global.KeyboardEvent} event
|
||||
* @return {number} Response code
|
||||
*/
|
||||
onMapKbdTab: function (keyboardNavigationHandler, event) {
|
||||
var button, chart = this.chart, response = keyboardNavigationHandler.response, isBackwards = event.shiftKey, isMoveOutOfRange = isBackwards && !this.focusedMapNavButtonIx ||
|
||||
!isBackwards && this.focusedMapNavButtonIx;
|
||||
// Deselect old
|
||||
chart.mapNavButtons[this.focusedMapNavButtonIx].setState(0);
|
||||
if (isMoveOutOfRange) {
|
||||
chart.mapZoom(); // Reset zoom
|
||||
return response[isBackwards ? 'prev' : 'next'];
|
||||
}
|
||||
// Select other button
|
||||
this.focusedMapNavButtonIx += isBackwards ? -1 : 1;
|
||||
button = chart.mapNavButtons[this.focusedMapNavButtonIx];
|
||||
chart.setFocusToElement(button.box, button.element);
|
||||
button.setState(2);
|
||||
return response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.KeyboardNavigationHandler} keyboardNavigationHandler
|
||||
* @return {number} Response code
|
||||
*/
|
||||
onMapKbdClick: function (keyboardNavigationHandler) {
|
||||
this.fakeClickEvent(this.chart.mapNavButtons[this.focusedMapNavButtonIx]
|
||||
.element);
|
||||
return keyboardNavigationHandler.response.success;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @param {number} direction
|
||||
*/
|
||||
onMapNavInit: function (direction) {
|
||||
var chart = this.chart, zoomIn = chart.mapNavButtons[0], zoomOut = chart.mapNavButtons[1], initialButton = direction > 0 ? zoomIn : zoomOut;
|
||||
chart.setFocusToElement(initialButton.box, initialButton.element);
|
||||
initialButton.setState(2);
|
||||
this.focusedMapNavButtonIx = direction > 0 ? 0 : 1;
|
||||
},
|
||||
/**
|
||||
* Get keyboard navigation handler for a simple chart button. Provide the
|
||||
* button reference for the chart, and a function to call on click.
|
||||
*
|
||||
* @private
|
||||
* @param {string} buttonProp The property on chart referencing the button.
|
||||
* @return {Highcharts.KeyboardNavigationHandler} The module object
|
||||
*/
|
||||
simpleButtonNavigation: function (buttonProp, proxyProp, onClick) {
|
||||
var keys = this.keyCodes, component = this, chart = this.chart;
|
||||
return new KeyboardNavigationHandler(chart, {
|
||||
keyCodeMap: [
|
||||
[
|
||||
[keys.tab, keys.up, keys.down, keys.left, keys.right],
|
||||
function (keyCode, e) {
|
||||
var isBackwards = keyCode === keys.tab && e.shiftKey ||
|
||||
keyCode === keys.left || keyCode === keys.up;
|
||||
// Arrow/tab => just move
|
||||
return this.response[isBackwards ? 'prev' : 'next'];
|
||||
}
|
||||
],
|
||||
[
|
||||
[keys.space, keys.enter],
|
||||
function () {
|
||||
var res = onClick(this, chart);
|
||||
return pick(res, this.response.success);
|
||||
}
|
||||
]
|
||||
],
|
||||
validate: function () {
|
||||
var hasButton = (chart[buttonProp] &&
|
||||
chart[buttonProp].box &&
|
||||
component[proxyProp]);
|
||||
return hasButton;
|
||||
},
|
||||
init: function () {
|
||||
chart.setFocusToElement(chart[buttonProp].box, component[proxyProp]);
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Get keyboard navigation handlers for this component.
|
||||
* @return {Array<Highcharts.KeyboardNavigationHandler>}
|
||||
* List of module objects
|
||||
*/
|
||||
getKeyboardNavigation: function () {
|
||||
return [
|
||||
this.simpleButtonNavigation('resetZoomButton', 'resetZoomProxyButton', function (_handler, chart) {
|
||||
chart.zoomOut();
|
||||
}),
|
||||
this.simpleButtonNavigation('drillUpButton', 'drillUpProxyButton', function (handler, chart) {
|
||||
chart.drillUp();
|
||||
return handler.response.prev;
|
||||
}),
|
||||
this.getMapZoomNavigation()
|
||||
];
|
||||
}
|
||||
});
|
||||
export default ZoomComponent;
|
||||
@@ -0,0 +1,251 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Extend SVG and Chart classes with focus border capabilities.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Core/Globals.js';
|
||||
import SVGElement from '../Core/Renderer/SVG/SVGElement.js';
|
||||
import SVGLabel from '../Core/Renderer/SVG/SVGLabel.js';
|
||||
import U from '../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, extend = U.extend, pick = U.pick;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
// Attributes that trigger a focus border update
|
||||
var svgElementBorderUpdateTriggers = [
|
||||
'x', 'y', 'transform', 'width', 'height', 'r', 'd', 'stroke-width'
|
||||
];
|
||||
/**
|
||||
* Add hook to destroy focus border if SVG element is destroyed, unless
|
||||
* hook already exists.
|
||||
* @private
|
||||
* @param el Element to add destroy hook to
|
||||
*/
|
||||
function addDestroyFocusBorderHook(el) {
|
||||
if (el.focusBorderDestroyHook) {
|
||||
return;
|
||||
}
|
||||
var origDestroy = el.destroy;
|
||||
el.destroy = function () {
|
||||
var _a, _b;
|
||||
(_b = (_a = el.focusBorder) === null || _a === void 0 ? void 0 : _a.destroy) === null || _b === void 0 ? void 0 : _b.call(_a);
|
||||
return origDestroy.apply(el, arguments);
|
||||
};
|
||||
el.focusBorderDestroyHook = origDestroy;
|
||||
}
|
||||
/**
|
||||
* Remove hook from SVG element added by addDestroyFocusBorderHook, if
|
||||
* existing.
|
||||
* @private
|
||||
* @param el Element to remove destroy hook from
|
||||
*/
|
||||
function removeDestroyFocusBorderHook(el) {
|
||||
if (!el.focusBorderDestroyHook) {
|
||||
return;
|
||||
}
|
||||
el.destroy = el.focusBorderDestroyHook;
|
||||
delete el.focusBorderDestroyHook;
|
||||
}
|
||||
/**
|
||||
* Add hooks to update the focus border of an element when the element
|
||||
* size/position is updated, unless already added.
|
||||
* @private
|
||||
* @param el Element to add update hooks to
|
||||
* @param updateParams Parameters to pass through to addFocusBorder when updating.
|
||||
*/
|
||||
function addUpdateFocusBorderHooks(el) {
|
||||
var updateParams = [];
|
||||
for (var _i = 1; _i < arguments.length; _i++) {
|
||||
updateParams[_i - 1] = arguments[_i];
|
||||
}
|
||||
if (el.focusBorderUpdateHooks) {
|
||||
return;
|
||||
}
|
||||
el.focusBorderUpdateHooks = {};
|
||||
svgElementBorderUpdateTriggers.forEach(function (trigger) {
|
||||
var setterKey = trigger + 'Setter';
|
||||
var origSetter = el[setterKey] || el._defaultSetter;
|
||||
el.focusBorderUpdateHooks[setterKey] = origSetter;
|
||||
el[setterKey] = function () {
|
||||
var ret = origSetter.apply(el, arguments);
|
||||
el.addFocusBorder.apply(el, updateParams);
|
||||
return ret;
|
||||
};
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Remove hooks from SVG element added by addUpdateFocusBorderHooks, if
|
||||
* existing.
|
||||
* @private
|
||||
* @param el Element to remove update hooks from
|
||||
*/
|
||||
function removeUpdateFocusBorderHooks(el) {
|
||||
if (!el.focusBorderUpdateHooks) {
|
||||
return;
|
||||
}
|
||||
Object.keys(el.focusBorderUpdateHooks).forEach(function (setterKey) {
|
||||
var origSetter = el.focusBorderUpdateHooks[setterKey];
|
||||
if (origSetter === el._defaultSetter) {
|
||||
delete el[setterKey];
|
||||
}
|
||||
else {
|
||||
el[setterKey] = origSetter;
|
||||
}
|
||||
});
|
||||
delete el.focusBorderUpdateHooks;
|
||||
}
|
||||
/*
|
||||
* Add focus border functionality to SVGElements. Draws a new rect on top of
|
||||
* element around its bounding box. This is used by multiple components.
|
||||
*/
|
||||
extend(SVGElement.prototype, {
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.SVGElement#addFocusBorder
|
||||
*
|
||||
* @param {number} margin
|
||||
*
|
||||
* @param {Highcharts.CSSObject} style
|
||||
*/
|
||||
addFocusBorder: function (margin, style) {
|
||||
// Allow updating by just adding new border
|
||||
if (this.focusBorder) {
|
||||
this.removeFocusBorder();
|
||||
}
|
||||
// Add the border rect
|
||||
var bb = this.getBBox(), pad = pick(margin, 3);
|
||||
bb.x += this.translateX ? this.translateX : 0;
|
||||
bb.y += this.translateY ? this.translateY : 0;
|
||||
var borderPosX = bb.x - pad, borderPosY = bb.y - pad, borderWidth = bb.width + 2 * pad, borderHeight = bb.height + 2 * pad;
|
||||
// For text elements, apply x and y offset, #11397.
|
||||
/**
|
||||
* @private
|
||||
* @function
|
||||
*
|
||||
* @param {Highcharts.SVGElement} text
|
||||
*
|
||||
* @return {TextAnchorCorrectionObject}
|
||||
*/
|
||||
function getTextAnchorCorrection(text) {
|
||||
var posXCorrection = 0, posYCorrection = 0;
|
||||
if (text.attr('text-anchor') === 'middle') {
|
||||
posXCorrection = H.isFirefox && text.rotation ? 0.25 : 0.5;
|
||||
posYCorrection = H.isFirefox && !text.rotation ? 0.75 : 0.5;
|
||||
}
|
||||
else if (!text.rotation) {
|
||||
posYCorrection = 0.75;
|
||||
}
|
||||
else {
|
||||
posXCorrection = 0.25;
|
||||
}
|
||||
return {
|
||||
x: posXCorrection,
|
||||
y: posYCorrection
|
||||
};
|
||||
}
|
||||
var isLabel = this instanceof SVGLabel;
|
||||
if (this.element.nodeName === 'text' || isLabel) {
|
||||
var isRotated = !!this.rotation, correction = !isLabel ? getTextAnchorCorrection(this) :
|
||||
{
|
||||
x: isRotated ? 1 : 0,
|
||||
y: 0
|
||||
};
|
||||
borderPosX = +this.attr('x') - (bb.width * correction.x) - pad;
|
||||
borderPosY = +this.attr('y') - (bb.height * correction.y) - pad;
|
||||
if (isLabel && isRotated) {
|
||||
var temp = borderWidth;
|
||||
borderWidth = borderHeight;
|
||||
borderHeight = temp;
|
||||
borderPosX = +this.attr('x') - (bb.height * correction.x) - pad;
|
||||
borderPosY = +this.attr('y') - (bb.width * correction.y) - pad;
|
||||
}
|
||||
}
|
||||
this.focusBorder = this.renderer.rect(borderPosX, borderPosY, borderWidth, borderHeight, parseInt((style && style.borderRadius || 0).toString(), 10))
|
||||
.addClass('highcharts-focus-border')
|
||||
.attr({
|
||||
zIndex: 99
|
||||
})
|
||||
.add(this.parentGroup);
|
||||
if (!this.renderer.styledMode) {
|
||||
this.focusBorder.attr({
|
||||
stroke: style && style.stroke,
|
||||
'stroke-width': style && style.strokeWidth
|
||||
});
|
||||
}
|
||||
addUpdateFocusBorderHooks(this, margin, style);
|
||||
addDestroyFocusBorderHook(this);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.SVGElement#removeFocusBorder
|
||||
*/
|
||||
removeFocusBorder: function () {
|
||||
removeUpdateFocusBorderHooks(this);
|
||||
removeDestroyFocusBorderHook(this);
|
||||
if (this.focusBorder) {
|
||||
this.focusBorder.destroy();
|
||||
delete this.focusBorder;
|
||||
}
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Redraws the focus border on the currently focused element.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#renderFocusBorder
|
||||
*/
|
||||
H.Chart.prototype.renderFocusBorder = function () {
|
||||
var focusElement = this.focusElement, focusBorderOptions = this.options.accessibility.keyboardNavigation.focusBorder;
|
||||
if (focusElement) {
|
||||
focusElement.removeFocusBorder();
|
||||
if (focusBorderOptions.enabled) {
|
||||
focusElement.addFocusBorder(focusBorderOptions.margin, {
|
||||
stroke: focusBorderOptions.style.color,
|
||||
strokeWidth: focusBorderOptions.style.lineWidth,
|
||||
borderRadius: focusBorderOptions.style.borderRadius
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Set chart's focus to an SVGElement. Calls focus() on it, and draws the focus
|
||||
* border. This is used by multiple components.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Chart#setFocusToElement
|
||||
*
|
||||
* @param {Highcharts.SVGElement} svgElement
|
||||
* Element to draw the border around.
|
||||
*
|
||||
* @param {SVGDOMElement|HTMLDOMElement} [focusElement]
|
||||
* If supplied, it draws the border around svgElement and sets the focus
|
||||
* to focusElement.
|
||||
*/
|
||||
H.Chart.prototype.setFocusToElement = function (svgElement, focusElement) {
|
||||
var focusBorderOptions = this.options.accessibility.keyboardNavigation.focusBorder, browserFocusElement = focusElement || svgElement.element;
|
||||
// Set browser focus if possible
|
||||
if (browserFocusElement &&
|
||||
browserFocusElement.focus) {
|
||||
// If there is no focusin-listener, add one to work around Edge issue
|
||||
// where Narrator is not reading out points despite calling focus().
|
||||
if (!(browserFocusElement.hcEvents &&
|
||||
browserFocusElement.hcEvents.focusin)) {
|
||||
addEvent(browserFocusElement, 'focusin', function () { });
|
||||
}
|
||||
browserFocusElement.focus();
|
||||
// Hide default focus ring
|
||||
if (focusBorderOptions.hideBrowserFocusOutline) {
|
||||
browserFocusElement.style.outline = 'none';
|
||||
}
|
||||
}
|
||||
if (this.focusElement) {
|
||||
this.focusElement.removeFocusBorder();
|
||||
}
|
||||
this.focusElement = svgElement;
|
||||
this.renderFocusBorder();
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Handling for Windows High Contrast Mode.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Core/Globals.js';
|
||||
var isMS = H.isMS, win = H.win, doc = win.document;
|
||||
var whcm = {
|
||||
/**
|
||||
* Detect WHCM in the browser.
|
||||
*
|
||||
* @function Highcharts#isHighContrastModeActive
|
||||
* @private
|
||||
* @return {boolean} Returns true if the browser is in High Contrast mode.
|
||||
*/
|
||||
isHighContrastModeActive: function () {
|
||||
// Use media query on Edge, but not on IE
|
||||
var isEdge = /(Edg)/.test(win.navigator.userAgent);
|
||||
if (win.matchMedia && isEdge) {
|
||||
return win.matchMedia('(-ms-high-contrast: active)').matches;
|
||||
}
|
||||
// Test BG image for IE
|
||||
if (isMS && win.getComputedStyle) {
|
||||
var testDiv = doc.createElement('div');
|
||||
var imageSrc = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
|
||||
testDiv.style.backgroundImage = "url(" + imageSrc + ")"; // #13071
|
||||
doc.body.appendChild(testDiv);
|
||||
var bi = (testDiv.currentStyle ||
|
||||
win.getComputedStyle(testDiv)).backgroundImage;
|
||||
doc.body.removeChild(testDiv);
|
||||
return bi === 'none';
|
||||
}
|
||||
// Not used for other browsers
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* Force high contrast theme for the chart. The default theme is defined in
|
||||
* a separate file.
|
||||
*
|
||||
* @function Highcharts#setHighContrastTheme
|
||||
* @private
|
||||
* @param {Highcharts.AccessibilityChart} chart The chart to set the theme of.
|
||||
* @return {void}
|
||||
*/
|
||||
setHighContrastTheme: function (chart) {
|
||||
// We might want to add additional functionality here in the future for
|
||||
// storing the old state so that we can reset the theme if HC mode is
|
||||
// disabled. For now, the user will have to reload the page.
|
||||
chart.highContrastModeActive = true;
|
||||
// Apply theme to chart
|
||||
var theme = (chart.options.accessibility.highContrastTheme);
|
||||
chart.update(theme, false);
|
||||
// Force series colors (plotOptions is not enough)
|
||||
chart.series.forEach(function (s) {
|
||||
var plotOpts = theme.plotOptions[s.type] || {};
|
||||
s.update({
|
||||
color: plotOpts.color || 'windowText',
|
||||
colors: [plotOpts.color || 'windowText'],
|
||||
borderColor: plotOpts.borderColor || 'window'
|
||||
});
|
||||
// Force point colors if existing
|
||||
s.points.forEach(function (p) {
|
||||
if (p.options && p.options.color) {
|
||||
p.update({
|
||||
color: plotOpts.color || 'windowText',
|
||||
borderColor: plotOpts.borderColor || 'window'
|
||||
}, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
// The redraw for each series and after is required for 3D pie
|
||||
// (workaround)
|
||||
chart.redraw();
|
||||
}
|
||||
};
|
||||
export default whcm;
|
||||
@@ -0,0 +1,211 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Default theme for Windows High Contrast Mode.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
var theme = {
|
||||
chart: {
|
||||
backgroundColor: 'window'
|
||||
},
|
||||
title: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
subtitle: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
colorAxis: {
|
||||
minColor: 'windowText',
|
||||
maxColor: 'windowText',
|
||||
stops: []
|
||||
},
|
||||
colors: ['windowText'],
|
||||
xAxis: {
|
||||
gridLineColor: 'windowText',
|
||||
labels: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
lineColor: 'windowText',
|
||||
minorGridLineColor: 'windowText',
|
||||
tickColor: 'windowText',
|
||||
title: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
gridLineColor: 'windowText',
|
||||
labels: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
lineColor: 'windowText',
|
||||
minorGridLineColor: 'windowText',
|
||||
tickColor: 'windowText',
|
||||
title: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'window',
|
||||
borderColor: 'windowText',
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
plotOptions: {
|
||||
series: {
|
||||
lineColor: 'windowText',
|
||||
fillColor: 'window',
|
||||
borderColor: 'windowText',
|
||||
edgeColor: 'windowText',
|
||||
borderWidth: 1,
|
||||
dataLabels: {
|
||||
connectorColor: 'windowText',
|
||||
color: 'windowText',
|
||||
style: {
|
||||
color: 'windowText',
|
||||
textOutline: 'none'
|
||||
}
|
||||
},
|
||||
marker: {
|
||||
lineColor: 'windowText',
|
||||
fillColor: 'windowText'
|
||||
}
|
||||
},
|
||||
pie: {
|
||||
color: 'window',
|
||||
colors: ['window'],
|
||||
borderColor: 'windowText',
|
||||
borderWidth: 1
|
||||
},
|
||||
boxplot: {
|
||||
fillColor: 'window'
|
||||
},
|
||||
candlestick: {
|
||||
lineColor: 'windowText',
|
||||
fillColor: 'window'
|
||||
},
|
||||
errorbar: {
|
||||
fillColor: 'window'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
backgroundColor: 'window',
|
||||
itemStyle: {
|
||||
color: 'windowText'
|
||||
},
|
||||
itemHoverStyle: {
|
||||
color: 'windowText'
|
||||
},
|
||||
itemHiddenStyle: {
|
||||
color: '#555'
|
||||
},
|
||||
title: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
}
|
||||
},
|
||||
credits: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
labels: {
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
drilldown: {
|
||||
activeAxisLabelStyle: {
|
||||
color: 'windowText'
|
||||
},
|
||||
activeDataLabelStyle: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
navigation: {
|
||||
buttonOptions: {
|
||||
symbolStroke: 'windowText',
|
||||
theme: {
|
||||
fill: 'window'
|
||||
}
|
||||
}
|
||||
},
|
||||
rangeSelector: {
|
||||
buttonTheme: {
|
||||
fill: 'window',
|
||||
stroke: 'windowText',
|
||||
style: {
|
||||
color: 'windowText'
|
||||
},
|
||||
states: {
|
||||
hover: {
|
||||
fill: 'window',
|
||||
stroke: 'windowText',
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
select: {
|
||||
fill: '#444',
|
||||
stroke: 'windowText',
|
||||
style: {
|
||||
color: 'windowText'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
inputBoxBorderColor: 'windowText',
|
||||
inputStyle: {
|
||||
backgroundColor: 'window',
|
||||
color: 'windowText'
|
||||
},
|
||||
labelStyle: {
|
||||
color: 'windowText'
|
||||
}
|
||||
},
|
||||
navigator: {
|
||||
handles: {
|
||||
backgroundColor: 'window',
|
||||
borderColor: 'windowText'
|
||||
},
|
||||
outlineColor: 'windowText',
|
||||
maskFill: 'transparent',
|
||||
series: {
|
||||
color: 'windowText',
|
||||
lineColor: 'windowText'
|
||||
},
|
||||
xAxis: {
|
||||
gridLineColor: 'windowText'
|
||||
}
|
||||
},
|
||||
scrollbar: {
|
||||
barBackgroundColor: '#444',
|
||||
barBorderColor: 'windowText',
|
||||
buttonArrowColor: 'windowText',
|
||||
buttonBackgroundColor: 'window',
|
||||
buttonBorderColor: 'windowText',
|
||||
rifleColor: 'windowText',
|
||||
trackBackgroundColor: 'window',
|
||||
trackBorderColor: 'windowText'
|
||||
}
|
||||
};
|
||||
export default theme;
|
||||
@@ -0,0 +1,359 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Main keyboard navigation handling.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Core/Globals.js';
|
||||
var doc = H.doc, win = H.win;
|
||||
import U from '../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, fireEvent = U.fireEvent;
|
||||
import HTMLUtilities from './Utils/HTMLUtilities.js';
|
||||
var getElement = HTMLUtilities.getElement;
|
||||
import EventProvider from './Utils/EventProvider.js';
|
||||
/* eslint-disable valid-jsdoc */
|
||||
// Add event listener to document to detect ESC key press and dismiss
|
||||
// hover/popup content.
|
||||
addEvent(doc, 'keydown', function (e) {
|
||||
var keycode = e.which || e.keyCode;
|
||||
var esc = 27;
|
||||
if (keycode === esc && H.charts) {
|
||||
H.charts.forEach(function (chart) {
|
||||
if (chart && chart.dismissPopupContent) {
|
||||
chart.dismissPopupContent();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Dismiss popup content in chart, including export menu and tooltip.
|
||||
*/
|
||||
H.Chart.prototype.dismissPopupContent = function () {
|
||||
var chart = this;
|
||||
fireEvent(this, 'dismissPopupContent', {}, function () {
|
||||
if (chart.tooltip) {
|
||||
chart.tooltip.hide(0);
|
||||
}
|
||||
chart.hideExportMenu();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* The KeyboardNavigation class, containing the overall keyboard navigation
|
||||
* logic for the chart.
|
||||
*
|
||||
* @requires module:modules/accessibility
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @param {Highcharts.Chart} chart
|
||||
* Chart object
|
||||
* @param {object} components
|
||||
* Map of component names to AccessibilityComponent objects.
|
||||
* @name Highcharts.KeyboardNavigation
|
||||
*/
|
||||
function KeyboardNavigation(chart, components) {
|
||||
this.init(chart, components);
|
||||
}
|
||||
KeyboardNavigation.prototype = {
|
||||
/**
|
||||
* Initialize the class
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
* Chart object
|
||||
* @param {object} components
|
||||
* Map of component names to AccessibilityComponent objects.
|
||||
*/
|
||||
init: function (chart, components) {
|
||||
var _this = this;
|
||||
var ep = this.eventProvider = new EventProvider();
|
||||
this.chart = chart;
|
||||
this.components = components;
|
||||
this.modules = [];
|
||||
this.currentModuleIx = 0;
|
||||
// Run an update to get all modules
|
||||
this.update();
|
||||
ep.addEvent(this.tabindexContainer, 'keydown', function (e) { return _this.onKeydown(e); });
|
||||
ep.addEvent(this.tabindexContainer, 'focus', function (e) { return _this.onFocus(e); });
|
||||
ep.addEvent(doc, 'mouseup', function () { return _this.onMouseUp(); });
|
||||
ep.addEvent(chart.renderTo, 'mousedown', function () {
|
||||
_this.isClickingChart = true;
|
||||
});
|
||||
ep.addEvent(chart.renderTo, 'mouseover', function () {
|
||||
_this.pointerIsOverChart = true;
|
||||
});
|
||||
ep.addEvent(chart.renderTo, 'mouseout', function () {
|
||||
_this.pointerIsOverChart = false;
|
||||
});
|
||||
// Init first module
|
||||
if (this.modules.length) {
|
||||
this.modules[0].init(1);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update the modules for the keyboard navigation.
|
||||
* @param {Array<string>} [order]
|
||||
* Array specifying the tab order of the components.
|
||||
*/
|
||||
update: function (order) {
|
||||
var a11yOptions = this.chart.options.accessibility, keyboardOptions = a11yOptions && a11yOptions.keyboardNavigation, components = this.components;
|
||||
this.updateContainerTabindex();
|
||||
if (keyboardOptions &&
|
||||
keyboardOptions.enabled &&
|
||||
order &&
|
||||
order.length) {
|
||||
// We (still) have keyboard navigation. Update module list
|
||||
this.modules = order.reduce(function (modules, componentName) {
|
||||
var navModules = components[componentName].getKeyboardNavigation();
|
||||
return modules.concat(navModules);
|
||||
}, []);
|
||||
this.updateExitAnchor();
|
||||
}
|
||||
else {
|
||||
this.modules = [];
|
||||
this.currentModuleIx = 0;
|
||||
this.removeExitAnchor();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Function to run on container focus
|
||||
* @private
|
||||
* @param {global.FocusEvent} e Browser focus event.
|
||||
*/
|
||||
onFocus: function (e) {
|
||||
var _a;
|
||||
var chart = this.chart;
|
||||
var focusComesFromChart = (e.relatedTarget &&
|
||||
chart.container.contains(e.relatedTarget));
|
||||
// Init keyboard nav if tabbing into chart
|
||||
if (!this.isClickingChart && !focusComesFromChart) {
|
||||
(_a = this.modules[0]) === null || _a === void 0 ? void 0 : _a.init(1);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Reset chart navigation state if we click outside the chart and it's
|
||||
* not already reset.
|
||||
* @private
|
||||
*/
|
||||
onMouseUp: function () {
|
||||
delete this.isClickingChart;
|
||||
if (!this.keyboardReset && !this.pointerIsOverChart) {
|
||||
var chart = this.chart, curMod = this.modules &&
|
||||
this.modules[this.currentModuleIx || 0];
|
||||
if (curMod && curMod.terminate) {
|
||||
curMod.terminate();
|
||||
}
|
||||
if (chart.focusElement) {
|
||||
chart.focusElement.removeFocusBorder();
|
||||
}
|
||||
this.currentModuleIx = 0;
|
||||
this.keyboardReset = true;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Function to run on keydown
|
||||
* @private
|
||||
* @param {global.KeyboardEvent} ev Browser keydown event.
|
||||
*/
|
||||
onKeydown: function (ev) {
|
||||
var e = ev || win.event, preventDefault, curNavModule = this.modules && this.modules.length &&
|
||||
this.modules[this.currentModuleIx];
|
||||
// Used for resetting nav state when clicking outside chart
|
||||
this.keyboardReset = false;
|
||||
// If there is a nav module for the current index, run it.
|
||||
// Otherwise, we are outside of the chart in some direction.
|
||||
if (curNavModule) {
|
||||
var response = curNavModule.run(e);
|
||||
if (response === curNavModule.response.success) {
|
||||
preventDefault = true;
|
||||
}
|
||||
else if (response === curNavModule.response.prev) {
|
||||
preventDefault = this.prev();
|
||||
}
|
||||
else if (response === curNavModule.response.next) {
|
||||
preventDefault = this.next();
|
||||
}
|
||||
if (preventDefault) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Go to previous module.
|
||||
* @private
|
||||
*/
|
||||
prev: function () {
|
||||
return this.move(-1);
|
||||
},
|
||||
/**
|
||||
* Go to next module.
|
||||
* @private
|
||||
*/
|
||||
next: function () {
|
||||
return this.move(1);
|
||||
},
|
||||
/**
|
||||
* Move to prev/next module.
|
||||
* @private
|
||||
* @param {number} direction
|
||||
* Direction to move. +1 for next, -1 for prev.
|
||||
* @return {boolean}
|
||||
* True if there was a valid module in direction.
|
||||
*/
|
||||
move: function (direction) {
|
||||
var curModule = this.modules && this.modules[this.currentModuleIx];
|
||||
if (curModule && curModule.terminate) {
|
||||
curModule.terminate(direction);
|
||||
}
|
||||
// Remove existing focus border if any
|
||||
if (this.chart.focusElement) {
|
||||
this.chart.focusElement.removeFocusBorder();
|
||||
}
|
||||
this.currentModuleIx += direction;
|
||||
var newModule = this.modules && this.modules[this.currentModuleIx];
|
||||
if (newModule) {
|
||||
if (newModule.validate && !newModule.validate()) {
|
||||
return this.move(direction); // Invalid module, recurse
|
||||
}
|
||||
if (newModule.init) {
|
||||
newModule.init(direction); // Valid module, init it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// No module
|
||||
this.currentModuleIx = 0; // Reset counter
|
||||
// Set focus to chart or exit anchor depending on direction
|
||||
if (direction > 0) {
|
||||
this.exiting = true;
|
||||
this.exitAnchor.focus();
|
||||
}
|
||||
else {
|
||||
this.tabindexContainer.focus();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* We use an exit anchor to move focus out of chart whenever we want, by
|
||||
* setting focus to this div and not preventing the default tab action. We
|
||||
* also use this when users come back into the chart by tabbing back, in
|
||||
* order to navigate from the end of the chart.
|
||||
* @private
|
||||
*/
|
||||
updateExitAnchor: function () {
|
||||
var endMarkerId = 'highcharts-end-of-chart-marker-' + this.chart.index, endMarker = getElement(endMarkerId);
|
||||
this.removeExitAnchor();
|
||||
if (endMarker) {
|
||||
this.makeElementAnExitAnchor(endMarker);
|
||||
this.exitAnchor = endMarker;
|
||||
}
|
||||
else {
|
||||
this.createExitAnchor();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Chart container should have tabindex if navigation is enabled.
|
||||
* @private
|
||||
*/
|
||||
updateContainerTabindex: function () {
|
||||
var a11yOptions = this.chart.options.accessibility, keyboardOptions = a11yOptions && a11yOptions.keyboardNavigation, shouldHaveTabindex = !(keyboardOptions && keyboardOptions.enabled === false), chart = this.chart, container = chart.container;
|
||||
var tabindexContainer;
|
||||
if (chart.renderTo.hasAttribute('tabindex')) {
|
||||
container.removeAttribute('tabindex');
|
||||
tabindexContainer = chart.renderTo;
|
||||
}
|
||||
else {
|
||||
tabindexContainer = container;
|
||||
}
|
||||
this.tabindexContainer = tabindexContainer;
|
||||
var curTabindex = tabindexContainer.getAttribute('tabindex');
|
||||
if (shouldHaveTabindex && !curTabindex) {
|
||||
tabindexContainer.setAttribute('tabindex', '0');
|
||||
}
|
||||
else if (!shouldHaveTabindex) {
|
||||
chart.container.removeAttribute('tabindex');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
makeElementAnExitAnchor: function (el) {
|
||||
var chartTabindex = this.tabindexContainer.getAttribute('tabindex') || 0;
|
||||
el.setAttribute('class', 'highcharts-exit-anchor');
|
||||
el.setAttribute('tabindex', chartTabindex);
|
||||
el.setAttribute('aria-hidden', false);
|
||||
// Handle focus
|
||||
this.addExitAnchorEventsToEl(el);
|
||||
},
|
||||
/**
|
||||
* Add new exit anchor to the chart.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
createExitAnchor: function () {
|
||||
var chart = this.chart, exitAnchor = this.exitAnchor = doc.createElement('div');
|
||||
chart.renderTo.appendChild(exitAnchor);
|
||||
this.makeElementAnExitAnchor(exitAnchor);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
removeExitAnchor: function () {
|
||||
if (this.exitAnchor && this.exitAnchor.parentNode) {
|
||||
this.exitAnchor.parentNode
|
||||
.removeChild(this.exitAnchor);
|
||||
delete this.exitAnchor;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
addExitAnchorEventsToEl: function (element) {
|
||||
var chart = this.chart, keyboardNavigation = this;
|
||||
this.eventProvider.addEvent(element, 'focus', function (ev) {
|
||||
var e = ev || win.event, curModule, focusComesFromChart = (e.relatedTarget &&
|
||||
chart.container.contains(e.relatedTarget)), comingInBackwards = !(focusComesFromChart || keyboardNavigation.exiting);
|
||||
if (comingInBackwards) {
|
||||
keyboardNavigation.tabindexContainer.focus();
|
||||
e.preventDefault();
|
||||
// Move to last valid keyboard nav module
|
||||
// Note the we don't run it, just set the index
|
||||
if (keyboardNavigation.modules &&
|
||||
keyboardNavigation.modules.length) {
|
||||
keyboardNavigation.currentModuleIx =
|
||||
keyboardNavigation.modules.length - 1;
|
||||
curModule = keyboardNavigation.modules[keyboardNavigation.currentModuleIx];
|
||||
// Validate the module
|
||||
if (curModule &&
|
||||
curModule.validate && !curModule.validate()) {
|
||||
// Invalid. Try moving backwards to find next valid.
|
||||
keyboardNavigation.prev();
|
||||
}
|
||||
else if (curModule) {
|
||||
// We have a valid module, init it
|
||||
curModule.init(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Don't skip the next focus, we only skip once.
|
||||
keyboardNavigation.exiting = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Remove all traces of keyboard navigation.
|
||||
* @private
|
||||
*/
|
||||
destroy: function () {
|
||||
this.removeExitAnchor();
|
||||
this.eventProvider.removeAddedEvents();
|
||||
this.chart.container.removeAttribute('tabindex');
|
||||
}
|
||||
};
|
||||
export default KeyboardNavigation;
|
||||
@@ -0,0 +1,99 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Keyboard navigation handler base class definition
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import U from '../Core/Utilities.js';
|
||||
var find = U.find;
|
||||
/**
|
||||
* Options for the keyboard navigation handler.
|
||||
*
|
||||
* @interface Highcharts.KeyboardNavigationHandlerOptionsObject
|
||||
*/ /**
|
||||
* An array containing pairs of an array of keycodes, mapped to a handler
|
||||
* function. When the keycode is received, the handler is called with the
|
||||
* keycode as parameter.
|
||||
* @name Highcharts.KeyboardNavigationHandlerOptionsObject#keyCodeMap
|
||||
* @type {Array<Array<Array<number>, Function>>}
|
||||
*/ /**
|
||||
* Function to run on initialization of module.
|
||||
* @name Highcharts.KeyboardNavigationHandlerOptionsObject#init
|
||||
* @type {Function}
|
||||
*/ /**
|
||||
* Function to run before moving to next/prev module. Receives moving direction
|
||||
* as parameter: +1 for next, -1 for previous.
|
||||
* @name Highcharts.KeyboardNavigationHandlerOptionsObject#terminate
|
||||
* @type {Function|undefined}
|
||||
*/ /**
|
||||
* Function to run to validate module. Should return false if module should not
|
||||
* run, true otherwise. Receives chart as parameter.
|
||||
* @name Highcharts.KeyboardNavigationHandlerOptionsObject#validate
|
||||
* @type {Function|undefined}
|
||||
*/
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Define a keyboard navigation handler for use with a
|
||||
* Highcharts.AccessibilityComponent instance. This functions as an abstraction
|
||||
* layer for keyboard navigation, and defines a map of keyCodes to handler
|
||||
* functions.
|
||||
*
|
||||
* @requires module:modules/accessibility
|
||||
*
|
||||
* @sample highcharts/accessibility/custom-component
|
||||
* Custom accessibility component
|
||||
*
|
||||
* @class
|
||||
* @name Highcharts.KeyboardNavigationHandler
|
||||
*
|
||||
* @param {Highcharts.Chart} chart
|
||||
* The chart this module should act on.
|
||||
*
|
||||
* @param {Highcharts.KeyboardNavigationHandlerOptionsObject} options
|
||||
* Options for the keyboard navigation handler.
|
||||
*/
|
||||
function KeyboardNavigationHandler(chart, options) {
|
||||
this.chart = chart;
|
||||
this.keyCodeMap = options.keyCodeMap || [];
|
||||
this.validate = options.validate;
|
||||
this.init = options.init;
|
||||
this.terminate = options.terminate;
|
||||
// Response enum
|
||||
this.response = {
|
||||
success: 1,
|
||||
prev: 2,
|
||||
next: 3,
|
||||
noHandler: 4,
|
||||
fail: 5 // Handler failed
|
||||
};
|
||||
}
|
||||
KeyboardNavigationHandler.prototype = {
|
||||
/**
|
||||
* Find handler function(s) for key code in the keyCodeMap and run it.
|
||||
*
|
||||
* @function KeyboardNavigationHandler#run
|
||||
* @param {global.KeyboardEvent} e
|
||||
* @return {number} Returns a response code indicating whether the run was
|
||||
* a success/fail/unhandled, or if we should move to next/prev module.
|
||||
*/
|
||||
run: function (e) {
|
||||
var keyCode = e.which || e.keyCode, response = this.response.noHandler, handlerCodeSet = find(this.keyCodeMap, function (codeSet) {
|
||||
return codeSet[0].indexOf(keyCode) > -1;
|
||||
});
|
||||
if (handlerCodeSet) {
|
||||
response = handlerCodeSet[1].call(this, keyCode, e);
|
||||
}
|
||||
else if (keyCode === 9) {
|
||||
// Default tab handler, move to next/prev module
|
||||
response = this.response[e.shiftKey ? 'prev' : 'next'];
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
export default KeyboardNavigationHandler;
|
||||
@@ -0,0 +1,233 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Default options for accessibility.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
/* eslint-disable max-len */
|
||||
/*
|
||||
* List of deprecated options:
|
||||
*
|
||||
* chart.description -> accessibility.description
|
||||
* chart.typeDescription -> accessibility.typeDescription
|
||||
* series.description -> series.accessibility.description
|
||||
* series.exposeElementToA11y -> series.accessibility.exposeAsGroupOnly
|
||||
* series.pointDescriptionFormatter ->
|
||||
* series.accessibility.pointDescriptionFormatter
|
||||
* series.skipKeyboardNavigation ->
|
||||
* series.accessibility.keyboardNavigation.enabled
|
||||
* point.description -> point.accessibility.description !!!! WARNING: No longer deprecated and handled, removed for HC8.
|
||||
* axis.description -> axis.accessibility.description
|
||||
*
|
||||
* accessibility.pointDateFormat -> accessibility.point.dateFormat
|
||||
* accessibility.addTableShortcut -> Handled by screenReaderSection.beforeChartFormat
|
||||
* accessibility.pointDateFormatter -> accessibility.point.dateFormatter
|
||||
* accessibility.pointDescriptionFormatter -> accessibility.point.descriptionFormatter
|
||||
* accessibility.pointDescriptionThreshold -> accessibility.series.pointDescriptionEnabledThreshold
|
||||
* accessibility.pointNavigationThreshold -> accessibility.keyboardNavigation.seriesNavigation.pointNavigationEnabledThreshold
|
||||
* accessibility.pointValueDecimals -> accessibility.point.valueDecimals
|
||||
* accessibility.pointValuePrefix -> accessibility.point.valuePrefix
|
||||
* accessibility.pointValueSuffix -> accessibility.point.valueSuffix
|
||||
* accessibility.screenReaderSectionFormatter -> accessibility.screenReaderSection.beforeChartFormatter
|
||||
* accessibility.describeSingleSeries -> accessibility.series.describeSingleSeries
|
||||
* accessibility.seriesDescriptionFormatter -> accessibility.series.descriptionFormatter
|
||||
* accessibility.onTableAnchorClick -> accessibility.screenReaderSection.onViewDataTableClick
|
||||
* accessibility.axisRangeDateFormat -> accessibility.screenReaderSection.axisRangeDateFormat
|
||||
* accessibility.keyboardNavigation.skipNullPoints -> accessibility.keyboardNavigation.seriesNavigation.skipNullPoints
|
||||
* accessibility.keyboardNavigation.mode -> accessibility.keyboardNavigation.seriesNavigation.mode
|
||||
*
|
||||
* lang.accessibility.chartHeading -> no longer used, remove
|
||||
* lang.accessibility.legendItem -> lang.accessibility.legend.legendItem
|
||||
* lang.accessibility.legendLabel -> lang.accessibility.legend.legendLabel
|
||||
* lang.accessibility.mapZoomIn -> lang.accessibility.zoom.mapZoomIn
|
||||
* lang.accessibility.mapZoomOut -> lang.accessibility.zoom.mapZoomOut
|
||||
* lang.accessibility.resetZoomButton -> lang.accessibility.zoom.resetZoomButton
|
||||
* lang.accessibility.screenReaderRegionLabel -> lang.accessibility.screenReaderSection.beforeRegionLabel
|
||||
* lang.accessibility.rangeSelectorButton -> lang.accessibility.rangeSelector.buttonText
|
||||
* lang.accessibility.rangeSelectorMaxInput -> lang.accessibility.rangeSelector.maxInputLabel
|
||||
* lang.accessibility.rangeSelectorMinInput -> lang.accessibility.rangeSelector.minInputLabel
|
||||
* lang.accessibility.svgContainerEnd -> lang.accessibility.screenReaderSection.endOfChartMarker
|
||||
* lang.accessibility.viewAsDataTable -> lang.accessibility.table.viewAsDataTableButtonText
|
||||
* lang.accessibility.tableSummary -> lang.accessibility.table.tableSummary
|
||||
*
|
||||
*/
|
||||
/* eslint-enable max-len */
|
||||
'use strict';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var error = U.error, pick = U.pick;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Set a new option on a root prop, where the option is defined as an array of
|
||||
* suboptions.
|
||||
* @private
|
||||
* @param root
|
||||
* @param {Array<string>} optionAsArray
|
||||
* @param {*} val
|
||||
* @return {void}
|
||||
*/
|
||||
function traverseSetOption(root, optionAsArray, val) {
|
||||
var opt = root, prop, i = 0;
|
||||
for (; i < optionAsArray.length - 1; ++i) {
|
||||
prop = optionAsArray[i];
|
||||
opt = opt[prop] = pick(opt[prop], {});
|
||||
}
|
||||
opt[optionAsArray[optionAsArray.length - 1]] = val;
|
||||
}
|
||||
/**
|
||||
* If we have a clear root option node for old and new options and a mapping
|
||||
* between, we can use this generic function for the copy and warn logic.
|
||||
*/
|
||||
function deprecateFromOptionsMap(chart, rootOldAsArray, rootNewAsArray, mapToNewOptions) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getChildProp(root, propAsArray) {
|
||||
return propAsArray.reduce(function (acc, cur) {
|
||||
return acc[cur];
|
||||
}, root);
|
||||
}
|
||||
var rootOld = getChildProp(chart.options, rootOldAsArray), rootNew = getChildProp(chart.options, rootNewAsArray);
|
||||
Object.keys(mapToNewOptions).forEach(function (oldOptionKey) {
|
||||
var _a;
|
||||
var val = rootOld[oldOptionKey];
|
||||
if (typeof val !== 'undefined') {
|
||||
traverseSetOption(rootNew, mapToNewOptions[oldOptionKey], val);
|
||||
error(32, false, chart, (_a = {},
|
||||
_a[rootOldAsArray.join('.') + "." + oldOptionKey] = rootNewAsArray.join('.') + "." + mapToNewOptions[oldOptionKey].join('.'),
|
||||
_a));
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function copyDeprecatedChartOptions(chart) {
|
||||
var chartOptions = chart.options.chart || {}, a11yOptions = chart.options.accessibility || {};
|
||||
['description', 'typeDescription'].forEach(function (prop) {
|
||||
var _a;
|
||||
if (chartOptions[prop]) {
|
||||
a11yOptions[prop] = chartOptions[prop];
|
||||
error(32, false, chart, (_a = {}, _a["chart." + prop] = "use accessibility." + prop, _a));
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function copyDeprecatedAxisOptions(chart) {
|
||||
chart.axes.forEach(function (axis) {
|
||||
var opts = axis.options;
|
||||
if (opts && opts.description) {
|
||||
opts.accessibility = opts.accessibility || {};
|
||||
opts.accessibility.description = opts.description;
|
||||
error(32, false, chart, { 'axis.description': 'use axis.accessibility.description' });
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function copyDeprecatedSeriesOptions(chart) {
|
||||
// Map of deprecated series options. New options are defined as
|
||||
// arrays of paths under series.options.
|
||||
var oldToNewSeriesOptions = {
|
||||
description: ['accessibility', 'description'],
|
||||
exposeElementToA11y: ['accessibility', 'exposeAsGroupOnly'],
|
||||
pointDescriptionFormatter: [
|
||||
'accessibility', 'pointDescriptionFormatter'
|
||||
],
|
||||
skipKeyboardNavigation: [
|
||||
'accessibility', 'keyboardNavigation', 'enabled'
|
||||
]
|
||||
};
|
||||
chart.series.forEach(function (series) {
|
||||
// Handle series wide options
|
||||
Object.keys(oldToNewSeriesOptions).forEach(function (oldOption) {
|
||||
var _a;
|
||||
var optionVal = series.options[oldOption];
|
||||
if (typeof optionVal !== 'undefined') {
|
||||
// Set the new option
|
||||
traverseSetOption(series.options, oldToNewSeriesOptions[oldOption],
|
||||
// Note that skipKeyboardNavigation has inverted option
|
||||
// value, since we set enabled rather than disabled
|
||||
oldOption === 'skipKeyboardNavigation' ?
|
||||
!optionVal : optionVal);
|
||||
error(32, false, chart, (_a = {}, _a["series." + oldOption] = "series." + oldToNewSeriesOptions[oldOption].join('.'), _a));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function copyDeprecatedTopLevelAccessibilityOptions(chart) {
|
||||
deprecateFromOptionsMap(chart, ['accessibility'], ['accessibility'], {
|
||||
pointDateFormat: ['point', 'dateFormat'],
|
||||
pointDateFormatter: ['point', 'dateFormatter'],
|
||||
pointDescriptionFormatter: ['point', 'descriptionFormatter'],
|
||||
pointDescriptionThreshold: ['series',
|
||||
'pointDescriptionEnabledThreshold'],
|
||||
pointNavigationThreshold: ['keyboardNavigation', 'seriesNavigation',
|
||||
'pointNavigationEnabledThreshold'],
|
||||
pointValueDecimals: ['point', 'valueDecimals'],
|
||||
pointValuePrefix: ['point', 'valuePrefix'],
|
||||
pointValueSuffix: ['point', 'valueSuffix'],
|
||||
screenReaderSectionFormatter: ['screenReaderSection',
|
||||
'beforeChartFormatter'],
|
||||
describeSingleSeries: ['series', 'describeSingleSeries'],
|
||||
seriesDescriptionFormatter: ['series', 'descriptionFormatter'],
|
||||
onTableAnchorClick: ['screenReaderSection', 'onViewDataTableClick'],
|
||||
axisRangeDateFormat: ['screenReaderSection', 'axisRangeDateFormat']
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function copyDeprecatedKeyboardNavigationOptions(chart) {
|
||||
deprecateFromOptionsMap(chart, ['accessibility', 'keyboardNavigation'], ['accessibility', 'keyboardNavigation', 'seriesNavigation'], {
|
||||
skipNullPoints: ['skipNullPoints'],
|
||||
mode: ['mode']
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function copyDeprecatedLangOptions(chart) {
|
||||
deprecateFromOptionsMap(chart, ['lang', 'accessibility'], ['lang', 'accessibility'], {
|
||||
legendItem: ['legend', 'legendItem'],
|
||||
legendLabel: ['legend', 'legendLabel'],
|
||||
mapZoomIn: ['zoom', 'mapZoomIn'],
|
||||
mapZoomOut: ['zoom', 'mapZoomOut'],
|
||||
resetZoomButton: ['zoom', 'resetZoomButton'],
|
||||
screenReaderRegionLabel: ['screenReaderSection',
|
||||
'beforeRegionLabel'],
|
||||
rangeSelectorButton: ['rangeSelector', 'buttonText'],
|
||||
rangeSelectorMaxInput: ['rangeSelector', 'maxInputLabel'],
|
||||
rangeSelectorMinInput: ['rangeSelector', 'minInputLabel'],
|
||||
svgContainerEnd: ['screenReaderSection', 'endOfChartMarker'],
|
||||
viewAsDataTable: ['table', 'viewAsDataTableButtonText'],
|
||||
tableSummary: ['table', 'tableSummary']
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Copy options that are deprecated over to new options. Logs warnings to
|
||||
* console if deprecated options are used.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function copyDeprecatedOptions(chart) {
|
||||
copyDeprecatedChartOptions(chart);
|
||||
copyDeprecatedAxisOptions(chart);
|
||||
if (chart.series) {
|
||||
copyDeprecatedSeriesOptions(chart);
|
||||
}
|
||||
copyDeprecatedTopLevelAccessibilityOptions(chart);
|
||||
copyDeprecatedKeyboardNavigationOptions(chart);
|
||||
copyDeprecatedLangOptions(chart);
|
||||
}
|
||||
export default copyDeprecatedOptions;
|
||||
@@ -0,0 +1,338 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Default lang/i18n options for accessibility.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
var langOptions = {
|
||||
/**
|
||||
* Configure the accessibility strings in the chart. Requires the
|
||||
* [accessibility module](https://code.highcharts.com/modules/accessibility.js)
|
||||
* to be loaded. For a description of the module and information on its
|
||||
* features, see
|
||||
* [Highcharts Accessibility](https://www.highcharts.com/docs/chart-concepts/accessibility).
|
||||
*
|
||||
* For more dynamic control over the accessibility functionality, see
|
||||
* [accessibility.pointDescriptionFormatter](#accessibility.pointDescriptionFormatter),
|
||||
* [accessibility.seriesDescriptionFormatter](#accessibility.seriesDescriptionFormatter),
|
||||
* and
|
||||
* [accessibility.screenReaderSectionFormatter](#accessibility.screenReaderSectionFormatter).
|
||||
*
|
||||
* @since 6.0.6
|
||||
* @optionparent lang.accessibility
|
||||
*/
|
||||
accessibility: {
|
||||
defaultChartTitle: 'Chart',
|
||||
chartContainerLabel: '{title}. Highcharts interactive chart.',
|
||||
svgContainerLabel: 'Interactive chart',
|
||||
drillUpButton: '{buttonText}',
|
||||
credits: 'Chart credits: {creditsStr}',
|
||||
/**
|
||||
* Thousands separator to use when formatting numbers for screen
|
||||
* readers. Note that many screen readers will not handle space as a
|
||||
* thousands separator, and will consider "11 700" as two numbers.
|
||||
*
|
||||
* Set to `null` to use the separator defined in
|
||||
* [lang.thousandsSep](lang.thousandsSep).
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
thousandsSep: ',',
|
||||
/**
|
||||
* Title element text for the chart SVG element. Leave this
|
||||
* empty to disable adding the title element. Browsers will display
|
||||
* this content when hovering over elements in the chart. Assistive
|
||||
* technology may use this element to label the chart.
|
||||
*
|
||||
* @since 6.0.8
|
||||
*/
|
||||
svgContainerTitle: '',
|
||||
/**
|
||||
* Set a label on the container wrapping the SVG.
|
||||
*
|
||||
* @see [chartContainerLabel](#lang.accessibility.chartContainerLabel)
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
graphicContainerLabel: '',
|
||||
/**
|
||||
* Language options for the screen reader information sections added
|
||||
* before and after the charts.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
screenReaderSection: {
|
||||
beforeRegionLabel: 'Chart screen reader information.',
|
||||
afterRegionLabel: '',
|
||||
/**
|
||||
* Language options for annotation descriptions.
|
||||
*
|
||||
* @since 8.0.1
|
||||
*/
|
||||
annotations: {
|
||||
heading: 'Chart annotations summary',
|
||||
descriptionSinglePoint: '{annotationText}. Related to {annotationPoint}',
|
||||
descriptionMultiplePoints: '{annotationText}. Related to {annotationPoint}' +
|
||||
'{ Also related to, #each(additionalAnnotationPoints)}',
|
||||
descriptionNoPoints: '{annotationText}'
|
||||
},
|
||||
/**
|
||||
* Label for the end of the chart. Announced by screen readers.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
endOfChartMarker: 'End of interactive chart.'
|
||||
},
|
||||
/**
|
||||
* Language options for sonification.
|
||||
*
|
||||
* @since 8.0.1
|
||||
*/
|
||||
sonification: {
|
||||
playAsSoundButtonText: 'Play as sound, {chartTitle}',
|
||||
playAsSoundClickAnnouncement: 'Play'
|
||||
},
|
||||
/**
|
||||
* Language options for accessibility of the legend.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
legend: {
|
||||
legendLabel: 'Toggle series visibility',
|
||||
legendItem: 'Hide {itemName}'
|
||||
},
|
||||
/**
|
||||
* Chart and map zoom accessibility language options.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
zoom: {
|
||||
mapZoomIn: 'Zoom chart',
|
||||
mapZoomOut: 'Zoom out chart',
|
||||
resetZoomButton: 'Reset zoom'
|
||||
},
|
||||
/**
|
||||
* Range selector language options for accessibility.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
rangeSelector: {
|
||||
minInputLabel: 'Select start date.',
|
||||
maxInputLabel: 'Select end date.',
|
||||
buttonText: 'Select range {buttonText}'
|
||||
},
|
||||
/**
|
||||
* Accessibility language options for the data table.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
table: {
|
||||
viewAsDataTableButtonText: 'View as data table, {chartTitle}',
|
||||
tableSummary: 'Table representation of chart.'
|
||||
},
|
||||
/**
|
||||
* Default announcement for new data in charts. If addPoint or
|
||||
* addSeries is used, and only one series/point is added, the
|
||||
* `newPointAnnounce` and `newSeriesAnnounce` strings are used.
|
||||
* The `...Single` versions will be used if there is only one chart
|
||||
* on the page, and the `...Multiple` versions will be used if there
|
||||
* are multiple charts on the page. For all other new data events,
|
||||
* the `newDataAnnounce` string will be used.
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
announceNewData: {
|
||||
newDataAnnounce: 'Updated data for chart {chartTitle}',
|
||||
newSeriesAnnounceSingle: 'New data series: {seriesDesc}',
|
||||
newPointAnnounceSingle: 'New data point: {pointDesc}',
|
||||
newSeriesAnnounceMultiple: 'New data series in chart {chartTitle}: {seriesDesc}',
|
||||
newPointAnnounceMultiple: 'New data point in chart {chartTitle}: {pointDesc}'
|
||||
},
|
||||
/**
|
||||
* Descriptions of lesser known series types. The relevant
|
||||
* description is added to the screen reader information region
|
||||
* when these series types are used.
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
seriesTypeDescriptions: {
|
||||
boxplot: 'Box plot charts are typically used to display ' +
|
||||
'groups of statistical data. Each data point in the ' +
|
||||
'chart can have up to 5 values: minimum, lower quartile, ' +
|
||||
'median, upper quartile, and maximum.',
|
||||
arearange: 'Arearange charts are line charts displaying a ' +
|
||||
'range between a lower and higher value for each point.',
|
||||
areasplinerange: 'These charts are line charts displaying a ' +
|
||||
'range between a lower and higher value for each point.',
|
||||
bubble: 'Bubble charts are scatter charts where each data ' +
|
||||
'point also has a size value.',
|
||||
columnrange: 'Columnrange charts are column charts ' +
|
||||
'displaying a range between a lower and higher value for ' +
|
||||
'each point.',
|
||||
errorbar: 'Errorbar series are used to display the ' +
|
||||
'variability of the data.',
|
||||
funnel: 'Funnel charts are used to display reduction of data ' +
|
||||
'in stages.',
|
||||
pyramid: 'Pyramid charts consist of a single pyramid with ' +
|
||||
'item heights corresponding to each point value.',
|
||||
waterfall: 'A waterfall chart is a column chart where each ' +
|
||||
'column contributes towards a total end value.'
|
||||
},
|
||||
/**
|
||||
* Chart type description strings. This is added to the chart
|
||||
* information region.
|
||||
*
|
||||
* If there is only a single series type used in the chart, we use
|
||||
* the format string for the series type, or default if missing.
|
||||
* There is one format string for cases where there is only a single
|
||||
* series in the chart, and one for multiple series of the same
|
||||
* type.
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
chartTypes: {
|
||||
/* eslint-disable max-len */
|
||||
emptyChart: 'Empty chart',
|
||||
mapTypeDescription: 'Map of {mapTitle} with {numSeries} data series.',
|
||||
unknownMap: 'Map of unspecified region with {numSeries} data series.',
|
||||
combinationChart: 'Combination chart with {numSeries} data series.',
|
||||
defaultSingle: 'Chart with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
defaultMultiple: 'Chart with {numSeries} data series.',
|
||||
splineSingle: 'Line chart with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
splineMultiple: 'Line chart with {numSeries} lines.',
|
||||
lineSingle: 'Line chart with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
lineMultiple: 'Line chart with {numSeries} lines.',
|
||||
columnSingle: 'Bar chart with {numPoints} {#plural(numPoints, bars, bar)}.',
|
||||
columnMultiple: 'Bar chart with {numSeries} data series.',
|
||||
barSingle: 'Bar chart with {numPoints} {#plural(numPoints, bars, bar)}.',
|
||||
barMultiple: 'Bar chart with {numSeries} data series.',
|
||||
pieSingle: 'Pie chart with {numPoints} {#plural(numPoints, slices, slice)}.',
|
||||
pieMultiple: 'Pie chart with {numSeries} pies.',
|
||||
scatterSingle: 'Scatter chart with {numPoints} {#plural(numPoints, points, point)}.',
|
||||
scatterMultiple: 'Scatter chart with {numSeries} data series.',
|
||||
boxplotSingle: 'Boxplot with {numPoints} {#plural(numPoints, boxes, box)}.',
|
||||
boxplotMultiple: 'Boxplot with {numSeries} data series.',
|
||||
bubbleSingle: 'Bubble chart with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
|
||||
bubbleMultiple: 'Bubble chart with {numSeries} data series.'
|
||||
},
|
||||
/**
|
||||
* Axis description format strings.
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
axis: {
|
||||
/* eslint-disable max-len */
|
||||
xAxisDescriptionSingular: 'The chart has 1 X axis displaying {names[0]}. {ranges[0]}',
|
||||
xAxisDescriptionPlural: 'The chart has {numAxes} X axes displaying {#each(names, -1) }and {names[-1]}.',
|
||||
yAxisDescriptionSingular: 'The chart has 1 Y axis displaying {names[0]}. {ranges[0]}',
|
||||
yAxisDescriptionPlural: 'The chart has {numAxes} Y axes displaying {#each(names, -1) }and {names[-1]}.',
|
||||
timeRangeDays: 'Range: {range} days.',
|
||||
timeRangeHours: 'Range: {range} hours.',
|
||||
timeRangeMinutes: 'Range: {range} minutes.',
|
||||
timeRangeSeconds: 'Range: {range} seconds.',
|
||||
rangeFromTo: 'Range: {rangeFrom} to {rangeTo}.',
|
||||
rangeCategories: 'Range: {numCategories} categories.'
|
||||
},
|
||||
/**
|
||||
* Exporting menu format strings for accessibility module.
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
exporting: {
|
||||
chartMenuLabel: 'Chart menu',
|
||||
menuButtonLabel: 'View chart menu',
|
||||
exportRegionLabel: 'Chart menu'
|
||||
},
|
||||
/**
|
||||
* Lang configuration for different series types. For more dynamic
|
||||
* control over the series element descriptions, see
|
||||
* [accessibility.seriesDescriptionFormatter](#accessibility.seriesDescriptionFormatter).
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
series: {
|
||||
/**
|
||||
* Lang configuration for the series main summary. Each series
|
||||
* type has two modes:
|
||||
*
|
||||
* 1. This series type is the only series type used in the
|
||||
* chart
|
||||
*
|
||||
* 2. This is a combination chart with multiple series types
|
||||
*
|
||||
* If a definition does not exist for the specific series type
|
||||
* and mode, the 'default' lang definitions are used.
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
summary: {
|
||||
/* eslint-disable max-len */
|
||||
'default': '{name}, series {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
defaultCombination: '{name}, series {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
line: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
lineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
spline: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
splineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
column: '{name}, bar series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bars, bar)}.',
|
||||
columnCombination: '{name}, series {ix} of {numSeries}. Bar series with {numPoints} {#plural(numPoints, bars, bar)}.',
|
||||
bar: '{name}, bar series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bars, bar)}.',
|
||||
barCombination: '{name}, series {ix} of {numSeries}. Bar series with {numPoints} {#plural(numPoints, bars, bar)}.',
|
||||
pie: '{name}, pie {ix} of {numSeries} with {numPoints} {#plural(numPoints, slices, slice)}.',
|
||||
pieCombination: '{name}, series {ix} of {numSeries}. Pie with {numPoints} {#plural(numPoints, slices, slice)}.',
|
||||
scatter: '{name}, scatter plot {ix} of {numSeries} with {numPoints} {#plural(numPoints, points, point)}.',
|
||||
scatterCombination: '{name}, series {ix} of {numSeries}, scatter plot with {numPoints} {#plural(numPoints, points, point)}.',
|
||||
boxplot: '{name}, boxplot {ix} of {numSeries} with {numPoints} {#plural(numPoints, boxes, box)}.',
|
||||
boxplotCombination: '{name}, series {ix} of {numSeries}. Boxplot with {numPoints} {#plural(numPoints, boxes, box)}.',
|
||||
bubble: '{name}, bubble series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
|
||||
bubbleCombination: '{name}, series {ix} of {numSeries}. Bubble series with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
|
||||
map: '{name}, map {ix} of {numSeries} with {numPoints} {#plural(numPoints, areas, area)}.',
|
||||
mapCombination: '{name}, series {ix} of {numSeries}. Map with {numPoints} {#plural(numPoints, areas, area)}.',
|
||||
mapline: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
maplineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
|
||||
mapbubble: '{name}, bubble series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
|
||||
mapbubbleCombination: '{name}, series {ix} of {numSeries}. Bubble series with {numPoints} {#plural(numPoints, bubbles, bubble)}.'
|
||||
},
|
||||
/**
|
||||
* User supplied description text. This is added in the point
|
||||
* comment description by default if present.
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
description: '{description}',
|
||||
/**
|
||||
* xAxis description for series if there are multiple xAxes in
|
||||
* the chart.
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
xAxisDescription: 'X axis, {name}',
|
||||
/**
|
||||
* yAxis description for series if there are multiple yAxes in
|
||||
* the chart.
|
||||
*
|
||||
* @since 6.0.6
|
||||
*/
|
||||
yAxisDescription: 'Y axis, {name}',
|
||||
/**
|
||||
* Description for the value of null points.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
nullPointValue: 'No value',
|
||||
/**
|
||||
* Description for annotations on a point, as it is made available
|
||||
* to assistive technology.
|
||||
*
|
||||
* @since 8.0.1
|
||||
*/
|
||||
pointAnnotationsDescription: '{Annotation: #each(annotations). }'
|
||||
}
|
||||
}
|
||||
};
|
||||
export default langOptions;
|
||||
@@ -0,0 +1,793 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Default options for accessibility.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
/**
|
||||
* Formatter callback for the accessibility announcement.
|
||||
*
|
||||
* @callback Highcharts.AccessibilityAnnouncementFormatter
|
||||
*
|
||||
* @param {Array<Highcharts.Series>} updatedSeries
|
||||
* Array of all series that received updates. If an announcement is already
|
||||
* queued, the series that received updates for that announcement are also
|
||||
* included in this array.
|
||||
*
|
||||
* @param {Highcharts.Series} [addedSeries]
|
||||
* This is provided if {@link Highcharts.Chart#addSeries} was called, and there
|
||||
* is a new series. In that case, this argument is a reference to the new
|
||||
* series.
|
||||
*
|
||||
* @param {Highcharts.Point} [addedPoint]
|
||||
* This is provided if {@link Highcharts.Series#addPoint} was called, and there
|
||||
* is a new point. In that case, this argument is a reference to the new point.
|
||||
*
|
||||
* @return {false|string}
|
||||
* The function should return a string with the text to announce to the user.
|
||||
* Return empty string to not announce anything. Return `false` to use the
|
||||
* default announcement format.
|
||||
*/
|
||||
/**
|
||||
* @interface Highcharts.PointAccessibilityOptionsObject
|
||||
*/ /**
|
||||
* Provide a description of the data point, announced to screen readers.
|
||||
* @name Highcharts.PointAccessibilityOptionsObject#description
|
||||
* @type {string|undefined}
|
||||
* @requires modules/accessibility
|
||||
* @since 7.1.0
|
||||
*/
|
||||
/* *
|
||||
* @interface Highcharts.PointOptionsObject in parts/Point.ts
|
||||
*/ /**
|
||||
* @name Highcharts.PointOptionsObject#accessibility
|
||||
* @type {Highcharts.PointAccessibilityOptionsObject|undefined}
|
||||
* @requires modules/accessibility
|
||||
* @since 7.1.0
|
||||
*/
|
||||
/**
|
||||
* @callback Highcharts.ScreenReaderClickCallbackFunction
|
||||
*
|
||||
* @param {global.MouseEvent} evt
|
||||
* Mouse click event
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
/**
|
||||
* Creates a formatted string for the screen reader module.
|
||||
*
|
||||
* @callback Highcharts.ScreenReaderFormatterCallbackFunction<T>
|
||||
*
|
||||
* @param {T} context
|
||||
* Context to format
|
||||
*
|
||||
* @return {string}
|
||||
* Formatted string for the screen reader module.
|
||||
*/
|
||||
var options = {
|
||||
/**
|
||||
* Options for configuring accessibility for the chart. Requires the
|
||||
* [accessibility module](https://code.highcharts.com/modules/accessibility.js)
|
||||
* to be loaded. For a description of the module and information
|
||||
* on its features, see
|
||||
* [Highcharts Accessibility](https://www.highcharts.com/docs/chart-concepts/accessibility).
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @requires modules/accessibility
|
||||
* @optionparent accessibility
|
||||
*/
|
||||
accessibility: {
|
||||
/**
|
||||
* Enable accessibility functionality for the chart.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
enabled: true,
|
||||
/**
|
||||
* Accessibility options for the screen reader information sections
|
||||
* added before and after the chart.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
screenReaderSection: {
|
||||
/**
|
||||
* Function to run upon clicking the "View as Data Table" link in
|
||||
* the screen reader region.
|
||||
*
|
||||
* By default Highcharts will insert and set focus to a data table
|
||||
* representation of the chart.
|
||||
*
|
||||
* @type {Highcharts.ScreenReaderClickCallbackFunction}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.screenReaderSection.onViewDataTableClick
|
||||
*/
|
||||
/**
|
||||
* Function to run upon clicking the "Play as sound" button in
|
||||
* the screen reader region.
|
||||
*
|
||||
* By default Highcharts will call the `chart.sonify` function.
|
||||
*
|
||||
* @type {Highcharts.ScreenReaderClickCallbackFunction}
|
||||
* @since 8.0.1
|
||||
* @apioption accessibility.screenReaderSection.onPlayAsSoundClick
|
||||
*/
|
||||
/**
|
||||
* A formatter function to create the HTML contents of the hidden
|
||||
* screen reader information region before the chart. Receives one
|
||||
* argument, `chart`, referring to the chart object. Should return a
|
||||
* string with the HTML content of the region. By default this
|
||||
* returns an automatic description of the chart based on
|
||||
* [beforeChartFormat](#accessibility.screenReaderSection.beforeChartFormat).
|
||||
*
|
||||
* @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Chart>}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.screenReaderSection.beforeChartFormatter
|
||||
*/
|
||||
/**
|
||||
* Format for the screen reader information region before the chart.
|
||||
* Supported HTML tags are `<h1-7>`, `<p>`, `<div>`, `<a>`, `<ul>`,
|
||||
* `<ol>`, `<li>`, and `<button>`. Attributes are not supported,
|
||||
* except for id on `<div>`, `<a>`, and `<button>`. Id is required
|
||||
* on `<a>` and `<button>` in the format `<tag id="abcd">`. Numbers,
|
||||
* lower- and uppercase letters, "-" and "#" are valid characters in
|
||||
* IDs.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
beforeChartFormat: '<h5>{chartTitle}</h5>' +
|
||||
'<div>{typeDescription}</div>' +
|
||||
'<div>{chartSubtitle}</div>' +
|
||||
'<div>{chartLongdesc}</div>' +
|
||||
'<div>{playAsSoundButton}</div>' +
|
||||
'<div>{viewTableButton}</div>' +
|
||||
'<div>{xAxisDescription}</div>' +
|
||||
'<div>{yAxisDescription}</div>' +
|
||||
'<div>{annotationsTitle}{annotationsList}</div>',
|
||||
/**
|
||||
* A formatter function to create the HTML contents of the hidden
|
||||
* screen reader information region after the chart. Analogous to
|
||||
* [beforeChartFormatter](#accessibility.screenReaderSection.beforeChartFormatter).
|
||||
*
|
||||
* @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Chart>}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.screenReaderSection.afterChartFormatter
|
||||
*/
|
||||
/**
|
||||
* Format for the screen reader information region after the chart.
|
||||
* Analogous to [beforeChartFormat](#accessibility.screenReaderSection.beforeChartFormat).
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
afterChartFormat: '{endOfChartMarker}',
|
||||
/**
|
||||
* Date format to use to describe range of datetime axes.
|
||||
*
|
||||
* For an overview of the replacement codes, see
|
||||
* [dateFormat](/class-reference/Highcharts#dateFormat).
|
||||
*
|
||||
* @see [point.dateFormat](#accessibility.point.dateFormat)
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
axisRangeDateFormat: '%Y-%m-%d %H:%M:%S'
|
||||
},
|
||||
/**
|
||||
* Accessibility options global to all data series. Individual series
|
||||
* can also have specific [accessibility options](#plotOptions.series.accessibility)
|
||||
* set.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
series: {
|
||||
/**
|
||||
* Formatter function to use instead of the default for series
|
||||
* descriptions. Receives one argument, `series`, referring to the
|
||||
* series to describe. Should return a string with the description
|
||||
* of the series for a screen reader user. If `false` is returned,
|
||||
* the default formatter will be used for that series.
|
||||
*
|
||||
* @see [series.description](#plotOptions.series.description)
|
||||
*
|
||||
* @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Series>}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.series.descriptionFormatter
|
||||
*/
|
||||
/**
|
||||
* Whether or not to add series descriptions to charts with a single
|
||||
* series.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
describeSingleSeries: false,
|
||||
/**
|
||||
* When a series contains more points than this, we no longer expose
|
||||
* information about individual points to screen readers.
|
||||
*
|
||||
* Set to `false` to disable.
|
||||
*
|
||||
* @type {boolean|number}
|
||||
* @since 8.0.0
|
||||
*/
|
||||
pointDescriptionEnabledThreshold: 200
|
||||
},
|
||||
/**
|
||||
* Options for descriptions of individual data points.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
point: {
|
||||
/**
|
||||
* Date format to use for points on datetime axes when describing
|
||||
* them to screen reader users.
|
||||
*
|
||||
* Defaults to the same format as in tooltip.
|
||||
*
|
||||
* For an overview of the replacement codes, see
|
||||
* [dateFormat](/class-reference/Highcharts#dateFormat).
|
||||
*
|
||||
* @see [dateFormatter](#accessibility.point.dateFormatter)
|
||||
*
|
||||
* @type {string}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.point.dateFormat
|
||||
*/
|
||||
/**
|
||||
* Formatter function to determine the date/time format used with
|
||||
* points on datetime axes when describing them to screen reader
|
||||
* users. Receives one argument, `point`, referring to the point
|
||||
* to describe. Should return a date format string compatible with
|
||||
* [dateFormat](/class-reference/Highcharts#dateFormat).
|
||||
*
|
||||
* @see [dateFormat](#accessibility.point.dateFormat)
|
||||
*
|
||||
* @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Point>}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.point.dateFormatter
|
||||
*/
|
||||
/**
|
||||
* Prefix to add to the values in the point descriptions. Uses
|
||||
* [tooltip.valuePrefix](#tooltip.valuePrefix) if not defined.
|
||||
*
|
||||
* @type {string}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.point.valuePrefix
|
||||
*/
|
||||
/**
|
||||
* Suffix to add to the values in the point descriptions. Uses
|
||||
* [tooltip.valueSuffix](#tooltip.valueSuffix) if not defined.
|
||||
*
|
||||
* @type {string}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.point.valueSuffix
|
||||
*/
|
||||
/**
|
||||
* Decimals to use for the values in the point descriptions. Uses
|
||||
* [tooltip.valueDecimals](#tooltip.valueDecimals) if not defined.
|
||||
*
|
||||
* @type {number}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.point.valueDecimals
|
||||
*/
|
||||
/**
|
||||
* Formatter function to use instead of the default for point
|
||||
* descriptions.
|
||||
*
|
||||
* Receives one argument, `point`, referring to the point to
|
||||
* describe. Should return a string with the description of the
|
||||
* point for a screen reader user. If `false` is returned, the
|
||||
* default formatter will be used for that point.
|
||||
*
|
||||
* Note: Prefer using [accessibility.point.valueDescriptionFormat](#accessibility.point.valueDescriptionFormat)
|
||||
* instead if possible, as default functionality such as describing
|
||||
* annotations will be preserved.
|
||||
*
|
||||
* @see [accessibility.point.valueDescriptionFormat](#accessibility.point.valueDescriptionFormat)
|
||||
* @see [point.accessibility.description](#series.line.data.accessibility.description)
|
||||
*
|
||||
* @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Point>}
|
||||
* @since 8.0.0
|
||||
* @apioption accessibility.point.descriptionFormatter
|
||||
*/
|
||||
/**
|
||||
* Format to use for describing the values of data points
|
||||
* to assistive technology - including screen readers.
|
||||
* The point context is available as `{point}`.
|
||||
*
|
||||
* Additionally, the series name, annotation info, and
|
||||
* description added in `point.accessibility.description`
|
||||
* is added by default if relevant. To override this, use the
|
||||
* [accessibility.point.descriptionFormatter](#accessibility.point.descriptionFormatter)
|
||||
* option.
|
||||
*
|
||||
* @see [point.accessibility.description](#series.line.data.accessibility.description)
|
||||
* @see [accessibility.point.descriptionFormatter](#accessibility.point.descriptionFormatter)
|
||||
*
|
||||
* @type {string}
|
||||
* @since 8.0.1
|
||||
*/
|
||||
valueDescriptionFormat: '{index}. {xDescription}{separator}{value}.'
|
||||
},
|
||||
/**
|
||||
* Amount of landmarks/regions to create for screen reader users. More
|
||||
* landmarks can make navigation with screen readers easier, but can
|
||||
* be distracting if there are lots of charts on the page. Three modes
|
||||
* are available:
|
||||
* - `all`: Adds regions for all series, legend, menu, information
|
||||
* region.
|
||||
* - `one`: Adds a single landmark per chart.
|
||||
* - `disabled`: No landmarks are added.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @validvalue ["all", "one", "disabled"]
|
||||
*/
|
||||
landmarkVerbosity: 'all',
|
||||
/**
|
||||
* Link the chart to an HTML element describing the contents of the
|
||||
* chart.
|
||||
*
|
||||
* It is always recommended to describe charts using visible text, to
|
||||
* improve SEO as well as accessibility for users with disabilities.
|
||||
* This option lets an HTML element with a description be linked to the
|
||||
* chart, so that screen reader users can connect the two.
|
||||
*
|
||||
* By setting this option to a string, Highcharts runs the string as an
|
||||
* HTML selector query on the entire document. If there is only a single
|
||||
* match, this element is linked to the chart. The content of the linked
|
||||
* element will be included in the chart description for screen reader
|
||||
* users.
|
||||
*
|
||||
* By default, the chart looks for an adjacent sibling element with the
|
||||
* `highcharts-description` class.
|
||||
*
|
||||
* The feature can be disabled by setting the option to an empty string,
|
||||
* or overridden by providing the
|
||||
* [accessibility.description](#accessibility.description) option.
|
||||
* Alternatively, the HTML element to link can be passed in directly as
|
||||
* an HTML node.
|
||||
*
|
||||
* If you need the description to be part of the exported image,
|
||||
* consider using the [caption](#caption) feature.
|
||||
*
|
||||
* If you need the description to be hidden visually, use the
|
||||
* [accessibility.description](#accessibility.description) option.
|
||||
*
|
||||
* @see [caption](#caption)
|
||||
* @see [description](#accessibility.description)
|
||||
* @see [typeDescription](#accessibility.typeDescription)
|
||||
*
|
||||
* @sample highcharts/accessibility/accessible-line
|
||||
* Accessible line chart
|
||||
*
|
||||
* @type {string|Highcharts.HTMLDOMElement}
|
||||
* @since 8.0.0
|
||||
*/
|
||||
linkedDescription: '*[data-highcharts-chart="{index}"] + .highcharts-description',
|
||||
/**
|
||||
* A hook for adding custom components to the accessibility module.
|
||||
* Should be an object mapping component names to instances of classes
|
||||
* inheriting from the Highcharts.AccessibilityComponent base class.
|
||||
* Remember to add the component to the
|
||||
* [keyboardNavigation.order](#accessibility.keyboardNavigation.order)
|
||||
* for the keyboard navigation to be usable.
|
||||
*
|
||||
* @sample highcharts/accessibility/custom-component
|
||||
* Custom accessibility component
|
||||
*
|
||||
* @type {*}
|
||||
* @since 7.1.0
|
||||
* @apioption accessibility.customComponents
|
||||
*/
|
||||
/**
|
||||
* Theme to apply to the chart when Windows High Contrast Mode is
|
||||
* detected. By default, a high contrast theme matching the high
|
||||
* contrast system system colors is used.
|
||||
*
|
||||
* @type {*}
|
||||
* @since 7.1.3
|
||||
* @apioption accessibility.highContrastTheme
|
||||
*/
|
||||
/**
|
||||
* A text description of the chart.
|
||||
*
|
||||
* **Note: Prefer using [linkedDescription](#accessibility.linkedDescription)
|
||||
* or [caption](#caption.text) instead.**
|
||||
*
|
||||
* If the Accessibility module is loaded, this option is included by
|
||||
* default as a long description of the chart in the hidden screen
|
||||
* reader information region.
|
||||
*
|
||||
* Note: Since Highcharts now supports captions and linked descriptions,
|
||||
* it is preferred to define the description using those methods, as a
|
||||
* visible caption/description benefits all users. If the
|
||||
* `accessibility.description` option is defined, the linked description
|
||||
* is ignored, and the caption is hidden from screen reader users.
|
||||
*
|
||||
* @see [linkedDescription](#accessibility.linkedDescription)
|
||||
* @see [caption](#caption)
|
||||
* @see [typeDescription](#accessibility.typeDescription)
|
||||
*
|
||||
* @type {string}
|
||||
* @since 5.0.0
|
||||
* @apioption accessibility.description
|
||||
*/
|
||||
/**
|
||||
* A text description of the chart type.
|
||||
*
|
||||
* If the Accessibility module is loaded, this will be included in the
|
||||
* description of the chart in the screen reader information region.
|
||||
*
|
||||
* Highcharts will by default attempt to guess the chart type, but for
|
||||
* more complex charts it is recommended to specify this property for
|
||||
* clarity.
|
||||
*
|
||||
* @type {string}
|
||||
* @since 5.0.0
|
||||
* @apioption accessibility.typeDescription
|
||||
*/
|
||||
/**
|
||||
* Options for keyboard navigation.
|
||||
*
|
||||
* @declare Highcharts.KeyboardNavigationOptionsObject
|
||||
* @since 5.0.0
|
||||
*/
|
||||
keyboardNavigation: {
|
||||
/**
|
||||
* Enable keyboard navigation for the chart.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
enabled: true,
|
||||
/**
|
||||
* Options for the focus border drawn around elements while
|
||||
* navigating through them.
|
||||
*
|
||||
* @sample highcharts/accessibility/custom-focus
|
||||
* Custom focus ring
|
||||
*
|
||||
* @declare Highcharts.KeyboardNavigationFocusBorderOptionsObject
|
||||
* @since 6.0.3
|
||||
*/
|
||||
focusBorder: {
|
||||
/**
|
||||
* Enable/disable focus border for chart.
|
||||
*
|
||||
* @since 6.0.3
|
||||
*/
|
||||
enabled: true,
|
||||
/**
|
||||
* Hide the browser's default focus indicator.
|
||||
*
|
||||
* @since 6.0.4
|
||||
*/
|
||||
hideBrowserFocusOutline: true,
|
||||
/**
|
||||
* Style options for the focus border drawn around elements
|
||||
* while navigating through them. Note that some browsers in
|
||||
* addition draw their own borders for focused elements. These
|
||||
* automatic borders can not be styled by Highcharts.
|
||||
*
|
||||
* In styled mode, the border is given the
|
||||
* `.highcharts-focus-border` class.
|
||||
*
|
||||
* @type {Highcharts.CSSObject}
|
||||
* @since 6.0.3
|
||||
*/
|
||||
style: {
|
||||
/** @internal */
|
||||
color: '#335cad',
|
||||
/** @internal */
|
||||
lineWidth: 2,
|
||||
/** @internal */
|
||||
borderRadius: 3
|
||||
},
|
||||
/**
|
||||
* Focus border margin around the elements.
|
||||
*
|
||||
* @since 6.0.3
|
||||
*/
|
||||
margin: 2
|
||||
},
|
||||
/**
|
||||
* Order of tab navigation in the chart. Determines which elements
|
||||
* are tabbed to first. Available elements are: `series`, `zoom`,
|
||||
* `rangeSelector`, `chartMenu`, `legend`. In addition, any custom
|
||||
* components can be added here.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
* @since 7.1.0
|
||||
*/
|
||||
order: ['series', 'zoom', 'rangeSelector', 'legend', 'chartMenu'],
|
||||
/**
|
||||
* Whether or not to wrap around when reaching the end of arrow-key
|
||||
* navigation for an element in the chart.
|
||||
* @since 7.1.0
|
||||
*/
|
||||
wrapAround: true,
|
||||
/**
|
||||
* Options for the keyboard navigation of data points and series.
|
||||
*
|
||||
* @declare Highcharts.KeyboardNavigationSeriesNavigationOptionsObject
|
||||
* @since 8.0.0
|
||||
*/
|
||||
seriesNavigation: {
|
||||
/**
|
||||
* Set the keyboard navigation mode for the chart. Can be
|
||||
* "normal" or "serialize". In normal mode, left/right arrow
|
||||
* keys move between points in a series, while up/down arrow
|
||||
* keys move between series. Up/down navigation acts
|
||||
* intelligently to figure out which series makes sense to move
|
||||
* to from any given point.
|
||||
*
|
||||
* In "serialize" mode, points are instead navigated as a single
|
||||
* list. Left/right behaves as in "normal" mode. Up/down arrow
|
||||
* keys will behave like left/right. This can be useful for
|
||||
* unifying navigation behavior with/without screen readers
|
||||
* enabled.
|
||||
*
|
||||
* @type {string}
|
||||
* @default normal
|
||||
* @since 8.0.0
|
||||
* @validvalue ["normal", "serialize"]
|
||||
* @apioption accessibility.keyboardNavigation.seriesNavigation.mode
|
||||
*/
|
||||
/**
|
||||
* Skip null points when navigating through points with the
|
||||
* keyboard.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
skipNullPoints: true,
|
||||
/**
|
||||
* When a series contains more points than this, we no longer
|
||||
* allow keyboard navigation for it.
|
||||
*
|
||||
* Set to `false` to disable.
|
||||
*
|
||||
* @type {boolean|number}
|
||||
* @since 8.0.0
|
||||
*/
|
||||
pointNavigationEnabledThreshold: false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Options for announcing new data to screen reader users. Useful
|
||||
* for dynamic data applications and drilldown.
|
||||
*
|
||||
* Keep in mind that frequent announcements will not be useful to
|
||||
* users, as they won't have time to explore the new data. For these
|
||||
* applications, consider making snapshots of the data accessible, and
|
||||
* do the announcements in batches.
|
||||
*
|
||||
* @declare Highcharts.AccessibilityAnnounceNewDataOptionsObject
|
||||
* @since 7.1.0
|
||||
*/
|
||||
announceNewData: {
|
||||
/**
|
||||
* Optional formatter callback for the announcement. Receives
|
||||
* up to three arguments. The first argument is always an array
|
||||
* of all series that received updates. If an announcement is
|
||||
* already queued, the series that received updates for that
|
||||
* announcement are also included in this array. The second
|
||||
* argument is provided if `chart.addSeries` was called, and
|
||||
* there is a new series. In that case, this argument is a
|
||||
* reference to the new series. The third argument, similarly,
|
||||
* is provided if `series.addPoint` was called, and there is a
|
||||
* new point. In that case, this argument is a reference to the
|
||||
* new point.
|
||||
*
|
||||
* The function should return a string with the text to announce
|
||||
* to the user. Return empty string to not announce anything.
|
||||
* Return `false` to use the default announcement format.
|
||||
*
|
||||
* @sample highcharts/accessibility/custom-dynamic
|
||||
* High priority live alerts
|
||||
*
|
||||
* @type {Highcharts.AccessibilityAnnouncementFormatter}
|
||||
* @apioption accessibility.announceNewData.announcementFormatter
|
||||
*/
|
||||
/**
|
||||
* Enable announcing new data to screen reader users
|
||||
* @sample highcharts/accessibility/accessible-dynamic
|
||||
* Dynamic data accessible
|
||||
*/
|
||||
enabled: false,
|
||||
/**
|
||||
* Minimum interval between announcements in milliseconds. If
|
||||
* new data arrives before this amount of time has passed, it is
|
||||
* queued for announcement. If another new data event happens
|
||||
* while an announcement is queued, the queued announcement is
|
||||
* dropped, and the latest announcement is queued instead. Set
|
||||
* to 0 to allow all announcements, but be warned that frequent
|
||||
* announcements are disturbing to users.
|
||||
*/
|
||||
minAnnounceInterval: 5000,
|
||||
/**
|
||||
* Choose whether or not the announcements should interrupt the
|
||||
* screen reader. If not enabled, the user will be notified once
|
||||
* idle. It is recommended not to enable this setting unless
|
||||
* there is a specific reason to do so.
|
||||
*/
|
||||
interruptUser: false
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Accessibility options for a data point.
|
||||
*
|
||||
* @declare Highcharts.PointAccessibilityOptionsObject
|
||||
* @since 7.1.0
|
||||
* @apioption series.line.data.accessibility
|
||||
*/
|
||||
/**
|
||||
* Provide a description of the data point, announced to screen readers.
|
||||
*
|
||||
* @type {string}
|
||||
* @since 7.1.0
|
||||
* @apioption series.line.data.accessibility.description
|
||||
*/
|
||||
/**
|
||||
* Accessibility options for a series.
|
||||
*
|
||||
* @declare Highcharts.SeriesAccessibilityOptionsObject
|
||||
* @since 7.1.0
|
||||
* @requires modules/accessibility
|
||||
* @apioption plotOptions.series.accessibility
|
||||
*/
|
||||
/**
|
||||
* Enable/disable accessibility functionality for a specific series.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @since 7.1.0
|
||||
* @apioption plotOptions.series.accessibility.enabled
|
||||
*/
|
||||
/**
|
||||
* Provide a description of the series, announced to screen readers.
|
||||
*
|
||||
* @type {string}
|
||||
* @since 7.1.0
|
||||
* @apioption plotOptions.series.accessibility.description
|
||||
*/
|
||||
/**
|
||||
* Formatter function to use instead of the default for point
|
||||
* descriptions. Same as `accessibility.point.descriptionFormatter`, but for
|
||||
* a single series.
|
||||
*
|
||||
* @see [accessibility.point.descriptionFormatter](#accessibility.point.descriptionFormatter)
|
||||
*
|
||||
* @type {Highcharts.ScreenReaderFormatterCallbackFunction<Highcharts.Point>}
|
||||
* @since 7.1.0
|
||||
* @apioption plotOptions.series.accessibility.pointDescriptionFormatter
|
||||
*/
|
||||
/**
|
||||
* Expose only the series element to screen readers, not its points.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @since 7.1.0
|
||||
* @apioption plotOptions.series.accessibility.exposeAsGroupOnly
|
||||
*/
|
||||
/**
|
||||
* Keyboard navigation for a series
|
||||
*
|
||||
* @declare Highcharts.SeriesAccessibilityKeyboardNavigationOptionsObject
|
||||
* @since 7.1.0
|
||||
* @apioption plotOptions.series.accessibility.keyboardNavigation
|
||||
*/
|
||||
/**
|
||||
* Enable/disable keyboard navigation support for a specific series.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @since 7.1.0
|
||||
* @apioption plotOptions.series.accessibility.keyboardNavigation.enabled
|
||||
*/
|
||||
/**
|
||||
* Accessibility options for an annotation label.
|
||||
*
|
||||
* @declare Highcharts.AnnotationLabelAccessibilityOptionsObject
|
||||
* @since 8.0.1
|
||||
* @requires modules/accessibility
|
||||
* @apioption annotations.labelOptions.accessibility
|
||||
*/
|
||||
/**
|
||||
* Description of an annotation label for screen readers and other assistive
|
||||
* technology.
|
||||
*
|
||||
* @type {string}
|
||||
* @since 8.0.1
|
||||
* @apioption annotations.labelOptions.accessibility.description
|
||||
*/
|
||||
/**
|
||||
* Accessibility options for an axis. Requires the accessibility module.
|
||||
*
|
||||
* @declare Highcharts.AxisAccessibilityOptionsObject
|
||||
* @since 7.1.0
|
||||
* @requires modules/accessibility
|
||||
* @apioption xAxis.accessibility
|
||||
*/
|
||||
/**
|
||||
* Enable axis accessibility features, including axis information in the
|
||||
* screen reader information region. If this is disabled on the xAxis, the
|
||||
* x values are not exposed to screen readers for the individual data points
|
||||
* by default.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @since 7.1.0
|
||||
* @apioption xAxis.accessibility.enabled
|
||||
*/
|
||||
/**
|
||||
* Description for an axis to expose to screen reader users.
|
||||
*
|
||||
* @type {string}
|
||||
* @since 7.1.0
|
||||
* @apioption xAxis.accessibility.description
|
||||
*/
|
||||
/**
|
||||
* Range description for an axis. Overrides the default range description.
|
||||
* Set to empty to disable range description for this axis.
|
||||
*
|
||||
* @type {string}
|
||||
* @since 7.1.0
|
||||
* @apioption xAxis.accessibility.rangeDescription
|
||||
*/
|
||||
/**
|
||||
* @optionparent legend
|
||||
*/
|
||||
legend: {
|
||||
/**
|
||||
* Accessibility options for the legend. Requires the Accessibility
|
||||
* module.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @requires modules/accessibility
|
||||
*/
|
||||
accessibility: {
|
||||
/**
|
||||
* Enable accessibility support for the legend.
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
enabled: true,
|
||||
/**
|
||||
* Options for keyboard navigation for the legend.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @requires modules/accessibility
|
||||
*/
|
||||
keyboardNavigation: {
|
||||
/**
|
||||
* Enable keyboard navigation for the legend.
|
||||
*
|
||||
* @see [accessibility.keyboardNavigation](#accessibility.keyboardNavigation.enabled)
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @optionparent exporting
|
||||
*/
|
||||
exporting: {
|
||||
/**
|
||||
* Accessibility options for the exporting menu. Requires the
|
||||
* Accessibility module.
|
||||
*
|
||||
* @since 7.1.0
|
||||
* @requires modules/accessibility
|
||||
*/
|
||||
accessibility: {
|
||||
/**
|
||||
* Enable accessibility support for the export menu.
|
||||
*
|
||||
* @since 7.1.0
|
||||
*/
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
};
|
||||
export default options;
|
||||
@@ -0,0 +1,51 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Create announcer to speak messages to screen readers and other AT.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
import DOMElementProvider from './DOMElementProvider.js';
|
||||
import HTMLUtilities from './HTMLUtilities.js';
|
||||
var visuallyHideElement = HTMLUtilities.visuallyHideElement;
|
||||
var Announcer = /** @class */ (function () {
|
||||
function Announcer(chart, type) {
|
||||
this.chart = chart;
|
||||
this.domElementProvider = new DOMElementProvider();
|
||||
this.announceRegion = this.addAnnounceRegion(type);
|
||||
}
|
||||
Announcer.prototype.destroy = function () {
|
||||
this.domElementProvider.destroyCreatedElements();
|
||||
};
|
||||
Announcer.prototype.announce = function (message) {
|
||||
var _this = this;
|
||||
this.announceRegion.innerHTML = message;
|
||||
// Delete contents after a little while to avoid user finding the live
|
||||
// region in the DOM.
|
||||
if (this.clearAnnouncementRegionTimer) {
|
||||
clearTimeout(this.clearAnnouncementRegionTimer);
|
||||
}
|
||||
this.clearAnnouncementRegionTimer = setTimeout(function () {
|
||||
_this.announceRegion.innerHTML = '';
|
||||
delete _this.clearAnnouncementRegionTimer;
|
||||
}, 1000);
|
||||
};
|
||||
Announcer.prototype.addAnnounceRegion = function (type) {
|
||||
var chartContainer = this.chart.renderTo;
|
||||
var div = this.domElementProvider.createElement('div');
|
||||
div.setAttribute('aria-hidden', false);
|
||||
div.setAttribute('aria-live', type);
|
||||
visuallyHideElement(div);
|
||||
chartContainer.insertBefore(div, chartContainer.firstChild);
|
||||
return div;
|
||||
};
|
||||
return Announcer;
|
||||
}());
|
||||
H.Announcer = Announcer;
|
||||
export default Announcer;
|
||||
@@ -0,0 +1,187 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Utils for dealing with charts.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import HTMLUtilities from './HTMLUtilities.js';
|
||||
var stripHTMLTags = HTMLUtilities.stripHTMLTagsFromString;
|
||||
import U from '../../Core/Utilities.js';
|
||||
var defined = U.defined, find = U.find, fireEvent = U.fireEvent;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
function getChartTitle(chart) {
|
||||
return stripHTMLTags(chart.options.title.text ||
|
||||
chart.langFormat('accessibility.defaultChartTitle', { chart: chart }));
|
||||
}
|
||||
/**
|
||||
* @param {Highcharts.Axis} axis
|
||||
* @return {string}
|
||||
*/
|
||||
function getAxisDescription(axis) {
|
||||
return stripHTMLTags(axis && (axis.userOptions && axis.userOptions.accessibility &&
|
||||
axis.userOptions.accessibility.description ||
|
||||
axis.axisTitle && axis.axisTitle.textStr ||
|
||||
axis.options.id ||
|
||||
axis.categories && 'categories' ||
|
||||
axis.dateTime && 'Time' ||
|
||||
'values'));
|
||||
}
|
||||
/**
|
||||
* Get the DOM element for the first point in the series.
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* The series to get element for.
|
||||
* @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined}
|
||||
* The DOM element for the point.
|
||||
*/
|
||||
function getSeriesFirstPointElement(series) {
|
||||
if (series.points &&
|
||||
series.points.length &&
|
||||
series.points[0].graphic) {
|
||||
return series.points[0].graphic.element;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the DOM element for the series that we put accessibility info on.
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* The series to get element for.
|
||||
* @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|undefined}
|
||||
* The DOM element for the series
|
||||
*/
|
||||
function getSeriesA11yElement(series) {
|
||||
var firstPointEl = getSeriesFirstPointElement(series);
|
||||
return (firstPointEl &&
|
||||
firstPointEl.parentNode || series.graph &&
|
||||
series.graph.element || series.group &&
|
||||
series.group.element); // Could be tracker series depending on series type
|
||||
}
|
||||
/**
|
||||
* Remove aria-hidden from element. Also unhides parents of the element, and
|
||||
* hides siblings that are not explicitly unhidden.
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} element
|
||||
*/
|
||||
function unhideChartElementFromAT(chart, element) {
|
||||
element.setAttribute('aria-hidden', false);
|
||||
if (element === chart.renderTo || !element.parentNode) {
|
||||
return;
|
||||
}
|
||||
// Hide siblings unless their hidden state is already explicitly set
|
||||
Array.prototype.forEach.call(element.parentNode.childNodes, function (node) {
|
||||
if (!node.hasAttribute('aria-hidden')) {
|
||||
node.setAttribute('aria-hidden', true);
|
||||
}
|
||||
});
|
||||
// Repeat for parent
|
||||
unhideChartElementFromAT(chart, element.parentNode);
|
||||
}
|
||||
/**
|
||||
* Hide series from screen readers.
|
||||
* @private
|
||||
* @param {Highcharts.Series} series
|
||||
* The series to hide
|
||||
* @return {void}
|
||||
*/
|
||||
function hideSeriesFromAT(series) {
|
||||
var seriesEl = getSeriesA11yElement(series);
|
||||
if (seriesEl) {
|
||||
seriesEl.setAttribute('aria-hidden', true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get series objects by series name.
|
||||
* @private
|
||||
* @param {Highcharts.Chart} chart
|
||||
* @param {string} name
|
||||
* @return {Array<Highcharts.Series>}
|
||||
*/
|
||||
function getSeriesFromName(chart, name) {
|
||||
if (!name) {
|
||||
return chart.series;
|
||||
}
|
||||
return (chart.series || []).filter(function (s) {
|
||||
return s.name === name;
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get point in a series from x/y values.
|
||||
* @private
|
||||
* @param {Array<Highcharts.Series>} series
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @return {Highcharts.Point|undefined}
|
||||
*/
|
||||
function getPointFromXY(series, x, y) {
|
||||
var i = series.length, res;
|
||||
while (i--) {
|
||||
res = find(series[i].points || [], function (p) {
|
||||
return p.x === x && p.y === y;
|
||||
});
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get relative position of point on an x/y axis from 0 to 1.
|
||||
* @private
|
||||
* @param {Highcharts.Axis} axis
|
||||
* @param {Highcharts.Point} point
|
||||
* @return {number}
|
||||
*/
|
||||
function getRelativePointAxisPosition(axis, point) {
|
||||
if (!defined(axis.dataMin) || !defined(axis.dataMax)) {
|
||||
return 0;
|
||||
}
|
||||
var axisStart = axis.toPixels(axis.dataMin);
|
||||
var axisEnd = axis.toPixels(axis.dataMax);
|
||||
// We have to use pixel position because of axis breaks, log axis etc.
|
||||
var positionProp = axis.coll === 'xAxis' ? 'x' : 'y';
|
||||
var pointPos = axis.toPixels(point[positionProp] || 0);
|
||||
return (pointPos - axisStart) / (axisEnd - axisStart);
|
||||
}
|
||||
/**
|
||||
* Get relative position of point on an x/y axis from 0 to 1.
|
||||
* @private
|
||||
* @param {Highcharts.Point} point
|
||||
*/
|
||||
function scrollToPoint(point) {
|
||||
var xAxis = point.series.xAxis;
|
||||
var yAxis = point.series.yAxis;
|
||||
var axis = (xAxis === null || xAxis === void 0 ? void 0 : xAxis.scrollbar) ? xAxis : yAxis;
|
||||
var scrollbar = axis === null || axis === void 0 ? void 0 : axis.scrollbar;
|
||||
if (scrollbar && defined(scrollbar.to) && defined(scrollbar.from)) {
|
||||
var range = scrollbar.to - scrollbar.from;
|
||||
var pos = getRelativePointAxisPosition(axis, point);
|
||||
scrollbar.updatePosition(pos - range / 2, pos + range / 2);
|
||||
fireEvent(scrollbar, 'changed', {
|
||||
from: scrollbar.from,
|
||||
to: scrollbar.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMEvent: null
|
||||
});
|
||||
}
|
||||
}
|
||||
var ChartUtilities = {
|
||||
getChartTitle: getChartTitle,
|
||||
getAxisDescription: getAxisDescription,
|
||||
getPointFromXY: getPointFromXY,
|
||||
getSeriesFirstPointElement: getSeriesFirstPointElement,
|
||||
getSeriesFromName: getSeriesFromName,
|
||||
getSeriesA11yElement: getSeriesA11yElement,
|
||||
unhideChartElementFromAT: unhideChartElementFromAT,
|
||||
hideSeriesFromAT: hideSeriesFromAT,
|
||||
scrollToPoint: scrollToPoint
|
||||
};
|
||||
export default ChartUtilities;
|
||||
@@ -0,0 +1,50 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Class that can keep track of elements added to DOM and clean them up on
|
||||
* destroy.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
var doc = H.win.document;
|
||||
import U from '../../Core/Utilities.js';
|
||||
var extend = U.extend;
|
||||
import HTMLUtilities from './HTMLUtilities.js';
|
||||
var removeElement = HTMLUtilities.removeElement;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var DOMElementProvider = function () {
|
||||
this.elements = [];
|
||||
};
|
||||
extend(DOMElementProvider.prototype, {
|
||||
/**
|
||||
* Create an element and keep track of it for later removal.
|
||||
* Same args as document.createElement
|
||||
* @private
|
||||
*/
|
||||
createElement: function () {
|
||||
var el = doc.createElement.apply(doc, arguments);
|
||||
this.elements.push(el);
|
||||
return el;
|
||||
},
|
||||
/**
|
||||
* Destroy all created elements, removing them from the DOM.
|
||||
* @private
|
||||
*/
|
||||
destroyCreatedElements: function () {
|
||||
this.elements.forEach(function (element) {
|
||||
removeElement(element);
|
||||
});
|
||||
this.elements = [];
|
||||
}
|
||||
});
|
||||
export default DOMElementProvider;
|
||||
@@ -0,0 +1,48 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Class that can keep track of events added, and clean them up on destroy.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Core/Globals.js';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, extend = U.extend;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var EventProvider = function () {
|
||||
this.eventRemovers = [];
|
||||
};
|
||||
extend(EventProvider.prototype, {
|
||||
/**
|
||||
* Add an event to an element and keep track of it for later removal.
|
||||
* Same args as Highcharts.addEvent.
|
||||
* @private
|
||||
* @return {Function}
|
||||
*/
|
||||
addEvent: function () {
|
||||
var remover = addEvent.apply(H, arguments);
|
||||
this.eventRemovers.push(remover);
|
||||
return remover;
|
||||
},
|
||||
/**
|
||||
* Remove all added events.
|
||||
* @private
|
||||
* @return {void}
|
||||
*/
|
||||
removeAddedEvents: function () {
|
||||
this.eventRemovers.forEach(function (remover) {
|
||||
remover();
|
||||
});
|
||||
this.eventRemovers = [];
|
||||
}
|
||||
});
|
||||
export default EventProvider;
|
||||
@@ -0,0 +1,173 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Øystein Moseng
|
||||
*
|
||||
* Utility functions for accessibility module.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import U from '../../Core/Utilities.js';
|
||||
var merge = U.merge;
|
||||
import H from '../../Core/Globals.js';
|
||||
var win = H.win, doc = win.document;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.HTMLDOMElement} el
|
||||
* @param {string} className
|
||||
* @return {void}
|
||||
*/
|
||||
function addClass(el, className) {
|
||||
if (el.classList) {
|
||||
el.classList.add(className);
|
||||
}
|
||||
else if (el.className.indexOf(className) < 0) {
|
||||
// Note: Dumb check for class name exists, should be fine for practical
|
||||
// use cases, but will return false positives if the element has a class
|
||||
// that contains the className.
|
||||
el.className += className;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
*/
|
||||
function escapeStringForHTML(str) {
|
||||
return str
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
.replace(/\//g, '/');
|
||||
}
|
||||
/**
|
||||
* Get an element by ID
|
||||
* @param {string} id
|
||||
* @private
|
||||
* @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement|null}
|
||||
*/
|
||||
function getElement(id) {
|
||||
return doc.getElementById(id);
|
||||
}
|
||||
/**
|
||||
* Get a fake mouse event of a given type
|
||||
* @param {string} type
|
||||
* @private
|
||||
* @return {global.MouseEvent}
|
||||
*/
|
||||
function getFakeMouseEvent(type) {
|
||||
if (typeof win.MouseEvent === 'function') {
|
||||
return new win.MouseEvent(type);
|
||||
}
|
||||
// No MouseEvent support, try using initMouseEvent
|
||||
if (doc.createEvent) {
|
||||
var evt = doc.createEvent('MouseEvent');
|
||||
if (evt.initMouseEvent) {
|
||||
evt.initMouseEvent(type, true, // Bubble
|
||||
true, // Cancel
|
||||
win, // View
|
||||
type === 'click' ? 1 : 0, // Detail
|
||||
// Coords
|
||||
0, 0, 0, 0,
|
||||
// Pressed keys
|
||||
false, false, false, false, 0, // button
|
||||
null // related target
|
||||
);
|
||||
return evt;
|
||||
}
|
||||
}
|
||||
return { type: type };
|
||||
}
|
||||
/**
|
||||
* Remove an element from the DOM.
|
||||
* @private
|
||||
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} [element]
|
||||
* @return {void}
|
||||
*/
|
||||
function removeElement(element) {
|
||||
if (element && element.parentNode) {
|
||||
element.parentNode.removeChild(element);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Utility function. Reverses child nodes of a DOM element.
|
||||
* @private
|
||||
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} node
|
||||
* @return {void}
|
||||
*/
|
||||
function reverseChildNodes(node) {
|
||||
var i = node.childNodes.length;
|
||||
while (i--) {
|
||||
node.appendChild(node.childNodes[i]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set attributes on element. Set to null to remove attribute.
|
||||
* @private
|
||||
* @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} el
|
||||
* @param {Highcharts.HTMLAttributes|Highcharts.SVGAttributes} attrs
|
||||
* @return {void}
|
||||
*/
|
||||
function setElAttrs(el, attrs) {
|
||||
Object.keys(attrs).forEach(function (attr) {
|
||||
var val = attrs[attr];
|
||||
if (val === null) {
|
||||
el.removeAttribute(attr);
|
||||
}
|
||||
else {
|
||||
var cleanedVal = escapeStringForHTML('' + val);
|
||||
el.setAttribute(attr, cleanedVal);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Used for aria-label attributes, painting on a canvas will fail if the
|
||||
* text contains tags.
|
||||
* @private
|
||||
* @param {string} str
|
||||
* @return {string}
|
||||
*/
|
||||
function stripHTMLTagsFromString(str) {
|
||||
return typeof str === 'string' ?
|
||||
str.replace(/<\/?[^>]+(>|$)/g, '') : str;
|
||||
}
|
||||
/**
|
||||
* Utility function for hiding an element visually, but still keeping it
|
||||
* available to screen reader users.
|
||||
* @private
|
||||
* @param {Highcharts.HTMLDOMElement} element
|
||||
* @return {void}
|
||||
*/
|
||||
function visuallyHideElement(element) {
|
||||
var hiddenStyle = {
|
||||
position: 'absolute',
|
||||
width: '1px',
|
||||
height: '1px',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'nowrap',
|
||||
clip: 'rect(1px, 1px, 1px, 1px)',
|
||||
marginTop: '-3px',
|
||||
'-ms-filter': 'progid:DXImageTransform.Microsoft.Alpha(Opacity=1)',
|
||||
filter: 'alpha(opacity=1)',
|
||||
opacity: '0.01'
|
||||
};
|
||||
merge(true, element.style, hiddenStyle);
|
||||
}
|
||||
var HTMLUtilities = {
|
||||
addClass: addClass,
|
||||
escapeStringForHTML: escapeStringForHTML,
|
||||
getElement: getElement,
|
||||
getFakeMouseEvent: getFakeMouseEvent,
|
||||
removeElement: removeElement,
|
||||
reverseChildNodes: reverseChildNodes,
|
||||
setElAttrs: setElAttrs,
|
||||
stripHTMLTagsFromString: stripHTMLTagsFromString,
|
||||
visuallyHideElement: visuallyHideElement
|
||||
};
|
||||
export default HTMLUtilities;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,611 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extenstion for 3d axes
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Globals.js';
|
||||
import Math3D from '../../Extensions/Math3D.js';
|
||||
var perspective = Math3D.perspective, perspective3D = Math3D.perspective3D, shapeArea = Math3D.shapeArea;
|
||||
import Tick from './Tick.js';
|
||||
import Tick3D from './Tick3D.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, merge = U.merge, pick = U.pick, wrap = U.wrap;
|
||||
var deg2rad = H.deg2rad;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Adds 3D support to axes.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var Axis3DAdditions = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function Axis3DAdditions(axis) {
|
||||
this.axis = axis;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
* @param {Highcharts.Axis} axis
|
||||
* Related axis.
|
||||
* @param {Highcharts.Position3dObject} pos
|
||||
* Position to fix.
|
||||
* @param {boolean} [isTitle]
|
||||
* Whether this is a title position.
|
||||
* @return {Highcharts.Position3dObject}
|
||||
* Fixed position.
|
||||
*/
|
||||
Axis3DAdditions.prototype.fix3dPosition = function (pos, isTitle) {
|
||||
var axis3D = this;
|
||||
var axis = axis3D.axis;
|
||||
var chart = axis.chart;
|
||||
// Do not do this if the chart is not 3D
|
||||
if (axis.coll === 'colorAxis' ||
|
||||
!chart.chart3d ||
|
||||
!chart.is3d()) {
|
||||
return pos;
|
||||
}
|
||||
var alpha = deg2rad * chart.options.chart.options3d.alpha, beta = deg2rad * chart.options.chart.options3d.beta, positionMode = pick(isTitle && axis.options.title.position3d, axis.options.labels.position3d), skew = pick(isTitle && axis.options.title.skew3d, axis.options.labels.skew3d), frame = chart.chart3d.frame3d, plotLeft = chart.plotLeft, plotRight = chart.plotWidth + plotLeft, plotTop = chart.plotTop, plotBottom = chart.plotHeight + plotTop,
|
||||
// Indicates that we are labelling an X or Z axis on the "back" of
|
||||
// the chart
|
||||
reverseFlap = false, offsetX = 0, offsetY = 0, vecX, vecY = { x: 0, y: 1, z: 0 };
|
||||
pos = axis.axis3D.swapZ({ x: pos.x, y: pos.y, z: 0 });
|
||||
if (axis.isZAxis) { // Z Axis
|
||||
if (axis.opposite) {
|
||||
if (frame.axes.z.top === null) {
|
||||
return {};
|
||||
}
|
||||
offsetY = pos.y - plotTop;
|
||||
pos.x = frame.axes.z.top.x;
|
||||
pos.y = frame.axes.z.top.y;
|
||||
vecX = frame.axes.z.top.xDir;
|
||||
reverseFlap = !frame.top.frontFacing;
|
||||
}
|
||||
else {
|
||||
if (frame.axes.z.bottom === null) {
|
||||
return {};
|
||||
}
|
||||
offsetY = pos.y - plotBottom;
|
||||
pos.x = frame.axes.z.bottom.x;
|
||||
pos.y = frame.axes.z.bottom.y;
|
||||
vecX = frame.axes.z.bottom.xDir;
|
||||
reverseFlap = !frame.bottom.frontFacing;
|
||||
}
|
||||
}
|
||||
else if (axis.horiz) { // X Axis
|
||||
if (axis.opposite) {
|
||||
if (frame.axes.x.top === null) {
|
||||
return {};
|
||||
}
|
||||
offsetY = pos.y - plotTop;
|
||||
pos.y = frame.axes.x.top.y;
|
||||
pos.z = frame.axes.x.top.z;
|
||||
vecX = frame.axes.x.top.xDir;
|
||||
reverseFlap = !frame.top.frontFacing;
|
||||
}
|
||||
else {
|
||||
if (frame.axes.x.bottom === null) {
|
||||
return {};
|
||||
}
|
||||
offsetY = pos.y - plotBottom;
|
||||
pos.y = frame.axes.x.bottom.y;
|
||||
pos.z = frame.axes.x.bottom.z;
|
||||
vecX = frame.axes.x.bottom.xDir;
|
||||
reverseFlap = !frame.bottom.frontFacing;
|
||||
}
|
||||
}
|
||||
else { // Y Axis
|
||||
if (axis.opposite) {
|
||||
if (frame.axes.y.right === null) {
|
||||
return {};
|
||||
}
|
||||
offsetX = pos.x - plotRight;
|
||||
pos.x = frame.axes.y.right.x;
|
||||
pos.z = frame.axes.y.right.z;
|
||||
vecX = frame.axes.y.right.xDir;
|
||||
// Rotate 90º on opposite edge
|
||||
vecX = { x: vecX.z, y: vecX.y, z: -vecX.x };
|
||||
}
|
||||
else {
|
||||
if (frame.axes.y.left === null) {
|
||||
return {};
|
||||
}
|
||||
offsetX = pos.x - plotLeft;
|
||||
pos.x = frame.axes.y.left.x;
|
||||
pos.z = frame.axes.y.left.z;
|
||||
vecX = frame.axes.y.left.xDir;
|
||||
}
|
||||
}
|
||||
if (positionMode === 'chart') {
|
||||
// Labels preserve their direction relative to the chart
|
||||
// nothing to do
|
||||
}
|
||||
else if (positionMode === 'flap') {
|
||||
// Labels are rotated around the axis direction to face the screen
|
||||
if (!axis.horiz) { // Y Axis
|
||||
vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
|
||||
}
|
||||
else { // X and Z Axis
|
||||
var sin = Math.sin(alpha);
|
||||
var cos = Math.cos(alpha);
|
||||
if (axis.opposite) {
|
||||
sin = -sin;
|
||||
}
|
||||
if (reverseFlap) {
|
||||
sin = -sin;
|
||||
}
|
||||
vecY = { x: vecX.z * sin, y: cos, z: -vecX.x * sin };
|
||||
}
|
||||
}
|
||||
else if (positionMode === 'ortho') {
|
||||
// Labels will be rotated to be ortogonal to the axis
|
||||
if (!axis.horiz) { // Y Axis
|
||||
vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
|
||||
}
|
||||
else { // X and Z Axis
|
||||
var sina = Math.sin(alpha);
|
||||
var cosa = Math.cos(alpha);
|
||||
var sinb = Math.sin(beta);
|
||||
var cosb = Math.cos(beta);
|
||||
var vecZ = { x: sinb * cosa, y: -sina, z: -cosa * cosb };
|
||||
vecY = {
|
||||
x: vecX.y * vecZ.z - vecX.z * vecZ.y,
|
||||
y: vecX.z * vecZ.x - vecX.x * vecZ.z,
|
||||
z: vecX.x * vecZ.y - vecX.y * vecZ.x
|
||||
};
|
||||
var scale = 1 / Math.sqrt(vecY.x * vecY.x + vecY.y * vecY.y + vecY.z * vecY.z);
|
||||
if (reverseFlap) {
|
||||
scale = -scale;
|
||||
}
|
||||
vecY = { x: scale * vecY.x, y: scale * vecY.y, z: scale * vecY.z };
|
||||
}
|
||||
}
|
||||
else { // positionMode == 'offset'
|
||||
// Labels will be skewd to maintain vertical / horizontal offsets
|
||||
// from axis
|
||||
if (!axis.horiz) { // Y Axis
|
||||
vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
|
||||
}
|
||||
else { // X and Z Axis
|
||||
vecY = {
|
||||
x: Math.sin(beta) * Math.sin(alpha),
|
||||
y: Math.cos(alpha),
|
||||
z: -Math.cos(beta) * Math.sin(alpha)
|
||||
};
|
||||
}
|
||||
}
|
||||
pos.x += offsetX * vecX.x + offsetY * vecY.x;
|
||||
pos.y += offsetX * vecX.y + offsetY * vecY.y;
|
||||
pos.z += offsetX * vecX.z + offsetY * vecY.z;
|
||||
var projected = perspective([pos], axis.chart)[0];
|
||||
if (skew) {
|
||||
// Check if the label text would be mirrored
|
||||
var isMirrored = shapeArea(perspective([
|
||||
pos,
|
||||
{ x: pos.x + vecX.x, y: pos.y + vecX.y, z: pos.z + vecX.z },
|
||||
{ x: pos.x + vecY.x, y: pos.y + vecY.y, z: pos.z + vecY.z }
|
||||
], axis.chart)) < 0;
|
||||
if (isMirrored) {
|
||||
vecX = { x: -vecX.x, y: -vecX.y, z: -vecX.z };
|
||||
}
|
||||
var pointsProjected = perspective([
|
||||
{ x: pos.x, y: pos.y, z: pos.z },
|
||||
{ x: pos.x + vecX.x, y: pos.y + vecX.y, z: pos.z + vecX.z },
|
||||
{ x: pos.x + vecY.x, y: pos.y + vecY.y, z: pos.z + vecY.z }
|
||||
], axis.chart);
|
||||
projected.matrix = [
|
||||
pointsProjected[1].x - pointsProjected[0].x,
|
||||
pointsProjected[1].y - pointsProjected[0].y,
|
||||
pointsProjected[2].x - pointsProjected[0].x,
|
||||
pointsProjected[2].y - pointsProjected[0].y,
|
||||
projected.x,
|
||||
projected.y
|
||||
];
|
||||
projected.matrix[4] -= projected.x * projected.matrix[0] +
|
||||
projected.y * projected.matrix[2];
|
||||
projected.matrix[5] -= projected.x * projected.matrix[1] +
|
||||
projected.y * projected.matrix[3];
|
||||
}
|
||||
return projected;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Axis3DAdditions.prototype.swapZ = function (p, insidePlotArea) {
|
||||
var axis = this.axis;
|
||||
if (axis.isZAxis) {
|
||||
var plotLeft = insidePlotArea ? 0 : axis.chart.plotLeft;
|
||||
return {
|
||||
x: plotLeft + p.z,
|
||||
y: p.y,
|
||||
z: p.x - plotLeft
|
||||
};
|
||||
}
|
||||
return p;
|
||||
};
|
||||
return Axis3DAdditions;
|
||||
}());
|
||||
/**
|
||||
* Axis with 3D support.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var Axis3D = /** @class */ (function () {
|
||||
function Axis3D() {
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Extends axis class with 3D support.
|
||||
* @private
|
||||
*/
|
||||
Axis3D.compose = function (AxisClass) {
|
||||
merge(true, AxisClass.defaultOptions, Axis3D.defaultOptions);
|
||||
AxisClass.keepProps.push('axis3D');
|
||||
addEvent(AxisClass, 'init', Axis3D.onInit);
|
||||
addEvent(AxisClass, 'afterSetOptions', Axis3D.onAfterSetOptions);
|
||||
addEvent(AxisClass, 'drawCrosshair', Axis3D.onDrawCrosshair);
|
||||
addEvent(AxisClass, 'destroy', Axis3D.onDestroy);
|
||||
var axisProto = AxisClass.prototype;
|
||||
wrap(axisProto, 'getLinePath', Axis3D.wrapGetLinePath);
|
||||
wrap(axisProto, 'getPlotBandPath', Axis3D.wrapGetPlotBandPath);
|
||||
wrap(axisProto, 'getPlotLinePath', Axis3D.wrapGetPlotLinePath);
|
||||
wrap(axisProto, 'getSlotWidth', Axis3D.wrapGetSlotWidth);
|
||||
wrap(axisProto, 'getTitlePosition', Axis3D.wrapGetTitlePosition);
|
||||
Tick3D.compose(Tick);
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Axis3D.onAfterSetOptions = function () {
|
||||
var axis = this;
|
||||
var chart = axis.chart;
|
||||
var options = axis.options;
|
||||
if (chart.is3d && chart.is3d() && axis.coll !== 'colorAxis') {
|
||||
options.tickWidth = pick(options.tickWidth, 0);
|
||||
options.gridLineWidth = pick(options.gridLineWidth, 1);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Axis3D.onDestroy = function () {
|
||||
['backFrame', 'bottomFrame', 'sideFrame'].forEach(function (prop) {
|
||||
if (this[prop]) {
|
||||
this[prop] = this[prop].destroy();
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Axis3D.onDrawCrosshair = function (e) {
|
||||
var axis = this;
|
||||
if (axis.chart.is3d() &&
|
||||
axis.coll !== 'colorAxis') {
|
||||
if (e.point) {
|
||||
e.point.crosshairPos = axis.isXAxis ?
|
||||
e.point.axisXpos :
|
||||
axis.len - e.point.axisYpos;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Axis3D.onInit = function () {
|
||||
var axis = this;
|
||||
if (!axis.axis3D) {
|
||||
axis.axis3D = new Axis3DAdditions(axis);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Do not draw axislines in 3D.
|
||||
* @private
|
||||
*/
|
||||
Axis3D.wrapGetLinePath = function (proceed) {
|
||||
var axis = this;
|
||||
// Do not do this if the chart is not 3D
|
||||
if (!axis.chart.is3d() || axis.coll === 'colorAxis') {
|
||||
return proceed.apply(axis, [].slice.call(arguments, 1));
|
||||
}
|
||||
return [];
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Axis3D.wrapGetPlotBandPath = function (proceed) {
|
||||
// Do not do this if the chart is not 3D
|
||||
if (!this.chart.is3d() || this.coll === 'colorAxis') {
|
||||
return proceed.apply(this, [].slice.call(arguments, 1));
|
||||
}
|
||||
var args = arguments, from = args[1], to = args[2], path = [], fromPath = this.getPlotLinePath({ value: from }), toPath = this.getPlotLinePath({ value: to });
|
||||
if (fromPath && toPath) {
|
||||
for (var i = 0; i < fromPath.length; i += 2) {
|
||||
var fromStartSeg = fromPath[i], fromEndSeg = fromPath[i + 1], toStartSeg = toPath[i], toEndSeg = toPath[i + 1];
|
||||
if (fromStartSeg[0] === 'M' &&
|
||||
fromEndSeg[0] === 'L' &&
|
||||
toStartSeg[0] === 'M' &&
|
||||
toEndSeg[0] === 'L') {
|
||||
path.push(fromStartSeg, fromEndSeg, toEndSeg,
|
||||
// lineTo instead of moveTo
|
||||
['L', toStartSeg[1], toStartSeg[2]], ['Z']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return path;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Axis3D.wrapGetPlotLinePath = function (proceed) {
|
||||
var axis = this;
|
||||
var axis3D = axis.axis3D;
|
||||
var chart = axis.chart;
|
||||
var path = proceed.apply(axis, [].slice.call(arguments, 1));
|
||||
// Do not do this if the chart is not 3D
|
||||
if (axis.coll === 'colorAxis' ||
|
||||
!chart.chart3d ||
|
||||
!chart.is3d()) {
|
||||
return path;
|
||||
}
|
||||
if (path === null) {
|
||||
return path;
|
||||
}
|
||||
var options3d = chart.options.chart.options3d, d = axis.isZAxis ? chart.plotWidth : options3d.depth, frame = chart.chart3d.frame3d, startSegment = path[0], endSegment = path[1], pArr, pathSegments = [];
|
||||
if (startSegment[0] === 'M' && endSegment[0] === 'L') {
|
||||
pArr = [
|
||||
axis3D.swapZ({ x: startSegment[1], y: startSegment[2], z: 0 }),
|
||||
axis3D.swapZ({ x: startSegment[1], y: startSegment[2], z: d }),
|
||||
axis3D.swapZ({ x: endSegment[1], y: endSegment[2], z: 0 }),
|
||||
axis3D.swapZ({ x: endSegment[1], y: endSegment[2], z: d })
|
||||
];
|
||||
if (!this.horiz) { // Y-Axis
|
||||
if (frame.front.visible) {
|
||||
pathSegments.push(pArr[0], pArr[2]);
|
||||
}
|
||||
if (frame.back.visible) {
|
||||
pathSegments.push(pArr[1], pArr[3]);
|
||||
}
|
||||
if (frame.left.visible) {
|
||||
pathSegments.push(pArr[0], pArr[1]);
|
||||
}
|
||||
if (frame.right.visible) {
|
||||
pathSegments.push(pArr[2], pArr[3]);
|
||||
}
|
||||
}
|
||||
else if (this.isZAxis) { // Z-Axis
|
||||
if (frame.left.visible) {
|
||||
pathSegments.push(pArr[0], pArr[2]);
|
||||
}
|
||||
if (frame.right.visible) {
|
||||
pathSegments.push(pArr[1], pArr[3]);
|
||||
}
|
||||
if (frame.top.visible) {
|
||||
pathSegments.push(pArr[0], pArr[1]);
|
||||
}
|
||||
if (frame.bottom.visible) {
|
||||
pathSegments.push(pArr[2], pArr[3]);
|
||||
}
|
||||
}
|
||||
else { // X-Axis
|
||||
if (frame.front.visible) {
|
||||
pathSegments.push(pArr[0], pArr[2]);
|
||||
}
|
||||
if (frame.back.visible) {
|
||||
pathSegments.push(pArr[1], pArr[3]);
|
||||
}
|
||||
if (frame.top.visible) {
|
||||
pathSegments.push(pArr[0], pArr[1]);
|
||||
}
|
||||
if (frame.bottom.visible) {
|
||||
pathSegments.push(pArr[2], pArr[3]);
|
||||
}
|
||||
}
|
||||
pathSegments = perspective(pathSegments, this.chart, false);
|
||||
}
|
||||
return chart.renderer.toLineSegments(pathSegments);
|
||||
};
|
||||
/**
|
||||
* Wrap getSlotWidth function to calculate individual width value for each
|
||||
* slot (#8042).
|
||||
* @private
|
||||
*/
|
||||
Axis3D.wrapGetSlotWidth = function (proceed, tick) {
|
||||
var axis = this;
|
||||
var chart = axis.chart;
|
||||
var ticks = axis.ticks;
|
||||
var gridGroup = axis.gridGroup;
|
||||
if (axis.categories &&
|
||||
chart.frameShapes &&
|
||||
chart.is3d() &&
|
||||
gridGroup &&
|
||||
tick &&
|
||||
tick.label) {
|
||||
var firstGridLine = gridGroup.element.childNodes[0].getBBox(), frame3DLeft = chart.frameShapes.left.getBBox(), options3d = chart.options.chart.options3d, origin = {
|
||||
x: chart.plotWidth / 2,
|
||||
y: chart.plotHeight / 2,
|
||||
z: options3d.depth / 2,
|
||||
vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0)
|
||||
}, labelPos, prevLabelPos, nextLabelPos, slotWidth, tickId = tick.pos, prevTick = ticks[tickId - 1], nextTick = ticks[tickId + 1];
|
||||
// Check whether the tick is not the first one and previous tick
|
||||
// exists, then calculate position of previous label.
|
||||
if (tickId !== 0 && prevTick && prevTick.label.xy) {
|
||||
prevLabelPos = perspective3D({
|
||||
x: prevTick.label.xy.x,
|
||||
y: prevTick.label.xy.y,
|
||||
z: null
|
||||
}, origin, origin.vd);
|
||||
}
|
||||
// If next label position is defined, then recalculate its position
|
||||
// basing on the perspective.
|
||||
if (nextTick && nextTick.label.xy) {
|
||||
nextLabelPos = perspective3D({
|
||||
x: nextTick.label.xy.x,
|
||||
y: nextTick.label.xy.y,
|
||||
z: null
|
||||
}, origin, origin.vd);
|
||||
}
|
||||
labelPos = {
|
||||
x: tick.label.xy.x,
|
||||
y: tick.label.xy.y,
|
||||
z: null
|
||||
};
|
||||
labelPos = perspective3D(labelPos, origin, origin.vd);
|
||||
// If tick is first one, check whether next label position is
|
||||
// already calculated, then return difference between the first and
|
||||
// the second label. If there is no next label position calculated,
|
||||
// return the difference between the first grid line and left 3d
|
||||
// frame.
|
||||
slotWidth = Math.abs(prevLabelPos ?
|
||||
labelPos.x - prevLabelPos.x : nextLabelPos ?
|
||||
nextLabelPos.x - labelPos.x :
|
||||
firstGridLine.x - frame3DLeft.x);
|
||||
return slotWidth;
|
||||
}
|
||||
return proceed.apply(axis, [].slice.call(arguments, 1));
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Axis3D.wrapGetTitlePosition = function (proceed) {
|
||||
var pos = proceed.apply(this, [].slice.call(arguments, 1));
|
||||
return this.axis3D ?
|
||||
this.axis3D.fix3dPosition(pos, true) :
|
||||
pos;
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @optionparent xAxis
|
||||
*/
|
||||
Axis3D.defaultOptions = {
|
||||
labels: {
|
||||
/**
|
||||
* Defines how the labels are be repositioned according to the 3D
|
||||
* chart orientation.
|
||||
*
|
||||
* - `'offset'`: Maintain a fixed horizontal/vertical distance from
|
||||
* the tick marks, despite the chart orientation. This is the
|
||||
* backwards compatible behavior, and causes skewing of X and Z
|
||||
* axes.
|
||||
*
|
||||
* - `'chart'`: Preserve 3D position relative to the chart. This
|
||||
* looks nice, but hard to read if the text isn't forward-facing.
|
||||
*
|
||||
* - `'flap'`: Rotated text along the axis to compensate for the
|
||||
* chart orientation. This tries to maintain text as legible as
|
||||
* possible on all orientations.
|
||||
*
|
||||
* - `'ortho'`: Rotated text along the axis direction so that the
|
||||
* labels are orthogonal to the axis. This is very similar to
|
||||
* `'flap'`, but prevents skewing the labels (X and Y scaling are
|
||||
* still present).
|
||||
*
|
||||
* @sample highcharts/3d/skewed-labels/
|
||||
* Skewed labels
|
||||
*
|
||||
* @since 5.0.15
|
||||
* @validvalue ['offset', 'chart', 'flap', 'ortho']
|
||||
* @product highcharts
|
||||
* @requires highcharts-3d
|
||||
*/
|
||||
position3d: 'offset',
|
||||
/**
|
||||
* If enabled, the axis labels will skewed to follow the
|
||||
* perspective.
|
||||
*
|
||||
* This will fix overlapping labels and titles, but texts become
|
||||
* less legible due to the distortion.
|
||||
*
|
||||
* The final appearance depends heavily on `labels.position3d`.
|
||||
*
|
||||
* @sample highcharts/3d/skewed-labels/
|
||||
* Skewed labels
|
||||
*
|
||||
* @since 5.0.15
|
||||
* @product highcharts
|
||||
* @requires highcharts-3d
|
||||
*/
|
||||
skew3d: false
|
||||
},
|
||||
title: {
|
||||
/**
|
||||
* Defines how the title is repositioned according to the 3D chart
|
||||
* orientation.
|
||||
*
|
||||
* - `'offset'`: Maintain a fixed horizontal/vertical distance from
|
||||
* the tick marks, despite the chart orientation. This is the
|
||||
* backwards compatible behavior, and causes skewing of X and Z
|
||||
* axes.
|
||||
*
|
||||
* - `'chart'`: Preserve 3D position relative to the chart. This
|
||||
* looks nice, but hard to read if the text isn't forward-facing.
|
||||
*
|
||||
* - `'flap'`: Rotated text along the axis to compensate for the
|
||||
* chart orientation. This tries to maintain text as legible as
|
||||
* possible on all orientations.
|
||||
*
|
||||
* - `'ortho'`: Rotated text along the axis direction so that the
|
||||
* labels are orthogonal to the axis. This is very similar to
|
||||
* `'flap'`, but prevents skewing the labels (X and Y scaling are
|
||||
* still present).
|
||||
*
|
||||
* - `undefined`: Will use the config from `labels.position3d`
|
||||
*
|
||||
* @sample highcharts/3d/skewed-labels/
|
||||
* Skewed labels
|
||||
*
|
||||
* @type {"offset"|"chart"|"flap"|"ortho"|null}
|
||||
* @since 5.0.15
|
||||
* @product highcharts
|
||||
* @requires highcharts-3d
|
||||
*/
|
||||
position3d: null,
|
||||
/**
|
||||
* If enabled, the axis title will skewed to follow the perspective.
|
||||
*
|
||||
* This will fix overlapping labels and titles, but texts become
|
||||
* less legible due to the distortion.
|
||||
*
|
||||
* The final appearance depends heavily on `title.position3d`.
|
||||
*
|
||||
* A `null` value will use the config from `labels.skew3d`.
|
||||
*
|
||||
* @sample highcharts/3d/skewed-labels/
|
||||
* Skewed labels
|
||||
*
|
||||
* @type {boolean|null}
|
||||
* @since 5.0.15
|
||||
* @product highcharts
|
||||
* @requires highcharts-3d
|
||||
*/
|
||||
skew3d: null
|
||||
}
|
||||
};
|
||||
return Axis3D;
|
||||
}());
|
||||
export default Axis3D;
|
||||
@@ -0,0 +1,534 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2009-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis.js';
|
||||
import H from '../Globals.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, find = U.find, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, pick = U.pick;
|
||||
import '../Series/Series.js';
|
||||
import StackItem from '../../Extensions/Stacking.js';
|
||||
var Series = H.Series;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Provides support for broken axes.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var BrokenAxisAdditions = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function BrokenAxisAdditions(axis) {
|
||||
this.hasBreaks = false;
|
||||
this.axis = axis;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
BrokenAxisAdditions.isInBreak = function (brk, val) {
|
||||
var ret, repeat = brk.repeat || Infinity, from = brk.from, length = brk.to - brk.from, test = (val >= from ?
|
||||
(val - from) % repeat :
|
||||
repeat - ((from - val) % repeat));
|
||||
if (!brk.inclusive) {
|
||||
ret = test < length && test !== 0;
|
||||
}
|
||||
else {
|
||||
ret = test <= length;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
BrokenAxisAdditions.lin2Val = function (val) {
|
||||
var axis = this;
|
||||
var brokenAxis = axis.brokenAxis;
|
||||
var breakArray = brokenAxis && brokenAxis.breakArray;
|
||||
if (!breakArray) {
|
||||
return val;
|
||||
}
|
||||
var nval = val, brk, i;
|
||||
for (i = 0; i < breakArray.length; i++) {
|
||||
brk = breakArray[i];
|
||||
if (brk.from >= nval) {
|
||||
break;
|
||||
}
|
||||
else if (brk.to < nval) {
|
||||
nval += brk.len;
|
||||
}
|
||||
else if (BrokenAxisAdditions.isInBreak(brk, nval)) {
|
||||
nval += brk.len;
|
||||
}
|
||||
}
|
||||
return nval;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
BrokenAxisAdditions.val2Lin = function (val) {
|
||||
var axis = this;
|
||||
var brokenAxis = axis.brokenAxis;
|
||||
var breakArray = brokenAxis && brokenAxis.breakArray;
|
||||
if (!breakArray) {
|
||||
return val;
|
||||
}
|
||||
var nval = val, brk, i;
|
||||
for (i = 0; i < breakArray.length; i++) {
|
||||
brk = breakArray[i];
|
||||
if (brk.to <= val) {
|
||||
nval -= brk.len;
|
||||
}
|
||||
else if (brk.from >= val) {
|
||||
break;
|
||||
}
|
||||
else if (BrokenAxisAdditions.isInBreak(brk, val)) {
|
||||
nval -= (val - brk.from);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nval;
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Returns the first break found where the x is larger then break.from and
|
||||
* smaller then break.to.
|
||||
*
|
||||
* @param {number} x
|
||||
* The number which should be within a break.
|
||||
*
|
||||
* @param {Array<Highcharts.XAxisBreaksOptions>} breaks
|
||||
* The array of breaks to search within.
|
||||
*
|
||||
* @return {Highcharts.XAxisBreaksOptions|undefined}
|
||||
* Returns the first break found that matches, returns false if no break is
|
||||
* found.
|
||||
*/
|
||||
BrokenAxisAdditions.prototype.findBreakAt = function (x, breaks) {
|
||||
return find(breaks, function (b) {
|
||||
return b.from < x && x < b.to;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
BrokenAxisAdditions.prototype.isInAnyBreak = function (val, testKeep) {
|
||||
var brokenAxis = this;
|
||||
var axis = brokenAxis.axis;
|
||||
var breaks = axis.options.breaks, i = breaks && breaks.length, inbrk, keep, ret;
|
||||
if (i) {
|
||||
while (i--) {
|
||||
if (BrokenAxisAdditions.isInBreak(breaks[i], val)) {
|
||||
inbrk = true;
|
||||
if (!keep) {
|
||||
keep = pick(breaks[i].showPoints, !axis.isXAxis);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inbrk && testKeep) {
|
||||
ret = inbrk && !keep;
|
||||
}
|
||||
else {
|
||||
ret = inbrk;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
/**
|
||||
* Dynamically set or unset breaks in an axis. This function in lighter than
|
||||
* usin Axis.update, and it also preserves animation.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Axis#setBreaks
|
||||
*
|
||||
* @param {Array<Highcharts.XAxisBreaksOptions>} [breaks]
|
||||
* The breaks to add. When `undefined` it removes existing breaks.
|
||||
*
|
||||
* @param {boolean} [redraw=true]
|
||||
* Whether to redraw the chart immediately.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
BrokenAxisAdditions.prototype.setBreaks = function (breaks, redraw) {
|
||||
var brokenAxis = this;
|
||||
var axis = brokenAxis.axis;
|
||||
var hasBreaks = (isArray(breaks) && !!breaks.length);
|
||||
axis.isDirty = brokenAxis.hasBreaks !== hasBreaks;
|
||||
brokenAxis.hasBreaks = hasBreaks;
|
||||
axis.options.breaks = axis.userOptions.breaks = breaks;
|
||||
axis.forceRedraw = true; // Force recalculation in setScale
|
||||
// Recalculate series related to the axis.
|
||||
axis.series.forEach(function (series) {
|
||||
series.isDirty = true;
|
||||
});
|
||||
if (!hasBreaks && axis.val2lin === BrokenAxisAdditions.val2Lin) {
|
||||
// Revert to prototype functions
|
||||
delete axis.val2lin;
|
||||
delete axis.lin2val;
|
||||
}
|
||||
if (hasBreaks) {
|
||||
axis.userOptions.ordinal = false;
|
||||
axis.lin2val = BrokenAxisAdditions.lin2Val;
|
||||
axis.val2lin = BrokenAxisAdditions.val2Lin;
|
||||
axis.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) {
|
||||
// If trying to set extremes inside a break, extend min to
|
||||
// after, and max to before the break ( #3857 )
|
||||
if (brokenAxis.hasBreaks) {
|
||||
var axisBreak, breaks = this.options.breaks;
|
||||
while ((axisBreak = brokenAxis.findBreakAt(newMin, breaks))) {
|
||||
newMin = axisBreak.to;
|
||||
}
|
||||
while ((axisBreak = brokenAxis.findBreakAt(newMax, breaks))) {
|
||||
newMax = axisBreak.from;
|
||||
}
|
||||
// If both min and max is within the same break.
|
||||
if (newMax < newMin) {
|
||||
newMax = newMin;
|
||||
}
|
||||
}
|
||||
Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments);
|
||||
};
|
||||
axis.setAxisTranslation = function (saveOld) {
|
||||
Axis.prototype.setAxisTranslation.call(this, saveOld);
|
||||
brokenAxis.unitLength = null;
|
||||
if (brokenAxis.hasBreaks) {
|
||||
var breaks = axis.options.breaks || [],
|
||||
// Temporary one:
|
||||
breakArrayT = [], breakArray = [], length = 0, inBrk, repeat, min = axis.userMin || axis.min, max = axis.userMax || axis.max, pointRangePadding = pick(axis.pointRangePadding, 0), start, i;
|
||||
// Min & max check (#4247)
|
||||
breaks.forEach(function (brk) {
|
||||
repeat = brk.repeat || Infinity;
|
||||
if (BrokenAxisAdditions.isInBreak(brk, min)) {
|
||||
min +=
|
||||
(brk.to % repeat) -
|
||||
(min % repeat);
|
||||
}
|
||||
if (BrokenAxisAdditions.isInBreak(brk, max)) {
|
||||
max -=
|
||||
(max % repeat) -
|
||||
(brk.from % repeat);
|
||||
}
|
||||
});
|
||||
// Construct an array holding all breaks in the axis
|
||||
breaks.forEach(function (brk) {
|
||||
start = brk.from;
|
||||
repeat = brk.repeat || Infinity;
|
||||
while (start - repeat > min) {
|
||||
start -= repeat;
|
||||
}
|
||||
while (start < min) {
|
||||
start += repeat;
|
||||
}
|
||||
for (i = start; i < max; i += repeat) {
|
||||
breakArrayT.push({
|
||||
value: i,
|
||||
move: 'in'
|
||||
});
|
||||
breakArrayT.push({
|
||||
value: i + (brk.to - brk.from),
|
||||
move: 'out',
|
||||
size: brk.breakSize
|
||||
});
|
||||
}
|
||||
});
|
||||
breakArrayT.sort(function (a, b) {
|
||||
return ((a.value === b.value) ?
|
||||
((a.move === 'in' ? 0 : 1) -
|
||||
(b.move === 'in' ? 0 : 1)) :
|
||||
a.value - b.value);
|
||||
});
|
||||
// Simplify the breaks
|
||||
inBrk = 0;
|
||||
start = min;
|
||||
breakArrayT.forEach(function (brk) {
|
||||
inBrk += (brk.move === 'in' ? 1 : -1);
|
||||
if (inBrk === 1 && brk.move === 'in') {
|
||||
start = brk.value;
|
||||
}
|
||||
if (inBrk === 0) {
|
||||
breakArray.push({
|
||||
from: start,
|
||||
to: brk.value,
|
||||
len: brk.value - start - (brk.size || 0)
|
||||
});
|
||||
length += brk.value - start - (brk.size || 0);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* HC <= 8 backwards compatibility, used by demo samples.
|
||||
* @deprecated
|
||||
* @private
|
||||
* @requires modules/broken-axis
|
||||
*/
|
||||
axis.breakArray = brokenAxis.breakArray = breakArray;
|
||||
// Used with staticScale, and below the actual axis length,
|
||||
// when breaks are substracted.
|
||||
brokenAxis.unitLength = max - min - length + pointRangePadding;
|
||||
fireEvent(axis, 'afterBreaks');
|
||||
if (axis.staticScale) {
|
||||
axis.transA = axis.staticScale;
|
||||
}
|
||||
else if (brokenAxis.unitLength) {
|
||||
axis.transA *=
|
||||
(max - axis.min + pointRangePadding) /
|
||||
brokenAxis.unitLength;
|
||||
}
|
||||
if (pointRangePadding) {
|
||||
axis.minPixelPadding =
|
||||
axis.transA * axis.minPointOffset;
|
||||
}
|
||||
axis.min = min;
|
||||
axis.max = max;
|
||||
}
|
||||
};
|
||||
}
|
||||
if (pick(redraw, true)) {
|
||||
axis.chart.redraw();
|
||||
}
|
||||
};
|
||||
return BrokenAxisAdditions;
|
||||
}());
|
||||
/**
|
||||
* Axis with support of broken data rows.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var BrokenAxis = /** @class */ (function () {
|
||||
function BrokenAxis() {
|
||||
}
|
||||
/**
|
||||
* Adds support for broken axes.
|
||||
* @private
|
||||
*/
|
||||
BrokenAxis.compose = function (AxisClass, SeriesClass) {
|
||||
AxisClass.keepProps.push('brokenAxis');
|
||||
var seriesProto = Series.prototype;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
seriesProto.drawBreaks = function (axis, keys) {
|
||||
var series = this, points = series.points, breaks, threshold, eventName, y;
|
||||
if (axis && // #5950
|
||||
axis.brokenAxis &&
|
||||
axis.brokenAxis.hasBreaks) {
|
||||
var brokenAxis_1 = axis.brokenAxis;
|
||||
keys.forEach(function (key) {
|
||||
breaks = brokenAxis_1 && brokenAxis_1.breakArray || [];
|
||||
threshold = axis.isXAxis ?
|
||||
axis.min :
|
||||
pick(series.options.threshold, axis.min);
|
||||
points.forEach(function (point) {
|
||||
y = pick(point['stack' + key.toUpperCase()], point[key]);
|
||||
breaks.forEach(function (brk) {
|
||||
if (isNumber(threshold) && isNumber(y)) {
|
||||
eventName = false;
|
||||
if ((threshold < brk.from && y > brk.to) ||
|
||||
(threshold > brk.from && y < brk.from)) {
|
||||
eventName = 'pointBreak';
|
||||
}
|
||||
else if ((threshold < brk.from && y > brk.from && y < brk.to) ||
|
||||
(threshold > brk.from && y > brk.to && y < brk.from)) {
|
||||
eventName = 'pointInBreak';
|
||||
}
|
||||
if (eventName) {
|
||||
fireEvent(axis, eventName, { point: point, brk: brk });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Extend getGraphPath by identifying gaps in the data so that we can
|
||||
* draw a gap in the line or area. This was moved from ordinal axis
|
||||
* module to broken axis module as of #5045.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Series#gappedPath
|
||||
*
|
||||
* @return {Highcharts.SVGPathArray}
|
||||
* Gapped path
|
||||
*/
|
||||
seriesProto.gappedPath = function () {
|
||||
var currentDataGrouping = this.currentDataGrouping, groupingSize = currentDataGrouping && currentDataGrouping.gapSize, gapSize = this.options.gapSize, points = this.points.slice(), i = points.length - 1, yAxis = this.yAxis, stack;
|
||||
/**
|
||||
* Defines when to display a gap in the graph, together with the
|
||||
* [gapUnit](plotOptions.series.gapUnit) option.
|
||||
*
|
||||
* In case when `dataGrouping` is enabled, points can be grouped
|
||||
* into a larger time span. This can make the grouped points to have
|
||||
* a greater distance than the absolute value of `gapSize` property,
|
||||
* which will result in disappearing graph completely. To prevent
|
||||
* this situation the mentioned distance between grouped points is
|
||||
* used instead of previously defined `gapSize`.
|
||||
*
|
||||
* In practice, this option is most often used to visualize gaps in
|
||||
* time series. In a stock chart, intraday data is available for
|
||||
* daytime hours, while gaps will appear in nights and weekends.
|
||||
*
|
||||
* @see [gapUnit](plotOptions.series.gapUnit)
|
||||
* @see [xAxis.breaks](#xAxis.breaks)
|
||||
*
|
||||
* @sample {highstock} stock/plotoptions/series-gapsize/
|
||||
* Setting the gap size to 2 introduces gaps for weekends
|
||||
* in daily datasets.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @product highstock
|
||||
* @requires modules/broken-axis
|
||||
* @apioption plotOptions.series.gapSize
|
||||
*/
|
||||
/**
|
||||
* Together with [gapSize](plotOptions.series.gapSize), this option
|
||||
* defines where to draw gaps in the graph.
|
||||
*
|
||||
* When the `gapUnit` is `"relative"` (default), a gap size of 5
|
||||
* means that if the distance between two points is greater than
|
||||
* 5 times that of the two closest points, the graph will be broken.
|
||||
*
|
||||
* When the `gapUnit` is `"value"`, the gap is based on absolute
|
||||
* axis values, which on a datetime axis is milliseconds. This also
|
||||
* applies to the navigator series that inherits gap options from
|
||||
* the base series.
|
||||
*
|
||||
* @see [gapSize](plotOptions.series.gapSize)
|
||||
*
|
||||
* @type {string}
|
||||
* @default relative
|
||||
* @since 5.0.13
|
||||
* @product highstock
|
||||
* @validvalue ["relative", "value"]
|
||||
* @requires modules/broken-axis
|
||||
* @apioption plotOptions.series.gapUnit
|
||||
*/
|
||||
if (gapSize && i > 0) { // #5008
|
||||
// Gap unit is relative
|
||||
if (this.options.gapUnit !== 'value') {
|
||||
gapSize *= this.basePointRange;
|
||||
}
|
||||
// Setting a new gapSize in case dataGrouping is enabled (#7686)
|
||||
if (groupingSize &&
|
||||
groupingSize > gapSize &&
|
||||
// Except when DG is forced (e.g. from other series)
|
||||
// and has lower granularity than actual points (#11351)
|
||||
groupingSize >= this.basePointRange) {
|
||||
gapSize = groupingSize;
|
||||
}
|
||||
// extension for ordinal breaks
|
||||
var current = void 0, next = void 0;
|
||||
while (i--) {
|
||||
// Reassign next if it is not visible
|
||||
if (!(next && next.visible !== false)) {
|
||||
next = points[i + 1];
|
||||
}
|
||||
current = points[i];
|
||||
// Skip iteration if one of the points is not visible
|
||||
if (next.visible === false || current.visible === false) {
|
||||
continue;
|
||||
}
|
||||
if (next.x - current.x > gapSize) {
|
||||
var xRange = (current.x + next.x) / 2;
|
||||
points.splice(// insert after this one
|
||||
i + 1, 0, {
|
||||
isNull: true,
|
||||
x: xRange
|
||||
});
|
||||
// For stacked chart generate empty stack items, #6546
|
||||
if (yAxis.stacking && this.options.stacking) {
|
||||
stack = yAxis.stacking.stacks[this.stackKey][xRange] =
|
||||
new StackItem(yAxis, yAxis.options
|
||||
.stackLabels, false, xRange, this.stack);
|
||||
stack.total = 0;
|
||||
}
|
||||
}
|
||||
// Assign current to next for the upcoming iteration
|
||||
next = current;
|
||||
}
|
||||
}
|
||||
// Call base method
|
||||
return this.getGraphPath(points);
|
||||
};
|
||||
/* eslint-disable no-invalid-this */
|
||||
addEvent(AxisClass, 'init', function () {
|
||||
var axis = this;
|
||||
if (!axis.brokenAxis) {
|
||||
axis.brokenAxis = new BrokenAxisAdditions(axis);
|
||||
}
|
||||
});
|
||||
addEvent(AxisClass, 'afterInit', function () {
|
||||
if (typeof this.brokenAxis !== 'undefined') {
|
||||
this.brokenAxis.setBreaks(this.options.breaks, false);
|
||||
}
|
||||
});
|
||||
addEvent(AxisClass, 'afterSetTickPositions', function () {
|
||||
var axis = this;
|
||||
var brokenAxis = axis.brokenAxis;
|
||||
if (brokenAxis &&
|
||||
brokenAxis.hasBreaks) {
|
||||
var tickPositions = this.tickPositions, info = this.tickPositions.info, newPositions = [], i;
|
||||
for (i = 0; i < tickPositions.length; i++) {
|
||||
if (!brokenAxis.isInAnyBreak(tickPositions[i])) {
|
||||
newPositions.push(tickPositions[i]);
|
||||
}
|
||||
}
|
||||
this.tickPositions = newPositions;
|
||||
this.tickPositions.info = info;
|
||||
}
|
||||
});
|
||||
// Force Axis to be not-ordinal when breaks are defined
|
||||
addEvent(AxisClass, 'afterSetOptions', function () {
|
||||
if (this.brokenAxis && this.brokenAxis.hasBreaks) {
|
||||
this.options.ordinal = false;
|
||||
}
|
||||
});
|
||||
addEvent(SeriesClass, 'afterGeneratePoints', function () {
|
||||
var _a = this, isDirty = _a.isDirty, connectNulls = _a.options.connectNulls, points = _a.points, xAxis = _a.xAxis, yAxis = _a.yAxis;
|
||||
// Set, or reset visibility of the points. Axis.setBreaks marks the
|
||||
// series as isDirty
|
||||
if (isDirty) {
|
||||
var i = points.length;
|
||||
while (i--) {
|
||||
var point = points[i];
|
||||
// Respect nulls inside the break (#4275)
|
||||
var nullGap = point.y === null && connectNulls === false;
|
||||
var isPointInBreak = (!nullGap && ((xAxis &&
|
||||
xAxis.brokenAxis &&
|
||||
xAxis.brokenAxis.isInAnyBreak(point.x, true)) || (yAxis &&
|
||||
yAxis.brokenAxis &&
|
||||
yAxis.brokenAxis.isInAnyBreak(point.y, true))));
|
||||
// Set point.visible if in any break.
|
||||
// If not in break, reset visible to original value.
|
||||
point.visible = isPointInBreak ?
|
||||
false :
|
||||
point.options.visible !== false;
|
||||
}
|
||||
}
|
||||
});
|
||||
addEvent(SeriesClass, 'afterRender', function drawPointsWrapped() {
|
||||
this.drawBreaks(this.xAxis, ['x']);
|
||||
this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y']));
|
||||
});
|
||||
};
|
||||
return BrokenAxis;
|
||||
}());
|
||||
BrokenAxis.compose(Axis, Series); // @todo remove automatism
|
||||
export default BrokenAxis;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,177 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis.js';
|
||||
import U from '../Utilities.js';
|
||||
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);
|
||||
export default DateTimeAxis;
|
||||
@@ -0,0 +1,884 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2016 Highsoft AS
|
||||
* Authors: Lars A. V. Cabrera
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis.js';
|
||||
import H from '../Globals.js';
|
||||
import O from '../Options.js';
|
||||
var dateFormat = O.dateFormat;
|
||||
import Tick from './Tick.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, defined = U.defined, erase = U.erase, find = U.find, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, timeUnits = U.timeUnits, wrap = U.wrap;
|
||||
var argsToArray = function (args) {
|
||||
return Array.prototype.slice.call(args, 1);
|
||||
}, isObject = function (x) {
|
||||
// Always use strict mode
|
||||
return U.isObject(x, true);
|
||||
}, Chart = H.Chart;
|
||||
var applyGridOptions = function applyGridOptions(axis) {
|
||||
var options = axis.options;
|
||||
// Center-align by default
|
||||
if (!options.labels) {
|
||||
options.labels = {};
|
||||
}
|
||||
options.labels.align = pick(options.labels.align, 'center');
|
||||
// @todo: Check against tickLabelPlacement between/on etc
|
||||
/* Prevents adding the last tick label if the axis is not a category
|
||||
axis.
|
||||
Since numeric labels are normally placed at starts and ends of a
|
||||
range of value, and this module makes the label point at the value,
|
||||
an "extra" label would appear. */
|
||||
if (!axis.categories) {
|
||||
options.showLastLabel = false;
|
||||
}
|
||||
// Prevents rotation of labels when squished, as rotating them would not
|
||||
// help.
|
||||
axis.labelRotation = 0;
|
||||
options.labels.rotation = 0;
|
||||
};
|
||||
/**
|
||||
* 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 grid axes (like in Gantt charts),
|
||||
* it is possible to declare as a list to provide different
|
||||
* formats depending on available space.
|
||||
* For an overview of the replacement codes, see
|
||||
* [dateFormat](/class-reference/Highcharts#dateFormat).
|
||||
*
|
||||
* Defaults to:
|
||||
* ```js
|
||||
* {
|
||||
hour: {
|
||||
list: ['%H:%M', '%H']
|
||||
},
|
||||
day: {
|
||||
list: ['%A, %e. %B', '%a, %e. %b', '%E']
|
||||
},
|
||||
week: {
|
||||
list: ['Week %W', 'W%W']
|
||||
},
|
||||
month: {
|
||||
list: ['%B', '%b', '%o']
|
||||
}
|
||||
},
|
||||
* ```
|
||||
*
|
||||
* @sample {gantt} gantt/demo/left-axis-table
|
||||
* Gantt Chart with custom axis date format.
|
||||
*
|
||||
* @product gantt
|
||||
* @apioption xAxis.dateTimeLabelFormats
|
||||
*/
|
||||
/**
|
||||
* Set grid options for the axis labels. Requires Highcharts Gantt.
|
||||
*
|
||||
* @since 6.2.0
|
||||
* @product gantt
|
||||
* @apioption xAxis.grid
|
||||
*/
|
||||
/**
|
||||
* Enable grid on the axis labels. Defaults to true for Gantt charts.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
* @since 6.2.0
|
||||
* @product gantt
|
||||
* @apioption xAxis.grid.enabled
|
||||
*/
|
||||
/**
|
||||
* Set specific options for each column (or row for horizontal axes) in the
|
||||
* grid. Each extra column/row is its own axis, and the axis options can be set
|
||||
* here.
|
||||
*
|
||||
* @sample gantt/demo/left-axis-table
|
||||
* Left axis as a table
|
||||
*
|
||||
* @type {Array<Highcharts.XAxisOptions>}
|
||||
* @apioption xAxis.grid.columns
|
||||
*/
|
||||
/**
|
||||
* Set border color for the label grid lines.
|
||||
*
|
||||
* @type {Highcharts.ColorString}
|
||||
* @apioption xAxis.grid.borderColor
|
||||
*/
|
||||
/**
|
||||
* Set border width of the label grid lines.
|
||||
*
|
||||
* @type {number}
|
||||
* @default 1
|
||||
* @apioption xAxis.grid.borderWidth
|
||||
*/
|
||||
/**
|
||||
* Set cell height for grid axis labels. By default this is calculated from font
|
||||
* size. This option only applies to horizontal axes.
|
||||
*
|
||||
* @sample gantt/grid-axis/cellheight
|
||||
* Gant chart with custom cell height
|
||||
* @type {number}
|
||||
* @apioption xAxis.grid.cellHeight
|
||||
*/
|
||||
''; // detach doclets above
|
||||
/**
|
||||
* Get the largest label width and height.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Axis#getMaxLabelDimensions
|
||||
*
|
||||
* @param {Highcharts.Dictionary<Highcharts.Tick>} ticks
|
||||
* All the ticks on one axis.
|
||||
*
|
||||
* @param {Array<number|string>} tickPositions
|
||||
* All the tick positions on one axis.
|
||||
*
|
||||
* @return {Highcharts.SizeObject}
|
||||
* Object containing the properties height and width.
|
||||
*
|
||||
* @todo Move this to the generic axis implementation, as it is used there.
|
||||
*/
|
||||
Axis.prototype.getMaxLabelDimensions = function (ticks, tickPositions) {
|
||||
var dimensions = {
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
tickPositions.forEach(function (pos) {
|
||||
var tick = ticks[pos], tickHeight = 0, tickWidth = 0, label;
|
||||
if (isObject(tick)) {
|
||||
label = isObject(tick.label) ? tick.label : {};
|
||||
// Find width and height of tick
|
||||
tickHeight = label.getBBox ? label.getBBox().height : 0;
|
||||
if (label.textStr) {
|
||||
// Set the tickWidth same as the label width after ellipsis
|
||||
// applied #10281
|
||||
tickWidth = Math.round(label.getBBox().width);
|
||||
}
|
||||
// Update the result if width and/or height are larger
|
||||
dimensions.height = Math.max(tickHeight, dimensions.height);
|
||||
dimensions.width = Math.max(tickWidth, dimensions.width);
|
||||
}
|
||||
});
|
||||
return dimensions;
|
||||
};
|
||||
// Adds week date format
|
||||
H.dateFormats.W = function (timestamp) {
|
||||
var d = new this.Date(timestamp);
|
||||
var firstDay = (this.get('Day', d) + 6) % 7;
|
||||
var thursday = new this.Date(d.valueOf());
|
||||
this.set('Date', thursday, this.get('Date', d) - firstDay + 3);
|
||||
var firstThursday = new this.Date(this.get('FullYear', thursday), 0, 1);
|
||||
if (this.get('Day', firstThursday) !== 4) {
|
||||
this.set('Month', d, 0);
|
||||
this.set('Date', d, 1 + (11 - this.get('Day', firstThursday)) % 7);
|
||||
}
|
||||
return (1 +
|
||||
Math.floor((thursday.valueOf() - firstThursday.valueOf()) / 604800000)).toString();
|
||||
};
|
||||
// First letter of the day of the week, e.g. 'M' for 'Monday'.
|
||||
H.dateFormats.E = function (timestamp) {
|
||||
return dateFormat('%a', timestamp, true).charAt(0);
|
||||
};
|
||||
/* eslint-disable no-invalid-this */
|
||||
addEvent(Chart, 'afterSetChartSize', function () {
|
||||
this.axes.forEach(function (axis) {
|
||||
(axis.grid && axis.grid.columns || []).forEach(function (column) {
|
||||
column.setAxisSize();
|
||||
column.setAxisTranslation();
|
||||
});
|
||||
});
|
||||
});
|
||||
// Center tick labels in cells.
|
||||
addEvent(Tick, 'afterGetLabelPosition', function (e) {
|
||||
var tick = this, label = tick.label, axis = tick.axis, reversed = axis.reversed, chart = axis.chart, options = axis.options, gridOptions = options.grid || {}, labelOpts = axis.options.labels, align = labelOpts.align,
|
||||
// verticalAlign is currently not supported for axis.labels.
|
||||
verticalAlign = 'middle', // labelOpts.verticalAlign,
|
||||
side = GridAxis.Side[axis.side], tickmarkOffset = e.tickmarkOffset, tickPositions = axis.tickPositions, tickPos = tick.pos - tickmarkOffset, nextTickPos = (isNumber(tickPositions[e.index + 1]) ?
|
||||
tickPositions[e.index + 1] - tickmarkOffset :
|
||||
axis.max + tickmarkOffset), tickSize = axis.tickSize('tick'), tickWidth = tickSize ? tickSize[0] : 0, crispCorr = tickSize ? tickSize[1] / 2 : 0, labelHeight, lblMetrics, lines, bottom, top, left, right;
|
||||
// Only center tick labels in grid axes
|
||||
if (gridOptions.enabled === true) {
|
||||
// Calculate top and bottom positions of the cell.
|
||||
if (side === 'top') {
|
||||
bottom = axis.top + axis.offset;
|
||||
top = bottom - tickWidth;
|
||||
}
|
||||
else if (side === 'bottom') {
|
||||
top = chart.chartHeight - axis.bottom + axis.offset;
|
||||
bottom = top + tickWidth;
|
||||
}
|
||||
else {
|
||||
bottom = axis.top + axis.len - axis.translate(reversed ? nextTickPos : tickPos);
|
||||
top = axis.top + axis.len - axis.translate(reversed ? tickPos : nextTickPos);
|
||||
}
|
||||
// Calculate left and right positions of the cell.
|
||||
if (side === 'right') {
|
||||
left = chart.chartWidth - axis.right + axis.offset;
|
||||
right = left + tickWidth;
|
||||
}
|
||||
else if (side === 'left') {
|
||||
right = axis.left + axis.offset;
|
||||
left = right - tickWidth;
|
||||
}
|
||||
else {
|
||||
left = Math.round(axis.left + axis.translate(reversed ? nextTickPos : tickPos)) - crispCorr;
|
||||
right = Math.round(axis.left + axis.translate(reversed ? tickPos : nextTickPos)) - crispCorr;
|
||||
}
|
||||
tick.slotWidth = right - left;
|
||||
// Calculate the positioning of the label based on
|
||||
// alignment.
|
||||
e.pos.x = (align === 'left' ?
|
||||
left :
|
||||
align === 'right' ?
|
||||
right :
|
||||
left + ((right - left) / 2) // default to center
|
||||
);
|
||||
e.pos.y = (verticalAlign === 'top' ?
|
||||
top :
|
||||
verticalAlign === 'bottom' ?
|
||||
bottom :
|
||||
top + ((bottom - top) / 2) // default to middle
|
||||
);
|
||||
lblMetrics = chart.renderer.fontMetrics(labelOpts.style.fontSize, label.element);
|
||||
labelHeight = label.getBBox().height;
|
||||
// Adjustment to y position to align the label correctly.
|
||||
// Would be better to have a setter or similar for this.
|
||||
if (!labelOpts.useHTML) {
|
||||
lines = Math.round(labelHeight / lblMetrics.h);
|
||||
e.pos.y += (
|
||||
// Center the label
|
||||
// TODO: why does this actually center the label?
|
||||
((lblMetrics.b - (lblMetrics.h - lblMetrics.f)) / 2) +
|
||||
// Adjust for height of additional lines.
|
||||
-(((lines - 1) * lblMetrics.h) / 2));
|
||||
}
|
||||
else {
|
||||
e.pos.y += (
|
||||
// Readjust yCorr in htmlUpdateTransform
|
||||
lblMetrics.b +
|
||||
// Adjust for height of html label
|
||||
-(labelHeight / 2));
|
||||
}
|
||||
e.pos.x += (axis.horiz && labelOpts.x || 0);
|
||||
}
|
||||
});
|
||||
/* eslint-enable no-invalid-this */
|
||||
/**
|
||||
* Additions for grid axes.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var GridAxisAdditions = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function GridAxisAdditions(axis) {
|
||||
this.axis = axis;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Checks if an axis is the outer axis in its dimension. Since
|
||||
* axes are placed outwards in order, the axis with the highest
|
||||
* index is the outermost axis.
|
||||
*
|
||||
* Example: If there are multiple x-axes at the top of the chart,
|
||||
* this function returns true if the axis supplied is the last
|
||||
* of the x-axes.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @return {boolean}
|
||||
* True if the axis is the outermost axis in its dimension; false if
|
||||
* not.
|
||||
*/
|
||||
GridAxisAdditions.prototype.isOuterAxis = function () {
|
||||
var axis = this.axis;
|
||||
var chart = axis.chart;
|
||||
var columnIndex = axis.grid.columnIndex;
|
||||
var columns = (axis.linkedParent && axis.linkedParent.grid.columns ||
|
||||
axis.grid.columns);
|
||||
var parentAxis = columnIndex ? axis.linkedParent : axis;
|
||||
var thisIndex = -1, lastIndex = 0;
|
||||
chart[axis.coll].forEach(function (otherAxis, index) {
|
||||
if (otherAxis.side === axis.side && !otherAxis.options.isInternal) {
|
||||
lastIndex = index;
|
||||
if (otherAxis === parentAxis) {
|
||||
// Get the index of the axis in question
|
||||
thisIndex = index;
|
||||
}
|
||||
}
|
||||
});
|
||||
return (lastIndex === thisIndex &&
|
||||
(isNumber(columnIndex) ? columns.length === columnIndex : true));
|
||||
};
|
||||
return GridAxisAdditions;
|
||||
}());
|
||||
/**
|
||||
* Axis with grid support.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var GridAxis = /** @class */ (function () {
|
||||
function GridAxis() {
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Extends axis class with grid support.
|
||||
* @private
|
||||
*/
|
||||
GridAxis.compose = function (AxisClass) {
|
||||
Axis.keepProps.push('grid');
|
||||
wrap(AxisClass.prototype, 'unsquish', GridAxis.wrapUnsquish);
|
||||
// Add event handlers
|
||||
addEvent(AxisClass, 'init', GridAxis.onInit);
|
||||
addEvent(AxisClass, 'afterGetOffset', GridAxis.onAfterGetOffset);
|
||||
addEvent(AxisClass, 'afterGetTitlePosition', GridAxis.onAfterGetTitlePosition);
|
||||
addEvent(AxisClass, 'afterInit', GridAxis.onAfterInit);
|
||||
addEvent(AxisClass, 'afterRender', GridAxis.onAfterRender);
|
||||
addEvent(AxisClass, 'afterSetAxisTranslation', GridAxis.onAfterSetAxisTranslation);
|
||||
addEvent(AxisClass, 'afterSetOptions', GridAxis.onAfterSetOptions);
|
||||
addEvent(AxisClass, 'afterSetOptions', GridAxis.onAfterSetOptions2);
|
||||
addEvent(AxisClass, 'afterSetScale', GridAxis.onAfterSetScale);
|
||||
addEvent(AxisClass, 'afterTickSize', GridAxis.onAfterTickSize);
|
||||
addEvent(AxisClass, 'trimTicks', GridAxis.onTrimTicks);
|
||||
addEvent(AxisClass, 'destroy', GridAxis.onDestroy);
|
||||
};
|
||||
/**
|
||||
* Handle columns and getOffset.
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterGetOffset = function () {
|
||||
var grid = this.grid;
|
||||
(grid && grid.columns || []).forEach(function (column) {
|
||||
column.getOffset();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterGetTitlePosition = function (e) {
|
||||
var axis = this;
|
||||
var options = axis.options;
|
||||
var gridOptions = options.grid || {};
|
||||
if (gridOptions.enabled === true) {
|
||||
// compute anchor points for each of the title align options
|
||||
var title = axis.axisTitle, axisHeight = axis.height, horiz = axis.horiz, axisLeft = axis.left, offset = axis.offset, opposite = axis.opposite, _a = axis.options.title, axisTitleOptions = _a === void 0 ? {} : _a, axisTop = axis.top, axisWidth = axis.width;
|
||||
var tickSize = axis.tickSize();
|
||||
var titleWidth = title && title.getBBox().width;
|
||||
var xOption = axisTitleOptions.x || 0;
|
||||
var yOption = axisTitleOptions.y || 0;
|
||||
var titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
|
||||
var titleFontSize = axis.chart.renderer.fontMetrics(axisTitleOptions.style &&
|
||||
axisTitleOptions.style.fontSize, title).f;
|
||||
var crispCorr = tickSize ? tickSize[0] / 2 : 0;
|
||||
// TODO account for alignment
|
||||
// the position in the perpendicular direction of the axis
|
||||
var offAxis = ((horiz ? axisTop + axisHeight : axisLeft) +
|
||||
(horiz ? 1 : -1) * // horizontal axis reverses the margin
|
||||
(opposite ? -1 : 1) * // so does opposite axes
|
||||
crispCorr +
|
||||
(axis.side === GridAxis.Side.bottom ? titleFontSize : 0));
|
||||
e.titlePosition.x = horiz ?
|
||||
axisLeft - titleWidth / 2 - titleMargin + xOption :
|
||||
offAxis + (opposite ? axisWidth : 0) + offset + xOption;
|
||||
e.titlePosition.y = horiz ?
|
||||
(offAxis -
|
||||
(opposite ? axisHeight : 0) +
|
||||
(opposite ? titleFontSize : -titleFontSize) / 2 +
|
||||
offset +
|
||||
yOption) :
|
||||
axisTop - titleMargin + yOption;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterInit = function () {
|
||||
var axis = this;
|
||||
var chart = axis.chart, _a = axis.options.grid, gridOptions = _a === void 0 ? {} : _a, userOptions = axis.userOptions;
|
||||
if (gridOptions.enabled) {
|
||||
applyGridOptions(axis);
|
||||
/* eslint-disable no-invalid-this */
|
||||
// TODO: wrap the axis instead
|
||||
wrap(axis, 'labelFormatter', function (proceed) {
|
||||
var _a = this, axis = _a.axis, value = _a.value;
|
||||
var tickPos = axis.tickPositions;
|
||||
var series = (axis.isLinked ?
|
||||
axis.linkedParent :
|
||||
axis).series[0];
|
||||
var isFirst = value === tickPos[0];
|
||||
var isLast = value === tickPos[tickPos.length - 1];
|
||||
var point = series && find(series.options.data, function (p) {
|
||||
return p[axis.isXAxis ? 'x' : 'y'] === value;
|
||||
});
|
||||
// Make additional properties available for the
|
||||
// formatter
|
||||
this.isFirst = isFirst;
|
||||
this.isLast = isLast;
|
||||
this.point = point;
|
||||
// Call original labelFormatter
|
||||
return proceed.call(this);
|
||||
});
|
||||
/* eslint-enable no-invalid-this */
|
||||
}
|
||||
if (gridOptions.columns) {
|
||||
var columns = axis.grid.columns = [], columnIndex = axis.grid.columnIndex = 0;
|
||||
// Handle columns, each column is a grid axis
|
||||
while (++columnIndex < gridOptions.columns.length) {
|
||||
var columnOptions = merge(userOptions, gridOptions.columns[gridOptions.columns.length - columnIndex - 1], {
|
||||
linkedTo: 0,
|
||||
// Force to behave like category axis
|
||||
type: 'category',
|
||||
// Disable by default the scrollbar on the grid axis
|
||||
scrollbar: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
delete columnOptions.grid.columns; // Prevent recursion
|
||||
var column = new Axis(axis.chart, columnOptions);
|
||||
column.grid.isColumn = true;
|
||||
column.grid.columnIndex = columnIndex;
|
||||
// Remove column axis from chart axes array, and place it
|
||||
// in the columns array.
|
||||
erase(chart.axes, column);
|
||||
erase(chart[axis.coll], column);
|
||||
columns.push(column);
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Draw an extra line on the far side of the outermost axis,
|
||||
* creating floor/roof/wall of a grid. And some padding.
|
||||
* ```
|
||||
* Make this:
|
||||
* (axis.min) __________________________ (axis.max)
|
||||
* | | | | |
|
||||
* Into this:
|
||||
* (axis.min) __________________________ (axis.max)
|
||||
* ___|____|____|____|____|__
|
||||
* ```
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterRender = function () {
|
||||
var axis = this;
|
||||
var grid = axis.grid;
|
||||
var options = axis.options;
|
||||
var renderer = axis.chart.renderer;
|
||||
var gridOptions = options.grid || {};
|
||||
var yStartIndex, yEndIndex, xStartIndex, xEndIndex;
|
||||
if (gridOptions.enabled === true) {
|
||||
// @todo acutual label padding (top, bottom, left, right)
|
||||
axis.maxLabelDimensions = axis.getMaxLabelDimensions(axis.ticks, axis.tickPositions);
|
||||
// Remove right wall before rendering if updating
|
||||
if (axis.rightWall) {
|
||||
axis.rightWall.destroy();
|
||||
}
|
||||
/*
|
||||
Draw an extra axis line on outer axes
|
||||
>
|
||||
Make this: |______|______|______|___
|
||||
|
||||
> _________________________
|
||||
Into this: |______|______|______|__|
|
||||
*/
|
||||
if (axis.grid && axis.grid.isOuterAxis() && axis.axisLine) {
|
||||
var lineWidth = options.lineWidth;
|
||||
if (lineWidth) {
|
||||
var linePath = axis.getLinePath(lineWidth);
|
||||
var startPoint = linePath[0];
|
||||
var endPoint = linePath[1];
|
||||
// Negate distance if top or left axis
|
||||
// Subtract 1px to draw the line at the end of the tick
|
||||
var tickLength = (axis.tickSize('tick') || [1])[0];
|
||||
var distance = (tickLength - 1) * ((axis.side === GridAxis.Side.top ||
|
||||
axis.side === GridAxis.Side.left) ? -1 : 1);
|
||||
// If axis is horizontal, reposition line path vertically
|
||||
if (startPoint[0] === 'M' && endPoint[0] === 'L') {
|
||||
if (axis.horiz) {
|
||||
startPoint[2] += distance;
|
||||
endPoint[2] += distance;
|
||||
}
|
||||
else {
|
||||
// If axis is vertical, reposition line path
|
||||
// horizontally
|
||||
startPoint[1] += distance;
|
||||
endPoint[1] += distance;
|
||||
}
|
||||
}
|
||||
if (!axis.grid.axisLineExtra) {
|
||||
axis.grid.axisLineExtra = renderer
|
||||
.path(linePath)
|
||||
.attr({
|
||||
zIndex: 7
|
||||
})
|
||||
.addClass('highcharts-axis-line')
|
||||
.add(axis.axisGroup);
|
||||
if (!renderer.styledMode) {
|
||||
axis.grid.axisLineExtra.attr({
|
||||
stroke: options.lineColor,
|
||||
'stroke-width': lineWidth
|
||||
});
|
||||
}
|
||||
}
|
||||
else {
|
||||
axis.grid.axisLineExtra.animate({
|
||||
d: linePath
|
||||
});
|
||||
}
|
||||
// show or hide the line depending on
|
||||
// options.showEmpty
|
||||
axis.axisLine[axis.showAxis ? 'show' : 'hide'](true);
|
||||
}
|
||||
}
|
||||
(grid && grid.columns || []).forEach(function (column) {
|
||||
column.render();
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterSetAxisTranslation = function () {
|
||||
var axis = this;
|
||||
var tickInfo = axis.tickPositions && axis.tickPositions.info;
|
||||
var options = axis.options;
|
||||
var gridOptions = options.grid || {};
|
||||
var userLabels = axis.userOptions.labels || {};
|
||||
if (axis.horiz) {
|
||||
if (gridOptions.enabled === true) {
|
||||
axis.series.forEach(function (series) {
|
||||
series.options.pointRange = 0;
|
||||
});
|
||||
}
|
||||
// Lower level time ticks, like hours or minutes, represent
|
||||
// points in time and not ranges. These should be aligned
|
||||
// left in the grid cell by default. The same applies to
|
||||
// years of higher order.
|
||||
if (tickInfo &&
|
||||
options.dateTimeLabelFormats &&
|
||||
options.labels &&
|
||||
!defined(userLabels.align) &&
|
||||
(options.dateTimeLabelFormats[tickInfo.unitName].range === false ||
|
||||
tickInfo.count > 1 // years
|
||||
)) {
|
||||
options.labels.align = 'left';
|
||||
if (!defined(userLabels.x)) {
|
||||
options.labels.x = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Creates a left and right wall on horizontal axes:
|
||||
* - Places leftmost tick at the start of the axis, to create a left
|
||||
* wall
|
||||
* - Ensures that the rightmost tick is at the end of the axis, to
|
||||
* create a right wall.
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterSetOptions = function (e) {
|
||||
var options = this.options, userOptions = e.userOptions, gridAxisOptions, gridOptions = ((options && isObject(options.grid)) ? options.grid : {});
|
||||
if (gridOptions.enabled === true) {
|
||||
// Merge the user options into default grid axis options so
|
||||
// that when a user option is set, it takes presedence.
|
||||
gridAxisOptions = merge(true, {
|
||||
className: ('highcharts-grid-axis ' + (userOptions.className || '')),
|
||||
dateTimeLabelFormats: {
|
||||
hour: {
|
||||
list: ['%H:%M', '%H']
|
||||
},
|
||||
day: {
|
||||
list: ['%A, %e. %B', '%a, %e. %b', '%E']
|
||||
},
|
||||
week: {
|
||||
list: ['Week %W', 'W%W']
|
||||
},
|
||||
month: {
|
||||
list: ['%B', '%b', '%o']
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
borderWidth: 1
|
||||
},
|
||||
labels: {
|
||||
padding: 2,
|
||||
style: {
|
||||
fontSize: '13px'
|
||||
}
|
||||
},
|
||||
margin: 0,
|
||||
title: {
|
||||
text: null,
|
||||
reserveSpace: false,
|
||||
rotation: 0
|
||||
},
|
||||
// In a grid axis, only allow one unit of certain types,
|
||||
// for example we shouln't have one grid cell spanning
|
||||
// two days.
|
||||
units: [[
|
||||
'millisecond',
|
||||
[1, 10, 100]
|
||||
], [
|
||||
'second',
|
||||
[1, 10]
|
||||
], [
|
||||
'minute',
|
||||
[1, 5, 15]
|
||||
], [
|
||||
'hour',
|
||||
[1, 6]
|
||||
], [
|
||||
'day',
|
||||
[1]
|
||||
], [
|
||||
'week',
|
||||
[1]
|
||||
], [
|
||||
'month',
|
||||
[1]
|
||||
], [
|
||||
'year',
|
||||
null
|
||||
]]
|
||||
}, userOptions);
|
||||
// X-axis specific options
|
||||
if (this.coll === 'xAxis') {
|
||||
// For linked axes, tickPixelInterval is used only if
|
||||
// the tickPositioner below doesn't run or returns
|
||||
// undefined (like multiple years)
|
||||
if (defined(userOptions.linkedTo) &&
|
||||
!defined(userOptions.tickPixelInterval)) {
|
||||
gridAxisOptions.tickPixelInterval = 350;
|
||||
}
|
||||
// For the secondary grid axis, use the primary axis'
|
||||
// tick intervals and return ticks one level higher.
|
||||
if (
|
||||
// Check for tick pixel interval in options
|
||||
!defined(userOptions.tickPixelInterval) &&
|
||||
// Only for linked axes
|
||||
defined(userOptions.linkedTo) &&
|
||||
!defined(userOptions.tickPositioner) &&
|
||||
!defined(userOptions.tickInterval)) {
|
||||
gridAxisOptions.tickPositioner = function (min, max) {
|
||||
var parentInfo = (this.linkedParent &&
|
||||
this.linkedParent.tickPositions &&
|
||||
this.linkedParent.tickPositions.info);
|
||||
if (parentInfo) {
|
||||
var unitIdx, count, unitName, i, units = gridAxisOptions.units, unitRange;
|
||||
for (i = 0; i < units.length; i++) {
|
||||
if (units[i][0] ===
|
||||
parentInfo.unitName) {
|
||||
unitIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Get the first allowed count on the next
|
||||
// unit.
|
||||
if (units[unitIdx + 1]) {
|
||||
unitName = units[unitIdx + 1][0];
|
||||
count =
|
||||
(units[unitIdx + 1][1] || [1])[0];
|
||||
// In case the base X axis shows years, make
|
||||
// the secondary axis show ten times the
|
||||
// years (#11427)
|
||||
}
|
||||
else if (parentInfo.unitName === 'year') {
|
||||
unitName = 'year';
|
||||
count = parentInfo.count * 10;
|
||||
}
|
||||
unitRange = timeUnits[unitName];
|
||||
this.tickInterval = unitRange * count;
|
||||
return this.getTimeTicks({
|
||||
unitRange: unitRange,
|
||||
count: count,
|
||||
unitName: unitName
|
||||
}, min, max, this.options.startOfWeek);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
// Now merge the combined options into the axis options
|
||||
merge(true, this.options, gridAxisOptions);
|
||||
if (this.horiz) {
|
||||
/* _________________________
|
||||
Make this: ___|_____|_____|_____|__|
|
||||
^ ^
|
||||
_________________________
|
||||
Into this: |_____|_____|_____|_____|
|
||||
^ ^ */
|
||||
options.minPadding = pick(userOptions.minPadding, 0);
|
||||
options.maxPadding = pick(userOptions.maxPadding, 0);
|
||||
}
|
||||
// If borderWidth is set, then use its value for tick and
|
||||
// line width.
|
||||
if (isNumber(options.grid.borderWidth)) {
|
||||
options.tickWidth = options.lineWidth = gridOptions.borderWidth;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterSetOptions2 = function (e) {
|
||||
var axis = this;
|
||||
var userOptions = e.userOptions;
|
||||
var gridOptions = userOptions && userOptions.grid || {};
|
||||
var columns = gridOptions.columns;
|
||||
// Add column options to the parent axis. Children has their column
|
||||
// options set on init in onGridAxisAfterInit.
|
||||
if (gridOptions.enabled && columns) {
|
||||
merge(true, axis.options, columns[columns.length - 1]);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Handle columns and setScale.
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterSetScale = function () {
|
||||
var axis = this;
|
||||
(axis.grid.columns || []).forEach(function (column) {
|
||||
column.setScale();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Draw vertical axis ticks extra long to create cell floors and roofs.
|
||||
* Overrides the tickLength for vertical axes.
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onAfterTickSize = function (e) {
|
||||
var defaultLeftAxisOptions = Axis.defaultLeftAxisOptions;
|
||||
var _a = this, horiz = _a.horiz, maxLabelDimensions = _a.maxLabelDimensions, _b = _a.options.grid, gridOptions = _b === void 0 ? {} : _b;
|
||||
if (gridOptions.enabled && maxLabelDimensions) {
|
||||
var labelPadding = (Math.abs(defaultLeftAxisOptions.labels.x) * 2);
|
||||
var distance = horiz ?
|
||||
gridOptions.cellHeight || labelPadding + maxLabelDimensions.height :
|
||||
labelPadding + maxLabelDimensions.width;
|
||||
if (isArray(e.tickSize)) {
|
||||
e.tickSize[0] = distance;
|
||||
}
|
||||
else {
|
||||
e.tickSize = [distance, 0];
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onDestroy = function (e) {
|
||||
var grid = this.grid;
|
||||
(grid.columns || []).forEach(function (column) {
|
||||
column.destroy(e.keepEvents);
|
||||
});
|
||||
grid.columns = void 0;
|
||||
};
|
||||
/**
|
||||
* Wraps axis init to draw cell walls on vertical axes.
|
||||
* @private
|
||||
*/
|
||||
GridAxis.onInit = function (e) {
|
||||
var axis = this;
|
||||
var userOptions = e.userOptions || {};
|
||||
var gridOptions = userOptions.grid || {};
|
||||
if (gridOptions.enabled && defined(gridOptions.borderColor)) {
|
||||
userOptions.tickColor = userOptions.lineColor = gridOptions.borderColor;
|
||||
}
|
||||
if (!axis.grid) {
|
||||
axis.grid = new GridAxisAdditions(axis);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Makes tick labels which are usually ignored in a linked axis
|
||||
* displayed if they are within range of linkedParent.min.
|
||||
* ```
|
||||
* _____________________________
|
||||
* | | | | |
|
||||
* Make this: | | 2 | 3 | 4 |
|
||||
* |___|_______|_______|_______|
|
||||
* ^
|
||||
* _____________________________
|
||||
* | | | | |
|
||||
* Into this: | 1 | 2 | 3 | 4 |
|
||||
* |___|_______|_______|_______|
|
||||
* ^
|
||||
* ```
|
||||
* @private
|
||||
* @todo Does this function do what the drawing says? Seems to affect
|
||||
* ticks and not the labels directly?
|
||||
*/
|
||||
GridAxis.onTrimTicks = function () {
|
||||
var axis = this;
|
||||
var options = axis.options;
|
||||
var gridOptions = options.grid || {};
|
||||
var categoryAxis = axis.categories;
|
||||
var tickPositions = axis.tickPositions;
|
||||
var firstPos = tickPositions[0];
|
||||
var lastPos = tickPositions[tickPositions.length - 1];
|
||||
var linkedMin = axis.linkedParent && axis.linkedParent.min;
|
||||
var linkedMax = axis.linkedParent && axis.linkedParent.max;
|
||||
var min = linkedMin || axis.min;
|
||||
var max = linkedMax || axis.max;
|
||||
var tickInterval = axis.tickInterval;
|
||||
var endMoreThanMin = (firstPos < min &&
|
||||
firstPos + tickInterval > min);
|
||||
var startLessThanMax = (lastPos > max &&
|
||||
lastPos - tickInterval < max);
|
||||
if (gridOptions.enabled === true &&
|
||||
!categoryAxis &&
|
||||
(axis.horiz || axis.isLinked)) {
|
||||
if (endMoreThanMin && !options.startOnTick) {
|
||||
tickPositions[0] = min;
|
||||
}
|
||||
if (startLessThanMax && !options.endOnTick) {
|
||||
tickPositions[tickPositions.length - 1] = max;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Avoid altering tickInterval when reserving space.
|
||||
* @private
|
||||
*/
|
||||
GridAxis.wrapUnsquish = function (proceed) {
|
||||
var axis = this;
|
||||
var _a = axis.options.grid, gridOptions = _a === void 0 ? {} : _a;
|
||||
if (gridOptions.enabled === true && axis.categories) {
|
||||
return axis.tickInterval;
|
||||
}
|
||||
return proceed.apply(axis, argsToArray(arguments));
|
||||
};
|
||||
return GridAxis;
|
||||
}());
|
||||
(function (GridAxis) {
|
||||
/**
|
||||
* Enum for which side the axis is on. Maps to axis.side.
|
||||
* @private
|
||||
*/
|
||||
var Side;
|
||||
(function (Side) {
|
||||
Side[Side["top"] = 0] = "top";
|
||||
Side[Side["right"] = 1] = "right";
|
||||
Side[Side["bottom"] = 2] = "bottom";
|
||||
Side[Side["left"] = 3] = "left";
|
||||
})(Side = GridAxis.Side || (GridAxis.Side = {}));
|
||||
})(GridAxis || (GridAxis = {}));
|
||||
GridAxis.compose(Axis);
|
||||
export default GridAxis;
|
||||
@@ -0,0 +1,46 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var HiddenAxis = /** @class */ (function () {
|
||||
function HiddenAxis() {
|
||||
}
|
||||
/**
|
||||
* Augments methods for the x axis in order to hide it completely. Used for
|
||||
* the X axis in gauges
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Highcharts.Axis} axis
|
||||
* Radial axis to augment.
|
||||
*/
|
||||
HiddenAxis.init = function (axis) {
|
||||
axis.getOffset = function () { };
|
||||
axis.redraw = function () {
|
||||
this.isDirty = false; // prevent setting Y axis dirty
|
||||
};
|
||||
axis.render = function () {
|
||||
this.isDirty = false; // prevent setting Y axis dirty
|
||||
};
|
||||
axis.createLabelCollector = function () {
|
||||
return function () {
|
||||
return;
|
||||
};
|
||||
};
|
||||
axis.setScale = function () { };
|
||||
axis.setCategories = function () { };
|
||||
axis.setTitle = function () { };
|
||||
axis.isHidden = true;
|
||||
};
|
||||
return HiddenAxis;
|
||||
}());
|
||||
export default HiddenAxis;
|
||||
@@ -0,0 +1,176 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis.js';
|
||||
import U from '../Utilities.js';
|
||||
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
|
||||
export default LogarithmicAxis;
|
||||
@@ -0,0 +1,157 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, pick = U.pick;
|
||||
/**
|
||||
* Map support for axes.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var MapAxisAdditions = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function MapAxisAdditions(axis) {
|
||||
this.axis = axis;
|
||||
}
|
||||
return MapAxisAdditions;
|
||||
}());
|
||||
/**
|
||||
* Axis with map support.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var MapAxis = /** @class */ (function () {
|
||||
function MapAxis() {
|
||||
}
|
||||
/**
|
||||
* Extends axes with map support.
|
||||
* @private
|
||||
*
|
||||
* @param {Highcharts.Axis} AxisClass
|
||||
* Axis class to extend.
|
||||
*/
|
||||
MapAxis.compose = function (AxisClass) {
|
||||
AxisClass.keepProps.push('mapAxis');
|
||||
/* eslint-disable no-invalid-this */
|
||||
addEvent(AxisClass, 'init', function () {
|
||||
var axis = this;
|
||||
if (!axis.mapAxis) {
|
||||
axis.mapAxis = new MapAxisAdditions(axis);
|
||||
}
|
||||
});
|
||||
// Override to use the extreme coordinates from the SVG shape, not the
|
||||
// data values
|
||||
addEvent(AxisClass, 'getSeriesExtremes', function () {
|
||||
if (!this.mapAxis) {
|
||||
return;
|
||||
}
|
||||
var axis = this;
|
||||
var xData = [];
|
||||
// Remove the xData array and cache it locally so that the proceed
|
||||
// method doesn't use it
|
||||
if (axis.isXAxis) {
|
||||
axis.series.forEach(function (series, i) {
|
||||
if (series.useMapGeometry) {
|
||||
xData[i] = series.xData;
|
||||
series.xData = [];
|
||||
}
|
||||
});
|
||||
axis.mapAxis.seriesXData = xData;
|
||||
}
|
||||
});
|
||||
addEvent(AxisClass, 'afterGetSeriesExtremes', function () {
|
||||
if (!this.mapAxis) {
|
||||
return;
|
||||
}
|
||||
var axis = this;
|
||||
var xData = axis.mapAxis.seriesXData || [];
|
||||
var dataMin, dataMax, useMapGeometry;
|
||||
// Run extremes logic for map and mapline
|
||||
if (axis.isXAxis) {
|
||||
dataMin = pick(axis.dataMin, Number.MAX_VALUE);
|
||||
dataMax = pick(axis.dataMax, -Number.MAX_VALUE);
|
||||
axis.series.forEach(function (series, i) {
|
||||
if (series.useMapGeometry) {
|
||||
dataMin = Math.min(dataMin, pick(series.minX, dataMin));
|
||||
dataMax = Math.max(dataMax, pick(series.maxX, dataMax));
|
||||
series.xData = xData[i]; // Reset xData array
|
||||
useMapGeometry = true;
|
||||
}
|
||||
});
|
||||
if (useMapGeometry) {
|
||||
axis.dataMin = dataMin;
|
||||
axis.dataMax = dataMax;
|
||||
}
|
||||
axis.mapAxis.seriesXData = void 0;
|
||||
}
|
||||
});
|
||||
// Override axis translation to make sure the aspect ratio is always
|
||||
// kept
|
||||
addEvent(AxisClass, 'afterSetAxisTranslation', function () {
|
||||
if (!this.mapAxis) {
|
||||
return;
|
||||
}
|
||||
var axis = this;
|
||||
var chart = axis.chart;
|
||||
var plotRatio = chart.plotWidth / chart.plotHeight;
|
||||
var xAxis = chart.xAxis[0];
|
||||
var mapRatio, adjustedAxisLength, padAxis, fixTo, fixDiff, preserveAspectRatio;
|
||||
// Check for map-like series
|
||||
if (axis.coll === 'yAxis' && typeof xAxis.transA !== 'undefined') {
|
||||
axis.series.forEach(function (series) {
|
||||
if (series.preserveAspectRatio) {
|
||||
preserveAspectRatio = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
// On Y axis, handle both
|
||||
if (preserveAspectRatio) {
|
||||
// Use the same translation for both axes
|
||||
axis.transA = xAxis.transA = Math.min(axis.transA, xAxis.transA);
|
||||
mapRatio = plotRatio / ((xAxis.max - xAxis.min) /
|
||||
(axis.max - axis.min));
|
||||
// What axis to pad to put the map in the middle
|
||||
padAxis = mapRatio < 1 ? axis : xAxis;
|
||||
// Pad it
|
||||
adjustedAxisLength =
|
||||
(padAxis.max - padAxis.min) * padAxis.transA;
|
||||
padAxis.mapAxis.pixelPadding = padAxis.len - adjustedAxisLength;
|
||||
padAxis.minPixelPadding = padAxis.mapAxis.pixelPadding / 2;
|
||||
fixTo = padAxis.mapAxis.fixTo;
|
||||
if (fixTo) {
|
||||
fixDiff = fixTo[1] - padAxis.toValue(fixTo[0], true);
|
||||
fixDiff *= padAxis.transA;
|
||||
if (Math.abs(fixDiff) > padAxis.minPixelPadding ||
|
||||
(padAxis.min === padAxis.dataMin &&
|
||||
padAxis.max === padAxis.dataMax)) { // zooming out again, keep within restricted area
|
||||
fixDiff = 0;
|
||||
}
|
||||
padAxis.minPixelPadding -= fixDiff;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Override Axis.render in order to delete the fixTo prop
|
||||
addEvent(AxisClass, 'render', function () {
|
||||
var axis = this;
|
||||
if (axis.mapAxis) {
|
||||
axis.mapAxis.fixTo = void 0;
|
||||
}
|
||||
});
|
||||
/* eslint-enable no-invalid-this */
|
||||
};
|
||||
return MapAxis;
|
||||
}());
|
||||
MapAxis.compose(Axis); // @todo move to factory functions
|
||||
export default MapAxis;
|
||||
@@ -0,0 +1,164 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Globals.js';
|
||||
var isTouchDevice = H.isTouchDevice;
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, isNumber = U.isNumber, pick = U.pick;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var NavigatorAxisAdditions = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function NavigatorAxisAdditions(axis) {
|
||||
this.axis = axis;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
NavigatorAxisAdditions.prototype.destroy = function () {
|
||||
this.axis = void 0;
|
||||
};
|
||||
/**
|
||||
* Add logic to normalize the zoomed range in order to preserve the pressed
|
||||
* state of range selector buttons
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Axis#toFixedRange
|
||||
* @param {number} [pxMin]
|
||||
* @param {number} [pxMax]
|
||||
* @param {number} [fixedMin]
|
||||
* @param {number} [fixedMax]
|
||||
* @return {*}
|
||||
*/
|
||||
NavigatorAxisAdditions.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) {
|
||||
var navigator = this;
|
||||
var axis = navigator.axis;
|
||||
var chart = axis.chart;
|
||||
var fixedRange = chart && chart.fixedRange, halfPointRange = (axis.pointRange || 0) / 2, newMin = pick(fixedMin, axis.translate(pxMin, true, !axis.horiz)), newMax = pick(fixedMax, axis.translate(pxMax, true, !axis.horiz)), changeRatio = fixedRange && (newMax - newMin) / fixedRange;
|
||||
// Add/remove half point range to/from the extremes (#1172)
|
||||
if (!defined(fixedMin)) {
|
||||
newMin = correctFloat(newMin + halfPointRange);
|
||||
}
|
||||
if (!defined(fixedMax)) {
|
||||
newMax = correctFloat(newMax - halfPointRange);
|
||||
}
|
||||
// If the difference between the fixed range and the actual requested
|
||||
// range is too great, the user is dragging across an ordinal gap, and
|
||||
// we need to release the range selector button.
|
||||
if (changeRatio > 0.7 && changeRatio < 1.3) {
|
||||
if (fixedMax) {
|
||||
newMin = newMax - fixedRange;
|
||||
}
|
||||
else {
|
||||
newMax = newMin + fixedRange;
|
||||
}
|
||||
}
|
||||
if (!isNumber(newMin) || !isNumber(newMax)) { // #1195, #7411
|
||||
newMin = newMax = void 0;
|
||||
}
|
||||
return {
|
||||
min: newMin,
|
||||
max: newMax
|
||||
};
|
||||
};
|
||||
return NavigatorAxisAdditions;
|
||||
}());
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var NavigatorAxis = /** @class */ (function () {
|
||||
function NavigatorAxis() {
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
NavigatorAxis.compose = function (AxisClass) {
|
||||
AxisClass.keepProps.push('navigatorAxis');
|
||||
/* eslint-disable no-invalid-this */
|
||||
addEvent(AxisClass, 'init', function () {
|
||||
var axis = this;
|
||||
if (!axis.navigatorAxis) {
|
||||
axis.navigatorAxis = new NavigatorAxisAdditions(axis);
|
||||
}
|
||||
});
|
||||
// For Stock charts, override selection zooming with some special
|
||||
// features because X axis zooming is already allowed by the Navigator
|
||||
// and Range selector.
|
||||
addEvent(AxisClass, 'zoom', function (e) {
|
||||
var axis = this;
|
||||
var chart = axis.chart;
|
||||
var chartOptions = chart.options;
|
||||
var navigator = chartOptions.navigator;
|
||||
var navigatorAxis = axis.navigatorAxis;
|
||||
var pinchType = chartOptions.chart.pinchType;
|
||||
var rangeSelector = chartOptions.rangeSelector;
|
||||
var zoomType = chartOptions.chart.zoomType;
|
||||
var previousZoom;
|
||||
if (axis.isXAxis && ((navigator && navigator.enabled) ||
|
||||
(rangeSelector && rangeSelector.enabled))) {
|
||||
// For y only zooming, ignore the X axis completely
|
||||
if (zoomType === 'y') {
|
||||
e.zoomed = false;
|
||||
// For xy zooming, record the state of the zoom before zoom
|
||||
// selection, then when the reset button is pressed, revert to
|
||||
// this state. This should apply only if the chart is
|
||||
// initialized with a range (#6612), otherwise zoom all the way
|
||||
// out.
|
||||
}
|
||||
else if (((!isTouchDevice && zoomType === 'xy') ||
|
||||
(isTouchDevice && pinchType === 'xy')) &&
|
||||
axis.options.range) {
|
||||
previousZoom = navigatorAxis.previousZoom;
|
||||
if (defined(e.newMin)) {
|
||||
navigatorAxis.previousZoom = [axis.min, axis.max];
|
||||
}
|
||||
else if (previousZoom) {
|
||||
e.newMin = previousZoom[0];
|
||||
e.newMax = previousZoom[1];
|
||||
navigatorAxis.previousZoom = void 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof e.zoomed !== 'undefined') {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
/* eslint-enable no-invalid-this */
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
NavigatorAxis.AdditionsClass = NavigatorAxisAdditions;
|
||||
return NavigatorAxis;
|
||||
}());
|
||||
export default NavigatorAxis;
|
||||
@@ -0,0 +1,777 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis.js';
|
||||
import H from '../Globals.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, css = U.css, defined = U.defined, pick = U.pick, timeUnits = U.timeUnits;
|
||||
import '../Chart/Chart.js';
|
||||
// Has a dependency on Navigator due to the use of Axis.toFixedRange
|
||||
import '../Navigator.js';
|
||||
import '../Series/Series.js';
|
||||
var Chart = H.Chart, Series = H.Series;
|
||||
/**
|
||||
* Extends the axis with ordinal support.
|
||||
* @private
|
||||
*/
|
||||
var OrdinalAxis;
|
||||
(function (OrdinalAxis) {
|
||||
/* *
|
||||
*
|
||||
* Classes
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
var Composition = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function Composition(axis) {
|
||||
this.index = {};
|
||||
this.axis = axis;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Calculate the ordinal positions before tick positions are calculated.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Composition.prototype.beforeSetTickPositions = function () {
|
||||
var axis = this.axis, ordinal = axis.ordinal, len, ordinalPositions = [], uniqueOrdinalPositions, useOrdinal = false, dist, extremes = axis.getExtremes(), min = extremes.min, max = extremes.max, minIndex, maxIndex, slope, hasBreaks = axis.isXAxis && !!axis.options.breaks, isOrdinal = axis.options.ordinal, overscrollPointsRange = Number.MAX_VALUE, ignoreHiddenSeries = axis.chart.options.chart.ignoreHiddenSeries, i, hasBoostedSeries;
|
||||
// Apply the ordinal logic
|
||||
if (isOrdinal || hasBreaks) { // #4167 YAxis is never ordinal ?
|
||||
axis.series.forEach(function (series, i) {
|
||||
uniqueOrdinalPositions = [];
|
||||
if ((!ignoreHiddenSeries || series.visible !== false) &&
|
||||
(series.takeOrdinalPosition !== false || hasBreaks)) {
|
||||
// concatenate the processed X data into the existing
|
||||
// positions, or the empty array
|
||||
ordinalPositions = ordinalPositions.concat(series.processedXData);
|
||||
len = ordinalPositions.length;
|
||||
// remove duplicates (#1588)
|
||||
ordinalPositions.sort(function (a, b) {
|
||||
// without a custom function it is sorted as strings
|
||||
return a - b;
|
||||
});
|
||||
overscrollPointsRange = Math.min(overscrollPointsRange, pick(
|
||||
// Check for a single-point series:
|
||||
series.closestPointRange, overscrollPointsRange));
|
||||
if (len) {
|
||||
i = 0;
|
||||
while (i < len - 1) {
|
||||
if (ordinalPositions[i] !== ordinalPositions[i + 1]) {
|
||||
uniqueOrdinalPositions.push(ordinalPositions[i + 1]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// Check first item:
|
||||
if (uniqueOrdinalPositions[0] !== ordinalPositions[0]) {
|
||||
uniqueOrdinalPositions.unshift(ordinalPositions[0]);
|
||||
}
|
||||
ordinalPositions = uniqueOrdinalPositions;
|
||||
}
|
||||
}
|
||||
if (series.isSeriesBoosting) {
|
||||
hasBoostedSeries = true;
|
||||
}
|
||||
});
|
||||
if (hasBoostedSeries) {
|
||||
ordinalPositions.length = 0;
|
||||
}
|
||||
// cache the length
|
||||
len = ordinalPositions.length;
|
||||
// Check if we really need the overhead of mapping axis data
|
||||
// against the ordinal positions. If the series consist of
|
||||
// evenly spaced data any way, we don't need any ordinal logic.
|
||||
if (len > 2) { // two points have equal distance by default
|
||||
dist = ordinalPositions[1] - ordinalPositions[0];
|
||||
i = len - 1;
|
||||
while (i-- && !useOrdinal) {
|
||||
if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) {
|
||||
useOrdinal = true;
|
||||
}
|
||||
}
|
||||
// When zooming in on a week, prevent axis padding for
|
||||
// weekends even though the data within the week is evenly
|
||||
// spaced.
|
||||
if (!axis.options.keepOrdinalPadding &&
|
||||
(ordinalPositions[0] - min > dist ||
|
||||
max - ordinalPositions[ordinalPositions.length - 1] >
|
||||
dist)) {
|
||||
useOrdinal = true;
|
||||
}
|
||||
}
|
||||
else if (axis.options.overscroll) {
|
||||
if (len === 2) {
|
||||
// Exactly two points, distance for overscroll is fixed:
|
||||
overscrollPointsRange =
|
||||
ordinalPositions[1] - ordinalPositions[0];
|
||||
}
|
||||
else if (len === 1) {
|
||||
// We have just one point, closest distance is unknown.
|
||||
// Assume then it is last point and overscrolled range:
|
||||
overscrollPointsRange = axis.options.overscroll;
|
||||
ordinalPositions = [
|
||||
ordinalPositions[0],
|
||||
ordinalPositions[0] + overscrollPointsRange
|
||||
];
|
||||
}
|
||||
else {
|
||||
// In case of zooming in on overscrolled range, stick to
|
||||
// the old range:
|
||||
overscrollPointsRange = ordinal.overscrollPointsRange;
|
||||
}
|
||||
}
|
||||
// Record the slope and offset to compute the linear values from
|
||||
// the array index. Since the ordinal positions may exceed the
|
||||
// current range, get the start and end positions within it
|
||||
// (#719, #665b)
|
||||
if (useOrdinal) {
|
||||
if (axis.options.overscroll) {
|
||||
ordinal.overscrollPointsRange = overscrollPointsRange;
|
||||
ordinalPositions = ordinalPositions.concat(ordinal.getOverscrollPositions());
|
||||
}
|
||||
// Register
|
||||
ordinal.positions = ordinalPositions;
|
||||
// This relies on the ordinalPositions being set. Use
|
||||
// Math.max and Math.min to prevent padding on either sides
|
||||
// of the data.
|
||||
minIndex = axis.ordinal2lin(// #5979
|
||||
Math.max(min, ordinalPositions[0]), true);
|
||||
maxIndex = Math.max(axis.ordinal2lin(Math.min(max, ordinalPositions[ordinalPositions.length - 1]), true), 1); // #3339
|
||||
// Set the slope and offset of the values compared to the
|
||||
// indices in the ordinal positions
|
||||
ordinal.slope = slope = (max - min) / (maxIndex - minIndex);
|
||||
ordinal.offset = min - (minIndex * slope);
|
||||
}
|
||||
else {
|
||||
ordinal.overscrollPointsRange = pick(axis.closestPointRange, ordinal.overscrollPointsRange);
|
||||
ordinal.positions = axis.ordinal.slope = ordinal.offset =
|
||||
void 0;
|
||||
}
|
||||
}
|
||||
axis.isOrdinal = isOrdinal && useOrdinal; // #3818, #4196, #4926
|
||||
ordinal.groupIntervalFactor = null; // reset for next run
|
||||
};
|
||||
/**
|
||||
* Get the ordinal positions for the entire data set. This is necessary
|
||||
* in chart panning because we need to find out what points or data
|
||||
* groups are available outside the visible range. When a panning
|
||||
* operation starts, if an index for the given grouping does not exists,
|
||||
* it is created and cached. This index is deleted on updated data, so
|
||||
* it will be regenerated the next time a panning operation starts.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Composition.prototype.getExtendedPositions = function () {
|
||||
var ordinal = this, axis = ordinal.axis, axisProto = axis.constructor.prototype, chart = axis.chart, grouping = axis.series[0].currentDataGrouping, ordinalIndex = ordinal.index, key = grouping ?
|
||||
grouping.count + grouping.unitName :
|
||||
'raw', overscroll = axis.options.overscroll, extremes = axis.getExtremes(), fakeAxis, fakeSeries;
|
||||
// If this is the first time, or the ordinal index is deleted by
|
||||
// updatedData,
|
||||
// create it.
|
||||
if (!ordinalIndex) {
|
||||
ordinalIndex = ordinal.index = {};
|
||||
}
|
||||
if (!ordinalIndex[key]) {
|
||||
// Create a fake axis object where the extended ordinal
|
||||
// positions are emulated
|
||||
fakeAxis = {
|
||||
series: [],
|
||||
chart: chart,
|
||||
getExtremes: function () {
|
||||
return {
|
||||
min: extremes.dataMin,
|
||||
max: extremes.dataMax + overscroll
|
||||
};
|
||||
},
|
||||
options: {
|
||||
ordinal: true
|
||||
},
|
||||
ordinal: {},
|
||||
ordinal2lin: axisProto.ordinal2lin,
|
||||
val2lin: axisProto.val2lin // #2590
|
||||
};
|
||||
fakeAxis.ordinal.axis = fakeAxis;
|
||||
// Add the fake series to hold the full data, then apply
|
||||
// processData to it
|
||||
axis.series.forEach(function (series) {
|
||||
fakeSeries = {
|
||||
xAxis: fakeAxis,
|
||||
xData: series.xData.slice(),
|
||||
chart: chart,
|
||||
destroyGroupedData: H.noop,
|
||||
getProcessedData: H.Series.prototype.getProcessedData
|
||||
};
|
||||
fakeSeries.xData = fakeSeries.xData.concat(ordinal.getOverscrollPositions());
|
||||
fakeSeries.options = {
|
||||
dataGrouping: grouping ? {
|
||||
enabled: true,
|
||||
forced: true,
|
||||
// doesn't matter which, use the fastest
|
||||
approximation: 'open',
|
||||
units: [[
|
||||
grouping.unitName,
|
||||
[grouping.count]
|
||||
]]
|
||||
} : {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
series.processData.apply(fakeSeries);
|
||||
fakeAxis.series.push(fakeSeries);
|
||||
});
|
||||
// Run beforeSetTickPositions to compute the ordinalPositions
|
||||
axis.ordinal.beforeSetTickPositions.apply({ axis: fakeAxis });
|
||||
// Cache it
|
||||
ordinalIndex[key] = fakeAxis.ordinal.positions;
|
||||
}
|
||||
return ordinalIndex[key];
|
||||
};
|
||||
/**
|
||||
* Find the factor to estimate how wide the plot area would have been if
|
||||
* ordinal gaps were included. This value is used to compute an imagined
|
||||
* plot width in order to establish the data grouping interval.
|
||||
*
|
||||
* A real world case is the intraday-candlestick example. Without this
|
||||
* logic, it would show the correct data grouping when viewing a range
|
||||
* within each day, but once moving the range to include the gap between
|
||||
* two days, the interval would include the cut-away night hours and the
|
||||
* data grouping would be wrong. So the below method tries to compensate
|
||||
* by identifying the most common point interval, in this case days.
|
||||
*
|
||||
* An opposite case is presented in issue #718. We have a long array of
|
||||
* daily data, then one point is appended one hour after the last point.
|
||||
* We expect the data grouping not to change.
|
||||
*
|
||||
* In the future, if we find cases where this estimation doesn't work
|
||||
* optimally, we might need to add a second pass to the data grouping
|
||||
* logic, where we do another run with a greater interval if the number
|
||||
* of data groups is more than a certain fraction of the desired group
|
||||
* count.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Composition.prototype.getGroupIntervalFactor = function (xMin, xMax, series) {
|
||||
var ordinal = this, axis = ordinal.axis, i, processedXData = series.processedXData, len = processedXData.length, distances = [], median, groupIntervalFactor = ordinal.groupIntervalFactor;
|
||||
// Only do this computation for the first series, let the other
|
||||
// inherit it (#2416)
|
||||
if (!groupIntervalFactor) {
|
||||
// Register all the distances in an array
|
||||
for (i = 0; i < len - 1; i++) {
|
||||
distances[i] =
|
||||
processedXData[i + 1] - processedXData[i];
|
||||
}
|
||||
// Sort them and find the median
|
||||
distances.sort(function (a, b) {
|
||||
return a - b;
|
||||
});
|
||||
median = distances[Math.floor(len / 2)];
|
||||
// Compensate for series that don't extend through the entire
|
||||
// axis extent. #1675.
|
||||
xMin = Math.max(xMin, processedXData[0]);
|
||||
xMax = Math.min(xMax, processedXData[len - 1]);
|
||||
ordinal.groupIntervalFactor = groupIntervalFactor =
|
||||
(len * median) / (xMax - xMin);
|
||||
}
|
||||
// Return the factor needed for data grouping
|
||||
return groupIntervalFactor;
|
||||
};
|
||||
/**
|
||||
* Get ticks for an ordinal axis within a range where points don't
|
||||
* exist. It is required when overscroll is enabled. We can't base on
|
||||
* points, because we may not have any, so we use approximated
|
||||
* pointRange and generate these ticks between Axis.dataMax,
|
||||
* Axis.dataMax + Axis.overscroll evenly spaced. Used in panning and
|
||||
* navigator scrolling.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Composition.prototype.getOverscrollPositions = function () {
|
||||
var ordinal = this, axis = ordinal.axis, extraRange = axis.options.overscroll, distance = ordinal.overscrollPointsRange, positions = [], max = axis.dataMax;
|
||||
if (defined(distance)) {
|
||||
// Max + pointRange because we need to scroll to the last
|
||||
positions.push(max);
|
||||
while (max <= axis.dataMax + extraRange) {
|
||||
max += distance;
|
||||
positions.push(max);
|
||||
}
|
||||
}
|
||||
return positions;
|
||||
};
|
||||
/**
|
||||
* Make the tick intervals closer because the ordinal gaps make the
|
||||
* ticks spread out or cluster.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Composition.prototype.postProcessTickInterval = function (tickInterval) {
|
||||
// Problem: https://jsfiddle.net/highcharts/FQm4E/1/
|
||||
// This is a case where this algorithm doesn't work optimally. In
|
||||
// this case, the tick labels are spread out per week, but all the
|
||||
// gaps reside within weeks. So we have a situation where the labels
|
||||
// are courser than the ordinal gaps, and thus the tick interval
|
||||
// should not be altered.
|
||||
var ordinal = this, axis = ordinal.axis, ordinalSlope = ordinal.slope, ret;
|
||||
if (ordinalSlope) {
|
||||
if (!axis.options.breaks) {
|
||||
ret = tickInterval / (ordinalSlope / axis.closestPointRange);
|
||||
}
|
||||
else {
|
||||
ret = axis.closestPointRange || tickInterval; // #7275
|
||||
}
|
||||
}
|
||||
else {
|
||||
ret = tickInterval;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
return Composition;
|
||||
}());
|
||||
OrdinalAxis.Composition = Composition;
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Extends the axis with ordinal support.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param AxisClass
|
||||
* Axis class to extend.
|
||||
*
|
||||
* @param ChartClass
|
||||
* Chart class to use.
|
||||
*
|
||||
* @param SeriesClass
|
||||
* Series class to use.
|
||||
*/
|
||||
function compose(AxisClass, ChartClass, SeriesClass) {
|
||||
AxisClass.keepProps.push('ordinal');
|
||||
var axisProto = AxisClass.prototype;
|
||||
/**
|
||||
* In an ordinal axis, there might be areas with dense consentrations of
|
||||
* points, then large gaps between some. Creating equally distributed
|
||||
* ticks over this entire range may lead to a huge number of ticks that
|
||||
* will later be removed. So instead, break the positions up in
|
||||
* segments, find the tick positions for each segment then concatenize
|
||||
* them. This method is used from both data grouping logic and X axis
|
||||
* tick position logic.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
AxisClass.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) {
|
||||
if (positions === void 0) { positions = []; }
|
||||
if (closestDistance === void 0) { closestDistance = 0; }
|
||||
var start = 0, end, segmentPositions, higherRanks = {}, hasCrossedHigherRank, info, posLength, outsideMax, groupPositions = [], lastGroupPosition = -Number.MAX_VALUE, tickPixelIntervalOption = this.options.tickPixelInterval, time = this.chart.time,
|
||||
// Record all the start positions of a segment, to use when
|
||||
// deciding what's a gap in the data.
|
||||
segmentStarts = [];
|
||||
// The positions are not always defined, for example for ordinal
|
||||
// positions when data has regular interval (#1557, #2090)
|
||||
if ((!this.options.ordinal && !this.options.breaks) ||
|
||||
!positions ||
|
||||
positions.length < 3 ||
|
||||
typeof min === 'undefined') {
|
||||
return time.getTimeTicks.apply(time, arguments);
|
||||
}
|
||||
// Analyze the positions array to split it into segments on gaps
|
||||
// larger than 5 times the closest distance. The closest distance is
|
||||
// already found at this point, so we reuse that instead of
|
||||
// computing it again.
|
||||
posLength = positions.length;
|
||||
for (end = 0; end < posLength; end++) {
|
||||
outsideMax = end && positions[end - 1] > max;
|
||||
if (positions[end] < min) { // Set the last position before min
|
||||
start = end;
|
||||
}
|
||||
if (end === posLength - 1 ||
|
||||
positions[end + 1] - positions[end] > closestDistance * 5 ||
|
||||
outsideMax) {
|
||||
// For each segment, calculate the tick positions from the
|
||||
// getTimeTicks utility function. The interval will be the
|
||||
// same regardless of how long the segment is.
|
||||
if (positions[end] > lastGroupPosition) { // #1475
|
||||
segmentPositions = time.getTimeTicks(normalizedInterval, positions[start], positions[end], startOfWeek);
|
||||
// Prevent duplicate groups, for example for multiple
|
||||
// segments within one larger time frame (#1475)
|
||||
while (segmentPositions.length &&
|
||||
segmentPositions[0] <= lastGroupPosition) {
|
||||
segmentPositions.shift();
|
||||
}
|
||||
if (segmentPositions.length) {
|
||||
lastGroupPosition =
|
||||
segmentPositions[segmentPositions.length - 1];
|
||||
}
|
||||
segmentStarts.push(groupPositions.length);
|
||||
groupPositions = groupPositions.concat(segmentPositions);
|
||||
}
|
||||
// Set start of next segment
|
||||
start = end + 1;
|
||||
}
|
||||
if (outsideMax) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Get the grouping info from the last of the segments. The info is
|
||||
// the same for all segments.
|
||||
info = segmentPositions.info;
|
||||
// Optionally identify ticks with higher rank, for example when the
|
||||
// ticks have crossed midnight.
|
||||
if (findHigherRanks && info.unitRange <= timeUnits.hour) {
|
||||
end = groupPositions.length - 1;
|
||||
// Compare points two by two
|
||||
for (start = 1; start < end; start++) {
|
||||
if (time.dateFormat('%d', groupPositions[start]) !==
|
||||
time.dateFormat('%d', groupPositions[start - 1])) {
|
||||
higherRanks[groupPositions[start]] = 'day';
|
||||
hasCrossedHigherRank = true;
|
||||
}
|
||||
}
|
||||
// If the complete array has crossed midnight, we want to mark
|
||||
// the first positions also as higher rank
|
||||
if (hasCrossedHigherRank) {
|
||||
higherRanks[groupPositions[0]] = 'day';
|
||||
}
|
||||
info.higherRanks = higherRanks;
|
||||
}
|
||||
// Save the info
|
||||
info.segmentStarts = segmentStarts;
|
||||
groupPositions.info = info;
|
||||
// Don't show ticks within a gap in the ordinal axis, where the
|
||||
// space between two points is greater than a portion of the tick
|
||||
// pixel interval
|
||||
if (findHigherRanks && defined(tickPixelIntervalOption)) {
|
||||
var length = groupPositions.length, i = length, itemToRemove, translated, translatedArr = [], lastTranslated, medianDistance, distance, distances = [];
|
||||
// Find median pixel distance in order to keep a reasonably even
|
||||
// distance between ticks (#748)
|
||||
while (i--) {
|
||||
translated = this.translate(groupPositions[i]);
|
||||
if (lastTranslated) {
|
||||
distances[i] = lastTranslated - translated;
|
||||
}
|
||||
translatedArr[i] = lastTranslated = translated;
|
||||
}
|
||||
distances.sort();
|
||||
medianDistance = distances[Math.floor(distances.length / 2)];
|
||||
if (medianDistance < tickPixelIntervalOption * 0.6) {
|
||||
medianDistance = null;
|
||||
}
|
||||
// Now loop over again and remove ticks where needed
|
||||
i = groupPositions[length - 1] > max ? length - 1 : length; // #817
|
||||
lastTranslated = void 0;
|
||||
while (i--) {
|
||||
translated = translatedArr[i];
|
||||
distance = Math.abs(lastTranslated - translated);
|
||||
// #4175 - when axis is reversed, the distance, is negative
|
||||
// but tickPixelIntervalOption positive, so we need to
|
||||
// compare the same values
|
||||
// Remove ticks that are closer than 0.6 times the pixel
|
||||
// interval from the one to the right, but not if it is
|
||||
// close to the median distance (#748).
|
||||
if (lastTranslated &&
|
||||
distance < tickPixelIntervalOption * 0.8 &&
|
||||
(medianDistance === null || distance < medianDistance * 0.8)) {
|
||||
// Is this a higher ranked position with a normal
|
||||
// position to the right?
|
||||
if (higherRanks[groupPositions[i]] &&
|
||||
!higherRanks[groupPositions[i + 1]]) {
|
||||
// Yes: remove the lower ranked neighbour to the
|
||||
// right
|
||||
itemToRemove = i + 1;
|
||||
lastTranslated = translated; // #709
|
||||
}
|
||||
else {
|
||||
// No: remove this one
|
||||
itemToRemove = i;
|
||||
}
|
||||
groupPositions.splice(itemToRemove, 1);
|
||||
}
|
||||
else {
|
||||
lastTranslated = translated;
|
||||
}
|
||||
}
|
||||
}
|
||||
return groupPositions;
|
||||
};
|
||||
/**
|
||||
* Translate from linear (internal) to axis value.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Axis#lin2val
|
||||
*
|
||||
* @param {number} val
|
||||
* The linear abstracted value.
|
||||
*
|
||||
* @param {boolean} [fromIndex]
|
||||
* Translate from an index in the ordinal positions rather than a
|
||||
* value.
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
axisProto.lin2val = function (val, fromIndex) {
|
||||
var axis = this, ordinal = axis.ordinal, ordinalPositions = ordinal.positions, ret;
|
||||
// the visible range contains only equally spaced values
|
||||
if (!ordinalPositions) {
|
||||
ret = val;
|
||||
}
|
||||
else {
|
||||
var ordinalSlope = ordinal.slope, ordinalOffset = ordinal.offset, i = ordinalPositions.length - 1, linearEquivalentLeft, linearEquivalentRight, distance;
|
||||
// Handle the case where we translate from the index directly,
|
||||
// used only when panning an ordinal axis
|
||||
if (fromIndex) {
|
||||
if (val < 0) { // out of range, in effect panning to the left
|
||||
val = ordinalPositions[0];
|
||||
}
|
||||
else if (val > i) { // out of range, panning to the right
|
||||
val = ordinalPositions[i];
|
||||
}
|
||||
else { // split it up
|
||||
i = Math.floor(val);
|
||||
distance = val - i; // the decimal
|
||||
}
|
||||
// Loop down along the ordinal positions. When the linear
|
||||
// equivalent of i matches an ordinal position, interpolate
|
||||
// between the left and right values.
|
||||
}
|
||||
else {
|
||||
while (i--) {
|
||||
linearEquivalentLeft =
|
||||
(ordinalSlope * i) + ordinalOffset;
|
||||
if (val >= linearEquivalentLeft) {
|
||||
linearEquivalentRight =
|
||||
(ordinalSlope *
|
||||
(i + 1)) +
|
||||
ordinalOffset;
|
||||
// something between 0 and 1
|
||||
distance = (val - linearEquivalentLeft) /
|
||||
(linearEquivalentRight - linearEquivalentLeft);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the index is within the range of the ordinal positions,
|
||||
// return the associated or interpolated value. If not, just
|
||||
// return the value.
|
||||
return (typeof distance !== 'undefined' &&
|
||||
typeof ordinalPositions[i] !== 'undefined' ?
|
||||
ordinalPositions[i] + (distance ?
|
||||
distance *
|
||||
(ordinalPositions[i + 1] - ordinalPositions[i]) :
|
||||
0) :
|
||||
val);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
/**
|
||||
* Translate from a linear axis value to the corresponding ordinal axis
|
||||
* position. If there are no gaps in the ordinal axis this will be the
|
||||
* same. The translated value is the value that the point would have if
|
||||
* the axis were linear, using the same min and max.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Axis#val2lin
|
||||
*
|
||||
* @param {number} val
|
||||
* The axis value.
|
||||
*
|
||||
* @param {boolean} [toIndex]
|
||||
* Whether to return the index in the ordinalPositions or the new value.
|
||||
*
|
||||
* @return {number}
|
||||
*/
|
||||
axisProto.val2lin = function (val, toIndex) {
|
||||
var axis = this, ordinal = axis.ordinal, ordinalPositions = ordinal.positions, ret;
|
||||
if (!ordinalPositions) {
|
||||
ret = val;
|
||||
}
|
||||
else {
|
||||
var ordinalLength = ordinalPositions.length, i, distance, ordinalIndex;
|
||||
// first look for an exact match in the ordinalpositions array
|
||||
i = ordinalLength;
|
||||
while (i--) {
|
||||
if (ordinalPositions[i] === val) {
|
||||
ordinalIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// if that failed, find the intermediate position between the
|
||||
// two nearest values
|
||||
i = ordinalLength - 1;
|
||||
while (i--) {
|
||||
if (val > ordinalPositions[i] || i === 0) { // interpolate
|
||||
// something between 0 and 1
|
||||
distance = (val - ordinalPositions[i]) /
|
||||
(ordinalPositions[i + 1] - ordinalPositions[i]);
|
||||
ordinalIndex = i + distance;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ret = toIndex ?
|
||||
ordinalIndex :
|
||||
ordinal.slope *
|
||||
(ordinalIndex || 0) +
|
||||
ordinal.offset;
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
// Record this to prevent overwriting by broken-axis module (#5979)
|
||||
axisProto.ordinal2lin = axisProto.val2lin;
|
||||
/* eslint-disable no-invalid-this */
|
||||
addEvent(AxisClass, 'afterInit', function () {
|
||||
var axis = this;
|
||||
if (!axis.ordinal) {
|
||||
axis.ordinal = new OrdinalAxis.Composition(axis);
|
||||
}
|
||||
});
|
||||
addEvent(AxisClass, 'foundExtremes', function () {
|
||||
var axis = this;
|
||||
if (axis.isXAxis &&
|
||||
defined(axis.options.overscroll) &&
|
||||
axis.max === axis.dataMax &&
|
||||
(
|
||||
// Panning is an execption. We don't want to apply
|
||||
// overscroll when panning over the dataMax
|
||||
!axis.chart.mouseIsDown ||
|
||||
axis.isInternal) && (
|
||||
// Scrollbar buttons are the other execption:
|
||||
!axis.eventArgs ||
|
||||
axis.eventArgs && axis.eventArgs.trigger !== 'navigator')) {
|
||||
axis.max += axis.options.overscroll;
|
||||
// Live data and buttons require translation for the min:
|
||||
if (!axis.isInternal && defined(axis.userMin)) {
|
||||
axis.min += axis.options.overscroll;
|
||||
}
|
||||
}
|
||||
});
|
||||
// For ordinal axis, that loads data async, redraw axis after data is
|
||||
// loaded. If we don't do that, axis will have the same extremes as
|
||||
// previously, but ordinal positions won't be calculated. See #10290
|
||||
addEvent(AxisClass, 'afterSetScale', function () {
|
||||
var axis = this;
|
||||
if (axis.horiz && !axis.isDirty) {
|
||||
axis.isDirty = axis.isOrdinal &&
|
||||
axis.chart.navigator &&
|
||||
!axis.chart.navigator.adaptToUpdatedData;
|
||||
}
|
||||
});
|
||||
addEvent(AxisClass, 'initialAxisTranslation', function () {
|
||||
var axis = this;
|
||||
if (axis.ordinal) {
|
||||
axis.ordinal.beforeSetTickPositions();
|
||||
axis.tickInterval = axis.ordinal.postProcessTickInterval(axis.tickInterval);
|
||||
}
|
||||
});
|
||||
// Extending the Chart.pan method for ordinal axes
|
||||
addEvent(ChartClass, 'pan', function (e) {
|
||||
var chart = this, xAxis = chart.xAxis[0], overscroll = xAxis.options.overscroll, chartX = e.originalEvent.chartX, panning = chart.options.chart &&
|
||||
chart.options.chart.panning, runBase = false;
|
||||
if (panning &&
|
||||
panning.type !== 'y' &&
|
||||
xAxis.options.ordinal &&
|
||||
xAxis.series.length) {
|
||||
var mouseDownX = chart.mouseDownX, extremes = xAxis.getExtremes(), dataMax = extremes.dataMax, min = extremes.min, max = extremes.max, trimmedRange, hoverPoints = chart.hoverPoints, closestPointRange = (xAxis.closestPointRange ||
|
||||
(xAxis.ordinal && xAxis.ordinal.overscrollPointsRange)), pointPixelWidth = (xAxis.translationSlope *
|
||||
(xAxis.ordinal.slope || closestPointRange)),
|
||||
// how many ordinal units did we move?
|
||||
movedUnits = (mouseDownX - chartX) / pointPixelWidth,
|
||||
// get index of all the chart's points
|
||||
extendedAxis = { ordinal: { positions: xAxis.ordinal.getExtendedPositions() } }, ordinalPositions, searchAxisLeft, lin2val = xAxis.lin2val, val2lin = xAxis.val2lin, searchAxisRight;
|
||||
// we have an ordinal axis, but the data is equally spaced
|
||||
if (!extendedAxis.ordinal.positions) {
|
||||
runBase = true;
|
||||
}
|
||||
else if (Math.abs(movedUnits) > 1) {
|
||||
// Remove active points for shared tooltip
|
||||
if (hoverPoints) {
|
||||
hoverPoints.forEach(function (point) {
|
||||
point.setState();
|
||||
});
|
||||
}
|
||||
if (movedUnits < 0) {
|
||||
searchAxisLeft = extendedAxis;
|
||||
searchAxisRight = xAxis.ordinal.positions ? xAxis : extendedAxis;
|
||||
}
|
||||
else {
|
||||
searchAxisLeft = xAxis.ordinal.positions ? xAxis : extendedAxis;
|
||||
searchAxisRight = extendedAxis;
|
||||
}
|
||||
// In grouped data series, the last ordinal position
|
||||
// represents the grouped data, which is to the left of the
|
||||
// real data max. If we don't compensate for this, we will
|
||||
// be allowed to pan grouped data series passed the right of
|
||||
// the plot area.
|
||||
ordinalPositions = searchAxisRight.ordinal.positions;
|
||||
if (dataMax >
|
||||
ordinalPositions[ordinalPositions.length - 1]) {
|
||||
ordinalPositions.push(dataMax);
|
||||
}
|
||||
// Get the new min and max values by getting the ordinal
|
||||
// index for the current extreme, then add the moved units
|
||||
// and translate back to values. This happens on the
|
||||
// extended ordinal positions if the new position is out of
|
||||
// range, else it happens on the current x axis which is
|
||||
// smaller and faster.
|
||||
chart.fixedRange = max - min;
|
||||
trimmedRange = xAxis.navigatorAxis.toFixedRange(null, null, lin2val.apply(searchAxisLeft, [
|
||||
val2lin.apply(searchAxisLeft, [min, true]) + movedUnits,
|
||||
true // translate from index
|
||||
]), lin2val.apply(searchAxisRight, [
|
||||
val2lin.apply(searchAxisRight, [max, true]) + movedUnits,
|
||||
true // translate from index
|
||||
]));
|
||||
// Apply it if it is within the available data range
|
||||
if (trimmedRange.min >= Math.min(extremes.dataMin, min) &&
|
||||
trimmedRange.max <= Math.max(dataMax, max) + overscroll) {
|
||||
xAxis.setExtremes(trimmedRange.min, trimmedRange.max, true, false, { trigger: 'pan' });
|
||||
}
|
||||
chart.mouseDownX = chartX; // set new reference for next run
|
||||
css(chart.container, { cursor: 'move' });
|
||||
}
|
||||
}
|
||||
else {
|
||||
runBase = true;
|
||||
}
|
||||
// revert to the linear chart.pan version
|
||||
if (runBase || (panning && /y/.test(panning.type))) {
|
||||
if (overscroll) {
|
||||
xAxis.max = xAxis.dataMax + overscroll;
|
||||
}
|
||||
}
|
||||
else {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
addEvent(SeriesClass, 'updatedData', function () {
|
||||
var xAxis = this.xAxis;
|
||||
// Destroy the extended ordinal index on updated data
|
||||
if (xAxis && xAxis.options.ordinal) {
|
||||
delete xAxis.ordinal.index;
|
||||
}
|
||||
});
|
||||
/* eslint-enable no-invalid-this */
|
||||
}
|
||||
OrdinalAxis.compose = compose;
|
||||
})(OrdinalAxis || (OrdinalAxis = {}));
|
||||
OrdinalAxis.compose(Axis, Chart, Series); // @todo move to StockChart, remove from master
|
||||
export default OrdinalAxis;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,863 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis.js';
|
||||
import Tick from './Tick.js';
|
||||
import HiddenAxis from './HiddenAxis.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, extend = U.extend, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, pick = U.pick, pInt = U.pInt, relativeLength = U.relativeLength, wrap = U.wrap;
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var RadialAxis = /** @class */ (function () {
|
||||
function RadialAxis() {
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
RadialAxis.init = function (axis) {
|
||||
var axisProto = Axis.prototype;
|
||||
// Merge and set options.
|
||||
axis.setOptions = function (userOptions) {
|
||||
var options = this.options = merge(axis.constructor.defaultOptions, this.defaultPolarOptions, userOptions);
|
||||
// Make sure the plotBands array is instanciated for each Axis
|
||||
// (#2649)
|
||||
if (!options.plotBands) {
|
||||
options.plotBands = [];
|
||||
}
|
||||
fireEvent(this, 'afterSetOptions');
|
||||
};
|
||||
// Wrap the getOffset method to return zero offset for title or labels
|
||||
// in a radial axis.
|
||||
axis.getOffset = function () {
|
||||
// Call the Axis prototype method (the method we're in now is on the
|
||||
// instance)
|
||||
axisProto.getOffset.call(this);
|
||||
// Title or label offsets are not counted
|
||||
this.chart.axisOffset[this.side] = 0;
|
||||
};
|
||||
/**
|
||||
* Get the path for the axis line. This method is also referenced in the
|
||||
* getPlotLinePath method.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {number} _lineWidth
|
||||
* Line width is not used.
|
||||
*
|
||||
* @param {number} [radius]
|
||||
* Radius of radial path.
|
||||
*
|
||||
* @param {number} [innerRadius]
|
||||
* Inner radius of radial path.
|
||||
*
|
||||
* @return {RadialAxisPath}
|
||||
*/
|
||||
axis.getLinePath = function (_lineWidth, radius, innerRadius) {
|
||||
var center = this.pane.center, end, chart = this.chart, r = pick(radius, center[2] / 2 - this.offset), path;
|
||||
if (typeof innerRadius === 'undefined') {
|
||||
innerRadius = this.horiz ? 0 : this.center && -this.center[3] / 2;
|
||||
}
|
||||
// In case when innerSize of pane is set, it must be included
|
||||
if (innerRadius) {
|
||||
r += innerRadius;
|
||||
}
|
||||
if (this.isCircular || typeof radius !== 'undefined') {
|
||||
path = this.chart.renderer.symbols.arc(this.left + center[0], this.top + center[1], r, r, {
|
||||
start: this.startAngleRad,
|
||||
end: this.endAngleRad,
|
||||
open: true,
|
||||
innerR: 0
|
||||
});
|
||||
// Bounds used to position the plotLine label next to the line
|
||||
// (#7117)
|
||||
path.xBounds = [this.left + center[0]];
|
||||
path.yBounds = [this.top + center[1] - r];
|
||||
}
|
||||
else {
|
||||
end = this.postTranslate(this.angleRad, r);
|
||||
path = [
|
||||
['M', this.center[0] + chart.plotLeft, this.center[1] + chart.plotTop],
|
||||
['L', end.x, end.y]
|
||||
];
|
||||
}
|
||||
return path;
|
||||
};
|
||||
/**
|
||||
* Override setAxisTranslation by setting the translation to the
|
||||
* difference in rotation. This allows the translate method to return
|
||||
* angle for any given value.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
axis.setAxisTranslation = function () {
|
||||
// Call uber method
|
||||
axisProto.setAxisTranslation.call(this);
|
||||
// Set transA and minPixelPadding
|
||||
if (this.center) { // it's not defined the first time
|
||||
if (this.isCircular) {
|
||||
this.transA = (this.endAngleRad - this.startAngleRad) /
|
||||
((this.max - this.min) || 1);
|
||||
}
|
||||
else {
|
||||
// The transA here is the length of the axis, so in case
|
||||
// of inner radius, the length must be decreased by it
|
||||
this.transA = ((this.center[2] - this.center[3]) / 2) /
|
||||
((this.max - this.min) || 1);
|
||||
}
|
||||
if (this.isXAxis) {
|
||||
this.minPixelPadding = this.transA * this.minPointOffset;
|
||||
}
|
||||
else {
|
||||
// This is a workaround for regression #2593, but categories
|
||||
// still don't position correctly.
|
||||
this.minPixelPadding = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* In case of auto connect, add one closestPointRange to the max value
|
||||
* right before tickPositions are computed, so that ticks will extend
|
||||
* passed the real max.
|
||||
* @private
|
||||
*/
|
||||
axis.beforeSetTickPositions = function () {
|
||||
// If autoConnect is true, polygonal grid lines are connected, and
|
||||
// one closestPointRange is added to the X axis to prevent the last
|
||||
// point from overlapping the first.
|
||||
this.autoConnect = (this.isCircular &&
|
||||
typeof pick(this.userMax, this.options.max) === 'undefined' &&
|
||||
correctFloat(this.endAngleRad - this.startAngleRad) ===
|
||||
correctFloat(2 * Math.PI));
|
||||
// This will lead to add an extra tick to xAxis in order to display
|
||||
// a correct range on inverted polar
|
||||
if (!this.isCircular && this.chart.inverted) {
|
||||
this.max++;
|
||||
}
|
||||
if (this.autoConnect) {
|
||||
this.max += ((this.categories && 1) ||
|
||||
this.pointRange ||
|
||||
this.closestPointRange ||
|
||||
0); // #1197, #2260
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Override the setAxisSize method to use the arc's circumference as
|
||||
* length. This allows tickPixelInterval to apply to pixel lengths along
|
||||
* the perimeter.
|
||||
* @private
|
||||
*/
|
||||
axis.setAxisSize = function () {
|
||||
var center, start;
|
||||
axisProto.setAxisSize.call(this);
|
||||
if (this.isRadial) {
|
||||
// Set the center array
|
||||
this.pane.updateCenter(this);
|
||||
// In case when the innerSize is set in a polar chart, the axis'
|
||||
// center cannot be a reference to pane's center
|
||||
center = this.center = extend([], this.pane.center);
|
||||
// The sector is used in Axis.translate to compute the
|
||||
// translation of reversed axis points (#2570)
|
||||
if (this.isCircular) {
|
||||
this.sector = this.endAngleRad - this.startAngleRad;
|
||||
}
|
||||
else {
|
||||
// When the pane's startAngle or the axis' angle is set then
|
||||
// new x and y values for vertical axis' center must be
|
||||
// calulated
|
||||
start = this.postTranslate(this.angleRad, center[3] / 2);
|
||||
center[0] = start.x - this.chart.plotLeft;
|
||||
center[1] = start.y - this.chart.plotTop;
|
||||
}
|
||||
// Axis len is used to lay out the ticks
|
||||
this.len = this.width = this.height =
|
||||
(center[2] - center[3]) * pick(this.sector, 1) / 2;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Returns the x, y coordinate of a point given by a value and a pixel
|
||||
* distance from center.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {number} value
|
||||
* Point value.
|
||||
*
|
||||
* @param {number} [length]
|
||||
* Distance from center.
|
||||
*
|
||||
* @return {Highcharts.PositionObject}
|
||||
*/
|
||||
axis.getPosition = function (value, length) {
|
||||
var translatedVal = this.translate(value);
|
||||
return this.postTranslate(this.isCircular ? translatedVal : this.angleRad, // #2848
|
||||
// In case when translatedVal is negative, the 0 value must be
|
||||
// used instead, in order to deal with lines and labels that
|
||||
// fall out of the visible range near the center of a pane
|
||||
pick(this.isCircular ?
|
||||
length :
|
||||
(translatedVal < 0 ? 0 : translatedVal), this.center[2] / 2) - this.offset);
|
||||
};
|
||||
/**
|
||||
* Translate from intermediate plotX (angle), plotY (axis.len - radius)
|
||||
* to final chart coordinates.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {number} angle
|
||||
* Translation angle.
|
||||
*
|
||||
* @param {number} radius
|
||||
* Translation radius.
|
||||
*
|
||||
* @return {Highcharts.PositionObject}
|
||||
*/
|
||||
axis.postTranslate = function (angle, radius) {
|
||||
var chart = this.chart, center = this.center;
|
||||
angle = this.startAngleRad + angle;
|
||||
return {
|
||||
x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
|
||||
y: chart.plotTop + center[1] + Math.sin(angle) * radius
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Find the path for plot bands along the radial axis.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {number} from
|
||||
* From value.
|
||||
*
|
||||
* @param {number} to
|
||||
* To value.
|
||||
*
|
||||
* @param {Highcharts.AxisPlotBandsOptions} options
|
||||
* Band options.
|
||||
*
|
||||
* @return {RadialAxisPath}
|
||||
*/
|
||||
axis.getPlotBandPath = function (from, to, options) {
|
||||
var radiusToPixels = function (radius) {
|
||||
if (typeof radius === 'string') {
|
||||
var r = parseInt(radius, 10);
|
||||
if (percentRegex.test(radius)) {
|
||||
r = (r * fullRadius) / 100;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
return radius;
|
||||
};
|
||||
var center = this.center, startAngleRad = this.startAngleRad, fullRadius = center[2] / 2, offset = Math.min(this.offset, 0), percentRegex = /%$/, start, end, angle, xOnPerimeter, open, isCircular = this.isCircular, // X axis in a polar chart
|
||||
path, outerRadius = pick(radiusToPixels(options.outerRadius), fullRadius), innerRadius = radiusToPixels(options.innerRadius), thickness = pick(radiusToPixels(options.thickness), 10);
|
||||
// Polygonal plot bands
|
||||
if (this.options.gridLineInterpolation === 'polygon') {
|
||||
path = this.getPlotLinePath({ value: from }).concat(this.getPlotLinePath({ value: to, reverse: true }));
|
||||
// Circular grid bands
|
||||
}
|
||||
else {
|
||||
// Keep within bounds
|
||||
from = Math.max(from, this.min);
|
||||
to = Math.min(to, this.max);
|
||||
var transFrom = this.translate(from);
|
||||
var transTo = this.translate(to);
|
||||
// Plot bands on Y axis (radial axis) - inner and outer
|
||||
// radius depend on to and from
|
||||
if (!isCircular) {
|
||||
outerRadius = transFrom || 0;
|
||||
innerRadius = transTo || 0;
|
||||
}
|
||||
// Handle full circle
|
||||
if (options.shape === 'circle' || !isCircular) {
|
||||
start = -Math.PI / 2;
|
||||
end = Math.PI * 1.5;
|
||||
open = true;
|
||||
}
|
||||
else {
|
||||
start = startAngleRad + (transFrom || 0);
|
||||
end = startAngleRad + (transTo || 0);
|
||||
}
|
||||
outerRadius -= offset; // #5283
|
||||
thickness -= offset; // #5283
|
||||
path = this.chart.renderer.symbols.arc(this.left + center[0], this.top + center[1], outerRadius, outerRadius, {
|
||||
// Math is for reversed yAxis (#3606)
|
||||
start: Math.min(start, end),
|
||||
end: Math.max(start, end),
|
||||
innerR: pick(innerRadius, outerRadius - thickness),
|
||||
open: open
|
||||
});
|
||||
// Provide positioning boxes for the label (#6406)
|
||||
if (isCircular) {
|
||||
angle = (end + start) / 2;
|
||||
xOnPerimeter = (this.left +
|
||||
center[0] +
|
||||
(center[2] / 2) * Math.cos(angle));
|
||||
path.xBounds = angle > -Math.PI / 2 && angle < Math.PI / 2 ?
|
||||
// Right hemisphere
|
||||
[xOnPerimeter, this.chart.plotWidth] :
|
||||
// Left hemisphere
|
||||
[0, xOnPerimeter];
|
||||
path.yBounds = [
|
||||
this.top + center[1] + (center[2] / 2) * Math.sin(angle)
|
||||
];
|
||||
// Shift up or down to get the label clear of the perimeter
|
||||
path.yBounds[0] += ((angle > -Math.PI && angle < 0) ||
|
||||
(angle > Math.PI)) ? -10 : 10;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
};
|
||||
// Find the correct end values of crosshair in polar.
|
||||
axis.getCrosshairPosition = function (options, x1, y1) {
|
||||
var axis = this, value = options.value, center = axis.pane.center, shapeArgs, end, x2, y2;
|
||||
if (axis.isCircular) {
|
||||
if (!defined(value)) {
|
||||
// When the snap is set to false
|
||||
x2 = options.chartX || 0;
|
||||
y2 = options.chartY || 0;
|
||||
value = axis.translate(Math.atan2(y2 - y1, x2 - x1) - axis.startAngleRad, true);
|
||||
}
|
||||
else if (options.point) {
|
||||
// When the snap is set to true
|
||||
shapeArgs = options.point.shapeArgs || {};
|
||||
if (shapeArgs.start) {
|
||||
// Find a true value of the point based on the
|
||||
// angle
|
||||
value = axis.chart.inverted ?
|
||||
axis.translate(options.point.rectPlotY, true) :
|
||||
options.point.x;
|
||||
}
|
||||
}
|
||||
end = axis.getPosition(value);
|
||||
x2 = end.x;
|
||||
y2 = end.y;
|
||||
}
|
||||
else {
|
||||
if (!defined(value)) {
|
||||
x2 = options.chartX;
|
||||
y2 = options.chartY;
|
||||
}
|
||||
if (defined(x2) && defined(y2)) {
|
||||
// Calculate radius of non-circular axis' crosshair
|
||||
y1 = center[1] + axis.chart.plotTop;
|
||||
value = axis.translate(Math.min(Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)), center[2] / 2) - center[3] / 2, true);
|
||||
}
|
||||
}
|
||||
return [value, x2 || 0, y2 || 0];
|
||||
};
|
||||
// Find the path for plot lines perpendicular to the radial axis.
|
||||
axis.getPlotLinePath = function (options) {
|
||||
var axis = this, center = axis.pane.center, chart = axis.chart, inverted = chart.inverted, value = options.value, reverse = options.reverse, end = axis.getPosition(value), background = axis.pane.options.background ?
|
||||
(axis.pane.options.background[0] ||
|
||||
axis.pane.options.background) :
|
||||
{}, innerRadius = background.innerRadius || '0%', outerRadius = background.outerRadius || '100%', x1 = center[0] + chart.plotLeft, y1 = center[1] + chart.plotTop, x2 = end.x, y2 = end.y, height = axis.height, isCrosshair = options.isCrosshair, paneInnerR = center[3] / 2, innerRatio, distance, a, b, otherAxis, xy, tickPositions, crossPos, path;
|
||||
// Crosshair logic
|
||||
if (isCrosshair) {
|
||||
// Find crosshair's position and perform destructuring
|
||||
// assignment
|
||||
crossPos = this.getCrosshairPosition(options, x1, y1);
|
||||
value = crossPos[0];
|
||||
x2 = crossPos[1];
|
||||
y2 = crossPos[2];
|
||||
}
|
||||
// Spokes
|
||||
if (axis.isCircular) {
|
||||
distance =
|
||||
Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
|
||||
a = (typeof innerRadius === 'string') ?
|
||||
relativeLength(innerRadius, 1) : (innerRadius / distance);
|
||||
b = (typeof outerRadius === 'string') ?
|
||||
relativeLength(outerRadius, 1) : (outerRadius / distance);
|
||||
// To ensure that gridlines won't be displayed in area
|
||||
// defined by innerSize in case of custom radiuses of pane's
|
||||
// background
|
||||
if (center && paneInnerR) {
|
||||
innerRatio = paneInnerR / distance;
|
||||
if (a < innerRatio) {
|
||||
a = innerRatio;
|
||||
}
|
||||
if (b < innerRatio) {
|
||||
b = innerRatio;
|
||||
}
|
||||
}
|
||||
path = [
|
||||
['M', x1 + a * (x2 - x1), y1 - a * (y1 - y2)],
|
||||
['L', x2 - (1 - b) * (x2 - x1), y2 + (1 - b) * (y1 - y2)]
|
||||
];
|
||||
// Concentric circles
|
||||
}
|
||||
else {
|
||||
// Pick the right values depending if it is grid line or
|
||||
// crosshair
|
||||
value = axis.translate(value);
|
||||
// This is required in case when xAxis is non-circular to
|
||||
// prevent grid lines (or crosshairs, if enabled) from
|
||||
// rendering above the center after they supposed to be
|
||||
// displayed below the center point
|
||||
if (value) {
|
||||
if (value < 0 || value > height) {
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
if (axis.options.gridLineInterpolation === 'circle') {
|
||||
// A value of 0 is in the center, so it won't be
|
||||
// visible, but draw it anyway for update and animation
|
||||
// (#2366)
|
||||
path = axis.getLinePath(0, value, paneInnerR);
|
||||
// Concentric polygons
|
||||
}
|
||||
else {
|
||||
path = [];
|
||||
// Find the other axis (a circular one) in the same pane
|
||||
chart[inverted ? 'yAxis' : 'xAxis'].forEach(function (a) {
|
||||
if (a.pane === axis.pane) {
|
||||
otherAxis = a;
|
||||
}
|
||||
});
|
||||
if (otherAxis) {
|
||||
tickPositions = otherAxis.tickPositions;
|
||||
if (otherAxis.autoConnect) {
|
||||
tickPositions =
|
||||
tickPositions.concat([tickPositions[0]]);
|
||||
}
|
||||
// Reverse the positions for concatenation of polygonal
|
||||
// plot bands
|
||||
if (reverse) {
|
||||
tickPositions = tickPositions.slice().reverse();
|
||||
}
|
||||
if (value) {
|
||||
value += paneInnerR;
|
||||
}
|
||||
for (var i = 0; i < tickPositions.length; i++) {
|
||||
xy = otherAxis.getPosition(tickPositions[i], value);
|
||||
path.push(i ? ['L', xy.x, xy.y] : ['M', xy.x, xy.y]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return path;
|
||||
};
|
||||
// Find the position for the axis title, by default inside the gauge.
|
||||
axis.getTitlePosition = function () {
|
||||
var center = this.center, chart = this.chart, titleOptions = this.options.title;
|
||||
return {
|
||||
x: chart.plotLeft + center[0] + (titleOptions.x || 0),
|
||||
y: (chart.plotTop +
|
||||
center[1] -
|
||||
({
|
||||
high: 0.5,
|
||||
middle: 0.25,
|
||||
low: 0
|
||||
}[titleOptions.align] *
|
||||
center[2]) +
|
||||
(titleOptions.y || 0))
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Attach and return collecting function for labels in radial axis for
|
||||
* anti-collision.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @return {Highcharts.ChartLabelCollectorFunction}
|
||||
*/
|
||||
axis.createLabelCollector = function () {
|
||||
var axis = this;
|
||||
return function () {
|
||||
if (axis.isRadial &&
|
||||
axis.tickPositions &&
|
||||
// undocumented option for now, but working
|
||||
axis.options.labels.allowOverlap !== true) {
|
||||
return axis.tickPositions
|
||||
.map(function (pos) {
|
||||
return axis.ticks[pos] && axis.ticks[pos].label;
|
||||
})
|
||||
.filter(function (label) {
|
||||
return Boolean(label);
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Augments methods for the value axis.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Highcharts.Axis} AxisClass
|
||||
* Axis class to extend.
|
||||
*
|
||||
* @param {Highcharts.Tick} TickClass
|
||||
* Tick class to use.
|
||||
*/
|
||||
RadialAxis.compose = function (AxisClass, TickClass) {
|
||||
/* eslint-disable no-invalid-this */
|
||||
// Actions before axis init.
|
||||
addEvent(AxisClass, 'init', function (e) {
|
||||
var axis = this;
|
||||
var chart = axis.chart;
|
||||
var inverted = chart.inverted, angular = chart.angular, polar = chart.polar, isX = axis.isXAxis, coll = axis.coll, isHidden = angular && isX, isCircular, chartOptions = chart.options, paneIndex = e.userOptions.pane || 0, pane = this.pane =
|
||||
chart.pane && chart.pane[paneIndex];
|
||||
// Prevent changes for colorAxis
|
||||
if (coll === 'colorAxis') {
|
||||
this.isRadial = false;
|
||||
return;
|
||||
}
|
||||
// Before prototype.init
|
||||
if (angular) {
|
||||
if (isHidden) {
|
||||
HiddenAxis.init(axis);
|
||||
}
|
||||
else {
|
||||
RadialAxis.init(axis);
|
||||
}
|
||||
isCircular = !isX;
|
||||
if (isCircular) {
|
||||
axis.defaultPolarOptions = RadialAxis.defaultRadialGaugeOptions;
|
||||
}
|
||||
}
|
||||
else if (polar) {
|
||||
RadialAxis.init(axis);
|
||||
// Check which axis is circular
|
||||
isCircular = axis.horiz;
|
||||
axis.defaultPolarOptions = isCircular ?
|
||||
RadialAxis.defaultCircularOptions :
|
||||
merge(coll === 'xAxis' ?
|
||||
AxisClass.defaultOptions :
|
||||
AxisClass.defaultYAxisOptions, RadialAxis.defaultRadialOptions);
|
||||
// Apply the stack labels for yAxis in case of inverted chart
|
||||
if (inverted && coll === 'yAxis') {
|
||||
axis.defaultPolarOptions.stackLabels = AxisClass.defaultYAxisOptions.stackLabels;
|
||||
}
|
||||
}
|
||||
// Disable certain features on angular and polar axes
|
||||
if (angular || polar) {
|
||||
axis.isRadial = true;
|
||||
chartOptions.chart.zoomType = null;
|
||||
if (!axis.labelCollector) {
|
||||
axis.labelCollector = axis.createLabelCollector();
|
||||
}
|
||||
if (axis.labelCollector) {
|
||||
// Prevent overlapping axis labels (#9761)
|
||||
chart.labelCollectors.push(axis.labelCollector);
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.isRadial = false;
|
||||
}
|
||||
// A pointer back to this axis to borrow geometry
|
||||
if (pane && isCircular) {
|
||||
pane.axis = axis;
|
||||
}
|
||||
axis.isCircular = isCircular;
|
||||
});
|
||||
addEvent(AxisClass, 'afterInit', function () {
|
||||
var axis = this;
|
||||
var chart = axis.chart, options = axis.options, isHidden = chart.angular && axis.isXAxis, pane = axis.pane, paneOptions = pane && pane.options;
|
||||
if (!isHidden && pane && (chart.angular || chart.polar)) {
|
||||
// Start and end angle options are given in degrees relative to
|
||||
// top, while internal computations are in radians relative to
|
||||
// right (like SVG).
|
||||
// Y axis in polar charts
|
||||
axis.angleRad = (options.angle || 0) * Math.PI / 180;
|
||||
// Gauges
|
||||
axis.startAngleRad =
|
||||
(paneOptions.startAngle - 90) * Math.PI / 180;
|
||||
axis.endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; // Gauges
|
||||
axis.offset = options.offset || 0;
|
||||
}
|
||||
});
|
||||
// Wrap auto label align to avoid setting axis-wide rotation on radial
|
||||
// axes. (#4920)
|
||||
addEvent(AxisClass, 'autoLabelAlign', function (e) {
|
||||
if (this.isRadial) {
|
||||
e.align = void 0;
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
// Remove label collector function on axis remove/update
|
||||
addEvent(AxisClass, 'destroy', function () {
|
||||
var axis = this;
|
||||
if (axis.chart &&
|
||||
axis.chart.labelCollectors) {
|
||||
var index = (axis.labelCollector ?
|
||||
axis.chart.labelCollectors.indexOf(axis.labelCollector) :
|
||||
-1);
|
||||
if (index >= 0) {
|
||||
axis.chart.labelCollectors.splice(index, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
addEvent(AxisClass, 'initialAxisTranslation', function () {
|
||||
var axis = this;
|
||||
if (axis.isRadial) {
|
||||
axis.beforeSetTickPositions();
|
||||
}
|
||||
});
|
||||
// Add special cases within the Tick class' methods for radial axes.
|
||||
addEvent(TickClass, 'afterGetPosition', function (e) {
|
||||
var tick = this;
|
||||
if (tick.axis.getPosition) {
|
||||
extend(e.pos, tick.axis.getPosition(this.pos));
|
||||
}
|
||||
});
|
||||
// Find the center position of the label based on the distance option.
|
||||
addEvent(TickClass, 'afterGetLabelPosition', function (e) {
|
||||
var tick = this;
|
||||
var axis = tick.axis;
|
||||
var label = tick.label;
|
||||
if (!label) {
|
||||
return;
|
||||
}
|
||||
var labelBBox = label.getBBox(), labelOptions = axis.options.labels, optionsY = labelOptions.y, ret, centerSlot = 20, // 20 degrees to each side at the top and bottom
|
||||
align = labelOptions.align, angle = ((axis.translate(this.pos) + axis.startAngleRad +
|
||||
Math.PI / 2) / Math.PI * 180) % 360, correctAngle = Math.round(angle), labelDir = 'end', // Direction of the label 'start' or 'end'
|
||||
reducedAngle1 = correctAngle < 0 ?
|
||||
correctAngle + 360 : correctAngle, reducedAngle2 = reducedAngle1, translateY = 0, translateX = 0, labelYPosCorrection = labelOptions.y === null ? -labelBBox.height * 0.3 : 0;
|
||||
if (axis.isRadial) { // Both X and Y axes in a polar chart
|
||||
ret = axis.getPosition(this.pos, (axis.center[2] / 2) +
|
||||
relativeLength(pick(labelOptions.distance, -25), axis.center[2] / 2, -axis.center[2] / 2));
|
||||
// Automatically rotated
|
||||
if (labelOptions.rotation === 'auto') {
|
||||
label.attr({
|
||||
rotation: angle
|
||||
});
|
||||
// Vertically centered
|
||||
}
|
||||
else if (optionsY === null) {
|
||||
optionsY = (axis.chart.renderer
|
||||
.fontMetrics(label.styles && label.styles.fontSize).b -
|
||||
labelBBox.height / 2);
|
||||
}
|
||||
// Automatic alignment
|
||||
if (align === null) {
|
||||
if (axis.isCircular) { // Y axis
|
||||
if (labelBBox.width >
|
||||
axis.len * axis.tickInterval / (axis.max - axis.min)) { // #3506
|
||||
centerSlot = 0;
|
||||
}
|
||||
if (angle > centerSlot && angle < 180 - centerSlot) {
|
||||
align = 'left'; // right hemisphere
|
||||
}
|
||||
else if (angle > 180 + centerSlot &&
|
||||
angle < 360 - centerSlot) {
|
||||
align = 'right'; // left hemisphere
|
||||
}
|
||||
else {
|
||||
align = 'center'; // top or bottom
|
||||
}
|
||||
}
|
||||
else {
|
||||
align = 'center';
|
||||
}
|
||||
label.attr({
|
||||
align: align
|
||||
});
|
||||
}
|
||||
// Auto alignment for solid-gauges with two labels (#10635)
|
||||
if (align === 'auto' &&
|
||||
axis.tickPositions.length === 2 &&
|
||||
axis.isCircular) {
|
||||
// Angles reduced to 0 - 90 or 180 - 270
|
||||
if (reducedAngle1 > 90 && reducedAngle1 < 180) {
|
||||
reducedAngle1 = 180 - reducedAngle1;
|
||||
}
|
||||
else if (reducedAngle1 > 270 && reducedAngle1 <= 360) {
|
||||
reducedAngle1 = 540 - reducedAngle1;
|
||||
}
|
||||
// Angles reduced to 0 - 180
|
||||
if (reducedAngle2 > 180 && reducedAngle2 <= 360) {
|
||||
reducedAngle2 = 360 - reducedAngle2;
|
||||
}
|
||||
if ((axis.pane.options.startAngle === correctAngle) ||
|
||||
(axis.pane.options.startAngle === correctAngle + 360) ||
|
||||
(axis.pane.options.startAngle === correctAngle - 360)) {
|
||||
labelDir = 'start';
|
||||
}
|
||||
if ((correctAngle >= -90 && correctAngle <= 90) ||
|
||||
(correctAngle >= -360 && correctAngle <= -270) ||
|
||||
(correctAngle >= 270 && correctAngle <= 360)) {
|
||||
align = (labelDir === 'start') ? 'right' : 'left';
|
||||
}
|
||||
else {
|
||||
align = (labelDir === 'start') ? 'left' : 'right';
|
||||
}
|
||||
// For angles beetwen (90 + n * 180) +- 20
|
||||
if (reducedAngle2 > 70 && reducedAngle2 < 110) {
|
||||
align = 'center';
|
||||
}
|
||||
// auto Y translation
|
||||
if (reducedAngle1 < 15 ||
|
||||
(reducedAngle1 >= 180 && reducedAngle1 < 195)) {
|
||||
translateY = labelBBox.height * 0.3;
|
||||
}
|
||||
else if (reducedAngle1 >= 15 && reducedAngle1 <= 35) {
|
||||
translateY = labelDir === 'start' ?
|
||||
0 : labelBBox.height * 0.75;
|
||||
}
|
||||
else if (reducedAngle1 >= 195 && reducedAngle1 <= 215) {
|
||||
translateY = labelDir === 'start' ?
|
||||
labelBBox.height * 0.75 : 0;
|
||||
}
|
||||
else if (reducedAngle1 > 35 && reducedAngle1 <= 90) {
|
||||
translateY = labelDir === 'start' ?
|
||||
-labelBBox.height * 0.25 : labelBBox.height;
|
||||
}
|
||||
else if (reducedAngle1 > 215 && reducedAngle1 <= 270) {
|
||||
translateY = labelDir === 'start' ?
|
||||
labelBBox.height : -labelBBox.height * 0.25;
|
||||
}
|
||||
// auto X translation
|
||||
if (reducedAngle2 < 15) {
|
||||
translateX = labelDir === 'start' ?
|
||||
-labelBBox.height * 0.15 : labelBBox.height * 0.15;
|
||||
}
|
||||
else if (reducedAngle2 > 165 && reducedAngle2 <= 180) {
|
||||
translateX = labelDir === 'start' ?
|
||||
labelBBox.height * 0.15 : -labelBBox.height * 0.15;
|
||||
}
|
||||
label.attr({ align: align });
|
||||
label.translate(translateX, translateY + labelYPosCorrection);
|
||||
}
|
||||
e.pos.x = ret.x + labelOptions.x;
|
||||
e.pos.y = ret.y + optionsY;
|
||||
}
|
||||
});
|
||||
// Wrap the getMarkPath function to return the path of the radial marker
|
||||
wrap(TickClass.prototype, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {
|
||||
var tick = this;
|
||||
var axis = tick.axis;
|
||||
var endPoint, ret;
|
||||
if (axis.isRadial) {
|
||||
endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
|
||||
ret = [
|
||||
'M',
|
||||
x,
|
||||
y,
|
||||
'L',
|
||||
endPoint.x,
|
||||
endPoint.y
|
||||
];
|
||||
}
|
||||
else {
|
||||
ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Circular axis around the perimeter of a polar chart.
|
||||
* @private
|
||||
*/
|
||||
RadialAxis.defaultCircularOptions = {
|
||||
gridLineWidth: 1,
|
||||
labels: {
|
||||
align: null,
|
||||
distance: 15,
|
||||
x: 0,
|
||||
y: null,
|
||||
style: {
|
||||
textOverflow: 'none' // wrap lines by default (#7248)
|
||||
}
|
||||
},
|
||||
maxPadding: 0,
|
||||
minPadding: 0,
|
||||
showLastLabel: false,
|
||||
tickLength: 0
|
||||
};
|
||||
/**
|
||||
* The default options extend defaultYAxisOptions.
|
||||
* @private
|
||||
*/
|
||||
RadialAxis.defaultRadialGaugeOptions = {
|
||||
labels: {
|
||||
align: 'center',
|
||||
x: 0,
|
||||
y: null // auto
|
||||
},
|
||||
minorGridLineWidth: 0,
|
||||
minorTickInterval: 'auto',
|
||||
minorTickLength: 10,
|
||||
minorTickPosition: 'inside',
|
||||
minorTickWidth: 1,
|
||||
tickLength: 10,
|
||||
tickPosition: 'inside',
|
||||
tickWidth: 2,
|
||||
title: {
|
||||
rotation: 0
|
||||
},
|
||||
zIndex: 2 // behind dials, points in the series group
|
||||
};
|
||||
/**
|
||||
* Radial axis, like a spoke in a polar chart.
|
||||
* @private
|
||||
*/
|
||||
RadialAxis.defaultRadialOptions = {
|
||||
/**
|
||||
* In a polar chart, this is the angle of the Y axis in degrees, where
|
||||
* 0 is up and 90 is right. The angle determines the position of the
|
||||
* axis line and the labels, though the coordinate system is unaffected.
|
||||
* Since v8.0.0 this option is also applicable for X axis (inverted
|
||||
* polar).
|
||||
*
|
||||
* @sample {highcharts} highcharts/xaxis/angle/
|
||||
* Custom X axis' angle on inverted polar chart
|
||||
* @sample {highcharts} highcharts/yaxis/angle/
|
||||
* Dual axis polar chart
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @since 4.2.7
|
||||
* @product highcharts
|
||||
* @apioption xAxis.angle
|
||||
*/
|
||||
/**
|
||||
* Polar charts only. Whether the grid lines should draw as a polygon
|
||||
* with straight lines between categories, or as circles. Can be either
|
||||
* `circle` or `polygon`. Since v8.0.0 this option is also applicable
|
||||
* for X axis (inverted polar).
|
||||
*
|
||||
* @sample {highcharts} highcharts/demo/polar-spider/
|
||||
* Polygon grid lines
|
||||
* @sample {highcharts} highcharts/xaxis/gridlineinterpolation/
|
||||
* Circle and polygon on inverted polar
|
||||
* @sample {highcharts} highcharts/yaxis/gridlineinterpolation/
|
||||
* Circle and polygon
|
||||
*
|
||||
* @type {string}
|
||||
* @product highcharts
|
||||
* @validvalue ["circle", "polygon"]
|
||||
* @apioption xAxis.gridLineInterpolation
|
||||
*/
|
||||
gridLineInterpolation: 'circle',
|
||||
gridLineWidth: 1,
|
||||
labels: {
|
||||
align: 'right',
|
||||
x: -3,
|
||||
y: -2
|
||||
},
|
||||
showLastLabel: false,
|
||||
title: {
|
||||
x: 4,
|
||||
text: null,
|
||||
rotation: 90
|
||||
}
|
||||
};
|
||||
return RadialAxis;
|
||||
}());
|
||||
RadialAxis.compose(Axis, Tick); // @todo move outside
|
||||
export default RadialAxis;
|
||||
@@ -0,0 +1,151 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
import H from '../Globals.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, defined = U.defined, pick = U.pick;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Creates scrollbars if enabled.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
var ScrollbarAxis = /** @class */ (function () {
|
||||
function ScrollbarAxis() {
|
||||
}
|
||||
/**
|
||||
* Attaches to axis events to create scrollbars if enabled.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param AxisClass
|
||||
* Axis class to extend.
|
||||
*
|
||||
* @param ScrollbarClass
|
||||
* Scrollbar class to use.
|
||||
*/
|
||||
ScrollbarAxis.compose = function (AxisClass, ScrollbarClass) {
|
||||
// Wrap axis initialization and create scrollbar if enabled:
|
||||
addEvent(AxisClass, 'afterInit', function () {
|
||||
var axis = this;
|
||||
if (axis.options &&
|
||||
axis.options.scrollbar &&
|
||||
axis.options.scrollbar.enabled) {
|
||||
// Predefined options:
|
||||
axis.options.scrollbar.vertical = !axis.horiz;
|
||||
axis.options.startOnTick = axis.options.endOnTick = false;
|
||||
axis.scrollbar = new ScrollbarClass(axis.chart.renderer, axis.options.scrollbar, axis.chart);
|
||||
addEvent(axis.scrollbar, 'changed', function (e) {
|
||||
var axisMin = pick(axis.options && axis.options.min, axis.min), axisMax = pick(axis.options && axis.options.max, axis.max), unitedMin = defined(axis.dataMin) ?
|
||||
Math.min(axisMin, axis.min, axis.dataMin) : axisMin, unitedMax = defined(axis.dataMax) ?
|
||||
Math.max(axisMax, axis.max, axis.dataMax) : axisMax, range = unitedMax - unitedMin, to, from;
|
||||
// #12834, scroll when show/hide series, wrong extremes
|
||||
if (!defined(axisMin) || !defined(axisMax)) {
|
||||
return;
|
||||
}
|
||||
if ((axis.horiz && !axis.reversed) ||
|
||||
(!axis.horiz && axis.reversed)) {
|
||||
to = unitedMin + range * this.to;
|
||||
from = unitedMin + range * this.from;
|
||||
}
|
||||
else {
|
||||
// y-values in browser are reversed, but this also
|
||||
// applies for reversed horizontal axis:
|
||||
to = unitedMin + range * (1 - this.from);
|
||||
from = unitedMin + range * (1 - this.to);
|
||||
}
|
||||
if (pick(this.options.liveRedraw, H.svg && !H.isTouchDevice && !this.chart.isBoosting) ||
|
||||
// Mouseup always should change extremes
|
||||
e.DOMType === 'mouseup' ||
|
||||
// Internal events
|
||||
!defined(e.DOMType)) {
|
||||
axis.setExtremes(from, to, true, e.DOMType !== 'mousemove', e);
|
||||
}
|
||||
else {
|
||||
// When live redraw is disabled, don't change extremes
|
||||
// Only change the position of the scollbar thumb
|
||||
this.setRange(this.from, this.to);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// Wrap rendering axis, and update scrollbar if one is created:
|
||||
addEvent(AxisClass, 'afterRender', function () {
|
||||
var axis = this, scrollMin = Math.min(pick(axis.options.min, axis.min), axis.min, pick(axis.dataMin, axis.min) // #6930
|
||||
), scrollMax = Math.max(pick(axis.options.max, axis.max), axis.max, pick(axis.dataMax, axis.max) // #6930
|
||||
), scrollbar = axis.scrollbar, offset = axis.axisTitleMargin + (axis.titleOffset || 0), scrollbarsOffsets = axis.chart.scrollbarsOffsets, axisMargin = axis.options.margin || 0, offsetsIndex, from, to;
|
||||
if (scrollbar) {
|
||||
if (axis.horiz) {
|
||||
// Reserve space for labels/title
|
||||
if (!axis.opposite) {
|
||||
scrollbarsOffsets[1] += offset;
|
||||
}
|
||||
scrollbar.position(axis.left, axis.top + axis.height + 2 + scrollbarsOffsets[1] -
|
||||
(axis.opposite ? axisMargin : 0), axis.width, axis.height);
|
||||
// Next scrollbar should reserve space for margin (if set)
|
||||
if (!axis.opposite) {
|
||||
scrollbarsOffsets[1] += axisMargin;
|
||||
}
|
||||
offsetsIndex = 1;
|
||||
}
|
||||
else {
|
||||
// Reserve space for labels/title
|
||||
if (axis.opposite) {
|
||||
scrollbarsOffsets[0] += offset;
|
||||
}
|
||||
scrollbar.position(axis.left + axis.width + 2 + scrollbarsOffsets[0] -
|
||||
(axis.opposite ? 0 : axisMargin), axis.top, axis.width, axis.height);
|
||||
// Next scrollbar should reserve space for margin (if set)
|
||||
if (axis.opposite) {
|
||||
scrollbarsOffsets[0] += axisMargin;
|
||||
}
|
||||
offsetsIndex = 0;
|
||||
}
|
||||
scrollbarsOffsets[offsetsIndex] += scrollbar.size +
|
||||
scrollbar.options.margin;
|
||||
if (isNaN(scrollMin) ||
|
||||
isNaN(scrollMax) ||
|
||||
!defined(axis.min) ||
|
||||
!defined(axis.max) ||
|
||||
axis.min === axis.max // #10733
|
||||
) {
|
||||
// default action: when extremes are the same or there is
|
||||
// not extremes on the axis, but scrollbar exists, make it
|
||||
// full size
|
||||
scrollbar.setRange(0, 1);
|
||||
}
|
||||
else {
|
||||
from =
|
||||
(axis.min - scrollMin) / (scrollMax - scrollMin);
|
||||
to =
|
||||
(axis.max - scrollMin) / (scrollMax - scrollMin);
|
||||
if ((axis.horiz && !axis.reversed) ||
|
||||
(!axis.horiz && axis.reversed)) {
|
||||
scrollbar.setRange(from, to);
|
||||
}
|
||||
else {
|
||||
// inverse vertical axis
|
||||
scrollbar.setRange(1 - to, 1 - from);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// Make space for a scrollbar:
|
||||
addEvent(AxisClass, 'afterGetOffset', function () {
|
||||
var axis = this, index = axis.horiz ? 2 : 1, scrollbar = axis.scrollbar;
|
||||
if (scrollbar) {
|
||||
axis.chart.scrollbarsOffsets = [0, 0]; // reset scrollbars offsets
|
||||
axis.chart.axisOffset[index] +=
|
||||
scrollbar.size + scrollbar.options.margin;
|
||||
}
|
||||
});
|
||||
};
|
||||
return ScrollbarAxis;
|
||||
}());
|
||||
export default ScrollbarAxis;
|
||||
@@ -0,0 +1,193 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, destroyObjectProperties = U.destroyObjectProperties, fireEvent = U.fireEvent, getDeferredAnimation = U.getDeferredAnimation, 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;
|
||||
}());
|
||||
export default StackingAxis;
|
||||
@@ -0,0 +1,667 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Globals.js';
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
import U from '../Utilities.js';
|
||||
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] }))
|
||||
});
|
||||
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;
|
||||
export default H.Tick;
|
||||
@@ -0,0 +1,68 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extenstion for 3d axes
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, extend = U.extend, wrap = U.wrap;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* Tick with 3D support
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var Tick3D = /** @class */ (function () {
|
||||
function Tick3D() {
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Tick3D.compose = function (TickClass) {
|
||||
addEvent(TickClass, 'afterGetLabelPosition', Tick3D.onAfterGetLabelPosition);
|
||||
var tickProto = TickClass.prototype;
|
||||
wrap(tickProto, 'getMarkPath', Tick3D.wrapGetMarkPath);
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Tick3D.onAfterGetLabelPosition = function (e) {
|
||||
var axis3D = this.axis.axis3D;
|
||||
if (axis3D) {
|
||||
extend(e.pos, axis3D.fix3dPosition(e.pos));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
Tick3D.wrapGetMarkPath = function (proceed) {
|
||||
var chart = this.axis.chart;
|
||||
var axis3D = this.axis.axis3D;
|
||||
var path = proceed.apply(this, [].slice.call(arguments, 1));
|
||||
if (axis3D) {
|
||||
var start = path[0];
|
||||
var end = path[1];
|
||||
if (start[0] === 'M' && end[0] === 'L') {
|
||||
var pArr = [
|
||||
axis3D.fix3dPosition({ x: start[1], y: start[2], z: 0 }),
|
||||
axis3D.fix3dPosition({ x: end[1], y: end[2], z: 0 })
|
||||
];
|
||||
return this.axis.chart.renderer.toLineSegments(pArr);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
};
|
||||
return Tick3D;
|
||||
}());
|
||||
export default Tick3D;
|
||||
@@ -0,0 +1,677 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2016 Highsoft AS
|
||||
* Authors: Jon Arild Nygard
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis.js';
|
||||
import Tick from './Tick.js';
|
||||
import Tree from '../../Gantt/Tree.js';
|
||||
import TreeGridTick from './TreeGridTick.js';
|
||||
import mixinTreeSeries from '../../Mixins/TreeSeries.js';
|
||||
var getLevelOptions = mixinTreeSeries.getLevelOptions;
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, find = U.find, fireEvent = U.fireEvent, isNumber = U.isNumber, isObject = U.isObject, isString = U.isString, merge = U.merge, pick = U.pick, wrap = U.wrap;
|
||||
import './GridAxis.js';
|
||||
import './BrokenAxis.js';
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
var TreeGridAxis;
|
||||
(function (TreeGridAxis) {
|
||||
/* *
|
||||
*
|
||||
* Interfaces
|
||||
*
|
||||
* */
|
||||
/* *
|
||||
*
|
||||
* Variables
|
||||
*
|
||||
* */
|
||||
var applied = false;
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function compose(AxisClass) {
|
||||
if (!applied) {
|
||||
wrap(AxisClass.prototype, 'generateTick', wrapGenerateTick);
|
||||
wrap(AxisClass.prototype, 'getMaxLabelDimensions', wrapGetMaxLabelDimensions);
|
||||
wrap(AxisClass.prototype, 'init', wrapInit);
|
||||
wrap(AxisClass.prototype, 'setTickInterval', wrapSetTickInterval);
|
||||
TreeGridTick.compose(Tick);
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
TreeGridAxis.compose = compose;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function getBreakFromNode(node, max) {
|
||||
var from = node.collapseStart || 0, to = node.collapseEnd || 0;
|
||||
// In broken-axis, the axis.max is minimized until it is not within a
|
||||
// break. Therefore, if break.to is larger than axis.max, the axis.to
|
||||
// should not add the 0.5 axis.tickMarkOffset, to avoid adding a break
|
||||
// larger than axis.max.
|
||||
// TODO consider simplifying broken-axis and this might solve itself
|
||||
if (to >= max) {
|
||||
from -= 0.5;
|
||||
}
|
||||
return {
|
||||
from: from,
|
||||
to: to,
|
||||
showPoints: false
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Creates a tree structure of the data, and the treegrid. Calculates
|
||||
* categories, and y-values of points based on the tree.
|
||||
*
|
||||
* @private
|
||||
* @function getTreeGridFromData
|
||||
*
|
||||
* @param {Array<Highcharts.GanttPointOptions>} data
|
||||
* All the data points to display in the axis.
|
||||
*
|
||||
* @param {boolean} uniqueNames
|
||||
* Wether or not the data node with the same name should share grid cell. If
|
||||
* true they do share cell. False by default.
|
||||
*
|
||||
* @param {number} numberOfSeries
|
||||
*
|
||||
* @return {object}
|
||||
* Returns an object containing categories, mapOfIdToNode,
|
||||
* mapOfPosToGridNode, and tree.
|
||||
*
|
||||
* @todo There should be only one point per line.
|
||||
* @todo It should be optional to have one category per point, or merge
|
||||
* cells
|
||||
* @todo Add unit-tests.
|
||||
*/
|
||||
function getTreeGridFromData(data, uniqueNames, numberOfSeries) {
|
||||
var categories = [], collapsedNodes = [], mapOfIdToNode = {}, mapOfPosToGridNode = {}, posIterator = -1, uniqueNamesEnabled = typeof uniqueNames === 'boolean' ? uniqueNames : false, tree;
|
||||
// Build the tree from the series data.
|
||||
var treeParams = {
|
||||
// After the children has been created.
|
||||
after: function (node) {
|
||||
var gridNode = mapOfPosToGridNode[node.pos], height = 0, descendants = 0;
|
||||
gridNode.children.forEach(function (child) {
|
||||
descendants += (child.descendants || 0) + 1;
|
||||
height = Math.max((child.height || 0) + 1, height);
|
||||
});
|
||||
gridNode.descendants = descendants;
|
||||
gridNode.height = height;
|
||||
if (gridNode.collapsed) {
|
||||
collapsedNodes.push(gridNode);
|
||||
}
|
||||
},
|
||||
// Before the children has been created.
|
||||
before: function (node) {
|
||||
var data = isObject(node.data, true) ? node.data : {}, name = isString(data.name) ? data.name : '', parentNode = mapOfIdToNode[node.parent], parentGridNode = (isObject(parentNode, true) ?
|
||||
mapOfPosToGridNode[parentNode.pos] :
|
||||
null), hasSameName = function (x) {
|
||||
return x.name === name;
|
||||
}, gridNode, pos;
|
||||
// If not unique names, look for sibling node with the same name
|
||||
if (uniqueNamesEnabled &&
|
||||
isObject(parentGridNode, true) &&
|
||||
!!(gridNode = find(parentGridNode.children, hasSameName))) {
|
||||
// If there is a gridNode with the same name, reuse position
|
||||
pos = gridNode.pos;
|
||||
// Add data node to list of nodes in the grid node.
|
||||
gridNode.nodes.push(node);
|
||||
}
|
||||
else {
|
||||
// If it is a new grid node, increment position.
|
||||
pos = posIterator++;
|
||||
}
|
||||
// Add new grid node to map.
|
||||
if (!mapOfPosToGridNode[pos]) {
|
||||
mapOfPosToGridNode[pos] = gridNode = {
|
||||
depth: parentGridNode ? parentGridNode.depth + 1 : 0,
|
||||
name: name,
|
||||
nodes: [node],
|
||||
children: [],
|
||||
pos: pos
|
||||
};
|
||||
// If not root, then add name to categories.
|
||||
if (pos !== -1) {
|
||||
categories.push(name);
|
||||
}
|
||||
// Add name to list of children.
|
||||
if (isObject(parentGridNode, true)) {
|
||||
parentGridNode.children.push(gridNode);
|
||||
}
|
||||
}
|
||||
// Add data node to map
|
||||
if (isString(node.id)) {
|
||||
mapOfIdToNode[node.id] = node;
|
||||
}
|
||||
// If one of the points are collapsed, then start the grid node
|
||||
// in collapsed state.
|
||||
if (gridNode &&
|
||||
data.collapsed === true) {
|
||||
gridNode.collapsed = true;
|
||||
}
|
||||
// Assign pos to data node
|
||||
node.pos = pos;
|
||||
}
|
||||
};
|
||||
var updateYValuesAndTickPos = function (map, numberOfSeries) {
|
||||
var setValues = function (gridNode, start, result) {
|
||||
var nodes = gridNode.nodes, end = start + (start === -1 ? 0 : numberOfSeries - 1), diff = (end - start) / 2, padding = 0.5, pos = start + diff;
|
||||
nodes.forEach(function (node) {
|
||||
var data = node.data;
|
||||
if (isObject(data, true)) {
|
||||
// Update point
|
||||
data.y = start + (data.seriesIndex || 0);
|
||||
// Remove the property once used
|
||||
delete data.seriesIndex;
|
||||
}
|
||||
node.pos = pos;
|
||||
});
|
||||
result[pos] = gridNode;
|
||||
gridNode.pos = pos;
|
||||
gridNode.tickmarkOffset = diff + padding;
|
||||
gridNode.collapseStart = end + padding;
|
||||
gridNode.children.forEach(function (child) {
|
||||
setValues(child, end + 1, result);
|
||||
end = (child.collapseEnd || 0) - padding;
|
||||
});
|
||||
// Set collapseEnd to the end of the last child node.
|
||||
gridNode.collapseEnd = end + padding;
|
||||
return result;
|
||||
};
|
||||
return setValues(map['-1'], -1, {});
|
||||
};
|
||||
// Create tree from data
|
||||
tree = Tree.getTree(data, treeParams);
|
||||
// Update y values of data, and set calculate tick positions.
|
||||
mapOfPosToGridNode = updateYValuesAndTickPos(mapOfPosToGridNode, numberOfSeries);
|
||||
// Return the resulting data.
|
||||
return {
|
||||
categories: categories,
|
||||
mapOfIdToNode: mapOfIdToNode,
|
||||
mapOfPosToGridNode: mapOfPosToGridNode,
|
||||
collapsedNodes: collapsedNodes,
|
||||
tree: tree
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Builds the tree of categories and calculates its positions.
|
||||
* @private
|
||||
* @param {object} e Event object
|
||||
* @param {object} e.target The chart instance which the event was fired on.
|
||||
* @param {object[]} e.target.axes The axes of the chart.
|
||||
*/
|
||||
function onBeforeRender(e) {
|
||||
var chart = e.target, axes = chart.axes;
|
||||
axes.filter(function (axis) {
|
||||
return axis.options.type === 'treegrid';
|
||||
}).forEach(function (axis) {
|
||||
var options = axis.options || {}, labelOptions = options.labels, uniqueNames = options.uniqueNames, numberOfSeries = 0, isDirty, data, treeGrid, max = options.max;
|
||||
// Check whether any of series is rendering for the first time,
|
||||
// visibility has changed, or its data is dirty,
|
||||
// and only then update. #10570, #10580
|
||||
// Also check if mapOfPosToGridNode exists. #10887
|
||||
isDirty = (!axis.treeGrid.mapOfPosToGridNode ||
|
||||
axis.series.some(function (series) {
|
||||
return !series.hasRendered ||
|
||||
series.isDirtyData ||
|
||||
series.isDirty;
|
||||
}));
|
||||
if (isDirty) {
|
||||
// Concatenate data from all series assigned to this axis.
|
||||
data = axis.series.reduce(function (arr, s) {
|
||||
if (s.visible) {
|
||||
// Push all data to array
|
||||
(s.options.data || []).forEach(function (data) {
|
||||
if (isObject(data, true)) {
|
||||
// Set series index on data. Removed again
|
||||
// after use.
|
||||
data.seriesIndex = numberOfSeries;
|
||||
arr.push(data);
|
||||
}
|
||||
});
|
||||
// Increment series index
|
||||
if (uniqueNames === true) {
|
||||
numberOfSeries++;
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
// If max is higher than set data - add a
|
||||
// dummy data to render categories #10779
|
||||
if (max && data.length < max) {
|
||||
for (var i = data.length; i <= max; i++) {
|
||||
data.push({
|
||||
// Use the zero-width character
|
||||
// to avoid conflict with uniqueNames
|
||||
name: i + '\u200B'
|
||||
});
|
||||
}
|
||||
}
|
||||
// setScale is fired after all the series is initialized,
|
||||
// which is an ideal time to update the axis.categories.
|
||||
treeGrid = getTreeGridFromData(data, uniqueNames || false, (uniqueNames === true) ? numberOfSeries : 1);
|
||||
// Assign values to the axis.
|
||||
axis.categories = treeGrid.categories;
|
||||
axis.treeGrid.mapOfPosToGridNode = treeGrid.mapOfPosToGridNode;
|
||||
axis.hasNames = true;
|
||||
axis.treeGrid.tree = treeGrid.tree;
|
||||
// Update yData now that we have calculated the y values
|
||||
axis.series.forEach(function (series) {
|
||||
var data = (series.options.data || []).map(function (d) {
|
||||
return isObject(d, true) ? merge(d) : d;
|
||||
});
|
||||
// Avoid destroying points when series is not visible
|
||||
if (series.visible) {
|
||||
series.setData(data, false);
|
||||
}
|
||||
});
|
||||
// Calculate the label options for each level in the tree.
|
||||
axis.treeGrid.mapOptionsToLevel =
|
||||
getLevelOptions({
|
||||
defaults: labelOptions,
|
||||
from: 1,
|
||||
levels: labelOptions && labelOptions.levels,
|
||||
to: axis.treeGrid.tree && axis.treeGrid.tree.height
|
||||
});
|
||||
// Setting initial collapsed nodes
|
||||
if (e.type === 'beforeRender') {
|
||||
axis.treeGrid.collapsedNodes = treeGrid.collapsedNodes;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Generates a tick for initial positioning.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.GridAxis#generateTick
|
||||
*
|
||||
* @param {Function} proceed
|
||||
* The original generateTick function.
|
||||
*
|
||||
* @param {number} pos
|
||||
* The tick position in axis values.
|
||||
*/
|
||||
function wrapGenerateTick(proceed, pos) {
|
||||
var axis = this, mapOptionsToLevel = axis.treeGrid.mapOptionsToLevel || {}, isTreeGrid = axis.options.type === 'treegrid', ticks = axis.ticks;
|
||||
var tick = ticks[pos], levelOptions, options, gridNode;
|
||||
if (isTreeGrid &&
|
||||
axis.treeGrid.mapOfPosToGridNode) {
|
||||
gridNode = axis.treeGrid.mapOfPosToGridNode[pos];
|
||||
levelOptions = mapOptionsToLevel[gridNode.depth];
|
||||
if (levelOptions) {
|
||||
options = {
|
||||
labels: levelOptions
|
||||
};
|
||||
}
|
||||
if (!tick) {
|
||||
ticks[pos] = tick =
|
||||
new Tick(axis, pos, void 0, void 0, {
|
||||
category: gridNode.name,
|
||||
tickmarkOffset: gridNode.tickmarkOffset,
|
||||
options: options
|
||||
});
|
||||
}
|
||||
else {
|
||||
// update labels depending on tick interval
|
||||
tick.parameters.category = gridNode.name;
|
||||
tick.options = options;
|
||||
tick.addLabel();
|
||||
}
|
||||
}
|
||||
else {
|
||||
proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Override to add indentation to axis.maxLabelDimensions.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.GridAxis#getMaxLabelDimensions
|
||||
*
|
||||
* @param {Function} proceed
|
||||
* The original function
|
||||
*/
|
||||
function wrapGetMaxLabelDimensions(proceed) {
|
||||
var axis = this, options = axis.options, labelOptions = options && options.labels, indentation = (labelOptions && isNumber(labelOptions.indentation) ?
|
||||
labelOptions.indentation :
|
||||
0), retVal = proceed.apply(axis, Array.prototype.slice.call(arguments, 1)), isTreeGrid = axis.options.type === 'treegrid';
|
||||
var treeDepth;
|
||||
if (isTreeGrid && axis.treeGrid.mapOfPosToGridNode) {
|
||||
treeDepth = axis.treeGrid.mapOfPosToGridNode[-1].height || 0;
|
||||
retVal.width += indentation * (treeDepth - 1);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function wrapInit(proceed, chart, userOptions) {
|
||||
var axis = this, isTreeGrid = userOptions.type === 'treegrid';
|
||||
if (!axis.treeGrid) {
|
||||
axis.treeGrid = new Additions(axis);
|
||||
}
|
||||
// Set default and forced options for TreeGrid
|
||||
if (isTreeGrid) {
|
||||
// Add event for updating the categories of a treegrid.
|
||||
// NOTE Preferably these events should be set on the axis.
|
||||
addEvent(chart, 'beforeRender', onBeforeRender);
|
||||
addEvent(chart, 'beforeRedraw', onBeforeRender);
|
||||
// Add new collapsed nodes on addseries
|
||||
addEvent(chart, 'addSeries', function (e) {
|
||||
if (e.options.data) {
|
||||
var treeGrid = getTreeGridFromData(e.options.data, userOptions.uniqueNames || false, 1);
|
||||
axis.treeGrid.collapsedNodes = (axis.treeGrid.collapsedNodes || []).concat(treeGrid.collapsedNodes);
|
||||
}
|
||||
});
|
||||
// Collapse all nodes in axis.treegrid.collapsednodes
|
||||
// where collapsed equals true.
|
||||
addEvent(axis, 'foundExtremes', function () {
|
||||
if (axis.treeGrid.collapsedNodes) {
|
||||
axis.treeGrid.collapsedNodes.forEach(function (node) {
|
||||
var breaks = axis.treeGrid.collapse(node);
|
||||
if (axis.brokenAxis) {
|
||||
axis.brokenAxis.setBreaks(breaks, false);
|
||||
// remove the node from the axis collapsedNodes
|
||||
if (axis.treeGrid.collapsedNodes) {
|
||||
axis.treeGrid.collapsedNodes = axis.treeGrid.collapsedNodes.filter(function (n) {
|
||||
return node.collapseStart !== n.collapseStart ||
|
||||
node.collapseEnd !== n.collapseEnd;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// If staticScale is not defined on the yAxis
|
||||
// and chart height is set, set axis.isDirty
|
||||
// to ensure collapsing works (#12012)
|
||||
addEvent(axis, 'afterBreaks', function () {
|
||||
var _a;
|
||||
if (axis.coll === 'yAxis' && !axis.staticScale && ((_a = axis.chart.options.chart) === null || _a === void 0 ? void 0 : _a.height)) {
|
||||
axis.isDirty = true;
|
||||
}
|
||||
});
|
||||
userOptions = merge({
|
||||
// Default options
|
||||
grid: {
|
||||
enabled: true
|
||||
},
|
||||
// TODO: add support for align in treegrid.
|
||||
labels: {
|
||||
align: 'left',
|
||||
/**
|
||||
* Set options on specific levels in a tree grid axis. Takes
|
||||
* precedence over labels options.
|
||||
*
|
||||
* @sample {gantt} gantt/treegrid-axis/labels-levels
|
||||
* Levels on TreeGrid Labels
|
||||
*
|
||||
* @type {Array<*>}
|
||||
* @product gantt
|
||||
* @apioption yAxis.labels.levels
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
levels: [{
|
||||
/**
|
||||
* Specify the level which the options within this object
|
||||
* applies to.
|
||||
*
|
||||
* @type {number}
|
||||
* @product gantt
|
||||
* @apioption yAxis.labels.levels.level
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
level: void 0
|
||||
}, {
|
||||
level: 1,
|
||||
/**
|
||||
* @type {Highcharts.CSSObject}
|
||||
* @product gantt
|
||||
* @apioption yAxis.labels.levels.style
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
style: {
|
||||
/** @ignore-option */
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
}],
|
||||
/**
|
||||
* The symbol for the collapse and expand icon in a
|
||||
* treegrid.
|
||||
*
|
||||
* @product gantt
|
||||
* @optionparent yAxis.labels.symbol
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
symbol: {
|
||||
/**
|
||||
* The symbol type. Points to a definition function in
|
||||
* the `Highcharts.Renderer.symbols` collection.
|
||||
*
|
||||
* @type {Highcharts.SymbolKeyValue}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
type: 'triangle',
|
||||
x: -5,
|
||||
y: -5,
|
||||
height: 10,
|
||||
width: 10,
|
||||
padding: 5
|
||||
}
|
||||
},
|
||||
uniqueNames: false
|
||||
}, userOptions, {
|
||||
// Forced options
|
||||
reversed: true,
|
||||
// grid.columns is not supported in treegrid
|
||||
grid: {
|
||||
columns: void 0
|
||||
}
|
||||
});
|
||||
}
|
||||
// Now apply the original function with the original arguments,
|
||||
// which are sliced off this function's arguments
|
||||
proceed.apply(axis, [chart, userOptions]);
|
||||
if (isTreeGrid) {
|
||||
axis.hasNames = true;
|
||||
axis.options.showLastLabel = true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Set the tick positions, tickInterval, axis min and max.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.GridAxis#setTickInterval
|
||||
*
|
||||
* @param {Function} proceed
|
||||
* The original setTickInterval function.
|
||||
*/
|
||||
function wrapSetTickInterval(proceed) {
|
||||
var axis = this, options = axis.options, isTreeGrid = options.type === 'treegrid';
|
||||
if (isTreeGrid) {
|
||||
axis.min = pick(axis.userMin, options.min, axis.dataMin);
|
||||
axis.max = pick(axis.userMax, options.max, axis.dataMax);
|
||||
fireEvent(axis, 'foundExtremes');
|
||||
// setAxisTranslation modifies the min and max according to
|
||||
// axis breaks.
|
||||
axis.setAxisTranslation(true);
|
||||
axis.tickmarkOffset = 0.5;
|
||||
axis.tickInterval = 1;
|
||||
axis.tickPositions = axis.treeGrid.mapOfPosToGridNode ?
|
||||
axis.treeGrid.getTickPositions() :
|
||||
[];
|
||||
}
|
||||
else {
|
||||
proceed.apply(axis, Array.prototype.slice.call(arguments, 1));
|
||||
}
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Classes
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var Additions = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function Additions(axis) {
|
||||
this.axis = axis;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Calculates the new axis breaks to collapse a node.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Highcharts.Axis} axis
|
||||
* The axis to check against.
|
||||
*
|
||||
* @param {Highcharts.GridNode} node
|
||||
* The node to collapse.
|
||||
*
|
||||
* @param {number} pos
|
||||
* The tick position to collapse.
|
||||
*
|
||||
* @return {Array<object>}
|
||||
* Returns an array of the new breaks for the axis.
|
||||
*/
|
||||
Additions.prototype.collapse = function (node) {
|
||||
var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
|
||||
breaks.push(obj);
|
||||
return breaks;
|
||||
};
|
||||
/**
|
||||
* Calculates the new axis breaks to expand a node.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Highcharts.Axis} axis
|
||||
* The axis to check against.
|
||||
*
|
||||
* @param {Highcharts.GridNode} node
|
||||
* The node to expand.
|
||||
*
|
||||
* @param {number} pos
|
||||
* The tick position to expand.
|
||||
*
|
||||
* @return {Array<object>}
|
||||
* Returns an array of the new breaks for the axis.
|
||||
*/
|
||||
Additions.prototype.expand = function (node) {
|
||||
var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
|
||||
// Remove the break from the axis breaks array.
|
||||
return breaks.reduce(function (arr, b) {
|
||||
if (b.to !== obj.to || b.from !== obj.from) {
|
||||
arr.push(b);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
};
|
||||
/**
|
||||
* Creates a list of positions for the ticks on the axis. Filters out
|
||||
* positions that are outside min and max, or is inside an axis break.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @return {Array<number>}
|
||||
* List of positions.
|
||||
*/
|
||||
Additions.prototype.getTickPositions = function () {
|
||||
var axis = this.axis;
|
||||
return Object.keys(axis.treeGrid.mapOfPosToGridNode || {}).reduce(function (arr, key) {
|
||||
var pos = +key;
|
||||
if (axis.min <= pos &&
|
||||
axis.max >= pos &&
|
||||
!(axis.brokenAxis && axis.brokenAxis.isInAnyBreak(pos))) {
|
||||
arr.push(pos);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
};
|
||||
/**
|
||||
* Check if a node is collapsed.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Highcharts.Axis} axis
|
||||
* The axis to check against.
|
||||
*
|
||||
* @param {object} node
|
||||
* The node to check if is collapsed.
|
||||
*
|
||||
* @param {number} pos
|
||||
* The tick position to collapse.
|
||||
*
|
||||
* @return {boolean}
|
||||
* Returns true if collapsed, false if expanded.
|
||||
*/
|
||||
Additions.prototype.isCollapsed = function (node) {
|
||||
var axis = this.axis, breaks = (axis.options.breaks || []), obj = getBreakFromNode(node, axis.max);
|
||||
return breaks.some(function (b) {
|
||||
return b.from === obj.from && b.to === obj.to;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Calculates the new axis breaks after toggling the collapse/expand
|
||||
* state of a node. If it is collapsed it will be expanded, and if it is
|
||||
* exapended it will be collapsed.
|
||||
*
|
||||
* @private
|
||||
*
|
||||
* @param {Highcharts.Axis} axis
|
||||
* The axis to check against.
|
||||
*
|
||||
* @param {Highcharts.GridNode} node
|
||||
* The node to toggle.
|
||||
*
|
||||
* @return {Array<object>}
|
||||
* Returns an array of the new breaks for the axis.
|
||||
*/
|
||||
Additions.prototype.toggleCollapse = function (node) {
|
||||
return (this.isCollapsed(node) ?
|
||||
this.expand(node) :
|
||||
this.collapse(node));
|
||||
};
|
||||
return Additions;
|
||||
}());
|
||||
TreeGridAxis.Additions = Additions;
|
||||
})(TreeGridAxis || (TreeGridAxis = {}));
|
||||
// Make utility functions available for testing.
|
||||
Axis.prototype.utils = {
|
||||
getNode: Tree.getNode
|
||||
};
|
||||
TreeGridAxis.compose(Axis);
|
||||
export default TreeGridAxis;
|
||||
@@ -0,0 +1,305 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2016 Highsoft AS
|
||||
* Authors: Jon Arild Nygard
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, defined = U.defined, isObject = U.isObject, isNumber = U.isNumber, pick = U.pick, wrap = U.wrap;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
var TreeGridTick;
|
||||
(function (TreeGridTick) {
|
||||
/* *
|
||||
*
|
||||
* Interfaces
|
||||
*
|
||||
* */
|
||||
/* *
|
||||
*
|
||||
* Variables
|
||||
*
|
||||
* */
|
||||
var applied = false;
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function compose(TickClass) {
|
||||
if (!applied) {
|
||||
addEvent(TickClass, 'init', onInit);
|
||||
wrap(TickClass.prototype, 'getLabelPosition', wrapGetLabelPosition);
|
||||
wrap(TickClass.prototype, 'renderLabel', wrapRenderLabel);
|
||||
// backwards compatibility
|
||||
TickClass.prototype.collapse = function (redraw) {
|
||||
this.treeGrid.collapse(redraw);
|
||||
};
|
||||
TickClass.prototype.expand = function (redraw) {
|
||||
this.treeGrid.expand(redraw);
|
||||
};
|
||||
TickClass.prototype.toggleCollapse = function (redraw) {
|
||||
this.treeGrid.toggleCollapse(redraw);
|
||||
};
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
TreeGridTick.compose = compose;
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function onInit() {
|
||||
var tick = this;
|
||||
if (!tick.treeGrid) {
|
||||
tick.treeGrid = new Additions(tick);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function onTickHover(label) {
|
||||
label.addClass('highcharts-treegrid-node-active');
|
||||
if (!label.renderer.styledMode) {
|
||||
label.css({
|
||||
textDecoration: 'underline'
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function onTickHoverExit(label, options) {
|
||||
var css = defined(options.style) ? options.style : {};
|
||||
label.removeClass('highcharts-treegrid-node-active');
|
||||
if (!label.renderer.styledMode) {
|
||||
label.css({ textDecoration: css.textDecoration });
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function renderLabelIcon(tick, params) {
|
||||
var treeGrid = tick.treeGrid, isNew = !treeGrid.labelIcon, renderer = params.renderer, labelBox = params.xy, options = params.options, width = options.width, height = options.height, iconCenter = {
|
||||
x: labelBox.x - (width / 2) - options.padding,
|
||||
y: labelBox.y - (height / 2)
|
||||
}, rotation = params.collapsed ? 90 : 180, shouldRender = params.show && isNumber(iconCenter.y);
|
||||
var icon = treeGrid.labelIcon;
|
||||
if (!icon) {
|
||||
treeGrid.labelIcon = icon = renderer
|
||||
.path(renderer.symbols[options.type](options.x, options.y, width, height))
|
||||
.addClass('highcharts-label-icon')
|
||||
.add(params.group);
|
||||
}
|
||||
// Set the new position, and show or hide
|
||||
if (!shouldRender) {
|
||||
icon.attr({ y: -9999 }); // #1338
|
||||
}
|
||||
// Presentational attributes
|
||||
if (!renderer.styledMode) {
|
||||
icon
|
||||
.attr({
|
||||
'stroke-width': 1,
|
||||
'fill': pick(params.color, '#666666')
|
||||
})
|
||||
.css({
|
||||
cursor: 'pointer',
|
||||
stroke: options.lineColor,
|
||||
strokeWidth: options.lineWidth
|
||||
});
|
||||
}
|
||||
// Update the icon positions
|
||||
icon[isNew ? 'attr' : 'animate']({
|
||||
translateX: iconCenter.x,
|
||||
translateY: iconCenter.y,
|
||||
rotation: rotation
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function wrapGetLabelPosition(proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
|
||||
var tick = this, lbOptions = pick(tick.options && tick.options.labels, labelOptions), pos = tick.pos, axis = tick.axis, options = axis.options, isTreeGrid = options.type === 'treegrid', result = proceed.apply(tick, [x, y, label, horiz, lbOptions, tickmarkOffset, index, step]);
|
||||
var symbolOptions, indentation, mapOfPosToGridNode, node, level;
|
||||
if (isTreeGrid) {
|
||||
symbolOptions = (lbOptions && isObject(lbOptions.symbol, true) ?
|
||||
lbOptions.symbol :
|
||||
{});
|
||||
indentation = (lbOptions && isNumber(lbOptions.indentation) ?
|
||||
lbOptions.indentation :
|
||||
0);
|
||||
mapOfPosToGridNode = axis.treeGrid.mapOfPosToGridNode;
|
||||
node = mapOfPosToGridNode && mapOfPosToGridNode[pos];
|
||||
level = (node && node.depth) || 1;
|
||||
result.x += (
|
||||
// Add space for symbols
|
||||
((symbolOptions.width) + (symbolOptions.padding * 2)) +
|
||||
// Apply indentation
|
||||
((level - 1) * indentation));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function wrapRenderLabel(proceed) {
|
||||
var tick = this, pos = tick.pos, axis = tick.axis, label = tick.label, mapOfPosToGridNode = axis.treeGrid.mapOfPosToGridNode, options = axis.options, labelOptions = pick(tick.options && tick.options.labels, options && options.labels), symbolOptions = (labelOptions && isObject(labelOptions.symbol, true) ?
|
||||
labelOptions.symbol :
|
||||
{}), node = mapOfPosToGridNode && mapOfPosToGridNode[pos], level = node && node.depth, isTreeGrid = options.type === 'treegrid', shouldRender = axis.tickPositions.indexOf(pos) > -1, prefixClassName = 'highcharts-treegrid-node-', styledMode = axis.chart.styledMode;
|
||||
var collapsed, addClassName, removeClassName;
|
||||
if (isTreeGrid && node) {
|
||||
// Add class name for hierarchical styling.
|
||||
if (label &&
|
||||
label.element) {
|
||||
label.addClass(prefixClassName + 'level-' + level);
|
||||
}
|
||||
}
|
||||
proceed.apply(tick, Array.prototype.slice.call(arguments, 1));
|
||||
if (isTreeGrid &&
|
||||
label &&
|
||||
label.element &&
|
||||
node &&
|
||||
node.descendants &&
|
||||
node.descendants > 0) {
|
||||
collapsed = axis.treeGrid.isCollapsed(node);
|
||||
renderLabelIcon(tick, {
|
||||
color: !styledMode && label.styles && label.styles.color || '',
|
||||
collapsed: collapsed,
|
||||
group: label.parentGroup,
|
||||
options: symbolOptions,
|
||||
renderer: label.renderer,
|
||||
show: shouldRender,
|
||||
xy: label.xy
|
||||
});
|
||||
// Add class name for the node.
|
||||
addClassName = prefixClassName +
|
||||
(collapsed ? 'collapsed' : 'expanded');
|
||||
removeClassName = prefixClassName +
|
||||
(collapsed ? 'expanded' : 'collapsed');
|
||||
label
|
||||
.addClass(addClassName)
|
||||
.removeClass(removeClassName);
|
||||
if (!styledMode) {
|
||||
label.css({
|
||||
cursor: 'pointer'
|
||||
});
|
||||
}
|
||||
// Add events to both label text and icon
|
||||
[label, tick.treeGrid.labelIcon].forEach(function (object) {
|
||||
if (object && !object.attachedTreeGridEvents) {
|
||||
// On hover
|
||||
addEvent(object.element, 'mouseover', function () {
|
||||
onTickHover(label);
|
||||
});
|
||||
// On hover out
|
||||
addEvent(object.element, 'mouseout', function () {
|
||||
onTickHoverExit(label, labelOptions);
|
||||
});
|
||||
addEvent(object.element, 'click', function () {
|
||||
tick.treeGrid.toggleCollapse();
|
||||
});
|
||||
object.attachedTreeGridEvents = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Classes
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var Additions = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function Additions(tick) {
|
||||
this.tick = tick;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Collapse the grid cell. Used when axis is of type treegrid.
|
||||
*
|
||||
* @see gantt/treegrid-axis/collapsed-dynamically/demo.js
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Tick#collapse
|
||||
*
|
||||
* @param {boolean} [redraw=true]
|
||||
* Whether to redraw the chart or wait for an explicit call to
|
||||
* {@link Highcharts.Chart#redraw}
|
||||
*/
|
||||
Additions.prototype.collapse = function (redraw) {
|
||||
var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
|
||||
if (brokenAxis &&
|
||||
axis.treeGrid.mapOfPosToGridNode) {
|
||||
var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.collapse(node);
|
||||
brokenAxis.setBreaks(breaks, pick(redraw, true));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Expand the grid cell. Used when axis is of type treegrid.
|
||||
*
|
||||
* @see gantt/treegrid-axis/collapsed-dynamically/demo.js
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Tick#expand
|
||||
*
|
||||
* @param {boolean} [redraw=true]
|
||||
* Whether to redraw the chart or wait for an explicit call to
|
||||
* {@link Highcharts.Chart#redraw}
|
||||
*/
|
||||
Additions.prototype.expand = function (redraw) {
|
||||
var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
|
||||
if (brokenAxis &&
|
||||
axis.treeGrid.mapOfPosToGridNode) {
|
||||
var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.expand(node);
|
||||
brokenAxis.setBreaks(breaks, pick(redraw, true));
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Toggle the collapse/expand state of the grid cell. Used when axis is
|
||||
* of type treegrid.
|
||||
*
|
||||
* @see gantt/treegrid-axis/collapsed-dynamically/demo.js
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Tick#toggleCollapse
|
||||
*
|
||||
* @param {boolean} [redraw=true]
|
||||
* Whether to redraw the chart or wait for an explicit call to
|
||||
* {@link Highcharts.Chart#redraw}
|
||||
*/
|
||||
Additions.prototype.toggleCollapse = function (redraw) {
|
||||
var tick = this.tick, axis = tick.axis, brokenAxis = axis.brokenAxis;
|
||||
if (brokenAxis &&
|
||||
axis.treeGrid.mapOfPosToGridNode) {
|
||||
var pos = tick.pos, node = axis.treeGrid.mapOfPosToGridNode[pos], breaks = axis.treeGrid.toggleCollapse(node);
|
||||
brokenAxis.setBreaks(breaks, pick(redraw, true));
|
||||
}
|
||||
};
|
||||
return Additions;
|
||||
}());
|
||||
TreeGridTick.Additions = Additions;
|
||||
})(TreeGridTick || (TreeGridTick = {}));
|
||||
export default TreeGridTick;
|
||||
@@ -0,0 +1,76 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extension to the VML Renderer
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
var VMLAxis3DAdditions = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function VMLAxis3DAdditions(axis) {
|
||||
this.axis = axis;
|
||||
}
|
||||
return VMLAxis3DAdditions;
|
||||
}());
|
||||
var VMLAxis3D = /** @class */ (function () {
|
||||
function VMLAxis3D() {
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
VMLAxis3D.compose = function (AxisClass) {
|
||||
AxisClass.keepProps.push('vml');
|
||||
addEvent(AxisClass, 'init', VMLAxis3D.onInit);
|
||||
addEvent(AxisClass, 'render', VMLAxis3D.onRender);
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
VMLAxis3D.onInit = function () {
|
||||
var axis = this;
|
||||
if (!axis.vml) {
|
||||
axis.vml = new VMLAxis3DAdditions(axis);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
VMLAxis3D.onRender = function () {
|
||||
var axis = this;
|
||||
var vml = axis.vml;
|
||||
// VML doesn't support a negative z-index
|
||||
if (vml.sideFrame) {
|
||||
vml.sideFrame.css({ zIndex: 0 });
|
||||
vml.sideFrame.front.attr({
|
||||
fill: vml.sideFrame.color
|
||||
});
|
||||
}
|
||||
if (vml.bottomFrame) {
|
||||
vml.bottomFrame.css({ zIndex: 1 });
|
||||
vml.bottomFrame.front.attr({
|
||||
fill: vml.bottomFrame.color
|
||||
});
|
||||
}
|
||||
if (vml.backFrame) {
|
||||
vml.backFrame.css({ zIndex: 0 });
|
||||
vml.backFrame.front.attr({
|
||||
fill: vml.backFrame.color
|
||||
});
|
||||
}
|
||||
};
|
||||
return VMLAxis3D;
|
||||
}());
|
||||
export default VMLAxis3D;
|
||||
@@ -0,0 +1,155 @@
|
||||
/* *
|
||||
*
|
||||
* (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 __());
|
||||
};
|
||||
})();
|
||||
import Axis from './Axis.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, merge = U.merge, pick = U.pick, splat = U.splat;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* 3D chart with support of z coordinates.
|
||||
* @private
|
||||
* @class
|
||||
*/
|
||||
var ZChart = /** @class */ (function () {
|
||||
function ZChart() {
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
ZChart.compose = function (ChartClass) {
|
||||
addEvent(ChartClass, 'afterGetAxes', ZChart.onAfterGetAxes);
|
||||
var chartProto = ChartClass.prototype;
|
||||
chartProto.addZAxis = ZChart.wrapAddZAxis;
|
||||
chartProto.collectionsWithInit.zAxis = [chartProto.addZAxis];
|
||||
chartProto.collectionsWithUpdate.push('zAxis');
|
||||
};
|
||||
/**
|
||||
* Get the Z axis in addition to the default X and Y.
|
||||
* @private
|
||||
*/
|
||||
ZChart.onAfterGetAxes = function () {
|
||||
var chart = this;
|
||||
var options = this.options;
|
||||
var zAxisOptions = options.zAxis = splat(options.zAxis || {});
|
||||
if (!chart.is3d()) {
|
||||
return;
|
||||
}
|
||||
chart.zAxis = [];
|
||||
zAxisOptions.forEach(function (axisOptions, i) {
|
||||
axisOptions.index = i;
|
||||
// Z-Axis is shown horizontally, so it's kind of a X-Axis
|
||||
axisOptions.isX = true;
|
||||
chart
|
||||
.addZAxis(axisOptions)
|
||||
.setScale();
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ZChart.wrapAddZAxis = function (options) {
|
||||
return new ZAxis(this, options);
|
||||
};
|
||||
return ZChart;
|
||||
}());
|
||||
/**
|
||||
* 3D axis for z coordinates.
|
||||
*/
|
||||
var ZAxis = /** @class */ (function (_super) {
|
||||
__extends(ZAxis, _super);
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function ZAxis(chart, userOptions) {
|
||||
var _this = _super.call(this, chart, userOptions) || this;
|
||||
_this.isZAxis = true;
|
||||
return _this;
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
ZAxis.prototype.getSeriesExtremes = function () {
|
||||
var axis = this;
|
||||
var chart = axis.chart;
|
||||
axis.hasVisibleSeries = false;
|
||||
// Reset properties in case we're redrawing (#3353)
|
||||
axis.dataMin = axis.dataMax = axis.ignoreMinPadding = axis.ignoreMaxPadding = void 0;
|
||||
if (axis.stacking) {
|
||||
axis.stacking.buildStacks();
|
||||
}
|
||||
// loop through this axis' series
|
||||
axis.series.forEach(function (series) {
|
||||
if (series.visible ||
|
||||
!(chart.options.chart &&
|
||||
chart.options.chart.ignoreHiddenSeries)) {
|
||||
var seriesOptions = series.options, zData, threshold = seriesOptions.threshold;
|
||||
axis.hasVisibleSeries = true;
|
||||
// Validate threshold in logarithmic axes
|
||||
if (axis.positiveValuesOnly && threshold <= 0) {
|
||||
threshold = void 0;
|
||||
}
|
||||
zData = series.zData;
|
||||
if (zData.length) {
|
||||
axis.dataMin = Math.min(pick(axis.dataMin, zData[0]), Math.min.apply(null, zData));
|
||||
axis.dataMax = Math.max(pick(axis.dataMax, zData[0]), Math.max.apply(null, zData));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ZAxis.prototype.setAxisSize = function () {
|
||||
var axis = this;
|
||||
var chart = axis.chart;
|
||||
_super.prototype.setAxisSize.call(this);
|
||||
axis.width = axis.len = (chart.options.chart &&
|
||||
chart.options.chart.options3d &&
|
||||
chart.options.chart.options3d.depth) || 0;
|
||||
axis.right = chart.chartWidth - axis.width - axis.left;
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
ZAxis.prototype.setOptions = function (userOptions) {
|
||||
userOptions = merge({
|
||||
offset: 0,
|
||||
lineWidth: 0
|
||||
}, userOptions);
|
||||
_super.prototype.setOptions.call(this, userOptions);
|
||||
this.coll = 'zAxis';
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
ZAxis.ZChartComposition = ZChart;
|
||||
return ZAxis;
|
||||
}(Axis));
|
||||
export default ZAxis;
|
||||
+2036
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2016-2020 Highsoft AS
|
||||
*
|
||||
* Author: Lars A. V. Cabrera
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Chart from './Chart.js';
|
||||
import H from '../Globals.js';
|
||||
import U from '../Utilities.js';
|
||||
var getOptions = U.getOptions, isArray = U.isArray, merge = U.merge, splat = U.splat;
|
||||
import '../../Series/GanttSeries.js';
|
||||
/**
|
||||
* Factory function for Gantt charts.
|
||||
*
|
||||
* @example
|
||||
* // Render a chart in to div#container
|
||||
* var chart = Highcharts.ganttChart('container', {
|
||||
* title: {
|
||||
* text: 'My chart'
|
||||
* },
|
||||
* series: [{
|
||||
* data: ...
|
||||
* }]
|
||||
* });
|
||||
*
|
||||
* @function Highcharts.ganttChart
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
H.ganttChart = function (renderTo, options, callback) {
|
||||
var hasRenderToArg = typeof renderTo === 'string' || renderTo.nodeName, seriesOptions = options.series, defaultOptions = getOptions(), defaultLinkedTo, userOptions = options;
|
||||
options = arguments[hasRenderToArg ? 1 : 0];
|
||||
// If user hasn't defined axes as array, make it into an array and add a
|
||||
// second axis by default.
|
||||
if (!isArray(options.xAxis)) {
|
||||
options.xAxis = [options.xAxis || {}, {}];
|
||||
}
|
||||
// apply X axis options to both single and multi x axes
|
||||
options.xAxis = options.xAxis.map(function (xAxisOptions, i) {
|
||||
if (i === 1) { // Second xAxis
|
||||
defaultLinkedTo = 0;
|
||||
}
|
||||
return merge(defaultOptions.xAxis, {
|
||||
grid: {
|
||||
enabled: true
|
||||
},
|
||||
opposite: true,
|
||||
linkedTo: defaultLinkedTo
|
||||
}, xAxisOptions, // user options
|
||||
{
|
||||
type: 'datetime'
|
||||
});
|
||||
});
|
||||
// apply Y axis options to both single and multi y axes
|
||||
options.yAxis = (splat(options.yAxis || {})).map(function (yAxisOptions) {
|
||||
return merge(defaultOptions.yAxis, // #3802
|
||||
{
|
||||
grid: {
|
||||
enabled: true
|
||||
},
|
||||
staticScale: 50,
|
||||
reversed: true,
|
||||
// Set default type treegrid, but only if 'categories' is
|
||||
// undefined
|
||||
type: yAxisOptions.categories ? yAxisOptions.type : 'treegrid'
|
||||
}, yAxisOptions // user options
|
||||
);
|
||||
});
|
||||
options.series = null;
|
||||
options = merge(true, {
|
||||
chart: {
|
||||
type: 'gantt'
|
||||
},
|
||||
title: {
|
||||
text: null
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
},
|
||||
navigator: {
|
||||
series: { type: 'gantt' }
|
||||
}
|
||||
}, options, // user's options
|
||||
// forced options
|
||||
{
|
||||
isGantt: true
|
||||
});
|
||||
options.series = userOptions.series = seriesOptions;
|
||||
(options.series || []).forEach(function (series) {
|
||||
if (series.data) {
|
||||
series.data.forEach(function (point) {
|
||||
H.seriesTypes.gantt.prototype.setGanttPointAliases(point);
|
||||
});
|
||||
}
|
||||
});
|
||||
return hasRenderToArg ?
|
||||
new Chart(renderTo, options, callback) :
|
||||
new Chart(options, options); // @todo does not look correct
|
||||
};
|
||||
@@ -0,0 +1,760 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from '../Axis/Axis.js';
|
||||
import Chart from '../Chart/Chart.js';
|
||||
import H from '../Globals.js';
|
||||
import Point from '../Series/Point.js';
|
||||
import SVGRenderer from '../Renderer/SVG/SVGRenderer.js';
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, clamp = U.clamp, defined = U.defined, extend = U.extend, find = U.find, format = U.format, getOptions = U.getOptions, isNumber = U.isNumber, isString = U.isString, merge = U.merge, pick = U.pick, splat = U.splat;
|
||||
import '../Pointer.js';
|
||||
import '../Series/Series.js';
|
||||
// Has a dependency on Navigator due to the use of
|
||||
// defaultOptions.navigator
|
||||
import '../Navigator.js';
|
||||
// Has a dependency on Scrollbar due to the use of
|
||||
// defaultOptions.scrollbar
|
||||
import '../Scrollbar.js';
|
||||
// Has a dependency on RangeSelector due to the use of
|
||||
// defaultOptions.rangeSelector
|
||||
import '../../Extensions/RangeSelector.js';
|
||||
var Series = H.Series, seriesProto = Series.prototype, seriesInit = seriesProto.init, seriesProcessData = seriesProto.processData, pointTooltipFormatter = Point.prototype.tooltipFormatter;
|
||||
/**
|
||||
* Compare the values of the series against the first non-null, non-
|
||||
* zero value in the visible range. The y axis will show percentage
|
||||
* or absolute change depending on whether `compare` is set to `"percent"`
|
||||
* or `"value"`. When this is applied to multiple series, it allows
|
||||
* comparing the development of the series against each other. Adds
|
||||
* a `change` field to every point object.
|
||||
*
|
||||
* @see [compareBase](#plotOptions.series.compareBase)
|
||||
* @see [Axis.setCompare()](/class-reference/Highcharts.Axis#setCompare)
|
||||
*
|
||||
* @sample {highstock} stock/plotoptions/series-compare-percent/
|
||||
* Percent
|
||||
* @sample {highstock} stock/plotoptions/series-compare-value/
|
||||
* Value
|
||||
*
|
||||
* @type {string}
|
||||
* @since 1.0.1
|
||||
* @product highstock
|
||||
* @apioption plotOptions.series.compare
|
||||
*/
|
||||
/**
|
||||
* Defines if comparison should start from the first point within the visible
|
||||
* range or should start from the first point **before** the range.
|
||||
*
|
||||
* In other words, this flag determines if first point within the visible range
|
||||
* will have 0% (`compareStart=true`) or should have been already calculated
|
||||
* according to the previous point (`compareStart=false`).
|
||||
*
|
||||
* @sample {highstock} stock/plotoptions/series-comparestart/
|
||||
* Calculate compare within visible range
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default false
|
||||
* @since 6.0.0
|
||||
* @product highstock
|
||||
* @apioption plotOptions.series.compareStart
|
||||
*/
|
||||
/**
|
||||
* When [compare](#plotOptions.series.compare) is `percent`, this option
|
||||
* dictates whether to use 0 or 100 as the base of comparison.
|
||||
*
|
||||
* @sample {highstock} stock/plotoptions/series-comparebase/
|
||||
* Compare base is 100
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @since 5.0.6
|
||||
* @product highstock
|
||||
* @validvalue [0, 100]
|
||||
* @apioption plotOptions.series.compareBase
|
||||
*/
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Factory function for creating new stock charts. Creates a new
|
||||
* {@link Highcharts.Chart|Chart} object with different default options than the
|
||||
* basic Chart.
|
||||
*
|
||||
* @example
|
||||
* var chart = Highcharts.stockChart('container', {
|
||||
* series: [{
|
||||
* data: [1, 2, 3, 4, 5, 6, 7, 8, 9],
|
||||
* pointInterval: 24 * 60 * 60 * 1000
|
||||
* }]
|
||||
* });
|
||||
*
|
||||
* @function Highcharts.stockChart
|
||||
*
|
||||
* @param {string|Highcharts.HTMLDOMElement} [renderTo]
|
||||
* The DOM element to render to, or its id.
|
||||
*
|
||||
* @param {Highcharts.Options} options
|
||||
* The chart options structure as described in the
|
||||
* [options reference](https://api.highcharts.com/highstock).
|
||||
*
|
||||
* @param {Highcharts.ChartCallbackFunction} [callback]
|
||||
* A function to execute when the chart object is finished loading and
|
||||
* rendering. In most cases the chart is built in one thread, but in
|
||||
* Internet Explorer version 8 or less the chart is sometimes
|
||||
* initialized before the document is ready, and in these cases the
|
||||
* chart object will not be finished synchronously. As a consequence,
|
||||
* code that relies on the newly built Chart object should always run in
|
||||
* the callback. Defining a
|
||||
* [chart.events.load](https://api.highcharts.com/highstock/chart.events.load)
|
||||
* handler is equivalent.
|
||||
*
|
||||
* @return {Highcharts.Chart}
|
||||
* The chart object.
|
||||
*/
|
||||
H.StockChart = H.stockChart = function (a, b, c) {
|
||||
var hasRenderToArg = isString(a) || a.nodeName, options = arguments[hasRenderToArg ? 1 : 0], userOptions = options,
|
||||
// to increase performance, don't merge the data
|
||||
seriesOptions = options.series, defaultOptions = getOptions(), opposite,
|
||||
// Always disable startOnTick:true on the main axis when the navigator
|
||||
// is enabled (#1090)
|
||||
navigatorEnabled = pick(options.navigator && options.navigator.enabled, defaultOptions.navigator.enabled, true);
|
||||
// apply X axis options to both single and multi y axes
|
||||
options.xAxis = splat(options.xAxis || {}).map(function (xAxisOptions, i) {
|
||||
return merge({
|
||||
minPadding: 0,
|
||||
maxPadding: 0,
|
||||
overscroll: 0,
|
||||
ordinal: true,
|
||||
title: {
|
||||
text: null
|
||||
},
|
||||
labels: {
|
||||
overflow: 'justify'
|
||||
},
|
||||
showLastLabel: true
|
||||
}, defaultOptions.xAxis, // #3802
|
||||
defaultOptions.xAxis && defaultOptions.xAxis[i], // #7690
|
||||
xAxisOptions, // user options
|
||||
{
|
||||
type: 'datetime',
|
||||
categories: null
|
||||
}, (navigatorEnabled ? {
|
||||
startOnTick: false,
|
||||
endOnTick: false
|
||||
} : null));
|
||||
});
|
||||
// apply Y axis options to both single and multi y axes
|
||||
options.yAxis = splat(options.yAxis || {}).map(function (yAxisOptions, i) {
|
||||
opposite = pick(yAxisOptions.opposite, true);
|
||||
return merge({
|
||||
labels: {
|
||||
y: -2
|
||||
},
|
||||
opposite: opposite,
|
||||
/**
|
||||
* @default {highcharts} true
|
||||
* @default {highstock} false
|
||||
* @apioption yAxis.showLastLabel
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
showLastLabel: !!(
|
||||
// #6104, show last label by default for category axes
|
||||
yAxisOptions.categories ||
|
||||
yAxisOptions.type === 'category'),
|
||||
title: {
|
||||
text: null
|
||||
}
|
||||
}, defaultOptions.yAxis, // #3802
|
||||
defaultOptions.yAxis && defaultOptions.yAxis[i], // #7690
|
||||
yAxisOptions // user options
|
||||
);
|
||||
});
|
||||
options.series = null;
|
||||
options = merge({
|
||||
chart: {
|
||||
panning: {
|
||||
enabled: true,
|
||||
type: 'x'
|
||||
},
|
||||
pinchType: 'x'
|
||||
},
|
||||
navigator: {
|
||||
enabled: navigatorEnabled
|
||||
},
|
||||
scrollbar: {
|
||||
// #4988 - check if setOptions was called
|
||||
enabled: pick(defaultOptions.scrollbar.enabled, true)
|
||||
},
|
||||
rangeSelector: {
|
||||
// #4988 - check if setOptions was called
|
||||
enabled: pick(defaultOptions.rangeSelector.enabled, true)
|
||||
},
|
||||
title: {
|
||||
text: null
|
||||
},
|
||||
tooltip: {
|
||||
split: pick(defaultOptions.tooltip.split, true),
|
||||
crosshairs: true
|
||||
},
|
||||
legend: {
|
||||
enabled: false
|
||||
}
|
||||
}, options, // user's options
|
||||
{
|
||||
isStock: true // internal flag
|
||||
});
|
||||
options.series = userOptions.series = seriesOptions;
|
||||
return hasRenderToArg ?
|
||||
new Chart(a, options, c) :
|
||||
new Chart(options, b);
|
||||
};
|
||||
// Handle som Stock-specific series defaults, override the plotOptions before
|
||||
// series options are handled.
|
||||
addEvent(Series, 'setOptions', function (e) {
|
||||
var overrides;
|
||||
if (this.chart.options.isStock) {
|
||||
if (this.is('column') || this.is('columnrange')) {
|
||||
overrides = {
|
||||
borderWidth: 0,
|
||||
shadow: false
|
||||
};
|
||||
}
|
||||
else if (!this.is('scatter') && !this.is('sma')) {
|
||||
overrides = {
|
||||
marker: {
|
||||
enabled: false,
|
||||
radius: 2
|
||||
}
|
||||
};
|
||||
}
|
||||
if (overrides) {
|
||||
e.plotOptions[this.type] = merge(e.plotOptions[this.type], overrides);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Override the automatic label alignment so that the first Y axis' labels
|
||||
// are drawn on top of the grid line, and subsequent axes are drawn outside
|
||||
addEvent(Axis, 'autoLabelAlign', function (e) {
|
||||
var chart = this.chart, options = this.options, panes = chart._labelPanes = chart._labelPanes || {}, key, labelOptions = this.options.labels;
|
||||
if (this.chart.options.isStock && this.coll === 'yAxis') {
|
||||
key = options.top + ',' + options.height;
|
||||
// do it only for the first Y axis of each pane
|
||||
if (!panes[key] && labelOptions.enabled) {
|
||||
if (labelOptions.x === 15) { // default
|
||||
labelOptions.x = 0;
|
||||
}
|
||||
if (typeof labelOptions.align === 'undefined') {
|
||||
labelOptions.align = 'right';
|
||||
}
|
||||
panes[key] = this;
|
||||
e.align = 'right';
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
// Clear axis from label panes (#6071)
|
||||
addEvent(Axis, 'destroy', function () {
|
||||
var chart = this.chart, key = this.options && (this.options.top + ',' + this.options.height);
|
||||
if (key && chart._labelPanes && chart._labelPanes[key] === this) {
|
||||
delete chart._labelPanes[key];
|
||||
}
|
||||
});
|
||||
// Override getPlotLinePath to allow for multipane charts
|
||||
addEvent(Axis, 'getPlotLinePath', function (e) {
|
||||
var axis = this, series = (this.isLinked && !this.series ?
|
||||
this.linkedParent.series :
|
||||
this.series), chart = axis.chart, renderer = chart.renderer, axisLeft = axis.left, axisTop = axis.top, x1, y1, x2, y2, result = [], axes = [], // #3416 need a default array
|
||||
axes2, uniqueAxes, translatedValue = e.translatedValue, value = e.value, force = e.force, transVal;
|
||||
/**
|
||||
* Return the other axis based on either the axis option or on related
|
||||
* series.
|
||||
* @private
|
||||
*/
|
||||
function getAxis(coll) {
|
||||
var otherColl = coll === 'xAxis' ? 'yAxis' : 'xAxis', opt = axis.options[otherColl];
|
||||
// Other axis indexed by number
|
||||
if (isNumber(opt)) {
|
||||
return [chart[otherColl][opt]];
|
||||
}
|
||||
// Other axis indexed by id (like navigator)
|
||||
if (isString(opt)) {
|
||||
return [chart.get(opt)];
|
||||
}
|
||||
// Auto detect based on existing series
|
||||
return series.map(function (s) {
|
||||
return s[otherColl];
|
||||
});
|
||||
}
|
||||
if ( // For stock chart, by default render paths across the panes
|
||||
// except the case when `acrossPanes` is disabled by user (#6644)
|
||||
(chart.options.isStock && e.acrossPanes !== false) &&
|
||||
// Ignore in case of colorAxis or zAxis. #3360, #3524, #6720
|
||||
axis.coll === 'xAxis' || axis.coll === 'yAxis') {
|
||||
e.preventDefault();
|
||||
// Get the related axes based on series
|
||||
axes = getAxis(axis.coll);
|
||||
// Get the related axes based options.*Axis setting #2810
|
||||
axes2 = (axis.isXAxis ? chart.yAxis : chart.xAxis);
|
||||
axes2.forEach(function (A) {
|
||||
if (defined(A.options.id) ?
|
||||
A.options.id.indexOf('navigator') === -1 :
|
||||
true) {
|
||||
var a = (A.isXAxis ? 'yAxis' : 'xAxis'), rax = (defined(A.options[a]) ?
|
||||
chart[a][A.options[a]] :
|
||||
chart[a][0]);
|
||||
if (axis === rax) {
|
||||
axes.push(A);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Remove duplicates in the axes array. If there are no axes in the axes
|
||||
// array, we are adding an axis without data, so we need to populate
|
||||
// this with grid lines (#2796).
|
||||
uniqueAxes = axes.length ?
|
||||
[] :
|
||||
[axis.isXAxis ? chart.yAxis[0] : chart.xAxis[0]]; // #3742
|
||||
axes.forEach(function (axis2) {
|
||||
if (uniqueAxes.indexOf(axis2) === -1 &&
|
||||
// Do not draw on axis which overlap completely. #5424
|
||||
!find(uniqueAxes, function (unique) {
|
||||
return unique.pos === axis2.pos && unique.len === axis2.len;
|
||||
})) {
|
||||
uniqueAxes.push(axis2);
|
||||
}
|
||||
});
|
||||
transVal = pick(translatedValue, axis.translate(value, null, null, e.old));
|
||||
if (isNumber(transVal)) {
|
||||
if (axis.horiz) {
|
||||
uniqueAxes.forEach(function (axis2) {
|
||||
var skip;
|
||||
y1 = axis2.pos;
|
||||
y2 = y1 + axis2.len;
|
||||
x1 = x2 = Math.round(transVal + axis.transB);
|
||||
// outside plot area
|
||||
if (force !== 'pass' &&
|
||||
(x1 < axisLeft || x1 > axisLeft + axis.width)) {
|
||||
if (force) {
|
||||
x1 = x2 = clamp(x1, axisLeft, axisLeft + axis.width);
|
||||
}
|
||||
else {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
result.push(['M', x1, y1], ['L', x2, y2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
uniqueAxes.forEach(function (axis2) {
|
||||
var skip;
|
||||
x1 = axis2.pos;
|
||||
x2 = x1 + axis2.len;
|
||||
y1 = y2 = Math.round(axisTop + axis.height - transVal);
|
||||
// outside plot area
|
||||
if (force !== 'pass' &&
|
||||
(y1 < axisTop || y1 > axisTop + axis.height)) {
|
||||
if (force) {
|
||||
y1 = y2 = clamp(y1, axisTop, axisTop + axis.height);
|
||||
}
|
||||
else {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
if (!skip) {
|
||||
result.push(['M', x1, y1], ['L', x2, y2]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
e.path = result.length > 0 ?
|
||||
renderer.crispPolyLine(result, e.lineWidth || 1) :
|
||||
// #3557 getPlotLinePath in regular Highcharts also returns null
|
||||
null;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Function to crisp a line with multiple segments
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.SVGRenderer#crispPolyLine
|
||||
* @param {Highcharts.SVGPathArray} points
|
||||
* @param {number} width
|
||||
* @return {Highcharts.SVGPathArray}
|
||||
*/
|
||||
SVGRenderer.prototype.crispPolyLine = function (points, width) {
|
||||
// points format: [['M', 0, 0], ['L', 100, 0]]
|
||||
// normalize to a crisp line
|
||||
for (var i = 0; i < points.length; i = i + 2) {
|
||||
var start = points[i], end = points[i + 1];
|
||||
if (start[1] === end[1]) {
|
||||
// Substract due to #1129. Now bottom and left axis gridlines behave
|
||||
// the same.
|
||||
start[1] = end[1] =
|
||||
Math.round(start[1]) - (width % 2 / 2);
|
||||
}
|
||||
if (start[2] === end[2]) {
|
||||
start[2] = end[2] =
|
||||
Math.round(start[2]) + (width % 2 / 2);
|
||||
}
|
||||
}
|
||||
return points;
|
||||
};
|
||||
// Wrapper to hide the label
|
||||
addEvent(Axis, 'afterHideCrosshair', function () {
|
||||
if (this.crossLabel) {
|
||||
this.crossLabel = this.crossLabel.hide();
|
||||
}
|
||||
});
|
||||
// Extend crosshairs to also draw the label
|
||||
addEvent(Axis, 'afterDrawCrosshair', function (event) {
|
||||
// Check if the label has to be drawn
|
||||
if (!defined(this.crosshair.label) ||
|
||||
!this.crosshair.label.enabled ||
|
||||
!this.cross) {
|
||||
return;
|
||||
}
|
||||
var chart = this.chart, log = this.logarithmic, options = this.options.crosshair.label, // the label's options
|
||||
horiz = this.horiz, // axis orientation
|
||||
opposite = this.opposite, // axis position
|
||||
left = this.left, // left position
|
||||
top = this.top, // top position
|
||||
crossLabel = this.crossLabel, // the svgElement
|
||||
posx, posy, crossBox, formatOption = options.format, formatFormat = '', limit, align, tickInside = this.options.tickPosition === 'inside', snap = this.crosshair.snap !== false, value, offset = 0,
|
||||
// Use last available event (#5287)
|
||||
e = event.e || (this.cross && this.cross.e), point = event.point, min = this.min, max = this.max;
|
||||
if (log) {
|
||||
min = log.lin2log(min);
|
||||
max = log.lin2log(max);
|
||||
}
|
||||
align = (horiz ? 'center' : opposite ?
|
||||
(this.labelAlign === 'right' ? 'right' : 'left') :
|
||||
(this.labelAlign === 'left' ? 'left' : 'center'));
|
||||
// If the label does not exist yet, create it.
|
||||
if (!crossLabel) {
|
||||
crossLabel = this.crossLabel = chart.renderer
|
||||
.label(null, null, null, options.shape || 'callout')
|
||||
.addClass('highcharts-crosshair-label' + (this.series[0] &&
|
||||
' highcharts-color-' + this.series[0].colorIndex))
|
||||
.attr({
|
||||
align: options.align || align,
|
||||
padding: pick(options.padding, 8),
|
||||
r: pick(options.borderRadius, 3),
|
||||
zIndex: 2
|
||||
})
|
||||
.add(this.labelGroup);
|
||||
// Presentational
|
||||
if (!chart.styledMode) {
|
||||
crossLabel
|
||||
.attr({
|
||||
fill: options.backgroundColor ||
|
||||
(this.series[0] && this.series[0].color) ||
|
||||
'#666666',
|
||||
stroke: options.borderColor || '',
|
||||
'stroke-width': options.borderWidth || 0
|
||||
})
|
||||
.css(extend({
|
||||
color: '#ffffff',
|
||||
fontWeight: 'normal',
|
||||
fontSize: '11px',
|
||||
textAlign: 'center'
|
||||
}, options.style));
|
||||
}
|
||||
}
|
||||
if (horiz) {
|
||||
posx = snap ? point.plotX + left : e.chartX;
|
||||
posy = top + (opposite ? 0 : this.height);
|
||||
}
|
||||
else {
|
||||
posx = opposite ? this.width + left : 0;
|
||||
posy = snap ? point.plotY + top : e.chartY;
|
||||
}
|
||||
if (!formatOption && !options.formatter) {
|
||||
if (this.dateTime) {
|
||||
formatFormat = '%b %d, %Y';
|
||||
}
|
||||
formatOption =
|
||||
'{value' + (formatFormat ? ':' + formatFormat : '') + '}';
|
||||
}
|
||||
// Show the label
|
||||
value = snap ?
|
||||
point[this.isXAxis ? 'x' : 'y'] :
|
||||
this.toValue(horiz ? e.chartX : e.chartY);
|
||||
crossLabel.attr({
|
||||
text: formatOption ?
|
||||
format(formatOption, { value: value }, chart) :
|
||||
options.formatter.call(this, value),
|
||||
x: posx,
|
||||
y: posy,
|
||||
// Crosshair should be rendered within Axis range (#7219)
|
||||
visibility: value < min || value > max ?
|
||||
'hidden' :
|
||||
'visible'
|
||||
});
|
||||
crossBox = crossLabel.getBBox();
|
||||
// now it is placed we can correct its position
|
||||
if (isNumber(crossLabel.y)) {
|
||||
if (horiz) {
|
||||
if ((tickInside && !opposite) || (!tickInside && opposite)) {
|
||||
posy = crossLabel.y - crossBox.height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
posy = crossLabel.y - (crossBox.height / 2);
|
||||
}
|
||||
}
|
||||
// check the edges
|
||||
if (horiz) {
|
||||
limit = {
|
||||
left: left - crossBox.x,
|
||||
right: left + this.width - crossBox.x
|
||||
};
|
||||
}
|
||||
else {
|
||||
limit = {
|
||||
left: this.labelAlign === 'left' ? left : 0,
|
||||
right: this.labelAlign === 'right' ?
|
||||
left + this.width :
|
||||
chart.chartWidth
|
||||
};
|
||||
}
|
||||
// left edge
|
||||
if (crossLabel.translateX < limit.left) {
|
||||
offset = limit.left - crossLabel.translateX;
|
||||
}
|
||||
// right edge
|
||||
if (crossLabel.translateX + crossBox.width >= limit.right) {
|
||||
offset = -(crossLabel.translateX + crossBox.width - limit.right);
|
||||
}
|
||||
// show the crosslabel
|
||||
crossLabel.attr({
|
||||
x: posx + offset,
|
||||
y: posy,
|
||||
// First set x and y, then anchorX and anchorY, when box is actually
|
||||
// calculated, #5702
|
||||
anchorX: horiz ?
|
||||
posx :
|
||||
(this.opposite ? 0 : chart.chartWidth),
|
||||
anchorY: horiz ?
|
||||
(this.opposite ? chart.chartHeight : 0) :
|
||||
posy + crossBox.height / 2
|
||||
});
|
||||
});
|
||||
/* ************************************************************************** *
|
||||
* Start value compare logic *
|
||||
* ************************************************************************** */
|
||||
/**
|
||||
* Extend series.init by adding a method to modify the y value used for plotting
|
||||
* on the y axis. This method is called both from the axis when finding dataMin
|
||||
* and dataMax, and from the series.translate method.
|
||||
*
|
||||
* @ignore
|
||||
* @function Highcharts.Series#init
|
||||
*/
|
||||
seriesProto.init = function () {
|
||||
// Call base method
|
||||
seriesInit.apply(this, arguments);
|
||||
// Set comparison mode
|
||||
this.setCompare(this.options.compare);
|
||||
};
|
||||
/**
|
||||
* Highstock only. Set the
|
||||
* [compare](https://api.highcharts.com/highstock/plotOptions.series.compare)
|
||||
* mode of the series after render time. In most cases it is more useful running
|
||||
* {@link Axis#setCompare} on the X axis to update all its series.
|
||||
*
|
||||
* @function Highcharts.Series#setCompare
|
||||
*
|
||||
* @param {string} [compare]
|
||||
* Can be one of `null` (default), `"percent"` or `"value"`.
|
||||
*/
|
||||
seriesProto.setCompare = function (compare) {
|
||||
// Set or unset the modifyValue method
|
||||
this.modifyValue = (compare === 'value' || compare === 'percent') ?
|
||||
function (value, point) {
|
||||
var compareValue = this.compareValue;
|
||||
if (typeof value !== 'undefined' &&
|
||||
typeof compareValue !== 'undefined') { // #2601, #5814
|
||||
// Get the modified value
|
||||
if (compare === 'value') {
|
||||
value -= compareValue;
|
||||
// Compare percent
|
||||
}
|
||||
else {
|
||||
value = 100 * (value / compareValue) -
|
||||
(this.options.compareBase === 100 ? 0 : 100);
|
||||
}
|
||||
// record for tooltip etc.
|
||||
if (point) {
|
||||
point.change = value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return 0;
|
||||
} :
|
||||
null;
|
||||
// Survive to export, #5485
|
||||
this.userOptions.compare = compare;
|
||||
// Mark dirty
|
||||
if (this.chart.hasRendered) {
|
||||
this.isDirty = true;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Extend series.processData by finding the first y value in the plot area,
|
||||
* used for comparing the following values
|
||||
*
|
||||
* @ignore
|
||||
* @function Highcharts.Series#processData
|
||||
*/
|
||||
seriesProto.processData = function (force) {
|
||||
var series = this, i, keyIndex = -1, processedXData, processedYData, compareStart = series.options.compareStart === true ? 0 : 1, length, compareValue;
|
||||
// call base method
|
||||
seriesProcessData.apply(this, arguments);
|
||||
if (series.xAxis && series.processedYData) { // not pies
|
||||
// local variables
|
||||
processedXData = series.processedXData;
|
||||
processedYData = series.processedYData;
|
||||
length = processedYData.length;
|
||||
// For series with more than one value (range, OHLC etc), compare
|
||||
// against close or the pointValKey (#4922, #3112, #9854)
|
||||
if (series.pointArrayMap) {
|
||||
keyIndex = series.pointArrayMap.indexOf(series.options.pointValKey || series.pointValKey || 'y');
|
||||
}
|
||||
// find the first value for comparison
|
||||
for (i = 0; i < length - compareStart; i++) {
|
||||
compareValue = processedYData[i] && keyIndex > -1 ?
|
||||
processedYData[i][keyIndex] :
|
||||
processedYData[i];
|
||||
if (isNumber(compareValue) &&
|
||||
processedXData[i + compareStart] >=
|
||||
series.xAxis.min &&
|
||||
compareValue !== 0) {
|
||||
series.compareValue = compareValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
};
|
||||
// Modify series extremes
|
||||
addEvent(Series, 'afterGetExtremes', function (e) {
|
||||
var dataExtremes = e.dataExtremes;
|
||||
if (this.modifyValue && dataExtremes) {
|
||||
var extremes = [
|
||||
this.modifyValue(dataExtremes.dataMin),
|
||||
this.modifyValue(dataExtremes.dataMax)
|
||||
];
|
||||
dataExtremes.dataMin = arrayMin(extremes);
|
||||
dataExtremes.dataMax = arrayMax(extremes);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Highstock only. Set the compare mode on all series belonging to an Y axis
|
||||
* after render time.
|
||||
*
|
||||
* @see [series.plotOptions.compare](https://api.highcharts.com/highstock/series.plotOptions.compare)
|
||||
*
|
||||
* @sample stock/members/axis-setcompare/
|
||||
* Set compoare
|
||||
*
|
||||
* @function Highcharts.Axis#setCompare
|
||||
*
|
||||
* @param {string} [compare]
|
||||
* The compare mode. Can be one of `null` (default), `"value"` or
|
||||
* `"percent"`.
|
||||
*
|
||||
* @param {boolean} [redraw=true]
|
||||
* Whether to redraw the chart or to wait for a later call to
|
||||
* {@link Chart#redraw}.
|
||||
*/
|
||||
Axis.prototype.setCompare = function (compare, redraw) {
|
||||
if (!this.isXAxis) {
|
||||
this.series.forEach(function (series) {
|
||||
series.setCompare(compare);
|
||||
});
|
||||
if (pick(redraw, true)) {
|
||||
this.chart.redraw();
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Extend the tooltip formatter by adding support for the point.change variable
|
||||
* as well as the changeDecimals option.
|
||||
*
|
||||
* @ignore
|
||||
* @function Highcharts.Point#tooltipFormatter
|
||||
*
|
||||
* @param {string} pointFormat
|
||||
*/
|
||||
Point.prototype.tooltipFormatter = function (pointFormat) {
|
||||
var point = this;
|
||||
var numberFormatter = point.series.chart.numberFormatter;
|
||||
pointFormat = pointFormat.replace('{point.change}', (point.change > 0 ? '+' : '') + numberFormatter(point.change, pick(point.series.tooltipOptions.changeDecimals, 2)));
|
||||
return pointTooltipFormatter.apply(this, [pointFormat]);
|
||||
};
|
||||
/* ************************************************************************** *
|
||||
* End value compare logic *
|
||||
* ************************************************************************** */
|
||||
// Extend the Series prototype to create a separate series clip box. This is
|
||||
// related to using multiple panes, and a future pane logic should incorporate
|
||||
// this feature (#2754).
|
||||
addEvent(Series, 'render', function () {
|
||||
var chart = this.chart, clipHeight;
|
||||
// Only do this on not 3d (#2939, #5904) nor polar (#6057) charts, and only
|
||||
// if the series type handles clipping in the animate method (#2975).
|
||||
if (!(chart.is3d && chart.is3d()) &&
|
||||
!chart.polar &&
|
||||
this.xAxis &&
|
||||
!this.xAxis.isRadial // Gauge, #6192
|
||||
) {
|
||||
clipHeight = this.yAxis.len;
|
||||
// Include xAxis line width (#8031) but only if the Y axis ends on the
|
||||
// edge of the X axis (#11005).
|
||||
if (this.xAxis.axisLine) {
|
||||
var dist = chart.plotTop + chart.plotHeight -
|
||||
this.yAxis.pos - this.yAxis.len, lineHeightCorrection = Math.floor(this.xAxis.axisLine.strokeWidth() / 2);
|
||||
if (dist >= 0) {
|
||||
clipHeight -= Math.max(lineHeightCorrection - dist, 0);
|
||||
}
|
||||
}
|
||||
// First render, initial clip box
|
||||
if (!this.clipBox && this.animate) {
|
||||
this.clipBox = merge(chart.clipBox);
|
||||
this.clipBox.width = this.xAxis.len;
|
||||
this.clipBox.height = clipHeight;
|
||||
// On redrawing, resizing etc, update the clip rectangle
|
||||
}
|
||||
else if (chart[this.sharedClipKey]) {
|
||||
// animate in case resize is done during initial animation
|
||||
chart[this.sharedClipKey].animate({
|
||||
width: this.xAxis.len,
|
||||
height: clipHeight
|
||||
});
|
||||
// also change markers clip animation for consistency
|
||||
// (marker clip rects should exist only on chart init)
|
||||
if (chart[this.sharedClipKey + 'm']) {
|
||||
chart[this.sharedClipKey + 'm'].animate({
|
||||
width: this.xAxis.len
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
addEvent(Chart, 'update', function (e) {
|
||||
var options = e.options;
|
||||
// Use case: enabling scrollbar from a disabled state.
|
||||
// Scrollbar needs to be initialized from a controller, Navigator in this
|
||||
// case (#6615)
|
||||
if ('scrollbar' in options && this.navigator) {
|
||||
merge(true, this.options.scrollbar, options.scrollbar);
|
||||
this.navigator.update({}, false);
|
||||
delete options.scrollbar;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,405 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from './Globals.js';
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
import U from './Utilities.js';
|
||||
var isNumber = U.isNumber, merge = U.merge, pInt = U.pInt;
|
||||
/* 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 () {
|
||||
/* *
|
||||
*
|
||||
* 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 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.
|
||||
*
|
||||
* @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 default H.Color;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,85 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
/* 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.0',
|
||||
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: {}
|
||||
};
|
||||
export default H;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
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 __());
|
||||
};
|
||||
})();
|
||||
import H from './Globals.js';
|
||||
var charts = H.charts, doc = H.doc, noop = H.noop, win = H.win;
|
||||
import Pointer from './Pointer.js';
|
||||
import U from './Utilities.js';
|
||||
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));
|
||||
export default MSPointer;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,427 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../../Globals.js';
|
||||
import SVGElement from '../SVG/SVGElement.js';
|
||||
import SVGRenderer from '../SVG/SVGRenderer.js';
|
||||
import U from '../../Utilities.js';
|
||||
var attr = U.attr, createElement = U.createElement, css = U.css, defined = U.defined, extend = U.extend, pick = U.pick, pInt = U.pInt;
|
||||
var isFirefox = H.isFirefox, isMS = H.isMS, isWebKit = H.isWebKit, win = H.win;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
// Extend SvgElement for useHTML option.
|
||||
extend(SVGElement.prototype, /** @lends SVGElement.prototype */ {
|
||||
/**
|
||||
* Apply CSS to HTML elements. This is used in text within SVG rendering and
|
||||
* by the VML renderer
|
||||
*
|
||||
* @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
|
||||
});
|
||||
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;
|
||||
}
|
||||
});
|
||||
// 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)
|
||||
})
|
||||
.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;
|
||||
}
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,411 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
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 __());
|
||||
};
|
||||
})();
|
||||
import SVGElement from './SVGElement.js';
|
||||
import U from '../../Utilities.js';
|
||||
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));
|
||||
export default SVGLabel;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,882 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extensions to the SVGRenderer class to enable 3D shapes
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Color from '../../Color.js';
|
||||
var color = Color.parse;
|
||||
import H from '../../Globals.js';
|
||||
import Math3D from '../../../Extensions/Math3D.js';
|
||||
var perspective = Math3D.perspective, shapeArea = Math3D.shapeArea;
|
||||
import SVGElement from './SVGElement.js';
|
||||
import SVGRenderer from './SVGRenderer.js';
|
||||
import U from '../../Utilities.js';
|
||||
var animObject = U.animObject, defined = U.defined, extend = U.extend, merge = U.merge, objectEach = U.objectEach, pick = U.pick;
|
||||
var cos = Math.cos, PI = Math.PI, sin = Math.sin;
|
||||
var charts = H.charts, deg2rad = H.deg2rad,
|
||||
// internal:
|
||||
dFactor, element3dMethods, cuboidMethods;
|
||||
/*
|
||||
EXTENSION TO THE SVG-RENDERER TO ENABLE 3D SHAPES
|
||||
*/
|
||||
// HELPER METHODS
|
||||
dFactor = (4 * (Math.sqrt(2) - 1) / 3) / (PI / 2);
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* Method to construct a curved path. Can 'wrap' around more then 180 degrees.
|
||||
* @private
|
||||
*/
|
||||
function curveTo(cx, cy, rx, ry, start, end, dx, dy) {
|
||||
var result = [], arcAngle = end - start;
|
||||
if ((end > start) && (end - start > Math.PI / 2 + 0.0001)) {
|
||||
result = result.concat(curveTo(cx, cy, rx, ry, start, start + (Math.PI / 2), dx, dy));
|
||||
result = result.concat(curveTo(cx, cy, rx, ry, start + (Math.PI / 2), end, dx, dy));
|
||||
return result;
|
||||
}
|
||||
if ((end < start) && (start - end > Math.PI / 2 + 0.0001)) {
|
||||
result = result.concat(curveTo(cx, cy, rx, ry, start, start - (Math.PI / 2), dx, dy));
|
||||
result = result.concat(curveTo(cx, cy, rx, ry, start - (Math.PI / 2), end, dx, dy));
|
||||
return result;
|
||||
}
|
||||
return [[
|
||||
'C',
|
||||
cx + (rx * Math.cos(start)) -
|
||||
((rx * dFactor * arcAngle) * Math.sin(start)) + dx,
|
||||
cy + (ry * Math.sin(start)) +
|
||||
((ry * dFactor * arcAngle) * Math.cos(start)) + dy,
|
||||
cx + (rx * Math.cos(end)) +
|
||||
((rx * dFactor * arcAngle) * Math.sin(end)) + dx,
|
||||
cy + (ry * Math.sin(end)) -
|
||||
((ry * dFactor * arcAngle) * Math.cos(end)) + dy,
|
||||
cx + (rx * Math.cos(end)) + dx,
|
||||
cy + (ry * Math.sin(end)) + dy
|
||||
]];
|
||||
}
|
||||
SVGRenderer.prototype.toLinePath = function (points, closed) {
|
||||
var result = [];
|
||||
// Put "L x y" for each point
|
||||
points.forEach(function (point) {
|
||||
result.push(['L', point.x, point.y]);
|
||||
});
|
||||
if (points.length) {
|
||||
// Set the first element to M
|
||||
result[0][0] = 'M';
|
||||
// If it is a closed line, add Z
|
||||
if (closed) {
|
||||
result.push(['Z']);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
SVGRenderer.prototype.toLineSegments = function (points) {
|
||||
var result = [], m = true;
|
||||
points.forEach(function (point) {
|
||||
result.push(m ? ['M', point.x, point.y] : ['L', point.x, point.y]);
|
||||
m = !m;
|
||||
});
|
||||
return result;
|
||||
};
|
||||
// A 3-D Face is defined by it's 3D vertexes, and is only visible if it's
|
||||
// vertexes are counter-clockwise (Back-face culling). It is used as a
|
||||
// polyhedron Element
|
||||
SVGRenderer.prototype.face3d = function (args) {
|
||||
var renderer = this, ret = this.createElement('path');
|
||||
ret.vertexes = [];
|
||||
ret.insidePlotArea = false;
|
||||
ret.enabled = true;
|
||||
ret.attr = function (hash) {
|
||||
if (typeof hash === 'object' &&
|
||||
(defined(hash.enabled) ||
|
||||
defined(hash.vertexes) ||
|
||||
defined(hash.insidePlotArea))) {
|
||||
this.enabled = pick(hash.enabled, this.enabled);
|
||||
this.vertexes = pick(hash.vertexes, this.vertexes);
|
||||
this.insidePlotArea = pick(hash.insidePlotArea, this.insidePlotArea);
|
||||
delete hash.enabled;
|
||||
delete hash.vertexes;
|
||||
delete hash.insidePlotArea;
|
||||
var chart = charts[renderer.chartIndex], vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea), path = renderer.toLinePath(vertexes2d, true), area = shapeArea(vertexes2d), visibility = (this.enabled && area > 0) ? 'visible' : 'hidden';
|
||||
hash.d = path;
|
||||
hash.visibility = visibility;
|
||||
}
|
||||
return SVGElement.prototype.attr.apply(this, arguments);
|
||||
};
|
||||
ret.animate = function (params) {
|
||||
if (typeof params === 'object' &&
|
||||
(defined(params.enabled) ||
|
||||
defined(params.vertexes) ||
|
||||
defined(params.insidePlotArea))) {
|
||||
this.enabled = pick(params.enabled, this.enabled);
|
||||
this.vertexes = pick(params.vertexes, this.vertexes);
|
||||
this.insidePlotArea = pick(params.insidePlotArea, this.insidePlotArea);
|
||||
delete params.enabled;
|
||||
delete params.vertexes;
|
||||
delete params.insidePlotArea;
|
||||
var chart = charts[renderer.chartIndex], vertexes2d = perspective(this.vertexes, chart, this.insidePlotArea), path = renderer.toLinePath(vertexes2d, true), area = shapeArea(vertexes2d), visibility = (this.enabled && area > 0) ? 'visible' : 'hidden';
|
||||
params.d = path;
|
||||
this.attr('visibility', visibility);
|
||||
}
|
||||
return SVGElement.prototype.animate.apply(this, arguments);
|
||||
};
|
||||
return ret.attr(args);
|
||||
};
|
||||
// A Polyhedron is a handy way of defining a group of 3-D faces. It's only
|
||||
// attribute is `faces`, an array of attributes of each one of it's Face3D
|
||||
// instances.
|
||||
SVGRenderer.prototype.polyhedron = function (args) {
|
||||
var renderer = this, result = this.g(), destroy = result.destroy;
|
||||
if (!this.styledMode) {
|
||||
result.attr({
|
||||
'stroke-linejoin': 'round'
|
||||
});
|
||||
}
|
||||
result.faces = [];
|
||||
// destroy all children
|
||||
result.destroy = function () {
|
||||
for (var i = 0; i < result.faces.length; i++) {
|
||||
result.faces[i].destroy();
|
||||
}
|
||||
return destroy.call(this);
|
||||
};
|
||||
result.attr = function (hash, val, complete, continueAnimation) {
|
||||
if (typeof hash === 'object' && defined(hash.faces)) {
|
||||
while (result.faces.length > hash.faces.length) {
|
||||
result.faces.pop().destroy();
|
||||
}
|
||||
while (result.faces.length < hash.faces.length) {
|
||||
result.faces.push(renderer.face3d().add(result));
|
||||
}
|
||||
for (var i = 0; i < hash.faces.length; i++) {
|
||||
if (renderer.styledMode) {
|
||||
delete hash.faces[i].fill;
|
||||
}
|
||||
result.faces[i].attr(hash.faces[i], null, complete, continueAnimation);
|
||||
}
|
||||
delete hash.faces;
|
||||
}
|
||||
return SVGElement.prototype.attr.apply(this, arguments);
|
||||
};
|
||||
result.animate = function (params, duration, complete) {
|
||||
if (params && params.faces) {
|
||||
while (result.faces.length > params.faces.length) {
|
||||
result.faces.pop().destroy();
|
||||
}
|
||||
while (result.faces.length < params.faces.length) {
|
||||
result.faces.push(renderer.face3d().add(result));
|
||||
}
|
||||
for (var i = 0; i < params.faces.length; i++) {
|
||||
result.faces[i].animate(params.faces[i], duration, complete);
|
||||
}
|
||||
delete params.faces;
|
||||
}
|
||||
return SVGElement.prototype.animate.apply(this, arguments);
|
||||
};
|
||||
return result.attr(args);
|
||||
};
|
||||
// Base, abstract prototype member for 3D elements
|
||||
element3dMethods = {
|
||||
/**
|
||||
* The init is used by base - renderer.Element
|
||||
* @private
|
||||
*/
|
||||
initArgs: function (args) {
|
||||
var elem3d = this, renderer = elem3d.renderer, paths = renderer[elem3d.pathType + 'Path'](args), zIndexes = paths.zIndexes;
|
||||
// build parts
|
||||
elem3d.parts.forEach(function (part) {
|
||||
elem3d[part] = renderer.path(paths[part]).attr({
|
||||
'class': 'highcharts-3d-' + part,
|
||||
zIndex: zIndexes[part] || 0
|
||||
}).add(elem3d);
|
||||
});
|
||||
elem3d.attr({
|
||||
'stroke-linejoin': 'round',
|
||||
zIndex: zIndexes.group
|
||||
});
|
||||
// store original destroy
|
||||
elem3d.originalDestroy = elem3d.destroy;
|
||||
elem3d.destroy = elem3d.destroyParts;
|
||||
// Store information if any side of element was rendered by force.
|
||||
elem3d.forcedSides = paths.forcedSides;
|
||||
},
|
||||
/**
|
||||
* Single property setter that applies options to each part
|
||||
* @private
|
||||
*/
|
||||
singleSetterForParts: function (prop, val, values, verb, duration, complete) {
|
||||
var elem3d = this, newAttr = {}, optionsToApply = [null, null, (verb || 'attr'), duration, complete], hasZIndexes = values && values.zIndexes;
|
||||
if (!values) {
|
||||
newAttr[prop] = val;
|
||||
optionsToApply[0] = newAttr;
|
||||
}
|
||||
else {
|
||||
// It is needed to deal with the whole group zIndexing
|
||||
// in case of graph rotation
|
||||
if (hasZIndexes && hasZIndexes.group) {
|
||||
this.attr({
|
||||
zIndex: hasZIndexes.group
|
||||
});
|
||||
}
|
||||
objectEach(values, function (partVal, part) {
|
||||
newAttr[part] = {};
|
||||
newAttr[part][prop] = partVal;
|
||||
// include zIndexes if provided
|
||||
if (hasZIndexes) {
|
||||
newAttr[part].zIndex = values.zIndexes[part] || 0;
|
||||
}
|
||||
});
|
||||
optionsToApply[1] = newAttr;
|
||||
}
|
||||
return elem3d.processParts.apply(elem3d, optionsToApply);
|
||||
},
|
||||
/**
|
||||
* Calls function for each part. Used for attr, animate and destroy.
|
||||
* @private
|
||||
*/
|
||||
processParts: function (props, partsProps, verb, duration, complete) {
|
||||
var elem3d = this;
|
||||
elem3d.parts.forEach(function (part) {
|
||||
// if different props for different parts
|
||||
if (partsProps) {
|
||||
props = pick(partsProps[part], false);
|
||||
}
|
||||
// only if something to set, but allow undefined
|
||||
if (props !== false) {
|
||||
elem3d[part][verb](props, duration, complete);
|
||||
}
|
||||
});
|
||||
return elem3d;
|
||||
},
|
||||
/**
|
||||
* Destroy all parts
|
||||
* @private
|
||||
*/
|
||||
destroyParts: function () {
|
||||
this.processParts(null, null, 'destroy');
|
||||
return this.originalDestroy();
|
||||
}
|
||||
};
|
||||
// CUBOID
|
||||
cuboidMethods = merge(element3dMethods, {
|
||||
parts: ['front', 'top', 'side'],
|
||||
pathType: 'cuboid',
|
||||
attr: function (args, val, complete, continueAnimation) {
|
||||
// Resolve setting attributes by string name
|
||||
if (typeof args === 'string' && typeof val !== 'undefined') {
|
||||
var key = args;
|
||||
args = {};
|
||||
args[key] = val;
|
||||
}
|
||||
if (args.shapeArgs || defined(args.x)) {
|
||||
return this.singleSetterForParts('d', null, this.renderer[this.pathType + 'Path'](args.shapeArgs || args));
|
||||
}
|
||||
return SVGElement.prototype.attr.call(this, args, void 0, complete, continueAnimation);
|
||||
},
|
||||
animate: function (args, duration, complete) {
|
||||
if (defined(args.x) && defined(args.y)) {
|
||||
var paths = this.renderer[this.pathType + 'Path'](args), forcedSides = paths.forcedSides;
|
||||
this.singleSetterForParts('d', null, paths, 'animate', duration, complete);
|
||||
this.attr({
|
||||
zIndex: paths.zIndexes.group
|
||||
});
|
||||
// If sides that are forced to render changed, recalculate colors.
|
||||
if (forcedSides !== this.forcedSides) {
|
||||
this.forcedSides = forcedSides;
|
||||
cuboidMethods.fillSetter.call(this, this.fill);
|
||||
}
|
||||
}
|
||||
else {
|
||||
SVGElement.prototype.animate.call(this, args, duration, complete);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
fillSetter: function (fill) {
|
||||
var elem3d = this;
|
||||
elem3d.forcedSides = elem3d.forcedSides || [];
|
||||
elem3d.singleSetterForParts('fill', null, {
|
||||
front: fill,
|
||||
// Do not change color if side was forced to render.
|
||||
top: color(fill).brighten(elem3d.forcedSides.indexOf('top') >= 0 ? 0 : 0.1).get(),
|
||||
side: color(fill).brighten(elem3d.forcedSides.indexOf('side') >= 0 ? 0 : -0.1).get()
|
||||
});
|
||||
// fill for animation getter (#6776)
|
||||
elem3d.color = elem3d.fill = fill;
|
||||
return elem3d;
|
||||
}
|
||||
});
|
||||
// set them up
|
||||
SVGRenderer.prototype.elements3d = {
|
||||
base: element3dMethods,
|
||||
cuboid: cuboidMethods
|
||||
};
|
||||
/**
|
||||
* return result, generalization
|
||||
* @private
|
||||
* @requires highcharts-3d
|
||||
*/
|
||||
SVGRenderer.prototype.element3d = function (type, shapeArgs) {
|
||||
// base
|
||||
var ret = this.g();
|
||||
// extend
|
||||
extend(ret, this.elements3d[type]);
|
||||
// init
|
||||
ret.initArgs(shapeArgs);
|
||||
// return
|
||||
return ret;
|
||||
};
|
||||
// generelized, so now use simply
|
||||
SVGRenderer.prototype.cuboid = function (shapeArgs) {
|
||||
return this.element3d('cuboid', shapeArgs);
|
||||
};
|
||||
// Generates a cuboid path and zIndexes
|
||||
SVGRenderer.prototype.cuboidPath = function (shapeArgs) {
|
||||
var x = shapeArgs.x, y = shapeArgs.y, z = shapeArgs.z || 0,
|
||||
// For side calculation (right/left)
|
||||
// there is a need for height (and other shapeArgs arguments)
|
||||
// to be at least 1px
|
||||
h = shapeArgs.height, w = shapeArgs.width, d = shapeArgs.depth, chart = charts[this.chartIndex], front, back, top, bottom, left, right, shape, path1, path2, path3, isFront, isTop, isRight, options3d = chart.options.chart.options3d, alpha = options3d.alpha,
|
||||
// Priority for x axis is the biggest,
|
||||
// because of x direction has biggest influence on zIndex
|
||||
incrementX = 1000000,
|
||||
// y axis has the smallest priority in case of our charts
|
||||
// (needs to be set because of stacking)
|
||||
incrementY = 10, incrementZ = 100, zIndex = 0,
|
||||
// The 8 corners of the cube
|
||||
pArr = [{
|
||||
x: x,
|
||||
y: y,
|
||||
z: z
|
||||
}, {
|
||||
x: x + w,
|
||||
y: y,
|
||||
z: z
|
||||
}, {
|
||||
x: x + w,
|
||||
y: y + h,
|
||||
z: z
|
||||
}, {
|
||||
x: x,
|
||||
y: y + h,
|
||||
z: z
|
||||
}, {
|
||||
x: x,
|
||||
y: y + h,
|
||||
z: z + d
|
||||
}, {
|
||||
x: x + w,
|
||||
y: y + h,
|
||||
z: z + d
|
||||
}, {
|
||||
x: x + w,
|
||||
y: y,
|
||||
z: z + d
|
||||
}, {
|
||||
x: x,
|
||||
y: y,
|
||||
z: z + d
|
||||
}], forcedSides = [], pickShape;
|
||||
// apply perspective
|
||||
pArr = perspective(pArr, chart, shapeArgs.insidePlotArea);
|
||||
/**
|
||||
* helper method to decide which side is visible
|
||||
* @private
|
||||
*/
|
||||
function mapSidePath(i) {
|
||||
// Added support for 0 value in columns, where height is 0
|
||||
// but the shape is rendered.
|
||||
// Height is used from 1st to 6th element of pArr
|
||||
if (h === 0 && i > 1 && i < 6) { // [2, 3, 4, 5]
|
||||
return {
|
||||
x: pArr[i].x,
|
||||
// when height is 0 instead of cuboid we render plane
|
||||
// so it is needed to add fake 10 height to imitate cuboid
|
||||
// for side calculation
|
||||
y: pArr[i].y + 10,
|
||||
z: pArr[i].z
|
||||
};
|
||||
}
|
||||
// It is needed to calculate dummy sides (front/back) for breaking
|
||||
// points in case of x and depth values. If column has side,
|
||||
// it means that x values of front and back side are different.
|
||||
if (pArr[0].x === pArr[7].x && i >= 4) { // [4, 5, 6, 7]
|
||||
return {
|
||||
x: pArr[i].x + 10,
|
||||
// when height is 0 instead of cuboid we render plane
|
||||
// so it is needed to add fake 10 height to imitate cuboid
|
||||
// for side calculation
|
||||
y: pArr[i].y,
|
||||
z: pArr[i].z
|
||||
};
|
||||
}
|
||||
// Added dummy depth
|
||||
if (d === 0 && i < 2 || i > 5) { // [0, 1, 6, 7]
|
||||
return {
|
||||
x: pArr[i].x,
|
||||
// when height is 0 instead of cuboid we render plane
|
||||
// so it is needed to add fake 10 height to imitate cuboid
|
||||
// for side calculation
|
||||
y: pArr[i].y,
|
||||
z: pArr[i].z + 10
|
||||
};
|
||||
}
|
||||
return pArr[i];
|
||||
}
|
||||
/**
|
||||
* method creating the final side
|
||||
* @private
|
||||
*/
|
||||
function mapPath(i) {
|
||||
return pArr[i];
|
||||
}
|
||||
/**
|
||||
* First value - path with specific face
|
||||
* Second value - added information about side for later calculations.
|
||||
* Possible second values are 0 for path1, 1 for path2 and -1 for no path
|
||||
* chosen.
|
||||
* Third value - string containing information about current side
|
||||
* of cuboid for forcing side rendering.
|
||||
* @private
|
||||
*/
|
||||
pickShape = function (verticesIndex1, verticesIndex2, side) {
|
||||
var ret = [[], -1],
|
||||
// An array of vertices for cuboid face
|
||||
face1 = verticesIndex1.map(mapPath), face2 = verticesIndex2.map(mapPath),
|
||||
// dummy face is calculated the same way as standard face,
|
||||
// but if cuboid height is 0 additional height is added so it is
|
||||
// possible to use this vertices array for visible face calculation
|
||||
dummyFace1 = verticesIndex1.map(mapSidePath), dummyFace2 = verticesIndex2.map(mapSidePath);
|
||||
if (shapeArea(face1) < 0) {
|
||||
ret = [face1, 0];
|
||||
}
|
||||
else if (shapeArea(face2) < 0) {
|
||||
ret = [face2, 1];
|
||||
}
|
||||
else if (side) {
|
||||
forcedSides.push(side);
|
||||
if (shapeArea(dummyFace1) < 0) {
|
||||
ret = [face1, 0];
|
||||
}
|
||||
else if (shapeArea(dummyFace2) < 0) {
|
||||
ret = [face2, 1];
|
||||
}
|
||||
else {
|
||||
ret = [face1, 0]; // force side calculation.
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
// front or back
|
||||
front = [3, 2, 1, 0];
|
||||
back = [7, 6, 5, 4];
|
||||
shape = pickShape(front, back, 'front');
|
||||
path1 = shape[0];
|
||||
isFront = shape[1];
|
||||
// top or bottom
|
||||
top = [1, 6, 7, 0];
|
||||
bottom = [4, 5, 2, 3];
|
||||
shape = pickShape(top, bottom, 'top');
|
||||
path2 = shape[0];
|
||||
isTop = shape[1];
|
||||
// side
|
||||
right = [1, 2, 5, 6];
|
||||
left = [0, 7, 4, 3];
|
||||
shape = pickShape(right, left, 'side');
|
||||
path3 = shape[0];
|
||||
isRight = shape[1];
|
||||
/* New block used for calculating zIndex. It is basing on X, Y and Z
|
||||
position of specific columns. All zIndexes (for X, Y and Z values) are
|
||||
added to the final zIndex, where every value has different priority. The
|
||||
biggest priority is in X and Z directions, the lowest index is for
|
||||
stacked columns (Y direction and the same X and Z positions). Big
|
||||
differences between priorities is made because we need to ensure that
|
||||
even for big changes in Y and Z parameters all columns will be drawn
|
||||
correctly. */
|
||||
if (isRight === 1) {
|
||||
// It is needed to connect value with current chart width
|
||||
// for big chart size.
|
||||
zIndex += incrementX * (chart.plotWidth - x);
|
||||
}
|
||||
else if (!isRight) {
|
||||
zIndex += incrementX * x;
|
||||
}
|
||||
zIndex += incrementY * (!isTop ||
|
||||
// Numbers checked empirically
|
||||
(alpha >= 0 && alpha <= 180 || alpha < 360 && alpha > 357.5) ?
|
||||
chart.plotHeight - y : 10 + y);
|
||||
if (isFront === 1) {
|
||||
zIndex += incrementZ * (z);
|
||||
}
|
||||
else if (!isFront) {
|
||||
zIndex += incrementZ * (1000 - z);
|
||||
}
|
||||
return {
|
||||
front: this.toLinePath(path1, true),
|
||||
top: this.toLinePath(path2, true),
|
||||
side: this.toLinePath(path3, true),
|
||||
zIndexes: {
|
||||
group: Math.round(zIndex)
|
||||
},
|
||||
forcedSides: forcedSides,
|
||||
// additional info about zIndexes
|
||||
isFront: isFront,
|
||||
isTop: isTop
|
||||
}; // #4774
|
||||
};
|
||||
// SECTORS //
|
||||
SVGRenderer.prototype.arc3d = function (attribs) {
|
||||
var wrapper = this.g(), renderer = wrapper.renderer, customAttribs = ['x', 'y', 'r', 'innerR', 'start', 'end', 'depth'];
|
||||
/**
|
||||
* Get custom attributes. Don't mutate the original object and return an
|
||||
* object with only custom attr.
|
||||
* @private
|
||||
*/
|
||||
function suckOutCustom(params) {
|
||||
var hasCA = false, ca = {}, key;
|
||||
params = merge(params); // Don't mutate the original object
|
||||
for (key in params) {
|
||||
if (customAttribs.indexOf(key) !== -1) {
|
||||
ca[key] = params[key];
|
||||
delete params[key];
|
||||
hasCA = true;
|
||||
}
|
||||
}
|
||||
return hasCA ? [ca, params] : false;
|
||||
}
|
||||
attribs = merge(attribs);
|
||||
attribs.alpha = (attribs.alpha || 0) * deg2rad;
|
||||
attribs.beta = (attribs.beta || 0) * deg2rad;
|
||||
// Create the different sub sections of the shape
|
||||
wrapper.top = renderer.path();
|
||||
wrapper.side1 = renderer.path();
|
||||
wrapper.side2 = renderer.path();
|
||||
wrapper.inn = renderer.path();
|
||||
wrapper.out = renderer.path();
|
||||
// Add all faces
|
||||
wrapper.onAdd = function () {
|
||||
var parent = wrapper.parentGroup, className = wrapper.attr('class');
|
||||
wrapper.top.add(wrapper);
|
||||
// These faces are added outside the wrapper group because the z index
|
||||
// relates to neighbour elements as well
|
||||
['out', 'inn', 'side1', 'side2'].forEach(function (face) {
|
||||
wrapper[face]
|
||||
.attr({
|
||||
'class': className + ' highcharts-3d-side'
|
||||
})
|
||||
.add(parent);
|
||||
});
|
||||
};
|
||||
// Cascade to faces
|
||||
['addClass', 'removeClass'].forEach(function (fn) {
|
||||
wrapper[fn] = function () {
|
||||
var args = arguments;
|
||||
['top', 'out', 'inn', 'side1', 'side2'].forEach(function (face) {
|
||||
wrapper[face][fn].apply(wrapper[face], args);
|
||||
});
|
||||
};
|
||||
});
|
||||
/**
|
||||
* Compute the transformed paths and set them to the composite shapes
|
||||
* @private
|
||||
*/
|
||||
wrapper.setPaths = function (attribs) {
|
||||
var paths = wrapper.renderer.arc3dPath(attribs), zIndex = paths.zTop * 100;
|
||||
wrapper.attribs = attribs;
|
||||
wrapper.top.attr({ d: paths.top, zIndex: paths.zTop });
|
||||
wrapper.inn.attr({ d: paths.inn, zIndex: paths.zInn });
|
||||
wrapper.out.attr({ d: paths.out, zIndex: paths.zOut });
|
||||
wrapper.side1.attr({ d: paths.side1, zIndex: paths.zSide1 });
|
||||
wrapper.side2.attr({ d: paths.side2, zIndex: paths.zSide2 });
|
||||
// show all children
|
||||
wrapper.zIndex = zIndex;
|
||||
wrapper.attr({ zIndex: zIndex });
|
||||
// Set the radial gradient center the first time
|
||||
if (attribs.center) {
|
||||
wrapper.top.setRadialReference(attribs.center);
|
||||
delete attribs.center;
|
||||
}
|
||||
};
|
||||
wrapper.setPaths(attribs);
|
||||
/**
|
||||
* Apply the fill to the top and a darker shade to the sides
|
||||
* @private
|
||||
*/
|
||||
wrapper.fillSetter = function (value) {
|
||||
var darker = color(value).brighten(-0.1).get();
|
||||
this.fill = value;
|
||||
this.side1.attr({ fill: darker });
|
||||
this.side2.attr({ fill: darker });
|
||||
this.inn.attr({ fill: darker });
|
||||
this.out.attr({ fill: darker });
|
||||
this.top.attr({ fill: value });
|
||||
return this;
|
||||
};
|
||||
// Apply the same value to all. These properties cascade down to the
|
||||
// children when set to the composite arc3d.
|
||||
['opacity', 'translateX', 'translateY', 'visibility'].forEach(function (setter) {
|
||||
wrapper[setter + 'Setter'] = function (value, key) {
|
||||
wrapper[key] = value;
|
||||
['out', 'inn', 'side1', 'side2', 'top'].forEach(function (el) {
|
||||
wrapper[el].attr(key, value);
|
||||
});
|
||||
};
|
||||
});
|
||||
// Override attr to remove shape attributes and use those to set child paths
|
||||
wrapper.attr = function (params) {
|
||||
var ca, paramArr;
|
||||
if (typeof params === 'object') {
|
||||
paramArr = suckOutCustom(params);
|
||||
if (paramArr) {
|
||||
ca = paramArr[0];
|
||||
arguments[0] = paramArr[1];
|
||||
extend(wrapper.attribs, ca);
|
||||
wrapper.setPaths(wrapper.attribs);
|
||||
}
|
||||
}
|
||||
return SVGElement.prototype.attr.apply(wrapper, arguments);
|
||||
};
|
||||
// Override the animate function by sucking out custom parameters related to
|
||||
// the shapes directly, and update the shapes from the animation step.
|
||||
wrapper.animate = function (params, animation, complete) {
|
||||
var paramArr, from = this.attribs, to, anim, randomProp = 'data-' + Math.random().toString(26).substring(2, 9);
|
||||
// Attribute-line properties connected to 3D. These shouldn't have been
|
||||
// in the attribs collection in the first place.
|
||||
delete params.center;
|
||||
delete params.z;
|
||||
delete params.alpha;
|
||||
delete params.beta;
|
||||
anim = animObject(pick(animation, this.renderer.globalAnimation));
|
||||
if (anim.duration) {
|
||||
paramArr = suckOutCustom(params);
|
||||
// Params need to have a property in order for the step to run
|
||||
// (#5765, #7097, #7437)
|
||||
wrapper[randomProp] = 0;
|
||||
params[randomProp] = 1;
|
||||
wrapper[randomProp + 'Setter'] = H.noop;
|
||||
if (paramArr) {
|
||||
to = paramArr[0]; // custom attr
|
||||
anim.step = function (a, fx) {
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function interpolate(key) {
|
||||
return from[key] + (pick(to[key], from[key]) -
|
||||
from[key]) * fx.pos;
|
||||
}
|
||||
if (fx.prop === randomProp) {
|
||||
fx.elem.setPaths(merge(from, {
|
||||
x: interpolate('x'),
|
||||
y: interpolate('y'),
|
||||
r: interpolate('r'),
|
||||
innerR: interpolate('innerR'),
|
||||
start: interpolate('start'),
|
||||
end: interpolate('end'),
|
||||
depth: interpolate('depth')
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
animation = anim; // Only when duration (#5572)
|
||||
}
|
||||
return SVGElement.prototype.animate.call(this, params, animation, complete);
|
||||
};
|
||||
// destroy all children
|
||||
wrapper.destroy = function () {
|
||||
this.top.destroy();
|
||||
this.out.destroy();
|
||||
this.inn.destroy();
|
||||
this.side1.destroy();
|
||||
this.side2.destroy();
|
||||
return SVGElement.prototype.destroy.call(this);
|
||||
};
|
||||
// hide all children
|
||||
wrapper.hide = function () {
|
||||
this.top.hide();
|
||||
this.out.hide();
|
||||
this.inn.hide();
|
||||
this.side1.hide();
|
||||
this.side2.hide();
|
||||
};
|
||||
wrapper.show = function (inherit) {
|
||||
this.top.show(inherit);
|
||||
this.out.show(inherit);
|
||||
this.inn.show(inherit);
|
||||
this.side1.show(inherit);
|
||||
this.side2.show(inherit);
|
||||
};
|
||||
return wrapper;
|
||||
};
|
||||
// Generate the paths required to draw a 3D arc
|
||||
SVGRenderer.prototype.arc3dPath = function (shapeArgs) {
|
||||
var cx = shapeArgs.x, // x coordinate of the center
|
||||
cy = shapeArgs.y, // y coordinate of the center
|
||||
start = shapeArgs.start, // start angle
|
||||
end = shapeArgs.end - 0.00001, // end angle
|
||||
r = shapeArgs.r, // radius
|
||||
ir = shapeArgs.innerR || 0, // inner radius
|
||||
d = shapeArgs.depth || 0, // depth
|
||||
alpha = shapeArgs.alpha, // alpha rotation of the chart
|
||||
beta = shapeArgs.beta; // beta rotation of the chart
|
||||
// Derived Variables
|
||||
var cs = Math.cos(start), // cosinus of the start angle
|
||||
ss = Math.sin(start), // sinus of the start angle
|
||||
ce = Math.cos(end), // cosinus of the end angle
|
||||
se = Math.sin(end), // sinus of the end angle
|
||||
rx = r * Math.cos(beta), // x-radius
|
||||
ry = r * Math.cos(alpha), // y-radius
|
||||
irx = ir * Math.cos(beta), // x-radius (inner)
|
||||
iry = ir * Math.cos(alpha), // y-radius (inner)
|
||||
dx = d * Math.sin(beta), // distance between top and bottom in x
|
||||
dy = d * Math.sin(alpha); // distance between top and bottom in y
|
||||
// TOP
|
||||
var top = [
|
||||
['M', cx + (rx * cs), cy + (ry * ss)]
|
||||
];
|
||||
top = top.concat(curveTo(cx, cy, rx, ry, start, end, 0, 0));
|
||||
top.push([
|
||||
'L', cx + (irx * ce), cy + (iry * se)
|
||||
]);
|
||||
top = top.concat(curveTo(cx, cy, irx, iry, end, start, 0, 0));
|
||||
top.push(['Z']);
|
||||
// OUTSIDE
|
||||
var b = (beta > 0 ? Math.PI / 2 : 0), a = (alpha > 0 ? 0 : Math.PI / 2);
|
||||
var start2 = start > -b ? start : (end > -b ? -b : start), end2 = end < PI - a ? end : (start < PI - a ? PI - a : end), midEnd = 2 * PI - a;
|
||||
// When slice goes over bottom middle, need to add both, left and right
|
||||
// outer side. Additionally, when we cross right hand edge, create sharp
|
||||
// edge. Outer shape/wall:
|
||||
//
|
||||
// -------
|
||||
// / ^ \
|
||||
// 4) / / \ \ 1)
|
||||
// / / \ \
|
||||
// / / \ \
|
||||
// (c)=> ==== ==== <=(d)
|
||||
// \ \ / /
|
||||
// \ \<=(a)/ /
|
||||
// \ \ / / <=(b)
|
||||
// 3) \ v / 2)
|
||||
// -------
|
||||
//
|
||||
// (a) - inner side
|
||||
// (b) - outer side
|
||||
// (c) - left edge (sharp)
|
||||
// (d) - right edge (sharp)
|
||||
// 1..n - rendering order for startAngle = 0, when set to e.g 90, order
|
||||
// changes clockwise (1->2, 2->3, n->1) and counterclockwise for negative
|
||||
// startAngle
|
||||
var out = [
|
||||
['M', cx + (rx * cos(start2)), cy + (ry * sin(start2))]
|
||||
];
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, start2, end2, 0, 0));
|
||||
// When shape is wide, it can cross both, (c) and (d) edges, when using
|
||||
// startAngle
|
||||
if (end > midEnd && start < midEnd) {
|
||||
// Go to outer side
|
||||
out.push([
|
||||
'L', cx + (rx * cos(end2)) + dx, cy + (ry * sin(end2)) + dy
|
||||
]);
|
||||
// Curve to the right edge of the slice (d)
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end2, midEnd, dx, dy));
|
||||
// Go to the inner side
|
||||
out.push([
|
||||
'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd))
|
||||
]);
|
||||
// Curve to the true end of the slice
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end, 0, 0));
|
||||
// Go to the outer side
|
||||
out.push([
|
||||
'L', cx + (rx * cos(end)) + dx, cy + (ry * sin(end)) + dy
|
||||
]);
|
||||
// Go back to middle (d)
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end, midEnd, dx, dy));
|
||||
out.push([
|
||||
'L', cx + (rx * cos(midEnd)), cy + (ry * sin(midEnd))
|
||||
]);
|
||||
// Go back to the left edge
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, midEnd, end2, 0, 0));
|
||||
// But shape can cross also only (c) edge:
|
||||
}
|
||||
else if (end > PI - a && start < PI - a) {
|
||||
// Go to outer side
|
||||
out.push([
|
||||
'L',
|
||||
cx + (rx * Math.cos(end2)) + dx,
|
||||
cy + (ry * Math.sin(end2)) + dy
|
||||
]);
|
||||
// Curve to the true end of the slice
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end2, end, dx, dy));
|
||||
// Go to the inner side
|
||||
out.push([
|
||||
'L', cx + (rx * Math.cos(end)), cy + (ry * Math.sin(end))
|
||||
]);
|
||||
// Go back to the artifical end2
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end, end2, 0, 0));
|
||||
}
|
||||
out.push([
|
||||
'L', cx + (rx * Math.cos(end2)) + dx, cy + (ry * Math.sin(end2)) + dy
|
||||
]);
|
||||
out = out.concat(curveTo(cx, cy, rx, ry, end2, start2, dx, dy));
|
||||
out.push(['Z']);
|
||||
// INSIDE
|
||||
var inn = [
|
||||
['M', cx + (irx * cs), cy + (iry * ss)]
|
||||
];
|
||||
inn = inn.concat(curveTo(cx, cy, irx, iry, start, end, 0, 0));
|
||||
inn.push([
|
||||
'L', cx + (irx * Math.cos(end)) + dx, cy + (iry * Math.sin(end)) + dy
|
||||
]);
|
||||
inn = inn.concat(curveTo(cx, cy, irx, iry, end, start, dx, dy));
|
||||
inn.push(['Z']);
|
||||
// SIDES
|
||||
var side1 = [
|
||||
['M', cx + (rx * cs), cy + (ry * ss)],
|
||||
['L', cx + (rx * cs) + dx, cy + (ry * ss) + dy],
|
||||
['L', cx + (irx * cs) + dx, cy + (iry * ss) + dy],
|
||||
['L', cx + (irx * cs), cy + (iry * ss)],
|
||||
['Z']
|
||||
];
|
||||
var side2 = [
|
||||
['M', cx + (rx * ce), cy + (ry * se)],
|
||||
['L', cx + (rx * ce) + dx, cy + (ry * se) + dy],
|
||||
['L', cx + (irx * ce) + dx, cy + (iry * se) + dy],
|
||||
['L', cx + (irx * ce), cy + (iry * se)],
|
||||
['Z']
|
||||
];
|
||||
// correction for changed position of vanishing point caused by alpha and
|
||||
// beta rotations
|
||||
var angleCorr = Math.atan2(dy, -dx), angleEnd = Math.abs(end + angleCorr), angleStart = Math.abs(start + angleCorr), angleMid = Math.abs((start + end) / 2 + angleCorr);
|
||||
/**
|
||||
* set to 0-PI range
|
||||
* @private
|
||||
*/
|
||||
function toZeroPIRange(angle) {
|
||||
angle = angle % (2 * Math.PI);
|
||||
if (angle > Math.PI) {
|
||||
angle = 2 * Math.PI - angle;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
angleEnd = toZeroPIRange(angleEnd);
|
||||
angleStart = toZeroPIRange(angleStart);
|
||||
angleMid = toZeroPIRange(angleMid);
|
||||
// *1e5 is to compensate pInt in zIndexSetter
|
||||
var incPrecision = 1e5, a1 = angleMid * incPrecision, a2 = angleStart * incPrecision, a3 = angleEnd * incPrecision;
|
||||
return {
|
||||
top: top,
|
||||
// max angle is PI, so this is always higher
|
||||
zTop: Math.PI * incPrecision + 1,
|
||||
out: out,
|
||||
zOut: Math.max(a1, a2, a3),
|
||||
inn: inn,
|
||||
zInn: Math.max(a1, a2, a3),
|
||||
side1: side1,
|
||||
zSide1: a3 * 0.99,
|
||||
side2: side2,
|
||||
zSide2: a2 * 0.99
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extension to the VML Renderer
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from '../../Axis/Axis.js';
|
||||
import H from '../../Globals.js';
|
||||
import SVGRenderer from '../../Renderer/SVG/SVGRenderer.js';
|
||||
import U from '../../Utilities.js';
|
||||
var setOptions = U.setOptions;
|
||||
import VMLAxis3D from '../../Axis/VMLAxis3D.js';
|
||||
var VMLRenderer = H.VMLRenderer;
|
||||
if (VMLRenderer) {
|
||||
setOptions({ animate: false });
|
||||
VMLRenderer.prototype.face3d = SVGRenderer.prototype.face3d;
|
||||
VMLRenderer.prototype.polyhedron = SVGRenderer.prototype.polyhedron;
|
||||
VMLRenderer.prototype.elements3d = SVGRenderer.prototype.elements3d;
|
||||
VMLRenderer.prototype.element3d = SVGRenderer.prototype.element3d;
|
||||
VMLRenderer.prototype.cuboid = SVGRenderer.prototype.cuboid;
|
||||
VMLRenderer.prototype.cuboidPath = SVGRenderer.prototype.cuboidPath;
|
||||
VMLRenderer.prototype.toLinePath = SVGRenderer.prototype.toLinePath;
|
||||
VMLRenderer.prototype.toLineSegments = SVGRenderer.prototype.toLineSegments;
|
||||
VMLRenderer.prototype.arc3d = function (shapeArgs) {
|
||||
var result = SVGRenderer.prototype.arc3d.call(this, shapeArgs);
|
||||
result.css({ zIndex: result.zIndex });
|
||||
return result;
|
||||
};
|
||||
H.VMLRenderer.prototype.arc3dPath = SVGRenderer.prototype.arc3dPath;
|
||||
VMLAxis3D.compose(Axis);
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Chart from './Chart/Chart.js';
|
||||
import U from './Utilities.js';
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,813 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Axis from './Axis/Axis.js';
|
||||
import H from './Globals.js';
|
||||
import ScrollbarAxis from './Axis/ScrollbarAxis.js';
|
||||
import U from './Utilities.js';
|
||||
var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, fireEvent = U.fireEvent, merge = U.merge, pick = U.pick, removeEvent = U.removeEvent;
|
||||
import O from './Options.js';
|
||||
var defaultOptions = O.defaultOptions;
|
||||
var hasTouch = H.hasTouch, isTouchDevice = H.isTouchDevice;
|
||||
/**
|
||||
* When we have vertical scrollbar, rifles and arrow in buttons should be
|
||||
* rotated. The same method is used in Navigator's handles, to rotate them.
|
||||
*
|
||||
* @function Highcharts.swapXY
|
||||
*
|
||||
* @param {Highcharts.SVGPathArray} path
|
||||
* Path to be rotated.
|
||||
*
|
||||
* @param {boolean} [vertical]
|
||||
* If vertical scrollbar, swap x-y values.
|
||||
*
|
||||
* @return {Highcharts.SVGPathArray}
|
||||
* Rotated path.
|
||||
*
|
||||
* @requires modules/stock
|
||||
*/
|
||||
var swapXY = H.swapXY = function (path, vertical) {
|
||||
if (vertical) {
|
||||
path.forEach(function (seg) {
|
||||
var len = seg.length;
|
||||
var temp;
|
||||
for (var i = 0; i < len; i += 2) {
|
||||
temp = seg[i + 1];
|
||||
if (typeof temp === 'number') {
|
||||
seg[i + 1] = seg[i + 2];
|
||||
seg[i + 2] = temp;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return path;
|
||||
};
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* A reusable scrollbar, internally used in Highstock's navigator and optionally
|
||||
* on individual axes.
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.Scrollbar
|
||||
* @param {Highcharts.SVGRenderer} renderer
|
||||
* @param {Highcharts.ScrollbarOptions} options
|
||||
* @param {Highcharts.Chart} chart
|
||||
*/
|
||||
var Scrollbar = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function Scrollbar(renderer, options, chart) {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
this._events = [];
|
||||
this.chartX = 0;
|
||||
this.chartY = 0;
|
||||
this.from = 0;
|
||||
this.group = void 0;
|
||||
this.scrollbar = void 0;
|
||||
this.scrollbarButtons = [];
|
||||
this.scrollbarGroup = void 0;
|
||||
this.scrollbarLeft = 0;
|
||||
this.scrollbarRifles = void 0;
|
||||
this.scrollbarStrokeWidth = 1;
|
||||
this.scrollbarTop = 0;
|
||||
this.size = 0;
|
||||
this.to = 0;
|
||||
this.track = void 0;
|
||||
this.trackBorderWidth = 1;
|
||||
this.userOptions = {};
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.chart = chart;
|
||||
this.options = options;
|
||||
this.renderer = chart.renderer;
|
||||
this.init(renderer, options, chart);
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Set up the mouse and touch events for the Scrollbar
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#addEvents
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.addEvents = function () {
|
||||
var buttonsOrder = this.options.inverted ? [1, 0] : [0, 1], buttons = this.scrollbarButtons, bar = this.scrollbarGroup.element, track = this.track.element, mouseDownHandler = this.mouseDownHandler.bind(this), mouseMoveHandler = this.mouseMoveHandler.bind(this), mouseUpHandler = this.mouseUpHandler.bind(this), _events;
|
||||
// Mouse events
|
||||
_events = [
|
||||
[buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick.bind(this)],
|
||||
[buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick.bind(this)],
|
||||
[track, 'click', this.trackClick.bind(this)],
|
||||
[bar, 'mousedown', mouseDownHandler],
|
||||
[bar.ownerDocument, 'mousemove', mouseMoveHandler],
|
||||
[bar.ownerDocument, 'mouseup', mouseUpHandler]
|
||||
];
|
||||
// Touch events
|
||||
if (hasTouch) {
|
||||
_events.push([bar, 'touchstart', mouseDownHandler], [bar.ownerDocument, 'touchmove', mouseMoveHandler], [bar.ownerDocument, 'touchend', mouseUpHandler]);
|
||||
}
|
||||
// Add them all
|
||||
_events.forEach(function (args) {
|
||||
addEvent.apply(null, args);
|
||||
});
|
||||
this._events = _events;
|
||||
};
|
||||
Scrollbar.prototype.buttonToMaxClick = function (e) {
|
||||
var scroller = this;
|
||||
var range = (scroller.to - scroller.from) * pick(scroller.options.step, 0.2);
|
||||
scroller.updatePosition(scroller.from + range, scroller.to + range);
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMEvent: e
|
||||
});
|
||||
};
|
||||
Scrollbar.prototype.buttonToMinClick = function (e) {
|
||||
var scroller = this;
|
||||
var range = correctFloat(scroller.to - scroller.from) *
|
||||
pick(scroller.options.step, 0.2);
|
||||
scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range));
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMEvent: e
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Get normalized (0-1) cursor position over the scrollbar
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#cursorToScrollbarPosition
|
||||
*
|
||||
* @param {*} normalizedEvent
|
||||
* normalized event, with chartX and chartY values
|
||||
*
|
||||
* @return {Highcharts.Dictionary<number>}
|
||||
* Local position {chartX, chartY}
|
||||
*/
|
||||
Scrollbar.prototype.cursorToScrollbarPosition = function (normalizedEvent) {
|
||||
var scroller = this, options = scroller.options, minWidthDifference = options.minWidth > scroller.calculatedWidth ?
|
||||
options.minWidth :
|
||||
0; // minWidth distorts translation
|
||||
return {
|
||||
chartX: (normalizedEvent.chartX - scroller.x -
|
||||
scroller.xOffset) /
|
||||
(scroller.barWidth - minWidthDifference),
|
||||
chartY: (normalizedEvent.chartY - scroller.y -
|
||||
scroller.yOffset) /
|
||||
(scroller.barWidth - minWidthDifference)
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Destroys allocated elements.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#destroy
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.destroy = function () {
|
||||
var scroller = this.chart.scroller;
|
||||
// Disconnect events added in addEvents
|
||||
this.removeEvents();
|
||||
// Destroy properties
|
||||
[
|
||||
'track',
|
||||
'scrollbarRifles',
|
||||
'scrollbar',
|
||||
'scrollbarGroup',
|
||||
'group'
|
||||
].forEach(function (prop) {
|
||||
if (this[prop] && this[prop].destroy) {
|
||||
this[prop] = this[prop].destroy();
|
||||
}
|
||||
}, this);
|
||||
// #6421, chart may have more scrollbars
|
||||
if (scroller && this === scroller.scrollbar) {
|
||||
scroller.scrollbar = null;
|
||||
// Destroy elements in collection
|
||||
destroyObjectProperties(scroller.scrollbarButtons);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Draw the scrollbar buttons with arrows
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#drawScrollbarButton
|
||||
* @param {number} index
|
||||
* 0 is left, 1 is right
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.drawScrollbarButton = function (index) {
|
||||
var scroller = this, renderer = scroller.renderer, scrollbarButtons = scroller.scrollbarButtons, options = scroller.options, size = scroller.size, group, tempElem;
|
||||
group = renderer.g().add(scroller.group);
|
||||
scrollbarButtons.push(group);
|
||||
// Create a rectangle for the scrollbar button
|
||||
tempElem = renderer.rect()
|
||||
.addClass('highcharts-scrollbar-button')
|
||||
.add(group);
|
||||
// Presentational attributes
|
||||
if (!this.chart.styledMode) {
|
||||
tempElem.attr({
|
||||
stroke: options.buttonBorderColor,
|
||||
'stroke-width': options.buttonBorderWidth,
|
||||
fill: options.buttonBackgroundColor
|
||||
});
|
||||
}
|
||||
// Place the rectangle based on the rendered stroke width
|
||||
tempElem.attr(tempElem.crisp({
|
||||
x: -0.5,
|
||||
y: -0.5,
|
||||
width: size + 1,
|
||||
height: size + 1,
|
||||
r: options.buttonBorderRadius
|
||||
}, tempElem.strokeWidth()));
|
||||
// Button arrow
|
||||
tempElem = renderer
|
||||
.path(swapXY([[
|
||||
'M',
|
||||
size / 2 + (index ? -1 : 1),
|
||||
size / 2 - 3
|
||||
], [
|
||||
'L',
|
||||
size / 2 + (index ? -1 : 1),
|
||||
size / 2 + 3
|
||||
], [
|
||||
'L',
|
||||
size / 2 + (index ? 2 : -2),
|
||||
size / 2
|
||||
]], options.vertical))
|
||||
.addClass('highcharts-scrollbar-arrow')
|
||||
.add(scrollbarButtons[index]);
|
||||
if (!this.chart.styledMode) {
|
||||
tempElem.attr({
|
||||
fill: options.buttonArrowColor
|
||||
});
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#init
|
||||
* @param {Highcharts.SVGRenderer} renderer
|
||||
* @param {Highcharts.ScrollbarOptions} options
|
||||
* @param {Highcharts.Chart} chart
|
||||
*/
|
||||
Scrollbar.prototype.init = function (renderer, options, chart) {
|
||||
this.scrollbarButtons = [];
|
||||
this.renderer = renderer;
|
||||
this.userOptions = options;
|
||||
this.options = merge(Scrollbar.defaultOptions, options);
|
||||
this.chart = chart;
|
||||
// backward compatibility
|
||||
this.size = pick(this.options.size, this.options.height);
|
||||
// Init
|
||||
if (options.enabled) {
|
||||
this.render();
|
||||
this.addEvents();
|
||||
}
|
||||
};
|
||||
Scrollbar.prototype.mouseDownHandler = function (e) {
|
||||
var scroller = this;
|
||||
var normalizedEvent = scroller.chart.pointer.normalize(e), mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent);
|
||||
scroller.chartX = mousePosition.chartX;
|
||||
scroller.chartY = mousePosition.chartY;
|
||||
scroller.initPositions = [scroller.from, scroller.to];
|
||||
scroller.grabbedCenter = true;
|
||||
};
|
||||
/**
|
||||
* Event handler for the mouse move event.
|
||||
* @private
|
||||
*/
|
||||
Scrollbar.prototype.mouseMoveHandler = function (e) {
|
||||
var scroller = this;
|
||||
var normalizedEvent = scroller.chart.pointer.normalize(e), options = scroller.options, direction = options.vertical ? 'chartY' : 'chartX', initPositions = scroller.initPositions || [], scrollPosition, chartPosition, change;
|
||||
// In iOS, a mousemove event with e.pageX === 0 is fired when
|
||||
// holding the finger down in the center of the scrollbar. This
|
||||
// should be ignored.
|
||||
if (scroller.grabbedCenter &&
|
||||
// #4696, scrollbar failed on Android
|
||||
(!e.touches || e.touches[0][direction] !== 0)) {
|
||||
chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction];
|
||||
scrollPosition = scroller[direction];
|
||||
change = chartPosition - scrollPosition;
|
||||
scroller.hasDragged = true;
|
||||
scroller.updatePosition(initPositions[0] + change, initPositions[1] + change);
|
||||
if (scroller.hasDragged) {
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMType: e.type,
|
||||
DOMEvent: e
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Event handler for the mouse up event.
|
||||
* @private
|
||||
*/
|
||||
Scrollbar.prototype.mouseUpHandler = function (e) {
|
||||
var scroller = this;
|
||||
if (scroller.hasDragged) {
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMType: e.type,
|
||||
DOMEvent: e
|
||||
});
|
||||
}
|
||||
scroller.grabbedCenter =
|
||||
scroller.hasDragged =
|
||||
scroller.chartX =
|
||||
scroller.chartY = null;
|
||||
};
|
||||
/**
|
||||
* Position the scrollbar, method called from a parent with defined
|
||||
* dimensions.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#position
|
||||
* @param {number} x
|
||||
* x-position on the chart
|
||||
* @param {number} y
|
||||
* y-position on the chart
|
||||
* @param {number} width
|
||||
* width of the scrollbar
|
||||
* @param {number} height
|
||||
* height of the scorllbar
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.position = function (x, y, width, height) {
|
||||
var scroller = this, options = scroller.options, vertical = options.vertical, xOffset = height, yOffset = 0, method = scroller.rendered ? 'animate' : 'attr';
|
||||
scroller.x = x;
|
||||
scroller.y = y + this.trackBorderWidth;
|
||||
scroller.width = width; // width with buttons
|
||||
scroller.height = height;
|
||||
scroller.xOffset = xOffset;
|
||||
scroller.yOffset = yOffset;
|
||||
// If Scrollbar is a vertical type, swap options:
|
||||
if (vertical) {
|
||||
scroller.width = scroller.yOffset = width = yOffset = scroller.size;
|
||||
scroller.xOffset = xOffset = 0;
|
||||
scroller.barWidth = height - width * 2; // width without buttons
|
||||
scroller.x = x = x + scroller.options.margin;
|
||||
}
|
||||
else {
|
||||
scroller.height = scroller.xOffset = height = xOffset =
|
||||
scroller.size;
|
||||
scroller.barWidth = width - height * 2; // width without buttons
|
||||
scroller.y = scroller.y + scroller.options.margin;
|
||||
}
|
||||
// Set general position for a group:
|
||||
scroller.group[method]({
|
||||
translateX: x,
|
||||
translateY: scroller.y
|
||||
});
|
||||
// Resize background/track:
|
||||
scroller.track[method]({
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
// Move right/bottom button ot it's place:
|
||||
scroller.scrollbarButtons[1][method]({
|
||||
translateX: vertical ? 0 : width - xOffset,
|
||||
translateY: vertical ? height - yOffset : 0
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Removes the event handlers attached previously with addEvents.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#removeEvents
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.removeEvents = function () {
|
||||
this._events.forEach(function (args) {
|
||||
removeEvent.apply(null, args);
|
||||
});
|
||||
this._events.length = 0;
|
||||
};
|
||||
/**
|
||||
* Render scrollbar with all required items.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#render
|
||||
*/
|
||||
Scrollbar.prototype.render = function () {
|
||||
var scroller = this, renderer = scroller.renderer, options = scroller.options, size = scroller.size, styledMode = this.chart.styledMode, group;
|
||||
// Draw the scrollbar group
|
||||
scroller.group = group = renderer.g('scrollbar').attr({
|
||||
zIndex: options.zIndex,
|
||||
translateY: -99999
|
||||
}).add();
|
||||
// Draw the scrollbar track:
|
||||
scroller.track = renderer.rect()
|
||||
.addClass('highcharts-scrollbar-track')
|
||||
.attr({
|
||||
x: 0,
|
||||
r: options.trackBorderRadius || 0,
|
||||
height: size,
|
||||
width: size
|
||||
}).add(group);
|
||||
if (!styledMode) {
|
||||
scroller.track.attr({
|
||||
fill: options.trackBackgroundColor,
|
||||
stroke: options.trackBorderColor,
|
||||
'stroke-width': options.trackBorderWidth
|
||||
});
|
||||
}
|
||||
this.trackBorderWidth = scroller.track.strokeWidth();
|
||||
scroller.track.attr({
|
||||
y: -this.trackBorderWidth % 2 / 2
|
||||
});
|
||||
// Draw the scrollbar itself
|
||||
scroller.scrollbarGroup = renderer.g().add(group);
|
||||
scroller.scrollbar = renderer.rect()
|
||||
.addClass('highcharts-scrollbar-thumb')
|
||||
.attr({
|
||||
height: size,
|
||||
width: size,
|
||||
r: options.barBorderRadius || 0
|
||||
}).add(scroller.scrollbarGroup);
|
||||
scroller.scrollbarRifles = renderer
|
||||
.path(swapXY([
|
||||
['M', -3, size / 4],
|
||||
['L', -3, 2 * size / 3],
|
||||
['M', 0, size / 4],
|
||||
['L', 0, 2 * size / 3],
|
||||
['M', 3, size / 4],
|
||||
['L', 3, 2 * size / 3]
|
||||
], options.vertical))
|
||||
.addClass('highcharts-scrollbar-rifles')
|
||||
.add(scroller.scrollbarGroup);
|
||||
if (!styledMode) {
|
||||
scroller.scrollbar.attr({
|
||||
fill: options.barBackgroundColor,
|
||||
stroke: options.barBorderColor,
|
||||
'stroke-width': options.barBorderWidth
|
||||
});
|
||||
scroller.scrollbarRifles.attr({
|
||||
stroke: options.rifleColor,
|
||||
'stroke-width': 1
|
||||
});
|
||||
}
|
||||
scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth();
|
||||
scroller.scrollbarGroup.translate(-scroller.scrollbarStrokeWidth % 2 / 2, -scroller.scrollbarStrokeWidth % 2 / 2);
|
||||
// Draw the buttons:
|
||||
scroller.drawScrollbarButton(0);
|
||||
scroller.drawScrollbarButton(1);
|
||||
};
|
||||
/**
|
||||
* Set scrollbar size, with a given scale.
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#setRange
|
||||
* @param {number} from
|
||||
* scale (0-1) where bar should start
|
||||
* @param {number} to
|
||||
* scale (0-1) where bar should end
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.setRange = function (from, to) {
|
||||
var scroller = this, options = scroller.options, vertical = options.vertical, minWidth = options.minWidth, fullWidth = scroller.barWidth, fromPX, toPX, newPos, newSize, newRiflesPos, method = (this.rendered &&
|
||||
!this.hasDragged &&
|
||||
!(this.chart.navigator && this.chart.navigator.hasDragged)) ? 'animate' : 'attr';
|
||||
if (!defined(fullWidth)) {
|
||||
return;
|
||||
}
|
||||
from = Math.max(from, 0);
|
||||
fromPX = Math.ceil(fullWidth * from);
|
||||
toPX = fullWidth * Math.min(to, 1);
|
||||
scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX);
|
||||
// We need to recalculate position, if minWidth is used
|
||||
if (newSize < minWidth) {
|
||||
fromPX = (fullWidth - minWidth + newSize) * from;
|
||||
newSize = minWidth;
|
||||
}
|
||||
newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset);
|
||||
newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2
|
||||
// Store current position:
|
||||
scroller.from = from;
|
||||
scroller.to = to;
|
||||
if (!vertical) {
|
||||
scroller.scrollbarGroup[method]({
|
||||
translateX: newPos
|
||||
});
|
||||
scroller.scrollbar[method]({
|
||||
width: newSize
|
||||
});
|
||||
scroller.scrollbarRifles[method]({
|
||||
translateX: newRiflesPos
|
||||
});
|
||||
scroller.scrollbarLeft = newPos;
|
||||
scroller.scrollbarTop = 0;
|
||||
}
|
||||
else {
|
||||
scroller.scrollbarGroup[method]({
|
||||
translateY: newPos
|
||||
});
|
||||
scroller.scrollbar[method]({
|
||||
height: newSize
|
||||
});
|
||||
scroller.scrollbarRifles[method]({
|
||||
translateY: newRiflesPos
|
||||
});
|
||||
scroller.scrollbarTop = newPos;
|
||||
scroller.scrollbarLeft = 0;
|
||||
}
|
||||
if (newSize <= 12) {
|
||||
scroller.scrollbarRifles.hide();
|
||||
}
|
||||
else {
|
||||
scroller.scrollbarRifles.show(true);
|
||||
}
|
||||
// Show or hide the scrollbar based on the showFull setting
|
||||
if (options.showFull === false) {
|
||||
if (from <= 0 && to >= 1) {
|
||||
scroller.group.hide();
|
||||
}
|
||||
else {
|
||||
scroller.group.show();
|
||||
}
|
||||
}
|
||||
scroller.rendered = true;
|
||||
};
|
||||
Scrollbar.prototype.trackClick = function (e) {
|
||||
var scroller = this;
|
||||
var normalizedEvent = scroller.chart.pointer.normalize(e), range = scroller.to - scroller.from, top = scroller.y + scroller.scrollbarTop, left = scroller.x + scroller.scrollbarLeft;
|
||||
if ((scroller.options.vertical && normalizedEvent.chartY > top) ||
|
||||
(!scroller.options.vertical && normalizedEvent.chartX > left)) {
|
||||
// On the top or on the left side of the track:
|
||||
scroller.updatePosition(scroller.from + range, scroller.to + range);
|
||||
}
|
||||
else {
|
||||
// On the bottom or the right side of the track:
|
||||
scroller.updatePosition(scroller.from - range, scroller.to - range);
|
||||
}
|
||||
fireEvent(scroller, 'changed', {
|
||||
from: scroller.from,
|
||||
to: scroller.to,
|
||||
trigger: 'scrollbar',
|
||||
DOMEvent: e
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Update the scrollbar with new options
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#update
|
||||
* @param {Highcharts.ScrollbarOptions} options
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.update = function (options) {
|
||||
this.destroy();
|
||||
this.init(this.chart.renderer, merge(true, this.options, options), this.chart);
|
||||
};
|
||||
/**
|
||||
* Update position option in the Scrollbar, with normalized 0-1 scale
|
||||
*
|
||||
* @private
|
||||
* @function Highcharts.Scrollbar#updatePosition
|
||||
* @param {number} from
|
||||
* @param {number} to
|
||||
* @return {void}
|
||||
*/
|
||||
Scrollbar.prototype.updatePosition = function (from, to) {
|
||||
if (to > 1) {
|
||||
from = correctFloat(1 - correctFloat(to - from));
|
||||
to = 1;
|
||||
}
|
||||
if (from < 0) {
|
||||
to = correctFloat(to - from);
|
||||
from = 0;
|
||||
}
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
*
|
||||
* The scrollbar is a means of panning over the X axis of a stock chart.
|
||||
* Scrollbars can also be applied to other types of axes.
|
||||
*
|
||||
* Another approach to scrollable charts is the [chart.scrollablePlotArea](
|
||||
* https://api.highcharts.com/highcharts/chart.scrollablePlotArea) option that
|
||||
* is especially suitable for simpler cartesian charts on mobile.
|
||||
*
|
||||
* 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 stock/yaxis/inverted-bar-scrollbar/
|
||||
* A scrollbar on a simple bar chart
|
||||
*
|
||||
* @product highstock gantt
|
||||
* @optionparent scrollbar
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
Scrollbar.defaultOptions = {
|
||||
/**
|
||||
* The height of the scrollbar. The height also applies to the width
|
||||
* of the scroll arrows so that they are always squares. Defaults to
|
||||
* 20 for touch devices and 14 for mouse devices.
|
||||
*
|
||||
* @sample stock/scrollbar/height/
|
||||
* A 30px scrollbar
|
||||
*
|
||||
* @type {number}
|
||||
* @default 20/14
|
||||
*/
|
||||
height: isTouchDevice ? 20 : 14,
|
||||
/**
|
||||
* The border rounding radius of the bar.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
barBorderRadius: 0,
|
||||
/**
|
||||
* The corner radius of the scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
buttonBorderRadius: 0,
|
||||
/**
|
||||
* Enable or disable the scrollbar.
|
||||
*
|
||||
* @sample stock/scrollbar/enabled/
|
||||
* Disable the scrollbar, only use navigator
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
* @apioption scrollbar.enabled
|
||||
*/
|
||||
/**
|
||||
* Whether to redraw the main chart as the scrollbar or the navigator
|
||||
* zoomed window is moved. Defaults to `true` for modern browsers and
|
||||
* `false` for legacy IE browsers as well as mobile devices.
|
||||
*
|
||||
* @sample stock/scrollbar/liveredraw
|
||||
* Setting live redraw to false
|
||||
*
|
||||
* @type {boolean}
|
||||
* @since 1.3
|
||||
*/
|
||||
liveRedraw: void 0,
|
||||
/**
|
||||
* The margin between the scrollbar and its axis when the scrollbar is
|
||||
* applied directly to an axis.
|
||||
*/
|
||||
margin: 10,
|
||||
/**
|
||||
* The minimum width of the scrollbar.
|
||||
*
|
||||
* @since 1.2.5
|
||||
*/
|
||||
minWidth: 6,
|
||||
/**
|
||||
* Whether to show or hide the scrollbar when the scrolled content is
|
||||
* zoomed out to it full extent.
|
||||
*
|
||||
* @type {boolean}
|
||||
* @default true
|
||||
* @apioption scrollbar.showFull
|
||||
*/
|
||||
step: 0.2,
|
||||
/**
|
||||
* The z index of the scrollbar group.
|
||||
*/
|
||||
zIndex: 3,
|
||||
/**
|
||||
* The background color of the scrollbar itself.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
barBackgroundColor: '#cccccc',
|
||||
/**
|
||||
* The width of the bar's border.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
barBorderWidth: 1,
|
||||
/**
|
||||
* The color of the scrollbar's border.
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
barBorderColor: '#cccccc',
|
||||
/**
|
||||
* The color of the small arrow inside the scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
buttonArrowColor: '#333333',
|
||||
/**
|
||||
* The color of scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
buttonBackgroundColor: '#e6e6e6',
|
||||
/**
|
||||
* The color of the border of the scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
buttonBorderColor: '#cccccc',
|
||||
/**
|
||||
* The border width of the scrollbar buttons.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
buttonBorderWidth: 1,
|
||||
/**
|
||||
* The color of the small rifles in the middle of the scrollbar.
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
rifleColor: '#333333',
|
||||
/**
|
||||
* The color of the track background.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
trackBackgroundColor: '#f2f2f2',
|
||||
/**
|
||||
* The color of the border of the scrollbar track.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
|
||||
*/
|
||||
trackBorderColor: '#f2f2f2',
|
||||
/**
|
||||
* The corner radius of the border of the scrollbar track.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*
|
||||
* @type {number}
|
||||
* @default 0
|
||||
* @apioption scrollbar.trackBorderRadius
|
||||
*/
|
||||
/**
|
||||
* The width of the border of the scrollbar track.
|
||||
*
|
||||
* @sample stock/scrollbar/style/
|
||||
* Scrollbar styling
|
||||
*/
|
||||
trackBorderWidth: 1
|
||||
};
|
||||
return Scrollbar;
|
||||
}());
|
||||
if (!H.Scrollbar) {
|
||||
defaultOptions.scrollbar = merge(true, Scrollbar.defaultOptions, defaultOptions.scrollbar);
|
||||
H.Scrollbar = Scrollbar;
|
||||
ScrollbarAxis.compose(Axis, Scrollbar);
|
||||
}
|
||||
export default H.Scrollbar;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,802 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Globals.js';
|
||||
import U from '../Utilities.js';
|
||||
var animObject = U.animObject, 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.
|
||||
*
|
||||
* @typedef {number|string|Array<(number|string)>|Highcharts.PointOptionsObject|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;
|
||||
export default Point;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* Extension to the Series object in 3D charts.
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Globals.js';
|
||||
import Math3D from '../../Extensions/Math3D.js';
|
||||
var perspective = Math3D.perspective;
|
||||
import U from '../Utilities.js';
|
||||
var addEvent = U.addEvent, pick = U.pick;
|
||||
import './Series.js';
|
||||
/* eslint-disable no-invalid-this */
|
||||
// Wrap the translate method to post-translate points into 3D perspective
|
||||
addEvent(H.Series, 'afterTranslate', function () {
|
||||
if (this.chart.is3d()) {
|
||||
this.translate3dPoints();
|
||||
}
|
||||
});
|
||||
// Translate the plotX, plotY properties and add plotZ.
|
||||
H.Series.prototype.translate3dPoints = function () {
|
||||
var series = this, chart = series.chart, zAxis = pick(series.zAxis, chart.options.zAxis[0]), rawPoints = [], rawPoint, projectedPoints, projectedPoint, zValue, i;
|
||||
for (i = 0; i < series.data.length; i++) {
|
||||
rawPoint = series.data[i];
|
||||
if (zAxis && zAxis.translate) {
|
||||
zValue = zAxis.logarithmic && zAxis.val2lin ?
|
||||
zAxis.val2lin(rawPoint.z) :
|
||||
rawPoint.z; // #4562
|
||||
rawPoint.plotZ = zAxis.translate(zValue);
|
||||
rawPoint.isInside = rawPoint.isInside ?
|
||||
(zValue >= zAxis.min &&
|
||||
zValue <= zAxis.max) :
|
||||
false;
|
||||
}
|
||||
else {
|
||||
rawPoint.plotZ = 0;
|
||||
}
|
||||
rawPoint.axisXpos = rawPoint.plotX;
|
||||
rawPoint.axisYpos = rawPoint.plotY;
|
||||
rawPoint.axisZpos = rawPoint.plotZ;
|
||||
rawPoints.push({
|
||||
x: rawPoint.plotX,
|
||||
y: rawPoint.plotY,
|
||||
z: rawPoint.plotZ
|
||||
});
|
||||
}
|
||||
projectedPoints = perspective(rawPoints, chart, true);
|
||||
for (i = 0; i < series.data.length; i++) {
|
||||
rawPoint = series.data[i];
|
||||
projectedPoint = projectedPoints[i];
|
||||
rawPoint.plotX = projectedPoint.x;
|
||||
rawPoint.plotY = projectedPoint.y;
|
||||
rawPoint.plotZ = projectedPoint.z;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,656 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2020 Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Highcharts from './Globals.js';
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
import U from './Utilities.js';
|
||||
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;
|
||||
export default H.Time;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,150 @@
|
||||
/* *
|
||||
*
|
||||
* (c) 2010-2017 Christer Vasseng, Torstein Honsi
|
||||
*
|
||||
* License: www.highcharts.com/license
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import H from '../Core/Globals.js';
|
||||
import U from '../Core/Utilities.js';
|
||||
var merge = U.merge, objectEach = U.objectEach;
|
||||
/**
|
||||
* @interface Highcharts.AjaxSettingsObject
|
||||
*/ /**
|
||||
* The payload to send.
|
||||
*
|
||||
* @name Highcharts.AjaxSettingsObject#data
|
||||
* @type {string|Highcharts.Dictionary<any>}
|
||||
*/ /**
|
||||
* The data type expected.
|
||||
* @name Highcharts.AjaxSettingsObject#dataType
|
||||
* @type {"json"|"xml"|"text"|"octet"}
|
||||
*/ /**
|
||||
* Function to call on error.
|
||||
* @name Highcharts.AjaxSettingsObject#error
|
||||
* @type {Function}
|
||||
*/ /**
|
||||
* The headers; keyed on header name.
|
||||
* @name Highcharts.AjaxSettingsObject#headers
|
||||
* @type {Highcharts.Dictionary<string>}
|
||||
*/ /**
|
||||
* Function to call on success.
|
||||
* @name Highcharts.AjaxSettingsObject#success
|
||||
* @type {Function}
|
||||
*/ /**
|
||||
* The HTTP method to use. For example GET or POST.
|
||||
* @name Highcharts.AjaxSettingsObject#type
|
||||
* @type {string}
|
||||
*/ /**
|
||||
* The URL to call.
|
||||
* @name Highcharts.AjaxSettingsObject#url
|
||||
* @type {string}
|
||||
*/
|
||||
/**
|
||||
* Perform an Ajax call.
|
||||
*
|
||||
* @function Highcharts.ajax
|
||||
*
|
||||
* @param {Partial<Highcharts.AjaxSettingsObject>} attr
|
||||
* The Ajax settings to use.
|
||||
*
|
||||
* @return {false|undefined}
|
||||
* Returns false, if error occured.
|
||||
*/
|
||||
H.ajax = function (attr) {
|
||||
var options = merge(true, {
|
||||
url: false,
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: false,
|
||||
error: false,
|
||||
data: false,
|
||||
headers: {}
|
||||
}, attr), headers = {
|
||||
json: 'application/json',
|
||||
xml: 'application/xml',
|
||||
text: 'text/plain',
|
||||
octet: 'application/octet-stream'
|
||||
}, r = new XMLHttpRequest();
|
||||
/**
|
||||
* @private
|
||||
* @param {XMLHttpRequest} xhr - Internal request object.
|
||||
* @param {string|Error} err - Occured error.
|
||||
* @return {void}
|
||||
*/
|
||||
function handleError(xhr, err) {
|
||||
if (options.error) {
|
||||
options.error(xhr, err);
|
||||
}
|
||||
else {
|
||||
// @todo Maybe emit a highcharts error event here
|
||||
}
|
||||
}
|
||||
if (!options.url) {
|
||||
return false;
|
||||
}
|
||||
r.open(options.type.toUpperCase(), options.url, true);
|
||||
if (!options.headers['Content-Type']) {
|
||||
r.setRequestHeader('Content-Type', headers[options.dataType] || headers.text);
|
||||
}
|
||||
objectEach(options.headers, function (val, key) {
|
||||
r.setRequestHeader(key, val);
|
||||
});
|
||||
// @todo lacking timeout handling
|
||||
r.onreadystatechange = function () {
|
||||
var res;
|
||||
if (r.readyState === 4) {
|
||||
if (r.status === 200) {
|
||||
res = r.responseText;
|
||||
if (options.dataType === 'json') {
|
||||
try {
|
||||
res = JSON.parse(res);
|
||||
}
|
||||
catch (e) {
|
||||
return handleError(r, e);
|
||||
}
|
||||
}
|
||||
return options.success && options.success(res);
|
||||
}
|
||||
handleError(r, r.responseText);
|
||||
}
|
||||
};
|
||||
try {
|
||||
options.data = JSON.stringify(options.data);
|
||||
}
|
||||
catch (e) {
|
||||
// empty
|
||||
}
|
||||
r.send(options.data || true);
|
||||
};
|
||||
/**
|
||||
* Get a JSON resource over XHR, also supporting CORS without preflight.
|
||||
*
|
||||
* @function Highcharts.getJSON
|
||||
* @param {string} url
|
||||
* The URL to load.
|
||||
* @param {Function} success
|
||||
* The success callback. For error handling, use the `Highcharts.ajax`
|
||||
* function instead.
|
||||
* @return {void}
|
||||
*/
|
||||
H.getJSON = function (url, success) {
|
||||
H.ajax({
|
||||
url: url,
|
||||
success: success,
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
// Override the Content-Type to avoid preflight problems with CORS
|
||||
// in the Highcharts demos
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
});
|
||||
};
|
||||
var exports = {
|
||||
ajax: H.ajax,
|
||||
getJSON: H.getJSON
|
||||
};
|
||||
export default exports;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,143 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Callback to modify annotation's possitioner controls.
|
||||
*
|
||||
* @callback Highcharts.AnnotationControlPointPositionerFunction
|
||||
* @param {Highcharts.AnnotationControlPoint} this
|
||||
* @param {Highcharts.AnnotationControllable} target
|
||||
* @return {Highcharts.PositionObject}
|
||||
*/
|
||||
import U from '../../Core/Utilities.js';
|
||||
var extend = U.extend, merge = U.merge, pick = U.pick;
|
||||
import eventEmitterMixin from './Mixins/EventEmitterMixin.js';
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* A control point class which is a connection between controllable
|
||||
* transform methods and a user actions.
|
||||
*
|
||||
* @requires modules/annotations
|
||||
*
|
||||
* @class
|
||||
* @name Highcharts.AnnotationControlPoint
|
||||
*
|
||||
* @hideconstructor
|
||||
*
|
||||
* @param {Highcharts.Chart} chart
|
||||
* A chart instance.
|
||||
*
|
||||
* @param {Highcharts.AnnotationControllable} target
|
||||
* A controllable instance which is a target for a control point.
|
||||
*
|
||||
* @param {Highcharts.AnnotationControlPointOptionsObject} options
|
||||
* An options object.
|
||||
*
|
||||
* @param {number} [index]
|
||||
* Point index.
|
||||
*/
|
||||
var ControlPoint = /** @class */ (function () {
|
||||
function ControlPoint(chart, target, options, index) {
|
||||
/**
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
*/
|
||||
this.addEvents = eventEmitterMixin.addEvents;
|
||||
this.graphic = void 0;
|
||||
this.mouseMoveToRadians = eventEmitterMixin.mouseMoveToRadians;
|
||||
this.mouseMoveToScale = eventEmitterMixin.mouseMoveToScale;
|
||||
this.mouseMoveToTranslation = eventEmitterMixin.mouseMoveToTranslation;
|
||||
this.onDrag = eventEmitterMixin.onDrag;
|
||||
this.onMouseDown = eventEmitterMixin.onMouseDown;
|
||||
this.onMouseUp = eventEmitterMixin.onMouseUp;
|
||||
this.removeDocEvents = eventEmitterMixin.removeDocEvents;
|
||||
/**
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
*/
|
||||
/**
|
||||
* List of events for `anntation.options.events` that should not be
|
||||
* added to `annotation.graphic` but to the `annotation`.
|
||||
* @private
|
||||
* @name Highcharts.AnnotationControlPoint#nonDOMEvents
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
this.nonDOMEvents = ['drag'];
|
||||
this.chart = chart;
|
||||
this.target = target;
|
||||
this.options = options;
|
||||
this.index = pick(options.index, index);
|
||||
}
|
||||
/**
|
||||
* Set the visibility of the control point.
|
||||
*
|
||||
* @function Highcharts.AnnotationControlPoint#setVisibility
|
||||
*
|
||||
* @param {boolean} visible
|
||||
* Visibility of the control point.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ControlPoint.prototype.setVisibility = function (visible) {
|
||||
this.graphic.attr('visibility', visible ? 'visible' : 'hidden');
|
||||
this.options.visible = visible;
|
||||
};
|
||||
/**
|
||||
* Render the control point.
|
||||
* @private
|
||||
*/
|
||||
ControlPoint.prototype.render = function () {
|
||||
var chart = this.chart, options = this.options;
|
||||
this.graphic = chart.renderer
|
||||
.symbol(options.symbol, 0, 0, options.width, options.height)
|
||||
.add(chart.controlPointsGroup)
|
||||
.css(options.style);
|
||||
this.setVisibility(options.visible);
|
||||
// npm test -- --tests "highcharts/annotations-advanced/*"
|
||||
this.addEvents();
|
||||
};
|
||||
/**
|
||||
* Redraw the control point.
|
||||
* @private
|
||||
* @param {boolean} [animation]
|
||||
*/
|
||||
ControlPoint.prototype.redraw = function (animation) {
|
||||
this.graphic[animation ? 'animate' : 'attr'](this.options.positioner.call(this, this.target));
|
||||
};
|
||||
/**
|
||||
* Destroy the control point.
|
||||
* @private
|
||||
*/
|
||||
ControlPoint.prototype.destroy = function () {
|
||||
eventEmitterMixin.destroy.call(this);
|
||||
if (this.graphic) {
|
||||
this.graphic = this.graphic.destroy();
|
||||
}
|
||||
this.chart = null;
|
||||
this.target = null;
|
||||
this.options = null;
|
||||
};
|
||||
/**
|
||||
* Update the control point.
|
||||
*
|
||||
* @function Highcharts.AnnotationControlPoint#update
|
||||
*
|
||||
* @param {Partial<Highcharts.AnnotationControlPointOptionsObject>} userOptions
|
||||
* New options for the control point.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ControlPoint.prototype.update = function (userOptions) {
|
||||
var chart = this.chart, target = this.target, index = this.index, options = merge(true, this.options, userOptions);
|
||||
this.destroy();
|
||||
this.constructor(chart, target, options, index);
|
||||
this.render(chart.controlPointsGroup);
|
||||
this.redraw();
|
||||
};
|
||||
return ControlPoint;
|
||||
}());
|
||||
export default ControlPoint;
|
||||
@@ -0,0 +1,117 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import ControllableMixin from '../Mixins/ControllableMixin.js';
|
||||
import ControllablePath from './ControllablePath.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var merge = U.merge;
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* A controllable circle class.
|
||||
*
|
||||
* @requires modules/annotations
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.AnnotationControllableCircle
|
||||
*
|
||||
* @param {Highcharts.Annotation} annotation an annotation instance
|
||||
* @param {Highcharts.AnnotationsShapeOptions} options a shape's options
|
||||
* @param {number} index of the circle
|
||||
*/
|
||||
var ControllableCircle = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function ControllableCircle(annotation, options, index) {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
this.addControlPoints = ControllableMixin.addControlPoints;
|
||||
this.anchor = ControllableMixin.anchor;
|
||||
this.attr = ControllableMixin.attr;
|
||||
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
|
||||
this.destroy = ControllableMixin.destroy;
|
||||
this.getPointsOptions = ControllableMixin.getPointsOptions;
|
||||
this.init = ControllableMixin.init;
|
||||
this.linkPoints = ControllableMixin.linkPoints;
|
||||
this.point = ControllableMixin.point;
|
||||
this.rotate = ControllableMixin.rotate;
|
||||
this.scale = ControllableMixin.scale;
|
||||
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
|
||||
this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
|
||||
this.transform = ControllableMixin.transform;
|
||||
this.transformPoint = ControllableMixin.transformPoint;
|
||||
this.translatePoint = ControllableMixin.translatePoint;
|
||||
this.translateShape = ControllableMixin.translateShape;
|
||||
this.update = ControllableMixin.update;
|
||||
/**
|
||||
* @type 'circle'
|
||||
*/
|
||||
this.type = 'circle';
|
||||
this.translate = ControllableMixin.translateShape;
|
||||
this.init(annotation, options, index);
|
||||
this.collection = 'shapes';
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
ControllableCircle.prototype.render = function (parent) {
|
||||
var attrs = this.attrsFromOptions(this.options);
|
||||
this.graphic = this.annotation.chart.renderer
|
||||
.circle(0, -9e9, 0)
|
||||
.attr(attrs)
|
||||
.add(parent);
|
||||
ControllableMixin.render.call(this);
|
||||
};
|
||||
ControllableCircle.prototype.redraw = function (animation) {
|
||||
var position = this.anchor(this.points[0]).absolutePosition;
|
||||
if (position) {
|
||||
this.graphic[animation ? 'animate' : 'attr']({
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
r: this.options.r
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.graphic.attr({
|
||||
x: 0,
|
||||
y: -9e9
|
||||
});
|
||||
}
|
||||
this.graphic.placed = Boolean(position);
|
||||
ControllableMixin.redraw.call(this, animation);
|
||||
};
|
||||
/**
|
||||
* Set the radius.
|
||||
*
|
||||
* @param {number} r a radius to be set
|
||||
*/
|
||||
ControllableCircle.prototype.setRadius = function (r) {
|
||||
this.options.r = r;
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* A map object which allows to map options attributes to element
|
||||
* attributes.
|
||||
*
|
||||
* @name Highcharts.AnnotationControllableCircle.attrsMap
|
||||
* @type {Highcharts.Dictionary<string>}
|
||||
*/
|
||||
ControllableCircle.attrsMap = merge(ControllablePath.attrsMap, { r: 'r' });
|
||||
return ControllableCircle;
|
||||
}());
|
||||
export default ControllableCircle;
|
||||
@@ -0,0 +1,111 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import ControllableLabel from './ControllableLabel.js';
|
||||
import ControllableMixin from '../Mixins/ControllableMixin.js';
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* A controllable image class.
|
||||
*
|
||||
* @requires modules/annotations
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.AnnotationControllableImage
|
||||
*
|
||||
* @param {Highcharts.Annotation} annotation
|
||||
* An annotation instance.
|
||||
*
|
||||
* @param {Highcharts.AnnotationsShapeOptions} options
|
||||
* A controllable's options.
|
||||
*
|
||||
* @param {number} index
|
||||
* Index of the image.
|
||||
*/
|
||||
var ControllableImage = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function ControllableImage(annotation, options, index) {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
this.addControlPoints = ControllableMixin.addControlPoints;
|
||||
this.anchor = ControllableMixin.anchor;
|
||||
this.attr = ControllableMixin.attr;
|
||||
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
|
||||
this.destroy = ControllableMixin.destroy;
|
||||
this.getPointsOptions = ControllableMixin.getPointsOptions;
|
||||
this.init = ControllableMixin.init;
|
||||
this.linkPoints = ControllableMixin.linkPoints;
|
||||
this.point = ControllableMixin.point;
|
||||
this.rotate = ControllableMixin.rotate;
|
||||
this.scale = ControllableMixin.scale;
|
||||
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
|
||||
this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
|
||||
this.transform = ControllableMixin.transform;
|
||||
this.transformPoint = ControllableMixin.transformPoint;
|
||||
this.translatePoint = ControllableMixin.translatePoint;
|
||||
this.translateShape = ControllableMixin.translateShape;
|
||||
this.update = ControllableMixin.update;
|
||||
/**
|
||||
* @type 'image'
|
||||
*/
|
||||
this.type = 'image';
|
||||
this.translate = ControllableMixin.translateShape;
|
||||
this.init(annotation, options, index);
|
||||
this.collection = 'shapes';
|
||||
}
|
||||
ControllableImage.prototype.render = function (parent) {
|
||||
var attrs = this.attrsFromOptions(this.options), options = this.options;
|
||||
this.graphic = this.annotation.chart.renderer
|
||||
.image(options.src, 0, -9e9, options.width, options.height)
|
||||
.attr(attrs)
|
||||
.add(parent);
|
||||
this.graphic.width = options.width;
|
||||
this.graphic.height = options.height;
|
||||
ControllableMixin.render.call(this);
|
||||
};
|
||||
ControllableImage.prototype.redraw = function (animation) {
|
||||
var anchor = this.anchor(this.points[0]), position = ControllableLabel.prototype.position.call(this, anchor);
|
||||
if (position) {
|
||||
this.graphic[animation ? 'animate' : 'attr']({
|
||||
x: position.x,
|
||||
y: position.y
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.graphic.attr({
|
||||
x: 0,
|
||||
y: -9e9
|
||||
});
|
||||
}
|
||||
this.graphic.placed = Boolean(position);
|
||||
ControllableMixin.redraw.call(this, animation);
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* A map object which allows to map options attributes to element attributes
|
||||
*
|
||||
* @name Highcharts.AnnotationControllableImage.attrsMap
|
||||
* @type {Highcharts.Dictionary<string>}
|
||||
*/
|
||||
ControllableImage.attrsMap = {
|
||||
width: 'width',
|
||||
height: 'height',
|
||||
zIndex: 'zIndex'
|
||||
};
|
||||
return ControllableImage;
|
||||
}());
|
||||
export default ControllableImage;
|
||||
@@ -0,0 +1,378 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import ControllableMixin from '../Mixins/ControllableMixin.js';
|
||||
import MockPoint from '../MockPoint.js';
|
||||
import SVGRenderer from '../../../Core/Renderer/SVG/SVGRenderer.js';
|
||||
import Tooltip from '../../../Core/Tooltip.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var extend = U.extend, format = U.format, isNumber = U.isNumber, pick = U.pick;
|
||||
import '../../../Core/Renderer/SVG/SVGRenderer.js';
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* A controllable label class.
|
||||
*
|
||||
* @requires modules/annotations
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.AnnotationControllableLabel
|
||||
*
|
||||
* @param {Highcharts.Annotation} annotation
|
||||
* An annotation instance.
|
||||
* @param {Highcharts.AnnotationsLabelOptions} options
|
||||
* A label's options.
|
||||
* @param {number} index
|
||||
* Index of the label.
|
||||
*/
|
||||
var ControllableLabel = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function ControllableLabel(annotation, options, index) {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
this.addControlPoints = ControllableMixin.addControlPoints;
|
||||
this.attr = ControllableMixin.attr;
|
||||
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
|
||||
this.destroy = ControllableMixin.destroy;
|
||||
this.getPointsOptions = ControllableMixin.getPointsOptions;
|
||||
this.init = ControllableMixin.init;
|
||||
this.linkPoints = ControllableMixin.linkPoints;
|
||||
this.point = ControllableMixin.point;
|
||||
this.rotate = ControllableMixin.rotate;
|
||||
this.scale = ControllableMixin.scale;
|
||||
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
|
||||
this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
|
||||
this.transform = ControllableMixin.transform;
|
||||
this.transformPoint = ControllableMixin.transformPoint;
|
||||
this.translateShape = ControllableMixin.translateShape;
|
||||
this.update = ControllableMixin.update;
|
||||
this.init(annotation, options, index);
|
||||
this.collection = 'labels';
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Static Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Returns new aligned position based alignment options and box to align to.
|
||||
* It is almost a one-to-one copy from SVGElement.prototype.align
|
||||
* except it does not use and mutate an element
|
||||
*
|
||||
* @param {Highcharts.AnnotationAlignObject} alignOptions
|
||||
*
|
||||
* @param {Highcharts.BBoxObject} box
|
||||
*
|
||||
* @return {Highcharts.PositionObject}
|
||||
* Aligned position.
|
||||
*/
|
||||
ControllableLabel.alignedPosition = function (alignOptions, box) {
|
||||
var align = alignOptions.align, vAlign = alignOptions.verticalAlign, x = (box.x || 0) + (alignOptions.x || 0), y = (box.y || 0) + (alignOptions.y || 0), alignFactor, vAlignFactor;
|
||||
if (align === 'right') {
|
||||
alignFactor = 1;
|
||||
}
|
||||
else if (align === 'center') {
|
||||
alignFactor = 2;
|
||||
}
|
||||
if (alignFactor) {
|
||||
x += (box.width - (alignOptions.width || 0)) / alignFactor;
|
||||
}
|
||||
if (vAlign === 'bottom') {
|
||||
vAlignFactor = 1;
|
||||
}
|
||||
else if (vAlign === 'middle') {
|
||||
vAlignFactor = 2;
|
||||
}
|
||||
if (vAlignFactor) {
|
||||
y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
|
||||
}
|
||||
return {
|
||||
x: Math.round(x),
|
||||
y: Math.round(y)
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Returns new alignment options for a label if the label is outside the
|
||||
* plot area. It is almost a one-to-one copy from
|
||||
* Series.prototype.justifyDataLabel except it does not mutate the label and
|
||||
* it works with absolute instead of relative position.
|
||||
*/
|
||||
ControllableLabel.justifiedOptions = function (chart, label, alignOptions, alignAttr) {
|
||||
var align = alignOptions.align, verticalAlign = alignOptions.verticalAlign, padding = label.box ? 0 : (label.padding || 0), bBox = label.getBBox(), off,
|
||||
//
|
||||
options = {
|
||||
align: align,
|
||||
verticalAlign: verticalAlign,
|
||||
x: alignOptions.x,
|
||||
y: alignOptions.y,
|
||||
width: label.width,
|
||||
height: label.height
|
||||
},
|
||||
//
|
||||
x = alignAttr.x - chart.plotLeft, y = alignAttr.y - chart.plotTop;
|
||||
// Off left
|
||||
off = x + padding;
|
||||
if (off < 0) {
|
||||
if (align === 'right') {
|
||||
options.align = 'left';
|
||||
}
|
||||
else {
|
||||
options.x = -off;
|
||||
}
|
||||
}
|
||||
// Off right
|
||||
off = x + bBox.width - padding;
|
||||
if (off > chart.plotWidth) {
|
||||
if (align === 'left') {
|
||||
options.align = 'right';
|
||||
}
|
||||
else {
|
||||
options.x = chart.plotWidth - off;
|
||||
}
|
||||
}
|
||||
// Off top
|
||||
off = y + padding;
|
||||
if (off < 0) {
|
||||
if (verticalAlign === 'bottom') {
|
||||
options.verticalAlign = 'top';
|
||||
}
|
||||
else {
|
||||
options.y = -off;
|
||||
}
|
||||
}
|
||||
// Off bottom
|
||||
off = y + bBox.height - padding;
|
||||
if (off > chart.plotHeight) {
|
||||
if (verticalAlign === 'top') {
|
||||
options.verticalAlign = 'bottom';
|
||||
}
|
||||
else {
|
||||
options.y = chart.plotHeight - off;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Translate the point of the label by deltaX and deltaY translations.
|
||||
* The point is the label's anchor.
|
||||
*
|
||||
* @param {number} dx translation for x coordinate
|
||||
* @param {number} dy translation for y coordinate
|
||||
*/
|
||||
ControllableLabel.prototype.translatePoint = function (dx, dy) {
|
||||
ControllableMixin.translatePoint.call(this, dx, dy, 0);
|
||||
};
|
||||
/**
|
||||
* Translate x and y position relative to the label's anchor.
|
||||
*
|
||||
* @param {number} dx translation for x coordinate
|
||||
* @param {number} dy translation for y coordinate
|
||||
*/
|
||||
ControllableLabel.prototype.translate = function (dx, dy) {
|
||||
var chart = this.annotation.chart,
|
||||
// Annotation.options
|
||||
labelOptions = this.annotation.userOptions,
|
||||
// Chart.options.annotations
|
||||
annotationIndex = chart.annotations.indexOf(this.annotation), chartAnnotations = chart.options.annotations, chartOptions = chartAnnotations[annotationIndex], temp;
|
||||
if (chart.inverted) {
|
||||
temp = dx;
|
||||
dx = dy;
|
||||
dy = temp;
|
||||
}
|
||||
// Local options:
|
||||
this.options.x += dx;
|
||||
this.options.y += dy;
|
||||
// Options stored in chart:
|
||||
chartOptions[this.collection][this.index].x = this.options.x;
|
||||
chartOptions[this.collection][this.index].y = this.options.y;
|
||||
labelOptions[this.collection][this.index].x = this.options.x;
|
||||
labelOptions[this.collection][this.index].y = this.options.y;
|
||||
};
|
||||
ControllableLabel.prototype.render = function (parent) {
|
||||
var options = this.options, attrs = this.attrsFromOptions(options), style = options.style;
|
||||
this.graphic = this.annotation.chart.renderer
|
||||
.label('', 0, -9999, // #10055
|
||||
options.shape, null, null, options.useHTML, null, 'annotation-label')
|
||||
.attr(attrs)
|
||||
.add(parent);
|
||||
if (!this.annotation.chart.styledMode) {
|
||||
if (style.color === 'contrast') {
|
||||
style.color = this.annotation.chart.renderer.getContrast(ControllableLabel.shapesWithoutBackground.indexOf(options.shape) > -1 ? '#FFFFFF' : options.backgroundColor);
|
||||
}
|
||||
this.graphic
|
||||
.css(options.style)
|
||||
.shadow(options.shadow);
|
||||
}
|
||||
if (options.className) {
|
||||
this.graphic.addClass(options.className);
|
||||
}
|
||||
this.graphic.labelrank = options.labelrank;
|
||||
ControllableMixin.render.call(this);
|
||||
};
|
||||
ControllableLabel.prototype.redraw = function (animation) {
|
||||
var options = this.options, text = this.text || options.format || options.text, label = this.graphic, point = this.points[0], anchor, attrs;
|
||||
label.attr({
|
||||
text: text ?
|
||||
format(text, point.getLabelConfig(), this.annotation.chart) :
|
||||
options.formatter.call(point, this)
|
||||
});
|
||||
anchor = this.anchor(point);
|
||||
attrs = this.position(anchor);
|
||||
if (attrs) {
|
||||
label.alignAttr = attrs;
|
||||
attrs.anchorX = anchor.absolutePosition.x;
|
||||
attrs.anchorY = anchor.absolutePosition.y;
|
||||
label[animation ? 'animate' : 'attr'](attrs);
|
||||
}
|
||||
else {
|
||||
label.attr({
|
||||
x: 0,
|
||||
y: -9999 // #10055
|
||||
});
|
||||
}
|
||||
label.placed = !!attrs;
|
||||
ControllableMixin.redraw.call(this, animation);
|
||||
};
|
||||
/**
|
||||
* All basic shapes don't support alignTo() method except label.
|
||||
* For a controllable label, we need to subtract translation from
|
||||
* options.
|
||||
*/
|
||||
ControllableLabel.prototype.anchor = function (_point) {
|
||||
var anchor = ControllableMixin.anchor.apply(this, arguments), x = this.options.x || 0, y = this.options.y || 0;
|
||||
anchor.absolutePosition.x -= x;
|
||||
anchor.absolutePosition.y -= y;
|
||||
anchor.relativePosition.x -= x;
|
||||
anchor.relativePosition.y -= y;
|
||||
return anchor;
|
||||
};
|
||||
/**
|
||||
* Returns the label position relative to its anchor.
|
||||
*
|
||||
* @param {Highcharts.AnnotationAnchorObject} anchor
|
||||
*
|
||||
* @return {Highcharts.PositionObject|null}
|
||||
*/
|
||||
ControllableLabel.prototype.position = function (anchor) {
|
||||
var item = this.graphic, chart = this.annotation.chart, point = this.points[0], itemOptions = this.options, anchorAbsolutePosition = anchor.absolutePosition, anchorRelativePosition = anchor.relativePosition, itemPosition, alignTo, itemPosRelativeX, itemPosRelativeY, showItem = point.series.visible &&
|
||||
MockPoint.prototype.isInsidePlot.call(point);
|
||||
if (showItem) {
|
||||
if (itemOptions.distance) {
|
||||
itemPosition = Tooltip.prototype.getPosition.call({
|
||||
chart: chart,
|
||||
distance: pick(itemOptions.distance, 16)
|
||||
}, item.width, item.height, {
|
||||
plotX: anchorRelativePosition.x,
|
||||
plotY: anchorRelativePosition.y,
|
||||
negative: point.negative,
|
||||
ttBelow: point.ttBelow,
|
||||
h: (anchorRelativePosition.height || anchorRelativePosition.width)
|
||||
});
|
||||
}
|
||||
else if (itemOptions.positioner) {
|
||||
itemPosition = itemOptions.positioner.call(this);
|
||||
}
|
||||
else {
|
||||
alignTo = {
|
||||
x: anchorAbsolutePosition.x,
|
||||
y: anchorAbsolutePosition.y,
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
itemPosition = ControllableLabel.alignedPosition(extend(itemOptions, {
|
||||
width: item.width,
|
||||
height: item.height
|
||||
}), alignTo);
|
||||
if (this.options.overflow === 'justify') {
|
||||
itemPosition = ControllableLabel.alignedPosition(ControllableLabel.justifiedOptions(chart, item, itemOptions, itemPosition), alignTo);
|
||||
}
|
||||
}
|
||||
if (itemOptions.crop) {
|
||||
itemPosRelativeX = itemPosition.x - chart.plotLeft;
|
||||
itemPosRelativeY = itemPosition.y - chart.plotTop;
|
||||
showItem =
|
||||
chart.isInsidePlot(itemPosRelativeX, itemPosRelativeY) &&
|
||||
chart.isInsidePlot(itemPosRelativeX + item.width, itemPosRelativeY + item.height);
|
||||
}
|
||||
}
|
||||
return showItem ? itemPosition : null;
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* A map object which allows to map options attributes to element attributes
|
||||
*
|
||||
* @type {Highcharts.Dictionary<string>}
|
||||
*/
|
||||
ControllableLabel.attrsMap = {
|
||||
backgroundColor: 'fill',
|
||||
borderColor: 'stroke',
|
||||
borderWidth: 'stroke-width',
|
||||
zIndex: 'zIndex',
|
||||
borderRadius: 'r',
|
||||
padding: 'padding'
|
||||
};
|
||||
/**
|
||||
* Shapes which do not have background - the object is used for proper
|
||||
* setting of the contrast color.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
*/
|
||||
ControllableLabel.shapesWithoutBackground = ['connector'];
|
||||
return ControllableLabel;
|
||||
}());
|
||||
export default ControllableLabel;
|
||||
/* ********************************************************************** */
|
||||
/**
|
||||
* General symbol definition for labels with connector
|
||||
* @private
|
||||
*/
|
||||
SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {
|
||||
var anchorX = options && options.anchorX, anchorY = options && options.anchorY, path, yOffset, lateral = w / 2;
|
||||
if (isNumber(anchorX) && isNumber(anchorY)) {
|
||||
path = [['M', anchorX, anchorY]];
|
||||
// Prefer 45 deg connectors
|
||||
yOffset = y - anchorY;
|
||||
if (yOffset < 0) {
|
||||
yOffset = -h - yOffset;
|
||||
}
|
||||
if (yOffset < w) {
|
||||
lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;
|
||||
}
|
||||
// Anchor below label
|
||||
if (anchorY > y + h) {
|
||||
path.push(['L', x + lateral, y + h]);
|
||||
// Anchor above label
|
||||
}
|
||||
else if (anchorY < y) {
|
||||
path.push(['L', x + lateral, y]);
|
||||
// Anchor left of label
|
||||
}
|
||||
else if (anchorX < x) {
|
||||
path.push(['L', x, y + h / 2]);
|
||||
// Anchor right of label
|
||||
}
|
||||
else if (anchorX > x + w) {
|
||||
path.push(['L', x + w, y + h / 2]);
|
||||
}
|
||||
}
|
||||
return path || [];
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import ControllableMixin from '../Mixins/ControllableMixin.js';
|
||||
import H from '../../../Core/Globals.js';
|
||||
import MarkerMixin from '../Mixins/MarkerMixin.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var extend = U.extend;
|
||||
// See TRACKER_FILL in highcharts.src.js
|
||||
var TRACKER_FILL = 'rgba(192,192,192,' + (H.svg ? 0.0001 : 0.002) + ')';
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* A controllable path class.
|
||||
*
|
||||
* @requires modules/annotations
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.AnnotationControllablePath
|
||||
*
|
||||
* @param {Highcharts.Annotation}
|
||||
* Related annotation.
|
||||
*
|
||||
* @param {Highcharts.AnnotationsShapeOptions} options
|
||||
* A path's options object.
|
||||
*
|
||||
* @param {number} index
|
||||
* Index of the path.
|
||||
*/
|
||||
var ControllablePath = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function ControllablePath(annotation, options, index) {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
this.addControlPoints = ControllableMixin.addControlPoints;
|
||||
this.anchor = ControllableMixin.anchor;
|
||||
this.attr = ControllableMixin.attr;
|
||||
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
|
||||
this.destroy = ControllableMixin.destroy;
|
||||
this.getPointsOptions = ControllableMixin.getPointsOptions;
|
||||
this.init = ControllableMixin.init;
|
||||
this.linkPoints = ControllableMixin.linkPoints;
|
||||
this.point = ControllableMixin.point;
|
||||
this.rotate = ControllableMixin.rotate;
|
||||
this.scale = ControllableMixin.scale;
|
||||
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
|
||||
this.setMarkers = MarkerMixin.setItemMarkers;
|
||||
this.transform = ControllableMixin.transform;
|
||||
this.transformPoint = ControllableMixin.transformPoint;
|
||||
this.translate = ControllableMixin.translate;
|
||||
this.translatePoint = ControllableMixin.translatePoint;
|
||||
this.translateShape = ControllableMixin.translateShape;
|
||||
this.update = ControllableMixin.update;
|
||||
/**
|
||||
* @type 'path'
|
||||
*/
|
||||
this.type = 'path';
|
||||
this.init(annotation, options, index);
|
||||
this.collection = 'shapes';
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* Map the controllable path to 'd' path attribute.
|
||||
*
|
||||
* @return {Highcharts.SVGPathArray|null}
|
||||
* A path's d attribute.
|
||||
*/
|
||||
ControllablePath.prototype.toD = function () {
|
||||
var dOption = this.options.d;
|
||||
if (dOption) {
|
||||
return typeof dOption === 'function' ?
|
||||
dOption.call(this) :
|
||||
dOption;
|
||||
}
|
||||
var points = this.points, len = points.length, showPath = len, point = points[0], position = showPath && this.anchor(point).absolutePosition, pointIndex = 0, command, d = [];
|
||||
if (position) {
|
||||
d.push(['M', position.x, position.y]);
|
||||
while (++pointIndex < len && showPath) {
|
||||
point = points[pointIndex];
|
||||
command = point.command || 'L';
|
||||
position = this.anchor(point).absolutePosition;
|
||||
if (command === 'M') {
|
||||
d.push([command, position.x, position.y]);
|
||||
}
|
||||
else if (command === 'L') {
|
||||
d.push([command, position.x, position.y]);
|
||||
}
|
||||
else if (command === 'Z') {
|
||||
d.push([command]);
|
||||
}
|
||||
showPath = point.series.visible;
|
||||
}
|
||||
}
|
||||
return showPath ?
|
||||
this.chart.renderer.crispLine(d, this.graphic.strokeWidth()) :
|
||||
null;
|
||||
};
|
||||
ControllablePath.prototype.shouldBeDrawn = function () {
|
||||
return (ControllableMixin.shouldBeDrawn.call(this) || Boolean(this.options.d));
|
||||
};
|
||||
ControllablePath.prototype.render = function (parent) {
|
||||
var options = this.options, attrs = this.attrsFromOptions(options);
|
||||
this.graphic = this.annotation.chart.renderer
|
||||
.path([['M', 0, 0]])
|
||||
.attr(attrs)
|
||||
.add(parent);
|
||||
if (options.className) {
|
||||
this.graphic.addClass(options.className);
|
||||
}
|
||||
this.tracker = this.annotation.chart.renderer
|
||||
.path([['M', 0, 0]])
|
||||
.addClass('highcharts-tracker-line')
|
||||
.attr({
|
||||
zIndex: 2
|
||||
})
|
||||
.add(parent);
|
||||
if (!this.annotation.chart.styledMode) {
|
||||
this.tracker.attr({
|
||||
'stroke-linejoin': 'round',
|
||||
stroke: TRACKER_FILL,
|
||||
fill: TRACKER_FILL,
|
||||
'stroke-width': this.graphic.strokeWidth() +
|
||||
options.snap * 2
|
||||
});
|
||||
}
|
||||
ControllableMixin.render.call(this);
|
||||
extend(this.graphic, {
|
||||
markerStartSetter: MarkerMixin.markerStartSetter,
|
||||
markerEndSetter: MarkerMixin.markerEndSetter
|
||||
});
|
||||
this.setMarkers(this);
|
||||
};
|
||||
ControllablePath.prototype.redraw = function (animation) {
|
||||
var d = this.toD(), action = animation ? 'animate' : 'attr';
|
||||
if (d) {
|
||||
this.graphic[action]({ d: d });
|
||||
this.tracker[action]({ d: d });
|
||||
}
|
||||
else {
|
||||
this.graphic.attr({ d: 'M 0 ' + -9e9 });
|
||||
this.tracker.attr({ d: 'M 0 ' + -9e9 });
|
||||
}
|
||||
this.graphic.placed = this.tracker.placed = Boolean(d);
|
||||
ControllableMixin.redraw.call(this, animation);
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* A map object which allows to map options attributes to element attributes
|
||||
*
|
||||
* @name Highcharts.AnnotationControllablePath.attrsMap
|
||||
* @type {Highcharts.Dictionary<string>}
|
||||
*/
|
||||
ControllablePath.attrsMap = {
|
||||
dashStyle: 'dashstyle',
|
||||
strokeWidth: 'stroke-width',
|
||||
stroke: 'stroke',
|
||||
fill: 'fill',
|
||||
zIndex: 'zIndex'
|
||||
};
|
||||
return ControllablePath;
|
||||
}());
|
||||
export default ControllablePath;
|
||||
@@ -0,0 +1,121 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
import ControllableMixin from '../Mixins/ControllableMixin.js';
|
||||
import ControllablePath from './ControllablePath.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var merge = U.merge;
|
||||
/**
|
||||
* @typedef {Annotation.ControllablePath.AttrsMap}
|
||||
* Annotation.ControllableRect.AttrsMap
|
||||
* @property {string} width=width
|
||||
* @property {string} height=height
|
||||
*/
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* A controllable rect class.
|
||||
*
|
||||
* @requires modules/annotations
|
||||
*
|
||||
* @private
|
||||
* @class
|
||||
* @name Highcharts.AnnotationControllableRect
|
||||
*
|
||||
* @param {Highcharts.Annotation} annotation
|
||||
* An annotation instance.
|
||||
*
|
||||
* @param {Highcharts.AnnotationsShapeOptions} options
|
||||
* A rect's options.
|
||||
*
|
||||
* @param {number} index
|
||||
* Index of the rectangle
|
||||
*/
|
||||
var ControllableRect = /** @class */ (function () {
|
||||
/* *
|
||||
*
|
||||
* Constructors
|
||||
*
|
||||
* */
|
||||
function ControllableRect(annotation, options, index) {
|
||||
/* *
|
||||
*
|
||||
* Properties
|
||||
*
|
||||
* */
|
||||
this.addControlPoints = ControllableMixin.addControlPoints;
|
||||
this.anchor = ControllableMixin.anchor;
|
||||
this.attr = ControllableMixin.attr;
|
||||
this.attrsFromOptions = ControllableMixin.attrsFromOptions;
|
||||
this.destroy = ControllableMixin.destroy;
|
||||
this.getPointsOptions = ControllableMixin.getPointsOptions;
|
||||
this.init = ControllableMixin.init;
|
||||
this.linkPoints = ControllableMixin.linkPoints;
|
||||
this.point = ControllableMixin.point;
|
||||
this.rotate = ControllableMixin.rotate;
|
||||
this.scale = ControllableMixin.scale;
|
||||
this.setControlPointsVisibility = ControllableMixin.setControlPointsVisibility;
|
||||
this.shouldBeDrawn = ControllableMixin.shouldBeDrawn;
|
||||
this.transform = ControllableMixin.transform;
|
||||
this.transformPoint = ControllableMixin.transformPoint;
|
||||
this.translatePoint = ControllableMixin.translatePoint;
|
||||
this.translateShape = ControllableMixin.translateShape;
|
||||
this.update = ControllableMixin.update;
|
||||
/**
|
||||
* @type 'rect'
|
||||
*/
|
||||
this.type = 'rect';
|
||||
this.translate = ControllableMixin.translateShape;
|
||||
this.init(annotation, options, index);
|
||||
this.collection = 'shapes';
|
||||
}
|
||||
/* *
|
||||
*
|
||||
* Functions
|
||||
*
|
||||
* */
|
||||
ControllableRect.prototype.render = function (parent) {
|
||||
var attrs = this.attrsFromOptions(this.options);
|
||||
this.graphic = this.annotation.chart.renderer
|
||||
.rect(0, -9e9, 0, 0)
|
||||
.attr(attrs)
|
||||
.add(parent);
|
||||
ControllableMixin.render.call(this);
|
||||
};
|
||||
ControllableRect.prototype.redraw = function (animation) {
|
||||
var position = this.anchor(this.points[0]).absolutePosition;
|
||||
if (position) {
|
||||
this.graphic[animation ? 'animate' : 'attr']({
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
width: this.options.width,
|
||||
height: this.options.height
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.attr({
|
||||
x: 0,
|
||||
y: -9e9
|
||||
});
|
||||
}
|
||||
this.graphic.placed = Boolean(position);
|
||||
ControllableMixin.redraw.call(this, animation);
|
||||
};
|
||||
/* *
|
||||
*
|
||||
* Static Properties
|
||||
*
|
||||
* */
|
||||
/**
|
||||
* A map object which allows to map options attributes to element attributes
|
||||
*
|
||||
* @type {Annotation.ControllableRect.AttrsMap}
|
||||
*/
|
||||
ControllableRect.attrsMap = merge(ControllablePath.attrsMap, {
|
||||
width: 'width',
|
||||
height: 'height'
|
||||
});
|
||||
return ControllableRect;
|
||||
}());
|
||||
export default ControllableRect;
|
||||
@@ -0,0 +1,369 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import ControlPoint from '../ControlPoint.js';
|
||||
import MockPoint from '../MockPoint.js';
|
||||
import Tooltip from '../../../Core/Tooltip.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var isObject = U.isObject, isString = U.isString, merge = U.merge, splat = U.splat;
|
||||
/**
|
||||
* An object which denots a controllable's anchor positions - relative and
|
||||
* absolute.
|
||||
*
|
||||
* @private
|
||||
* @interface Highcharts.AnnotationAnchorObject
|
||||
*/ /**
|
||||
* Relative to the plot area position
|
||||
* @name Highcharts.AnnotationAnchorObject#relativePosition
|
||||
* @type {Highcharts.BBoxObject}
|
||||
*/ /**
|
||||
* Absolute position
|
||||
* @name Highcharts.AnnotationAnchorObject#absolutePosition
|
||||
* @type {Highcharts.BBoxObject}
|
||||
*/
|
||||
/**
|
||||
* @interface Highcharts.AnnotationControllable
|
||||
*/ /**
|
||||
* @name Highcharts.AnnotationControllable#annotation
|
||||
* @type {Highcharts.Annotation}
|
||||
*/ /**
|
||||
* @name Highcharts.AnnotationControllable#chart
|
||||
* @type {Highcharts.Chart}
|
||||
*/ /**
|
||||
* @name Highcharts.AnnotationControllable#collection
|
||||
* @type {string}
|
||||
*/ /**
|
||||
* @private
|
||||
* @name Highcharts.AnnotationControllable#controlPoints
|
||||
* @type {Array<Highcharts.AnnotationControlPoint>}
|
||||
*/ /**
|
||||
* @name Highcharts.AnnotationControllable#points
|
||||
* @type {Array<Highcharts.Point>}
|
||||
*/
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* It provides methods for handling points, control points
|
||||
* and points transformations.
|
||||
*
|
||||
* @private
|
||||
* @mixin
|
||||
* @name Highcharts.AnnotationControllableMixin
|
||||
*/
|
||||
var controllableMixin = {
|
||||
/**
|
||||
* Init the controllable
|
||||
*/
|
||||
init: function (annotation, options, index) {
|
||||
this.annotation = annotation;
|
||||
this.chart = annotation.chart;
|
||||
this.options = options;
|
||||
this.points = [];
|
||||
this.controlPoints = [];
|
||||
this.index = index;
|
||||
this.linkPoints();
|
||||
this.addControlPoints();
|
||||
},
|
||||
/**
|
||||
* Redirect attr usage on the controllable graphic element.
|
||||
*/
|
||||
attr: function () {
|
||||
this.graphic.attr.apply(this.graphic, arguments);
|
||||
},
|
||||
/**
|
||||
* Get the controllable's points options.
|
||||
*
|
||||
* @return {Array<Highcharts.PointOptionsObject>}
|
||||
* An array of points' options.
|
||||
*/
|
||||
getPointsOptions: function () {
|
||||
var options = this.options;
|
||||
return (options.points || (options.point && splat(options.point)));
|
||||
},
|
||||
/**
|
||||
* Utility function for mapping item's options
|
||||
* to element's attribute
|
||||
*
|
||||
* @param {Highcharts.AnnotationsLabelsOptions|Highcharts.AnnotationsShapesOptions} options
|
||||
*
|
||||
* @return {Highcharts.SVGAttributes}
|
||||
* Mapped options.
|
||||
*/
|
||||
attrsFromOptions: function (options) {
|
||||
var map = this.constructor.attrsMap, attrs = {}, key, mappedKey, styledMode = this.chart.styledMode;
|
||||
for (key in options) { // eslint-disable-line guard-for-in
|
||||
mappedKey = map[key];
|
||||
if (mappedKey &&
|
||||
(!styledMode ||
|
||||
['fill', 'stroke', 'stroke-width']
|
||||
.indexOf(mappedKey) === -1)) {
|
||||
attrs[mappedKey] = options[key];
|
||||
}
|
||||
}
|
||||
return attrs;
|
||||
},
|
||||
/**
|
||||
* Returns object which denotes anchor position - relative and absolute.
|
||||
*
|
||||
* @param {Highcharts.AnnotationPointType} point
|
||||
* A point like object.
|
||||
*
|
||||
* @return {Highcharts.AnnotationAnchorObject} a controllable anchor
|
||||
*/
|
||||
anchor: function (point) {
|
||||
var plotBox = point.series.getPlotBox(), box = point.mock ?
|
||||
point.toAnchor() :
|
||||
Tooltip.prototype.getAnchor.call({
|
||||
chart: point.series.chart
|
||||
}, point), anchor = {
|
||||
x: box[0] + (this.options.x || 0),
|
||||
y: box[1] + (this.options.y || 0),
|
||||
height: box[2] || 0,
|
||||
width: box[3] || 0
|
||||
};
|
||||
return {
|
||||
relativePosition: anchor,
|
||||
absolutePosition: merge(anchor, {
|
||||
x: anchor.x + plotBox.translateX,
|
||||
y: anchor.y + plotBox.translateY
|
||||
})
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Map point's options to a point-like object.
|
||||
*
|
||||
* @param {string|Function|Highcharts.AnnotationMockPointOptionsObject|Highcharts.AnnotationPointType} pointOptions
|
||||
* Point's options.
|
||||
*
|
||||
* @param {Highcharts.AnnotationPointType} point
|
||||
* A point-like instance.
|
||||
*
|
||||
* @return {Highcharts.AnnotationPointType|null}
|
||||
* if the point is found/set returns this point, otherwise null
|
||||
*/
|
||||
point: function (pointOptions, point) {
|
||||
if (pointOptions && pointOptions.series) {
|
||||
return pointOptions;
|
||||
}
|
||||
if (!point || point.series === null) {
|
||||
if (isObject(pointOptions)) {
|
||||
point = new MockPoint(this.chart, this, pointOptions);
|
||||
}
|
||||
else if (isString(pointOptions)) {
|
||||
point = this.chart.get(pointOptions) || null;
|
||||
}
|
||||
else if (typeof pointOptions === 'function') {
|
||||
var pointConfig = pointOptions.call(point, this);
|
||||
point = pointConfig.series ?
|
||||
pointConfig :
|
||||
new MockPoint(this.chart, this, pointOptions);
|
||||
}
|
||||
}
|
||||
return point;
|
||||
},
|
||||
/**
|
||||
* Find point-like objects based on points options.
|
||||
*
|
||||
* @return {Array<Annotation.PointLike>} an array of point-like objects
|
||||
*/
|
||||
linkPoints: function () {
|
||||
var pointsOptions = this.getPointsOptions(), points = this.points, len = (pointsOptions && pointsOptions.length) || 0, i, point;
|
||||
for (i = 0; i < len; i++) {
|
||||
point = this.point(pointsOptions[i], points[i]);
|
||||
if (!point) {
|
||||
points.length = 0;
|
||||
return;
|
||||
}
|
||||
if (point.mock) {
|
||||
point.refresh();
|
||||
}
|
||||
points[i] = point;
|
||||
}
|
||||
return points;
|
||||
},
|
||||
/**
|
||||
* Add control points to a controllable.
|
||||
*/
|
||||
addControlPoints: function () {
|
||||
var controlPointsOptions = this.options.controlPoints;
|
||||
(controlPointsOptions || []).forEach(function (controlPointOptions, i) {
|
||||
var options = merge(this.options.controlPointOptions, controlPointOptions);
|
||||
if (!options.index) {
|
||||
options.index = i;
|
||||
}
|
||||
controlPointsOptions[i] = options;
|
||||
this.controlPoints.push(new ControlPoint(this.chart, this, options));
|
||||
}, this);
|
||||
},
|
||||
/**
|
||||
* Check if a controllable should be rendered/redrawn.
|
||||
*
|
||||
* @return {boolean}
|
||||
* Whether a controllable should be drawn.
|
||||
*/
|
||||
shouldBeDrawn: function () {
|
||||
return Boolean(this.points.length);
|
||||
},
|
||||
/**
|
||||
* Render a controllable.
|
||||
*/
|
||||
render: function (_parentGroup) {
|
||||
this.controlPoints.forEach(function (controlPoint) {
|
||||
controlPoint.render();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Redraw a controllable.
|
||||
*
|
||||
* @param {boolean} [animation]
|
||||
*/
|
||||
redraw: function (animation) {
|
||||
this.controlPoints.forEach(function (controlPoint) {
|
||||
controlPoint.redraw(animation);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Transform a controllable with a specific transformation.
|
||||
*
|
||||
* @param {string} transformation a transformation name
|
||||
* @param {number|null} cx origin x transformation
|
||||
* @param {number|null} cy origin y transformation
|
||||
* @param {number} p1 param for the transformation
|
||||
* @param {number} [p2] param for the transformation
|
||||
*/
|
||||
transform: function (transformation, cx, cy, p1, p2) {
|
||||
if (this.chart.inverted) {
|
||||
var temp = cx;
|
||||
cx = cy;
|
||||
cy = temp;
|
||||
}
|
||||
this.points.forEach(function (point, i) {
|
||||
this.transformPoint(transformation, cx, cy, p1, p2, i);
|
||||
}, this);
|
||||
},
|
||||
/**
|
||||
* Transform a point with a specific transformation
|
||||
* If a transformed point is a real point it is replaced with
|
||||
* the mock point.
|
||||
*
|
||||
* @param {string} transformation a transformation name
|
||||
* @param {number|null} cx origin x transformation
|
||||
* @param {number|null} cy origin y transformation
|
||||
* @param {number} p1 param for the transformation
|
||||
* @param {number|undefined} p2 param for the transformation
|
||||
* @param {number} i index of the point
|
||||
*/
|
||||
transformPoint: function (transformation, cx, cy, p1, p2, i) {
|
||||
var point = this.points[i];
|
||||
if (!point.mock) {
|
||||
point = this.points[i] = MockPoint.fromPoint(point);
|
||||
}
|
||||
point[transformation](cx, cy, p1, p2);
|
||||
},
|
||||
/**
|
||||
* Translate a controllable.
|
||||
*
|
||||
* @param {number} dx translation for x coordinate
|
||||
* @param {number} dy translation for y coordinate
|
||||
**/
|
||||
translate: function (dx, dy) {
|
||||
this.transform('translate', null, null, dx, dy);
|
||||
},
|
||||
/**
|
||||
* Translate a specific point within a controllable.
|
||||
*
|
||||
* @param {number} dx translation for x coordinate
|
||||
* @param {number} dy translation for y coordinate
|
||||
* @param {number} i index of the point
|
||||
**/
|
||||
translatePoint: function (dx, dy, i) {
|
||||
this.transformPoint('translate', null, null, dx, dy, i);
|
||||
},
|
||||
/**
|
||||
* Translate shape within controllable item.
|
||||
* Replaces `controllable.translate` method.
|
||||
*
|
||||
* @param {number} dx translation for x coordinate
|
||||
* @param {number} dy translation for y coordinate
|
||||
*/
|
||||
translateShape: function (dx, dy) {
|
||||
var chart = this.annotation.chart,
|
||||
// Annotation.options
|
||||
shapeOptions = this.annotation.userOptions,
|
||||
// Chart.options.annotations
|
||||
annotationIndex = chart.annotations.indexOf(this.annotation), chartOptions = chart.options.annotations[annotationIndex];
|
||||
this.translatePoint(dx, dy, 0);
|
||||
// Options stored in:
|
||||
// - chart (for exporting)
|
||||
// - current config (for redraws)
|
||||
chartOptions[this.collection][this.index].point = this.options.point;
|
||||
shapeOptions[this.collection][this.index].point = this.options.point;
|
||||
},
|
||||
/**
|
||||
* Rotate a controllable.
|
||||
*
|
||||
* @param {number} cx origin x rotation
|
||||
* @param {number} cy origin y rotation
|
||||
* @param {number} radians
|
||||
**/
|
||||
rotate: function (cx, cy, radians) {
|
||||
this.transform('rotate', cx, cy, radians);
|
||||
},
|
||||
/**
|
||||
* Scale a controllable.
|
||||
*
|
||||
* @param {number} cx origin x rotation
|
||||
* @param {number} cy origin y rotation
|
||||
* @param {number} sx scale factor x
|
||||
* @param {number} sy scale factor y
|
||||
*/
|
||||
scale: function (cx, cy, sx, sy) {
|
||||
this.transform('scale', cx, cy, sx, sy);
|
||||
},
|
||||
/**
|
||||
* Set control points' visibility.
|
||||
*
|
||||
* @param {boolean} visible
|
||||
*/
|
||||
setControlPointsVisibility: function (visible) {
|
||||
this.controlPoints.forEach(function (controlPoint) {
|
||||
controlPoint.setVisibility(visible);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Destroy a controllable.
|
||||
*/
|
||||
destroy: function () {
|
||||
if (this.graphic) {
|
||||
this.graphic = this.graphic.destroy();
|
||||
}
|
||||
if (this.tracker) {
|
||||
this.tracker = this.tracker.destroy();
|
||||
}
|
||||
this.controlPoints.forEach(function (controlPoint) {
|
||||
controlPoint.destroy();
|
||||
});
|
||||
this.chart = null;
|
||||
this.points = null;
|
||||
this.controlPoints = null;
|
||||
this.options = null;
|
||||
if (this.annotation) {
|
||||
this.annotation = null;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Update a controllable.
|
||||
*
|
||||
* @param {Object} newOptions
|
||||
*/
|
||||
update: function (newOptions) {
|
||||
var annotation = this.annotation, options = merge(true, this.options, newOptions), parentGroup = this.graphic.parentGroup;
|
||||
this.destroy();
|
||||
this.constructor(annotation, options);
|
||||
this.render(parentGroup);
|
||||
this.redraw();
|
||||
}
|
||||
};
|
||||
export default controllableMixin;
|
||||
@@ -0,0 +1,212 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
import H from '../../../Core/Globals.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, fireEvent = U.fireEvent, objectEach = U.objectEach, pick = U.pick, removeEvent = U.removeEvent;
|
||||
/* eslint-disable valid-jsdoc */
|
||||
/**
|
||||
* It provides methods for:
|
||||
* - adding and handling DOM events and a drag event,
|
||||
* - mapping a mouse move event to the distance between two following events.
|
||||
* The units of the distance are specific to a transformation,
|
||||
* e.g. for rotation they are radians, for scaling they are scale factors.
|
||||
*
|
||||
* @private
|
||||
* @mixin
|
||||
* @memberOf Annotation
|
||||
*/
|
||||
var eventEmitterMixin = {
|
||||
/**
|
||||
* Add emitter events.
|
||||
*/
|
||||
addEvents: function () {
|
||||
var emitter = this, addMouseDownEvent = function (element) {
|
||||
addEvent(element, H.isTouchDevice ? 'touchstart' : 'mousedown', function (e) {
|
||||
emitter.onMouseDown(e);
|
||||
});
|
||||
};
|
||||
addMouseDownEvent(this.graphic.element);
|
||||
(emitter.labels || []).forEach(function (label) {
|
||||
if (label.options.useHTML && label.graphic.text) {
|
||||
// Mousedown event bound to HTML element (#13070).
|
||||
addMouseDownEvent(label.graphic.text.element);
|
||||
}
|
||||
});
|
||||
objectEach(emitter.options.events, function (event, type) {
|
||||
var eventHandler = function (e) {
|
||||
if (type !== 'click' || !emitter.cancelClick) {
|
||||
event.call(emitter, emitter.chart.pointer.normalize(e), emitter.target);
|
||||
}
|
||||
};
|
||||
if ((emitter.nonDOMEvents || []).indexOf(type) === -1) {
|
||||
emitter.graphic.on(type, eventHandler);
|
||||
}
|
||||
else {
|
||||
addEvent(emitter, type, eventHandler);
|
||||
}
|
||||
});
|
||||
if (emitter.options.draggable) {
|
||||
addEvent(emitter, 'drag', emitter.onDrag);
|
||||
if (!emitter.graphic.renderer.styledMode) {
|
||||
var cssPointer_1 = {
|
||||
cursor: {
|
||||
x: 'ew-resize',
|
||||
y: 'ns-resize',
|
||||
xy: 'move'
|
||||
}[emitter.options.draggable]
|
||||
};
|
||||
emitter.graphic.css(cssPointer_1);
|
||||
(emitter.labels || []).forEach(function (label) {
|
||||
if (label.options.useHTML && label.graphic.text) {
|
||||
label.graphic.text.css(cssPointer_1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!emitter.isUpdating) {
|
||||
fireEvent(emitter, 'add');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Remove emitter document events.
|
||||
*/
|
||||
removeDocEvents: function () {
|
||||
if (this.removeDrag) {
|
||||
this.removeDrag = this.removeDrag();
|
||||
}
|
||||
if (this.removeMouseUp) {
|
||||
this.removeMouseUp = this.removeMouseUp();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Mouse down handler.
|
||||
*/
|
||||
onMouseDown: function (e) {
|
||||
var emitter = this, pointer = emitter.chart.pointer, prevChartX, prevChartY;
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
// On right click, do nothing:
|
||||
if (e.button === 2) {
|
||||
return;
|
||||
}
|
||||
e = pointer.normalize(e);
|
||||
prevChartX = e.chartX;
|
||||
prevChartY = e.chartY;
|
||||
emitter.cancelClick = false;
|
||||
emitter.chart.hasDraggedAnnotation = true;
|
||||
emitter.removeDrag = addEvent(H.doc, H.isTouchDevice ? 'touchmove' : 'mousemove', function (e) {
|
||||
emitter.hasDragged = true;
|
||||
e = pointer.normalize(e);
|
||||
e.prevChartX = prevChartX;
|
||||
e.prevChartY = prevChartY;
|
||||
fireEvent(emitter, 'drag', e);
|
||||
prevChartX = e.chartX;
|
||||
prevChartY = e.chartY;
|
||||
});
|
||||
emitter.removeMouseUp = addEvent(H.doc, H.isTouchDevice ? 'touchend' : 'mouseup', function (e) {
|
||||
emitter.cancelClick = emitter.hasDragged;
|
||||
emitter.hasDragged = false;
|
||||
emitter.chart.hasDraggedAnnotation = false;
|
||||
// ControlPoints vs Annotation:
|
||||
fireEvent(pick(emitter.target, emitter), 'afterUpdate');
|
||||
emitter.onMouseUp(e);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Mouse up handler.
|
||||
*/
|
||||
onMouseUp: function (_e) {
|
||||
var chart = this.chart, annotation = this.target || this, annotationsOptions = chart.options.annotations, index = chart.annotations.indexOf(annotation);
|
||||
this.removeDocEvents();
|
||||
annotationsOptions[index] = annotation.options;
|
||||
},
|
||||
/**
|
||||
* Drag and drop event. All basic annotations should share this
|
||||
* capability as well as the extended ones.
|
||||
*/
|
||||
onDrag: function (e) {
|
||||
if (this.chart.isInsidePlot(e.chartX - this.chart.plotLeft, e.chartY - this.chart.plotTop)) {
|
||||
var translation = this.mouseMoveToTranslation(e);
|
||||
if (this.options.draggable === 'x') {
|
||||
translation.y = 0;
|
||||
}
|
||||
if (this.options.draggable === 'y') {
|
||||
translation.x = 0;
|
||||
}
|
||||
if (this.points.length) {
|
||||
this.translate(translation.x, translation.y);
|
||||
}
|
||||
else {
|
||||
this.shapes.forEach(function (shape) {
|
||||
shape.translate(translation.x, translation.y);
|
||||
});
|
||||
this.labels.forEach(function (label) {
|
||||
label.translate(translation.x, translation.y);
|
||||
});
|
||||
}
|
||||
this.redraw(false);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Map mouse move event to the radians.
|
||||
*/
|
||||
mouseMoveToRadians: function (e, cx, cy) {
|
||||
var prevDy = e.prevChartY - cy, prevDx = e.prevChartX - cx, dy = e.chartY - cy, dx = e.chartX - cx, temp;
|
||||
if (this.chart.inverted) {
|
||||
temp = prevDx;
|
||||
prevDx = prevDy;
|
||||
prevDy = temp;
|
||||
temp = dx;
|
||||
dx = dy;
|
||||
dy = temp;
|
||||
}
|
||||
return Math.atan2(dy, dx) - Math.atan2(prevDy, prevDx);
|
||||
},
|
||||
/**
|
||||
* Map mouse move event to the distance between two following events.
|
||||
*/
|
||||
mouseMoveToTranslation: function (e) {
|
||||
var dx = e.chartX - e.prevChartX, dy = e.chartY - e.prevChartY, temp;
|
||||
if (this.chart.inverted) {
|
||||
temp = dy;
|
||||
dy = dx;
|
||||
dx = temp;
|
||||
}
|
||||
return {
|
||||
x: dx,
|
||||
y: dy
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Map mouse move to the scale factors.
|
||||
*
|
||||
* @param {Object} e event
|
||||
* @param {number} cx center x
|
||||
* @param {number} cy center y
|
||||
**/
|
||||
mouseMoveToScale: function (e, cx, cy) {
|
||||
var prevDx = e.prevChartX - cx, prevDy = e.prevChartY - cy, dx = e.chartX - cx, dy = e.chartY - cy, sx = (dx || 1) / (prevDx || 1), sy = (dy || 1) / (prevDy || 1), temp;
|
||||
if (this.chart.inverted) {
|
||||
temp = sy;
|
||||
sy = sx;
|
||||
sx = temp;
|
||||
}
|
||||
return {
|
||||
x: sx,
|
||||
y: sy
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Destroy the event emitter.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.removeDocEvents();
|
||||
removeEvent(this);
|
||||
this.hcEvents = null;
|
||||
}
|
||||
};
|
||||
export default eventEmitterMixin;
|
||||
@@ -0,0 +1,159 @@
|
||||
/* *
|
||||
*
|
||||
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
|
||||
*
|
||||
* */
|
||||
'use strict';
|
||||
import Chart from '../../../Core/Chart/Chart.js';
|
||||
import SVGRenderer from '../../../Core/Renderer/SVG/SVGRenderer.js';
|
||||
import U from '../../../Core/Utilities.js';
|
||||
var addEvent = U.addEvent, defined = U.defined, merge = U.merge, objectEach = U.objectEach, uniqueKey = U.uniqueKey;
|
||||
/**
|
||||
* Options for configuring markers for annotations.
|
||||
*
|
||||
* An example of the arrow marker:
|
||||
* <pre>
|
||||
* {
|
||||
* arrow: {
|
||||
* id: 'arrow',
|
||||
* tagName: 'marker',
|
||||
* refY: 5,
|
||||
* refX: 5,
|
||||
* markerWidth: 10,
|
||||
* markerHeight: 10,
|
||||
* children: [{
|
||||
* tagName: 'path',
|
||||
* attrs: {
|
||||
* d: 'M 0 0 L 10 5 L 0 10 Z',
|
||||
* strokeWidth: 0
|
||||
* }
|
||||
* }]
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @sample highcharts/annotations/custom-markers/
|
||||
* Define a custom marker for annotations
|
||||
*
|
||||
* @sample highcharts/css/annotations-markers/
|
||||
* Define markers in a styled mode
|
||||
*
|
||||
* @type {Highcharts.Dictionary<Highcharts.SVGDefinitionObject>}
|
||||
* @since 6.0.0
|
||||
* @optionparent defs
|
||||
*/
|
||||
var defaultMarkers = {
|
||||
/**
|
||||
* @type {Highcharts.SVGDefinitionObject}
|
||||
*/
|
||||
arrow: {
|
||||
tagName: 'marker',
|
||||
render: false,
|
||||
id: 'arrow',
|
||||
refY: 5,
|
||||
refX: 9,
|
||||
markerWidth: 10,
|
||||
markerHeight: 10,
|
||||
/**
|
||||
* @type {Array<Highcharts.DefsOptions>}
|
||||
*/
|
||||
children: [{
|
||||
tagName: 'path',
|
||||
d: 'M 0 0 L 10 5 L 0 10 Z',
|
||||
strokeWidth: 0
|
||||
}]
|
||||
},
|
||||
/**
|
||||
* @type {Highcharts.SVGDefinitionObject}
|
||||
*/
|
||||
'reverse-arrow': {
|
||||
tagName: 'marker',
|
||||
render: false,
|
||||
id: 'reverse-arrow',
|
||||
refY: 5,
|
||||
refX: 1,
|
||||
markerWidth: 10,
|
||||
markerHeight: 10,
|
||||
children: [{
|
||||
tagName: 'path',
|
||||
// reverse triangle (used as an arrow)
|
||||
d: 'M 0 5 L 10 0 L 10 10 Z',
|
||||
strokeWidth: 0
|
||||
}]
|
||||
}
|
||||
};
|
||||
SVGRenderer.prototype.addMarker = function (id, markerOptions) {
|
||||
var options = { id: id };
|
||||
var attrs = {
|
||||
stroke: markerOptions.color || 'none',
|
||||
fill: markerOptions.color || 'rgba(0, 0, 0, 0.75)'
|
||||
};
|
||||
options.children = markerOptions.children.map(function (child) {
|
||||
return merge(attrs, child);
|
||||
});
|
||||
var marker = this.definition(merge(true, {
|
||||
markerWidth: 20,
|
||||
markerHeight: 20,
|
||||
refX: 0,
|
||||
refY: 0,
|
||||
orient: 'auto'
|
||||
}, markerOptions, options));
|
||||
marker.id = id;
|
||||
return marker;
|
||||
};
|
||||
/* eslint-disable no-invalid-this, valid-jsdoc */
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function createMarkerSetter(markerType) {
|
||||
return function (value) {
|
||||
this.attr(markerType, 'url(#' + value + ')');
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @mixin
|
||||
* @name Highcharts.AnnotaitonMarkerMixin
|
||||
*/
|
||||
var markerMixin = {
|
||||
markerEndSetter: createMarkerSetter('marker-end'),
|
||||
markerStartSetter: createMarkerSetter('marker-start'),
|
||||
/**
|
||||
* Set markers.
|
||||
* @private
|
||||
* @param {Highcharts.AnnotationControllablePath} item
|
||||
*/
|
||||
setItemMarkers: function (item) {
|
||||
var itemOptions = item.options, chart = item.chart, defs = chart.options.defs, fill = itemOptions.fill, color = defined(fill) && fill !== 'none' ?
|
||||
fill :
|
||||
itemOptions.stroke, setMarker = function (markerType) {
|
||||
var markerId = itemOptions[markerType], def, predefinedMarker, key, marker;
|
||||
if (markerId) {
|
||||
for (key in defs) { // eslint-disable-line guard-for-in
|
||||
def = defs[key];
|
||||
if (markerId === def.id &&
|
||||
def.tagName === 'marker') {
|
||||
predefinedMarker = def;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (predefinedMarker) {
|
||||
marker = item[markerType] = chart.renderer
|
||||
.addMarker((itemOptions.id || uniqueKey()) + '-' +
|
||||
predefinedMarker.id, merge(predefinedMarker, { color: color }));
|
||||
item.attr(markerType, marker.attr('id'));
|
||||
}
|
||||
}
|
||||
};
|
||||
['markerStart', 'markerEnd'].forEach(setMarker);
|
||||
}
|
||||
};
|
||||
addEvent(Chart, 'afterGetContainer', function () {
|
||||
this.options.defs = merge(defaultMarkers, this.options.defs || {});
|
||||
objectEach(this.options.defs, function (def) {
|
||||
if (def.tagName === 'marker' && def.render !== false) {
|
||||
this.renderer.addMarker(def.id, def);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
export default markerMixin;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user