avances en plantillas

This commit is contained in:
JACS 2026-05-01 18:15:40 -05:00
parent 0f84beacf1
commit da0530d79b
2062 changed files with 598814 additions and 22 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,74 @@
.apexcharts-flip-y {
transform: scaleY(-1) translateY(-100%);
transform-origin: top;
transform-box: fill-box;
}
.apexcharts-flip-x {
transform: scaleX(-1);
transform-origin: center;
transform-box: fill-box;
}
.apexcharts-legend {
display: flex;
overflow: auto;
padding: 0 10px;
}
.apexcharts-legend.apexcharts-legend-group-horizontal {
flex-direction: column;
}
.apexcharts-legend-group {
display: flex;
}
.apexcharts-legend-group-vertical {
flex-direction: column-reverse;
}
.apexcharts-legend.apx-legend-position-bottom, .apexcharts-legend.apx-legend-position-top {
flex-wrap: wrap
}
.apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {
flex-direction: column;
bottom: 0;
}
.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-left, .apexcharts-legend.apx-legend-position-top.apexcharts-align-left, .apexcharts-legend.apx-legend-position-right, .apexcharts-legend.apx-legend-position-left {
justify-content: flex-start;
align-items: flex-start;
}
.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-center, .apexcharts-legend.apx-legend-position-top.apexcharts-align-center {
justify-content: center;
align-items: center;
}
.apexcharts-legend.apx-legend-position-bottom.apexcharts-align-right, .apexcharts-legend.apx-legend-position-top.apexcharts-align-right {
justify-content: flex-end;
align-items: flex-end;
}
.apexcharts-legend-series {
cursor: pointer;
line-height: normal;
display: flex;
align-items: center;
}
.apexcharts-legend-text {
position: relative;
font-size: 14px;
}
.apexcharts-legend-text *, .apexcharts-legend-marker * {
pointer-events: none;
}
.apexcharts-legend-marker {
position: relative;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
margin-right: 1px;
}
.apexcharts-legend-series.apexcharts-no-click {
cursor: auto;
}
.apexcharts-legend .apexcharts-hidden-zero-series, .apexcharts-legend .apexcharts-hidden-null-series {
display: none !important;
}
.apexcharts-inactive-legend {
opacity: 0.45;
}

View file

@ -0,0 +1,804 @@
@keyframes opaque {
0% {
opacity: 0
}
to {
opacity: 1
}
}
@keyframes resizeanim {
0%,
to {
opacity: 0
}
}
.apexcharts-canvas {
position: relative;
direction: ltr !important;
user-select: none
}
.apexcharts-canvas ::-webkit-scrollbar {
-webkit-appearance: none;
width: 6px
}
.apexcharts-canvas ::-webkit-scrollbar-thumb {
border-radius: 4px;
background-color: rgba(0, 0, 0, .5);
box-shadow: 0 0 1px rgba(255, 255, 255, .5);
-webkit-box-shadow: 0 0 1px rgba(255, 255, 255, .5)
}
.apexcharts-inner {
position: relative
}
.apexcharts-text tspan {
font-family: inherit
}
rect.legend-mouseover-inactive,
.legend-mouseover-inactive rect,
.legend-mouseover-inactive path,
.legend-mouseover-inactive circle,
.legend-mouseover-inactive line,
.legend-mouseover-inactive text.apexcharts-yaxis-title-text,
.legend-mouseover-inactive text.apexcharts-yaxis-label {
transition: .15s ease all;
opacity: .2
}
.apexcharts-legend-text {
padding-left: 15px;
margin-left: -15px;
}
.apexcharts-legend-series[role="button"]:focus {
outline: 2px solid #008FFB;
outline-offset: 2px;
}
.apexcharts-legend-series[role="button"]:focus:not(:focus-visible) {
outline: none;
}
.apexcharts-legend-series[role="button"]:focus-visible {
outline: 2px solid #008FFB;
outline-offset: 2px;
}
.apexcharts-series-collapsed {
opacity: 0
}
/* Keyboard navigation focus indicator on SVG data elements.
SVG elements don't support CSS outline, so we use stroke. */
.apexcharts-bar-area.apexcharts-keyboard-focused,
.apexcharts-candlestick-area.apexcharts-keyboard-focused,
.apexcharts-boxPlot-area.apexcharts-keyboard-focused,
.apexcharts-rangebar-area.apexcharts-keyboard-focused,
.apexcharts-pie-area.apexcharts-keyboard-focused,
.apexcharts-heatmap-rect.apexcharts-keyboard-focused,
.apexcharts-treemap-rect.apexcharts-keyboard-focused {
stroke: #008FFB;
stroke-width: 2;
stroke-opacity: 1;
}
.apexcharts-tooltip {
border-radius: 5px;
box-shadow: 2px 2px 6px -4px #999;
cursor: default;
font-size: 14px;
left: 62px;
opacity: 0;
pointer-events: none;
position: absolute;
top: 20px;
display: flex;
flex-direction: column;
overflow: hidden;
white-space: nowrap;
z-index: 12;
transition: .15s ease all
}
.apexcharts-tooltip.apexcharts-active {
opacity: 1;
transition: .15s ease all
}
.apexcharts-tooltip.apexcharts-theme-light {
border: 1px solid #e3e3e3;
background: rgba(255, 255, 255, .96)
}
.apexcharts-tooltip.apexcharts-theme-dark {
color: #fff;
background: rgba(30, 30, 30, .8)
}
.apexcharts-tooltip * {
font-family: inherit
}
.apexcharts-tooltip-title {
padding: 6px;
font-size: 15px;
margin-bottom: 4px
}
.apexcharts-tooltip.apexcharts-theme-light .apexcharts-tooltip-title {
background: #eceff1;
border-bottom: 1px solid #ddd
}
.apexcharts-tooltip.apexcharts-theme-dark .apexcharts-tooltip-title {
background: rgba(0, 0, 0, .7);
border-bottom: 1px solid #333
}
.apexcharts-tooltip-text-goals-value,
.apexcharts-tooltip-text-y-value,
.apexcharts-tooltip-text-z-value {
display: inline-block;
margin-left: 5px;
font-weight: 600
}
.apexcharts-tooltip-text-goals-label:empty,
.apexcharts-tooltip-text-goals-value:empty,
.apexcharts-tooltip-text-y-label:empty,
.apexcharts-tooltip-text-y-value:empty,
.apexcharts-tooltip-text-z-value:empty,
.apexcharts-tooltip-title:empty {
display: none
}
.apexcharts-tooltip-text-goals-label,
.apexcharts-tooltip-text-goals-value {
padding: 6px 0 5px
}
.apexcharts-tooltip-goals-group,
.apexcharts-tooltip-text-goals-label,
.apexcharts-tooltip-text-goals-value {
display: flex
}
.apexcharts-tooltip-text-goals-label:not(:empty),
.apexcharts-tooltip-text-goals-value:not(:empty) {
margin-top: -6px
}
.apexcharts-tooltip-marker {
display: inline-block;
position: relative;
width: 16px;
height: 16px;
font-size: 16px;
line-height: 16px;
margin-right: 4px;
text-align: center;
vertical-align: middle;
color: inherit;
}
.apexcharts-tooltip-marker::before {
content: "";
display: inline-block;
width: 100%;
text-align: center;
color: currentcolor;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
font-size: 26px;
font-family: Arial, Helvetica, sans-serif;
line-height: 14px;
font-weight: 900;
}
.apexcharts-tooltip-marker[shape="circle"]::before {
content: "\25CF";
}
.apexcharts-tooltip-marker[shape="square"]::before,
.apexcharts-tooltip-marker[shape="rect"]::before {
content: "\25A0";
transform: translate(-1px, -2px);
}
.apexcharts-tooltip-marker[shape="line"]::before {
content: "\2500";
}
.apexcharts-tooltip-marker[shape="diamond"]::before {
content: "\25C6";
font-size: 28px;
}
.apexcharts-tooltip-marker[shape="triangle"]::before {
content: "\25B2";
font-size: 22px;
}
.apexcharts-tooltip-marker[shape="cross"]::before {
content: "\2715";
font-size: 18px;
}
.apexcharts-tooltip-marker[shape="plus"]::before {
content: "\2715";
transform: rotate(45deg) translate(-1px, -1px);
font-size: 18px;
}
.apexcharts-tooltip-marker[shape="star"]::before {
content: "\2605";
font-size: 18px;
}
.apexcharts-tooltip-marker[shape="sparkle"]::before {
content: "\2726";
font-size: 20px;
}
.apexcharts-tooltip-series-group {
padding: 0 10px;
display: none;
text-align: left;
justify-content: left;
align-items: center
}
.apexcharts-tooltip-series-group.apexcharts-active .apexcharts-tooltip-marker {
opacity: 1
}
.apexcharts-tooltip-series-group.apexcharts-active,
.apexcharts-tooltip-series-group:last-child {
padding-bottom: 4px
}
.apexcharts-tooltip-y-group {
padding: 6px 0 5px
}
.apexcharts-custom-tooltip,
.apexcharts-tooltip-box {
padding: 4px 8px
}
.apexcharts-tooltip-boxPlot {
display: flex;
flex-direction: column-reverse
}
.apexcharts-tooltip-box>div {
margin: 4px 0
}
.apexcharts-tooltip-box span.value {
font-weight: 700
}
.apexcharts-tooltip-rangebar {
padding: 5px 8px
}
.apexcharts-tooltip-rangebar .category {
font-weight: 600;
color: #777
}
.apexcharts-tooltip-rangebar .series-name {
font-weight: 700;
display: block;
margin-bottom: 5px
}
.apexcharts-xaxistooltip,
.apexcharts-yaxistooltip {
opacity: 0;
pointer-events: none;
color: #373d3f;
font-size: 13px;
text-align: center;
border-radius: 2px;
position: absolute;
z-index: 10;
background: #eceff1;
border: 1px solid #90a4ae
}
.apexcharts-xaxistooltip {
padding: 9px 10px;
transition: .15s ease all
}
.apexcharts-xaxistooltip.apexcharts-theme-dark {
background: rgba(0, 0, 0, .7);
border: 1px solid rgba(0, 0, 0, .5);
color: #fff
}
.apexcharts-xaxistooltip:after,
.apexcharts-xaxistooltip:before {
left: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none
}
.apexcharts-xaxistooltip:after {
border-color: transparent;
border-width: 6px;
margin-left: -6px
}
.apexcharts-xaxistooltip:before {
border-color: transparent;
border-width: 7px;
margin-left: -7px
}
.apexcharts-xaxistooltip-bottom:after,
.apexcharts-xaxistooltip-bottom:before {
bottom: 100%
}
.apexcharts-xaxistooltip-top:after,
.apexcharts-xaxistooltip-top:before {
top: 100%
}
.apexcharts-xaxistooltip-bottom:after {
border-bottom-color: #eceff1
}
.apexcharts-xaxistooltip-bottom:before {
border-bottom-color: #90a4ae
}
.apexcharts-xaxistooltip-bottom.apexcharts-theme-dark:after,
.apexcharts-xaxistooltip-bottom.apexcharts-theme-dark:before {
border-bottom-color: rgba(0, 0, 0, .5)
}
.apexcharts-xaxistooltip-top:after {
border-top-color: #eceff1
}
.apexcharts-xaxistooltip-top:before {
border-top-color: #90a4ae
}
.apexcharts-xaxistooltip-top.apexcharts-theme-dark:after,
.apexcharts-xaxistooltip-top.apexcharts-theme-dark:before {
border-top-color: rgba(0, 0, 0, .5)
}
.apexcharts-xaxistooltip.apexcharts-active {
opacity: 1;
transition: .15s ease all
}
.apexcharts-yaxistooltip {
padding: 4px 10px
}
.apexcharts-yaxistooltip.apexcharts-theme-dark {
background: rgba(0, 0, 0, .7);
border: 1px solid rgba(0, 0, 0, .5);
color: #fff
}
.apexcharts-yaxistooltip:after,
.apexcharts-yaxistooltip:before {
top: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none
}
.apexcharts-yaxistooltip:after {
border-color: transparent;
border-width: 6px;
margin-top: -6px
}
.apexcharts-yaxistooltip:before {
border-color: transparent;
border-width: 7px;
margin-top: -7px
}
.apexcharts-yaxistooltip-left:after,
.apexcharts-yaxistooltip-left:before {
left: 100%
}
.apexcharts-yaxistooltip-right:after,
.apexcharts-yaxistooltip-right:before {
right: 100%
}
.apexcharts-yaxistooltip-left:after {
border-left-color: #eceff1
}
.apexcharts-yaxistooltip-left:before {
border-left-color: #90a4ae
}
.apexcharts-yaxistooltip-left.apexcharts-theme-dark:after,
.apexcharts-yaxistooltip-left.apexcharts-theme-dark:before {
border-left-color: rgba(0, 0, 0, .5)
}
.apexcharts-yaxistooltip-right:after {
border-right-color: #eceff1
}
.apexcharts-yaxistooltip-right:before {
border-right-color: #90a4ae
}
.apexcharts-yaxistooltip-right.apexcharts-theme-dark:after,
.apexcharts-yaxistooltip-right.apexcharts-theme-dark:before {
border-right-color: rgba(0, 0, 0, .5)
}
.apexcharts-yaxistooltip.apexcharts-active {
opacity: 1
}
.apexcharts-yaxistooltip-hidden {
display: none
}
.apexcharts-xcrosshairs,
.apexcharts-ycrosshairs {
pointer-events: none;
opacity: 0;
transition: .15s ease all
}
.apexcharts-xcrosshairs.apexcharts-active,
.apexcharts-ycrosshairs.apexcharts-active {
opacity: 1;
transition: .15s ease all
}
.apexcharts-ycrosshairs-hidden {
opacity: 0
}
.apexcharts-selection-rect {
cursor: move
}
.svg_select_shape {
stroke-width: 1;
stroke-dasharray: 10 10;
stroke: black;
stroke-opacity: 0.1;
pointer-events: none;
fill: none;
}
.svg_select_handle {
stroke-width: 3;
stroke: black;
fill: none;
}
.svg_select_handle_r {
cursor: e-resize;
}
.svg_select_handle_l {
cursor: w-resize;
}
.apexcharts-svg.apexcharts-zoomable.hovering-zoom {
cursor: crosshair
}
.apexcharts-svg.apexcharts-zoomable.hovering-pan {
cursor: move
}
.apexcharts-menu-icon,
.apexcharts-pan-icon,
.apexcharts-reset-icon,
.apexcharts-selection-icon,
.apexcharts-toolbar-custom-icon,
.apexcharts-zoom-icon,
.apexcharts-zoomin-icon,
.apexcharts-zoomout-icon {
cursor: pointer;
width: 20px;
height: 20px;
line-height: 24px;
color: #6e8192;
text-align: center
}
.apexcharts-menu-icon svg,
.apexcharts-reset-icon svg,
.apexcharts-zoom-icon svg,
.apexcharts-zoomin-icon svg,
.apexcharts-zoomout-icon svg {
fill: #6e8192
}
.apexcharts-selection-icon svg {
fill: #444;
transform: scale(.76)
}
.apexcharts-theme-dark .apexcharts-menu-icon svg,
.apexcharts-theme-dark .apexcharts-pan-icon svg,
.apexcharts-theme-dark .apexcharts-reset-icon svg,
.apexcharts-theme-dark .apexcharts-selection-icon svg,
.apexcharts-theme-dark .apexcharts-toolbar-custom-icon svg,
.apexcharts-theme-dark .apexcharts-zoom-icon svg,
.apexcharts-theme-dark .apexcharts-zoomin-icon svg,
.apexcharts-theme-dark .apexcharts-zoomout-icon svg {
fill: #f3f4f5
}
.apexcharts-canvas .apexcharts-reset-zoom-icon.apexcharts-selected svg,
.apexcharts-canvas .apexcharts-selection-icon.apexcharts-selected svg,
.apexcharts-canvas .apexcharts-zoom-icon.apexcharts-selected svg {
fill: #008ffb
}
.apexcharts-theme-light .apexcharts-menu-icon:hover svg,
.apexcharts-theme-light .apexcharts-reset-icon:hover svg,
.apexcharts-theme-light .apexcharts-selection-icon:not(.apexcharts-selected):hover svg,
.apexcharts-theme-light .apexcharts-zoom-icon:not(.apexcharts-selected):hover svg,
.apexcharts-theme-light .apexcharts-zoomin-icon:hover svg,
.apexcharts-theme-light .apexcharts-zoomout-icon:hover svg {
fill: #333
}
.apexcharts-menu-icon,
.apexcharts-selection-icon {
position: relative
}
.apexcharts-reset-icon {
margin-left: 5px
}
.apexcharts-menu-icon,
.apexcharts-reset-icon,
.apexcharts-zoom-icon {
transform: scale(.85)
}
.apexcharts-zoomin-icon,
.apexcharts-zoomout-icon {
transform: scale(.7)
}
.apexcharts-zoomout-icon {
margin-right: 3px
}
.apexcharts-pan-icon {
transform: scale(.62);
position: relative;
left: 1px;
top: 0
}
.apexcharts-pan-icon svg {
fill: #fff;
stroke: #6e8192;
stroke-width: 2
}
.apexcharts-pan-icon.apexcharts-selected svg {
stroke: #008ffb
}
.apexcharts-pan-icon:not(.apexcharts-selected):hover svg {
stroke: #333
}
.apexcharts-toolbar {
position: absolute;
z-index: 11;
max-width: 176px;
text-align: right;
border-radius: 3px;
padding: 0 6px 2px;
display: flex;
justify-content: space-between;
align-items: center
}
.apexcharts-menu {
background: #fff;
position: absolute;
top: 100%;
border: 1px solid #ddd;
border-radius: 3px;
padding: 3px;
right: 10px;
opacity: 0;
min-width: 110px;
transition: .15s ease all;
pointer-events: none
}
.apexcharts-menu.apexcharts-menu-open {
opacity: 1;
pointer-events: all;
transition: .15s ease all
}
.apexcharts-menu-item {
padding: 6px 7px;
font-size: 12px;
cursor: pointer
}
.apexcharts-theme-light .apexcharts-menu-item:hover {
background: #eee
}
.apexcharts-theme-dark .apexcharts-menu {
background: rgba(0, 0, 0, .7);
color: #fff
}
@media screen and (min-width:768px) {
.apexcharts-canvas:hover .apexcharts-toolbar {
opacity: 1
}
}
/* Toolbar keyboard accessibility: show toolbar when any button inside it is focused */
.apexcharts-toolbar:focus-within {
opacity: 1
}
/* Focus indicator for toolbar icon buttons */
.apexcharts-menu-icon:focus-visible,
.apexcharts-pan-icon:focus-visible,
.apexcharts-reset-icon:focus-visible,
.apexcharts-selection-icon:focus-visible,
.apexcharts-toolbar-custom-icon:focus-visible,
.apexcharts-zoom-icon:focus-visible,
.apexcharts-zoomin-icon:focus-visible,
.apexcharts-zoomout-icon:focus-visible {
outline: 2px solid #008FFB;
outline-offset: 2px;
border-radius: 2px
}
/* Focus indicator for hamburger menu items */
.apexcharts-menu-item:focus-visible {
outline: 2px solid #008FFB;
outline-offset: -2px;
background: #eee
}
.apexcharts-canvas .apexcharts-element-hidden,
.apexcharts-datalabel.apexcharts-element-hidden,
.apexcharts-hide .apexcharts-series-points {
opacity: 0;
}
.apexcharts-hidden-element-shown {
opacity: 1;
transition: 0.25s ease all;
}
.apexcharts-datalabel,
.apexcharts-datalabel-label,
.apexcharts-datalabel-value,
.apexcharts-datalabels,
.apexcharts-pie-label {
cursor: default;
pointer-events: none
}
.apexcharts-pie-label-delay {
opacity: 0;
animation-name: opaque;
animation-duration: .3s;
animation-fill-mode: forwards;
animation-timing-function: ease
}
.apexcharts-radialbar-label {
cursor: pointer;
}
.apexcharts-annotation-rect,
.apexcharts-area-series .apexcharts-area,
.apexcharts-gridline,
.apexcharts-line,
.apexcharts-point-annotation-label,
.apexcharts-radar-series path:not(.apexcharts-marker),
.apexcharts-radar-series polygon,
.apexcharts-toolbar svg,
.apexcharts-tooltip .apexcharts-marker,
.apexcharts-xaxis-annotation-label,
.apexcharts-yaxis-annotation-label,
.apexcharts-zoom-rect,
.no-pointer-events {
pointer-events: none
}
.apexcharts-tooltip-active .apexcharts-marker {
transition: .15s ease all
}
.apexcharts-radar-series .apexcharts-yaxis {
pointer-events: none;
}
.resize-triggers {
animation: 1ms resizeanim;
visibility: hidden;
opacity: 0;
height: 100%;
width: 100%;
overflow: hidden
}
.contract-trigger:before,
.resize-triggers,
.resize-triggers>div {
content: " ";
display: block;
position: absolute;
top: 0;
left: 0
}
.resize-triggers>div {
height: 100%;
width: 100%;
background: #eee;
overflow: auto
}
.contract-trigger:before {
overflow: hidden;
width: 200%;
height: 200%
}
.apexcharts-bar-goals-markers {
pointer-events: none
}
.apexcharts-bar-shadows {
pointer-events: none
}
.apexcharts-rangebar-goals-markers {
pointer-events: none
}
.apexcharts-disable-transitions * {
transition: none !important;
}

View file

@ -0,0 +1,5 @@
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="3.2"/>
<path d="M9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 355 B

View file

@ -0,0 +1,4 @@
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 199 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0V0z"/><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></svg>

After

Width:  |  Height:  |  Size: 185 B

View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" width="24" height="24">
<defs>
<path id="a" d="M0 0h24v24H0z"/>
</defs>
<clipPath id="b">
<use xlink:href="#a" overflow="visible"/>
</clipPath>
<path clip-path="url(#b)" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2z"/>
</svg>

After

Width:  |  Height:  |  Size: 416 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
</svg>

After

Width:  |  Height:  |  Size: 289 B

View file

@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="#000000" height="24" viewBox="0 0 24 24" width="24">
<defs>
<path d="M0 0h24v24H0z" id="a"/>
</defs>
<clipPath id="b">
<use overflow="visible" xlink:href="#a"/>
</clipPath>
<path clip-path="url(#b)" d="M23 5.5V20c0 2.2-1.8 4-4 4h-7.3c-1.08 0-2.1-.43-2.85-1.19L1 14.83s1.26-1.23 1.3-1.25c.22-.19.49-.29.79-.29.22 0 .42.06.6.16.04.01 4.31 2.46 4.31 2.46V4c0-.83.67-1.5 1.5-1.5S11 3.17 11 4v7h1V1.5c0-.83.67-1.5 1.5-1.5S15 .67 15 1.5V11h1V2.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5V11h1V5.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 656 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" height="24" viewBox="0 0 24 24" width="24">
<path d="M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 269 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 263 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/>
</svg>

After

Width:  |  Height:  |  Size: 308 B

View file

@ -0,0 +1,4 @@
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 366 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" height="24" viewBox="0 0 24 24" width="24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>
</svg>

After

Width:  |  Height:  |  Size: 265 B

View file

@ -0,0 +1,4 @@
<svg fill="#6E8192" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2z"/>
</svg>

After

Width:  |  Height:  |  Size: 439 B

View file

@ -0,0 +1,3 @@
<svg stroke="#333" fill="none" height="24" viewBox="0 0 28 24" width="24" xmlns="http://www.w3.org/2000/svg">
<rect x="4" y="4" width="16" height="16" stroke-width="2.5" stroke-dasharray="4" stroke-dashoffset="2"></rect>
</svg>

After

Width:  |  Height:  |  Size: 231 B

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" height="24" viewBox="0 0 24 24" width="24">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
<path d="M0 0h24v24H0V0z" fill="none"/>
<path d="M12 10h-2v2H9v-2H7V9h2V7h1v2h2v1z"/>
</svg>

After

Width:  |  Height:  |  Size: 450 B

View file

@ -0,0 +1,10 @@
<svg fill="#000000" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<path d="M0 0h24v24H0z" id="a"/>
</defs>
<clipPath id="b">
<use overflow="visible" xlink:href="#a"/>
</clipPath>
<path clip-path="url(#b)" d="M15 3l2.3 2.3-2.89 2.87 1.42 1.42L18.7 6.7 21 9V3zM3 9l2.3-2.3 2.87 2.89 1.42-1.42L6.7 5.3 9 3H3zm6 12l-2.3-2.3 2.89-2.87-1.42-1.42L5.3 17.3 3 15v6zm12-6l-2.3 2.3-2.87-2.89-1.42 1.42 2.89 2.87L15 21h6z"/>
<path clip-path="url(#b)" d="M0 0h24v24H0z" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 600 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="#000000" height="24" viewBox="0 0 24 24" width="24">
<path d="M0 0h24v24H0z" fill="none"/>
<path d="M3 5v4h2V5h4V3H5c-1.1 0-2 .9-2 2zm2 10H3v4c0 1.1.9 2 2 2h4v-2H5v-4zm14 4h-4v2h4c1.1 0 2-.9 2-2v-4h-2v4zm0-16h-4v2h4v4h2V5c0-1.1-.9-2-2-2z"/>
</svg>

After

Width:  |  Height:  |  Size: 301 B

View file

@ -0,0 +1,782 @@
// @ts-check
import BarDataLabels from './common/bar/DataLabels'
import BarHelpers from './common/bar/Helpers'
import CoreUtils from '../modules/CoreUtils'
import Utils from '../utils/Utils'
import Filters from '../modules/Filters'
import Graphics from '../modules/Graphics'
import Series from '../modules/Series'
/**
* ApexCharts Bar Class responsible for drawing both Columns and Bars.
*
* @module Bar
**/
class Bar {
/**
* @param {import('../types/internal').ChartStateW} w
* @param {import('../types/internal').ChartContext} ctx
* @param {import('../types/internal').XYRatios} xyRatios
*/
constructor(w, ctx, xyRatios) {
this.ctx = ctx
this.w = w
this.barOptions = w.config.plotOptions.bar
this.isHorizontal = this.barOptions.horizontal
this.strokeWidth = w.config.stroke.width
this.isNullValue = false
this.isRangeBar = w.rangeData.seriesRange.length && this.isHorizontal
this.isVerticalGroupedRangeBar =
!w.globals.isBarHorizontal &&
w.rangeData.seriesRange.length &&
w.config.plotOptions.bar.rangeBarGroupRows
this.isFunnel = this.barOptions.isFunnel
this.xyRatios = xyRatios
/** @type {number} */
this.xRatio = 0
/** @type {number[]} */
this.yRatio = []
/** @type {number} */
this.invertedXRatio = 0
/** @type {number} */
this.invertedYRatio = 0
/** @type {number[]} */
this.baseLineY = []
/** @type {number} */
this.baseLineInvertedY = 0
if (this.xyRatios !== null) {
this.xRatio = xyRatios.xRatio
this.yRatio = xyRatios.yRatio
this.invertedXRatio = xyRatios.invertedXRatio
this.invertedYRatio = xyRatios.invertedYRatio
this.baseLineY = xyRatios.baseLineY
this.baseLineInvertedY = xyRatios.baseLineInvertedY
}
this.yaxisIndex = 0
this.translationsIndex = 0
this.seriesLen = 0
/** @type {any} */
this.pathArr = []
/** @type {any[]} */
this.series = []
/** @type {any} */
this.elSeries = null
/** @type {number} */
this.visibleI = 0
/** @type {boolean} */
this.isReversed = false
const ser = new Series(this.w)
this.lastActiveBarSerieIndex = ser.getActiveConfigSeriesIndex('desc', [
'bar',
'column',
])
this.columnGroupIndices = []
const barSeriesIndices = ser.getBarSeriesIndices()
const coreUtils = new CoreUtils(this.w)
this.stackedSeriesTotals = coreUtils.getStackedSeriesTotals(
this.w.config.series
/**
* @param {any} s
* @param {number} i
*/
.map((s, i) => {
return barSeriesIndices.indexOf(i) === -1 ? i : -1
})
/**
* @param {any} s
*/
.filter((s) => {
return s !== -1
}),
)
this.barHelpers = new BarHelpers(this)
}
/** primary draw method which is called on bar object
* @memberof Bar
* @param {any[]} series - user supplied series values
* @param {number} seriesIndex - the index by which series will be drawn on the svg
* @return {Element} element which is supplied to parent chart draw method for appending
**/
draw(series, seriesIndex) {
const w = this.w
const graphics = new Graphics(this.w)
const coreUtils = new CoreUtils(this.w)
series = coreUtils.getLogSeries(series)
this.series = series
this.yRatio = coreUtils.getLogYRatios(this.yRatio)
this.barHelpers.initVariables(series)
const ret = graphics.group({
class: 'apexcharts-bar-series apexcharts-plot-series',
})
if (w.config.dataLabels.enabled) {
// @ts-ignore — totalItems is set dynamically by bar/Helpers.js initializePoints()
if (this.totalItems > this.barOptions.dataLabels.maxItems) {
console.warn(
'WARNING: DataLabels are enabled but there are too many to display. This may cause performance issue when rendering - ApexCharts',
)
}
}
for (let i = 0, bc = 0; i < series.length; i++, bc++) {
let x, y
const yArrj = [] // hold y values of current iterating series
const xArrj = [] // hold x values of current iterating series
const realIndex = w.globals.comboCharts
? /** @type {any} */ (seriesIndex)[i]
: i
const { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
// el to which series will be drawn
const elSeries = graphics.group({
class: `apexcharts-series`,
rel: i + 1,
seriesName: Utils.escapeString(w.seriesData.seriesNames[realIndex]),
'data:realIndex': realIndex,
})
Series.addCollapsedClassToSeries(this.w, elSeries, realIndex)
if (series[i].length > 0) {
this.visibleI = this.visibleI + 1
}
if (this.yRatio.length > 1) {
this.yaxisIndex = w.globals.seriesYAxisReverseMap[realIndex]
this.translationsIndex = realIndex
}
const translationsIndex = this.translationsIndex
this.isReversed =
w.config.yaxis[this.yaxisIndex] &&
w.config.yaxis[this.yaxisIndex].reversed
const initPositions = this.barHelpers.initialPositions(realIndex)
const {
y: initY,
yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
zeroW, // zeroW is the baseline where 0 meets x axis
x: initX,
xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
zeroH, // zeroH is the baseline where 0 meets y axis
} = initPositions
let barHeight = initPositions.barHeight
let barWidth = initPositions.barWidth
y = initY
x = initX
if (!this.isHorizontal) {
xArrj.push(x + (barWidth ?? 0) / 2)
}
// eldatalabels
const elDataLabelsWrap = graphics.group({
class: 'apexcharts-datalabels',
'data:realIndex': realIndex,
})
w.globals.delayedElements.push({
el: elDataLabelsWrap.node,
})
elDataLabelsWrap.node.classList.add('apexcharts-element-hidden')
const elGoalsMarkers = graphics.group({
class: 'apexcharts-bar-goals-markers',
})
const elBarShadows = graphics.group({
class: 'apexcharts-bar-shadows',
})
w.globals.delayedElements.push({
el: elBarShadows.node,
})
elBarShadows.node.classList.add('apexcharts-element-hidden')
for (let j = 0; j < series[i].length; j++) {
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
let paths = /** @type {any} */ (null)
const pathsParams = {
indexes: {
i,
j,
realIndex,
translationsIndex,
bc,
},
x,
y,
strokeWidth,
elSeries,
}
if (this.isHorizontal) {
paths = this.drawBarPaths({
...pathsParams,
barHeight,
zeroW,
yDivision,
})
barWidth = this.series[i][j] / this.invertedYRatio
} else {
paths = this.drawColumnPaths({
...pathsParams,
xDivision,
barWidth,
zeroH,
})
barHeight = this.series[i][j] / this.yRatio[translationsIndex]
}
const pathFill = this.barHelpers.getPathFillColor(
series,
i,
j,
realIndex,
)
if (
this.isFunnel &&
this.barOptions.isFunnel3d &&
this.pathArr.length &&
j > 0
) {
const barShadow = this.barHelpers.drawBarShadow({
color:
typeof pathFill.color === 'string' &&
pathFill.color?.indexOf('url') === -1
? pathFill.color
: Utils.hexToRgba(w.globals.colors[i]),
prevPaths: this.pathArr[this.pathArr.length - 1],
currPaths: paths,
realIndex,
j,
})
elBarShadows.add(barShadow)
if (w.config.chart.dropShadow.enabled) {
const filters = new Filters(this.w)
filters.dropShadow(barShadow, w.config.chart.dropShadow, realIndex)
}
}
this.pathArr.push(paths)
const barGoalLine = this.barHelpers.drawGoalLine({
barXPosition: paths.barXPosition,
barYPosition: paths.barYPosition,
goalX: paths.goalX,
goalY: paths.goalY,
barHeight,
barWidth,
})
if (barGoalLine) {
elGoalsMarkers.add(barGoalLine)
}
y = paths.y
x = paths.x
// push current X
if (j > 0) {
xArrj.push(x + (barWidth ?? 0) / 2)
}
yArrj.push(y)
this.renderSeries({
realIndex,
pathFill: pathFill.color,
...(pathFill.useRangeColor ? { lineFill: pathFill.color } : {}),
j,
i,
columnGroupIndex,
pathFrom: paths.pathFrom,
pathTo: paths.pathTo,
strokeWidth,
elSeries,
x,
y,
series,
barHeight: Math.abs(paths.barHeight ? paths.barHeight : barHeight),
barWidth: Math.abs(paths.barWidth ? paths.barWidth : barWidth),
elDataLabelsWrap,
elGoalsMarkers,
elBarShadows,
visibleSeries: this.visibleI,
type: 'bar',
})
}
// push all x val arrays into main xArr
w.globals.seriesXvalues[realIndex] = xArrj
w.globals.seriesYvalues[realIndex] = yArrj
ret.add(elSeries)
}
return ret
}
/** @param {{ realIndex?: any, pathFill?: any, lineFill?: any, j?: any, i?: any, columnGroupIndex?: any, pathFrom?: any, pathTo?: any, strokeWidth?: any, elSeries?: any, x?: any, y?: any, y1?: any, y2?: any, series?: any, barHeight?: any, barWidth?: any, barXPosition?: any, barYPosition?: any, elDataLabelsWrap?: any, elGoalsMarkers?: any, elBarShadows?: any, visibleSeries?: any, type?: any, classes?: any }} opts */
renderSeries({
realIndex,
pathFill,
lineFill,
j,
i,
columnGroupIndex,
pathFrom,
pathTo,
strokeWidth,
elSeries,
x, // x pos
y, // y pos
y1, // absolute value
y2, // absolute value
series,
barHeight,
barWidth,
barXPosition,
barYPosition,
elDataLabelsWrap,
elGoalsMarkers,
elBarShadows,
visibleSeries,
type,
classes,
}) {
const w = this.w
const graphics = new Graphics(this.w, this.ctx)
let skipDrawing = false
// Set up event delegation once per series group instead of per-element listeners
if (!elSeries._bindingsDelegated) {
elSeries._bindingsDelegated = true
graphics.setupEventDelegation(elSeries, `.apexcharts-${type}-area`)
}
if (!lineFill) {
// if user provided a function in colors, we need to eval here
// Note: the position of this function logic (ex. stroke: { colors: ["",function(){}] }) i.e array index 1 depicts the realIndex/seriesIndex.
/**
* @param {number} i
*/
function fetchColor(i) {
const exp = w.config.stroke.colors
let c
if (Array.isArray(exp) && exp.length > 0) {
c = exp[i]
if (!c) c = ''
if (typeof c === 'function') {
return c({
value: w.seriesData.series[i][j],
dataPointIndex: j,
w,
})
}
}
return c
}
const checkAvailableColor =
typeof w.globals.stroke.colors[realIndex] === 'function'
? fetchColor(realIndex)
: w.globals.stroke.colors[realIndex]
/* fix apexcharts#341 */
lineFill = this.barOptions.distributed
? w.globals.stroke.colors[j]
: checkAvailableColor
}
const barDataLabels = new BarDataLabels(this)
const dataLabelsObj = /** @type {any} */ (
barDataLabels.handleBarDataLabels({
x,
y,
y1,
y2,
i,
j,
series,
realIndex,
columnGroupIndex,
barHeight,
barWidth,
barXPosition,
barYPosition,
visibleSeries,
})
)
if (!w.globals.isBarHorizontal) {
if (
dataLabelsObj.dataLabelsPos.dataLabelsX +
Math.max(barWidth, w.globals.barPadForNumericAxis) <
0 ||
dataLabelsObj.dataLabelsPos.dataLabelsX -
Math.max(barWidth, w.globals.barPadForNumericAxis) >
w.layout.gridWidth
) {
skipDrawing = true
}
}
if (
/** @type {Record<string,any>} */ (w.config.series[i]).data[j] &&
/** @type {Record<string,any>} */ (w.config.series[i]).data[j].strokeColor
) {
lineFill = /** @type {Record<string,any>} */ (w.config.series[i]).data[j]
.strokeColor
}
if (this.isNullValue) {
pathFill = 'none'
}
const delay =
((j / w.config.chart.animations.animateGradually.delay) *
(w.config.chart.animations.speed / w.globals.dataPoints)) /
2.4
if (!skipDrawing) {
const renderedPath = /** @type {any} */ (
graphics.renderPaths({
i,
j,
realIndex,
pathFrom,
pathTo,
stroke: lineFill,
strokeWidth,
strokeLineCap: w.config.stroke.lineCap,
fill: pathFill,
animationDelay: delay,
initialSpeed: w.config.chart.animations.speed,
dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
className: `apexcharts-${type}-area ${classes}`,
chartType: type,
bindEventsOnPaths: false,
})
)
renderedPath.attr('clip-path', `url(#gridRectBarMask${w.globals.cuid})`)
const forecast = w.config.forecastDataPoints
if (forecast.count > 0) {
if (j >= w.globals.dataPoints - forecast.count) {
renderedPath.node.setAttribute('stroke-dasharray', forecast.dashArray)
renderedPath.node.setAttribute('stroke-width', forecast.strokeWidth)
renderedPath.node.setAttribute('fill-opacity', forecast.fillOpacity)
}
}
if (typeof y1 !== 'undefined' && typeof y2 !== 'undefined') {
renderedPath.attr('data-range-y1', y1)
renderedPath.attr('data-range-y2', y2)
}
const filters = new Filters(this.w)
filters.setSelectionFilter(renderedPath, realIndex, j)
elSeries.add(renderedPath)
renderedPath.attr({
cy: dataLabelsObj.dataLabelsPos.bcy,
cx: dataLabelsObj.dataLabelsPos.bcx,
j,
val: w.seriesData.series[i][j],
barHeight,
barWidth,
})
if (dataLabelsObj.dataLabels !== null) {
elDataLabelsWrap.add(dataLabelsObj.dataLabels)
}
if (dataLabelsObj.totalDataLabels) {
elDataLabelsWrap.add(dataLabelsObj.totalDataLabels)
}
elSeries.add(elDataLabelsWrap)
if (elGoalsMarkers) {
elSeries.add(elGoalsMarkers)
}
if (elBarShadows) {
elSeries.add(elBarShadows)
}
}
return elSeries
}
/** @param {{indexes: any, barHeight: any, strokeWidth: any, zeroW: any, x: any, y: any, yDivision: any, elSeries: any}} opts */
drawBarPaths({
indexes,
barHeight,
strokeWidth,
zeroW,
x,
y,
yDivision,
elSeries,
}) {
const w = this.w
const i = indexes.i
const j = indexes.j
let barYPosition
if (w.axisFlags.isXNumeric) {
y =
(w.seriesData.seriesX[i][j] - w.globals.minX) / this.invertedXRatio -
barHeight
barYPosition = y + barHeight * this.visibleI
} else {
if (w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
const { nonZeroColumns, zeroEncounters } =
this.barHelpers.getZeroValueEncounters({ i, j })
if (nonZeroColumns > 0) {
barHeight = (this.seriesLen * barHeight) / nonZeroColumns
}
barYPosition = y + barHeight * this.visibleI
barYPosition -= barHeight * zeroEncounters
} else {
barYPosition = y + barHeight * this.visibleI
}
}
if (this.isFunnel) {
const _zeroW = zeroW ?? 0
zeroW =
_zeroW -
/** @type {number} */ ((
/** @type {any} */ (
this.barHelpers.getXForValue(
/** @type {any} */ (this.series)[i][j],
_zeroW,
)
)
) -
_zeroW) /
2
}
x = this.barHelpers.getXForValue(
/** @type {any} */ (this.series)[i][j],
zeroW ?? 0,
)
const paths = /** @type {any} */ (
this.barHelpers.getBarpaths({
barYPosition,
barHeight,
x1: zeroW,
x2: x,
strokeWidth,
isReversed: this.isReversed,
series: this.series,
realIndex: indexes.realIndex,
i,
j,
w,
})
)
if (!w.axisFlags.isXNumeric) {
y = y + yDivision
}
this.barHelpers.barBackground({
j,
i,
y1: barYPosition - barHeight * this.visibleI,
y2: barHeight * this.seriesLen,
elSeries,
})
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
x1: zeroW,
x,
y,
goalX: this.barHelpers.getGoalValues(
'x',
zeroW,
/** @type {any} */ (null),
i,
j,
0,
),
barYPosition,
barHeight,
}
}
/** @param {{indexes: any, x: any, y: any, xDivision: any, barWidth: any, zeroH: any, strokeWidth: any, elSeries: any}} opts */
drawColumnPaths({
indexes,
x,
y,
xDivision,
barWidth,
zeroH,
strokeWidth,
elSeries,
}) {
const w = this.w
const realIndex = indexes.realIndex
const translationsIndex = indexes.translationsIndex
const i = indexes.i
const j = indexes.j
const bc = indexes.bc
let barXPosition
if (w.axisFlags.isXNumeric) {
const xForNumericX = this.getBarXForNumericXAxis({
x,
j,
realIndex,
barWidth,
})
x = xForNumericX.x
barXPosition = xForNumericX.barXPosition
} else {
if (w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
const { nonZeroColumns, zeroEncounters } =
this.barHelpers.getZeroValueEncounters({ i, j })
if (nonZeroColumns > 0) {
barWidth = (this.seriesLen * barWidth) / nonZeroColumns
}
barXPosition = x + barWidth * this.visibleI
barXPosition -= barWidth * zeroEncounters
} else {
barXPosition = x + barWidth * this.visibleI
}
}
y = this.barHelpers.getYForValue(
/** @type {any} */ (this.series)[i][j],
zeroH,
translationsIndex,
)
const paths = /** @type {any} */ (
this.barHelpers.getColumnPaths({
barXPosition,
barWidth,
y1: zeroH,
y2: y,
strokeWidth,
isReversed: this.isReversed,
series: this.series,
realIndex: realIndex,
i,
j,
w,
})
)
if (!w.axisFlags.isXNumeric) {
x = x + xDivision
}
this.barHelpers.barBackground({
bc,
j,
i,
x1: barXPosition - strokeWidth / 2 - barWidth * this.visibleI,
x2: barWidth * this.seriesLen + strokeWidth / 2,
elSeries,
})
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
x,
y,
goalY: this.barHelpers.getGoalValues(
'y',
/** @type {any} */ (null),
zeroH,
i,
j,
translationsIndex,
),
barXPosition,
barWidth,
}
}
/** @param {{x: any, barWidth: any, realIndex: any, j: any}} opts */
getBarXForNumericXAxis({ x, barWidth, realIndex, j }) {
const w = this.w
let sxI = realIndex
if (!w.seriesData.seriesX[realIndex].length) {
sxI = w.globals.maxValsInArrayIndex
}
if (Utils.isNumber(w.seriesData.seriesX[sxI][j])) {
x =
(w.seriesData.seriesX[sxI][j] - w.globals.minX) / this.xRatio -
(barWidth * this.seriesLen) / 2
}
return {
barXPosition: x + barWidth * this.visibleI,
x,
}
}
/** getPreviousPath is a common function for bars/columns which is used to get previous paths when data changes.
* @memberof Bar
* @param {number} realIndex - current iterating i
* @param {number} j - current iterating series's j index
* @return {string} pathFrom is the string which will be appended in animations
**/
getPreviousPath(realIndex, j) {
const w = this.w
let pathFrom = 'M 0 0'
for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
const gpp = w.globals.previousPaths[pp]
if (
gpp.paths &&
gpp.paths.length > 0 &&
parseInt(gpp.realIndex, 10) === parseInt(String(realIndex), 10)
) {
if (typeof w.globals.previousPaths[pp].paths[j] !== 'undefined') {
pathFrom = w.globals.previousPaths[pp].paths[j].d
}
}
}
return pathFrom
}
}
export default Bar

View file

@ -0,0 +1,620 @@
// @ts-check
import CoreUtils from '../modules/CoreUtils'
import Bar from './Bar'
import Graphics from '../modules/Graphics'
import Series from '../modules/Series'
import Utils from '../utils/Utils'
/**
* ApexCharts BarStacked Class responsible for drawing both Stacked Columns and Bars.
*
* @module BarStacked
* The whole calculation for stacked bar/column is different from normal bar/column,
* hence it makes sense to derive a new class for it extending most of the props of Parent Bar
**/
class BarStacked extends Bar {
/**
* @param {any[]} series
* @param {number} seriesIndex
*/
draw(series, seriesIndex) {
const w = this.w
this.graphics = new Graphics(this.w)
this.bar = new Bar(this.w, this.ctx, this.xyRatios)
const coreUtils = new CoreUtils(this.w)
series = coreUtils.getLogSeries(series)
this.yRatio = coreUtils.getLogYRatios(this.yRatio)
this.barHelpers.initVariables(series)
if (w.config.chart.stackType === '100%') {
series = w.globals.comboCharts
? /** @type {any} */ (seriesIndex).map(
(/** @type {any} */ _) => w.globals.seriesPercent[_],
)
: w.globals.seriesPercent.slice()
}
this.series = series
this.barHelpers.initializeStackedPrevVars(this)
const ret = this.graphics.group({
class: 'apexcharts-bar-series apexcharts-plot-series',
})
let x = 0
let y = 0
for (let i = 0, bc = 0; i < series.length; i++, bc++) {
const realIndex = w.globals.comboCharts
? /** @type {any} */ (seriesIndex)[i]
: i
const { groupIndex, columnGroupIndex } =
this.barHelpers.getGroupIndex(realIndex)
this.groupCtx = /** @type {any} */ (this)[
/** @type {any} */ (w.labelData.seriesGroups[groupIndex])
]
const xArrValues = []
const yArrValues = []
let translationsIndex = 0
if (this.yRatio.length > 1) {
this.yaxisIndex = /** @type {any} */ (
w.globals.seriesYAxisReverseMap[realIndex]
)[0]
translationsIndex = realIndex
}
this.isReversed =
w.config.yaxis[this.yaxisIndex] &&
w.config.yaxis[this.yaxisIndex].reversed
// el to which series will be drawn
let elSeries = this.graphics.group({
class: `apexcharts-series`,
seriesName: Utils.escapeString(w.seriesData.seriesNames[realIndex]),
rel: i + 1,
'data:realIndex': realIndex,
})
Series.addCollapsedClassToSeries(this.w, elSeries, realIndex)
// eldatalabels
const elDataLabelsWrap = this.graphics.group({
class: 'apexcharts-datalabels',
'data:realIndex': realIndex,
})
const elGoalsMarkers = this.graphics.group({
class: 'apexcharts-bar-goals-markers',
})
const initPositions = this.initialPositions(
x,
y,
undefined,
undefined,
undefined,
undefined,
translationsIndex,
)
const {
xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
zeroH, // zeroH is the baseline where 0 meets y axis
zeroW, // zeroW is the baseline where 0 meets x axis
} = initPositions
let barHeight = initPositions.barHeight
let barWidth = initPositions.barWidth
y = initPositions.y
x = initPositions.x
w.globals.barHeight = barHeight
w.globals.barWidth = barWidth
this.barHelpers.initializeStackedXYVars(this)
// where all stack bar disappear after collapsing the first series
if (
this.groupCtx.prevY.length === 1 &&
/**
* @param {number} val
*/
this.groupCtx.prevY[0].every((/** @type {any} */ val) => isNaN(val))
) {
this.groupCtx.prevY[0] = this.groupCtx.prevY[0].map(() => zeroH)
this.groupCtx.prevYF[0] = this.groupCtx.prevYF[0].map(() => 0)
}
for (let j = 0; j < w.globals.dataPoints; j++) {
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
const commonPathOpts = {
indexes: { i, j, realIndex, translationsIndex, bc },
strokeWidth,
x,
y,
elSeries,
columnGroupIndex,
seriesGroup: w.labelData.seriesGroups[groupIndex],
}
let paths = /** @type {any} */ (null)
if (this.isHorizontal) {
paths = this.drawStackedBarPaths({
...commonPathOpts,
zeroW,
barHeight,
yDivision,
})
barWidth = this.series[i][j] / this.invertedYRatio
} else {
paths = this.drawStackedColumnPaths({
...commonPathOpts,
xDivision,
barWidth,
zeroH,
})
barHeight = this.series[i][j] / this.yRatio[translationsIndex]
}
const barGoalLine = this.barHelpers.drawGoalLine({
barXPosition: paths.barXPosition,
barYPosition: paths.barYPosition,
goalX: paths.goalX,
goalY: paths.goalY,
barHeight,
barWidth,
})
if (barGoalLine) {
elGoalsMarkers.add(barGoalLine)
}
y = paths.y
x = paths.x
xArrValues.push(x)
yArrValues.push(y)
const pathFill = this.barHelpers.getPathFillColor(
series,
i,
j,
realIndex,
)
let classes = ''
const flipClass = w.globals.isBarHorizontal
? 'apexcharts-flip-x'
: 'apexcharts-flip-y'
if (
(this.barHelpers.arrBorderRadius[realIndex][j] === 'bottom' &&
w.seriesData.series[realIndex][j] > 0) ||
(this.barHelpers.arrBorderRadius[realIndex][j] === 'top' &&
w.seriesData.series[realIndex][j] < 0)
) {
classes = flipClass
}
elSeries = this.renderSeries({
realIndex,
pathFill: pathFill.color,
...(pathFill.useRangeColor ? { lineFill: pathFill.color } : {}),
j,
i,
columnGroupIndex,
pathFrom: paths.pathFrom,
pathTo: paths.pathTo,
strokeWidth,
elSeries,
x,
y,
series,
barHeight,
barWidth,
elDataLabelsWrap,
elGoalsMarkers,
type: 'bar',
visibleSeries: columnGroupIndex,
classes,
})
}
// push all x val arrays into main xArr
w.globals.seriesXvalues[realIndex] = xArrValues
w.globals.seriesYvalues[realIndex] = yArrValues
// push all current y values array to main PrevY Array
this.groupCtx.prevY.push(this.groupCtx.yArrj)
this.groupCtx.prevYF.push(this.groupCtx.yArrjF)
this.groupCtx.prevYVal.push(this.groupCtx.yArrjVal)
this.groupCtx.prevX.push(this.groupCtx.xArrj)
this.groupCtx.prevXF.push(this.groupCtx.xArrjF)
this.groupCtx.prevXVal.push(this.groupCtx.xArrjVal)
ret.add(elSeries)
}
return ret
}
/**
* @param {number} x
* @param {number} y
* @param {number | undefined} xDivision
* @param {number | undefined} yDivision
* @param {number | undefined} zeroH
* @param {number | undefined} zeroW
* @param {number} translationsIndex
*/
initialPositions(
x,
y,
xDivision,
yDivision,
zeroH,
zeroW,
translationsIndex,
) {
const w = this.w
let barHeight, barWidth
if (this.isHorizontal) {
// height divided into equal parts
yDivision = w.layout.gridHeight / w.globals.dataPoints
const userBarHeight = w.config.plotOptions.bar.barHeight
if (String(userBarHeight).indexOf('%') === -1) {
barHeight = parseInt(userBarHeight, 10)
} else {
barHeight = (yDivision * parseInt(userBarHeight, 10)) / 100
}
zeroW =
w.globals.padHorizontal +
(this.isReversed
? w.layout.gridWidth - this.baseLineInvertedY
: this.baseLineInvertedY)
// initial y position is half of barHeight * half of number of Bars
y = (yDivision - barHeight) / 2
} else {
// width divided into equal parts
xDivision = w.layout.gridWidth / w.globals.dataPoints
barWidth = xDivision
const userColumnWidth = w.config.plotOptions.bar.columnWidth
if (w.axisFlags.isXNumeric && w.globals.dataPoints > 1) {
xDivision = w.globals.minXDiff / this.xRatio
barWidth = (xDivision * parseInt(this.barOptions.columnWidth, 10)) / 100
} else if (String(userColumnWidth).indexOf('%') === -1) {
barWidth = parseInt(userColumnWidth, 10)
} else {
barWidth *= parseInt(userColumnWidth, 10) / 100
}
if (this.isReversed) {
zeroH = this.baseLineY[translationsIndex]
} else {
zeroH = w.layout.gridHeight - this.baseLineY[translationsIndex]
}
// initial x position is the left-most edge of the first bar relative to
// the left-most side of the grid area.
x = w.globals.padHorizontal + (xDivision - barWidth) / 2
}
// Up to this point, barWidth is the width that will accommodate all bars
// at each datapoint or category.
// The crude subdivision here assumes the series within each group are
// stacked. If there is no stacking then the barWidth/barHeight is
// further divided later by the number of series in the group. So, eg, two
// groups of three series would become six bars side-by-side unstacked,
// or two bars stacked.
const subDivisions = w.globals.barGroups.length || 1
return {
x,
y,
yDivision,
xDivision,
barHeight: (barHeight ?? 0) / subDivisions,
barWidth: (barWidth ?? 0) / subDivisions,
zeroH,
zeroW,
}
}
/** @param {{indexes: any, barHeight: any, strokeWidth: any, zeroW: any, x: any, y: any, columnGroupIndex: any, seriesGroup: any, yDivision: any, elSeries: any}} opts */
drawStackedBarPaths({
indexes,
barHeight,
strokeWidth,
zeroW,
x,
y,
columnGroupIndex,
seriesGroup,
yDivision,
elSeries,
}) {
const w = this.w
const barYPosition = y + columnGroupIndex * barHeight
let barXPosition
const i = indexes.i
const j = indexes.j
const realIndex = indexes.realIndex
const translationsIndex = indexes.translationsIndex
let prevBarW = 0
for (let k = 0; k < this.groupCtx.prevXF.length; k++) {
prevBarW = prevBarW + this.groupCtx.prevXF[k][j]
}
let gsi = i // an index to keep track of the series inside a group
if (/** @type {Record<string,any>} */ (w.config.series[realIndex]).name) {
gsi = seriesGroup.indexOf(
/** @type {Record<string,any>} */ (w.config.series[realIndex]).name,
)
}
if (gsi > 0) {
let bXP = zeroW
if (this.groupCtx.prevXVal[gsi - 1][j] < 0) {
bXP =
/** @type {any} */ (this.series)[i]?.[j] >= 0
? this.groupCtx.prevX[gsi - 1][j] +
prevBarW -
(this.isReversed ? prevBarW : 0) * 2
: this.groupCtx.prevX[gsi - 1][j]
} else if (this.groupCtx.prevXVal[gsi - 1][j] >= 0) {
bXP =
/** @type {any} */ (this.series)[i]?.[j] >= 0
? this.groupCtx.prevX[gsi - 1][j]
: this.groupCtx.prevX[gsi - 1][j] -
prevBarW +
(this.isReversed ? prevBarW : 0) * 2
}
barXPosition = bXP
} else {
// the first series will not have prevX values
barXPosition = zeroW
}
if (/** @type {any} */ (this.series)[i]?.[j] === null) {
x = barXPosition
} else {
x =
barXPosition +
/** @type {any} */ (this.series)[i]?.[j] / this.invertedYRatio -
(this.isReversed
? /** @type {any} */ (this.series)[i]?.[j] / this.invertedYRatio
: 0) *
2
}
const paths = this.barHelpers.getBarpaths({
barYPosition,
barHeight,
x1: barXPosition,
x2: x,
strokeWidth,
isReversed: this.isReversed,
series: this.series,
realIndex: indexes.realIndex,
seriesGroup,
i,
j,
w,
})
this.barHelpers.barBackground({
j,
i,
y1: barYPosition,
y2: barHeight,
elSeries,
})
y = y + yDivision
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
goalX: this.barHelpers.getGoalValues(
'x',
zeroW,
/** @type {any} */ (null),
i,
j,
translationsIndex,
),
barXPosition,
barYPosition,
x,
y,
}
}
/** @param {{indexes: any, x: any, y: any, xDivision: any, barWidth: any, zeroH: any, columnGroupIndex: any, seriesGroup: any, elSeries: any}} opts */
drawStackedColumnPaths({
indexes,
x,
y,
xDivision,
barWidth,
zeroH,
columnGroupIndex,
seriesGroup,
elSeries,
}) {
const w = this.w
const i = indexes.i
const j = indexes.j
const bc = indexes.bc
const realIndex = indexes.realIndex
const translationsIndex = indexes.translationsIndex
if (w.axisFlags.isXNumeric) {
let seriesVal = w.seriesData.seriesX[realIndex][j]
if (!seriesVal) seriesVal = 0
// TODO: move the barWidth factor to barXPosition
x =
(seriesVal - w.globals.minX) / this.xRatio -
(barWidth / 2) * w.globals.barGroups.length
}
const barXPosition = x + columnGroupIndex * barWidth
let barYPosition
let prevBarH = 0
for (let k = 0; k < this.groupCtx.prevYF.length; k++) {
// fix issue #1215
// in case where this.groupCtx.prevYF[k][j] is NaN, use 0 instead
prevBarH =
prevBarH +
(!isNaN(this.groupCtx.prevYF[k][j]) ? this.groupCtx.prevYF[k][j] : 0)
}
let gsi = i // an index to keep track of the series inside a group
if (seriesGroup) {
gsi = seriesGroup.indexOf(w.seriesData.seriesNames[realIndex])
}
if (
(gsi > 0 && !w.axisFlags.isXNumeric) ||
(gsi > 0 &&
w.axisFlags.isXNumeric &&
w.seriesData.seriesX[realIndex - 1][j] ===
w.seriesData.seriesX[realIndex][j])
) {
let bYP
let prevYValue
const p = Math.min(this.yRatio.length + 1, realIndex + 1)
if (
this.groupCtx.prevY[gsi - 1] !== undefined &&
this.groupCtx.prevY[gsi - 1].length
) {
for (let ii = 1; ii < p; ii++) {
if (!isNaN(this.groupCtx.prevY[gsi - ii]?.[j])) {
// find the previous available value to give prevYValue
prevYValue = this.groupCtx.prevY[gsi - ii][j]
// if found it, break the loop
break
}
}
}
for (let ii = 1; ii < p; ii++) {
// find the previous available value(non-NaN) to give bYP
if (this.groupCtx.prevYVal[gsi - ii]?.[j] < 0) {
bYP =
/** @type {any} */ (this.series)[i]?.[j] >= 0
? prevYValue - prevBarH + (this.isReversed ? prevBarH : 0) * 2
: prevYValue
// found it? break the loop
break
} else if (this.groupCtx.prevYVal[gsi - ii]?.[j] >= 0) {
bYP =
/** @type {any} */ (this.series)[i]?.[j] >= 0
? prevYValue
: prevYValue + prevBarH - (this.isReversed ? prevBarH : 0) * 2
// found it? break the loop
break
}
}
if (typeof bYP === 'undefined') bYP = w.layout.gridHeight
// if this.prevYF[0] is all 0 resulted from line #486
// AND every arr starting from the second only contains NaN
if (
/**
* @param {number} val
*/
this.groupCtx.prevYF[0]?.every((/** @type {any} */ val) => val === 0) &&
this.groupCtx.prevYF
.slice(1, gsi)
/**
* @param {any[]} arr
* @param {number} val
*/
.every((/** @type {any} */ arr) =>
arr.every((/** @type {any} */ val) => isNaN(val)),
)
) {
barYPosition = zeroH
} else {
// Nothing special
barYPosition = bYP
}
} else {
// the first series will not have prevY values, also if the prev index's
// series X doesn't matches the current index's series X, then start from
// zero
barYPosition = zeroH
}
if (/** @type {any} */ (this.series)[i]?.[j]) {
y =
barYPosition -
/** @type {any} */ (this.series)[i]?.[j] /
this.yRatio[translationsIndex] +
(this.isReversed
? /** @type {any} */ (this.series)[i]?.[j] /
this.yRatio[translationsIndex]
: 0) *
2
} else {
// fixes #3610
y = barYPosition
}
const paths = this.barHelpers.getColumnPaths({
barXPosition,
barWidth,
y1: barYPosition,
y2: y,
yRatio: this.yRatio[translationsIndex],
strokeWidth: this.strokeWidth,
isReversed: this.isReversed,
series: this.series,
seriesGroup,
realIndex: indexes.realIndex,
i,
j,
w,
})
this.barHelpers.barBackground({
bc,
j,
i,
x1: barXPosition,
x2: barWidth,
elSeries,
})
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
goalY: this.barHelpers.getGoalValues(
'y',
/** @type {any} */ (null),
zeroH,
i,
j,
0,
),
barXPosition,
x: w.axisFlags.isXNumeric ? x : x + xDivision,
y,
}
}
}
export default BarStacked

View file

@ -0,0 +1,527 @@
// @ts-check
import CoreUtils from '../modules/CoreUtils'
import Bar from './Bar'
import Fill from '../modules/Fill'
import Graphics from '../modules/Graphics'
import Series from '../modules/Series'
import Utils from '../utils/Utils'
/**
* ApexCharts BoxCandleStick Class responsible for drawing both Stacked Columns and Bars.
*
* @module BoxCandleStick
**/
class BoxCandleStick extends Bar {
/**
* @param {any[]} series
* @param {string} ctype
* @param {number} seriesIndex
*/
// @ts-ignore -- BoxCandleStick.draw has an extra ctype param compared to Bar.draw
draw(series, ctype, seriesIndex) {
const w = this.w
const graphics = new Graphics(this.w)
const type = w.globals.comboCharts ? ctype : w.config.chart.type
const fill = new Fill(this.w)
this.candlestickOptions = this.w.config.plotOptions.candlestick
this.boxOptions = this.w.config.plotOptions.boxPlot
this.isHorizontal = w.config.plotOptions.bar.horizontal
this.isOHLC =
this.candlestickOptions && this.candlestickOptions.type === 'ohlc'
this.coreUtils = new CoreUtils(this.w)
series = this.coreUtils.getLogSeries(series)
this.series = series
this.yRatio = this.coreUtils.getLogYRatios(this.yRatio)
this.barHelpers.initVariables(series)
const ret = graphics.group({
class: `apexcharts-${type}-series apexcharts-plot-series`,
})
for (let i = 0; i < series.length; i++) {
this.isBoxPlot =
w.config.chart.type === 'boxPlot' ||
/** @type {Record<string,any>} */ (w.config.series[i]).type === 'boxPlot'
/** @type {any} */
let x
/** @type {any} */
let y
const yArrj = [] // hold y values of current iterating series
const xArrj = [] // hold x values of current iterating series
const realIndex = w.globals.comboCharts
? /** @type {any} */ (seriesIndex)[i]
: i
const { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
const elSeries = graphics.group({
class: `apexcharts-series`,
seriesName: Utils.escapeString(w.seriesData.seriesNames[realIndex]),
rel: i + 1,
'data:realIndex': realIndex,
})
Series.addCollapsedClassToSeries(this.w, elSeries, realIndex)
if (series[i].length > 0) {
this.visibleI = this.visibleI + 1
}
let translationsIndex = 0
if (this.yRatio.length > 1) {
this.yaxisIndex = /** @type {any} */ (
w.globals.seriesYAxisReverseMap[realIndex]
)[0]
translationsIndex = realIndex
}
const initPositions = this.barHelpers.initialPositions(realIndex)
const {
y: initY,
barHeight,
yDivision, // yDivision is the GRIDHEIGHT divided by number of datapoints (bars)
zeroW, // zeroW is the baseline where 0 meets x axis
x: initX,
barWidth,
xDivision, // xDivision is the GRIDWIDTH divided by number of datapoints (columns)
zeroH, // zeroH is the baseline where 0 meets y axis
} = initPositions
y = initY
x = initX
xArrj.push(x + (barWidth ?? 0) / 2)
const elDataLabelsWrap = graphics.group({
class: 'apexcharts-datalabels',
'data:realIndex': realIndex,
})
const elGoalsMarkers = graphics.group({
class: 'apexcharts-bar-goals-markers',
})
for (let j = 0; j < w.globals.dataPoints; j++) {
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
let paths = /** @type {any} */ (null)
const pathsParams = {
indexes: {
i,
j,
realIndex,
translationsIndex,
},
x,
y,
strokeWidth,
elSeries,
}
if (this.isHorizontal) {
paths = this.drawHorizontalBoxPaths({
...pathsParams,
yDivision,
barHeight,
zeroW,
})
} else {
paths = this.drawVerticalBoxPaths({
...pathsParams,
xDivision,
barWidth,
zeroH,
})
}
y = paths.y
x = paths.x
const barGoalLine = this.barHelpers.drawGoalLine({
barXPosition: paths.barXPosition,
barYPosition: paths.barYPosition,
goalX: paths.goalX,
goalY: paths.goalY,
barHeight,
barWidth,
})
if (barGoalLine) {
elGoalsMarkers.add(barGoalLine)
}
// push current X
if (j > 0) {
xArrj.push(x + (barWidth ?? 0) / 2)
}
yArrj.push(y)
/**
* @param {string} pathTo
* @param {number} pi
*/
paths.pathTo.forEach(
(/** @type {any} */ pathTo, /** @type {any} */ pi) => {
const lineFill =
!this.isBoxPlot && this.candlestickOptions.wick.useFillColor
? paths.color[pi]
: w.globals.stroke.colors[i]
const pathFill = fill.fillPath({
seriesNumber: realIndex,
dataPointIndex: j,
color: paths.color[pi],
value: series[i][j],
})
this.renderSeries({
realIndex,
pathFill,
lineFill,
j,
i,
pathFrom: paths.pathFrom,
pathTo,
strokeWidth,
elSeries,
x,
y,
series,
columnGroupIndex,
barHeight,
barWidth,
elDataLabelsWrap,
elGoalsMarkers,
visibleSeries: this.visibleI,
type: w.config.chart.type,
})
},
)
}
// push all x val arrays into main xArr
w.globals.seriesXvalues[realIndex] = xArrj
w.globals.seriesYvalues[realIndex] = yArrj
ret.add(elSeries)
}
return ret
}
/** @param {{indexes: any, x: any, xDivision: any, barWidth: any, zeroH: any, strokeWidth: any}} opts */
drawVerticalBoxPaths({
indexes,
x,
xDivision,
barWidth,
zeroH,
strokeWidth,
}) {
const w = this.w
const graphics = new Graphics(this.w)
const i = indexes.i
const j = indexes.j
const { colors: candleColors } = w.config.plotOptions.candlestick
const { colors: boxColors } = this.boxOptions
const realIndex = indexes.realIndex
/**
* @param {string} color
*/
const getColor = (color) =>
Array.isArray(color) ? color[realIndex] : color
const colorPos = getColor(candleColors.upward)
const colorNeg = getColor(candleColors.downward)
const yRatio = this.yRatio[indexes.translationsIndex]
const ohlc = this.getOHLCValue(realIndex, j)
let l1 = zeroH
let l2 = zeroH
let color = ohlc.o < ohlc.c ? [colorPos] : [colorNeg]
if (this.isBoxPlot) {
color = [getColor(boxColors.lower), getColor(boxColors.upper)]
}
let y1 = Math.min(ohlc.o, ohlc.c)
let y2 = Math.max(ohlc.o, ohlc.c)
let m = ohlc.m
if (w.axisFlags.isXNumeric) {
x =
(w.seriesData.seriesX[realIndex][j] - w.globals.minX) / this.xRatio -
barWidth / 2
}
const barXPosition = x + barWidth * this.visibleI
if (
typeof /** @type {any} */ (this.series)[i]?.[j] === 'undefined' ||
/** @type {any} */ (this.series)[i]?.[j] === null
) {
y1 = zeroH
y2 = zeroH
} else {
y1 = zeroH - y1 / yRatio
y2 = zeroH - y2 / yRatio
l1 = zeroH - ohlc.h / yRatio
l2 = zeroH - ohlc.l / yRatio
m = zeroH - ohlc.m / yRatio
}
let pathTo
let pathFrom = graphics.move(barXPosition + barWidth / 2, y1)
if (w.globals.previousPaths.length > 0) {
pathFrom = this.getPreviousPath(realIndex, j)
}
if (this.isOHLC) {
const centerX = barXPosition + barWidth / 2
const openY = zeroH - ohlc.o / yRatio
const closeY = zeroH - ohlc.c / yRatio
pathTo = [
graphics.move(centerX, l1) +
graphics.line(centerX, l2) +
graphics.move(centerX, openY) +
graphics.line(barXPosition, openY) +
graphics.move(centerX, closeY) +
graphics.line(barXPosition + barWidth, closeY),
]
} else if (this.isBoxPlot) {
pathTo = [
graphics.move(barXPosition, y1) +
graphics.line(barXPosition + barWidth / 2, y1) +
graphics.line(barXPosition + barWidth / 2, l1) +
graphics.line(barXPosition + barWidth / 4, l1) +
graphics.line(barXPosition + barWidth - barWidth / 4, l1) +
graphics.line(barXPosition + barWidth / 2, l1) +
graphics.line(barXPosition + barWidth / 2, y1) +
graphics.line(barXPosition + barWidth, y1) +
graphics.line(barXPosition + barWidth, m) +
graphics.line(barXPosition, m) +
graphics.line(barXPosition, y1 + strokeWidth / 2),
graphics.move(barXPosition, m) +
graphics.line(barXPosition + barWidth, m) +
graphics.line(barXPosition + barWidth, y2) +
graphics.line(barXPosition + barWidth / 2, y2) +
graphics.line(barXPosition + barWidth / 2, l2) +
graphics.line(barXPosition + barWidth - barWidth / 4, l2) +
graphics.line(barXPosition + barWidth / 4, l2) +
graphics.line(barXPosition + barWidth / 2, l2) +
graphics.line(barXPosition + barWidth / 2, y2) +
graphics.line(barXPosition, y2) +
graphics.line(barXPosition, m) +
'z',
]
} else {
// Regular candlestick
pathTo = [
graphics.move(barXPosition, y2) +
graphics.line(barXPosition + barWidth / 2, y2) +
graphics.line(barXPosition + barWidth / 2, l1) +
graphics.line(barXPosition + barWidth / 2, y2) +
graphics.line(barXPosition + barWidth, y2) +
graphics.line(barXPosition + barWidth, y1) +
graphics.line(barXPosition + barWidth / 2, y1) +
graphics.line(barXPosition + barWidth / 2, l2) +
graphics.line(barXPosition + barWidth / 2, y1) +
graphics.line(barXPosition, y1) +
graphics.line(barXPosition, y2 - strokeWidth / 2),
]
}
pathFrom = pathFrom + graphics.move(barXPosition, y1)
if (!w.axisFlags.isXNumeric) {
x = x + xDivision
}
return {
pathTo,
pathFrom,
x,
y: y2,
goalY: this.barHelpers.getGoalValues(
'y',
/** @type {any} */ (null),
zeroH,
i,
j,
indexes.translationsIndex,
),
barXPosition,
color,
}
}
/** @param {{indexes: any, y: any, yDivision: any, barHeight: any, zeroW: any, strokeWidth: any}} opts */
drawHorizontalBoxPaths({
indexes,
y,
yDivision,
barHeight,
zeroW,
strokeWidth,
}) {
const w = this.w
const graphics = new Graphics(this.w)
const i = indexes.i
const j = indexes.j
const realIndex = indexes.realIndex
const { colors: candleColors } = w.config.plotOptions.candlestick
const { colors: boxColors } = this.boxOptions
/**
* @param {string} color
*/
const getColor = (color) =>
Array.isArray(color) ? color[realIndex] : color
const yRatio = this.invertedYRatio
const ohlc = this.getOHLCValue(realIndex, j)
let color =
ohlc.o < ohlc.c
? [getColor(candleColors.upward)]
: [getColor(candleColors.downward)]
if (this.isBoxPlot) {
color = [getColor(boxColors.lower), getColor(boxColors.upper)]
}
let l1 = zeroW
let l2 = zeroW
let x1 = Math.min(ohlc.o, ohlc.c)
let x2 = Math.max(ohlc.o, ohlc.c)
let m = ohlc.m
if (w.axisFlags.isXNumeric) {
y =
(w.seriesData.seriesX[realIndex][j] - w.globals.minX) /
this.invertedXRatio -
barHeight / 2
}
const barYPosition = y + barHeight * this.visibleI
if (
typeof /** @type {any} */ (this.series)[i]?.[j] === 'undefined' ||
/** @type {any} */ (this.series)[i]?.[j] === null
) {
x1 = zeroW
x2 = zeroW
} else {
x1 = zeroW + x1 / yRatio
x2 = zeroW + x2 / yRatio
l1 = zeroW + ohlc.h / yRatio
l2 = zeroW + ohlc.l / yRatio
m = zeroW + ohlc.m / yRatio
}
let pathFrom = graphics.move(x1, barYPosition + barHeight / 2)
if (w.globals.previousPaths.length > 0) {
pathFrom = this.getPreviousPath(realIndex, j)
}
const pathTo = [
graphics.move(x1, barYPosition) +
graphics.line(x1, barYPosition + barHeight / 2) +
graphics.line(l1, barYPosition + barHeight / 2) +
graphics.line(l1, barYPosition + barHeight / 2 - barHeight / 4) +
graphics.line(l1, barYPosition + barHeight / 2 + barHeight / 4) +
graphics.line(l1, barYPosition + barHeight / 2) +
graphics.line(x1, barYPosition + barHeight / 2) +
graphics.line(x1, barYPosition + barHeight) +
graphics.line(m, barYPosition + barHeight) +
graphics.line(m, barYPosition) +
graphics.line(x1 + strokeWidth / 2, barYPosition),
graphics.move(m, barYPosition) +
graphics.line(m, barYPosition + barHeight) +
graphics.line(x2, barYPosition + barHeight) +
graphics.line(x2, barYPosition + barHeight / 2) +
graphics.line(l2, barYPosition + barHeight / 2) +
graphics.line(l2, barYPosition + barHeight - barHeight / 4) +
graphics.line(l2, barYPosition + barHeight / 4) +
graphics.line(l2, barYPosition + barHeight / 2) +
graphics.line(x2, barYPosition + barHeight / 2) +
graphics.line(x2, barYPosition) +
graphics.line(m, barYPosition) +
'z',
]
pathFrom = pathFrom + graphics.move(x1, barYPosition)
if (!w.axisFlags.isXNumeric) {
y = y + yDivision
}
return {
pathTo,
pathFrom,
x: x2,
y,
goalX: this.barHelpers.getGoalValues(
'x',
zeroW,
/** @type {any} */ (null),
i,
j,
0,
),
barYPosition,
color,
}
}
/**
* @param {number} i
* @param {number} j
*/
getOHLCValue(i, j) {
const w = this.w
const coreUtils = this.coreUtils
/**
* @param {any[]} arr
*/
const getCandleVal = (arr) =>
arr[i] && arr[i][j] != null
? /** @type {any} */ (coreUtils).getLogValAtSeriesIndex(arr[i][j], i)
: 0
const h = getCandleVal(w.candleData.seriesCandleH)
const o = getCandleVal(w.candleData.seriesCandleO)
const m = getCandleVal(w.candleData.seriesCandleM)
const c = getCandleVal(w.candleData.seriesCandleC)
const l = getCandleVal(w.candleData.seriesCandleL)
// BoxPlot data arrives as [min, q1, median, q3, max] and is stored in
// H=min, O=q1, M=median, C=q3, L=max — remap to OHLC semantics:
// o=q1(O), h=min(H), m=median(M), l=q3(C), c=max(L)
return {
o: this.isBoxPlot ? h : o,
h: this.isBoxPlot ? o : h,
m,
l: this.isBoxPlot ? c : l,
c: this.isBoxPlot ? l : c,
}
}
}
export default BoxCandleStick

View file

@ -0,0 +1,283 @@
// @ts-check
import Animations from '../modules/Animations'
import Graphics from '../modules/Graphics'
import Fill from '../modules/Fill'
import Series from '../modules/Series'
import Utils from '../utils/Utils'
import Helpers from './common/treemap/Helpers'
import Filters from '../modules/Filters'
/**
* ApexCharts HeatMap Class.
* @module HeatMap
**/
export default class HeatMap {
/**
* @param {import('../types/internal').ChartStateW} w
* @param {import('../types/internal').ChartContext} ctx
* @param {import('../types/internal').XYRatios} xyRatios
*/
constructor(w, ctx, xyRatios) {
this.ctx = ctx
this.w = w
this.xRatio = xyRatios.xRatio
this.yRatio = xyRatios.yRatio
this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
this.helpers = new Helpers(w, ctx)
this.rectRadius = this.w.config.plotOptions.heatmap.radius
this.strokeWidth = this.w.config.stroke.show
? this.w.config.stroke.width
: 0
}
/**
* @param {any[]} series
*/
draw(series) {
const w = this.w
const graphics = new Graphics(this.w, this.ctx)
const ret = graphics.group({
class: 'apexcharts-heatmap',
})
ret.attr('clip-path', `url(#gridRectMask${w.globals.cuid})`)
// width divided into equal parts
const xDivision = w.layout.gridWidth / w.globals.dataPoints
const yDivision = w.layout.gridHeight / w.seriesData.series.length
let y1 = 0
let rev = false
this.negRange = this.helpers.checkColorRange()
const heatSeries = series.slice()
if (w.config.yaxis[0].reversed) {
rev = true
heatSeries.reverse()
}
for (
let i = rev ? 0 : heatSeries.length - 1;
rev ? i < heatSeries.length : i >= 0;
rev ? i++ : i--
) {
// el to which series will be drawn
const elSeries = graphics.group({
class: `apexcharts-series apexcharts-heatmap-series`,
seriesName: Utils.escapeString(w.seriesData.seriesNames[i]),
rel: i + 1,
'data:realIndex': i,
})
Series.addCollapsedClassToSeries(this.w, elSeries, i)
// Set up event delegation once per series group instead of per-cell listeners
graphics.setupEventDelegation(elSeries, '.apexcharts-heatmap-rect')
if (w.config.chart.dropShadow.enabled) {
const shadow = w.config.chart.dropShadow
const filters = new Filters(this.w)
filters.dropShadow(elSeries, shadow, i)
}
let x1 = 0
const shadeIntensity = w.config.plotOptions.heatmap.shadeIntensity
let j = 0
for (let dIndex = 0; dIndex < w.globals.dataPoints; dIndex++) {
// Recognize gaps and align values based on x axis
if (w.seriesData.seriesX.length && !w.globals.allSeriesHasEqualX) {
if (
w.globals.minX + w.globals.minXDiff * dIndex <
w.seriesData.seriesX[i][j]
) {
x1 = x1 + xDivision
continue
}
}
// Stop loop if index is out of array length
if (j >= heatSeries[i].length) break
const heatColor = this.helpers.getShadeColor(
w.config.chart.type,
i,
j,
this.negRange,
)
let color = heatColor.color
const heatColorProps = heatColor.colorProps
if (w.config.fill.type === 'image') {
const fill = new Fill(this.w)
color = fill.fillPath({
seriesNumber: i,
dataPointIndex: j,
opacity: /** @type {any} */ (w.globals).hasNegs
? heatColorProps.percent < 0
? 1 - (1 + heatColorProps.percent / 100)
: shadeIntensity + heatColorProps.percent / 100
: heatColorProps.percent / 100,
patternID: Utils.randomId(),
width: w.config.fill.image.width
? w.config.fill.image.width
: xDivision,
height: w.config.fill.image.height
? w.config.fill.image.height
: yDivision,
})
}
const radius = this.rectRadius
const rect = graphics.drawRect(x1, y1, xDivision, yDivision, radius)
rect.attr({
cx: x1,
cy: y1,
})
rect.node.classList.add('apexcharts-heatmap-rect')
elSeries.add(rect)
rect.attr({
fill: color,
i,
index: i,
j,
val: series[i][j],
'stroke-width': this.strokeWidth,
stroke: w.config.plotOptions.heatmap.useFillColorAsStroke
? color
: w.globals.stroke.colors[0],
color,
})
if (w.config.chart.animations.enabled && !w.globals.dataChanged) {
let speed = 1
if (!w.globals.resized) {
speed = w.config.chart.animations.speed
}
this.animateHeatMap(rect, x1, y1, xDivision, yDivision, speed)
}
if (w.globals.dataChanged) {
let speed = 1
if (this.dynamicAnim.enabled && w.globals.shouldAnimate) {
speed = this.dynamicAnim.speed
let colorFrom =
w.globals.previousPaths[i] &&
w.globals.previousPaths[i][j] &&
w.globals.previousPaths[i][j].color
if (!colorFrom) colorFrom = 'rgba(255, 255, 255, 0)'
this.animateHeatColor(
rect,
Utils.isColorHex(colorFrom)
? colorFrom
: Utils.rgb2hex(colorFrom),
Utils.isColorHex(color) ? color : Utils.rgb2hex(color),
speed,
)
}
}
const formatter = w.config.dataLabels.formatter
const formattedText = formatter(w.seriesData.series[i][j], {
value: w.seriesData.series[i][j],
seriesIndex: i,
dataPointIndex: j,
w,
})
const dataLabels = this.helpers.calculateDataLabels({
text: formattedText,
x: x1 + xDivision / 2,
y: y1 + yDivision / 2,
i,
j,
colorProps: heatColorProps,
series: heatSeries,
})
if (dataLabels !== null) {
elSeries.add(dataLabels)
}
x1 = x1 + xDivision
j++
}
y1 = y1 + yDivision
ret.add(elSeries)
}
// adjust yaxis labels for heatmap
const yAxisScale = /** @type {any[]} */ (
w.globals.yAxisScale[0].result.slice()
)
if (w.config.yaxis[0].reversed) {
yAxisScale.unshift('')
} else {
yAxisScale.push('')
}
w.globals.yAxisScale[0].result = yAxisScale
return ret
}
/**
* @param {any} el
* @param {number} x
* @param {number} y
* @param {number} width
* @param {number} height
* @param {number} speed
*/
animateHeatMap(el, x, y, width, height, speed) {
const animations = new Animations(this.w)
animations.animateRect(
el,
{
x: x + width / 2,
y: y + height / 2,
width: 0,
height: 0,
},
{
x,
y,
width,
height,
},
speed,
() => {
animations.animationCompleted(el)
},
)
}
/**
* @param {any} el
* @param {string} colorFrom
* @param {string} colorTo
* @param {number} speed
*/
animateHeatColor(el, colorFrom, colorTo, speed) {
el.attr({
fill: colorFrom,
})
.animate(speed)
.attr({
fill: colorTo,
})
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,598 @@
// @ts-check
import Fill from '../modules/Fill'
import Graphics from '../modules/Graphics'
import Markers from '../modules/Markers'
import DataLabels from '../modules/DataLabels'
import Filters from '../modules/Filters'
import Utils from '../utils/Utils'
import Helpers from './common/circle/Helpers'
import CoreUtils from '../modules/CoreUtils'
/**
* ApexCharts Radar Class for Spider/Radar Charts.
* @module Radar
**/
class Radar {
/**
* @param {import('../types/internal').ChartStateW} w
* @param {import('../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.ctx = ctx
this.w = w
this.chartType = this.w.config.chart.type
this.initialAnim = this.w.config.chart.animations.enabled
this.dynamicAnim =
this.initialAnim &&
this.w.config.chart.animations.dynamicAnimation.enabled
this.animDur = 0
this.graphics = new Graphics(this.w)
this.lineColorArr =
w.globals.stroke.colors !== undefined
? w.globals.stroke.colors
: w.globals.colors
this.defaultSize =
w.globals.svgHeight < w.globals.svgWidth
? w.layout.gridHeight
: w.layout.gridWidth
this.isLog = w.config.yaxis[0].logarithmic
this.logBase = w.config.yaxis[0].logBase
this.coreUtils = new CoreUtils(this.w)
this.maxValue = this.isLog
? this.coreUtils.getLogVal(this.logBase, w.globals.maxY, 0)
: w.globals.maxY
this.minValue = this.isLog
? this.coreUtils.getLogVal(this.logBase, this.w.globals.minY, 0)
: w.globals.minY
this.polygons = w.config.plotOptions.radar.polygons
this.strokeWidth = w.config.stroke.show ? w.config.stroke.width : 0
this.size =
this.defaultSize / 2.1 - this.strokeWidth - w.config.chart.dropShadow.blur
if (w.config.xaxis.labels.show) {
this.size = this.size - w.layout.xAxisLabelsWidth / 1.75
}
if (w.config.plotOptions.radar.size !== undefined) {
this.size = w.config.plotOptions.radar.size
}
this.dataRadiusOfPercent = /** @type {any} */ ([])
this.dataRadius = /** @type {any} */ ([])
this.angleArr = /** @type {any} */ ([])
this.dataPointsLen = 0
this.disAngle = 0
/** @type {any} */
/** @type {any[]} */
this.yaxisLabelsTextsPos = []
}
/**
* @param {any[]} series
*/
draw(series) {
const w = this.w
const fill = new Fill(this.w)
/** @type {any[]} */
const allSeries = []
const dataLabels = new DataLabels(this.w, this.ctx)
if (series.length) {
this.dataPointsLen = series[w.globals.maxValsInArrayIndex].length
}
this.disAngle = (Math.PI * 2) / this.dataPointsLen
const halfW = w.layout.gridWidth / 2
const halfH = w.layout.gridHeight / 2
const translateX = halfW + w.config.plotOptions.radar.offsetX
const translateY = halfH + w.config.plotOptions.radar.offsetY
const ret = this.graphics.group({
class: 'apexcharts-radar-series apexcharts-plot-series',
transform: `translate(${translateX || 0}, ${translateY || 0})`,
})
/** @type {any[]} */
let dataPointsPos = []
/** @type {any | null} */
let elPointsMain = null
/** @type {any | null} */
let elDataPointsMain = null
this.yaxisLabels = this.graphics.group({
class: 'apexcharts-yaxis',
})
/**
* @param {number[]} s
* @param {number} i
*/
series.forEach((s, i) => {
const longestSeries = s.length === w.globals.dataPoints
// el to which series will be drawn
const elSeries = this.graphics.group().attr({
class: `apexcharts-series`,
'data:longestSeries': longestSeries,
seriesName: Utils.escapeString(w.seriesData.seriesNames[i]),
rel: i + 1,
'data:realIndex': i,
})
this.dataRadiusOfPercent[i] = []
this.dataRadius[i] = []
this.angleArr[i] = []
/**
* @param {number} dv
* @param {number} j
*/
s.forEach((/** @type {any} */ dv, /** @type {any} */ j) => {
const range = Math.abs(this.maxValue - this.minValue)
dv = dv - this.minValue
if (this.isLog) {
dv = this.coreUtils.getLogVal(this.logBase, dv, 0)
}
this.dataRadiusOfPercent[i][j] = dv / range
this.dataRadius[i][j] = this.dataRadiusOfPercent[i][j] * this.size
this.angleArr[i][j] = j * this.disAngle
})
dataPointsPos = this.getDataPointsPos(
this.dataRadius[i],
this.angleArr[i],
)
const paths = this.createPaths(dataPointsPos, {
x: 0,
y: 0,
})
// points
elPointsMain = this.graphics.group({
class: 'apexcharts-series-markers-wrap apexcharts-element-hidden',
})
// datapoints
elDataPointsMain = this.graphics.group({
class: `apexcharts-datalabels`,
'data:realIndex': i,
})
w.globals.delayedElements.push({
el: elPointsMain.node,
index: i,
})
const defaultRenderedPathOptions = {
i,
realIndex: i,
animationDelay: i,
initialSpeed: w.config.chart.animations.speed,
dataChangeSpeed: w.config.chart.animations.dynamicAnimation.speed,
className: `apexcharts-radar`,
shouldClipToGrid: false,
bindEventsOnPaths: false,
stroke: w.globals.stroke.colors[i],
strokeLineCap: w.config.stroke.lineCap,
}
let pathFrom = null
if (w.globals.previousPaths.length > 0) {
pathFrom = this.getPreviousPath(i)
}
for (let p = 0; p < paths.linePathsTo.length; p++) {
const renderedLinePath = this.graphics.renderPaths({
...defaultRenderedPathOptions,
pathFrom: pathFrom === null ? paths.linePathsFrom[p] : pathFrom,
pathTo: paths.linePathsTo[p],
strokeWidth: Array.isArray(this.strokeWidth)
? this.strokeWidth[i]
: this.strokeWidth,
fill: 'none',
drawShadow: false,
})
elSeries.add(renderedLinePath)
const pathFill = fill.fillPath({
seriesNumber: i,
})
const renderedAreaPath = this.graphics.renderPaths({
...defaultRenderedPathOptions,
pathFrom: pathFrom === null ? paths.areaPathsFrom[p] : pathFrom,
pathTo: paths.areaPathsTo[p],
strokeWidth: 0,
fill: pathFill,
drawShadow: false,
})
if (w.config.chart.dropShadow.enabled) {
const filters = new Filters(this.w)
const shadow = w.config.chart.dropShadow
filters.dropShadow(
renderedAreaPath,
Object.assign({}, shadow, { noUserSpaceOnUse: true }),
i,
)
}
elSeries.add(renderedAreaPath)
}
/**
* @param {any} sj
* @param {number} j
*/
s.forEach((/** @type {any} */ sj, /** @type {any} */ j) => {
const markers = new Markers(this.w, this.ctx)
const opts = markers.getMarkerConfig({
cssClass: 'apexcharts-marker',
seriesIndex: i,
dataPointIndex: j,
})
const point = this.graphics.drawMarker(
dataPointsPos[j].x,
dataPointsPos[j].y,
opts,
)
point.attr('rel', j)
point.attr('j', j)
point.attr('index', i)
point.node.setAttribute('default-marker-size', opts.pSize)
const elPointsWrap = this.graphics.group({
class: 'apexcharts-series-markers',
})
if (elPointsWrap) {
elPointsWrap.add(point)
}
elPointsMain.add(elPointsWrap)
elSeries.add(elPointsMain)
const dataLabelsConfig = w.config.dataLabels
if (dataLabelsConfig.enabled) {
const text = dataLabelsConfig.formatter(w.seriesData.series[i][j], {
seriesIndex: i,
dataPointIndex: j,
w,
})
dataLabels.plotDataLabelsText({
x: dataPointsPos[j].x,
y: dataPointsPos[j].y,
text,
textAnchor: 'middle',
i,
j: i,
parent: elDataPointsMain,
offsetCorrection: false,
dataLabelsConfig: {
...dataLabelsConfig,
},
})
}
elSeries.add(elDataPointsMain)
})
allSeries.push(elSeries)
})
this.drawPolygons({
parent: ret,
})
if (w.config.xaxis.labels.show) {
const xaxisTexts = this.drawXAxisTexts()
ret.add(xaxisTexts)
}
allSeries.forEach((elS) => {
ret.add(elS)
})
ret.add(this.yaxisLabels)
return ret
}
/**
* @param {Record<string, any>} opts
*/
drawPolygons(opts) {
const w = this.w
const { parent } = opts
const helpers = new Helpers(this.w)
const yaxisTexts = w.globals.yAxisScale[0].result.reverse()
const layers = yaxisTexts.length
const radiusSizes = []
const layerDis = this.size / (layers - 1)
for (let i = 0; i < layers; i++) {
radiusSizes[i] = layerDis * i
}
radiusSizes.reverse()
/** @type {any[]} */
const polygonStrings = []
/** @type {any[]} */
const lines = []
radiusSizes.forEach((radiusSize, r) => {
const polygon = Utils.getPolygonPos(radiusSize, this.dataPointsLen)
let string = ''
polygon.forEach((p, i) => {
if (r === 0) {
const line = this.graphics.drawLine(
p.x,
p.y,
0,
0,
Array.isArray(this.polygons.connectorColors)
? this.polygons.connectorColors[i]
: this.polygons.connectorColors,
)
lines.push(line)
}
if (i === 0) {
this.yaxisLabelsTextsPos.push({
x: p.x,
y: p.y,
})
}
string += p.x + ',' + p.y + ' '
})
polygonStrings.push(string)
})
polygonStrings.forEach((p, i) => {
const strokeColors = this.polygons.strokeColors
const strokeWidth = this.polygons.strokeWidth
const polygon = this.graphics.drawPolygon(
p,
Array.isArray(strokeColors) ? strokeColors[i] : strokeColors,
Array.isArray(strokeWidth) ? strokeWidth[i] : strokeWidth,
w.globals.radarPolygons.fill.colors[i],
)
parent.add(polygon)
})
lines.forEach((l) => {
parent.add(l)
})
if (w.config.yaxis[0].show) {
this.yaxisLabelsTextsPos.forEach(
(/** @type {any} */ p, /** @type {any} */ i) => {
const yText = helpers.drawYAxisTexts(p.x, p.y, i, yaxisTexts[i])
this.yaxisLabels.add(yText)
},
)
}
}
drawXAxisTexts() {
const w = this.w
const xaxisLabelsConfig = w.config.xaxis.labels
const elXAxisWrap = this.graphics.group({
class: 'apexcharts-xaxis',
})
const polygonPos = Utils.getPolygonPos(this.size, this.dataPointsLen)
/**
* @param {string} label
* @param {number} i
*/
w.labelData.labels.forEach((label, i) => {
const formatter = w.config.xaxis.labels.formatter
const dataLabels = new DataLabels(this.w, this.ctx)
if (polygonPos[i]) {
const textPos = this.getTextPos(polygonPos[i], this.size)
const text = formatter(label, {
seriesIndex: -1,
dataPointIndex: i,
w,
})
const dataLabelText = dataLabels.plotDataLabelsText({
x: textPos.newX,
y: textPos.newY,
text,
textAnchor: textPos.textAnchor,
i,
j: i,
parent: elXAxisWrap,
className: 'apexcharts-xaxis-label',
color:
Array.isArray(xaxisLabelsConfig.style.colors) &&
xaxisLabelsConfig.style.colors[i]
? xaxisLabelsConfig.style.colors[i]
: '#a8a8a8',
dataLabelsConfig: {
textAnchor: textPos.textAnchor,
dropShadow: { enabled: false },
...xaxisLabelsConfig,
},
offsetCorrection: false,
})
/**
* @param {Event} e
*/
dataLabelText.on('click', (/** @type {any} */ e) => {
if (typeof w.config.chart.events.xAxisLabelClick === 'function') {
const opts = Object.assign({}, w, {
labelIndex: i,
})
w.config.chart.events.xAxisLabelClick(e, this.ctx, opts)
}
})
}
})
return elXAxisWrap
}
/**
* @param {Array<Record<string, any>>} pos
* @param {Record<string, any>} origin
*/
createPaths(pos, origin) {
const linePathsTo = []
/** @type {any[]} */
let linePathsFrom = []
const areaPathsTo = []
/** @type {any[]} */
let areaPathsFrom = []
if (pos.length) {
linePathsFrom = [this.graphics.move(origin.x, origin.y)]
areaPathsFrom = [this.graphics.move(origin.x, origin.y)]
let linePathTo = this.graphics.move(pos[0].x, pos[0].y)
let areaPathTo = this.graphics.move(pos[0].x, pos[0].y)
/**
* @param {number} p
* @param {number} i
*/
pos.forEach((/** @type {any} */ p, /** @type {any} */ i) => {
linePathTo += this.graphics.line(p.x, p.y)
areaPathTo += this.graphics.line(p.x, p.y)
if (i === pos.length - 1) {
linePathTo += 'Z'
areaPathTo += 'Z'
}
})
linePathsTo.push(linePathTo)
areaPathsTo.push(areaPathTo)
}
return {
linePathsFrom,
linePathsTo,
areaPathsFrom,
areaPathsTo,
}
}
/**
* @param {Record<string, any>} pos
* @param {number} polygonSize
*/
getTextPos(pos, polygonSize) {
const limit = 10
let textAnchor = 'middle'
let newX = pos.x
let newY = pos.y
if (Math.abs(pos.x) >= limit) {
if (pos.x > 0) {
textAnchor = 'start'
newX += 10
} else if (pos.x < 0) {
textAnchor = 'end'
newX -= 10
}
} else {
textAnchor = 'middle'
}
if (Math.abs(pos.y) >= polygonSize - limit) {
if (pos.y < 0) {
newY -= 10
} else if (pos.y > 0) {
newY += 10
}
}
return {
textAnchor,
newX,
newY,
}
}
/**
* @param {number} realIndex
*/
getPreviousPath(realIndex) {
const w = this.w
let pathFrom = null
for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
const gpp = w.globals.previousPaths[pp]
if (
gpp.paths.length > 0 &&
parseInt(gpp.realIndex, 10) === parseInt(String(realIndex), 10)
) {
if (typeof w.globals.previousPaths[pp].paths[0] !== 'undefined') {
pathFrom = w.globals.previousPaths[pp].paths[0].d
}
}
}
return pathFrom
}
/**
* @param {any[]} dataRadiusArr
* @param {any[]} angleArr
*/
getDataPointsPos(
dataRadiusArr,
angleArr,
dataPointsLen = this.dataPointsLen,
) {
dataRadiusArr = dataRadiusArr || []
angleArr = angleArr || []
const dataPointsPosArray = []
for (let j = 0; j < dataPointsLen; j++) {
const curPointPos = {}
curPointPos.x = dataRadiusArr[j] * Math.sin(angleArr[j])
curPointPos.y = -dataRadiusArr[j] * Math.cos(angleArr[j])
dataPointsPosArray.push(curPointPos)
}
return dataPointsPosArray
}
}
export default Radar

View file

@ -0,0 +1,590 @@
// @ts-check
import Pie from './Pie'
import Utils from '../utils/Utils'
import Fill from '../modules/Fill'
import Graphics from '../modules/Graphics'
import Filters from '../modules/Filters'
import Series from '../modules/Series'
/**
* ApexCharts Radial Class for drawing Circle / Semi Circle Charts.
* @module Radial
**/
class Radial extends Pie {
/**
* @param {import('../types/internal').ChartStateW} w
* @param {import('../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
super(w, ctx)
this.ctx = ctx
this.w = w
this.animBeginArr = [0]
this.animDur = 0
this.startAngle = w.config.plotOptions.radialBar.startAngle
this.endAngle = w.config.plotOptions.radialBar.endAngle
this.totalAngle = Math.abs(
w.config.plotOptions.radialBar.endAngle -
w.config.plotOptions.radialBar.startAngle,
)
this.trackStartAngle = w.config.plotOptions.radialBar.track.startAngle
this.trackEndAngle = w.config.plotOptions.radialBar.track.endAngle
this.barLabels = this.w.config.plotOptions.radialBar.barLabels
this.donutDataLabels = this.w.config.plotOptions.radialBar.dataLabels
this.radialDataLabels = this.donutDataLabels // make a copy for easy reference
if (!this.trackStartAngle) this.trackStartAngle = this.startAngle
if (!this.trackEndAngle) this.trackEndAngle = this.endAngle
if (this.endAngle === 360) this.endAngle = 359.99
this.margin = parseInt(w.config.plotOptions.radialBar.track.margin, 10)
this.onBarLabelClick = this.onBarLabelClick.bind(this)
}
/**
* @param {any[]} series
*/
draw(series) {
const w = this.w
const graphics = new Graphics(this.w)
const ret = graphics.group({
class: 'apexcharts-radialbar',
})
if (w.globals.noData) return ret
const elSeries = graphics.group()
const centerY = this.defaultSize / 2
const centerX = w.layout.gridWidth / 2
let size = this.defaultSize / 2.05
if (!w.config.chart.sparkline.enabled) {
size = size - w.config.stroke.width - w.config.chart.dropShadow.blur
}
const colorArr = w.globals.fill.colors
if (w.config.plotOptions.radialBar.track.show) {
const elTracks = this.drawTracks({
size,
centerX,
centerY,
colorArr,
series,
})
elSeries.add(elTracks)
}
const elG = this.drawArcs({
size,
centerX,
centerY,
colorArr,
series,
})
let totalAngle = 360
if (w.config.plotOptions.radialBar.startAngle < 0) {
totalAngle = this.totalAngle
}
const angleRatio = (360 - totalAngle) / 360
w.globals.radialSize = size - size * angleRatio
if (this.radialDataLabels.value.show) {
const offset = Math.max(
this.radialDataLabels.value.offsetY,
this.radialDataLabels.name.offsetY,
)
w.globals.radialSize += offset * angleRatio
}
elSeries.add(elG.g)
if (w.config.plotOptions.radialBar.hollow.position === 'front') {
elG.g.add(elG.elHollow)
if (elG.dataLabels) {
elG.g.add(elG.dataLabels)
}
}
ret.add(elSeries)
return ret
}
/**
* @param {Record<string, any>} opts
*/
drawTracks(opts) {
const w = this.w
const graphics = new Graphics(this.w)
const g = graphics.group({
class: 'apexcharts-tracks',
})
const filters = new Filters(this.w)
const fill = new Fill(this.w)
const strokeWidth = this.getStrokeWidth(opts)
opts.size = opts.size - strokeWidth / 2
for (let i = 0; i < opts.series.length; i++) {
const elRadialBarTrack = graphics.group({
class: 'apexcharts-radialbar-track apexcharts-track',
})
g.add(elRadialBarTrack)
elRadialBarTrack.attr({
rel: i + 1,
})
opts.size = opts.size - strokeWidth - this.margin
const trackConfig = w.config.plotOptions.radialBar.track
const pathFill = fill.fillPath({
seriesNumber: 0,
size: opts.size,
fillColors: Array.isArray(trackConfig.background)
? trackConfig.background[i]
: trackConfig.background,
solid: true,
})
const startAngle = this.trackStartAngle
let endAngle = this.trackEndAngle
if (Math.abs(endAngle) + Math.abs(startAngle) >= 360)
endAngle = 360 - Math.abs(this.startAngle) - 0.1
const elPath = graphics.drawPath({
d: '',
stroke: pathFill,
strokeWidth:
(strokeWidth * parseInt(trackConfig.strokeWidth, 10)) / 100,
fill: 'none',
strokeOpacity: trackConfig.opacity,
classes: 'apexcharts-radialbar-area',
})
if (trackConfig.dropShadow.enabled) {
const shadow = trackConfig.dropShadow
filters.dropShadow(elPath, shadow)
}
elRadialBarTrack.add(elPath)
elPath.attr('id', 'apexcharts-radialbarTrack-' + i)
this.animatePaths(elPath, {
centerX: opts.centerX,
centerY: opts.centerY,
endAngle,
startAngle,
size: opts.size,
i,
totalItems: 2,
animBeginArr: 0,
dur: 0,
isTrack: true,
})
}
return g
}
/**
* @param {Record<string, any>} opts
*/
drawArcs(opts) {
const w = this.w
// size, donutSize, centerX, centerY, colorArr, lineColorArr, sectorAngleArr, series
const graphics = new Graphics(this.w)
const fill = new Fill(this.w)
const filters = new Filters(this.w)
const g = graphics.group()
const strokeWidth = this.getStrokeWidth(opts)
opts.size = opts.size - strokeWidth / 2
let hollowFillID = w.config.plotOptions.radialBar.hollow.background
const hollowSize =
opts.size -
strokeWidth * opts.series.length -
this.margin * opts.series.length -
(strokeWidth *
parseInt(w.config.plotOptions.radialBar.track.strokeWidth, 10)) /
100 /
2
const hollowRadius =
hollowSize - w.config.plotOptions.radialBar.hollow.margin
if (w.config.plotOptions.radialBar.hollow.image !== undefined) {
hollowFillID = this.drawHollowImage(opts, g, hollowSize, hollowFillID)
}
const elHollow = this.drawHollow({
size: hollowRadius,
centerX: opts.centerX,
centerY: opts.centerY,
fill: hollowFillID ? hollowFillID : 'transparent',
})
if (w.config.plotOptions.radialBar.hollow.dropShadow.enabled) {
const shadow = w.config.plotOptions.radialBar.hollow.dropShadow
filters.dropShadow(elHollow, shadow)
}
let shown = 1
if (!this.radialDataLabels.total.show && w.seriesData.series.length > 1) {
shown = 0
}
let dataLabels = null
if (this.radialDataLabels.show) {
const dataLabelsGroup = w.dom.Paper.findOne(
`.apexcharts-datalabels-group`,
)
dataLabels = this.renderInnerDataLabels(
dataLabelsGroup,
this.radialDataLabels,
{
hollowSize,
centerX: opts.centerX,
centerY: opts.centerY,
opacity: shown,
},
)
}
if (w.config.plotOptions.radialBar.hollow.position === 'back') {
g.add(elHollow)
if (dataLabels) {
g.add(dataLabels)
}
}
let reverseLoop = false
if (w.config.plotOptions.radialBar.inverseOrder) {
reverseLoop = true
}
for (
let i = reverseLoop ? opts.series.length - 1 : 0;
reverseLoop ? i >= 0 : i < opts.series.length;
reverseLoop ? i-- : i++
) {
const elRadialBarArc = graphics.group({
class: `apexcharts-series apexcharts-radial-series`,
seriesName: Utils.escapeString(w.seriesData.seriesNames[i]),
})
g.add(elRadialBarArc)
elRadialBarArc.attr({
rel: i + 1,
'data:realIndex': i,
})
Series.addCollapsedClassToSeries(this.w, elRadialBarArc, i)
opts.size = opts.size - strokeWidth - this.margin
const pathFill = fill.fillPath({
seriesNumber: i,
size: opts.size,
value: opts.series[i],
})
const startAngle = this.startAngle
let prevStartAngle
// if data exceeds 100, make it 100
const dataValue =
Utils.negToZero(opts.series[i] > 100 ? 100 : opts.series[i]) / 100
let endAngle = Math.round(this.totalAngle * dataValue) + this.startAngle
let prevEndAngle
if (w.globals.dataChanged) {
prevStartAngle = this.startAngle
prevEndAngle =
Math.round(
(this.totalAngle * Utils.negToZero(w.globals.previousPaths[i])) /
100,
) + prevStartAngle
}
const currFullAngle = Math.abs(endAngle) + Math.abs(startAngle)
if (currFullAngle > 360) {
endAngle = endAngle - 0.01
}
const prevFullAngle = Math.abs(prevEndAngle) + Math.abs(prevStartAngle)
if (prevFullAngle > 360) {
prevEndAngle = prevEndAngle - 0.01
}
const angle = endAngle - startAngle
const dashArray = Array.isArray(w.config.stroke.dashArray)
? w.config.stroke.dashArray[i]
: w.config.stroke.dashArray
const elPath = graphics.drawPath({
d: '',
stroke: pathFill,
strokeWidth,
fill: 'none',
fillOpacity: w.config.fill.opacity,
classes: 'apexcharts-radialbar-area apexcharts-radialbar-slice-' + i,
strokeDashArray: dashArray,
})
const radialMidAngle = startAngle + angle / 2
const radialArcCenter = Utils.polarToCartesian(
opts.centerX,
opts.centerY,
opts.size,
radialMidAngle,
)
Graphics.setAttrs(elPath.node, {
'data:angle': angle,
'data:value': opts.series[i],
'data:cx': radialArcCenter.x,
'data:cy': radialArcCenter.y,
})
if (w.config.chart.dropShadow.enabled) {
const shadow = w.config.chart.dropShadow
filters.dropShadow(elPath, shadow, i)
}
filters.setSelectionFilter(elPath, 0, i)
this.addListeners(elPath, this.radialDataLabels)
elRadialBarArc.add(elPath)
elPath.attr({
index: 0,
j: i,
})
if (this.barLabels.enabled) {
const barStartCords = Utils.polarToCartesian(
opts.centerX,
opts.centerY,
opts.size,
startAngle,
)
const text = this.barLabels.formatter(w.seriesData.seriesNames[i], {
seriesIndex: i,
w,
})
const classes = ['apexcharts-radialbar-label']
if (!this.barLabels.onClick) {
classes.push('apexcharts-no-click')
}
let textColor = this.barLabels.useSeriesColors
? w.globals.colors[i]
: w.config.chart.foreColor
if (!textColor) {
textColor = w.config.chart.foreColor
}
const x = barStartCords.x + this.barLabels.offsetX
const y = barStartCords.y + this.barLabels.offsetY
const elText = graphics.drawText({
x,
y,
text,
textAnchor: 'end',
dominantBaseline: 'middle',
fontFamily: this.barLabels.fontFamily,
fontWeight: this.barLabels.fontWeight,
fontSize: this.barLabels.fontSize,
foreColor: textColor,
cssClass: classes.join(' '),
})
elText.on('click', this.onBarLabelClick)
elText.attr({
rel: i + 1,
})
if (startAngle !== 0) {
elText.attr({
'transform-origin': `${x} ${y}`,
transform: `rotate(${startAngle} 0 0)`,
})
}
elRadialBarArc.add(elText)
}
let dur = 0
if (this.initialAnim && !w.globals.resized && !w.globals.dataChanged) {
dur = w.config.chart.animations.speed
}
if (w.globals.dataChanged) {
dur = w.config.chart.animations.dynamicAnimation.speed
}
this.animDur = dur / (opts.series.length * 1.2) + this.animDur
this.animBeginArr.push(this.animDur)
this.animatePaths(elPath, {
centerX: opts.centerX,
centerY: opts.centerY,
endAngle,
startAngle,
prevEndAngle,
prevStartAngle,
size: opts.size,
i,
totalItems: 2,
animBeginArr: this.animBeginArr,
dur,
shouldSetPrevPaths: true,
})
}
return {
g,
elHollow,
dataLabels,
}
}
/**
* @param {Record<string, any>} opts
*/
drawHollow(opts) {
const graphics = new Graphics(this.w)
const circle = graphics.drawCircle(opts.size * 2)
circle.attr({
class: 'apexcharts-radialbar-hollow',
cx: opts.centerX,
cy: opts.centerY,
r: opts.size,
fill: opts.fill,
})
return circle
}
/**
* @param {Record<string, any>} opts
* @param {any} g
* @param {number} hollowSize
* @param {string} hollowFillID
*/
drawHollowImage(opts, g, hollowSize, hollowFillID) {
const w = this.w
const fill = new Fill(this.w)
const randID = Utils.randomId()
const hollowFillImg = w.config.plotOptions.radialBar.hollow.image
if (w.config.plotOptions.radialBar.hollow.imageClipped) {
fill.clippedImgArea({
width: hollowSize,
height: hollowSize,
image: hollowFillImg,
patternID: `pattern${w.globals.cuid}${randID}`,
})
hollowFillID = `url(#pattern${w.globals.cuid}${randID})`
} else {
const imgWidth = w.config.plotOptions.radialBar.hollow.imageWidth
const imgHeight = w.config.plotOptions.radialBar.hollow.imageHeight
if (imgWidth === undefined && imgHeight === undefined) {
/**
* @param {Record<string, any>} loader
*/
const image = w.dom.Paper.image(
hollowFillImg,
/** @this {any} */
function (/** @type {Record<string, any>} */ loader) {
this.move(
opts.centerX -
loader.width / 2 +
w.config.plotOptions.radialBar.hollow.imageOffsetX,
opts.centerY -
loader.height / 2 +
w.config.plotOptions.radialBar.hollow.imageOffsetY,
)
},
)
g.add(image)
} else {
const image = w.dom.Paper.image(
hollowFillImg,
/** @this {any} */ function () {
this.move(
opts.centerX -
imgWidth / 2 +
w.config.plotOptions.radialBar.hollow.imageOffsetX,
opts.centerY -
imgHeight / 2 +
w.config.plotOptions.radialBar.hollow.imageOffsetY,
)
this.size(imgWidth, imgHeight)
},
)
g.add(image)
}
}
return hollowFillID
}
/**
* @param {Record<string, any>} opts
*/
getStrokeWidth(opts) {
const w = this.w
return (
(opts.size *
(100 - parseInt(w.config.plotOptions.radialBar.hollow.size, 10))) /
100 /
(opts.series.length + 1) -
this.margin
)
}
/**
* @param {Event} e
*/
onBarLabelClick(e) {
const target = /** @type {Element} */ (e.target)
const seriesIndex = parseInt(target.getAttribute('rel') ?? '', 10) - 1
const legendClick = this.barLabels.onClick
const w = this.w
if (legendClick) {
legendClick(w.seriesData.seriesNames[seriesIndex], { w, seriesIndex })
}
}
}
export default Radial

View file

@ -0,0 +1,499 @@
// @ts-check
import Bar from './Bar'
import Graphics from '../modules/Graphics'
import Series from '../modules/Series'
import Utils from '../utils/Utils'
/**
* ApexCharts RangeBar Class responsible for drawing Range/Timeline Bars.
*
* @module RangeBar
**/
class RangeBar extends Bar {
/**
* @param {any[]} series
* @param {number} seriesIndex
*/
draw(series, seriesIndex) {
const w = this.w
const graphics = new Graphics(this.w)
this.rangeBarOptions = this.w.config.plotOptions.rangeBar
this.series = series
this.seriesRangeStart = w.rangeData.seriesRangeStart
this.seriesRangeEnd = w.rangeData.seriesRangeEnd
this.barHelpers.initVariables(series)
const ret = graphics.group({
class: 'apexcharts-rangebar-series apexcharts-plot-series',
})
for (let i = 0; i < series.length; i++) {
let x, y
const realIndex = w.globals.comboCharts
? /** @type {any} */ (seriesIndex)[i]
: i
const { columnGroupIndex } = this.barHelpers.getGroupIndex(realIndex)
// el to which series will be drawn
const elSeries = graphics.group({
class: `apexcharts-series`,
seriesName: Utils.escapeString(w.seriesData.seriesNames[realIndex]),
rel: i + 1,
'data:realIndex': realIndex,
})
Series.addCollapsedClassToSeries(this.w, elSeries, realIndex)
if (series[i].length > 0) {
this.visibleI = this.visibleI + 1
}
let translationsIndex = 0
if (this.yRatio.length > 1) {
this.yaxisIndex = /** @type {any} */ (
w.globals.seriesYAxisReverseMap[realIndex]
)[0]
translationsIndex = realIndex
}
const initPositions = this.barHelpers.initialPositions(realIndex)
const {
y: initY,
zeroW, // zeroW is the baseline where 0 meets x axis
x: initX,
zeroH, // zeroH is the baseline where 0 meets y axis
} = initPositions
let barWidth = initPositions.barWidth ?? 0
let barHeight = initPositions.barHeight ?? 0
const yDivision = initPositions.yDivision ?? 0
const xDivision = initPositions.xDivision ?? 0
y = initY
x = initX
// eldatalabels
const elDataLabelsWrap = graphics.group({
class: 'apexcharts-datalabels',
'data:realIndex': realIndex,
})
const elGoalsMarkers = graphics.group({
class: 'apexcharts-rangebar-goals-markers',
})
for (let j = 0; j < w.globals.dataPoints; j++) {
const strokeWidth = this.barHelpers.getStrokeWidth(i, j, realIndex)
const y1 = this.seriesRangeStart[i][j]
const y2 = this.seriesRangeEnd[i][j]
let paths = /** @type {any} */ (null)
let barXPosition = null
let barYPosition = null
const params = { x, y, strokeWidth, elSeries }
let seriesLen = this.seriesLen
if (w.config.plotOptions.bar.rangeBarGroupRows) {
seriesLen = 1
}
if (
typeof /** @type {Record<string,any>} */ (w.config.series[i]).data?.[j] ===
'undefined'
) {
// no data exists for further indexes, hence we need to get out the innr loop.
// As we are iterating over total datapoints, there is a possiblity the series might not have data for j index
break
}
if (this.isHorizontal) {
barYPosition = y + barHeight * /** @type {any} */ (this).visibleI
const srty = (yDivision - barHeight * seriesLen) / 2
if (/** @type {Record<string,any>} */ (w.config.series[i]).data?.[j]?.x) {
const positions = this.detectOverlappingBars({
i,
j,
barYPosition,
srty,
barHeight,
yDivision,
initPositions,
})
barHeight = positions.barHeight
barYPosition = positions.barYPosition
}
paths = this.drawRangeBarPaths({
indexes: { i, j, realIndex },
barHeight,
barYPosition,
zeroW,
yDivision,
y1,
y2,
...params,
})
barWidth = paths.barWidth
} else {
if (w.axisFlags.isXNumeric) {
x =
(w.seriesData.seriesX[i][j] - w.globals.minX) / this.xRatio -
barWidth / 2
}
barXPosition = x + barWidth * /** @type {any} */ (this).visibleI
const srtx = (xDivision - barWidth * seriesLen) / 2
if (/** @type {Record<string,any>} */ (w.config.series[i]).data?.[j]?.x) {
const positions = this.detectOverlappingBars({
i,
j,
barXPosition,
srtx,
barWidth,
xDivision,
initPositions,
})
barWidth = positions.barWidth
barXPosition = positions.barXPosition
}
paths = this.drawRangeColumnPaths({
indexes: { i, j, realIndex, translationsIndex },
barWidth,
barXPosition,
zeroH,
xDivision,
...params,
})
barHeight = paths.barHeight
}
const barGoalLine = this.barHelpers.drawGoalLine({
barXPosition: paths.barXPosition,
barYPosition,
goalX: paths.goalX,
goalY: paths.goalY,
barHeight,
barWidth,
})
if (barGoalLine) {
elGoalsMarkers.add(barGoalLine)
}
y = paths.y
x = paths.x
const pathFill = this.barHelpers.getPathFillColor(
series,
i,
j,
realIndex,
)
this.renderSeries({
realIndex,
pathFill: pathFill.color,
lineFill: pathFill.useRangeColor
? pathFill.color
: w.globals.stroke.colors[realIndex],
j,
i,
x,
y,
y1,
y2,
pathFrom: paths.pathFrom,
pathTo: paths.pathTo,
strokeWidth,
elSeries,
series,
barHeight,
barWidth,
barXPosition,
barYPosition,
columnGroupIndex,
elDataLabelsWrap,
elGoalsMarkers,
visibleSeries: this.visibleI,
type: 'rangebar',
})
}
ret.add(elSeries)
}
return ret
}
/** @param {{ i?: any, j?: any, barYPosition?: any, barXPosition?: any, srty?: any, srtx?: any, barHeight?: any, barWidth?: any, yDivision?: any, xDivision?: any, initPositions?: any }} opts */
detectOverlappingBars({
i,
j,
barYPosition,
barXPosition,
srty,
srtx,
barHeight,
barWidth,
yDivision,
xDivision,
initPositions,
}) {
const w = this.w
let overlaps = []
const rangeName = /** @type {Record<string,any>} */ (w.config.series[i]).data?.[j]
?.rangeName
const x = /** @type {Record<string,any>} */ (w.config.series[i]).data?.[j]?.x
const labelX = Array.isArray(x) ? x.join(' ') : x
const rowIndex = w.labelData.labels
/**
* @param {any} _
*/
.map((_) => (Array.isArray(_) ? _.join(' ') : _))
.indexOf(labelX)
/**
* @param {any} tx
*/
const overlappedIndex = w.rangeData.seriesRange[i].findIndex(
(/** @type {any} */ tx) => tx.x === labelX && tx.overlaps?.size > 0,
)
if (this.isHorizontal) {
if (w.config.plotOptions.bar.rangeBarGroupRows) {
barYPosition = srty + yDivision * rowIndex
} else {
barYPosition = srty + barHeight * this.visibleI + yDivision * rowIndex
}
if (overlappedIndex > -1 && !w.config.plotOptions.bar.rangeBarOverlap) {
overlaps = Array.from(
/** @type {any} */ (w.rangeData.seriesRange[i][overlappedIndex])
.overlaps,
)
if (overlaps.indexOf(rangeName) > -1) {
barHeight = initPositions.barHeight / overlaps.length
barYPosition =
barHeight * this.visibleI +
(yDivision * (100 - parseInt(this.barOptions.barHeight, 10))) /
100 /
2 +
barHeight * (this.visibleI + overlaps.indexOf(rangeName)) +
yDivision * rowIndex
}
}
} else {
if (rowIndex > -1 && !w.labelData.timescaleLabels.length) {
if (w.config.plotOptions.bar.rangeBarGroupRows) {
barXPosition = srtx + xDivision * rowIndex
} else {
barXPosition = srtx + barWidth * this.visibleI + xDivision * rowIndex
}
}
if (overlappedIndex > -1 && !w.config.plotOptions.bar.rangeBarOverlap) {
overlaps = Array.from(
/** @type {any} */ (w.rangeData.seriesRange[i][overlappedIndex])
.overlaps,
)
if (overlaps.indexOf(rangeName) > -1) {
barWidth = initPositions.barWidth / overlaps.length
barXPosition =
barWidth * this.visibleI +
(xDivision * (100 - parseInt(this.barOptions.barWidth, 10))) /
100 /
2 +
barWidth * (this.visibleI + overlaps.indexOf(rangeName)) +
xDivision * rowIndex
}
}
}
return {
barYPosition,
barXPosition,
barHeight,
barWidth,
}
}
/** @param {{indexes: any, x: any, xDivision: any, barWidth: any, barXPosition: any, zeroH: any}} opts */
drawRangeColumnPaths({
indexes,
x,
xDivision,
barWidth,
barXPosition,
zeroH,
}) {
const w = this.w
const { i, j, realIndex, translationsIndex } = indexes
const yRatio = this.yRatio[translationsIndex]
const range = this.getRangeValue(realIndex, j)
let y1 = Math.min(range.start, range.end)
let y2 = Math.max(range.start, range.end)
if (
typeof /** @type {any} */ (this.series)[i]?.[j] === 'undefined' ||
/** @type {any} */ (this.series)[i]?.[j] === null
) {
y1 = zeroH
} else {
y1 = zeroH - y1 / yRatio
y2 = zeroH - y2 / yRatio
}
const barHeight = Math.abs(y2 - y1)
const paths = this.barHelpers.getColumnPaths({
barXPosition,
barWidth,
y1,
y2,
strokeWidth: this.strokeWidth,
series: this.seriesRangeEnd,
realIndex: realIndex,
i: realIndex,
j,
w,
})
if (!w.axisFlags.isXNumeric) {
x = x + xDivision
} else {
const xForNumericXAxis = this.getBarXForNumericXAxis({
x,
j,
realIndex,
barWidth,
})
x = xForNumericXAxis.x
barXPosition = xForNumericXAxis.barXPosition
}
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
barHeight,
x,
y: range.start < 0 && range.end < 0 ? y1 : y2,
goalY: this.barHelpers.getGoalValues(
'y',
/** @type {any} */ (null),
zeroH,
i,
j,
translationsIndex,
),
barXPosition,
}
}
/**
* @param {number} val
*/
preventBarOverflow(val) {
const w = this.w
if (val < 0) {
val = 0
}
if (val > w.layout.gridWidth) {
val = w.layout.gridWidth
}
return val
}
/** @param {{indexes: any, y: any, y1: any, y2: any, yDivision: any, barHeight: any, barYPosition: any, zeroW: any}} opts */
drawRangeBarPaths({
indexes,
y,
y1,
y2,
yDivision,
barHeight,
barYPosition,
zeroW,
}) {
const w = this.w
const { realIndex, j } = indexes
const x1 = this.preventBarOverflow(zeroW + y1 / this.invertedYRatio)
const x2 = this.preventBarOverflow(zeroW + y2 / this.invertedYRatio)
const range = this.getRangeValue(realIndex, j)
const barWidth = Math.abs(x2 - x1)
const paths = this.barHelpers.getBarpaths({
barYPosition,
barHeight,
x1,
x2,
strokeWidth: this.strokeWidth,
series: this.seriesRangeEnd,
i: realIndex,
realIndex,
j,
w,
})
if (!w.axisFlags.isXNumeric) {
y = y + yDivision
}
return {
pathTo: paths.pathTo,
pathFrom: paths.pathFrom,
barWidth,
x: range.start < 0 && range.end < 0 ? x1 : x2,
goalX: this.barHelpers.getGoalValues(
'x',
zeroW,
/** @type {any} */ (null),
realIndex,
j,
0,
),
y,
}
}
/**
* @param {number} i
* @param {number} j
*/
getRangeValue(i, j) {
const w = this.w
return {
start: w.rangeData.seriesRangeStart[i][j],
end: w.rangeData.seriesRangeEnd[i][j],
}
}
}
export default RangeBar

View file

@ -0,0 +1,212 @@
// @ts-check
import Animations from '../modules/Animations'
import Fill from '../modules/Fill'
import Filters from '../modules/Filters'
import Graphics from '../modules/Graphics'
import Markers from '../modules/Markers'
/**
* ApexCharts Scatter Class.
* This Class also handles bubbles chart as currently there is no major difference in drawing them,
* @module Scatter
**/
export default class Scatter {
/**
* @param {import('../types/internal').ChartStateW} w
* @param {import('../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.ctx = ctx
this.w = w
this.initialAnim = this.w.config.chart.animations.enabled
this.anim = new Animations(this.w)
this.filters = new Filters(this.w)
this.fill = new Fill(this.w)
this.markers = new Markers(this.w, this.ctx)
this.graphics = new Graphics(this.w)
}
/**
* @param {Element} elSeries
* @param {number} j
* @param {Record<string, any>} opts
*/
draw(elSeries, j, opts) {
const w = this.w
const graphics = this.graphics
const realIndex = opts.realIndex
const pointsPos = opts.pointsPos
const zRatio = opts.zRatio
const elPointsMain = opts.elParent
const elPointsWrap = graphics.group({
class: `apexcharts-series-markers apexcharts-series-${w.config.chart.type}`,
})
elPointsWrap.attr('clip-path', `url(#gridRectMarkerMask${w.globals.cuid})`)
// Set up event delegation once on the group instead of per-point listeners
this.markers.setupMarkerDelegation(elPointsWrap)
if (Array.isArray(pointsPos.x)) {
for (let q = 0; q < pointsPos.x.length; q++) {
let dataPointIndex = j + 1
let shouldDraw = true
// a small hack as we have 2 points for the first val to connect it
if (j === 0 && q === 0) dataPointIndex = 0
if (j === 0 && q === 1) dataPointIndex = 1
let radius = w.globals.markers.size[realIndex]
if (zRatio !== Infinity) {
// means we have a bubble
const bubble = w.config.plotOptions.bubble
radius = w.seriesData.seriesZ[realIndex][dataPointIndex]
if (bubble.zScaling) {
radius /= zRatio
}
if (bubble.minBubbleRadius && radius < bubble.minBubbleRadius) {
radius = bubble.minBubbleRadius
}
if (bubble.maxBubbleRadius && radius > bubble.maxBubbleRadius) {
radius = bubble.maxBubbleRadius
}
}
const x = pointsPos.x[q]
const y = pointsPos.y[q]
radius = radius || 0
if (
y === null ||
typeof w.seriesData.series[realIndex][dataPointIndex] === 'undefined'
) {
shouldDraw = false
}
if (shouldDraw) {
const point = this.drawPoint(
x,
y,
radius,
realIndex,
dataPointIndex,
j,
)
elPointsWrap.add(point)
}
elPointsMain.add(elPointsWrap)
}
}
}
/**
* @param {number} x
* @param {number} y
* @param {number} radius
* @param {number} realIndex
* @param {number} dataPointIndex
* @param {number} j
*/
drawPoint(x, y, radius, realIndex, dataPointIndex, j) {
const w = this.w
const i = realIndex
const anim = this.anim
const filters = this.filters
const fill = this.fill
const markers = this.markers
const graphics = this.graphics
const markerConfig = markers.getMarkerConfig({
cssClass: 'apexcharts-marker',
seriesIndex: i,
dataPointIndex,
radius:
w.config.chart.type === 'bubble' ||
(w.globals.comboCharts &&
w.config.series[realIndex] &&
/** @type {Record<string,any>} */ (w.config.series[realIndex]).type === 'bubble')
? radius
: null,
})
let pathFillCircle = fill.fillPath({
seriesNumber: realIndex,
dataPointIndex,
color: markerConfig.pointFillColor,
patternUnits: 'objectBoundingBox',
value: w.seriesData.series[realIndex][j],
})
const el = graphics.drawMarker(x, y, markerConfig)
const _si = /** @type {Record<string,any>} */ (w.config.series[i])
if (_si.data[dataPointIndex]) {
if (_si.data[dataPointIndex].fillColor) {
pathFillCircle = _si.data[dataPointIndex].fillColor
}
}
el.attr({
fill: pathFillCircle,
})
if (w.config.chart.dropShadow.enabled) {
const dropShadow = w.config.chart.dropShadow
filters.dropShadow(el, dropShadow, realIndex)
}
if (this.initialAnim && !w.globals.dataChanged && !w.globals.resized) {
const speed = w.config.chart.animations.speed
anim.animateMarker(
el,
speed,
/** @type {any} */ (w.globals).easing,
() => {
window.setTimeout(() => {
anim.animationCompleted(el)
}, 100)
},
)
} else {
w.globals.animationEnded = true
}
el.attr({
rel: dataPointIndex,
j: dataPointIndex,
index: realIndex,
'default-marker-size': markerConfig.pSize,
})
filters.setSelectionFilter(el, realIndex, dataPointIndex)
el.node.classList.add('apexcharts-marker')
return el
}
/**
* @param {number} y
*/
centerTextInBubble(y) {
const w = this.w
y = y + parseInt(w.config.dataLabels.style.fontSize, 10) / 4
return {
y,
}
}
}

View file

@ -0,0 +1,469 @@
// @ts-check
import TreemapSquared from '../libs/Treemap-squared'
import Graphics from '../modules/Graphics'
import Animations from '../modules/Animations'
import Fill from '../modules/Fill'
import Helpers from './common/treemap/Helpers'
import Filters from '../modules/Filters'
import Utils from '../utils/Utils'
/**
* ApexCharts TreemapChart Class.
* @module TreemapChart
**/
export default class TreemapChart {
/**
* @param {import('../types/internal').ChartStateW} w
* @param {import('../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.ctx = ctx
this.w = w
this.strokeWidth = this.w.config.stroke.width
this.helpers = new Helpers(w, ctx)
this.dynamicAnim = this.w.config.chart.animations.dynamicAnimation
/** @type {any} */
this.labels = []
}
/**
* @param {any[]} series
*/
draw(series) {
const w = this.w
const graphics = new Graphics(this.w, this.ctx)
const fill = new Fill(this.w)
const ret = graphics.group({
class: 'apexcharts-treemap',
})
if (w.globals.noData) return ret
/** @type {any[]} */
const ser = []
/**
* @param {number[]} s
*/
series.forEach((s) => {
/**
* @param {number} v
*/
const d = s.map((/** @type {any} */ v) => {
return Math.abs(v)
})
ser.push(d)
})
this.negRange = this.helpers.checkColorRange()
w.config.series.forEach((/** @type {any} */ s, /** @type {any} */ i) => {
/**
* @param {number} l
*/
s.data.forEach((/** @type {any} */ l) => {
if (!Array.isArray(this.labels[i])) this.labels[i] = []
this.labels[i].push(l.x)
})
})
const nodes = TreemapSquared.generate(
ser,
w.layout.gridWidth,
w.layout.gridHeight,
)
nodes.forEach((node, i) => {
const elSeries = graphics.group({
class: `apexcharts-series apexcharts-treemap-series`,
seriesName: Utils.escapeString(w.seriesData.seriesNames[i]),
rel: i + 1,
'data:realIndex': i,
})
// Set up event delegation once per series group instead of per-cell listeners
graphics.setupEventDelegation(elSeries, '.apexcharts-treemap-rect')
if (w.config.chart.dropShadow.enabled) {
const shadow = w.config.chart.dropShadow
const filters = new Filters(this.w)
filters.dropShadow(ret, shadow, i)
}
const elDataLabelWrap = graphics.group({
class: 'apexcharts-data-labels',
})
const bounds = {
xMin: Infinity,
yMin: Infinity,
xMax: -Infinity,
yMax: -Infinity,
}
/**
* @param {number} r
* @param {number} j
*/
node.forEach((/** @type {any} */ r, /** @type {any} */ j) => {
const x1 = r[0]
const y1 = r[1]
const x2 = r[2]
const y2 = r[3]
bounds.xMin = Math.min(bounds.xMin, x1)
bounds.yMin = Math.min(bounds.yMin, y1)
bounds.xMax = Math.max(bounds.xMax, x2)
bounds.yMax = Math.max(bounds.yMax, y2)
const colorProps = this.helpers.getShadeColor(
w.config.chart.type,
i,
j,
this.negRange,
)
const color = colorProps.color
const pathFill = fill.fillPath({
color,
seriesNumber: i,
dataPointIndex: j,
})
const elRect = graphics.drawRect(
x1,
y1,
x2 - x1,
y2 - y1,
w.config.plotOptions.treemap.borderRadius,
'#fff',
1,
this.strokeWidth,
w.config.plotOptions.treemap.useFillColorAsStroke
? color
: w.globals.stroke.colors[i],
)
elRect.attr({
cx: x1,
cy: y1,
index: i,
i,
j,
width: x2 - x1,
height: y2 - y1,
fill: pathFill,
})
elRect.node.classList.add('apexcharts-treemap-rect')
let fromRect = {
x: x1 + (x2 - x1) / 2,
y: y1 + (y2 - y1) / 2,
width: 0,
height: 0,
}
const toRect = {
x: x1,
y: y1,
width: x2 - x1,
height: y2 - y1,
}
if (w.config.chart.animations.enabled && !w.globals.dataChanged) {
let speed = 1
if (!w.globals.resized) {
speed = w.config.chart.animations.speed
}
this.animateTreemap(elRect, fromRect, toRect, speed)
}
if (w.globals.dataChanged) {
let speed = 1
if (this.dynamicAnim.enabled && w.globals.shouldAnimate) {
speed = this.dynamicAnim.speed
if (
w.globals.previousPaths[i] &&
/** @type {Record<string,any>} */ (w.globals.previousPaths[i])[
j
] &&
/** @type {Record<string,any>} */ (w.globals.previousPaths[i])[j]
.rect
) {
fromRect = /** @type {Record<string,any>} */ (
w.globals.previousPaths[i]
)[j].rect
}
this.animateTreemap(elRect, fromRect, toRect, speed)
}
}
let fontSize = this.getFontSize(r)
let formattedText = w.config.dataLabels.formatter(this.labels[i][j], {
value: w.seriesData.series[i][j],
seriesIndex: i,
dataPointIndex: j,
w,
})
if (w.config.plotOptions.treemap.dataLabels.format === 'truncate') {
fontSize = parseInt(String(w.config.dataLabels.style.fontSize), 10)
formattedText = this.truncateLabels(
String(formattedText),
fontSize,
x1,
y1,
x2,
y2,
)
}
let dataLabels = null
if (w.seriesData.series[i][j]) {
dataLabels = this.helpers.calculateDataLabels({
text: formattedText,
x: (x1 + x2) / 2,
y: (y1 + y2) / 2 + this.strokeWidth / 2 + fontSize / 3,
i,
j,
colorProps,
fontSize,
series,
})
}
if (w.config.dataLabels.enabled && dataLabels) {
this.rotateToFitLabel(
dataLabels,
fontSize,
formattedText,
x1,
y1,
x2,
y2,
)
}
elSeries.add(elRect)
if (dataLabels !== null) {
elSeries.add(dataLabels)
}
})
const seriesTitle = w.config.plotOptions.treemap.seriesTitle
if (w.config.series.length > 1 && seriesTitle && seriesTitle.show) {
const sName =
/** @type {Record<string,any>} */ (w.config.series[i]).name || ''
if (sName && bounds.xMin < Infinity && bounds.yMin < Infinity) {
const {
offsetX,
offsetY,
borderColor,
borderWidth,
borderRadius,
style,
} = seriesTitle
const textColor = style.color || w.config.chart.foreColor
const padding = {
left: style.padding.left,
right: style.padding.right,
top: style.padding.top,
bottom: style.padding.bottom,
}
const textSize = graphics.getTextRects(
sName,
style.fontSize,
style.fontFamily,
)
const labelRectWidth = textSize.width + padding.left + padding.right
const labelRectHeight = textSize.height + padding.top + padding.bottom
// Position
const labelX = bounds.xMin + (offsetX || 0)
const labelY = bounds.yMin + (offsetY || 0)
// Draw background rect
const elLabelRect = graphics.drawRect(
labelX,
labelY,
labelRectWidth,
labelRectHeight,
borderRadius,
style.background,
1,
borderWidth,
borderColor,
)
const elLabelText = graphics.drawText({
x: labelX + padding.left,
y: labelY + padding.top + (textSize?.height ?? 0) * 0.75,
text: sName,
fontSize: style.fontSize,
fontFamily: style.fontFamily,
fontWeight: style.fontWeight,
foreColor: textColor,
cssClass: style.cssClass || '',
})
elSeries.add(elLabelRect)
elSeries.add(elLabelText)
}
}
elSeries.add(elDataLabelWrap)
ret.add(elSeries)
})
return ret
}
// This calculates a font-size based upon
// average label length and the size of the box
/**
* @param {number[]} coordinates
*/
getFontSize(coordinates) {
const w = this.w
// total length of labels (i.e [["Italy"],["Spain", "Greece"]] -> 16)
/**
* @param {any[]} arr
*/
function totalLabelLength(arr) {
let i,
total = 0
if (Array.isArray(arr[0])) {
for (i = 0; i < arr.length; i++) {
total += totalLabelLength(arr[i])
}
} else {
for (i = 0; i < arr.length; i++) {
total += arr[i].length
}
}
return total
}
// count of labels (i.e [["Italy"],["Spain", "Greece"]] -> 3)
/**
* @param {any[]} arr
*/
function countLabels(arr) {
let i,
total = 0
if (Array.isArray(arr[0])) {
for (i = 0; i < arr.length; i++) {
total += countLabels(arr[i])
}
} else {
for (i = 0; i < arr.length; i++) {
total += 1
}
}
return total
}
const averagelabelsize =
totalLabelLength(this.labels) / countLabels(this.labels)
/**
* @param {number} width
* @param {number} height
*/
function fontSize(width, height) {
const area = width * height
const arearoot = Math.pow(area, 0.5)
return Math.min(
arearoot / averagelabelsize,
parseInt(w.config.dataLabels.style.fontSize, 10),
)
}
return fontSize(
coordinates[2] - coordinates[0],
coordinates[3] - coordinates[1],
)
}
/**
* @param {any} elText
* @param {string | number} fontSize
* @param {string} text
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
*/
rotateToFitLabel(elText, fontSize, text, x1, y1, x2, y2) {
const graphics = new Graphics(this.w)
const textRect = graphics.getTextRects(text, String(fontSize))
// if the label fits better sideways then rotate it
if (
textRect.width + this.w.config.stroke.width + 5 > x2 - x1 &&
textRect.width <= y2 - y1
) {
const labelRotatingCenter = graphics.rotateAroundCenter(elText.node)
elText.node.setAttribute(
'transform',
`rotate(-90 ${labelRotatingCenter.x} ${
labelRotatingCenter.y
}) translate(${textRect.height / 3})`,
)
}
}
// This is an alternative label formatting method that uses a
// consistent font size, and trims the edge of long labels
/**
* @param {string} text
* @param {number} fontSize
* @param {number} x1
* @param {number} y1
* @param {number} x2
* @param {number} y2
*/
truncateLabels(text, fontSize, x1, y1, x2, y2) {
const graphics = new Graphics(this.w)
const textRect = graphics.getTextRects(text, String(fontSize))
// Determine max width based on ideal orientation of text
const labelMaxWidth =
textRect.width + this.w.config.stroke.width + 5 > x2 - x1 &&
y2 - y1 > x2 - x1
? y2 - y1
: x2 - x1
const truncatedText = graphics.getTextBasedOnMaxWidth({
text: text,
maxWidth: labelMaxWidth,
fontSize: fontSize,
})
// Return empty label when text has been trimmed for very small rects
if (text.length !== truncatedText.length && labelMaxWidth / fontSize < 5) {
return ''
} else {
return truncatedText
}
}
/**
* @param {any} el
* @param {Record<string, any>} fromRect
* @param {Record<string, any>} toRect
* @param {number} speed
*/
animateTreemap(el, fromRect, toRect, speed) {
const animations = new Animations(this.w)
animations.animateRect(el, fromRect, toRect, speed, () => {
animations.animationCompleted(el)
})
}
}

View file

@ -0,0 +1,717 @@
// @ts-check
import Graphics from '../../../modules/Graphics'
import DataLabels from '../../../modules/DataLabels'
export default class BarDataLabels {
/**
* @param {import('../../../charts/Bar').default} barCtx
*/
constructor(barCtx) {
this.w = barCtx.w
this.barCtx = barCtx
this.totalFormatter =
this.w.config.plotOptions.bar.dataLabels.total.formatter
if (!this.totalFormatter) {
this.totalFormatter = this.w.config.dataLabels.formatter
}
}
/** handleBarDataLabels is used to calculate the positions for the data-labels
* It also sets the element's data attr for bars and calls drawCalculatedBarDataLabels()
* After calculating, it also calls the function to draw data labels
* @memberof Bar
* @param {Record<string, any>} opts - bar properties used throughout the bar drawing function
* @return {object} dataLabels node-element which you can append later
**/
handleBarDataLabels(opts) {
const {
x,
y,
y1,
y2,
i,
j,
realIndex,
columnGroupIndex,
series,
barHeight,
barWidth,
barXPosition,
barYPosition,
visibleSeries,
} = opts
const w = this.w
const graphics = new Graphics(this.barCtx.w)
const strokeWidth = Array.isArray(this.barCtx.strokeWidth)
? this.barCtx.strokeWidth[realIndex]
: this.barCtx.strokeWidth
let bcx
let bcy
if (w.axisFlags.isXNumeric && !w.globals.isBarHorizontal) {
bcx = x + barWidth * (visibleSeries + 1)
bcy = y + barHeight * (visibleSeries + 1) - strokeWidth
} else {
bcx = x + barWidth * visibleSeries
bcy = y + barHeight * visibleSeries
}
let dataLabels = null
let totalDataLabels = null
let dataLabelsX = x
let dataLabelsY = y
let dataLabelsPos = /** @type {any} */ ({})
const dataLabelsConfig = w.config.dataLabels
const barDataLabelsConfig = this.barCtx.barOptions.dataLabels
const barTotalDataLabelsConfig = this.barCtx.barOptions.dataLabels.total
if (typeof barYPosition !== 'undefined' && this.barCtx.isRangeBar) {
bcy = barYPosition
dataLabelsY = barYPosition
}
if (
typeof barXPosition !== 'undefined' &&
this.barCtx.isVerticalGroupedRangeBar
) {
bcx = barXPosition
dataLabelsX = barXPosition
}
const offX = dataLabelsConfig.offsetX
const offY = dataLabelsConfig.offsetY
let textRects = {
width: 0,
height: 0,
}
if (w.config.dataLabels.enabled) {
const yLabel = w.seriesData.series[i][j]
textRects = graphics.getTextRects(
w.config.dataLabels.formatter
? w.config.dataLabels.formatter(yLabel, {
...w,
seriesIndex: i,
dataPointIndex: j,
w,
})
: w.formatters.yLabelFormatters[0](yLabel),
parseFloat(dataLabelsConfig.style.fontSize).toString(),
)
}
const params = {
x,
y,
i,
j,
realIndex,
columnGroupIndex,
bcx,
bcy,
barHeight,
barWidth,
textRects,
strokeWidth,
dataLabelsX,
dataLabelsY,
dataLabelsConfig,
barDataLabelsConfig,
barTotalDataLabelsConfig,
offX,
offY,
}
if (this.barCtx.isHorizontal) {
dataLabelsPos = this.calculateBarsDataLabelsPosition(params)
} else {
dataLabelsPos = this.calculateColumnsDataLabelsPosition(params)
}
dataLabels = this.drawCalculatedDataLabels({
x: dataLabelsPos.dataLabelsX,
y: dataLabelsPos.dataLabelsY,
val: this.barCtx.isRangeBar
? [y1, y2]
: w.config.chart.stackType === '100%'
? series[realIndex][j]
: w.seriesData.series[realIndex][j],
i: realIndex,
j,
barWidth,
barHeight,
textRects,
dataLabelsConfig,
})
if (w.config.chart.stacked && barTotalDataLabelsConfig.enabled) {
totalDataLabels = this.drawTotalDataLabels({
x: dataLabelsPos.totalDataLabelsX,
y: dataLabelsPos.totalDataLabelsY,
barWidth,
barHeight,
realIndex,
textAnchor: dataLabelsPos.totalDataLabelsAnchor,
val: this.getStackedTotalDataLabel({ realIndex, j }),
dataLabelsConfig,
barTotalDataLabelsConfig,
})
}
return {
dataLabelsPos,
dataLabels,
totalDataLabels,
}
}
/** @param {{realIndex: any, j: any}} opts */
getStackedTotalDataLabel({ realIndex, j }) {
const w = this.w
let val = this.barCtx.stackedSeriesTotals[j]
if (this.totalFormatter) {
val = this.totalFormatter(val, {
...w,
seriesIndex: realIndex,
dataPointIndex: j,
w,
})
}
return val
}
/**
* @param {Record<string, any>} opts
*/
calculateColumnsDataLabelsPosition(opts) {
const w = this.w
let {
i,
j,
realIndex,
y,
bcx,
barWidth,
barHeight,
textRects,
dataLabelsX,
dataLabelsY,
dataLabelsConfig,
barDataLabelsConfig,
barTotalDataLabelsConfig,
strokeWidth,
offX,
offY,
} = opts
let totalDataLabelsY
let totalDataLabelsX
const totalDataLabelsAnchor = 'middle'
const totalDataLabelsBcx = bcx
barHeight = Math.abs(barHeight)
const vertical =
w.config.plotOptions.bar.dataLabels.orientation === 'vertical'
const { zeroEncounters } = this.barCtx.barHelpers.getZeroValueEncounters({
i,
j,
})
bcx = bcx - strokeWidth / 2
const dataPointsDividedWidth = w.layout.gridWidth / w.globals.dataPoints
if (this.barCtx.isVerticalGroupedRangeBar) {
dataLabelsX += barWidth / 2
} else {
if (w.axisFlags.isXNumeric) {
dataLabelsX = bcx - barWidth / 2 + offX
} else {
dataLabelsX = bcx - dataPointsDividedWidth + barWidth / 2 + offX
}
if (
!w.config.chart.stacked &&
zeroEncounters > 0 &&
w.config.plotOptions.bar.hideZeroBarsWhenGrouped
) {
dataLabelsX -= barWidth * zeroEncounters
}
}
if (vertical) {
const offsetDLX = 2
dataLabelsX =
dataLabelsX + textRects.height / 2 - strokeWidth / 2 - offsetDLX
}
const valIsNegative = w.seriesData.series[i][j] < 0
let newY = y
if (this.barCtx.isReversed) {
newY = y + (valIsNegative ? barHeight : -barHeight)
}
switch (barDataLabelsConfig.position) {
case 'center':
if (vertical) {
if (valIsNegative) {
dataLabelsY = newY - barHeight / 2 + offY
} else {
dataLabelsY = newY + barHeight / 2 - offY
}
} else {
if (valIsNegative) {
dataLabelsY = newY - barHeight / 2 + textRects.height / 2 + offY
} else {
dataLabelsY = newY + barHeight / 2 + textRects.height / 2 - offY
}
}
break
case 'bottom':
if (vertical) {
if (valIsNegative) {
dataLabelsY = newY - barHeight + offY
} else {
dataLabelsY = newY + barHeight - offY
}
} else {
if (valIsNegative) {
dataLabelsY =
newY - barHeight + textRects.height + strokeWidth + offY
} else {
dataLabelsY =
newY + barHeight - textRects.height / 2 + strokeWidth - offY
}
}
break
case 'top':
if (vertical) {
if (valIsNegative) {
dataLabelsY = newY + offY
} else {
dataLabelsY = newY - offY
}
} else {
if (valIsNegative) {
dataLabelsY = newY - textRects.height / 2 - offY
} else {
dataLabelsY = newY + textRects.height + offY
}
}
break
}
let lowestPrevY = newY
/**
* @param {string[]} sg
*/
w.labelData.seriesGroups.forEach((/** @type {any} */ sg) => {
/**
* @param {any[]} arr
*/
;/** @type {any} */ (this.barCtx)[sg.join(',')]?.prevY.forEach(
(/** @type {any} */ arr) => {
if (valIsNegative) {
lowestPrevY = Math.max(arr[j], lowestPrevY)
} else {
lowestPrevY = Math.min(arr[j], lowestPrevY)
}
},
)
})
if (
this.barCtx.lastActiveBarSerieIndex === realIndex &&
barTotalDataLabelsConfig.enabled
) {
const ADDITIONAL_OFFY = 18
const graphics = new Graphics(this.barCtx.w)
const totalLabeltextRects = graphics.getTextRects(
this.getStackedTotalDataLabel({ realIndex, j }),
dataLabelsConfig.fontSize,
)
if (valIsNegative) {
totalDataLabelsY =
lowestPrevY -
totalLabeltextRects.height / 2 -
offY -
barTotalDataLabelsConfig.offsetY +
ADDITIONAL_OFFY
} else {
totalDataLabelsY =
lowestPrevY +
totalLabeltextRects.height +
offY +
barTotalDataLabelsConfig.offsetY -
ADDITIONAL_OFFY
}
// width divided into equal parts
const xDivision = dataPointsDividedWidth
totalDataLabelsX =
totalDataLabelsBcx +
(w.axisFlags.isXNumeric
? (-barWidth * w.globals.barGroups.length) / 2
: (w.globals.barGroups.length * barWidth) / 2 -
(w.globals.barGroups.length - 1) * barWidth -
xDivision) +
barTotalDataLabelsConfig.offsetX
}
if (!w.config.chart.stacked) {
if (dataLabelsY < 0) {
dataLabelsY = 0 + strokeWidth
} else if (dataLabelsY + textRects.height / 3 > w.layout.gridHeight) {
dataLabelsY = w.layout.gridHeight - strokeWidth
}
}
return {
bcx,
bcy: y,
dataLabelsX,
dataLabelsY,
totalDataLabelsX,
totalDataLabelsY,
totalDataLabelsAnchor,
}
}
/**
* @param {Record<string, any>} opts
*/
calculateBarsDataLabelsPosition(opts) {
const w = this.w
let {
x,
i,
j,
realIndex,
bcy,
barHeight,
barWidth,
textRects,
dataLabelsX,
strokeWidth,
dataLabelsConfig,
barDataLabelsConfig,
barTotalDataLabelsConfig,
offX,
offY,
} = opts
const dataPointsDividedHeight = w.layout.gridHeight / w.globals.dataPoints
const { zeroEncounters } = this.barCtx.barHelpers.getZeroValueEncounters({
i,
j,
})
barWidth = Math.abs(barWidth)
let dataLabelsY =
bcy -
(this.barCtx.isRangeBar ? 0 : dataPointsDividedHeight) +
barHeight / 2 +
textRects.height / 2 +
offY -
3
if (
!w.config.chart.stacked &&
zeroEncounters > 0 &&
w.config.plotOptions.bar.hideZeroBarsWhenGrouped
) {
dataLabelsY -= barHeight * zeroEncounters
}
let totalDataLabelsX
let totalDataLabelsY
let totalDataLabelsAnchor = 'start'
const valIsNegative = w.seriesData.series[i][j] < 0
let newX = x
if (this.barCtx.isReversed) {
newX = x + (valIsNegative ? -barWidth : barWidth)
totalDataLabelsAnchor = valIsNegative ? 'start' : 'end'
}
switch (barDataLabelsConfig.position) {
case 'center':
if (valIsNegative) {
dataLabelsX = newX + barWidth / 2 - offX
} else {
dataLabelsX =
Math.max(textRects.width / 2, newX - barWidth / 2) + offX
}
break
case 'bottom':
if (valIsNegative) {
dataLabelsX = newX + barWidth - strokeWidth - offX
} else {
dataLabelsX = newX - barWidth + strokeWidth + offX
}
break
case 'top':
if (valIsNegative) {
dataLabelsX = newX - strokeWidth - offX
} else {
dataLabelsX = newX - strokeWidth + offX
}
break
}
let lowestPrevX = newX
/**
* @param {string[]} sg
*/
w.labelData.seriesGroups.forEach((/** @type {any} */ sg) => {
/**
* @param {any[]} arr
*/
;/** @type {any} */ (this.barCtx)[sg.join(',')]?.prevX.forEach(
(/** @type {any} */ arr) => {
if (valIsNegative) {
lowestPrevX = Math.min(arr[j], lowestPrevX)
} else {
lowestPrevX = Math.max(arr[j], lowestPrevX)
}
},
)
})
if (
this.barCtx.lastActiveBarSerieIndex === realIndex &&
barTotalDataLabelsConfig.enabled
) {
const graphics = new Graphics(this.barCtx.w)
const totalLabeltextRects = graphics.getTextRects(
this.getStackedTotalDataLabel({ realIndex, j }),
dataLabelsConfig.fontSize,
)
if (valIsNegative) {
totalDataLabelsX =
lowestPrevX - strokeWidth - offX - barTotalDataLabelsConfig.offsetX
totalDataLabelsAnchor = 'end'
} else {
totalDataLabelsX =
lowestPrevX +
offX +
barTotalDataLabelsConfig.offsetX +
(this.barCtx.isReversed ? -(barWidth + strokeWidth) : strokeWidth)
}
totalDataLabelsY =
dataLabelsY -
textRects.height / 2 +
totalLabeltextRects.height / 2 +
barTotalDataLabelsConfig.offsetY +
strokeWidth
if (w.globals.barGroups.length > 1) {
totalDataLabelsY =
totalDataLabelsY - (w.globals.barGroups.length / 2) * (barHeight / 2)
}
}
if (!w.config.chart.stacked) {
if (dataLabelsConfig.textAnchor === 'start') {
if (dataLabelsX - textRects.width < 0) {
dataLabelsX = valIsNegative
? textRects.width + strokeWidth
: strokeWidth
} else if (dataLabelsX + textRects.width > w.layout.gridWidth) {
dataLabelsX = valIsNegative
? w.layout.gridWidth - strokeWidth
: w.layout.gridWidth - textRects.width - strokeWidth
}
} else if (dataLabelsConfig.textAnchor === 'middle') {
if (dataLabelsX - textRects.width / 2 < 0) {
dataLabelsX = textRects.width / 2 + strokeWidth
} else if (dataLabelsX + textRects.width / 2 > w.layout.gridWidth) {
dataLabelsX = w.layout.gridWidth - textRects.width / 2 - strokeWidth
}
} else if (dataLabelsConfig.textAnchor === 'end') {
if (dataLabelsX < 1) {
dataLabelsX = textRects.width + strokeWidth
} else if (dataLabelsX + 1 > w.layout.gridWidth) {
dataLabelsX = w.layout.gridWidth - textRects.width - strokeWidth
}
}
}
return {
bcx: x,
bcy,
dataLabelsX,
dataLabelsY,
totalDataLabelsX,
totalDataLabelsY,
totalDataLabelsAnchor,
}
}
/** @param {{x: any, y: any, val: any, i: any, j: any, textRects: any, barHeight: any, barWidth: any, dataLabelsConfig: any}} opts */
drawCalculatedDataLabels({
x,
y,
val,
i, // = realIndex
j,
textRects,
barHeight,
barWidth,
dataLabelsConfig,
}) {
const w = this.w
let rotate = 'rotate(0)'
if (w.config.plotOptions.bar.dataLabels.orientation === 'vertical')
rotate = `rotate(-90, ${x}, ${y})`
const dataLabels = new DataLabels(this.barCtx.w, this.barCtx.ctx)
const graphics = new Graphics(this.barCtx.w)
const formatter = dataLabelsConfig.formatter
let elDataLabelsWrap = null
const isSeriesNotCollapsed =
w.globals.collapsedSeriesIndices.indexOf(i) > -1
if (dataLabelsConfig.enabled && !isSeriesNotCollapsed) {
elDataLabelsWrap = graphics.group({
class: 'apexcharts-data-labels',
transform: rotate,
})
let text = ''
if (typeof val !== 'undefined') {
text = formatter(val, {
...w,
seriesIndex: i,
dataPointIndex: j,
w,
})
}
if (!val && w.config.plotOptions.bar.hideZeroBarsWhenGrouped) {
text = ''
}
const valIsNegative = w.seriesData.series[i][j] < 0
const position = w.config.plotOptions.bar.dataLabels.position
if (w.config.plotOptions.bar.dataLabels.orientation === 'vertical') {
if (position === 'top') {
if (valIsNegative) dataLabelsConfig.textAnchor = 'end'
else dataLabelsConfig.textAnchor = 'start'
}
if (position === 'center') {
dataLabelsConfig.textAnchor = 'middle'
}
if (position === 'bottom') {
if (valIsNegative) dataLabelsConfig.textAnchor = 'end'
else dataLabelsConfig.textAnchor = 'start'
}
}
if (
this.barCtx.isRangeBar &&
this.barCtx.barOptions.dataLabels.hideOverflowingLabels
) {
// hide the datalabel if it cannot fit into the rect
const txRect = graphics.getTextRects(
text,
parseFloat(dataLabelsConfig.style.fontSize).toString(),
)
if (barWidth < txRect.width) {
text = ''
}
}
if (
w.config.chart.stacked &&
this.barCtx.barOptions.dataLabels.hideOverflowingLabels
) {
// if there is not enough space to draw the label in the bar/column rect, check hideOverflowingLabels property to prevent overflowing on wrong rect
// Note: This issue is only seen in stacked charts
if (this.barCtx.isHorizontal) {
if (textRects.width / 1.6 > Math.abs(barWidth)) {
text = ''
}
} else {
if (textRects.height / 1.6 > Math.abs(barHeight)) {
text = ''
}
}
}
const modifiedDataLabelsConfig = {
...dataLabelsConfig,
}
if (this.barCtx.isHorizontal) {
if (val < 0) {
if (dataLabelsConfig.textAnchor === 'start') {
modifiedDataLabelsConfig.textAnchor = 'end'
} else if (dataLabelsConfig.textAnchor === 'end') {
modifiedDataLabelsConfig.textAnchor = 'start'
}
}
}
dataLabels.plotDataLabelsText({
x,
y,
text,
i,
j,
parent: elDataLabelsWrap,
dataLabelsConfig: modifiedDataLabelsConfig,
alwaysDrawDataLabel: true,
offsetCorrection: true,
})
}
return elDataLabelsWrap
}
/** @param {{ x?: any, y?: any, val?: any, realIndex?: any, textAnchor?: any, barWidth?: any, barHeight?: any, dataLabelsConfig?: any, barTotalDataLabelsConfig?: any }} opts */
drawTotalDataLabels({
x,
y,
val,
realIndex,
textAnchor,
barTotalDataLabelsConfig,
}) {
const graphics = new Graphics(this.barCtx.w)
let totalDataLabelText
if (
barTotalDataLabelsConfig.enabled &&
typeof x !== 'undefined' &&
typeof y !== 'undefined' &&
this.barCtx.lastActiveBarSerieIndex === realIndex
) {
totalDataLabelText = graphics.drawText({
x: x,
y: y,
foreColor: barTotalDataLabelsConfig.style.color,
text: val,
textAnchor,
fontFamily: barTotalDataLabelsConfig.style.fontFamily,
fontSize: barTotalDataLabelsConfig.style.fontSize,
fontWeight: barTotalDataLabelsConfig.style.fontWeight,
})
}
return totalDataLabelText
}
}

View file

@ -0,0 +1,923 @@
// @ts-check
import Graphics from '../../../modules/Graphics'
import Series from '../../../modules/Series'
import Fill from '../../../modules/Fill'
import Utils from '../../../utils/Utils'
export default class Helpers {
/**
* @param {Record<string, any>} barCtx
*/
constructor(barCtx) {
this.w = barCtx.w
this.barCtx = barCtx
}
/**
* @param {any[]} series
*/
initVariables(series) {
const w = this.w
this.barCtx.series = series
this.barCtx.totalItems = 0
this.barCtx.seriesLen = 0
this.barCtx.visibleI = -1 // visible Series
this.barCtx.visibleItems = 1 // number of visible bars after user zoomed in/out
for (let sl = 0; sl < series.length; sl++) {
if (series[sl].length > 0) {
this.barCtx.seriesLen = this.barCtx.seriesLen + 1
this.barCtx.totalItems += series[sl].length
}
if (w.axisFlags.isXNumeric) {
// get max visible items
for (let j = 0; j < series[sl].length; j++) {
if (
w.seriesData.seriesX[sl][j] > w.globals.minX &&
w.seriesData.seriesX[sl][j] < w.globals.maxX
) {
this.barCtx.visibleItems++
}
}
} else {
this.barCtx.visibleItems = w.globals.dataPoints
}
}
this.arrBorderRadius = this.createBorderRadiusArr(w.seriesData.series)
if (Utils.isSafari()) {
// https://github.com/apexcharts/apexcharts.js/issues/4996
// to temporarily fix the above issue, border radius is disabled
/**
* @param {any[]} brArr
*/
this.arrBorderRadius = this.arrBorderRadius.map((/** @type {any} */ brArr) =>
/**
* @param {any} _
*/
brArr.map((/** @type {any} */ _) => 'none')
)
}
if (this.barCtx.seriesLen === 0) {
// A small adjustment when combo charts are used
this.barCtx.seriesLen = 1
}
this.barCtx.zeroSerieses = []
if (!w.globals.comboCharts) {
this.checkZeroSeries({ series })
}
}
/**
* @param {number} realIndex
*/
initialPositions(realIndex) {
const w = this.w
let x, y, yDivision, xDivision, barHeight, barWidth, zeroH, zeroW
let dataPoints = w.globals.dataPoints
if (this.barCtx.isRangeBar) {
// timeline rangebar chart
dataPoints = w.labelData.labels.length
}
let seriesLen = this.barCtx.seriesLen
if (w.config.plotOptions.bar.rangeBarGroupRows) {
seriesLen = 1
}
if (this.barCtx.isHorizontal) {
// height divided into equal parts
yDivision = w.layout.gridHeight / dataPoints
barHeight = yDivision / seriesLen
if (w.axisFlags.isXNumeric) {
yDivision = w.layout.gridHeight / this.barCtx.totalItems
barHeight = yDivision / this.barCtx.seriesLen
}
barHeight =
(barHeight * parseInt(this.barCtx.barOptions.barHeight, 10)) / 100
if (String(this.barCtx.barOptions.barHeight).indexOf('%') === -1) {
barHeight = parseInt(this.barCtx.barOptions.barHeight, 10)
}
zeroW =
this.barCtx.baseLineInvertedY +
w.globals.padHorizontal +
(this.barCtx.isReversed ? w.layout.gridWidth : 0) -
(this.barCtx.isReversed ? this.barCtx.baseLineInvertedY * 2 : 0)
if (this.barCtx.isFunnel) {
zeroW = w.layout.gridWidth / 2
}
y = (yDivision - barHeight * this.barCtx.seriesLen) / 2
} else {
// width divided into equal parts
xDivision = w.layout.gridWidth / this.barCtx.visibleItems
if (w.config.xaxis.convertedCatToNumeric) {
xDivision = w.layout.gridWidth / w.globals.dataPoints
}
barWidth =
((xDivision / seriesLen) *
parseInt(this.barCtx.barOptions.columnWidth, 10)) /
100
if (w.axisFlags.isXNumeric) {
// max barwidth should be equal to minXDiff to avoid overlap
const xRatio = this.barCtx.xRatio
if (
w.globals.minXDiff &&
w.globals.minXDiff !== 0.5 &&
w.globals.minXDiff / xRatio > 0
) {
xDivision = w.globals.minXDiff / xRatio
}
barWidth =
((xDivision / seriesLen) *
parseInt(this.barCtx.barOptions.columnWidth, 10)) /
100
if (barWidth < 1) {
barWidth = 1
}
}
if (String(this.barCtx.barOptions.columnWidth).indexOf('%') === -1) {
barWidth = parseInt(this.barCtx.barOptions.columnWidth, 10)
}
zeroH =
w.layout.gridHeight -
this.barCtx.baseLineY[this.barCtx.translationsIndex] -
(this.barCtx.isReversed ? w.layout.gridHeight : 0) +
(this.barCtx.isReversed
? this.barCtx.baseLineY[this.barCtx.translationsIndex] * 2
: 0)
if (w.axisFlags.isXNumeric) {
const xForNumericX = this.barCtx.getBarXForNumericXAxis({
x,
j: 0,
realIndex,
barWidth,
})
x = xForNumericX.x
} else {
x =
w.globals.padHorizontal +
Utils.noExponents(xDivision - barWidth * this.barCtx.seriesLen) / 2
}
}
w.globals.barHeight = barHeight
w.globals.barWidth = barWidth
return {
x,
y,
yDivision,
xDivision,
barHeight,
barWidth,
zeroH,
zeroW,
}
}
/**
* @param {Record<string, any>} ctx
*/
initializeStackedPrevVars(ctx) {
const w = ctx.w
/**
* @param {Element} group
*/
w.labelData.seriesGroups.forEach((/** @type {any} */ group) => {
if (!ctx[group]) ctx[group] = {}
ctx[group].prevY = []
ctx[group].prevX = []
ctx[group].prevYF = []
ctx[group].prevXF = []
ctx[group].prevYVal = []
ctx[group].prevXVal = []
})
}
/**
* @param {Record<string, any>} ctx
*/
initializeStackedXYVars(ctx) {
const w = ctx.w
/**
* @param {Element} group
*/
w.labelData.seriesGroups.forEach((/** @type {any} */ group) => {
if (!ctx[group]) ctx[group] = {}
ctx[group].xArrj = []
ctx[group].xArrjF = []
ctx[group].xArrjVal = []
ctx[group].yArrj = []
ctx[group].yArrjF = []
ctx[group].yArrjVal = []
})
}
/**
* @param {any[]} series
* @param {number} i
* @param {number} j
* @param {number} realIndex
*/
getPathFillColor(series, i, j, realIndex) {
const w = this.w
const fill = new Fill(this.barCtx.w)
let fillColor = null
const seriesNumber = this.barCtx.barOptions.distributed ? j : i
let useRangeColor = false
if (this.barCtx.barOptions.colors.ranges.length > 0) {
const colorRange = this.barCtx.barOptions.colors.ranges
/**
* @param {number} range
*/
colorRange.map((/** @type {any} */ range) => {
if (series[i][j] >= range.from && series[i][j] <= range.to) {
fillColor = range.color
useRangeColor = true
}
})
}
const pathFill = fill.fillPath({
seriesNumber: this.barCtx.barOptions.distributed
? seriesNumber
: realIndex,
dataPointIndex: j,
color: fillColor,
value: series[i][j],
fillConfig: w.config.series[i].data[j]?.fill,
fillType: w.config.series[i].data[j]?.fill?.type
? w.config.series[i].data[j]?.fill.type
: Array.isArray(w.config.fill.type)
? w.config.fill.type[realIndex]
: w.config.fill.type,
})
return {
color: pathFill,
useRangeColor,
}
}
/**
* @param {number} i
* @param {number} j
* @param {number} realIndex
*/
getStrokeWidth(i, j, realIndex) {
let strokeWidth = 0
const w = this.w
if (
typeof this.barCtx.series[i][j] === 'undefined' ||
this.barCtx.series[i][j] === null ||
(w.config.chart.type === 'bar' && !this.barCtx.series[i][j])
) {
this.barCtx.isNullValue = true
} else {
this.barCtx.isNullValue = false
}
if (w.config.stroke.show) {
if (!this.barCtx.isNullValue) {
strokeWidth = Array.isArray(this.barCtx.strokeWidth)
? this.barCtx.strokeWidth[realIndex]
: this.barCtx.strokeWidth
}
}
return strokeWidth
}
/**
* @param {any[]} series
*/
createBorderRadiusArr(series) {
const w = this.w
const alwaysApplyRadius =
!this.w.config.chart.stacked || w.config.plotOptions.bar.borderRadius <= 0
const numSeries = series.length
const numColumns = series[0]?.length | 0
const output = Array.from({ length: numSeries }, () =>
Array(numColumns).fill(alwaysApplyRadius ? 'top' : 'none')
)
if (alwaysApplyRadius) return output
const chartType = this.w.config.chart.type;
for (let j = 0; j < numColumns; j++) {
const positiveIndices = []
const negativeIndices = []
let nonZeroCount = 0
// Collect positive and negative indices
for (let i = 0; i < numSeries; i++) {
const value = series[i][j]
if (value > 0) {
positiveIndices.push(i)
nonZeroCount++
} else if (value < 0) {
negativeIndices.push(i)
nonZeroCount++
}
}
if (positiveIndices.length > 0 && negativeIndices.length === 0) {
// Only positive values in this column
if (positiveIndices.length === 1) {
// Single positive value
output[positiveIndices[0]][j] = (chartType === 'bar' && numColumns === 1) ? 'top' : 'both'
} else {
// Multiple positive values
const firstPositiveIndex = positiveIndices[0]
const lastPositiveIndex = positiveIndices[positiveIndices.length - 1]
for (const i of positiveIndices) {
if (i === firstPositiveIndex) {
output[i][j] = (chartType === 'bar' && numColumns === 1) ? 'top' : 'bottom'
} else if (i === lastPositiveIndex) {
output[i][j] = 'top'
} else {
output[i][j] = 'none'
}
}
}
} else if (negativeIndices.length > 0 && positiveIndices.length === 0) {
// Only negative values in this column
if (negativeIndices.length === 1) {
// Single negative value
output[negativeIndices[0]][j] = 'both'
} else {
// Multiple negative values
const highestNegativeIndex = Math.max(...negativeIndices)
const lowestNegativeIndex = Math.min(...negativeIndices)
for (const i of negativeIndices) {
if (i === highestNegativeIndex) {
output[i][j] = 'bottom' // Closest to axis
} else if (i === lowestNegativeIndex) {
output[i][j] = 'top' // Farthest from axis
} else {
output[i][j] = 'none'
}
}
}
} else if (positiveIndices.length > 0 && negativeIndices.length > 0) {
// Mixed positive and negative values
// Assign 'top' to the last positive bar
const lastPositiveIndex = positiveIndices[positiveIndices.length - 1]
for (const i of positiveIndices) {
if (i === lastPositiveIndex) {
output[i][j] = 'top'
} else {
output[i][j] = 'none'
}
}
// Assign 'bottom' to the highest negative index (closest to axis)
const highestNegativeIndex = Math.max(...negativeIndices)
for (const i of negativeIndices) {
if (i === highestNegativeIndex) {
output[i][j] = 'bottom'
} else {
output[i][j] = 'none'
}
}
} else if (nonZeroCount === 1) {
// Only one non-zero value (either positive or negative)
const index = positiveIndices[0] || negativeIndices[0]
output[index][j] = 'both'
}
}
return output
}
/** @param {{ j?: any, i?: any, x1?: any, x2?: any, y1?: any, y2?: any, bc?: any, elSeries?: any }} opts */
barBackground({ j, i, x1, x2, y1, y2, elSeries }) {
const w = this.w
const graphics = new Graphics(this.barCtx.w)
const sr = new Series(this.barCtx.w)
const activeSeriesIndex = sr.getActiveConfigSeriesIndex()
if (
this.barCtx.barOptions.colors.backgroundBarColors.length > 0 &&
activeSeriesIndex === i
) {
if (j >= this.barCtx.barOptions.colors.backgroundBarColors.length) {
j %= this.barCtx.barOptions.colors.backgroundBarColors.length
}
const bcolor = this.barCtx.barOptions.colors.backgroundBarColors[j]
const rect = graphics.drawRect(
typeof x1 !== 'undefined' ? x1 : 0,
typeof y1 !== 'undefined' ? y1 : 0,
typeof x2 !== 'undefined' ? x2 : w.layout.gridWidth,
typeof y2 !== 'undefined' ? y2 : w.layout.gridHeight,
this.barCtx.barOptions.colors.backgroundBarRadius,
bcolor,
this.barCtx.barOptions.colors.backgroundBarOpacity
)
elSeries.add(rect)
rect.node.classList.add('apexcharts-backgroundBar')
}
}
/** @param {{ barWidth?: any, barXPosition?: any, y1?: any, y2?: any, yRatio?: any, strokeWidth?: any, isReversed?: any, series?: any, seriesGroup?: any, realIndex?: any, i?: any, j?: any, w?: any }} opts */
getColumnPaths({
barWidth,
barXPosition,
y1,
y2,
strokeWidth,
isReversed,
series,
seriesGroup,
realIndex,
i,
j,
w,
}) {
const graphics = new Graphics(this.barCtx.w)
strokeWidth = Array.isArray(strokeWidth)
? strokeWidth[realIndex]
: strokeWidth
if (!strokeWidth) strokeWidth = 0
let bW = barWidth
let bXP = barXPosition
if (w.config.series[realIndex].data[j]?.columnWidthOffset) {
bXP =
barXPosition - w.config.series[realIndex].data[j].columnWidthOffset / 2
bW = barWidth + w.config.series[realIndex].data[j].columnWidthOffset
}
// Center the stroke on the coordinates
const strokeCenter = strokeWidth / 2
const x1 = bXP + strokeCenter
const x2 = bXP + bW - strokeCenter
const direction = (series[i][j] >= 0 ? 1 : -1) * (isReversed ? -1 : 1)
// append tiny pixels to avoid exponentials (which cause issues in border-radius)
y1 += 0.001 - strokeCenter * direction
y2 += 0.001 + strokeCenter * direction
let pathTo = graphics.move(x1, y1)
let pathFrom = graphics.move(x1, y1)
const sl = graphics.line(x2, y1)
if (w.globals.previousPaths.length > 0) {
pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
}
pathTo =
pathTo +
graphics.line(x1, y2) +
graphics.line(x2, y2) +
sl +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
// the lines in pathFrom are repeated to equal it to the points of pathTo
// this is to avoid weird animation (bug in svg.js)
pathFrom =
pathFrom +
graphics.line(x1, y1) +
sl +
sl +
sl +
sl +
sl +
graphics.line(x1, y1) +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
if (this.arrBorderRadius[realIndex][j] !== 'none') {
pathTo = graphics.roundPathCorners(
pathTo,
w.config.plotOptions.bar.borderRadius
)
}
if (w.config.chart.stacked) {
let _ctx = this.barCtx
_ctx = this.barCtx[seriesGroup]
_ctx.yArrj.push(y2 - strokeCenter * direction)
_ctx.yArrjF.push(Math.abs(y1 - y2 + strokeWidth * direction))
_ctx.yArrjVal.push(this.barCtx.series[i][j])
}
return {
pathTo,
pathFrom,
}
}
/** @param {{ barYPosition?: any, barHeight?: any, x1?: any, x2?: any, strokeWidth?: any, isReversed?: any, series?: any, seriesGroup?: any, realIndex?: any, i?: any, j?: any, w?: any }} opts */
getBarpaths({
barYPosition,
barHeight,
x1,
x2,
strokeWidth,
isReversed,
series,
seriesGroup,
realIndex,
i,
j,
w,
}) {
const graphics = new Graphics(this.barCtx.w)
strokeWidth = Array.isArray(strokeWidth)
? strokeWidth[realIndex]
: strokeWidth
if (!strokeWidth) strokeWidth = 0
let bYP = barYPosition
let bH = barHeight
if (w.config.series[realIndex].data[j]?.barHeightOffset) {
bYP =
barYPosition - w.config.series[realIndex].data[j].barHeightOffset / 2
bH = barHeight + w.config.series[realIndex].data[j].barHeightOffset
}
// Center the stroke on the coordinates
const strokeCenter = strokeWidth / 2
const y1 = bYP + strokeCenter
const y2 = bYP + bH - strokeCenter
const direction = (series[i][j] >= 0 ? 1 : -1) * (isReversed ? -1 : 1)
// append tiny pixels to avoid exponentials (which cause issues in border-radius)
x1 += 0.001 + strokeCenter * direction
x2 += 0.001 - strokeCenter * direction
let pathTo = graphics.move(x1, y1)
let pathFrom = graphics.move(x1, y1)
if (w.globals.previousPaths.length > 0) {
pathFrom = this.barCtx.getPreviousPath(realIndex, j, false)
}
const sl = graphics.line(x1, y2)
pathTo =
pathTo +
graphics.line(x2, y1) +
graphics.line(x2, y2) +
sl +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
pathFrom =
pathFrom +
graphics.line(x1, y1) +
sl +
sl +
sl +
sl +
sl +
graphics.line(x1, y1) +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
if (this.arrBorderRadius[realIndex][j] !== 'none') {
pathTo = graphics.roundPathCorners(
pathTo,
w.config.plotOptions.bar.borderRadius
)
}
if (w.config.chart.stacked) {
let _ctx = this.barCtx
_ctx = this.barCtx[seriesGroup]
_ctx.xArrj.push(x2 + strokeCenter * direction)
_ctx.xArrjF.push(Math.abs(x1 - x2 - strokeWidth * direction))
_ctx.xArrjVal.push(this.barCtx.series[i][j])
}
return {
pathTo,
pathFrom,
}
}
/** @param {{series: any}} opts */
checkZeroSeries({ series }) {
const w = this.w
for (let zs = 0; zs < series.length; zs++) {
let total = 0
for (
let zsj = 0;
zsj < series[w.globals.maxValsInArrayIndex].length;
zsj++
) {
total += series[zs][zsj]
}
if (total === 0) {
this.barCtx.zeroSerieses.push(zs)
}
}
}
/**
* @param {number} value
* @param {number} zeroW
*/
getXForValue(value, zeroW, zeroPositionForNull = true) {
let xForVal = zeroPositionForNull ? zeroW : null
if (typeof value !== 'undefined' && value !== null) {
xForVal =
zeroW +
value / this.barCtx.invertedYRatio -
(this.barCtx.isReversed ? value / this.barCtx.invertedYRatio : 0) * 2
}
return xForVal
}
/**
* @param {number} value
* @param {number} zeroH
* @param {number} translationsIndex
*/
getYForValue(value, zeroH, translationsIndex, zeroPositionForNull = true) {
let yForVal = zeroPositionForNull ? zeroH : null
if (typeof value !== 'undefined' && value !== null) {
yForVal =
zeroH -
value / this.barCtx.yRatio[translationsIndex] +
(this.barCtx.isReversed
? value / this.barCtx.yRatio[translationsIndex]
: 0) *
2
}
return yForVal
}
/**
* @param {string} type
* @param {number} zeroW
* @param {number} zeroH
* @param {number} i
* @param {number} j
* @param {number} translationsIndex
*/
getGoalValues(type, zeroW, zeroH, i, j, translationsIndex) {
const w = this.w
/** @type {any[]} */
const goals = []
/**
* @param {number} value
* @param {Record<string, any>} attrs
*/
const pushGoal = (value, attrs) => {
goals.push({
[type]:
type === 'x'
? this.getXForValue(value, zeroW, false)
: this.getYForValue(value, zeroH, translationsIndex, false),
attrs,
})
}
if (
w.seriesData.seriesGoals[i] &&
w.seriesData.seriesGoals[i][j] &&
Array.isArray(w.seriesData.seriesGoals[i][j])
) {
/**
* @param {Record<string, any>} goal
*/
w.seriesData.seriesGoals[i][j].forEach((/** @type {any} */ goal) => {
pushGoal(goal.value, goal)
})
}
if (this.barCtx.barOptions.isDumbbell && w.rangeData.seriesRange.length) {
const colors = this.barCtx.barOptions.dumbbellColors
? this.barCtx.barOptions.dumbbellColors
: w.globals.colors
const commonAttrs = {
strokeHeight: type === 'x' ? 0 : w.globals.markers.size[i],
strokeWidth: type === 'x' ? w.globals.markers.size[i] : 0,
strokeDashArray: 0,
strokeLineCap: 'round',
strokeColor: Array.isArray(colors[i]) ? colors[i][0] : colors[i],
}
pushGoal(w.rangeData.seriesRangeStart[i][j], commonAttrs)
pushGoal(w.rangeData.seriesRangeEnd[i][j], {
...commonAttrs,
strokeColor: Array.isArray(colors[i]) ? colors[i][1] : colors[i],
})
}
return goals
}
/** @param {{barXPosition: any, barYPosition: any, goalX: any, goalY: any, barWidth: any, barHeight: any}} opts */
drawGoalLine({
barXPosition,
barYPosition,
goalX,
goalY,
barWidth,
barHeight,
}) {
const graphics = new Graphics(this.barCtx.w)
const lineGroup = graphics.group({
className: 'apexcharts-bar-goals-groups',
})
lineGroup.node.classList.add('apexcharts-element-hidden')
this.barCtx.w.globals.delayedElements.push({
el: lineGroup.node,
})
lineGroup.attr(
'clip-path',
`url(#gridRectMarkerMask${this.barCtx.w.globals.cuid})`
)
let line = null
if (this.barCtx.isHorizontal) {
if (Array.isArray(goalX)) {
goalX.forEach((goal) => {
// Need a tiny margin of 1 each side so goals don't disappear at extremeties
if (goal.x >= -1 && goal.x <= graphics.w.layout.gridWidth + 1) {
const sHeight =
typeof goal.attrs.strokeHeight !== 'undefined'
? goal.attrs.strokeHeight
: barHeight / 2
const y = barYPosition + sHeight + barHeight / 2
line = graphics.drawLine(
goal.x,
y - sHeight * 2,
goal.x,
y,
goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined,
goal.attrs.strokeDashArray,
goal.attrs.strokeWidth ? goal.attrs.strokeWidth : 2,
goal.attrs.strokeLineCap
)
lineGroup.add(line)
}
})
}
} else {
if (Array.isArray(goalY)) {
goalY.forEach((goal) => {
// Need a tiny margin of 1 each side so goals don't disappear at extremeties
if (goal.y >= -1 && goal.y <= graphics.w.layout.gridHeight + 1) {
const sWidth =
typeof goal.attrs.strokeWidth !== 'undefined'
? goal.attrs.strokeWidth
: barWidth / 2
const x = barXPosition + sWidth + barWidth / 2
line = graphics.drawLine(
x - sWidth * 2,
goal.y,
x,
goal.y,
goal.attrs.strokeColor ? goal.attrs.strokeColor : undefined,
goal.attrs.strokeDashArray,
goal.attrs.strokeHeight ? goal.attrs.strokeHeight : 2,
goal.attrs.strokeLineCap
)
lineGroup.add(line)
}
})
}
}
return lineGroup
}
/** @param {{prevPaths: any, currPaths: any, color: any, realIndex: any, j: any}} opts */
drawBarShadow({ prevPaths, currPaths, color, realIndex, j }) {
const w = this.w
const { x: prevX2, x1: prevX1, barYPosition: prevY1 } = prevPaths
const { x: currX2, x1: currX1, barYPosition: currY1 } = currPaths
const prevY2 = prevY1 + currPaths.barHeight
const graphics = new Graphics(this.barCtx.w)
const utils = new Utils()
const shadowPath =
graphics.move(prevX1, prevY2) +
graphics.line(prevX2, prevY2) +
graphics.line(currX2, currY1) +
graphics.line(currX1, currY1) +
graphics.line(prevX1, prevY2) +
(w.config.plotOptions.bar.borderRadiusApplication === 'around' ||
this.arrBorderRadius[realIndex][j] === 'both'
? ' Z'
: ' z')
return graphics.drawPath({
d: shadowPath,
fill: utils.shadeColor(0.5, Utils.rgb2hex(color)),
stroke: 'none',
strokeWidth: 0,
fillOpacity: 1,
classes: 'apexcharts-bar-shadow apexcharts-decoration-element',
})
}
/** @param {{i: any, j: any}} opts */
getZeroValueEncounters({ i, j }) {
const w = this.w
let nonZeroColumns = 0
let zeroEncounters = 0
const seriesIndices = w.config.plotOptions.bar.horizontal
/**
* @param {any} _
* @param {number} _i
*/
? w.seriesData.series.map((/** @type {any} */ _, /** @type {any} */ _i) => _i)
/**
* @param {number} _i
*/
: w.globals.columnSeries?.i.map((/** @type {any} */ _i) => _i) || []
/**
* @param {number} _si
*/
seriesIndices.forEach((/** @type {any} */ _si) => {
const val = w.globals.seriesPercent[_si][j]
if (val) {
nonZeroColumns++
}
if (_si < i && val === 0) {
zeroEncounters++
}
})
return {
nonZeroColumns,
zeroEncounters,
}
}
/**
* @param {number} seriesIndex
*/
getGroupIndex(seriesIndex) {
const w = this.w
// groupIndex is the index of group buckets (group1, group2, ...)
/**
* @param {Element} group
*/
const groupIndex = w.labelData.seriesGroups.findIndex(
(/** @type {any} */ group) =>
// w.config.series[i].name may be undefined, so use
// w.seriesData.seriesNames[i], which has default names for those
// series. w.labelData.seriesGroups[] uses the same default naming.
group.indexOf(w.seriesData.seriesNames[seriesIndex]) > -1
)
// We need the column groups to be indexable as 0,1,2,... for their
// positioning relative to each other.
const cGI = this.barCtx.columnGroupIndices
let columnGroupIndex = cGI.indexOf(groupIndex)
if (columnGroupIndex < 0) {
cGI.push(groupIndex)
columnGroupIndex = cGI.length - 1
}
return { groupIndex, columnGroupIndex }
}
}

View file

@ -0,0 +1,39 @@
// @ts-check
import Graphics from '../../../modules/Graphics'
export default class CircularChartsHelpers {
/**
* @param {import('../../../types/internal').ChartStateW} w
*/
constructor(w) {
this.w = w
}
/**
* @param {number} x
* @param {number} y
* @param {number} i
* @param {string | number} text
*/
drawYAxisTexts(x, y, i, text) {
const w = this.w
const yaxisConfig = w.config.yaxis[0]
const formatter = w.formatters.yLabelFormatters[0]
const graphics = new Graphics(this.w)
const yaxisLabel = graphics.drawText({
x: x + yaxisConfig.labels.offsetX,
y: y + yaxisConfig.labels.offsetY,
text: formatter(text, i),
textAnchor: 'middle',
fontSize: yaxisConfig.labels.style.fontSize,
fontFamily: yaxisConfig.labels.style.fontFamily,
foreColor: Array.isArray(yaxisConfig.labels.style.colors)
? yaxisConfig.labels.style.colors[i]
: yaxisConfig.labels.style.colors,
})
return yaxisLabel
}
}

View file

@ -0,0 +1,167 @@
// @ts-check
import CoreUtils from '../../../modules/CoreUtils'
import Utils from '../../../utils/Utils'
export default class Helpers {
/**
* @param {import('../../../charts/Line').default} lineCtx
*/
constructor(lineCtx) {
this.w = lineCtx.w
this.lineCtx = lineCtx
}
/**
* @param {number} i
* @param {any[]} series
*/
sameValueSeriesFix(i, series) {
const w = this.w
if (
w.config.fill.type === 'gradient' ||
w.config.fill.type[i] === 'gradient'
) {
const coreUtils = new CoreUtils(this.lineCtx.w)
// applied only to LINE chart
// a small adjustment to allow gradient line to draw correctly for all same values
/* #fix https://github.com/apexcharts/apexcharts.js/issues/358 */
if (coreUtils.seriesHaveSameValues(i)) {
const gSeries = series[i].slice()
gSeries[gSeries.length - 1] = gSeries[gSeries.length - 1] + 0.000001
series[i] = gSeries
}
}
return series
}
/** @param {{series: any, realIndex: any, x: any, y: any, i: any, j: any, prevY: any}} opts */
calculatePoints({ series, realIndex, x, y, i, j, prevY }) {
const w = this.w
const ptX = []
const ptY = []
let xPT1st = this.lineCtx.categoryAxisCorrection + w.config.markers.offsetX
// the first point for line series
// we need to check whether it's not a time series, because a time series may
// start from the middle of the x axis
if (w.axisFlags.isXNumeric) {
xPT1st =
(w.seriesData.seriesX[realIndex][0] - w.globals.minX) /
this.lineCtx.xRatio +
w.config.markers.offsetX
}
// push 2 points for the first data values
if (j === 0) {
ptX.push(xPT1st)
ptY.push(
Utils.isNumber(series[i][0]) ? prevY + w.config.markers.offsetY : null,
)
}
ptX.push(x + w.config.markers.offsetX)
ptY.push(
Utils.isNumber(series[i][j + 1]) ? y + w.config.markers.offsetY : null,
)
return {
x: ptX,
y: ptY,
}
}
/** @param {{pathFromLine: any, pathFromArea: any, realIndex: any}} opts */
checkPreviousPaths({ pathFromLine, pathFromArea, realIndex }) {
const w = this.w
for (let pp = 0; pp < w.globals.previousPaths.length; pp++) {
const gpp = w.globals.previousPaths[pp]
if (
(gpp.type === 'line' || gpp.type === 'area') &&
gpp.paths.length > 0 &&
parseInt(gpp.realIndex, 10) === parseInt(realIndex, 10)
) {
if (gpp.type === 'line') {
this.lineCtx.appendPathFrom = false
pathFromLine = w.globals.previousPaths[pp].paths[0].d
} else if (gpp.type === 'area') {
this.lineCtx.appendPathFrom = false
pathFromArea = w.globals.previousPaths[pp].paths[0].d
if (w.config.stroke.show && w.globals.previousPaths[pp].paths[1]) {
pathFromLine = w.globals.previousPaths[pp].paths[1].d
}
}
}
}
return {
pathFromLine,
pathFromArea,
}
}
/** @param {{i: any, realIndex: any, series: any, prevY: any, lineYPosition: any, translationsIndex: any}} opts */
determineFirstPrevY({
i,
realIndex,
series,
prevY,
lineYPosition,
translationsIndex,
}) {
const w = this.w
const stackSeries =
(w.config.chart.stacked && !w.globals.comboCharts) ||
(w.config.chart.stacked &&
w.globals.comboCharts &&
(!this.w.config.chart.stackOnlyBar ||
/** @type {any} */ (this.w.config.series[realIndex])?.type ===
'bar' ||
/** @type {any} */ (this.w.config.series[realIndex])?.type ===
'column'))
if (typeof series[i]?.[0] !== 'undefined') {
if (stackSeries) {
if (i > 0) {
// 1st y value of previous series
lineYPosition = this.lineCtx.prevSeriesY[i - 1][0]
} else {
// the first series will not have prevY values
lineYPosition = this.lineCtx.zeroY
}
} else {
lineYPosition = this.lineCtx.zeroY
}
prevY =
lineYPosition -
series[i][0] / this.lineCtx.yRatio[translationsIndex] +
(this.lineCtx.isReversed
? series[i][0] / this.lineCtx.yRatio[translationsIndex]
: 0) *
2
} else {
// the first value in the current series is null
if (stackSeries && i > 0 && typeof series[i][0] === 'undefined') {
// check for undefined value (undefined value will occur when we clear the series while user clicks on legend to hide serieses)
for (let s = i - 1; s >= 0; s--) {
// for loop to get to 1st previous value until we get it
if (series[s][0] !== null && typeof series[s][0] !== 'undefined') {
lineYPosition = this.lineCtx.prevSeriesY[s][0]
prevY = lineYPosition
break
}
}
}
}
return {
prevY,
lineYPosition,
}
}
}

View file

@ -0,0 +1,207 @@
// @ts-check
import Utils from '../../../utils/Utils'
import Graphics from '../../../modules/Graphics'
import DataLabels from '../../../modules/DataLabels'
export default class TreemapHelpers {
/**
* @param {import('../../../types/internal').ChartStateW} w
* @param {import('../../../types/internal').ChartContext} ctx
*/
constructor(w, ctx) {
this.ctx = ctx
this.w = w
}
checkColorRange() {
const w = this.w
let negRange = false
const chartOpts = w.config.plotOptions[w.config.chart.type]
if (chartOpts.colorScale.ranges.length > 0) {
/**
* @param {number} range
*/
chartOpts.colorScale.ranges.map((/** @type {any} */ range) => {
if (range.from <= 0) {
negRange = true
}
})
}
return negRange
}
/**
* @param {string} chartType
* @param {number} i
* @param {number} j
* @param {any} negRange
*/
getShadeColor(chartType, i, j, negRange) {
const w = this.w
let colorShadePercent = 1
const shadeIntensity = w.config.plotOptions[chartType].shadeIntensity
const colorProps = this.determineColor(chartType, i, j)
if (/** @type {any} */ (w.globals).hasNegs || negRange) {
if (w.config.plotOptions[chartType].reverseNegativeShade) {
if (colorProps.percent < 0) {
colorShadePercent =
(colorProps.percent / 100) * (shadeIntensity * 1.25)
} else {
colorShadePercent =
(1 - colorProps.percent / 100) * (shadeIntensity * 1.25)
}
} else {
if (colorProps.percent <= 0) {
colorShadePercent =
1 - (1 + colorProps.percent / 100) * shadeIntensity
} else {
colorShadePercent = (1 - colorProps.percent / 100) * shadeIntensity
}
}
} else {
colorShadePercent = 1 - colorProps.percent / 100
if (chartType === 'treemap') {
colorShadePercent =
(1 - colorProps.percent / 100) * (shadeIntensity * 1.25)
}
}
let color = colorProps.color
const utils = new Utils()
if (w.config.plotOptions[chartType].enableShades) {
// The shadeColor function may return either an RGB or a hex color value
// However, hexToRgba requires the input to be in hex format
// The ternary operator checks if the color is in RGB format, and if so, converts it to hex
if (this.w.config.theme.mode === 'dark') {
const shadeColor = utils.shadeColor(
colorShadePercent * -1,
colorProps.color,
)
color = Utils.hexToRgba(
Utils.isColorHex(shadeColor) ? shadeColor : Utils.rgb2hex(shadeColor),
w.config.fill.opacity,
)
} else {
const shadeColor = utils.shadeColor(colorShadePercent, colorProps.color)
color = Utils.hexToRgba(
Utils.isColorHex(shadeColor) ? shadeColor : Utils.rgb2hex(shadeColor),
w.config.fill.opacity,
)
}
}
return { color, colorProps }
}
/**
* @param {string} chartType
* @param {number} i
* @param {number} j
*/
determineColor(chartType, i, j) {
const w = this.w
const val = w.seriesData.series[i][j]
const chartOpts = w.config.plotOptions[chartType]
let seriesNumber = chartOpts.colorScale.inverse ? j : i
if (chartOpts.distributed && w.config.chart.type === 'treemap') {
seriesNumber = j
}
let color = w.globals.colors[seriesNumber]
let foreColor = null
let min = Math.min(...w.seriesData.series[i])
let max = Math.max(...w.seriesData.series[i])
if (!chartOpts.distributed && chartType === 'heatmap') {
min = w.globals.minY
max = w.globals.maxY
}
if (typeof chartOpts.colorScale.min !== 'undefined') {
min =
chartOpts.colorScale.min < w.globals.minY
? chartOpts.colorScale.min
: w.globals.minY
max =
chartOpts.colorScale.max > w.globals.maxY
? chartOpts.colorScale.max
: w.globals.maxY
}
const total = Math.abs(max) + Math.abs(min)
let percent = (100 * val) / (total === 0 ? total - 0.000001 : total)
if (chartOpts.colorScale.ranges.length > 0) {
const colorRange = chartOpts.colorScale.ranges
/**
* @param {number} range
*/
colorRange.map((/** @type {any} */ range) => {
if (val >= range.from && val <= range.to) {
color = range.color
foreColor = range.foreColor ? range.foreColor : null
min = range.from
max = range.to
const rTotal = Math.abs(max) + Math.abs(min)
percent = (100 * val) / (rTotal === 0 ? rTotal - 0.000001 : rTotal)
}
})
}
return {
color,
foreColor,
percent,
}
}
/** @param {{ text?: any, x?: any, y?: any, i?: any, j?: any, colorProps?: any, fontSize?: any, series?: any }} opts */
calculateDataLabels({ text, x, y, i, j, colorProps, fontSize }) {
const w = this.w
const dataLabelsConfig = w.config.dataLabels
const graphics = new Graphics(this.w)
const dataLabels = new DataLabels(this.w, this.ctx)
let elDataLabelsWrap = null
if (dataLabelsConfig.enabled) {
elDataLabelsWrap = graphics.group({
class: 'apexcharts-data-labels',
})
const offX = dataLabelsConfig.offsetX
const offY = dataLabelsConfig.offsetY
const dataLabelsX = x + offX
const dataLabelsY =
y + parseFloat(dataLabelsConfig.style.fontSize) / 3 + offY
dataLabels.plotDataLabelsText({
x: dataLabelsX,
y: dataLabelsY,
text,
i,
j,
color: colorProps.foreColor,
parent: elDataLabelsWrap,
fontSize,
dataLabelsConfig,
})
}
return elDataLabelsWrap
}
}

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts area entry point.
* Alias for 'apexcharts/line' also registers line, scatter, bubble, rangeArea.
*/
export { default } from './line'

View file

@ -0,0 +1,24 @@
// @ts-check
/**
* ApexCharts bar / column / rangeBar entry point.
*
* Usage:
* import ApexCharts from 'apexcharts/bar'
*
* Registers: bar, column, barStacked, rangeBar
* Excludes: line, area, scatter, bubble, candlestick, boxPlot, heatmap,
* treemap, pie, donut, polarArea, radialBar, radar, rangeArea
*/
import ApexCharts from '../apexcharts'
import Bar from '../charts/Bar'
import BarStacked from '../charts/BarStacked'
import RangeBar from '../charts/RangeBar'
ApexCharts.use({
bar: Bar,
column: Bar,
barStacked: BarStacked,
rangeBar: RangeBar,
})
export default ApexCharts

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts boxPlot entry point.
* Alias for 'apexcharts/candlestick' also registers candlestick.
*/
export { default } from './candlestick'

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts bubble entry point.
* Alias for 'apexcharts/line' also registers line, area, scatter, rangeArea.
*/
export { default } from './line'

View file

@ -0,0 +1,18 @@
// @ts-check
/**
* ApexCharts candlestick / boxPlot entry point.
*
* Usage:
* import ApexCharts from 'apexcharts/candlestick'
*
* Registers: candlestick, boxPlot
*/
import ApexCharts from '../apexcharts'
import BoxCandleStick from '../charts/BoxCandleStick'
ApexCharts.use({
candlestick: BoxCandleStick,
boxPlot: BoxCandleStick,
})
export default ApexCharts

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts column entry point.
* Alias for 'apexcharts/bar' also registers bar, barStacked, rangeBar.
*/
export { default } from './bar'

View file

@ -0,0 +1,89 @@
// @ts-check
/**
* ApexCharts core entry point.
*
* Exports the base ApexCharts class with NO chart types and NO optional
* feature modules registered. Use this as the starting point for custom
* minimal bundles:
*
* import ApexCharts from 'apexcharts/core'
* import 'apexcharts/line' // line/area/scatter chart types
* import 'apexcharts/features/legend' // legend (optional)
* // Omit features you don't need — they won't be included in your bundle.
* // Note: tooltip is always included as part of core.
*
* The __apex_* named exports below are internal they allow chart-type
* sub-entries (line.esm.js, bar.esm.js, etc.) to import shared utilities
* from this bundle rather than bundling their own copies, avoiding duplication.
* Do not rely on these exports in application code; they may change.
*/
export { default } from '../apexcharts'
// Shared utilities re-exported for sub-entry de-duplication (internal use only)
export { default as __apex_charts_Scatter } from '../charts/Scatter.js'
export { default as __apex_Animations } from '../modules/Animations.js'
export { default as __apex_Base } from '../modules/Base.js'
export { register as __apex_ChartFactory_register, getChartClass as __apex_ChartFactory_getChartClass } from '../modules/ChartFactory.js'
export { default as __apex_Core } from '../modules/Core.js'
export { default as __apex_CoreUtils } from '../modules/CoreUtils.js'
export { default as __apex_Crosshairs } from '../modules/Crosshairs.js'
export { default as __apex_Data } from '../modules/Data.js'
export { default as __apex_DataLabels } from '../modules/DataLabels.js'
export { default as __apex_Events } from '../modules/Events.js'
export { default as __apex_Fill } from '../modules/Fill.js'
export { default as __apex_Filters } from '../modules/Filters.js'
export { default as __apex_Formatters } from '../modules/Formatters.js'
export { default as __apex_Graphics } from '../modules/Graphics.js'
export { default as __apex_Markers } from '../modules/Markers.js'
export { default as __apex_Range } from '../modules/Range.js'
export { default as __apex_Responsive } from '../modules/Responsive.js'
export { default as __apex_Scales } from '../modules/Scales.js'
export { default as __apex_Series } from '../modules/Series.js'
export { default as __apex_Theme } from '../modules/Theme.js'
export { default as __apex_TimeScale } from '../modules/TimeScale.js'
export { default as __apex_TitleSubtitle } from '../modules/TitleSubtitle.js'
export { default as __apex_axes_Axes } from '../modules/axes/Axes.js'
export { default as __apex_axes_AxesUtils } from '../modules/axes/AxesUtils.js'
export { default as __apex_axes_Grid } from '../modules/axes/Grid.js'
export { default as __apex_axes_XAxis } from '../modules/axes/XAxis.js'
export { default as __apex_axes_YAxis } from '../modules/axes/YAxis.js'
export { default as __apex_dimensions_Dimensions } from '../modules/dimensions/Dimensions.js'
export { default as __apex_dimensions_Grid } from '../modules/dimensions/Grid.js'
export { default as __apex_dimensions_Helpers } from '../modules/dimensions/Helpers.js'
export { default as __apex_dimensions_XAxis } from '../modules/dimensions/XAxis.js'
export { default as __apex_dimensions_YAxis } from '../modules/dimensions/YAxis.js'
export { default as __apex_helpers_Destroy } from '../modules/helpers/Destroy.js'
export { default as __apex_helpers_InitCtxVariables } from '../modules/helpers/InitCtxVariables.js'
export { default as __apex_helpers_Localization } from '../modules/helpers/Localization.js'
export { default as __apex_helpers_UpdateHelpers } from '../modules/helpers/UpdateHelpers.js'
export { default as __apex_Config } from '../modules/settings/Config.js'
export { default as __apex_Defaults } from '../modules/settings/Defaults.js'
export { default as __apex_Globals } from '../modules/settings/Globals.js'
export { default as __apex_Options } from '../modules/settings/Options.js'
export { default as __apex_tooltip_AxesTooltip } from '../modules/tooltip/AxesTooltip.js'
export { default as __apex_tooltip_Intersect } from '../modules/tooltip/Intersect.js'
export { default as __apex_tooltip_Labels } from '../modules/tooltip/Labels.js'
export { default as __apex_tooltip_Marker } from '../modules/tooltip/Marker.js'
export { default as __apex_tooltip_Position } from '../modules/tooltip/Position.js'
export { default as __apex_tooltip_Tooltip } from '../modules/tooltip/Tooltip.js'
export { default as __apex_tooltip_Utils } from '../modules/tooltip/Utils.js'
export { BrowserAPIs as __apex_BrowserAPIs_BrowserAPIs } from '../ssr/BrowserAPIs.js'
export { SSRDOMShim as __apex_DOMShim_SSRDOMShim, SSRElement as __apex_DOMShim_SSRElement, SSRClassList as __apex_DOMShim_SSRClassList } from '../ssr/DOMShim.js'
export { parsePath as __apex_PathMorphing_parsePath, morphPaths as __apex_PathMorphing_morphPaths, pathBbox as __apex_PathMorphing_pathBbox, arrayToPath as __apex_PathMorphing_arrayToPath } from '../svg/PathMorphing.js'
export { SVGAnimationRunner as __apex_SVGAnimation_SVGAnimationRunner, installAnimationMethods as __apex_SVGAnimation_installAnimationMethods } from '../svg/SVGAnimation.js'
export { default as __apex_SVGContainer } from '../svg/SVGContainer.js'
export { installDraggable as __apex_SVGDraggable_installDraggable } from '../svg/SVGDraggable.js'
export { default as __apex_SVGElement } from '../svg/SVGElement.js'
export { SVGFilter as __apex_SVGFilter_SVGFilter, FilterBuilder as __apex_SVGFilter_FilterBuilder, installFilterMethods as __apex_SVGFilter_installFilterMethods } from '../svg/SVGFilter.js'
export { SVGGradient as __apex_SVGGradient_SVGGradient } from '../svg/SVGGradient.js'
export { SVGPattern as __apex_SVGPattern_SVGPattern } from '../svg/SVGPattern.js'
export { installSelectable as __apex_SVGSelectable_installSelectable } from '../svg/SVGSelectable.js'
export { SVG as __apex_index_SVG, Box as __apex_index_Box } from '../svg/index.js'
export { SVGNS as __apex_math_SVGNS, Point as __apex_math_Point, Matrix as __apex_math_Matrix, Box as __apex_math_Box } from '../svg/math.js'
export { LINE_HEIGHT_RATIO as __apex_Constants_LINE_HEIGHT_RATIO, NICE_SCALE_ALLOWED_MAG_MSD as __apex_Constants_NICE_SCALE_ALLOWED_MAG_MSD, NICE_SCALE_DEFAULT_TICKS as __apex_Constants_NICE_SCALE_DEFAULT_TICKS } from '../utils/Constants.js'
export { default as __apex_DateTime } from '../utils/DateTime.js'
export { Environment as __apex_Environment_Environment } from '../utils/Environment.js'
export { default as __apex_PerformanceCache } from '../utils/PerformanceCache.js'
export { addResizeListener as __apex_Resize_addResizeListener, removeResizeListener as __apex_Resize_removeResizeListener } from '../utils/Resize.js'
export { getThemePalettes as __apex_ThemePalettes_getThemePalettes } from '../utils/ThemePalettes.js'
export { default as __apex_Utils } from '../utils/Utils.js'

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts donut entry point.
* Alias for 'apexcharts/pie' also registers pie, polarArea.
*/
export { default } from './pie'

View file

@ -0,0 +1,45 @@
// @ts-check
/**
* ApexCharts full bundle entry point (all chart types).
*
* This is what the default import resolves to:
* import ApexCharts from 'apexcharts'
*
* Registers every built-in chart type. Use a sub-entry point instead
* (e.g. 'apexcharts/line') to ship only the types you need.
*/
import ApexCharts from '../apexcharts'
import '../features/all.js'
import Bar from '../charts/Bar'
import BarStacked from '../charts/BarStacked'
import BoxCandleStick from '../charts/BoxCandleStick'
import HeatMap from '../charts/HeatMap'
import Line from '../charts/Line'
import Pie from '../charts/Pie'
import Radar from '../charts/Radar'
import Radial from '../charts/Radial'
import RangeBar from '../charts/RangeBar'
import Treemap from '../charts/Treemap'
ApexCharts.use({
line: Line,
area: Line,
scatter: Line,
bubble: Line,
rangeArea: Line,
bar: Bar,
column: Bar,
barStacked: BarStacked,
rangeBar: RangeBar,
candlestick: BoxCandleStick,
boxPlot: BoxCandleStick,
pie: Pie,
donut: Pie,
polarArea: Pie,
radialBar: Radial,
radar: Radar,
heatmap: HeatMap,
treemap: Treemap,
})
export default ApexCharts

View file

@ -0,0 +1,17 @@
// @ts-check
/**
* ApexCharts heatmap entry point.
*
* Usage:
* import ApexCharts from 'apexcharts/heatmap'
*
* Registers: heatmap
*/
import ApexCharts from '../apexcharts'
import HeatMap from '../charts/HeatMap'
ApexCharts.use({
heatmap: HeatMap,
})
export default ApexCharts

View file

@ -0,0 +1,23 @@
// @ts-check
/**
* ApexCharts line / area / scatter / bubble / rangeArea entry point.
*
* Usage:
* import ApexCharts from 'apexcharts/line'
*
* Registers: line, area, scatter, bubble, rangeArea
* Excludes: bar, candlestick, boxPlot, heatmap, treemap, pie, donut,
* polarArea, radialBar, radar, rangeBar
*/
import ApexCharts from '../apexcharts'
import Line from '../charts/Line'
ApexCharts.use({
line: Line,
area: Line,
scatter: Line,
bubble: Line,
rangeArea: Line,
})
export default ApexCharts

View file

@ -0,0 +1,19 @@
// @ts-check
/**
* ApexCharts pie / donut / polarArea entry point.
*
* Usage:
* import ApexCharts from 'apexcharts/pie'
*
* Registers: pie, donut, polarArea
*/
import ApexCharts from '../apexcharts'
import Pie from '../charts/Pie'
ApexCharts.use({
pie: Pie,
donut: Pie,
polarArea: Pie,
})
export default ApexCharts

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts polarArea entry point.
* Alias for 'apexcharts/pie' also registers pie, donut.
*/
export { default } from './pie'

View file

@ -0,0 +1,17 @@
// @ts-check
/**
* ApexCharts radar entry point.
*
* Usage:
* import ApexCharts from 'apexcharts/radar'
*
* Registers: radar
*/
import ApexCharts from '../apexcharts'
import Radar from '../charts/Radar'
ApexCharts.use({
radar: Radar,
})
export default ApexCharts

View file

@ -0,0 +1,17 @@
// @ts-check
/**
* ApexCharts radialBar entry point.
*
* Usage:
* import ApexCharts from 'apexcharts/radialBar'
*
* Registers: radialBar
*/
import ApexCharts from '../apexcharts'
import Radial from '../charts/Radial'
ApexCharts.use({
radialBar: Radial,
})
export default ApexCharts

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts rangeArea entry point.
* Alias for 'apexcharts/line' also registers line, area, scatter, bubble.
*/
export { default } from './line'

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts rangeBar entry point.
* Alias for 'apexcharts/bar' also registers bar, column, barStacked.
*/
export { default } from './bar'

View file

@ -0,0 +1,6 @@
// @ts-check
/**
* ApexCharts scatter entry point.
* Alias for 'apexcharts/line' also registers line, area, bubble, rangeArea.
*/
export { default } from './line'

View file

@ -0,0 +1,17 @@
// @ts-check
/**
* ApexCharts treemap entry point.
*
* Usage:
* import ApexCharts from 'apexcharts/treemap'
*
* Registers: treemap
*/
import ApexCharts from '../apexcharts'
import Treemap from '../charts/Treemap'
ApexCharts.use({
treemap: Treemap,
})
export default ApexCharts

View file

@ -0,0 +1,18 @@
// @ts-check
/**
* Registers all optional ApexCharts feature modules.
*
* Imported by src/entries/full.js to preserve the existing full-bundle
* behaviour where every feature is available out of the box.
*
* For a minimal custom bundle, import only the features you need:
*
* import 'apexcharts/features/legend'
* import 'apexcharts/features/exports'
* // etc. (tooltip is always included — it is part of core)
*/
import './exports.js'
import './legend.js'
import './toolbar.js'
import './annotations.js'
import './keyboard.js'

View file

@ -0,0 +1,7 @@
// @ts-check
import ApexCharts from '../apexcharts'
import Annotations from '../modules/annotations/Annotations'
ApexCharts.registerFeatures({ annotations: Annotations })
export default ApexCharts

View file

@ -0,0 +1,7 @@
// @ts-check
import ApexCharts from '../apexcharts'
import Exports from '../modules/Exports'
ApexCharts.registerFeatures({ exports: Exports })
export default ApexCharts

View file

@ -0,0 +1,7 @@
// @ts-check
import ApexCharts from '../apexcharts'
import KeyboardNavigation from '../modules/accessibility/KeyboardNavigation'
ApexCharts.registerFeatures({ keyboardNavigation: KeyboardNavigation })
export default ApexCharts

View file

@ -0,0 +1,7 @@
// @ts-check
import ApexCharts from '../apexcharts'
import Legend from '../modules/legend/Legend'
ApexCharts.registerFeatures({ legend: Legend })
export default ApexCharts

View file

@ -0,0 +1,14 @@
// @ts-check
import ApexCharts from '../apexcharts'
import Toolbar from '../modules/Toolbar'
import ZoomPanSelection from '../modules/ZoomPanSelection'
// Toolbar and ZoomPanSelection are always registered together — zoom/pan
// controls require both to function. Toolbar renders the UI buttons;
// ZoomPanSelection handles the mouse/touch interaction.
ApexCharts.registerFeatures({
toolbar: Toolbar,
zoomPanSelection: ZoomPanSelection,
})
export default ApexCharts

View file

@ -0,0 +1,200 @@
// @ts-check
/*
* treemap-squarify.js - open source implementation of squarified treemaps
*
* Based on Treemap Squared 0.5 by Imran Ghory
* https://github.com/imranghory/treemap-squared/
*
* Copyright (c) 2012 Imran Ghory (imranghory@gmail.com)
* Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
*
* Implementation of the squarify treemap algorithm described in:
*
* Bruls, Mark; Huizing, Kees; van Wijk, Jarke J. (2000), "Squarified treemaps"
* in de Leeuw, W.; van Liere, R., Data Visualization 2000:
* Proc. Joint Eurographics and IEEE TCVG Symp. on Visualization, Springer-Verlag, pp. 33-42.
*
*/
/**
* @param {number[]} data
* @param {number} area
*/
function normalize(data, area) {
let sum = 0
for (let i = 0; i < data.length; i++) {
sum += data[i]
}
const multiplier = area / sum
const result = new Array(data.length)
for (let i = 0; i < data.length; i++) {
result[i] = data[i] * multiplier
}
return result
}
/**
* @param {number} rowMin
* @param {number} rowMax
* @param {number} rowSum
* @param {number} length
*/
function calculateRatio(rowMin, rowMax, rowSum, length) {
const lengthSq = length * length
const sumSq = rowSum * rowSum
return Math.max(
(lengthSq * rowMax) / sumSq,
sumSq / (lengthSq * rowMin)
)
}
/**
* @param {number} rowLen
* @param {number} rowMin
* @param {number} rowMax
* @param {number} rowSum
* @param {number} nextNode
* @param {number} length
*/
function improvesRatio(rowLen, rowMin, rowMax, rowSum, nextNode, length) {
if (rowLen === 0) return true
const currentRatio = calculateRatio(rowMin, rowMax, rowSum, length)
const newRatio = calculateRatio(
Math.min(rowMin, nextNode),
Math.max(rowMax, nextNode),
rowSum + nextNode,
length
)
return currentRatio >= newRatio
}
/**
* @param {any[]} coords
* @param {number[]} row
* @param {number} rowLen
* @param {number} rowSum
* @param {number} xoffset
* @param {number} yoffset
* @param {number} width
* @param {number} height
*/
function emitCoordinates(coords, row, rowLen, rowSum, xoffset, yoffset, width, height) {
if (width >= height) {
const areaWidth = rowSum / height
let subY = yoffset
for (let i = 0; i < rowLen; i++) {
const h = row[i] / areaWidth
coords.push([xoffset, subY, xoffset + areaWidth, subY + h])
subY += h
}
} else {
const areaHeight = rowSum / width
let subX = xoffset
for (let i = 0; i < rowLen; i++) {
const w = row[i] / areaHeight
coords.push([subX, yoffset, subX + w, yoffset + areaHeight])
subX += w
}
}
}
/**
* @param {number[]} data
* @param {number} xoffset
* @param {number} yoffset
* @param {number} width
* @param {number} height
*/
function squarify(data, xoffset, yoffset, width, height) {
/** @type {any[]} */
const coords = []
const n = data.length
if (n === 0) return coords
const row = new Array(n)
let rowLen = 0
let rowSum = 0
let rowMin = Infinity
let rowMax = -Infinity
let i = 0
while (i < n) {
const length = Math.min(width, height)
const val = data[i]
if (improvesRatio(rowLen, rowMin, rowMax, rowSum, val, length)) {
row[rowLen] = val
rowLen++
rowSum += val
if (val < rowMin) rowMin = val
if (val > rowMax) rowMax = val
i++
} else {
emitCoordinates(coords, row, rowLen, rowSum, xoffset, yoffset, width, height)
if (width >= height) {
const areaWidth = rowSum / height
xoffset += areaWidth
width -= areaWidth
} else {
const areaHeight = rowSum / width
yoffset += areaHeight
height -= areaHeight
}
rowLen = 0
rowSum = 0
rowMin = Infinity
rowMax = -Infinity
}
}
if (rowLen > 0) {
emitCoordinates(coords, row, rowLen, rowSum, xoffset, yoffset, width, height)
}
return coords
}
/**
* @param {any[]} data
* @param {number} width
* @param {number} height
*/
function generate(data, width, height) {
const n = data.length
const sums = new Array(n)
for (let i = 0; i < n; i++) {
let s = 0
const series = data[i]
for (let j = 0; j < series.length; j++) {
s += series[j]
}
sums[i] = s
}
const seriesRects = squarify(
normalize(sums, width * height),
0, 0, width, height
)
const results = new Array(n)
for (let i = 0; i < n; i++) {
const rect = seriesRects[i]
const rx = rect[0]
const ry = rect[1]
const rw = rect[2] - rx
const rh = rect[3] - ry
results[i] = squarify(
normalize(data[i], rw * rh),
rx, ry, rw, rh
)
}
return results
}
export default { generate }

View file

@ -0,0 +1,187 @@
// @ts-check
/**
*
* @yr/monotone-cubic-spline (https://github.com/YR/monotone-cubic-spline)
*
* The MIT License (MIT)
*
* Copyright (c) 2015 yr.no
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Generate tangents for 'points'
* @param {any[]} points
* @returns {any[]}
*/
export const tangents = (points) => {
const m = finiteDifferences(points)
const n = points.length - 1
const ε = 1e-6
const tgts = []
let a, b, d, s
for (let i = 0; i < n; i++) {
d = slope(points[i], points[i + 1])
if (Math.abs(d) < ε) {
m[i] = m[i + 1] = 0
} else {
a = m[i] / d
b = m[i + 1] / d
s = a * a + b * b
if (s > 9) {
s = (d * 3) / Math.sqrt(s)
m[i] = s * a
m[i + 1] = s * b
}
}
}
for (let i = 0; i <= n; i++) {
s =
(points[Math.min(n, i + 1)][0] - points[Math.max(0, i - 1)][0]) /
(6 * (1 + m[i] * m[i]))
tgts.push([s || 0, m[i] * s || 0])
}
return tgts
}
/**
* Convert 'points' to svg path
* @param {any[]} points
* @returns {String}
*/
export const svgPath = (points) => {
let p = ''
for (let i = 0; i < points.length; i++) {
const point = points[i]
const n = point.length
if (n > 4) {
p += `C${point[0]}, ${point[1]}`
p += `, ${point[2]}, ${point[3]}`
p += `, ${point[4]}, ${point[5]}`
} else if (n > 2) {
p += `S${point[0]}, ${point[1]}`
p += `, ${point[2]}, ${point[3]}`
}
}
return p
}
export const spline = {
/**
* Convert 'points' to bezier
* @param {any[]} points
* @returns {any[]}
*/
points(points) {
const tgts = tangents(points)
const p = points[1]
const p0 = points[0]
const pts = []
const t = tgts[1]
const t0 = tgts[0]
// Add starting 'M' and 'C' points
pts.push(p0, [
p0[0] + t0[0],
p0[1] + t0[1],
p[0] - t[0],
p[1] - t[1],
p[0],
p[1],
])
// Add 'S' points
for (let i = 2, n = tgts.length; i < n; i++) {
const p = points[i]
const t = tgts[i]
pts.push([p[0] - t[0], p[1] - t[1], p[0], p[1]])
}
return pts
},
/**
* Slice out a segment of 'points'
* @param {any[]} points
* @param {Number} start
* @param {Number} end
* @returns {any[]}
*/
slice(points, start, end) {
const pts = points.slice(start, end)
if (start) {
// Add additional 'C' points
if (end - start > 1 && pts[1].length < 6) {
const n = pts[0].length
pts[1] = [
pts[0][n - 2] * 2 - pts[0][n - 4],
pts[0][n - 1] * 2 - pts[0][n - 3],
].concat(pts[1])
}
// Remove control points for 'M'
pts[0] = pts[0].slice(-2)
}
return pts
},
}
/**
* Compute slope from point 'p0' to 'p1'
* @param {any[]} p0
* @param {any[]} p1
* @returns {Number}
*/
function slope(p0, p1) {
return (p1[1] - p0[1]) / (p1[0] - p0[0])
}
/**
* Compute three-point differences for 'points'
* @param {any[]} points
* @returns {any[]}
*/
function finiteDifferences(points) {
const m = []
let p0 = points[0]
let p1 = points[1]
let d = (m[0] = slope(p0, p1))
let i = 1
for (let n = points.length - 1; i < n; i++) {
p0 = p1
p1 = points[i + 1]
m[i] = (d + (d = slope(p0, p1))) * 0.5
}
m[i] = d
return m
}

View file

@ -0,0 +1,63 @@
{
"name": "ar",
"options": {
"months": [
"يناير",
"فبراير",
"مارس",
"أبريل",
"مايو",
"يونيو",
"يوليو",
"أغسطس",
"سبتمبر",
"أكتوبر",
"نوفمبر",
"ديسمبر"
],
"shortMonths": [
"يناير",
"فبراير",
"مارس",
"أبريل",
"مايو",
"يونيو",
"يوليو",
"أغسطس",
"سبتمبر",
"أكتوبر",
"نوفمبر",
"ديسمبر"
],
"days": [
"الأحد",
"الإثنين",
"الثلاثاء",
"الأربعاء",
"الخميس",
"الجمعة",
"السبت"
],
"shortDays": [
"أحد",
"إثنين",
"ثلاثاء",
"أربعاء",
"خميس",
"جمعة",
"سبت"
],
"toolbar": {
"exportToSVG": "تحميل بصيغة SVG",
"exportToPNG": "تحميل بصيغة PNG",
"exportToCSV": "تحميل بصيغة CSV",
"menu": "القائمة",
"selection": "تحديد",
"selectionZoom": "تكبير التحديد",
"zoomIn": "تكبير",
"zoomOut": "تصغير",
"pan": "تحريك",
"reset": "إعادة التعيين"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "be-cyrl",
"options": {
"months": [
"Студзень",
"Люты",
"Сакавік",
"Красавік",
"Травень",
"Чэрвень",
"Ліпень",
"Жнівень",
"Верасень",
"Кастрычнік",
"Лістапад",
"Сьнежань"
],
"shortMonths": [
"Сту",
"Лют",
"Сак",
"Кра",
"Тра",
"Чэр",
"Ліп",
"Жні",
"Вер",
"Кас",
"Ліс",
"Сьн"
],
"days": [
"Нядзеля",
"Панядзелак",
"Аўторак",
"Серада",
"Чацьвер",
"Пятніца",
"Субота"
],
"shortDays": ["Нд", "Пн", "Аў", "Ср", "Чц", "Пт", "Сб"],
"toolbar": {
"exportToSVG": "Спампаваць SVG",
"exportToPNG": "Спампаваць PNG",
"exportToCSV": "Спампаваць CSV",
"menu": "Мэню",
"selection": "Вылучэньне",
"selectionZoom": "Вылучэньне з маштабаваньнем",
"zoomIn": "Наблізіць",
"zoomOut": "Аддаліць",
"pan": "Ссоўваньне",
"reset": "Скінуць маштабаваньне"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "be-latn",
"options": {
"months": [
"Studzień",
"Luty",
"Sakavik",
"Krasavik",
"Travień",
"Červień",
"Lipień",
"Žnivień",
"Vierasień",
"Kastryčnik",
"Listapad",
"Śniežań"
],
"shortMonths": [
"Stu",
"Lut",
"Sak",
"Kra",
"Tra",
"Čer",
"Lip",
"Žni",
"Vie",
"Kas",
"Lis",
"Śni"
],
"days": [
"Niadziela",
"Paniadziełak",
"Aŭtorak",
"Sierada",
"Čaćvier",
"Piatnica",
"Subota"
],
"shortDays": ["Nd", "Pn", "Aŭ", "Sr", "Čć", "Pt", "Sb"],
"toolbar": {
"exportToSVG": "Spampavać SVG",
"exportToPNG": "Spampavać PNG",
"exportToCSV": "Spampavać CSV",
"menu": "Meniu",
"selection": "Vyłučeńnie",
"selectionZoom": "Vyłučeńnie z maštabavańniem",
"zoomIn": "Nablizić",
"zoomOut": "Addalić",
"pan": "Ssoŭvańnie",
"reset": "Skinuć maštabavańnie"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "bg",
"options": {
"months": [
"Януари",
"Февруари",
"Март",
"Април",
"Май",
"Юни",
"Юли",
"Август",
"Септември",
"Октомври",
"Ноември",
"Декември"
],
"shortMonths": [
"Яну",
"Фев",
"Мар",
"Апр",
"Май",
"Юни",
"Юли",
"Авг",
"Сеп",
"Окт",
"Ное",
"Дек"
],
"days": [
"Неделя",
"Понеделник",
"Вторник",
"Сряда",
"Четвъртък",
"Петък",
"Събота"
],
"shortDays": ["Нед", "Пон", "Вто", "Сря", "Чет", "Пет", "Съб"],
"toolbar": {
"exportToSVG": "Изтегли SVG",
"exportToPNG": "Изтегли PNG",
"exportToCSV": "Изтегли CSV",
"menu": "Меню",
"selection": "Избор",
"selectionZoom": "Мащабиране на избора",
"zoomIn": "Увеличаване",
"zoomOut": "Намаляване",
"pan": "Панориране",
"reset": "Нулиране на мащаба"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "ca",
"options": {
"months": [
"Gener",
"Febrer",
"Març",
"Abril",
"Maig",
"Juny",
"Juliol",
"Agost",
"Setembre",
"Octubre",
"Novembre",
"Desembre"
],
"shortMonths": [
"Gen.",
"Febr.",
"Març",
"Abr.",
"Maig",
"Juny",
"Jul.",
"Ag.",
"Set.",
"Oct.",
"Nov.",
"Des."
],
"days": [
"Diumenge",
"Dilluns",
"Dimarts",
"Dimecres",
"Dijous",
"Divendres",
"Dissabte"
],
"shortDays": ["Dg", "Dl", "Dt", "Dc", "Dj", "Dv", "Ds"],
"toolbar": {
"exportToSVG": "Descarregar SVG",
"exportToPNG": "Descarregar PNG",
"exportToCSV": "Descarregar CSV",
"menu": "Menú",
"selection": "Seleccionar",
"selectionZoom": "Seleccionar Zoom",
"zoomIn": "Augmentar",
"zoomOut": "Disminuir",
"pan": "Navegació",
"reset": "Reiniciar Zoom"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "cs",
"options": {
"months": [
"Leden",
"Únor",
"Březen",
"Duben",
"Květen",
"Červen",
"Červenec",
"Srpen",
"Září",
"Říjen",
"Listopad",
"Prosinec"
],
"shortMonths": [
"Led",
"Úno",
"Bře",
"Dub",
"Kvě",
"Čvn",
"Čvc",
"Srp",
"Zář",
"Říj",
"Lis",
"Pro"
],
"days": [
"Neděle",
"Pondělí",
"Úterý",
"Středa",
"Čtvrtek",
"Pátek",
"Sobota"
],
"shortDays": ["Ne", "Po", "Út", "St", "Čt", "Pá", "So"],
"toolbar": {
"exportToSVG": "Stáhnout SVG",
"exportToPNG": "Stáhnout PNG",
"exportToCSV": "Stáhnout CSV",
"menu": "Menu",
"selection": "Vybrat",
"selectionZoom": "Zoom: Vybrat",
"zoomIn": "Zoom: Přiblížit",
"zoomOut": "Zoom: Oddálit",
"pan": "Přesouvat",
"reset": "Resetovat"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "da",
"options": {
"months": [
"januar",
"februar",
"marts",
"april",
"maj",
"juni",
"juli",
"august",
"september",
"oktober",
"november",
"december"
],
"shortMonths": [
"jan",
"feb",
"mar",
"apr",
"maj",
"jun",
"jul",
"aug",
"sep",
"okt",
"nov",
"dec"
],
"days": [
"Søndag",
"Mandag",
"Tirsdag",
"Onsdag",
"Torsdag",
"Fredag",
"Lørdag"
],
"shortDays": ["Søn", "Man", "Tir", "Ons", "Tor", "Fre", "Lør"],
"toolbar": {
"exportToSVG": "Download SVG",
"exportToPNG": "Download PNG",
"exportToCSV": "Download CSV",
"menu": "Menu",
"selection": "Valg",
"selectionZoom": "Zoom til valg",
"zoomIn": "Zoom ind",
"zoomOut": "Zoom ud",
"pan": "Panorér",
"reset": "Nulstil zoom"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "de",
"options": {
"months": [
"Januar",
"Februar",
"März",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Dezember"
],
"shortMonths": [
"Jan",
"Feb",
"Mär",
"Apr",
"Mai",
"Jun",
"Jul",
"Aug",
"Sep",
"Okt",
"Nov",
"Dez"
],
"days": [
"Sonntag",
"Montag",
"Dienstag",
"Mittwoch",
"Donnerstag",
"Freitag",
"Samstag"
],
"shortDays": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
"toolbar": {
"exportToSVG": "SVG speichern",
"exportToPNG": "PNG speichern",
"exportToCSV": "CSV speichern",
"menu": "Menü",
"selection": "Auswahl",
"selectionZoom": "Auswahl vergrößern",
"zoomIn": "Vergrößern",
"zoomOut": "Verkleinern",
"pan": "Verschieben",
"reset": "Zoom zurücksetzen"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "el",
"options": {
"months": [
"Ιανουάριος",
"Φεβρουάριος",
"Μάρτιος",
"Απρίλιος",
"Μάιος",
"Ιούνιος",
"Ιούλιος",
"Αύγουστος",
"Σεπτέμβριος",
"Οκτώβριος",
"Νοέμβριος",
"Δεκέμβριος"
],
"shortMonths": [
"Ιαν",
"Φεβ",
"Μαρ",
"Απρ",
"Μάι",
"Ιουν",
"Ιουλ",
"Αυγ",
"Σεπ",
"Οκτ",
"Νοε",
"Δεκ"
],
"days": [
"Κυριακή",
"Δευτέρα",
"Τρίτη",
"Τετάρτη",
"Πέμπτη",
"Παρασκευή",
"Σάββατο"
],
"shortDays": ["Κυρ", "Δευ", "Τρι", "Τετ", "Πεμ", "Παρ", "Σαβ"],
"toolbar": {
"exportToSVG": "Λήψη SVG",
"exportToPNG": "Λήψη PNG",
"exportToCSV": "Λήψη CSV",
"menu": "Menu",
"selection": "Επιλογή",
"selectionZoom": "Μεγέθυνση βάση επιλογής",
"zoomIn": "Μεγέθυνση",
"zoomOut": "Σμίκρυνση",
"pan": "Μετατόπιση",
"reset": "Επαναφορά μεγέθυνσης"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "en",
"options": {
"months": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
"shortMonths": [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
],
"days": [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
"shortDays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
"toolbar": {
"exportToSVG": "Download SVG",
"exportToPNG": "Download PNG",
"exportToCSV": "Download CSV",
"menu": "Menu",
"selection": "Selection",
"selectionZoom": "Selection Zoom",
"zoomIn": "Zoom In",
"zoomOut": "Zoom Out",
"pan": "Panning",
"reset": "Reset Zoom"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "es",
"options": {
"months": [
"Enero",
"Febrero",
"Marzo",
"Abril",
"Mayo",
"Junio",
"Julio",
"Agosto",
"Septiembre",
"Octubre",
"Noviembre",
"Diciembre"
],
"shortMonths": [
"Ene",
"Feb",
"Mar",
"Abr",
"May",
"Jun",
"Jul",
"Ago",
"Sep",
"Oct",
"Nov",
"Dic"
],
"days": [
"Domingo",
"Lunes",
"Martes",
"Miércoles",
"Jueves",
"Viernes",
"Sábado"
],
"shortDays": ["Dom", "Lun", "Mar", "Mie", "Jue", "Vie", "Sab"],
"toolbar": {
"exportToSVG": "Descargar SVG",
"exportToPNG": "Descargar PNG",
"exportToCSV": "Descargar CSV",
"menu": "Menu",
"selection": "Seleccionar",
"selectionZoom": "Seleccionar Zoom",
"zoomIn": "Aumentar",
"zoomOut": "Disminuir",
"pan": "Navegación",
"reset": "Reiniciar Zoom"
}
}
}

View file

@ -0,0 +1,63 @@
{
"name": "et",
"options": {
"months": [
"jaanuar",
"veebruar",
"märts",
"aprill",
"mai",
"juuni",
"juuli",
"august",
"september",
"oktoober",
"november",
"detsember"
],
"shortMonths": [
"jaan",
"veebr",
"märts",
"apr",
"mai",
"juuni",
"juuli",
"aug",
"sept",
"okt",
"nov",
"dets"
],
"days": [
"pühapäev",
"esmaspäev",
"teisipäev",
"kolmapäev",
"neljapäev",
"reede",
"laupäev"
],
"shortDays": [
"P",
"E",
"T",
"K",
"N",
"R",
"L"
],
"toolbar": {
"exportToSVG": "Lae alla SVG",
"exportToPNG": "Lae alla PNG",
"exportToCSV": "Lae alla CSV",
"menu": "Menüü",
"selection": "Valik",
"selectionZoom": "Valiku suum",
"zoomIn": "Suurenda",
"zoomOut": "Vähenda",
"pan": "Panoraamimine",
"reset": "Lähtesta suum"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "fa",
"options": {
"months": [
"فروردین",
"اردیبهشت",
"خرداد",
"تیر",
"مرداد",
"شهریور",
"مهر",
"آبان",
"آذر",
"دی",
"بهمن",
"اسفند"
],
"shortMonths": [
"فرو",
"ارد",
"خرد",
"تیر",
"مرد",
"شهر",
"مهر",
"آبا",
"آذر",
"دی",
"بهمـ",
"اسفـ"
],
"days": [
"یکشنبه",
"دوشنبه",
"سه شنبه",
"چهارشنبه",
"پنجشنبه",
"جمعه",
"شنبه"
],
"shortDays": ["ی", "د", "س", "چ", "پ", "ج", "ش"],
"toolbar": {
"exportToSVG": "دانلود SVG",
"exportToPNG": "دانلود PNG",
"exportToCSV": "دانلود CSV",
"menu": "منو",
"selection": "انتخاب",
"selectionZoom": "بزرگنمایی انتخابی",
"zoomIn": "بزرگنمایی",
"zoomOut": "کوچکنمایی",
"pan": "پیمایش",
"reset": "بازنشانی بزرگنمایی"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "fi",
"options": {
"months": [
"Tammikuu",
"Helmikuu",
"Maaliskuu",
"Huhtikuu",
"Toukokuu",
"Kesäkuu",
"Heinäkuu",
"Elokuu",
"Syyskuu",
"Lokakuu",
"Marraskuu",
"Joulukuu"
],
"shortMonths": [
"Tammi",
"Helmi",
"Maalis",
"Huhti",
"Touko",
"Kesä",
"Heinä",
"Elo",
"Syys",
"Loka",
"Marras",
"Joulu"
],
"days": [
"Sunnuntai",
"Maanantai",
"Tiistai",
"Keskiviikko",
"Torstai",
"Perjantai",
"Lauantai"
],
"shortDays": ["Su", "Ma", "Ti", "Ke", "To", "Pe", "La"],
"toolbar": {
"exportToSVG": "Lataa SVG",
"exportToPNG": "Lataa PNG",
"exportToCSV": "Lataa CSV",
"menu": "Valikko",
"selection": "Valinta",
"selectionZoom": "Valinnan zoomaus",
"zoomIn": "Lähennä",
"zoomOut": "Loitonna",
"pan": "Panoroi",
"reset": "Nollaa zoomaus"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "fr",
"options": {
"months": [
"janvier",
"février",
"mars",
"avril",
"mai",
"juin",
"juillet",
"août",
"septembre",
"octobre",
"novembre",
"décembre"
],
"shortMonths": [
"janv.",
"févr.",
"mars",
"avr.",
"mai",
"juin",
"juill.",
"août",
"sept.",
"oct.",
"nov.",
"déc."
],
"days": [
"dimanche",
"lundi",
"mardi",
"mercredi",
"jeudi",
"vendredi",
"samedi"
],
"shortDays": ["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."],
"toolbar": {
"exportToSVG": "Télécharger au format SVG",
"exportToPNG": "Télécharger au format PNG",
"exportToCSV": "Télécharger au format CSV",
"menu": "Menu",
"selection": "Sélection",
"selectionZoom": "Sélection et zoom",
"zoomIn": "Zoomer",
"zoomOut": "Dézoomer",
"pan": "Navigation",
"reset": "Réinitialiser le zoom"
}
}
}

View file

@ -0,0 +1,63 @@
{
"name": "gl",
"options": {
"months": [
"Xaneiro",
"Febreiro",
"Marzo",
"Abril",
"Maio",
"Xuño",
"Xullo",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Decembro"
],
"shortMonths": [
"Xan",
"Feb",
"Mar",
"Abr",
"Mai",
"Xuñ",
"Xul",
"Ago",
"Set",
"Out",
"Nov",
"Dec"
],
"days": [
"Domingo",
"Luns",
"Martes",
"Mércores",
"Xoves",
"Venres",
"Sábado"
],
"shortDays": [
"Dom",
"Lun",
"Mar",
"Mér",
"Xov",
"Ven",
"Sáb"
],
"toolbar": {
"exportToSVG": "Descargar SVG",
"exportToPNG": "Descargar PNG",
"exportToCSV": "Descargar CSV",
"menu": "Menu",
"selection": "Seleccionar",
"selectionZoom": "Seleccionar Zoom",
"zoomIn": "Aumentar",
"zoomOut": "Disminuír",
"pan": "Navegación",
"reset": "Reiniciar Zoom"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "he",
"options": {
"months": [
"ינואר",
"פברואר",
"מרץ",
"אפריל",
"מאי",
"יוני",
"יולי",
"אוגוסט",
"ספטמבר",
"אוקטובר",
"נובמבר",
"דצמבר"
],
"shortMonths": [
"ינו׳",
"פבר׳",
"מרץ",
"אפר׳",
"מאי",
"יוני",
"יולי",
"אוג׳",
"ספט׳",
"אוק׳",
"נוב׳",
"דצמ׳"
],
"days": [
"ראשון",
"שני",
"שלישי",
"רביעי",
"חמישי",
"שישי",
"שבת"
],
"shortDays": ["א׳", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳"],
"toolbar": {
"exportToSVG": "הורד SVG",
"exportToPNG": "הורד PNG",
"exportToCSV": "הורד CSV",
"menu": "תפריט",
"selection": "בחירה",
"selectionZoom": "זום בחירה",
"zoomIn": "הגדלה",
"zoomOut": "הקטנה",
"pan": "הזזה",
"reset": "איפוס תצוגה"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "hi",
"options": {
"months": [
"जनवरी",
"फ़रवरी",
"मार्च",
"अप्रैल",
"मई",
"जून",
"जुलाई",
"अगस्त",
"सितंबर",
"अक्टूबर",
"नवंबर",
"दिसंबर"
],
"shortMonths": [
"जनवरी",
"फ़रवरी",
"मार्च",
"अप्रैल",
"मई",
"जून",
"जुलाई",
"अगस्त",
"सितंबर",
"अक्टूबर",
"नवंबर",
"दिसंबर"
],
"days": [
"रविवार",
"सोमवार",
"मंगलवार",
"बुधवार",
"गुरुवार",
"शुक्रवार",
"शनिवार"
],
"shortDays": ["रवि", "सोम", "मंगल", "बुध", "गुरु", "शुक्र", "शनि"],
"toolbar": {
"exportToSVG": "निर्यात SVG",
"exportToPNG": "निर्यात PNG",
"exportToCSV": "निर्यात CSV",
"menu": "सूची",
"selection": "चयन",
"selectionZoom": "ज़ूम करना",
"zoomIn": "ज़ूम इन",
"zoomOut": "ज़ूम आउट",
"pan": "पैनिंग",
"reset": "फिर से कायम करना"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "hr",
"options": {
"months": [
"Siječanj",
"Veljača",
"Ožujak",
"Travanj",
"Svibanj",
"Lipanj",
"Srpanj",
"Kolovoz",
"Rujan",
"Listopad",
"Studeni",
"Prosinac"
],
"shortMonths": [
"Sij",
"Velj",
"Ožu",
"Tra",
"Svi",
"Lip",
"Srp",
"Kol",
"Ruj",
"Lis",
"Stu",
"Pro"
],
"days": [
"Nedjelja",
"Ponedjeljak",
"Utorak",
"Srijeda",
"Četvrtak",
"Petak",
"Subota"
],
"shortDays": ["Ned", "Pon", "Uto", "Sri", "Čet", "Pet", "Sub"],
"toolbar": {
"exportToSVG": "Preuzmi SVG",
"exportToPNG": "Preuzmi PNG",
"exportToCSV": "Preuzmi CSV",
"menu": "Izbornik",
"selection": "Odabir",
"selectionZoom": "Odabirno povećanje",
"zoomIn": "Uvećajte prikaz",
"zoomOut": "Umanjite prikaz",
"pan": "Pomicanje",
"reset": "Povratak na zadani prikaz"
}
}
}

View file

@ -0,0 +1,64 @@
{
"name": "hu",
"options": {
"months": [
"január",
"február",
"március",
"április",
"május",
"június",
"július",
"augusztus",
"szeptember",
"október",
"november",
"december"
],
"shortMonths": [
"jan",
"feb",
"mar",
"ápr",
"máj",
"jún",
"júl",
"aug",
"szept",
"okt",
"nov",
"dec"
],
"days": [
"hétfő",
"kedd",
"szerda",
"csütörtök",
"péntek",
"szombat",
"vasárnap"
],
"shortDays": [
"H",
"K",
"Sze",
"Cs",
"P",
"Szo",
"V"
],
"toolbar": {
"exportToSVG": "Exportálás SVG-be",
"exportToPNG": "Exportálás PNG-be",
"exportToCSV": "Exportálás CSV-be",
"menu": "Fő ajánlat",
"download": "SVG letöltése",
"selection": "Kiválasztás",
"selectionZoom": "Nagyító kiválasztása",
"zoomIn": "Nagyítás",
"zoomOut": "Kicsinyítés",
"pan": "Képcsúsztatás",
"reset": "Nagyító visszaállítása"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "hy",
"options": {
"months": [
"Հունվար",
"Փետրվար",
"Մարտ",
"Ապրիլ",
"Մայիս",
"Հունիս",
"Հուլիս",
"Օգոստոս",
"Սեպտեմբեր",
"Հոկտեմբեր",
"Նոյեմբեր",
"Դեկտեմբեր"
],
"shortMonths": [
"Հնվ",
"Փտվ",
"Մրտ",
"Ապր",
"Մյս",
"Հնս",
"Հլիս",
"Օգս",
"Սեպ",
"Հոկ",
"Նոյ",
"Դեկ"
],
"days": [
"Կիրակի",
"Երկուշաբթի",
"Երեքշաբթի",
"Չորեքշաբթի",
"Հինգշաբթի",
"Ուրբաթ",
"Շաբաթ"
],
"shortDays": ["Կիր", "Երկ", "Երք", "Չրք", "Հնգ", "Ուրբ", "Շբթ"],
"toolbar": {
"exportToSVG": "Բեռնել SVG",
"exportToPNG": "Բեռնել PNG",
"exportToCSV": "Բեռնել CSV",
"menu": "Մենյու",
"selection": "Ընտրված",
"selectionZoom": "Ընտրված հատվածի խոշորացում",
"zoomIn": "Խոշորացնել",
"zoomOut": "Մանրացնել",
"pan": "Տեղափոխում",
"reset": "Բերել սկզբնական վիճակի"
}
}
}

View file

@ -0,0 +1,47 @@
{
"name": "id",
"options": {
"months": [
"Januari",
"Februari",
"Maret",
"April",
"Mei",
"Juni",
"Juli",
"Agustus",
"September",
"Oktober",
"November",
"Desember"
],
"shortMonths": [
"Jan",
"Feb",
"Mar",
"Apr",
"Mei",
"Jun",
"Jul",
"Agu",
"Sep",
"Okt",
"Nov",
"Des"
],
"days": ["Minggu", "Senin", "Selasa", "Rabu", "kamis", "Jumat", "Sabtu"],
"shortDays": ["Min", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab"],
"toolbar": {
"exportToSVG": "Unduh SVG",
"exportToPNG": "Unduh PNG",
"exportToCSV": "Unduh CSV",
"menu": "Menu",
"selection": "Pilihan",
"selectionZoom": "Perbesar Pilihan",
"zoomIn": "Perbesar",
"zoomOut": "Perkecil",
"pan": "Geser",
"reset": "Atur Ulang Zoom"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "it",
"options": {
"months": [
"Gennaio",
"Febbraio",
"Marzo",
"Aprile",
"Maggio",
"Giugno",
"Luglio",
"Agosto",
"Settembre",
"Ottobre",
"Novembre",
"Dicembre"
],
"shortMonths": [
"Gen",
"Feb",
"Mar",
"Apr",
"Mag",
"Giu",
"Lug",
"Ago",
"Set",
"Ott",
"Nov",
"Dic"
],
"days": [
"Domenica",
"Lunedì",
"Martedì",
"Mercoledì",
"Giovedì",
"Venerdì",
"Sabato"
],
"shortDays": ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"],
"toolbar": {
"exportToSVG": "Scarica SVG",
"exportToPNG": "Scarica PNG",
"exportToCSV": "Scarica CSV",
"menu": "Menu",
"selection": "Selezione",
"selectionZoom": "Seleziona Zoom",
"zoomIn": "Zoom In",
"zoomOut": "Zoom Out",
"pan": "Sposta",
"reset": "Reimposta Zoom"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "ja",
"options": {
"months": [
"1月",
"2月",
"3月",
"4月",
"5月",
"6月",
"7月",
"8月",
"9月",
"10月",
"11月",
"12月"
],
"shortMonths": [
"1月",
"2月",
"3月",
"4月",
"5月",
"6月",
"7月",
"8月",
"9月",
"10月",
"11月",
"12月"
],
"days": [
"日曜日",
"月曜日",
"火曜日",
"水曜日",
"木曜日",
"金曜日",
"土曜日"
],
"shortDays": ["日", "月", "火", "水", "木", "金", "土"],
"toolbar": {
"exportToSVG": "SVGダウンロード",
"exportToPNG": "PNGダウンロード",
"exportToCSV": "CSVダウンロード",
"menu": "メニュー",
"selection": "選択",
"selectionZoom": "選択ズーム",
"zoomIn": "拡大",
"zoomOut": "縮小",
"pan": "パン",
"reset": "ズームリセット"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "ka",
"options": {
"months": [
"იანვარი",
"თებერვალი",
"მარტი",
"აპრილი",
"მაისი",
"ივნისი",
"ივლისი",
"აგვისტო",
"სექტემბერი",
"ოქტომბერი",
"ნოემბერი",
"დეკემბერი"
],
"shortMonths": [
"იან",
"თებ",
"მარ",
"აპრ",
"მაი",
"ივნ",
"ივლ",
"აგვ",
"სექ",
"ოქტ",
"ნოე",
"დეკ"
],
"days": [
"კვირა",
"ორშაბათი",
"სამშაბათი",
"ოთხშაბათი",
"ხუთშაბათი",
"პარასკევი",
"შაბათი"
],
"shortDays": ["კვი", "ორშ", "სამ", "ოთხ", "ხუთ", "პარ", "შაბ"],
"toolbar": {
"exportToSVG": "გადმოქაჩე SVG",
"exportToPNG": "გადმოქაჩე PNG",
"exportToCSV": "გადმოქაჩე CSV",
"menu": "მენიუ",
"selection": "არჩევა",
"selectionZoom": "არჩეულის გადიდება",
"zoomIn": "გადიდება",
"zoomOut": "დაპატარაება",
"pan": "გადაჩოჩება",
"reset": "გადიდების გაუქმება"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "ko",
"options": {
"months": [
"1월",
"2월",
"3월",
"4월",
"5월",
"6월",
"7월",
"8월",
"9월",
"10월",
"11월",
"12월"
],
"shortMonths": [
"1월",
"2월",
"3월",
"4월",
"5월",
"6월",
"7월",
"8월",
"9월",
"10월",
"11월",
"12월"
],
"days": [
"일요일",
"월요일",
"화요일",
"수요일",
"목요일",
"금요일",
"토요일"
],
"shortDays": ["일", "월", "화", "수", "목", "금", "토"],
"toolbar": {
"exportToSVG": "SVG 다운로드",
"exportToPNG": "PNG 다운로드",
"exportToCSV": "CSV 다운로드",
"menu": "메뉴",
"selection": "선택",
"selectionZoom": "선택영역 확대",
"zoomIn": "확대",
"zoomOut": "축소",
"pan": "패닝",
"reset": "원래대로"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "lt",
"options": {
"months": [
"Sausis",
"Vasaris",
"Kovas",
"Balandis",
"Gegužė",
"Birželis",
"Liepa",
"Rugpjūtis",
"Rugsėjis",
"Spalis",
"Lapkritis",
"Gruodis"
],
"shortMonths": [
"Sau",
"Vas",
"Kov",
"Bal",
"Geg",
"Bir",
"Lie",
"Rgp",
"Rgs",
"Spl",
"Lap",
"Grd"
],
"days": [
"Sekmadienis",
"Pirmadienis",
"Antradienis",
"Trečiadienis",
"Ketvirtadienis",
"Penktadienis",
"Šeštadienis"
],
"shortDays": ["Sk", "Per", "An", "Tr", "Kt", "Pn", "Št"],
"toolbar": {
"exportToSVG": "Atsisiųsti SVG",
"exportToPNG": "Atsisiųsti PNG",
"exportToCSV": "Atsisiųsti CSV",
"menu": "Menu",
"selection": "Pasirinkimas",
"selectionZoom": "Zoom: Pasirinkimas",
"zoomIn": "Zoom: Priartinti",
"zoomOut": "Zoom: Atitolinti",
"pan": "Perkėlimas",
"reset": "Atstatyti"
}
}
}

View file

@ -0,0 +1,64 @@
{
"name": "lv",
"options": {
"months": [
"janvāris",
"februāris",
"marts",
"aprīlis",
"maijs",
"jūnijs",
"jūlijs",
"augusts",
"septembris",
"oktobris",
"novembris",
"decembris"
],
"shortMonths": [
"janv",
"febr",
"marts",
"apr",
"maijs",
"jūn",
"jūl",
"aug",
"sept",
"okt",
"nov",
"dec"
],
"days": [
"svētdiena",
"pirmdiena",
"otrdiena",
"trešdiena",
"ceturtdiena",
"piektdiena",
"sestdiena"
],
"shortDays": [
"Sv",
"P",
"O",
"T",
"C",
"P",
"S"
],
"toolbar": {
"exportToSVG": "Lejuplādēt SVG",
"exportToPNG": "Lejuplādēt PNG",
"exportToCSV": "Lejuplādēt CSV",
"menu": "Izvēlne",
"selection": "Atlase",
"selectionZoom": "Pietuvināt atlasi",
"zoomIn": "Pietuvināt",
"zoomOut": "Attālināt",
"pan": "Pārvietoties diagrammā",
"reset": "Atiestatīt pietuvinājumu"
}
}
}

View file

@ -0,0 +1,63 @@
{
"name": "ms",
"options": {
"months": [
"Januari",
"Februari",
"Mac",
"April",
"Mei",
"Jun",
"Julai",
"Ogos",
"September",
"Oktober",
"November",
"Disember"
],
"shortMonths": [
"Jan",
"Feb",
"Mac",
"Apr",
"Mei",
"Jun",
"Jul",
"Ogos",
"Sep",
"Okt",
"Nov",
"Dis"
],
"days": [
"Ahad",
"Isnin",
"Selasa",
"Rabu",
"Khamis",
"Jumaat",
"Sabtu"
],
"shortDays": [
"Ahd",
"Isn",
"Sel",
"Rab",
"Kha",
"Jum",
"Sab"
],
"toolbar": {
"exportToSVG": "Muat turun SVG",
"exportToPNG": "Muat turun PNG",
"exportToCSV": "Muat turun CSV",
"menu": "Menu",
"selection": "Pilihan",
"selectionZoom": "Zum Pilihan",
"zoomIn": "Zoom Masuk",
"zoomOut": "Zoom Keluar",
"pan": "Pemusingan",
"reset": "Tetapkan Semula Zum"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "nb",
"options": {
"months": [
"Januar",
"Februar",
"Mars",
"April",
"Mai",
"Juni",
"Juli",
"August",
"September",
"Oktober",
"November",
"Desember"
],
"shortMonths": [
"Jan",
"Feb",
"Mar",
"Apr",
"Mai",
"Jun",
"Jul",
"Aug",
"Sep",
"Okt",
"Nov",
"Des"
],
"days": [
"Søndag",
"Mandag",
"Tirsdag",
"Onsdag",
"Torsdag",
"Fredag",
"Lørdag"
],
"shortDays": ["Sø", "Ma", "Ti", "On", "To", "Fr", "Lø"],
"toolbar": {
"exportToSVG": "Last ned SVG",
"exportToPNG": "Last ned PNG",
"exportToCSV": "Last ned CSV",
"menu": "Menu",
"selection": "Velg",
"selectionZoom": "Zoom: Velg",
"zoomIn": "Zoome inn",
"zoomOut": "Zoome ut",
"pan": "Skyving",
"reset": "Start på nytt"
}
}
}

View file

@ -0,0 +1,63 @@
{
"name": "nl",
"options": {
"months": [
"januari",
"februari",
"maart",
"april",
"mei",
"juni",
"juli",
"augustus",
"september",
"oktober",
"november",
"december"
],
"shortMonths": [
"jan.",
"feb.",
"mrt.",
"apr.",
"mei.",
"jun.",
"jul.",
"aug.",
"sep.",
"okt.",
"nov.",
"dec."
],
"days": [
"zondag",
"maandag",
"dinsdag",
"woensdag",
"donderdag",
"vrijdag",
"zaterdag"
],
"shortDays": [
"zo.",
"ma.",
"di.",
"wo.",
"do.",
"vr.",
"za."
],
"toolbar": {
"exportToSVG": "Download SVG",
"exportToPNG": "Download PNG",
"exportToCSV": "Download CSV",
"menu": "Menu",
"selection": "Selectie",
"selectionZoom": "Zoom selectie",
"zoomIn": "Zoom in",
"zoomOut": "Zoom out",
"pan": "Verplaatsen",
"reset": "Standaardwaarden"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "pl",
"options": {
"months": [
"Styczeń",
"Luty",
"Marzec",
"Kwiecień",
"Maj",
"Czerwiec",
"Lipiec",
"Sierpień",
"Wrzesień",
"Październik",
"Listopad",
"Grudzień"
],
"shortMonths": [
"Sty",
"Lut",
"Mar",
"Kwi",
"Maj",
"Cze",
"Lip",
"Sie",
"Wrz",
"Paź",
"Lis",
"Gru"
],
"days": [
"Niedziela",
"Poniedziałek",
"Wtorek",
"Środa",
"Czwartek",
"Piątek",
"Sobota"
],
"shortDays": ["Nd", "Pn", "Wt", "Śr", "Cz", "Pt", "Sb"],
"toolbar": {
"exportToSVG": "Pobierz SVG",
"exportToPNG": "Pobierz PNG",
"exportToCSV": "Pobierz CSV",
"menu": "Menu",
"selection": "Wybieranie",
"selectionZoom": "Zoom: Wybieranie",
"zoomIn": "Zoom: Przybliż",
"zoomOut": "Zoom: Oddal",
"pan": "Przesuwanie",
"reset": "Resetuj"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "pt-br",
"options": {
"months": [
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro"
],
"shortMonths": [
"Jan",
"Fev",
"Mar",
"Abr",
"Mai",
"Jun",
"Jul",
"Ago",
"Set",
"Out",
"Nov",
"Dez"
],
"days": [
"Domingo",
"Segunda",
"Terça",
"Quarta",
"Quinta",
"Sexta",
"Sábado"
],
"shortDays": ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sab"],
"toolbar": {
"exportToSVG": "Baixar SVG",
"exportToPNG": "Baixar PNG",
"exportToCSV": "Baixar CSV",
"menu": "Menu",
"selection": "Selecionar",
"selectionZoom": "Selecionar Zoom",
"zoomIn": "Aumentar",
"zoomOut": "Diminuir",
"pan": "Navegação",
"reset": "Reiniciar Zoom"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "pt",
"options": {
"months": [
"Janeiro",
"Fevereiro",
"Março",
"Abril",
"Maio",
"Junho",
"Julho",
"Agosto",
"Setembro",
"Outubro",
"Novembro",
"Dezembro"
],
"shortMonths": [
"Jan",
"Fev",
"Mar",
"Abr",
"Mai",
"Jun",
"Jul",
"Ag",
"Set",
"Out",
"Nov",
"Dez"
],
"days": [
"Domingo",
"Segunda-feira",
"Terça-feira",
"Quarta-feira",
"Quinta-feira",
"Sexta-feira",
"Sábado"
],
"shortDays": ["Do", "Se", "Te", "Qa", "Qi", "Sx", "Sa"],
"toolbar": {
"exportToSVG": "Transferir SVG",
"exportToPNG": "Transferir PNG",
"exportToCSV": "Transferir CSV",
"menu": "Menu",
"selection": "Selecionar",
"selectionZoom": "Zoom: Selecionar",
"zoomIn": "Zoom: Aumentar",
"zoomOut": "Zoom: Diminuir",
"pan": "Deslocamento",
"reset": "Redefinir"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "ro",
"options": {
"months": [
"Ianuarie",
"Februarie",
"Martie",
"Aprilie",
"Mai",
"Iunie",
"Iulie",
"August",
"Septembrie",
"Octombrie",
"Noiembrie",
"Decembrie"
],
"shortMonths": [
"Ian",
"Feb",
"Mar",
"Apr",
"Mai",
"Iun",
"Iul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
],
"days": [
"Duminică",
"Luni",
"Marți",
"Miercuri",
"Joi",
"Vineri",
"Sâmbătă"
],
"shortDays": ["Dum", "Lun", "Mar", "Mie", "Joi", "Vin", "Sâm"],
"toolbar": {
"exportToSVG": "Descarcă SVG",
"exportToPNG": "Descarcă PNG",
"exportToCSV": "Descarcă CSV",
"menu": "Meniu",
"selection": "Selecție",
"selectionZoom": "Zoom selecție",
"zoomIn": "Mărire",
"zoomOut": "Micșorare",
"pan": "Panoramare",
"reset": "Resetare zoom"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "ru",
"options": {
"months": [
"Январь",
"Февраль",
"Март",
"Апрель",
"Май",
"Июнь",
"Июль",
"Август",
"Сентябрь",
"Октябрь",
"Ноябрь",
"Декабрь"
],
"shortMonths": [
"Янв",
"Фев",
"Мар",
"Апр",
"Май",
"Июн",
"Июл",
"Авг",
"Сен",
"Окт",
"Ноя",
"Дек"
],
"days": [
"Воскресенье",
"Понедельник",
"Вторник",
"Среда",
"Четверг",
"Пятница",
"Суббота"
],
"shortDays": ["Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб"],
"toolbar": {
"exportToSVG": "Сохранить SVG",
"exportToPNG": "Сохранить PNG",
"exportToCSV": "Сохранить CSV",
"menu": "Меню",
"selection": "Выбор",
"selectionZoom": "Выбор с увеличением",
"zoomIn": "Увеличить",
"zoomOut": "Уменьшить",
"pan": "Перемещение",
"reset": "Сбросить увеличение"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "sk",
"options": {
"months": [
"Január",
"Február",
"Marec",
"Apríl",
"Máj",
"Jún",
"Júl",
"August",
"September",
"Október",
"November",
"December"
],
"shortMonths": [
"Jan",
"Feb",
"Mar",
"Apr",
"Máj",
"Jún",
"Júl",
"Aug",
"Sep",
"Okt",
"Nov",
"Dec"
],
"days": [
"Nedeľa",
"Pondelok",
"Utorok",
"Streda",
"Štvrtok",
"Piatok",
"Sobota"
],
"shortDays": ["Ne", "Po", "Ut", "St", "Št", "Pi", "So"],
"toolbar": {
"exportToSVG": "Stiahnuť SVG",
"exportToPNG": "Stiahnuť PNG",
"exportToCSV": "Stiahnuť CSV",
"menu": "Menu",
"selection": "Vyberanie",
"selectionZoom": "Zoom: Vyberanie",
"zoomIn": "Zoom: Priblížiť",
"zoomOut": "Zoom: Vzdialiť",
"pan": "Presúvanie",
"reset": "Resetovať"
}
}
}

View file

@ -0,0 +1,55 @@
{
"name": "sl",
"options": {
"months": [
"Januar",
"Februar",
"Marec",
"April",
"Maj",
"Junij",
"Julij",
"Avgust",
"Septemer",
"Oktober",
"November",
"December"
],
"shortMonths": [
"Jan",
"Feb",
"Mar",
"Apr",
"Maj",
"Jun",
"Jul",
"Avg",
"Sep",
"Okt",
"Nov",
"Dec"
],
"days": [
"Nedelja",
"Ponedeljek",
"Torek",
"Sreda",
"Četrtek",
"Petek",
"Sobota"
],
"shortDays": ["Ne", "Po", "To", "Sr", "Če", "Pe", "So"],
"toolbar": {
"exportToSVG": "Prenesi SVG",
"exportToPNG": "Prenesi PNG",
"exportToCSV": "Prenesi CSV",
"menu": "Menu",
"selection": "Izbiranje",
"selectionZoom": "Zoom: Izbira",
"zoomIn": "Zoom: Približaj",
"zoomOut": "Zoom: Oddalji",
"pan": "Pomikanje",
"reset": "Resetiraj"
}
}
}

Some files were not shown because too many files have changed in this diff Show more