@@ -1,14 +0,0 @@ | |||||
{ | |||||
"presets": [ | |||||
["latest", { | |||||
"es2015": { | |||||
"modules": false | |||||
} | |||||
}] | |||||
], | |||||
"env": { | |||||
"test": { | |||||
"presets": ["env"] | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,9 @@ | |||||
version = 1 | |||||
[[analyzers]] | |||||
name = "javascript" | |||||
enabled = true | |||||
[analyzers.meta] | |||||
environment = ["browser"] | |||||
style_guide = "standard" |
@@ -0,0 +1,14 @@ | |||||
# Root editor config file | |||||
root = true | |||||
# Common settings | |||||
[*] | |||||
end_of_line = lf | |||||
insert_final_newline = true | |||||
trim_trailing_whitespace = true | |||||
charset = utf-8 | |||||
# indentation settings | |||||
[{*.js,*.css,*.html}] | |||||
indent_style = tab | |||||
indent_size = 4 |
@@ -30,4 +30,4 @@ | |||||
"globals": { | "globals": { | ||||
"ENV": true | "ENV": true | ||||
} | } | ||||
} | |||||
} |
@@ -60,4 +60,7 @@ typings/ | |||||
# next.js build output | # next.js build output | ||||
.next | .next | ||||
# npm build output | |||||
dist | |||||
.DS_Store | .DS_Store |
@@ -1 +0,0 @@ | |||||
.chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px} |
@@ -1,2 +0,0 @@ | |||||
plugins: | |||||
- jekyll-redirect-from |
@@ -1,99 +0,0 @@ | |||||
/* | |||||
github.com style (c) Vasily Polovnyov <vast@whiteants.net> | |||||
*/ | |||||
.hljs { | |||||
display: block; | |||||
color: #36414c; | |||||
overflow-x: auto; | |||||
padding: 0.5em; | |||||
background: #F8F8F9; | |||||
border-radius: 3px; | |||||
} | |||||
.hljs-comment, | |||||
.hljs-quote { | |||||
color: #998; | |||||
font-style: italic; | |||||
} | |||||
.hljs-keyword, | |||||
.hljs-selector-tag, | |||||
.hljs-subst { | |||||
color: #333; | |||||
font-weight: bold; | |||||
} | |||||
.hljs-number, | |||||
.hljs-literal, | |||||
.hljs-variable, | |||||
.hljs-template-variable, | |||||
.hljs-tag .hljs-attr { | |||||
color: #008080; | |||||
} | |||||
.hljs-string, | |||||
.hljs-doctag { | |||||
color: #d14; | |||||
} | |||||
.hljs-title, | |||||
.hljs-section, | |||||
.hljs-selector-id { | |||||
color: #900; | |||||
font-weight: bold; | |||||
} | |||||
.hljs-subst { | |||||
font-weight: normal; | |||||
} | |||||
.hljs-type, | |||||
.hljs-class .hljs-title { | |||||
color: #458; | |||||
font-weight: bold; | |||||
} | |||||
.hljs-tag, | |||||
.hljs-name, | |||||
.hljs-attribute { | |||||
color: #000080; | |||||
font-weight: normal; | |||||
} | |||||
.hljs-regexp, | |||||
.hljs-link { | |||||
color: #009926; | |||||
} | |||||
.hljs-symbol, | |||||
.hljs-bullet { | |||||
color: #990073; | |||||
} | |||||
.hljs-built_in, | |||||
.hljs-builtin-name { | |||||
color: #0086b3; | |||||
} | |||||
.hljs-meta { | |||||
color: #999; | |||||
font-weight: bold; | |||||
} | |||||
.hljs-deletion { | |||||
background: #fdd; | |||||
} | |||||
.hljs-addition { | |||||
background: #dfd; | |||||
} | |||||
.hljs-emphasis { | |||||
font-style: italic; | |||||
} | |||||
.hljs-strong { | |||||
font-weight: bold; | |||||
} |
@@ -1,110 +0,0 @@ | |||||
body { | |||||
/* container styles */ | |||||
max-width: 720px; | |||||
margin: auto; | |||||
font-family: "proxima-nova", sans-serif; | |||||
font-size: 15px; | |||||
color: #6c7680; | |||||
text-rendering: optimizeLegibility !important; | |||||
line-height: 1.5em; | |||||
-moz-osx-font-smoothing: grayscale; | |||||
-webkit-font-smoothing: antialiased; | |||||
} | |||||
h1, | |||||
h2, | |||||
h3, | |||||
h4, | |||||
h5, | |||||
h6, | |||||
.lead, | |||||
.page-sidebar, | |||||
.breadcrumb, | |||||
.label, | |||||
.h6, | |||||
.sans, | |||||
blockquote { | |||||
font-family: "proxima-nova", sans-serif; | |||||
color: #36414C; | |||||
} | |||||
header { | |||||
margin: 4rem 0; /* SAME 1 */ | |||||
font-size: 1.6em; | |||||
font-weight: 300; | |||||
text-align: center; | |||||
} | |||||
header .lead-text { | |||||
line-height: 3rem; | |||||
margin: 2rem 0; | |||||
} | |||||
.demo-tip { | |||||
margin-top: 1rem; /* SAME 2 */ | |||||
font-size: 1rem; | |||||
} | |||||
section { | |||||
margin: 4em 0; /* SAME 1 */ | |||||
} | |||||
h1 { | |||||
font-size: 3.5rem; | |||||
margin-bottom: 1.5rem; | |||||
} | |||||
h1, h6 { | |||||
font-weight: 700; | |||||
} | |||||
.btn { | |||||
outline: none !important; | |||||
} | |||||
.blue.button { | |||||
color: #fff; | |||||
background: #7575ff; | |||||
border: 0px; | |||||
border-bottom: 3px solid rgba(0, 0, 0, 0.2); | |||||
} | |||||
.blue.button:hover { | |||||
background: #5b5be5; | |||||
} | |||||
.large.button { | |||||
font-size: 1.33em; | |||||
padding: 12px 24px 10px; | |||||
border-bottom: 3px solid rgba(0, 0, 0, 0.2); | |||||
} | |||||
a { | |||||
color: #5E64FF; | |||||
} | |||||
a, a:focus, a:hover { | |||||
transition: color 0.3s, border 0.3s, background-color 0.3s; | |||||
} | |||||
/* BaseCSS */ | |||||
.margin-top { | |||||
margin-top: 1rem; /* SAME 2 */ | |||||
} | |||||
.mv1 { | |||||
margin: 2em 0 1em 0; | |||||
} | |||||
.border { | |||||
border: 1px solid #ddd; | |||||
border-radius: 3px; | |||||
} | |||||
/* Moon images */ | |||||
.image-container { | |||||
padding: 3px; | |||||
} | |||||
.image-container img{ | |||||
display: block; | |||||
width: 100%; | |||||
} | |||||
.content-data p { | |||||
margin-bottom: 5px; | |||||
font-size: 12px; | |||||
} | |||||
.text-center { | |||||
text-align: center; | |||||
} |
@@ -1,353 +0,0 @@ | |||||
/*! | |||||
*this reset is a copy of bootstrap's reboot.css which is inturn a fork of normalise* | |||||
* Bootstrap Reboot v4.0.0-beta.3 (https://getbootstrap.com) | |||||
* Copyright 2011-2017 The Bootstrap Authors | |||||
* Copyright 2011-2017 Twitter, Inc. | |||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) | |||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) | |||||
*/ | |||||
*, | |||||
*::before, | |||||
*::after { | |||||
box-sizing: border-box; | |||||
} | |||||
html { | |||||
font-family: sans-serif; | |||||
-webkit-text-size-adjust: 100%; | |||||
-ms-text-size-adjust: 100%; | |||||
-ms-overflow-style: scrollbar; | |||||
-webkit-tap-highlight-color: transparent; | |||||
--line-height: 3; | |||||
line-height: calc(((var(--line-height) * var(--capital-height)) - var(--valign)) * 1px); | |||||
} | |||||
@-ms-viewport { | |||||
width: device-width; | |||||
} | |||||
article, aside, dialog, figcaption, figure, footer, header, hgroup, main, nav, section { | |||||
display: block; | |||||
} | |||||
body { | |||||
margin: 0; | |||||
font-size: 1em; | |||||
font-weight: 400; | |||||
/* line-height: 1.5; */ | |||||
text-align: left; | |||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, Noto, Oxygen-Sans, "Noto Sans", Ubuntu,Cantarell, sans-serif, "Apple Color Emoji", "Noto Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; | |||||
color: #36414c; | |||||
font-weight:normal; | |||||
-webkit-text-size-adjust: 100%; | |||||
-webkit-font-feature-settings: "kern" 1; | |||||
-moz-font-feature-settings: "kern" 1; | |||||
-o-font-feature-settings: "kern" 1; | |||||
font-feature-settings: "kern" 1; | |||||
font-kerning: normal; | |||||
text-rendering: optimizeLegibility; | |||||
} | |||||
[tabindex="-1"]:focus { | |||||
outline: 0 !important; | |||||
} | |||||
hr { | |||||
box-sizing: content-box; | |||||
height: 0; | |||||
overflow: visible; | |||||
} | |||||
h1, h2, h3, h4, h5, h6 { | |||||
margin-top: 0; | |||||
margin-bottom: 1.6rem; | |||||
} | |||||
p { | |||||
margin-top: 0; | |||||
margin-bottom: 1rem; | |||||
} | |||||
abbr[title], | |||||
abbr[data-original-title] { | |||||
text-decoration: underline; | |||||
-webkit-text-decoration: underline dotted; | |||||
text-decoration: underline dotted; | |||||
cursor: help; | |||||
border-bottom: 0; | |||||
} | |||||
address { | |||||
margin-bottom: 1rem; | |||||
font-style: normal; | |||||
line-height: inherit; | |||||
} | |||||
ol, | |||||
ul, | |||||
dl { | |||||
margin-top: 0; | |||||
margin-bottom: 1rem; | |||||
} | |||||
ol ol, | |||||
ul ul, | |||||
ol ul, | |||||
ul ol { | |||||
margin-bottom: 0; | |||||
} | |||||
dt { | |||||
font-weight: 700; | |||||
} | |||||
dd { | |||||
margin-bottom: .5rem; | |||||
margin-left: 0; | |||||
} | |||||
blockquote { | |||||
margin: 0 0 1rem; | |||||
} | |||||
dfn { | |||||
font-style: italic; | |||||
} | |||||
b, | |||||
strong { | |||||
font-weight: bolder; | |||||
} | |||||
small { | |||||
font-size: 80%; | |||||
} | |||||
sub, | |||||
sup { | |||||
position: relative; | |||||
font-size: 75%; | |||||
line-height: 0; | |||||
vertical-align: baseline; | |||||
} | |||||
sub { | |||||
bottom: -.25em; | |||||
} | |||||
sup { | |||||
top: -.5em; | |||||
} | |||||
a { | |||||
color: #007bff; | |||||
text-decoration: none; | |||||
background-color: transparent; | |||||
-webkit-text-decoration-skip: objects; | |||||
} | |||||
a:hover { | |||||
color: #0056b3; | |||||
text-decoration: underline; | |||||
} | |||||
a:not([href]):not([tabindex]) { | |||||
color: inherit; | |||||
text-decoration: none; | |||||
} | |||||
a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover { | |||||
color: inherit; | |||||
text-decoration: none; | |||||
} | |||||
a:not([href]):not([tabindex]):focus { | |||||
outline: 0; | |||||
} | |||||
pre, | |||||
code, | |||||
kbd, | |||||
samp { | |||||
font-family: monospace, monospace; | |||||
font-size: 1em; | |||||
} | |||||
pre { | |||||
margin-top: 0; | |||||
margin-bottom: 1rem; | |||||
overflow: auto; | |||||
-ms-overflow-style: scrollbar; | |||||
} | |||||
figure { | |||||
margin: 0 0 1rem; | |||||
} | |||||
img { | |||||
vertical-align: middle; | |||||
border-style: none; | |||||
} | |||||
svg:not(:root) { | |||||
overflow: hidden; | |||||
} | |||||
a, | |||||
area, | |||||
button, | |||||
[role="button"], | |||||
input:not([type="range"]), | |||||
label, | |||||
select, | |||||
summary, | |||||
textarea { | |||||
-ms-touch-action: manipulation; | |||||
touch-action: manipulation; | |||||
} | |||||
table { | |||||
border-collapse: collapse; | |||||
} | |||||
caption { | |||||
padding-top: 0.75rem; | |||||
padding-bottom: 0.75rem; | |||||
color: #6c757d; | |||||
text-align: left; | |||||
caption-side: bottom; | |||||
} | |||||
th { | |||||
text-align: inherit; | |||||
} | |||||
label { | |||||
display: inline-block; | |||||
margin-bottom: .5rem; | |||||
} | |||||
button { | |||||
border-radius: 0; | |||||
} | |||||
button:focus { | |||||
outline: 1px dotted; | |||||
outline: 5px auto -webkit-focus-ring-color; | |||||
} | |||||
input, | |||||
button, | |||||
select, | |||||
optgroup, | |||||
textarea { | |||||
margin: 0; | |||||
font-family: inherit; | |||||
font-size: inherit; | |||||
line-height: inherit; | |||||
} | |||||
button, | |||||
input { | |||||
overflow: visible; | |||||
} | |||||
button, | |||||
select { | |||||
text-transform: none; | |||||
} | |||||
button, | |||||
html [type="button"], | |||||
[type="reset"], | |||||
[type="submit"] { | |||||
-webkit-appearance: button; | |||||
} | |||||
button::-moz-focus-inner, | |||||
[type="button"]::-moz-focus-inner, | |||||
[type="reset"]::-moz-focus-inner, | |||||
[type="submit"]::-moz-focus-inner { | |||||
padding: 0; | |||||
border-style: none; | |||||
} | |||||
input[type="radio"], | |||||
input[type="checkbox"] { | |||||
box-sizing: border-box; | |||||
padding: 0; | |||||
} | |||||
input[type="date"], | |||||
input[type="time"], | |||||
input[type="datetime-local"], | |||||
input[type="month"] { | |||||
-webkit-appearance: listbox; | |||||
} | |||||
textarea { | |||||
overflow: auto; | |||||
resize: vertical; | |||||
} | |||||
fieldset { | |||||
min-width: 0; | |||||
padding: 0; | |||||
margin: 0; | |||||
border: 0; | |||||
} | |||||
legend { | |||||
display: block; | |||||
width: 100%; | |||||
max-width: 100%; | |||||
padding: 0; | |||||
margin-bottom: .5rem; | |||||
font-size: 1.5rem; | |||||
line-height: inherit; | |||||
color: inherit; | |||||
white-space: normal; | |||||
} | |||||
progress { | |||||
vertical-align: baseline; | |||||
} | |||||
[type="number"]::-webkit-inner-spin-button, | |||||
[type="number"]::-webkit-outer-spin-button { | |||||
height: auto; | |||||
} | |||||
[type="search"] { | |||||
outline-offset: -2px; | |||||
-webkit-appearance: none; | |||||
} | |||||
[type="search"]::-webkit-search-cancel-button, | |||||
[type="search"]::-webkit-search-decoration { | |||||
-webkit-appearance: none; | |||||
} | |||||
::-webkit-file-upload-button { | |||||
font: inherit; | |||||
-webkit-appearance: button; | |||||
} | |||||
output { | |||||
display: inline-block; | |||||
} | |||||
summary { | |||||
display: list-item; | |||||
cursor: pointer; | |||||
} | |||||
template { | |||||
display: none; | |||||
} | |||||
[hidden] { | |||||
display: none !important; | |||||
} | |||||
/*# sourceMappingURL=bootstrap-reboot.css.map */ |
@@ -1,278 +0,0 @@ | |||||
import { MONTH_NAMES_SHORT } from "../../../src/js/utils/date-utils"; | |||||
// Composite Chart | |||||
// ================================================================================ | |||||
const reportCountList = [ | |||||
152, | |||||
222, | |||||
199, | |||||
287, | |||||
534, | |||||
709, | |||||
1179, | |||||
1256, | |||||
1632, | |||||
1856, | |||||
1850, | |||||
]; | |||||
export const lineCompositeData = { | |||||
labels: [ | |||||
"2007", | |||||
"2008", | |||||
"2009", | |||||
"2010", | |||||
"2011", | |||||
"2012", | |||||
"2013", | |||||
"2014", | |||||
"2015", | |||||
"2016", | |||||
"2017", | |||||
], | |||||
yMarkers: [ | |||||
{ | |||||
label: "Average 100 reports/month", | |||||
value: 1200, | |||||
options: { labelPos: "left" }, | |||||
}, | |||||
], | |||||
datasets: [ | |||||
{ | |||||
name: "Events", | |||||
values: reportCountList, | |||||
}, | |||||
], | |||||
}; | |||||
export const fireball_5_25 = [ | |||||
[4, 0, 3, 1, 1, 2, 1, 1, 1, 0, 1, 1], | |||||
[2, 3, 3, 2, 1, 3, 0, 1, 2, 7, 10, 4], | |||||
[5, 6, 2, 4, 0, 1, 4, 3, 0, 2, 0, 1], | |||||
[0, 2, 6, 2, 1, 1, 2, 3, 6, 3, 7, 8], | |||||
[6, 8, 7, 7, 4, 5, 6, 5, 22, 12, 10, 11], | |||||
[7, 10, 11, 7, 3, 2, 7, 7, 11, 15, 22, 20], | |||||
[13, 16, 21, 18, 19, 17, 12, 17, 31, 28, 25, 29], | |||||
[24, 14, 21, 14, 11, 15, 19, 21, 41, 22, 32, 18], | |||||
[31, 20, 30, 22, 14, 17, 21, 35, 27, 50, 117, 24], | |||||
[32, 24, 21, 27, 11, 27, 43, 37, 44, 40, 48, 32], | |||||
[31, 38, 36, 26, 23, 23, 25, 29, 26, 47, 61, 50], | |||||
]; | |||||
export const fireball_2_5 = [ | |||||
[22, 6, 6, 9, 7, 8, 6, 14, 19, 10, 8, 20], | |||||
[11, 13, 12, 8, 9, 11, 9, 13, 10, 22, 40, 24], | |||||
[20, 13, 13, 19, 13, 10, 14, 13, 20, 18, 5, 9], | |||||
[7, 13, 16, 19, 12, 11, 21, 27, 27, 24, 33, 33], | |||||
[38, 25, 28, 22, 31, 21, 35, 42, 37, 32, 46, 53], | |||||
[50, 33, 36, 34, 35, 28, 27, 52, 58, 59, 75, 69], | |||||
[54, 67, 67, 45, 66, 51, 38, 64, 90, 113, 116, 87], | |||||
[84, 52, 56, 51, 55, 46, 50, 87, 114, 83, 152, 93], | |||||
[73, 58, 59, 63, 56, 51, 83, 140, 103, 115, 265, 89], | |||||
[106, 95, 94, 71, 77, 75, 99, 136, 129, 154, 168, 156], | |||||
[81, 102, 95, 72, 58, 91, 89, 122, 124, 135, 183, 171], | |||||
]; | |||||
export const fireballOver25 = [ | |||||
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], | |||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0], | |||||
[1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0], | |||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2], | |||||
[3, 2, 1, 3, 2, 0, 2, 2, 2, 3, 0, 1], | |||||
[2, 3, 5, 2, 1, 3, 0, 2, 3, 5, 1, 4], | |||||
[7, 4, 6, 1, 9, 2, 2, 2, 20, 9, 4, 9], | |||||
[5, 6, 1, 2, 5, 4, 5, 5, 16, 9, 14, 9], | |||||
[5, 4, 7, 5, 1, 5, 3, 3, 5, 7, 22, 2], | |||||
[5, 13, 11, 6, 1, 7, 9, 8, 14, 17, 16, 3], | |||||
[8, 9, 8, 6, 4, 8, 5, 6, 14, 11, 21, 12], | |||||
]; | |||||
export const barCompositeData = { | |||||
labels: MONTH_NAMES_SHORT, | |||||
datasets: [ | |||||
{ | |||||
name: "Over 25 reports", | |||||
values: fireballOver25[9], | |||||
}, | |||||
{ | |||||
name: "5 to 25 reports", | |||||
values: fireball_5_25[9], | |||||
}, | |||||
{ | |||||
name: "2 to 5 reports", | |||||
values: fireball_2_5[9], | |||||
}, | |||||
], | |||||
}; | |||||
// Demo Chart multitype Chart | |||||
// ================================================================================ | |||||
export const typeData = { | |||||
labels: [ | |||||
"12am-3am", | |||||
"3am-6am", | |||||
"6am-9am", | |||||
"9am-12pm", | |||||
"12pm-3pm", | |||||
"3pm-6pm", | |||||
"6pm-9pm", | |||||
"9pm-12am", | |||||
], | |||||
yMarkers: [ | |||||
{ | |||||
label: "Marker", | |||||
value: 43, | |||||
options: { labelPos: "left" }, | |||||
// type: 'dashed' | |||||
}, | |||||
], | |||||
yRegions: [ | |||||
{ | |||||
label: "Region", | |||||
start: -10, | |||||
end: 50, | |||||
options: { labelPos: "right" }, | |||||
}, | |||||
], | |||||
datasets: [ | |||||
{ | |||||
name: "Some Data", | |||||
values: [18, 40, 30, 35, 8, 52, 17, -4], | |||||
axisPosition: "right", | |||||
chartType: "bar", | |||||
}, | |||||
{ | |||||
name: "Another Set", | |||||
values: [30, 50, -10, 15, 18, 32, 27, 14], | |||||
axisPosition: "right", | |||||
chartType: "bar", | |||||
}, | |||||
{ | |||||
name: "Yet Another", | |||||
values: [15, 20, -3, -15, 58, 12, -17, 37], | |||||
chartType: "line", | |||||
}, | |||||
], | |||||
}; | |||||
export const trendsData = { | |||||
labels: [ | |||||
1967, | |||||
1968, | |||||
1969, | |||||
1970, | |||||
1971, | |||||
1972, | |||||
1973, | |||||
1974, | |||||
1975, | |||||
1976, | |||||
1977, | |||||
1978, | |||||
1979, | |||||
1980, | |||||
1981, | |||||
1982, | |||||
1983, | |||||
1984, | |||||
1985, | |||||
1986, | |||||
1987, | |||||
1988, | |||||
1989, | |||||
1990, | |||||
1991, | |||||
1992, | |||||
1993, | |||||
1994, | |||||
1995, | |||||
1996, | |||||
1997, | |||||
1998, | |||||
1999, | |||||
2000, | |||||
2001, | |||||
2002, | |||||
2003, | |||||
2004, | |||||
2005, | |||||
2006, | |||||
2007, | |||||
2008, | |||||
2009, | |||||
2010, | |||||
2011, | |||||
2012, | |||||
2013, | |||||
2014, | |||||
2015, | |||||
2016, | |||||
], | |||||
datasets: [ | |||||
{ | |||||
values: [ | |||||
132.9, | |||||
150.0, | |||||
149.4, | |||||
148.0, | |||||
94.4, | |||||
97.6, | |||||
54.1, | |||||
49.2, | |||||
22.5, | |||||
18.4, | |||||
39.3, | |||||
131.0, | |||||
220.1, | |||||
218.9, | |||||
198.9, | |||||
162.4, | |||||
91.0, | |||||
60.5, | |||||
20.6, | |||||
14.8, | |||||
33.9, | |||||
123.0, | |||||
211.1, | |||||
191.8, | |||||
203.3, | |||||
133.0, | |||||
76.1, | |||||
44.9, | |||||
25.1, | |||||
11.6, | |||||
28.9, | |||||
88.3, | |||||
136.3, | |||||
173.9, | |||||
170.4, | |||||
163.6, | |||||
99.3, | |||||
65.3, | |||||
45.8, | |||||
24.7, | |||||
12.6, | |||||
4.2, | |||||
4.8, | |||||
24.9, | |||||
80.8, | |||||
84.5, | |||||
94.0, | |||||
113.3, | |||||
69.8, | |||||
39.8, | |||||
], | |||||
}, | |||||
], | |||||
}; | |||||
export const moonData = { | |||||
names: ["Ganymede", "Callisto", "Io", "Europa"], | |||||
masses: [14819000, 10759000, 8931900, 4800000], | |||||
distances: [1070.412, 1882.709, 421.7, 671.034], | |||||
diameters: [5262.4, 4820.6, 3637.4, 3121.6], | |||||
}; |
@@ -1,55 +0,0 @@ | |||||
import { lineCompositeData, barCompositeData } from './data'; | |||||
export default { | |||||
lineComposite: { | |||||
elementID: "#chart-composite-1", | |||||
options: { | |||||
title: "Fireball/Bolide Events - Yearly (reported)", | |||||
data: lineCompositeData, | |||||
type: "line", | |||||
height: 190, | |||||
colors: ["green"], | |||||
isNavigable: 1, | |||||
valuesOverPoints: 1, | |||||
lineOptions: { | |||||
dotSize: 8 | |||||
} | |||||
} | |||||
}, | |||||
barComposite: { | |||||
elementID: "#chart-composite-2", | |||||
options: { | |||||
data: barCompositeData, | |||||
type: "bar", | |||||
height: 210, | |||||
colors: ["violet", "light-blue", "#46a9f9"], | |||||
valuesOverPoints: 1, | |||||
axisOptions: { | |||||
xAxisMode: "tick", | |||||
shortenYAxisNumbers: true | |||||
}, | |||||
barOptions: { | |||||
stacked: 1 | |||||
} | |||||
} | |||||
}, | |||||
demoMain: { | |||||
elementID: "", | |||||
options: { | |||||
title: "My Awesome Chart", | |||||
data: "typeData", | |||||
type: "axis-mixed", | |||||
height: 300, | |||||
colors: ["purple", "magenta", "light-blue"], | |||||
maxSlices: 10, | |||||
tooltipOptions: { | |||||
formatTooltipX: d => (d + '').toUpperCase(), | |||||
formatTooltipY: d => d + ' pts', | |||||
} | |||||
} | |||||
} | |||||
}; |
@@ -1,375 +0,0 @@ | |||||
import { shuffle, getRandomBias } from '../../../src/js/utils/helpers'; | |||||
import { HEATMAP_COLORS_YELLOW, HEATMAP_COLORS_BLUE } from '../../../src/js/utils/constants'; | |||||
import { SEC_IN_DAY, clone, timestampToMidnight, timestampSec, addDays } from '../../../src/js/utils/date-utils'; | |||||
/* eslint-disable no-unused-vars */ | |||||
import { fireballOver25, fireball_2_5, fireball_5_25, lineCompositeData, | |||||
barCompositeData, typeData, trendsData, moonData } from './data'; | |||||
/* eslint-enable no-unused-vars */ | |||||
import demoConfig from './demoConfig'; | |||||
// import { lineComposite, barComposite } from './demoConfig'; | |||||
// ================================================================================ | |||||
let Chart = frappe.Chart; // eslint-disable-line no-undef | |||||
let lc = demoConfig.lineComposite; | |||||
let lineCompositeChart = new Chart (lc.elementID, lc.options); | |||||
let bc = demoConfig.barComposite; | |||||
let barCompositeChart = new Chart (bc.elementID, bc.options); | |||||
lineCompositeChart.parent.addEventListener('data-select', (e) => { | |||||
let i = e.index; | |||||
barCompositeChart.updateDatasets([ | |||||
fireballOver25[i], fireball_5_25[i], fireball_2_5[i] | |||||
]); | |||||
}); | |||||
// ================================================================================ | |||||
let customColors = ['purple', 'magenta', 'light-blue']; | |||||
let typeChartArgs = { | |||||
title: "My Awesome Chart", | |||||
data: typeData, | |||||
type: 'axis-mixed', | |||||
height: 300, | |||||
colors: customColors, | |||||
// maxLegendPoints: 6, | |||||
maxSlices: 10, | |||||
tooltipOptions: { | |||||
formatTooltipX: d => (d + '').toUpperCase(), | |||||
formatTooltipY: d => d + ' pts', | |||||
} | |||||
}; | |||||
let aggrChart = new Chart("#chart-aggr", typeChartArgs); | |||||
Array.prototype.slice.call( | |||||
document.querySelectorAll('.aggr-type-buttons button') | |||||
).map(el => { | |||||
el.addEventListener('click', (e) => { | |||||
let btn = e.target; | |||||
let type = btn.getAttribute('data-type'); | |||||
typeChartArgs.type = type; | |||||
if(type !== 'axis-mixed') { | |||||
typeChartArgs.colors = undefined; | |||||
} else { | |||||
typeChartArgs.colors = customColors; | |||||
} | |||||
if(type !== 'percentage') { | |||||
typeChartArgs.height = 300; | |||||
} else { | |||||
typeChartArgs.height = undefined; | |||||
} | |||||
let newChart = new Chart("#chart-aggr", typeChartArgs); | |||||
if(newChart){ | |||||
aggrChart = newChart; | |||||
} | |||||
Array.prototype.slice.call( | |||||
btn.parentNode.querySelectorAll('button')).map(el => { | |||||
el.classList.remove('active'); | |||||
}); | |||||
btn.classList.add('active'); | |||||
}); | |||||
}); | |||||
document.querySelector('.export-aggr').addEventListener('click', () => { | |||||
aggrChart.export(); | |||||
}); | |||||
// Update values chart | |||||
// ================================================================================ | |||||
let updateDataAllLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", | |||||
"Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", | |||||
"Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon"]; | |||||
let getRandom = () => Math.floor(getRandomBias(-40, 60, 0.8, 1)); | |||||
let updateDataAllValues = Array.from({length: 30}, getRandom); | |||||
// We're gonna be shuffling this | |||||
let updateDataAllIndices = updateDataAllLabels.map((d,i) => i); | |||||
let getUpdateData = (source_array, length=10) => { | |||||
let indices = updateDataAllIndices.slice(0, length); | |||||
return indices.map((index) => source_array[index]); | |||||
}; | |||||
let updateData = { | |||||
labels: getUpdateData(updateDataAllLabels), | |||||
datasets: [{ | |||||
"values": getUpdateData(updateDataAllValues) | |||||
}], | |||||
yMarkers: [ | |||||
{ | |||||
label: "Altitude", | |||||
value: 25, | |||||
type: 'dashed' | |||||
} | |||||
], | |||||
yRegions: [ | |||||
{ | |||||
label: "Range", | |||||
start: 10, | |||||
end: 45 | |||||
}, | |||||
], | |||||
}; | |||||
let updateChart = new Chart("#chart-update", { | |||||
data: updateData, | |||||
type: 'line', | |||||
height: 300, | |||||
colors: ['#ff6c03'], | |||||
lineOptions: { | |||||
// hideLine: 1, | |||||
regionFill: 1 | |||||
}, | |||||
}); | |||||
let chartUpdateButtons = document.querySelector('.chart-update-buttons'); | |||||
chartUpdateButtons.querySelector('[data-update="random"]').addEventListener("click", () => { | |||||
shuffle(updateDataAllIndices); | |||||
let value = getRandom(); | |||||
let start = getRandom(); | |||||
let end = getRandom(); | |||||
let data = { | |||||
labels: updateDataAllLabels.slice(0, 10), | |||||
datasets: [{values: getUpdateData(updateDataAllValues)}], | |||||
yMarkers: [ | |||||
{ | |||||
label: "Altitude", | |||||
value: value, | |||||
type: 'dashed' | |||||
} | |||||
], | |||||
yRegions: [ | |||||
{ | |||||
label: "Range", | |||||
start: start, | |||||
end: end | |||||
}, | |||||
], | |||||
}; | |||||
updateChart.update(data); | |||||
}); | |||||
chartUpdateButtons.querySelector('[data-update="add"]').addEventListener("click", () => { | |||||
let index = updateChart.state.datasetLength; // last index to add | |||||
if(index >= updateDataAllIndices.length) return; | |||||
updateChart.addDataPoint( | |||||
updateDataAllLabels[index], [updateDataAllValues[index]] | |||||
); | |||||
}); | |||||
chartUpdateButtons.querySelector('[data-update="remove"]').addEventListener("click", () => { | |||||
updateChart.removeDataPoint(); | |||||
}); | |||||
document.querySelector('.export-update').addEventListener('click', () => { | |||||
updateChart.export(); | |||||
}); | |||||
// Trends Chart | |||||
// ================================================================================ | |||||
let plotChartArgs = { | |||||
title: "Mean Total Sunspot Count - Yearly", | |||||
data: trendsData, | |||||
type: 'line', | |||||
height: 300, | |||||
colors: ['#238e38'], | |||||
lineOptions: { | |||||
hideDots: 1, | |||||
heatline: 1, | |||||
}, | |||||
axisOptions: { | |||||
xAxisMode: 'tick', | |||||
yAxisMode: 'span', | |||||
xIsSeries: 1 | |||||
} | |||||
}; | |||||
let trendsChart = new Chart("#chart-trends", plotChartArgs); | |||||
Array.prototype.slice.call( | |||||
document.querySelectorAll('.chart-plot-buttons button') | |||||
).map(el => { | |||||
el.addEventListener('click', (e) => { | |||||
let btn = e.target; | |||||
let type = btn.getAttribute('data-type'); | |||||
let config = {}; | |||||
config[type] = 1; | |||||
if(['regionFill', 'heatline'].includes(type)) { | |||||
config.hideDots = 1; | |||||
} | |||||
// plotChartArgs.init = false; | |||||
plotChartArgs.lineOptions = config; | |||||
new Chart("#chart-trends", plotChartArgs); | |||||
Array.prototype.slice.call( | |||||
btn.parentNode.querySelectorAll('button')).map(el => { | |||||
el.classList.remove('active'); | |||||
}); | |||||
btn.classList.add('active'); | |||||
}); | |||||
}); | |||||
document.querySelector('.export-trends').addEventListener('click', () => { | |||||
trendsChart.export(); | |||||
}); | |||||
// Event chart | |||||
// ================================================================================ | |||||
let eventsData = { | |||||
labels: ["Ganymede", "Callisto", "Io", "Europa"], | |||||
datasets: [ | |||||
{ | |||||
"values": moonData.distances, | |||||
"formatted": moonData.distances.map(d => d*1000 + " km") | |||||
} | |||||
] | |||||
}; | |||||
let eventsChart = new Chart("#chart-events", { | |||||
title: "Jupiter's Moons: Semi-major Axis (1000 km)", | |||||
data: eventsData, | |||||
type: 'bar', | |||||
height: 330, | |||||
colors: ['grey'], | |||||
isNavigable: 1, | |||||
}); | |||||
let dataDiv = document.querySelector('.chart-events-data'); | |||||
eventsChart.parent.addEventListener('data-select', (e) => { | |||||
let name = moonData.names[e.index]; | |||||
dataDiv.querySelector('.moon-name').innerHTML = name; | |||||
dataDiv.querySelector('.semi-major-axis').innerHTML = moonData.distances[e.index] * 1000; | |||||
dataDiv.querySelector('.mass').innerHTML = moonData.masses[e.index]; | |||||
dataDiv.querySelector('.diameter').innerHTML = moonData.diameters[e.index]; | |||||
dataDiv.querySelector('img').src = "./assets/img/" + name.toLowerCase() + ".jpg"; | |||||
}); | |||||
// Heatmap | |||||
// ================================================================================ | |||||
let today = new Date(); | |||||
let start = clone(today); | |||||
addDays(start, 4); | |||||
let end = clone(start); | |||||
start.setFullYear( start.getFullYear() - 2 ); | |||||
end.setFullYear( end.getFullYear() - 1 ); | |||||
let dataPoints = {}; | |||||
let startTs = timestampSec(start); | |||||
let endTs = timestampSec(end); | |||||
startTs = timestampToMidnight(startTs); | |||||
endTs = timestampToMidnight(endTs, true); | |||||
while (startTs < endTs) { | |||||
dataPoints[parseInt(startTs)] = Math.floor(getRandomBias(0, 5, 0.2, 1)); | |||||
startTs += SEC_IN_DAY; | |||||
} | |||||
const heatmapData = { | |||||
dataPoints: dataPoints, | |||||
start: start, | |||||
end: end | |||||
}; | |||||
let heatmapArgs = { | |||||
title: "Monthly Distribution", | |||||
data: heatmapData, | |||||
type: 'heatmap', | |||||
discreteDomains: 1, | |||||
countLabel: 'Level', | |||||
colors: HEATMAP_COLORS_BLUE, | |||||
legendScale: [0, 1, 2, 4, 5] | |||||
}; | |||||
let heatmapChart = new Chart("#chart-heatmap", heatmapArgs); | |||||
Array.prototype.slice.call( | |||||
document.querySelectorAll('.heatmap-mode-buttons button') | |||||
).map(el => { | |||||
el.addEventListener('click', (e) => { | |||||
let btn = e.target; | |||||
let mode = btn.getAttribute('data-mode'); | |||||
let discreteDomains = 0; | |||||
if(mode === 'discrete') { | |||||
discreteDomains = 1; | |||||
} | |||||
let colors = []; | |||||
let colors_mode = document | |||||
.querySelector('.heatmap-color-buttons .active') | |||||
.getAttribute('data-color'); | |||||
if(colors_mode === 'halloween') { | |||||
colors = HEATMAP_COLORS_YELLOW; | |||||
} else if (colors_mode === 'blue') { | |||||
colors = HEATMAP_COLORS_BLUE; | |||||
} | |||||
heatmapArgs.discreteDomains = discreteDomains; | |||||
heatmapArgs.colors = colors; | |||||
new Chart("#chart-heatmap", heatmapArgs); | |||||
Array.prototype.slice.call( | |||||
btn.parentNode.querySelectorAll('button')).map(el => { | |||||
el.classList.remove('active'); | |||||
}); | |||||
btn.classList.add('active'); | |||||
}); | |||||
}); | |||||
Array.prototype.slice.call( | |||||
document.querySelectorAll('.heatmap-color-buttons button') | |||||
).map(el => { | |||||
el.addEventListener('click', (e) => { | |||||
let btn = e.target; | |||||
let colors_mode = btn.getAttribute('data-color'); | |||||
let colors = []; | |||||
if(colors_mode === 'halloween') { | |||||
colors = HEATMAP_COLORS_YELLOW; | |||||
} else if (colors_mode === 'blue') { | |||||
colors = HEATMAP_COLORS_BLUE; | |||||
} | |||||
let discreteDomains = 1; | |||||
let view_mode = document | |||||
.querySelector('.heatmap-mode-buttons .active') | |||||
.getAttribute('data-mode'); | |||||
if(view_mode === 'continuous') { | |||||
discreteDomains = 0; | |||||
} | |||||
heatmapArgs.discreteDomains = discreteDomains; | |||||
heatmapArgs.colors = colors; | |||||
new Chart("#chart-heatmap", heatmapArgs); | |||||
Array.prototype.slice.call( | |||||
btn.parentNode.querySelectorAll('button')).map(el => { | |||||
el.classList.remove('active'); | |||||
}); | |||||
btn.classList.add('active'); | |||||
}); | |||||
}); | |||||
document.querySelector('.export-heatmap').addEventListener('click', () => { | |||||
heatmapChart.export(); | |||||
}); |
@@ -1,648 +0,0 @@ | |||||
(function () { | |||||
'use strict'; | |||||
// Fixed 5-color theme, | |||||
// More colors are difficult to parse visually | |||||
var HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e']; | |||||
var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | |||||
// Universal constants | |||||
/** | |||||
* Returns the value of a number upto 2 decimal places. | |||||
* @param {Number} d Any number | |||||
*/ | |||||
/** | |||||
* Returns whether or not two given arrays are equal. | |||||
* @param {Array} arr1 First array | |||||
* @param {Array} arr2 Second array | |||||
*/ | |||||
/** | |||||
* Shuffles array in place. ES6 version | |||||
* @param {Array} array An array containing the items. | |||||
*/ | |||||
function shuffle(array) { | |||||
// Awesomeness: https://bost.ocks.org/mike/shuffle/ | |||||
// https://stackoverflow.com/a/2450976/6495043 | |||||
// https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array?noredirect=1&lq=1 | |||||
for (var i = array.length - 1; i > 0; i--) { | |||||
var j = Math.floor(Math.random() * (i + 1)); | |||||
var _ref = [array[j], array[i]]; | |||||
array[i] = _ref[0]; | |||||
array[j] = _ref[1]; | |||||
} | |||||
return array; | |||||
} | |||||
/** | |||||
* Fill an array with extra points | |||||
* @param {Array} array Array | |||||
* @param {Number} count number of filler elements | |||||
* @param {Object} element element to fill with | |||||
* @param {Boolean} start fill at start? | |||||
*/ | |||||
/** | |||||
* Returns pixel width of string. | |||||
* @param {String} string | |||||
* @param {Number} charWidth Width of single char in pixels | |||||
*/ | |||||
// https://stackoverflow.com/a/29325222 | |||||
function getRandomBias(min, max, bias, influence) { | |||||
var range = max - min; | |||||
var biasValue = range * bias + min; | |||||
var rnd = Math.random() * range + min, | |||||
// random in range | |||||
mix = Math.random() * influence; // random mixer | |||||
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias | |||||
} | |||||
/** | |||||
* Check if a number is valid for svg attributes | |||||
* @param {object} candidate Candidate to test | |||||
* @param {Boolean} nonNegative flag to treat negative number as invalid | |||||
*/ | |||||
/** | |||||
* Round a number to the closes precision, max max precision 4 | |||||
* @param {Number} d Any Number | |||||
*/ | |||||
// Playing around with dates | |||||
var NO_OF_MILLIS = 1000; | |||||
var SEC_IN_DAY = 86400; | |||||
var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; | |||||
function clone(date) { | |||||
return new Date(date.getTime()); | |||||
} | |||||
function timestampSec(date) { | |||||
return date.getTime() / NO_OF_MILLIS; | |||||
} | |||||
function timestampToMidnight(timestamp) { | |||||
var roundAhead = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; | |||||
var midnightTs = Math.floor(timestamp - timestamp % SEC_IN_DAY); | |||||
if (roundAhead) { | |||||
return midnightTs + SEC_IN_DAY; | |||||
} | |||||
return midnightTs; | |||||
} | |||||
// export function getMonthsBetween(startDate, endDate) {} | |||||
// mutates | |||||
// mutates | |||||
function addDays(date, numberOfDays) { | |||||
date.setDate(date.getDate() + numberOfDays); | |||||
} | |||||
// Composite Chart | |||||
// ================================================================================ | |||||
var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850]; | |||||
var lineCompositeData = { | |||||
labels: ["2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017"], | |||||
yMarkers: [{ | |||||
label: "Average 100 reports/month", | |||||
value: 1200, | |||||
options: { labelPos: "left" } | |||||
}], | |||||
datasets: [{ | |||||
name: "Events", | |||||
values: reportCountList | |||||
}] | |||||
}; | |||||
var fireball_5_25 = [[4, 0, 3, 1, 1, 2, 1, 1, 1, 0, 1, 1], [2, 3, 3, 2, 1, 3, 0, 1, 2, 7, 10, 4], [5, 6, 2, 4, 0, 1, 4, 3, 0, 2, 0, 1], [0, 2, 6, 2, 1, 1, 2, 3, 6, 3, 7, 8], [6, 8, 7, 7, 4, 5, 6, 5, 22, 12, 10, 11], [7, 10, 11, 7, 3, 2, 7, 7, 11, 15, 22, 20], [13, 16, 21, 18, 19, 17, 12, 17, 31, 28, 25, 29], [24, 14, 21, 14, 11, 15, 19, 21, 41, 22, 32, 18], [31, 20, 30, 22, 14, 17, 21, 35, 27, 50, 117, 24], [32, 24, 21, 27, 11, 27, 43, 37, 44, 40, 48, 32], [31, 38, 36, 26, 23, 23, 25, 29, 26, 47, 61, 50]]; | |||||
var fireball_2_5 = [[22, 6, 6, 9, 7, 8, 6, 14, 19, 10, 8, 20], [11, 13, 12, 8, 9, 11, 9, 13, 10, 22, 40, 24], [20, 13, 13, 19, 13, 10, 14, 13, 20, 18, 5, 9], [7, 13, 16, 19, 12, 11, 21, 27, 27, 24, 33, 33], [38, 25, 28, 22, 31, 21, 35, 42, 37, 32, 46, 53], [50, 33, 36, 34, 35, 28, 27, 52, 58, 59, 75, 69], [54, 67, 67, 45, 66, 51, 38, 64, 90, 113, 116, 87], [84, 52, 56, 51, 55, 46, 50, 87, 114, 83, 152, 93], [73, 58, 59, 63, 56, 51, 83, 140, 103, 115, 265, 89], [106, 95, 94, 71, 77, 75, 99, 136, 129, 154, 168, 156], [81, 102, 95, 72, 58, 91, 89, 122, 124, 135, 183, 171]]; | |||||
var fireballOver25 = [ | |||||
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | |||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2], [3, 2, 1, 3, 2, 0, 2, 2, 2, 3, 0, 1], [2, 3, 5, 2, 1, 3, 0, 2, 3, 5, 1, 4], [7, 4, 6, 1, 9, 2, 2, 2, 20, 9, 4, 9], [5, 6, 1, 2, 5, 4, 5, 5, 16, 9, 14, 9], [5, 4, 7, 5, 1, 5, 3, 3, 5, 7, 22, 2], [5, 13, 11, 6, 1, 7, 9, 8, 14, 17, 16, 3], [8, 9, 8, 6, 4, 8, 5, 6, 14, 11, 21, 12]]; | |||||
var barCompositeData = { | |||||
labels: MONTH_NAMES_SHORT, | |||||
datasets: [{ | |||||
name: "Over 25 reports", | |||||
values: fireballOver25[9] | |||||
}, { | |||||
name: "5 to 25 reports", | |||||
values: fireball_5_25[9] | |||||
}, { | |||||
name: "2 to 5 reports", | |||||
values: fireball_2_5[9] | |||||
}] | |||||
}; | |||||
// Demo Chart multitype Chart | |||||
// ================================================================================ | |||||
var typeData = { | |||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm", "12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"], | |||||
yMarkers: [{ | |||||
label: "Marker", | |||||
value: 43, | |||||
options: { labelPos: "left" } | |||||
// type: 'dashed' | |||||
}], | |||||
yRegions: [{ | |||||
label: "Region", | |||||
start: -10, | |||||
end: 50, | |||||
options: { labelPos: "right" } | |||||
}], | |||||
datasets: [{ | |||||
name: "Some Data", | |||||
values: [18, 40, 30, 35, 8, 52, 17, -4], | |||||
axisPosition: "right", | |||||
chartType: "bar" | |||||
}, { | |||||
name: "Another Set", | |||||
values: [30, 50, -10, 15, 18, 32, 27, 14], | |||||
axisPosition: "right", | |||||
chartType: "bar" | |||||
}, { | |||||
name: "Yet Another", | |||||
values: [15, 20, -3, -15, 58, 12, -17, 37], | |||||
chartType: "line" | |||||
}] | |||||
}; | |||||
var trendsData = { | |||||
labels: [1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016], | |||||
datasets: [{ | |||||
values: [132.9, 150.0, 149.4, 148.0, 94.4, 97.6, 54.1, 49.2, 22.5, 18.4, 39.3, 131.0, 220.1, 218.9, 198.9, 162.4, 91.0, 60.5, 20.6, 14.8, 33.9, 123.0, 211.1, 191.8, 203.3, 133.0, 76.1, 44.9, 25.1, 11.6, 28.9, 88.3, 136.3, 173.9, 170.4, 163.6, 99.3, 65.3, 45.8, 24.7, 12.6, 4.2, 4.8, 24.9, 80.8, 84.5, 94.0, 113.3, 69.8, 39.8] | |||||
}] | |||||
}; | |||||
var moonData = { | |||||
names: ["Ganymede", "Callisto", "Io", "Europa"], | |||||
masses: [14819000, 10759000, 8931900, 4800000], | |||||
distances: [1070.412, 1882.709, 421.7, 671.034], | |||||
diameters: [5262.4, 4820.6, 3637.4, 3121.6] | |||||
}; | |||||
var demoConfig = { | |||||
lineComposite: { | |||||
elementID: "#chart-composite-1", | |||||
options: { | |||||
title: "Fireball/Bolide Events - Yearly (reported)", | |||||
data: lineCompositeData, | |||||
type: "line", | |||||
height: 190, | |||||
colors: ["green"], | |||||
isNavigable: 1, | |||||
valuesOverPoints: 1, | |||||
lineOptions: { | |||||
dotSize: 8 | |||||
} | |||||
} | |||||
}, | |||||
barComposite: { | |||||
elementID: "#chart-composite-2", | |||||
options: { | |||||
data: barCompositeData, | |||||
type: "bar", | |||||
height: 210, | |||||
colors: ["violet", "light-blue", "#46a9f9"], | |||||
valuesOverPoints: 1, | |||||
axisOptions: { | |||||
xAxisMode: "tick", | |||||
shortenYAxisNumbers: true | |||||
}, | |||||
barOptions: { | |||||
stacked: 1 | |||||
} | |||||
} | |||||
}, | |||||
demoMain: { | |||||
elementID: "", | |||||
options: { | |||||
title: "My Awesome Chart", | |||||
data: "typeData", | |||||
type: "axis-mixed", | |||||
height: 300, | |||||
colors: ["purple", "magenta", "light-blue"], | |||||
maxSlices: 10, | |||||
tooltipOptions: { | |||||
formatTooltipX: function formatTooltipX(d) { | |||||
return (d + '').toUpperCase(); | |||||
}, | |||||
formatTooltipY: function formatTooltipY(d) { | |||||
return d + ' pts'; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
}; | |||||
/* eslint-disable no-unused-vars */ | |||||
/* eslint-enable no-unused-vars */ | |||||
// import { lineComposite, barComposite } from './demoConfig'; | |||||
// ================================================================================ | |||||
var Chart = frappe.Chart; // eslint-disable-line no-undef | |||||
var lc = demoConfig.lineComposite; | |||||
var lineCompositeChart = new Chart(lc.elementID, lc.options); | |||||
var bc = demoConfig.barComposite; | |||||
var barCompositeChart = new Chart(bc.elementID, bc.options); | |||||
lineCompositeChart.parent.addEventListener('data-select', function (e) { | |||||
var i = e.index; | |||||
barCompositeChart.updateDatasets([fireballOver25[i], fireball_5_25[i], fireball_2_5[i]]); | |||||
}); | |||||
// ================================================================================ | |||||
var customColors = ['purple', 'magenta', 'light-blue']; | |||||
var typeChartArgs = { | |||||
title: "My Awesome Chart", | |||||
data: typeData, | |||||
type: 'axis-mixed', | |||||
height: 300, | |||||
colors: customColors, | |||||
// maxLegendPoints: 6, | |||||
maxSlices: 10, | |||||
tooltipOptions: { | |||||
formatTooltipX: function formatTooltipX(d) { | |||||
return (d + '').toUpperCase(); | |||||
}, | |||||
formatTooltipY: function formatTooltipY(d) { | |||||
return d + ' pts'; | |||||
} | |||||
} | |||||
}; | |||||
var aggrChart = new Chart("#chart-aggr", typeChartArgs); | |||||
Array.prototype.slice.call(document.querySelectorAll('.aggr-type-buttons button')).map(function (el) { | |||||
el.addEventListener('click', function (e) { | |||||
var btn = e.target; | |||||
var type = btn.getAttribute('data-type'); | |||||
typeChartArgs.type = type; | |||||
if (type !== 'axis-mixed') { | |||||
typeChartArgs.colors = undefined; | |||||
} else { | |||||
typeChartArgs.colors = customColors; | |||||
} | |||||
if (type !== 'percentage') { | |||||
typeChartArgs.height = 300; | |||||
} else { | |||||
typeChartArgs.height = undefined; | |||||
} | |||||
var newChart = new Chart("#chart-aggr", typeChartArgs); | |||||
if (newChart) { | |||||
aggrChart = newChart; | |||||
} | |||||
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) { | |||||
el.classList.remove('active'); | |||||
}); | |||||
btn.classList.add('active'); | |||||
}); | |||||
}); | |||||
document.querySelector('.export-aggr').addEventListener('click', function () { | |||||
aggrChart.export(); | |||||
}); | |||||
// Update values chart | |||||
// ================================================================================ | |||||
var updateDataAllLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon"]; | |||||
var getRandom = function getRandom() { | |||||
return Math.floor(getRandomBias(-40, 60, 0.8, 1)); | |||||
}; | |||||
var updateDataAllValues = Array.from({ length: 30 }, getRandom); | |||||
// We're gonna be shuffling this | |||||
var updateDataAllIndices = updateDataAllLabels.map(function (d, i) { | |||||
return i; | |||||
}); | |||||
var getUpdateData = function getUpdateData(source_array) { | |||||
var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10; | |||||
var indices = updateDataAllIndices.slice(0, length); | |||||
return indices.map(function (index) { | |||||
return source_array[index]; | |||||
}); | |||||
}; | |||||
var updateData = { | |||||
labels: getUpdateData(updateDataAllLabels), | |||||
datasets: [{ | |||||
"values": getUpdateData(updateDataAllValues) | |||||
}], | |||||
yMarkers: [{ | |||||
label: "Altitude", | |||||
value: 25, | |||||
type: 'dashed' | |||||
}], | |||||
yRegions: [{ | |||||
label: "Range", | |||||
start: 10, | |||||
end: 45 | |||||
}] | |||||
}; | |||||
var updateChart = new Chart("#chart-update", { | |||||
data: updateData, | |||||
type: 'line', | |||||
height: 300, | |||||
colors: ['#ff6c03'], | |||||
lineOptions: { | |||||
// hideLine: 1, | |||||
regionFill: 1 | |||||
} | |||||
}); | |||||
var chartUpdateButtons = document.querySelector('.chart-update-buttons'); | |||||
chartUpdateButtons.querySelector('[data-update="random"]').addEventListener("click", function () { | |||||
shuffle(updateDataAllIndices); | |||||
var value = getRandom(); | |||||
var start = getRandom(); | |||||
var end = getRandom(); | |||||
var data = { | |||||
labels: updateDataAllLabels.slice(0, 10), | |||||
datasets: [{ values: getUpdateData(updateDataAllValues) }], | |||||
yMarkers: [{ | |||||
label: "Altitude", | |||||
value: value, | |||||
type: 'dashed' | |||||
}], | |||||
yRegions: [{ | |||||
label: "Range", | |||||
start: start, | |||||
end: end | |||||
}] | |||||
}; | |||||
updateChart.update(data); | |||||
}); | |||||
chartUpdateButtons.querySelector('[data-update="add"]').addEventListener("click", function () { | |||||
var index = updateChart.state.datasetLength; // last index to add | |||||
if (index >= updateDataAllIndices.length) return; | |||||
updateChart.addDataPoint(updateDataAllLabels[index], [updateDataAllValues[index]]); | |||||
}); | |||||
chartUpdateButtons.querySelector('[data-update="remove"]').addEventListener("click", function () { | |||||
updateChart.removeDataPoint(); | |||||
}); | |||||
document.querySelector('.export-update').addEventListener('click', function () { | |||||
updateChart.export(); | |||||
}); | |||||
// Trends Chart | |||||
// ================================================================================ | |||||
var plotChartArgs = { | |||||
title: "Mean Total Sunspot Count - Yearly", | |||||
data: trendsData, | |||||
type: 'line', | |||||
height: 300, | |||||
colors: ['#238e38'], | |||||
lineOptions: { | |||||
hideDots: 1, | |||||
heatline: 1 | |||||
}, | |||||
axisOptions: { | |||||
xAxisMode: 'tick', | |||||
yAxisMode: 'span', | |||||
xIsSeries: 1 | |||||
} | |||||
}; | |||||
var trendsChart = new Chart("#chart-trends", plotChartArgs); | |||||
Array.prototype.slice.call(document.querySelectorAll('.chart-plot-buttons button')).map(function (el) { | |||||
el.addEventListener('click', function (e) { | |||||
var btn = e.target; | |||||
var type = btn.getAttribute('data-type'); | |||||
var config = {}; | |||||
config[type] = 1; | |||||
if (['regionFill', 'heatline'].includes(type)) { | |||||
config.hideDots = 1; | |||||
} | |||||
// plotChartArgs.init = false; | |||||
plotChartArgs.lineOptions = config; | |||||
new Chart("#chart-trends", plotChartArgs); | |||||
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) { | |||||
el.classList.remove('active'); | |||||
}); | |||||
btn.classList.add('active'); | |||||
}); | |||||
}); | |||||
document.querySelector('.export-trends').addEventListener('click', function () { | |||||
trendsChart.export(); | |||||
}); | |||||
// Event chart | |||||
// ================================================================================ | |||||
var eventsData = { | |||||
labels: ["Ganymede", "Callisto", "Io", "Europa"], | |||||
datasets: [{ | |||||
"values": moonData.distances, | |||||
"formatted": moonData.distances.map(function (d) { | |||||
return d * 1000 + " km"; | |||||
}) | |||||
}] | |||||
}; | |||||
var eventsChart = new Chart("#chart-events", { | |||||
title: "Jupiter's Moons: Semi-major Axis (1000 km)", | |||||
data: eventsData, | |||||
type: 'bar', | |||||
height: 330, | |||||
colors: ['grey'], | |||||
isNavigable: 1 | |||||
}); | |||||
var dataDiv = document.querySelector('.chart-events-data'); | |||||
eventsChart.parent.addEventListener('data-select', function (e) { | |||||
var name = moonData.names[e.index]; | |||||
dataDiv.querySelector('.moon-name').innerHTML = name; | |||||
dataDiv.querySelector('.semi-major-axis').innerHTML = moonData.distances[e.index] * 1000; | |||||
dataDiv.querySelector('.mass').innerHTML = moonData.masses[e.index]; | |||||
dataDiv.querySelector('.diameter').innerHTML = moonData.diameters[e.index]; | |||||
dataDiv.querySelector('img').src = "./assets/img/" + name.toLowerCase() + ".jpg"; | |||||
}); | |||||
// Heatmap | |||||
// ================================================================================ | |||||
var today = new Date(); | |||||
var start = clone(today); | |||||
addDays(start, 4); | |||||
var end = clone(start); | |||||
start.setFullYear(start.getFullYear() - 2); | |||||
end.setFullYear(end.getFullYear() - 1); | |||||
var dataPoints = {}; | |||||
var startTs = timestampSec(start); | |||||
var endTs = timestampSec(end); | |||||
startTs = timestampToMidnight(startTs); | |||||
endTs = timestampToMidnight(endTs, true); | |||||
while (startTs < endTs) { | |||||
dataPoints[parseInt(startTs)] = Math.floor(getRandomBias(0, 5, 0.2, 1)); | |||||
startTs += SEC_IN_DAY; | |||||
} | |||||
var heatmapData = { | |||||
dataPoints: dataPoints, | |||||
start: start, | |||||
end: end | |||||
}; | |||||
var heatmapArgs = { | |||||
title: "Monthly Distribution", | |||||
data: heatmapData, | |||||
type: 'heatmap', | |||||
discreteDomains: 1, | |||||
countLabel: 'Level', | |||||
colors: HEATMAP_COLORS_BLUE, | |||||
legendScale: [0, 1, 2, 4, 5] | |||||
}; | |||||
var heatmapChart = new Chart("#chart-heatmap", heatmapArgs); | |||||
Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons button')).map(function (el) { | |||||
el.addEventListener('click', function (e) { | |||||
var btn = e.target; | |||||
var mode = btn.getAttribute('data-mode'); | |||||
var discreteDomains = 0; | |||||
if (mode === 'discrete') { | |||||
discreteDomains = 1; | |||||
} | |||||
var colors = []; | |||||
var colors_mode = document.querySelector('.heatmap-color-buttons .active').getAttribute('data-color'); | |||||
if (colors_mode === 'halloween') { | |||||
colors = HEATMAP_COLORS_YELLOW; | |||||
} else if (colors_mode === 'blue') { | |||||
colors = HEATMAP_COLORS_BLUE; | |||||
} | |||||
heatmapArgs.discreteDomains = discreteDomains; | |||||
heatmapArgs.colors = colors; | |||||
new Chart("#chart-heatmap", heatmapArgs); | |||||
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) { | |||||
el.classList.remove('active'); | |||||
}); | |||||
btn.classList.add('active'); | |||||
}); | |||||
}); | |||||
Array.prototype.slice.call(document.querySelectorAll('.heatmap-color-buttons button')).map(function (el) { | |||||
el.addEventListener('click', function (e) { | |||||
var btn = e.target; | |||||
var colors_mode = btn.getAttribute('data-color'); | |||||
var colors = []; | |||||
if (colors_mode === 'halloween') { | |||||
colors = HEATMAP_COLORS_YELLOW; | |||||
} else if (colors_mode === 'blue') { | |||||
colors = HEATMAP_COLORS_BLUE; | |||||
} | |||||
var discreteDomains = 1; | |||||
var view_mode = document.querySelector('.heatmap-mode-buttons .active').getAttribute('data-mode'); | |||||
if (view_mode === 'continuous') { | |||||
discreteDomains = 0; | |||||
} | |||||
heatmapArgs.discreteDomains = discreteDomains; | |||||
heatmapArgs.colors = colors; | |||||
new Chart("#chart-heatmap", heatmapArgs); | |||||
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) { | |||||
el.classList.remove('active'); | |||||
}); | |||||
btn.classList.add('active'); | |||||
}); | |||||
}); | |||||
document.querySelector('.export-heatmap').addEventListener('click', function () { | |||||
heatmapChart.export(); | |||||
}); | |||||
}()); | |||||
//# sourceMappingURL=index.min.js.map |
@@ -1,323 +0,0 @@ | |||||
--- | |||||
redirect_to: "https://frappe.io/charts" | |||||
--- | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>Frappe Charts</title> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
<meta name="keywords" content="open source javascript js charts library svg zero-dependency interactive data visualization beautiful drag resize"> | |||||
<meta name="description" content="A simple, responsive, modern charts library for the web."> | |||||
<link rel="stylesheet" type="text/css" href="assets/css/reset.css" media="screen"> | |||||
<link rel="stylesheet" type="text/css" href="assets/css/bootstrap.min.css" media="screen"> | |||||
<link rel="stylesheet" type="text/css" href="assets/css/index.css" media="screen"> | |||||
<link rel="stylesheet" type="text/css" href="assets/css/hljs.css" media="screen"> | |||||
<script src="assets/js/highlight.pack.js"></script> | |||||
<script>hljs.initHighlightingOnLoad();</script> | |||||
<link rel="shortcut icon" href="https://frappe.github.io/frappe/assets/img/favicon.png" type="image/x-icon"> | |||||
<link rel="icon" href="https://frappe.github.io/frappe/assets/img/favicon.png" type="image/x-icon"> | |||||
<script async defer src="https://buttons.github.io/buttons.js"></script> | |||||
</head> | |||||
<body> | |||||
<header> | |||||
<h1>Frappe Charts</h1> | |||||
<p class="lead-text">GitHub-inspired simple and modern SVG charts for the web<br>with zero dependencies.</p> | |||||
<div id="chart-composite-1" class="border"></div> | |||||
<p class="demo-tip">Click or use arrow keys to navigate data points</p> | |||||
<div id="chart-composite-2" class="border"></div> | |||||
</header> | |||||
<section> | |||||
<h6>Create a chart</h6> | |||||
<pre><code class="hljs html"> <!--HTML--> | |||||
<div id="chart"></div></code></pre> | |||||
<pre><code class="hljs javascript"> // Javascript | |||||
let chart = new frappe.Chart( "#chart", { // or DOM element | |||||
data: { | |||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm", | |||||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"], | |||||
datasets: [ | |||||
{ | |||||
name: "Some Data", chartType: 'bar', | |||||
values: [25, 40, 30, 35, 8, 52, 17, -4] | |||||
}, | |||||
{ | |||||
name: "Another Set", chartType: 'bar', | |||||
values: [25, 50, -10, 15, 18, 32, 27, 14] | |||||
}, | |||||
{ | |||||
name: "Yet Another", chartType: 'line', | |||||
values: [15, 20, -3, -15, 58, 12, -17, 37] | |||||
} | |||||
], | |||||
yMarkers: [{ label: "Marker", value: 70, | |||||
options: { labelPos: 'left' }}], | |||||
yRegions: [{ label: "Region", start: -10, end: 50, | |||||
options: { labelPos: 'right' }}] | |||||
}, | |||||
title: "My Awesome Chart", | |||||
type: 'axis-mixed', // or 'bar', 'line', 'pie', 'percentage', 'donut' | |||||
height: 300, | |||||
colors: ['purple', '#ffa3ef', 'light-blue'], | |||||
tooltipOptions: { | |||||
formatTooltipX: d => (d + '').toUpperCase(), | |||||
formatTooltipY: d => d + ' pts', | |||||
} | |||||
}); | |||||
chart.export(); | |||||
</code></pre> | |||||
<div id="chart-aggr" class="border"></div> | |||||
<div class="btn-group aggr-type-buttons margin-top mx-auto" role="group"> | |||||
<button type="button" class="btn btn-sm btn-secondary active" data-type='axis-mixed'>Mixed</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-type='donut'>Donut Chart</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button> | |||||
</div> | |||||
<div class="btn-group export-buttons margin-top mx-auto" role="group"> | |||||
<button type="button" class="btn btn-sm btn-secondary export-aggr">Export ...</button> | |||||
</div> | |||||
</section> | |||||
<section> | |||||
<h6>Update Values</h6> | |||||
<div id="chart-update" class="border"></div> | |||||
<div class="chart-update-buttons mt-1 mx-auto" role="group"> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-update="random">Random Data</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-update="add">Add Value</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-update="remove">Remove Value</button> | |||||
<button type="button" class="btn btn-sm btn-secondary export-update">Export ...</button> | |||||
</div> | |||||
</section> | |||||
<section> | |||||
<h6>Plot Trends</h6> | |||||
<div id="chart-trends" class="border"></div> | |||||
<div class="btn-group chart-plot-buttons mt-1 mx-auto" role="group"> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-type="hideDots">Line</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-type="hideLine">Dots</button> | |||||
<button type="button" class="btn btn-sm btn-secondary active" data-type="heatline">HeatLine</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-type="regionFill">Region</button> | |||||
</div> | |||||
<div class="btn-group export-buttons mt-1 mx-auto" role="group"> | |||||
<button type="button" class="btn btn-sm btn-secondary export-trends">Export ...</button> | |||||
</div> | |||||
</section> | |||||
<section> | |||||
<h6>Listen to state change</h6> | |||||
<div class="row"> | |||||
<div class="col-sm-8"> | |||||
<div id="chart-events" class="border"></div> | |||||
</div> | |||||
<div class="col-sm-4 chart-events-data"> | |||||
<div class="image-container border"> | |||||
<img class="moon-image" src="./assets/img/europa.jpg"> | |||||
</div> | |||||
<div class="content-data margin-top"> | |||||
<h6 class="moon-name">Europa</h6> | |||||
<p>Semi-major-axis: <span class="semi-major-axis">671034</span> km</p> | |||||
<p>Mass: <span class="mass">4800000</span> x 10^16 kg</p> | |||||
<p>Diameter: <span class="diameter">3121.6</span> km</p> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<pre><code class="hljs javascript margin-top"> ... | |||||
isNavigable: 1, // Navigate across data points; default 0 | |||||
... | |||||
chart.parent.addEventListener('data-select', (e) => { | |||||
update_moon_data(e.index); // e contains index and value of current datapoint | |||||
});</code></pre> | |||||
</section> | |||||
<section> | |||||
<h6> | |||||
And a Month-wise Heatmap | |||||
</h6> | |||||
<div id="chart-heatmap" class="border" | |||||
style="overflow: scroll;"></div> | |||||
<div class="heatmap-mode-buttons btn-group mt-1 mx-auto" role="group"> | |||||
<button type="button" class="btn btn-sm btn-secondary active" data-mode="discrete">Discrete</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-mode="continuous">Continuous</button> | |||||
</div> | |||||
<div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group"> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-color="default">Green (Default)</button> | |||||
<button type="button" class="btn btn-sm btn-secondary active" data-color="blue">Blue</button> | |||||
<button type="button" class="btn btn-sm btn-secondary" data-color="halloween">GitHub's Halloween</button> | |||||
</div> | |||||
<div class="btn-group export-buttons mt-1 mx-auto" role="group"> | |||||
<button type="button" class="btn btn-sm btn-secondary export-heatmap">Export ...</button> | |||||
</div> | |||||
<pre><code class="hljs javascript margin-top"> let heatmap = new frappe.Chart("#heatmap", { | |||||
type: 'heatmap', | |||||
title: "Monthly Distribution", | |||||
data: { | |||||
dataPoints: {'1524064033': 8, /* ... */}, | |||||
// object with timestamp-value pairs | |||||
start: startDate | |||||
end: endDate // Date objects | |||||
}, | |||||
countLabel: 'Level', | |||||
discreteDomains: 0 // default: 1 | |||||
colors: ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'], | |||||
// Set of five incremental colors, | |||||
// preferably with a low-saturation color for zero data; | |||||
// def: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'] | |||||
});</code></pre> | |||||
</section> | |||||
<section> | |||||
<h6>Demo</h6> | |||||
<p data-height="299" data-theme-id="light" data-slug-hash="wjKBoq" data-default-tab="js,result" | |||||
data-user="pratu16x7" data-embed-version="2" data-pen-title="Frappe Charts Demo" class="codepen"> | |||||
See the Pen <a href="https://codepen.io/pratu16x7/pen/wjKBoq/">Frappe Charts Demo</a> | |||||
by Prateeksha Singh (<a href="https://codepen.io/pratu16x7">@pratu16x7</a>) on | |||||
<a href="https://codepen.io">CodePen</a>. | |||||
</p> | |||||
<script async src="https://static.codepen.io/assets/embed/ei.js"></script> | |||||
</section> | |||||
<section> | |||||
<h6>Available options</h6> | |||||
<pre><code class="hljs javascript"> | |||||
... | |||||
{ | |||||
data: { | |||||
labels: [], | |||||
datasets: [], | |||||
yRegions: [], | |||||
yMarkers: [] | |||||
} | |||||
title: '', | |||||
colors: [], | |||||
height: 200, | |||||
tooltipOptions: { | |||||
formatTooltipX: d => (d + '').toUpperCase(), | |||||
formatTooltipY: d => d + ' pts', | |||||
} | |||||
// Axis charts | |||||
isNavigable: 1, // default: 0 | |||||
valuesOverPoints: 1, // default: 0 | |||||
barOptions: { | |||||
spaceRatio: 1 // default: 0.5 | |||||
stacked: 1 // default: 0 | |||||
} | |||||
lineOptions: { | |||||
dotSize: 6, // default: 4 | |||||
hideLine: 0, // default: 0 | |||||
hideDots: 1, // default: 0 | |||||
heatline: 1, // default: 0 | |||||
regionFill: 1 // default: 0 | |||||
} | |||||
axisOptions: { | |||||
yAxisMode: 'span', // Axis lines, default | |||||
xAxisMode: 'tick', // No axis lines, only short ticks | |||||
xIsSeries: 1 // Allow skipping x values for space | |||||
// default: 0 | |||||
}, | |||||
// Pie/Percentage/Donut charts | |||||
maxLegendPoints: 6, // default: 20 | |||||
maxSlices: 10, // default: 20 | |||||
// Percentage chart | |||||
barOptions: { | |||||
height: 15 // default: 20 | |||||
depth: 5 // default: 2 | |||||
} | |||||
// Heatmap | |||||
discreteDomains: 1, // default: 1 | |||||
} | |||||
... | |||||
// Updating values | |||||
chart.update(data); | |||||
// Axis charts: | |||||
chart.addDataPoint(label, valueFromEachDataset, index) | |||||
chart.removeDataPoint(index) | |||||
chart.updateDataset(datasetValues, index) | |||||
// Exporting | |||||
chart.export(); | |||||
// Unbind window-resize events | |||||
chart.destroy(); | |||||
</code></pre> | |||||
</section> | |||||
<section> | |||||
<h6>Install</h6> | |||||
<p>Install via npm</p> | |||||
<pre><code class="hljs console"> npm install frappe-charts</code></pre> | |||||
<p>And include it in your project</p> | |||||
<pre><code class="hljs javascript"> import { Chart } from "frappe-charts"</code></pre> | |||||
<p>(for ES6+ import the ESModule from the dist folder)</p> | |||||
<pre><code class="hljs javascript"> import { Chart } from "frappe-charts/dist/frappe-charts.esm.js"</code></pre> | |||||
<p>... or include it directly in your HTML</p> | |||||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@1.1.0"></script></code></pre> | |||||
<p>Use as:</p> | |||||
<pre><code class="hljs javascript"> new Chart(); // ES6 module | |||||
// or | |||||
new frappe.Chart(); // Browser</code></pre> | |||||
</section> | |||||
<section class="text-center"> | |||||
<!-- Closing --> | |||||
<a href="https://github.com/frappe/charts/archive/master.zip"><button class="large blue button btn">Download</button></a> | |||||
<p style="margin-top: 3rem;margin-bottom: 1.5rem;"> | |||||
<!-- <a href="docs.html" style="margin-right: 1rem;" target="_blank">Documentation</a> --> | |||||
<a href="https://github.com/frappe/charts" target="_blank">View on GitHub</a> | |||||
</p> | |||||
<p style="margin-top: 1rem;"> | |||||
<a class="github-button" href="https://github.com/frappe/charts" data-icon="octicon-star" data-show-count="true" aria-label="Star frappe/charts on GitHub">Star</a> | |||||
</p> | |||||
<p>License: MIT</p> | |||||
</section> | |||||
<footer class="built-with-frappe text-center"> | |||||
<img style="padding: 5px; width: 40px; background: #fff" class="frappe-bird" src="./assets/img/frappe-bird.png"> | |||||
<p style="margin: 24px 0 0px 0; font-size: 15px"> | |||||
Project maintained by <a href="https://frappe.io" target="_blank">Frappe</a>. | |||||
Used in <a href="https://erpnext.com" target="_blank">ERPNext</a>. | |||||
Read the <a href="https://medium.com/@pratu16x7/so-we-decided-to-create-our-own-charts-a95cb5032c97" target="_blank">blog post</a>. | |||||
</p> | |||||
<p style="margin: 24px 0 80px 0; font-size: 12px"> | |||||
Data from the <a href="https://www.amsmeteors.org" target="_blank">American Meteor Society</a>, | |||||
<a href="http://www.sidc.be/silso" target="_blank">SILSO</a> and | |||||
<a href="https://api.nasa.gov/index.html" target="_blank">NASA Open APIs</a> | |||||
</p> | |||||
</footer> | |||||
<a href="https://github.com/frappe/charts" target="_blank" class="github-corner" aria-label="View source on Github"> | |||||
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#9a9a9a; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"> | |||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> | |||||
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path> | |||||
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path> | |||||
</svg> | |||||
</a> | |||||
<script src="assets/js/frappe-charts.min.js"></script> | |||||
<script src="assets/js/index.min.js"></script> | |||||
</body> | |||||
</html> |
@@ -1,11 +1,13 @@ | |||||
{ | { | ||||
"name": "frappe-charts", | "name": "frappe-charts", | ||||
"version": "1.5.5", | |||||
"version": "2.0.0-rc22", | |||||
"main": "dist/frappe-charts.esm.js", | |||||
"module": "dist/frappe-charts.esm.js", | |||||
"browser": "dist/frappe-charts.umd.js", | |||||
"common": "dist/frappe-charts.cjs.js", | |||||
"unnpkg": "dist/frappe-charts.umd.js", | |||||
"type": "module", | |||||
"description": "https://frappe.github.io/charts", | "description": "https://frappe.github.io/charts", | ||||
"main": "dist/frappe-charts.min.cjs.js", | |||||
"module": "dist/frappe-charts.min.esm.js", | |||||
"src": "dist/frappe-charts.esm.js", | |||||
"browser": "dist/frappe-charts.min.iife.js", | |||||
"directories": { | "directories": { | ||||
"doc": "docs" | "doc": "docs" | ||||
}, | }, | ||||
@@ -34,35 +36,15 @@ | |||||
}, | }, | ||||
"homepage": "https://github.com/frappe/charts#readme", | "homepage": "https://github.com/frappe/charts#readme", | ||||
"devDependencies": { | "devDependencies": { | ||||
"autoprefixer": "^8.2.0", | |||||
"babel-core": "^6.26.3", | |||||
"babel-plugin-external-helpers": "^6.22.0", | |||||
"babel-plugin-istanbul": "^5.1.4", | |||||
"babel-preset-env": "^1.7.0", | |||||
"babel-preset-latest": "^6.24.1", | |||||
"babel-register": "^6.26.0", | |||||
"clean-css": "^4.1.11", | |||||
"coveralls": "^3.0.0", | |||||
"cross-env": "^5.1.4", | |||||
"cssnano": "^4.1.10", | |||||
"eslint": "^4.18.2", | |||||
"mocha": "^5.0.5", | |||||
"node-sass": "^4.12.0", | |||||
"npm-run-all": "^4.1.1", | |||||
"nyc": "^14.1.1", | |||||
"postcss": "^6.0.21", | |||||
"postcss-cssnext": "^3.0.2", | |||||
"postcss-nested": "^2.1.2", | |||||
"precss": "^3.1.2", | |||||
"rollup": "^0.50.0", | |||||
"rollup-plugin-babel": "^3.0.2", | |||||
"rollup-plugin-eslint": "^6.0.0", | |||||
"rollup-plugin-node-resolve": "^3.0.0", | |||||
"rollup-plugin-postcss": "^2.0.3", | |||||
"rollup-plugin-replace": "^2.0.0", | |||||
"rollup-plugin-uglify": "^2.0.1", | |||||
"rollup-plugin-uglify-es": "0.0.1", | |||||
"rollup-watch": "^4.3.1" | |||||
}, | |||||
"dependencies": {} | |||||
"@babel/core": "^7.10.5", | |||||
"@babel/preset-env": "^7.10.4", | |||||
"rollup": "^2.21.0", | |||||
"rollup-plugin-babel": "^4.4.0", | |||||
"rollup-plugin-bundle-size": "^1.0.3", | |||||
"rollup-plugin-commonjs": "^10.1.0", | |||||
"rollup-plugin-eslint": "^7.0.0", | |||||
"rollup-plugin-postcss": "^3.1.3", | |||||
"rollup-plugin-scss": "^2.5.0", | |||||
"rollup-plugin-terser": "^6.1.0" | |||||
} | |||||
} | } |
@@ -1,195 +1,48 @@ | |||||
import pkg from './package.json'; | import pkg from './package.json'; | ||||
// Rollup plugins | |||||
import commonjs from 'rollup-plugin-commonjs'; | |||||
import babel from 'rollup-plugin-babel'; | import babel from 'rollup-plugin-babel'; | ||||
import { eslint } from 'rollup-plugin-eslint'; | |||||
import replace from 'rollup-plugin-replace'; | |||||
import uglify from 'rollup-plugin-uglify-es'; | |||||
import sass from 'node-sass'; | |||||
import postcss from 'rollup-plugin-postcss'; | |||||
import scss from 'rollup-plugin-scss'; | |||||
import bundleSize from 'rollup-plugin-bundle-size'; | |||||
import { terser } from 'rollup-plugin-terser'; | |||||
// PostCSS plugins | |||||
import postcssPlugin from 'rollup-plugin-postcss'; | |||||
import nested from 'postcss-nested'; | |||||
import cssnext from 'postcss-cssnext'; | |||||
import cssnano from 'cssnano'; | |||||
import postcss from 'postcss'; | |||||
import precss from 'precss'; | |||||
import CleanCSS from 'clean-css'; | |||||
import autoprefixer from 'autoprefixer'; | |||||
import fs from 'fs'; | |||||
fs.readFile('src/css/charts.scss', (err, css) => { | |||||
postcss([precss, autoprefixer]) | |||||
.process(css, { from: 'src/css/charts.scss', to: 'src/css/charts.css' }) | |||||
.then(result => { | |||||
let options = { | |||||
level: { | |||||
1: { | |||||
removeQuotes: false, | |||||
} | |||||
} | |||||
} | |||||
let output = new CleanCSS(options).minify(result.css); | |||||
let res = JSON.stringify(output.styles).replace(/"/g, "'"); | |||||
let js = `export const CSSTEXT = "${res.slice(1, -1)}";`; | |||||
fs.writeFile('src/css/chartsCss.js', js); | |||||
}); | |||||
}); | |||||
export default [ | export default [ | ||||
// browser-friendly UMD build | |||||
{ | { | ||||
input: 'src/js/index.js', | input: 'src/js/index.js', | ||||
sourcemap: true, | |||||
output: [ | |||||
{ | |||||
file: 'docs/assets/js/frappe-charts.min.js', | |||||
format: 'iife', | |||||
}, | |||||
{ | |||||
file: pkg.browser, | |||||
format: 'iife', | |||||
} | |||||
], | |||||
name: 'frappe', | |||||
plugins: [ | |||||
postcssPlugin({ | |||||
preprocessor: (content, id) => new Promise((resolve, reject) => { | |||||
const result = sass.renderSync({ file: id }) | |||||
resolve({ code: result.css.toString() }) | |||||
}), | |||||
extensions: [ '.scss' ], | |||||
plugins: [ | |||||
nested(), | |||||
cssnext({ warnForDuplicates: false }), | |||||
cssnano() | |||||
] | |||||
}), | |||||
eslint({ | |||||
exclude: [ | |||||
'src/css/**' | |||||
] | |||||
}), | |||||
babel({ | |||||
exclude: 'node_modules/**', | |||||
plugins: ['external-helpers'] | |||||
}), | |||||
replace({ | |||||
exclude: 'node_modules/**', | |||||
ENV: JSON.stringify(process.env.NODE_ENV || 'development'), | |||||
}), | |||||
uglify() | |||||
] | |||||
}, | |||||
{ | |||||
input: 'docs/assets/js/index.js', | |||||
sourcemap: true, | |||||
output: [ | |||||
{ | |||||
file: 'docs/assets/js/index.min.js', | |||||
format: 'iife', | |||||
} | |||||
], | |||||
name: 'frappe', | |||||
output: { | |||||
sourcemap: true, | |||||
name: 'frappe-charts', | |||||
file: pkg.browser, | |||||
format: 'umd' | |||||
}, | |||||
plugins: [ | plugins: [ | ||||
postcssPlugin({ | |||||
preprocessor: (content, id) => new Promise((resolve, reject) => { | |||||
const result = sass.renderSync({ file: id }) | |||||
resolve({ code: result.css.toString() }) | |||||
}), | |||||
extensions: [ '.scss' ], | |||||
plugins: [ | |||||
nested(), | |||||
cssnext({ warnForDuplicates: false }), | |||||
cssnano() | |||||
] | |||||
}), | |||||
eslint({ | |||||
exclude: [ | |||||
'src/css/**' | |||||
] | |||||
}), | |||||
commonjs(), | |||||
babel({ | babel({ | ||||
exclude: 'node_modules/**' | |||||
exclude: ['node_modules/**'] | |||||
}), | }), | ||||
replace({ | |||||
exclude: 'node_modules/**', | |||||
ENV: JSON.stringify(process.env.NODE_ENV || 'development'), | |||||
}) | |||||
terser(), | |||||
scss({ output: 'dist/frappe-charts.min.css' }), | |||||
bundleSize() | |||||
] | ] | ||||
}, | }, | ||||
// CommonJS (for Node) and ES module (for bundlers) build. | |||||
{ | { | ||||
input: 'src/js/chart.js', | input: 'src/js/chart.js', | ||||
sourcemap: true, | |||||
output: [ | output: [ | ||||
{ | |||||
file: pkg.main, | |||||
format: 'cjs', | |||||
}, | |||||
{ | |||||
file: pkg.module, | |||||
format: 'es', | |||||
} | |||||
{ file: pkg.common, format: 'cjs', sourcemap: true }, | |||||
{ file: pkg.module, format: 'es', sourcemap: true }, | |||||
], | ], | ||||
plugins: [ | plugins: [ | ||||
postcssPlugin({ | |||||
preprocessor: (content, id) => new Promise((resolve, reject) => { | |||||
const result = sass.renderSync({ file: id }) | |||||
resolve({ code: result.css.toString() }) | |||||
}), | |||||
extensions: [ '.scss' ], | |||||
plugins: [ | |||||
nested(), | |||||
cssnext({ warnForDuplicates: false }), | |||||
cssnano() | |||||
] | |||||
}), | |||||
eslint({ | |||||
exclude: [ | |||||
'src/css/**', | |||||
] | |||||
}), | |||||
babel({ | babel({ | ||||
exclude: 'node_modules/**', | |||||
}), | |||||
replace({ | |||||
exclude: 'node_modules/**', | |||||
ENV: JSON.stringify(process.env.NODE_ENV || 'development'), | |||||
}), | |||||
uglify() | |||||
], | |||||
}, | |||||
{ | |||||
input: 'src/js/chart.js', | |||||
output: [ | |||||
{ | |||||
file: pkg.src, | |||||
format: 'es', | |||||
} | |||||
], | |||||
plugins: [ | |||||
postcssPlugin({ | |||||
preprocessor: (content, id) => new Promise((resolve, reject) => { | |||||
const result = sass.renderSync({ file: id }) | |||||
resolve({ code: result.css.toString() }) | |||||
}), | |||||
extensions: [ '.scss' ], | |||||
extract: 'dist/frappe-charts.min.css', | |||||
plugins: [ | |||||
nested(), | |||||
cssnext({ warnForDuplicates: false }), | |||||
cssnano() | |||||
] | |||||
}), | |||||
eslint({ | |||||
exclude: [ | |||||
'src/css/**', | |||||
] | |||||
exclude: ['node_modules/**'] | |||||
}), | }), | ||||
replace({ | |||||
exclude: 'node_modules/**', | |||||
ENV: JSON.stringify(process.env.NODE_ENV || 'development'), | |||||
}) | |||||
], | |||||
terser(), | |||||
postcss(), | |||||
bundleSize() | |||||
] | |||||
} | } | ||||
]; | |||||
]; |
@@ -0,0 +1,10 @@ | |||||
{ | |||||
"presets": [ | |||||
["@babel/preset-env", { | |||||
"targets": { | |||||
"browsers": ["last 2 versions", "safari >= 7"] | |||||
}, | |||||
"modules": false | |||||
}] | |||||
] | |||||
} |
@@ -1,43 +1,65 @@ | |||||
:root { | |||||
--charts-label-color: #313b44; | |||||
--charts-axis-line-color: #f4f5f6; | |||||
--charts-tooltip-title: var(--charts-label-color); | |||||
--charts-tooltip-label: var(--charts-label-color); | |||||
--charts-tooltip-value: #192734; | |||||
--charts-tooltip-bg: #ffffff; | |||||
--charts-stroke-width: 2px; | |||||
--charts-dataset-circle-stroke: #ffffff; | |||||
--charts-dataset-circle-stroke-width: var(--charts-stroke-width); | |||||
--charts-legend-label: var(--charts-label-color); | |||||
--charts-legend-value: var(--charts-label-color); | |||||
} | |||||
.chart-container { | .chart-container { | ||||
position: relative; /* for absolutely positioned tooltip */ | position: relative; /* for absolutely positioned tooltip */ | ||||
/* https://www.smashingmagazine.com/2015/11/using-system-ui-fonts-practical-guide/ */ | |||||
font-family: -apple-system, BlinkMacSystemFont, | |||||
'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', | |||||
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; | |||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", | |||||
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", | |||||
"Helvetica Neue", sans-serif; | |||||
.axis, .chart-label { | |||||
fill: #555b51; | |||||
.axis, | |||||
.chart-label { | |||||
fill: var(--charts-label-color); | |||||
line { | line { | ||||
stroke: #dadada; | |||||
stroke: var(--charts-axis-line-color); | |||||
} | } | ||||
} | } | ||||
.dataset-units { | .dataset-units { | ||||
circle { | circle { | ||||
stroke: #fff; | |||||
stroke-width: 2; | |||||
stroke: var(--charts-dataset-circle-stroke); | |||||
stroke-width: var(--charts-dataset-circle-stroke-width); | |||||
} | } | ||||
path { | path { | ||||
fill: none; | fill: none; | ||||
stroke-opacity: 1; | stroke-opacity: 1; | ||||
stroke-width: 2px; | |||||
stroke-width: var(--charts-stroke-width); | |||||
} | } | ||||
} | } | ||||
.dataset-path { | .dataset-path { | ||||
stroke-width: 2px; | |||||
stroke-width: var(--charts-stroke-width); | |||||
} | } | ||||
.path-group { | .path-group { | ||||
path { | path { | ||||
fill: none; | fill: none; | ||||
stroke-opacity: 1; | stroke-opacity: 1; | ||||
stroke-width: 2px; | |||||
stroke-width: var(--charts-stroke-width); | |||||
} | } | ||||
} | } | ||||
line.dashed { | line.dashed { | ||||
stroke-dasharray: 5, 3; | stroke-dasharray: 5, 3; | ||||
} | } | ||||
.axis-line { | .axis-line { | ||||
.specific-value { | .specific-value { | ||||
text-anchor: start; | text-anchor: start; | ||||
@@ -49,10 +71,15 @@ | |||||
text-anchor: middle; | text-anchor: middle; | ||||
} | } | ||||
} | } | ||||
.legend-dataset-text { | |||||
fill: #6c7680; | |||||
.legend-dataset-label { | |||||
fill: var(--charts-legend-label); | |||||
font-weight: 600; | font-weight: 600; | ||||
} | } | ||||
.legend-dataset-value { | |||||
fill: var(--charts-legend-value); | |||||
} | |||||
} | } | ||||
.graph-svg-tip { | .graph-svg-tip { | ||||
@@ -60,57 +87,102 @@ | |||||
z-index: 99999; | z-index: 99999; | ||||
padding: 10px; | padding: 10px; | ||||
font-size: 12px; | font-size: 12px; | ||||
color: #959da5; | |||||
text-align: center; | text-align: center; | ||||
background: rgba(0, 0, 0, 0.8); | |||||
border-radius: 3px; | |||||
background: var(--charts-tooltip-bg); | |||||
box-shadow: 0px 1px 4px rgba(17, 43, 66, 0.1), | |||||
0px 2px 6px rgba(17, 43, 66, 0.08), | |||||
0px 40px 30px -30px rgba(17, 43, 66, 0.1); | |||||
border-radius: 6px; | |||||
ul { | ul { | ||||
padding-left: 0; | padding-left: 0; | ||||
display: flex; | display: flex; | ||||
} | } | ||||
ol { | ol { | ||||
padding-left: 0; | padding-left: 0; | ||||
display: flex; | display: flex; | ||||
} | } | ||||
ul.data-point-list { | ul.data-point-list { | ||||
li { | li { | ||||
min-width: 90px; | min-width: 90px; | ||||
flex: 1; | |||||
font-weight: 600; | font-weight: 600; | ||||
} | } | ||||
} | } | ||||
strong { | |||||
color: #dfe2e5; | |||||
font-weight: 600; | |||||
} | |||||
.svg-pointer { | .svg-pointer { | ||||
position: absolute; | position: absolute; | ||||
height: 5px; | |||||
margin: 0 0 0 -5px; | |||||
content: ' '; | |||||
border: 5px solid transparent; | |||||
border-top-color: rgba(0, 0, 0, 0.8); | |||||
height: 12px; | |||||
width: 12px; | |||||
border-radius: 2px; | |||||
background: var(--charts-tooltip-bg); | |||||
transform: rotate(45deg); | |||||
margin-top: -7px; | |||||
margin-left: -6px; | |||||
} | } | ||||
&.comparison { | &.comparison { | ||||
padding: 0; | |||||
text-align: left; | text-align: left; | ||||
padding: 0px; | |||||
pointer-events: none; | pointer-events: none; | ||||
.title { | .title { | ||||
display: block; | display: block; | ||||
padding: 10px; | |||||
padding: 16px; | |||||
margin: 0; | margin: 0; | ||||
color: var(--charts-tooltip-title); | |||||
font-weight: 600; | font-weight: 600; | ||||
line-height: 1; | line-height: 1; | ||||
pointer-events: none; | pointer-events: none; | ||||
text-transform: uppercase; | |||||
strong { | |||||
color: var(--charts-tooltip-value); | |||||
} | |||||
} | } | ||||
ul { | ul { | ||||
margin: 0; | margin: 0; | ||||
white-space: nowrap; | white-space: nowrap; | ||||
list-style: none; | list-style: none; | ||||
&.tooltip-grid { | |||||
display: grid; | |||||
grid-template-columns: repeat(4, minmax(0, 1fr)); | |||||
gap: 5px; | |||||
} | |||||
} | } | ||||
li { | li { | ||||
display: inline-block; | display: inline-block; | ||||
padding: 5px 10px; | |||||
display: flex; | |||||
flex-direction: row; | |||||
font-weight: 600; | |||||
line-height: 1; | |||||
padding: 5px 15px 15px 15px; | |||||
.tooltip-legend { | |||||
height: 12px; | |||||
width: 12px; | |||||
margin-right: 8px; | |||||
border-radius: 2px; | |||||
} | |||||
.tooltip-label { | |||||
margin-top: 4px; | |||||
font-size: 11px; | |||||
line-height: 1.25; | |||||
max-width: 150px; | |||||
white-space: normal; | |||||
color: var(--charts-tooltip-label); | |||||
} | |||||
.tooltip-value { | |||||
color: var(--charts-tooltip-value); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -1 +1 @@ | |||||
export const CSSTEXT = ".chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}"; | |||||
export const CSSTEXT = ".chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}"; |
@@ -1,6 +1,5 @@ | |||||
import '../css/charts.scss'; | import '../css/charts.scss'; | ||||
// import MultiAxisChart from './charts/MultiAxisChart'; | |||||
import PercentageChart from './charts/PercentageChart'; | import PercentageChart from './charts/PercentageChart'; | ||||
import PieChart from './charts/PieChart'; | import PieChart from './charts/PieChart'; | ||||
import Heatmap from './charts/Heatmap'; | import Heatmap from './charts/Heatmap'; | ||||
@@ -10,7 +9,6 @@ import DonutChart from './charts/DonutChart'; | |||||
const chartTypes = { | const chartTypes = { | ||||
bar: AxisChart, | bar: AxisChart, | ||||
line: AxisChart, | line: AxisChart, | ||||
// multiaxis: MultiAxisChart, | |||||
percentage: PercentageChart, | percentage: PercentageChart, | ||||
heatmap: Heatmap, | heatmap: Heatmap, | ||||
pie: PieChart, | pie: PieChart, | ||||
@@ -1,8 +1,6 @@ | |||||
import BaseChart from './BaseChart'; | import BaseChart from './BaseChart'; | ||||
import { truncateString } from '../utils/draw-utils'; | |||||
import { legendDot } from '../utils/draw'; | import { legendDot } from '../utils/draw'; | ||||
import { round } from '../utils/helpers'; | import { round } from '../utils/helpers'; | ||||
import { getExtraWidth } from '../utils/constants'; | |||||
export default class AggregationChart extends BaseChart { | export default class AggregationChart extends BaseChart { | ||||
constructor(parent, args) { | constructor(parent, args) { | ||||
@@ -15,6 +13,7 @@ export default class AggregationChart extends BaseChart { | |||||
this.config.formatTooltipY = (args.tooltipOptions || {}).formatTooltipY; | this.config.formatTooltipY = (args.tooltipOptions || {}).formatTooltipY; | ||||
this.config.maxSlices = args.maxSlices || 20; | this.config.maxSlices = args.maxSlices || 20; | ||||
this.config.maxLegendPoints = args.maxLegendPoints || 20; | this.config.maxLegendPoints = args.maxLegendPoints || 20; | ||||
this.config.legendRowHeight = 60; | |||||
} | } | ||||
calc() { | calc() { | ||||
@@ -31,17 +30,17 @@ export default class AggregationChart extends BaseChart { | |||||
}).filter(d => { return d[0] >= 0; }); // keep only positive results | }).filter(d => { return d[0] >= 0; }); // keep only positive results | ||||
let totals = allTotals; | let totals = allTotals; | ||||
if(allTotals.length > maxSlices) { | |||||
if (allTotals.length > maxSlices) { | |||||
// Prune and keep a grey area for rest as per maxSlices | // Prune and keep a grey area for rest as per maxSlices | ||||
allTotals.sort((a, b) => { return b[0] - a[0]; }); | allTotals.sort((a, b) => { return b[0] - a[0]; }); | ||||
totals = allTotals.slice(0, maxSlices-1); | |||||
let remaining = allTotals.slice(maxSlices-1); | |||||
totals = allTotals.slice(0, maxSlices - 1); | |||||
let remaining = allTotals.slice(maxSlices - 1); | |||||
let sumOfRemaining = 0; | let sumOfRemaining = 0; | ||||
remaining.map(d => {sumOfRemaining += d[0];}); | |||||
remaining.map(d => { sumOfRemaining += d[0]; }); | |||||
totals.push([sumOfRemaining, 'Rest']); | totals.push([sumOfRemaining, 'Rest']); | ||||
this.colors[maxSlices-1] = 'grey'; | |||||
this.colors[maxSlices - 1] = 'grey'; | |||||
} | } | ||||
s.labels = []; | s.labels = []; | ||||
@@ -62,34 +61,22 @@ export default class AggregationChart extends BaseChart { | |||||
let s = this.state; | let s = this.state; | ||||
this.legendArea.textContent = ''; | this.legendArea.textContent = ''; | ||||
this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints); | this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints); | ||||
super.renderLegend(this.legendTotals); | |||||
} | |||||
let count = 0; | |||||
let y = 0; | |||||
this.legendTotals.map((d, i) => { | |||||
let barWidth = 150; | |||||
let divisor = Math.floor( | |||||
(this.width - getExtraWidth(this.measures))/barWidth | |||||
); | |||||
if (this.legendTotals.length < divisor) { | |||||
barWidth = this.width/this.legendTotals.length; | |||||
} | |||||
if(count > divisor) { | |||||
count = 0; | |||||
y += 20; | |||||
} | |||||
let x = barWidth * count + 5; | |||||
let label = this.config.truncateLegends ? truncateString(s.labels[i], barWidth/10) : s.labels[i]; | |||||
let formatted = this.config.formatTooltipY ? this.config.formatTooltipY(d) : d; | |||||
let dot = legendDot( | |||||
x, | |||||
y, | |||||
5, | |||||
this.colors[i], | |||||
`${label}: ${formatted}`, | |||||
false | |||||
); | |||||
this.legendArea.appendChild(dot); | |||||
count++; | |||||
}); | |||||
makeLegend(data, index, x_pos, y_pos) { | |||||
let formatted = this.config.formatTooltipY ? this.config.formatTooltipY(data) : data; | |||||
return legendDot( | |||||
x_pos, | |||||
y_pos, | |||||
12, // size | |||||
3, // dot radius | |||||
this.colors[index], // fill | |||||
this.state.labels[index], // label | |||||
formatted, // value | |||||
null, // base_font_size | |||||
this.config.truncateLegends // truncate_legends | |||||
); | |||||
} | } | ||||
} | } |
@@ -1,13 +1,14 @@ | |||||
import BaseChart from './BaseChart'; | import BaseChart from './BaseChart'; | ||||
import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils'; | import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils'; | ||||
import { AXIS_LEGEND_BAR_SIZE } from '../utils/constants'; | |||||
import { getComponent } from '../objects/ChartComponents'; | import { getComponent } from '../objects/ChartComponents'; | ||||
import { getOffset, fire } from '../utils/dom'; | import { getOffset, fire } from '../utils/dom'; | ||||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals'; | import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals'; | ||||
import { floatTwo } from '../utils/helpers'; | import { floatTwo } from '../utils/helpers'; | ||||
import { makeOverlay, updateOverlay, legendBar } from '../utils/draw'; | |||||
import { getTopOffset, getLeftOffset, MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO, | |||||
LINE_CHART_DOT_SIZE } from '../utils/constants'; | |||||
import { makeOverlay, updateOverlay, legendDot } from '../utils/draw'; | |||||
import { | |||||
getTopOffset, getLeftOffset, MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO, | |||||
LINE_CHART_DOT_SIZE | |||||
} from '../utils/constants'; | |||||
export default class AxisChart extends BaseChart { | export default class AxisChart extends BaseChart { | ||||
constructor(parent, args) { | constructor(parent, args) { | ||||
@@ -23,7 +24,7 @@ export default class AxisChart extends BaseChart { | |||||
} | } | ||||
setMeasures() { | setMeasures() { | ||||
if(this.data.datasets.length <= 1) { | |||||
if (this.data.datasets.length <= 1) { | |||||
this.config.showLegend = 0; | this.config.showLegend = 0; | ||||
this.measures.paddings.bottom = 30; | this.measures.paddings.bottom = 30; | ||||
} | } | ||||
@@ -39,24 +40,28 @@ export default class AxisChart extends BaseChart { | |||||
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span'; | this.config.yAxisMode = options.axisOptions.yAxisMode || 'span'; | ||||
this.config.xIsSeries = options.axisOptions.xIsSeries || 0; | this.config.xIsSeries = options.axisOptions.xIsSeries || 0; | ||||
this.config.shortenYAxisNumbers = options.axisOptions.shortenYAxisNumbers || 0; | this.config.shortenYAxisNumbers = options.axisOptions.shortenYAxisNumbers || 0; | ||||
this.config.numberFormatter = options.axisOptions.numberFormatter; | |||||
this.config.yAxisRange = options.axisOptions.yAxisRange || {}, | |||||
this.config.formatTooltipX = options.tooltipOptions.formatTooltipX; | this.config.formatTooltipX = options.tooltipOptions.formatTooltipX; | ||||
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY; | this.config.formatTooltipY = options.tooltipOptions.formatTooltipY; | ||||
this.config.valuesOverPoints = options.valuesOverPoints; | this.config.valuesOverPoints = options.valuesOverPoints; | ||||
this.config.legendRowHeight = 30; | |||||
} | } | ||||
prepareData(data=this.data) { | |||||
prepareData(data = this.data) { | |||||
return dataPrep(data, this.type); | return dataPrep(data, this.type); | ||||
} | } | ||||
prepareFirstData(data=this.data) { | |||||
prepareFirstData(data = this.data) { | |||||
return zeroDataPrep(data); | return zeroDataPrep(data); | ||||
} | } | ||||
calc(onlyWidthChange = false) { | calc(onlyWidthChange = false) { | ||||
this.calcXPositions(); | this.calcXPositions(); | ||||
if(!onlyWidthChange) { | |||||
if (!onlyWidthChange) { | |||||
this.calcYAxisParameters(this.getAllYValues(), this.type === 'line'); | this.calcYAxisParameters(this.getAllYValues(), this.type === 'line'); | ||||
} | } | ||||
this.makeDataByIndex(); | this.makeDataByIndex(); | ||||
@@ -67,9 +72,9 @@ export default class AxisChart extends BaseChart { | |||||
let labels = this.data.labels; | let labels = this.data.labels; | ||||
s.datasetLength = labels.length; | s.datasetLength = labels.length; | ||||
s.unitWidth = this.width/(s.datasetLength); | |||||
s.unitWidth = this.width / (s.datasetLength); | |||||
// Default, as per bar, and mixed. Only line will be a special case | // Default, as per bar, and mixed. Only line will be a special case | ||||
s.xOffset = s.unitWidth/2; | |||||
s.xOffset = s.unitWidth / 2; | |||||
// // For a pure Line Chart | // // For a pure Line Chart | ||||
// s.unitWidth = this.width/(s.datasetLength - 1); | // s.unitWidth = this.width/(s.datasetLength - 1); | ||||
@@ -84,7 +89,7 @@ export default class AxisChart extends BaseChart { | |||||
} | } | ||||
calcYAxisParameters(dataValues, withMinimum = 'false') { | calcYAxisParameters(dataValues, withMinimum = 'false') { | ||||
const yPts = calcChartIntervals(dataValues, withMinimum); | |||||
const yPts = calcChartIntervals(dataValues, withMinimum, this.config.yAxisRange); | |||||
const scaleMultiplier = this.height / getValueRange(yPts); | const scaleMultiplier = this.height / getValueRange(yPts); | ||||
const intervalHeight = getIntervalSize(yPts) * scaleMultiplier; | const intervalHeight = getIntervalSize(yPts) * scaleMultiplier; | ||||
const zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight); | const zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight); | ||||
@@ -110,7 +115,7 @@ export default class AxisChart extends BaseChart { | |||||
let values = d.values; | let values = d.values; | ||||
let cumulativeYs = d.cumulativeYs || []; | let cumulativeYs = d.cumulativeYs || []; | ||||
return { | return { | ||||
name: d.name.replace(/<|>|&/g, (char) => char == '&' ? '&' : char == '<' ? '<' : '>'), | |||||
name: d.name && d.name.replace(/<|>|&/g, (char) => char == '&' ? '&' : char == '<' ? '<' : '>'), | |||||
index: i, | index: i, | ||||
chartType: d.chartType, | chartType: d.chartType, | ||||
@@ -125,14 +130,14 @@ export default class AxisChart extends BaseChart { | |||||
calcYExtremes() { | calcYExtremes() { | ||||
let s = this.state; | let s = this.state; | ||||
if(this.barOptions.stacked) { | |||||
if (this.barOptions.stacked) { | |||||
s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos; | s.yExtremes = s.datasets[s.datasets.length - 1].cumulativeYPos; | ||||
return; | return; | ||||
} | } | ||||
s.yExtremes = new Array(s.datasetLength).fill(9999); | s.yExtremes = new Array(s.datasetLength).fill(9999); | ||||
s.datasets.map(d => { | s.datasets.map(d => { | ||||
d.yPositions.map((pos, j) => { | d.yPositions.map((pos, j) => { | ||||
if(pos < s.yExtremes[j]) { | |||||
if (pos < s.yExtremes[j]) { | |||||
s.yExtremes[j] = pos; | s.yExtremes[j] = pos; | ||||
} | } | ||||
}); | }); | ||||
@@ -141,21 +146,21 @@ export default class AxisChart extends BaseChart { | |||||
calcYRegions() { | calcYRegions() { | ||||
let s = this.state; | let s = this.state; | ||||
if(this.data.yMarkers) { | |||||
if (this.data.yMarkers) { | |||||
this.state.yMarkers = this.data.yMarkers.map(d => { | this.state.yMarkers = this.data.yMarkers.map(d => { | ||||
d.position = scale(d.value, s.yAxis); | d.position = scale(d.value, s.yAxis); | ||||
if(!d.options) d.options = {}; | |||||
if (!d.options) d.options = {}; | |||||
// if(!d.label.includes(':')) { | // if(!d.label.includes(':')) { | ||||
// d.label += ': ' + d.value; | // d.label += ': ' + d.value; | ||||
// } | // } | ||||
return d; | return d; | ||||
}); | }); | ||||
} | } | ||||
if(this.data.yRegions) { | |||||
if (this.data.yRegions) { | |||||
this.state.yRegions = this.data.yRegions.map(d => { | this.state.yRegions = this.data.yRegions.map(d => { | ||||
d.startPos = scale(d.start, s.yAxis); | d.startPos = scale(d.start, s.yAxis); | ||||
d.endPos = scale(d.end, s.yAxis); | d.endPos = scale(d.end, s.yAxis); | ||||
if(!d.options) d.options = {}; | |||||
if (!d.options) d.options = {}; | |||||
return d; | return d; | ||||
}); | }); | ||||
} | } | ||||
@@ -164,7 +169,7 @@ export default class AxisChart extends BaseChart { | |||||
getAllYValues() { | getAllYValues() { | ||||
let key = 'values'; | let key = 'values'; | ||||
if(this.barOptions.stacked) { | |||||
if (this.barOptions.stacked) { | |||||
key = 'cumulativeYs'; | key = 'cumulativeYs'; | ||||
let cumulative = new Array(this.state.datasetLength).fill(0); | let cumulative = new Array(this.state.datasetLength).fill(0); | ||||
this.data.datasets.map((d, i) => { | this.data.datasets.map((d, i) => { | ||||
@@ -174,10 +179,10 @@ export default class AxisChart extends BaseChart { | |||||
} | } | ||||
let allValueLists = this.data.datasets.map(d => d[key]); | let allValueLists = this.data.datasets.map(d => d[key]); | ||||
if(this.data.yMarkers) { | |||||
if (this.data.yMarkers) { | |||||
allValueLists.push(this.data.yMarkers.map(d => d.value)); | allValueLists.push(this.data.yMarkers.map(d => d.value)); | ||||
} | } | ||||
if(this.data.yRegions) { | |||||
if (this.data.yRegions) { | |||||
this.data.yRegions.map(d => { | this.data.yRegions.map(d => { | ||||
allValueLists.push([d.end, d.start]); | allValueLists.push([d.end, d.start]); | ||||
}); | }); | ||||
@@ -193,10 +198,10 @@ export default class AxisChart extends BaseChart { | |||||
{ | { | ||||
mode: this.config.yAxisMode, | mode: this.config.yAxisMode, | ||||
width: this.width, | width: this.width, | ||||
shortenNumbers: this.config.shortenYAxisNumbers | |||||
// pos: 'right' | |||||
shortenNumbers: this.config.shortenYAxisNumbers, | |||||
numberFormatter: this.config.numberFormatter, | |||||
}, | }, | ||||
function() { | |||||
function () { | |||||
return this.state.yAxis; | return this.state.yAxis; | ||||
}.bind(this) | }.bind(this) | ||||
], | ], | ||||
@@ -208,7 +213,7 @@ export default class AxisChart extends BaseChart { | |||||
height: this.height, | height: this.height, | ||||
// pos: 'right' | // pos: 'right' | ||||
}, | }, | ||||
function() { | |||||
function () { | |||||
let s = this.state; | let s = this.state; | ||||
s.xAxis.calcLabels = getShortenedLabels(this.width, | s.xAxis.calcLabels = getShortenedLabels(this.width, | ||||
s.xAxis.labels, this.config.xIsSeries); | s.xAxis.labels, this.config.xIsSeries); | ||||
@@ -223,7 +228,7 @@ export default class AxisChart extends BaseChart { | |||||
width: this.width, | width: this.width, | ||||
pos: 'right' | pos: 'right' | ||||
}, | }, | ||||
function() { | |||||
function () { | |||||
return this.state.yRegions; | return this.state.yRegions; | ||||
}.bind(this) | }.bind(this) | ||||
], | ], | ||||
@@ -245,23 +250,23 @@ export default class AxisChart extends BaseChart { | |||||
valuesOverPoints: this.config.valuesOverPoints, | valuesOverPoints: this.config.valuesOverPoints, | ||||
minHeight: this.height * MIN_BAR_PERCENT_HEIGHT, | minHeight: this.height * MIN_BAR_PERCENT_HEIGHT, | ||||
}, | }, | ||||
function() { | |||||
function () { | |||||
let s = this.state; | let s = this.state; | ||||
let d = s.datasets[index]; | let d = s.datasets[index]; | ||||
let stacked = this.barOptions.stacked; | let stacked = this.barOptions.stacked; | ||||
let spaceRatio = this.barOptions.spaceRatio || BAR_CHART_SPACE_RATIO; | let spaceRatio = this.barOptions.spaceRatio || BAR_CHART_SPACE_RATIO; | ||||
let barsWidth = s.unitWidth * (1 - spaceRatio); | let barsWidth = s.unitWidth * (1 - spaceRatio); | ||||
let barWidth = barsWidth/(stacked ? 1 : barDatasets.length); | |||||
let barWidth = barsWidth / (stacked ? 1 : barDatasets.length); | |||||
let xPositions = s.xAxis.positions.map(x => x - barsWidth/2); | |||||
if(!stacked) { | |||||
let xPositions = s.xAxis.positions.map(x => x - barsWidth / 2); | |||||
if (!stacked) { | |||||
xPositions = xPositions.map(p => p + barWidth * index); | xPositions = xPositions.map(p => p + barWidth * index); | ||||
} | } | ||||
let labels = new Array(s.datasetLength).fill(''); | let labels = new Array(s.datasetLength).fill(''); | ||||
if(this.config.valuesOverPoints) { | |||||
if(stacked && d.index === s.datasets.length - 1) { | |||||
if (this.config.valuesOverPoints) { | |||||
if (stacked && d.index === s.datasets.length - 1) { | |||||
labels = d.cumulativeYs; | labels = d.cumulativeYs; | ||||
} else { | } else { | ||||
labels = d.values; | labels = d.values; | ||||
@@ -269,7 +274,7 @@ export default class AxisChart extends BaseChart { | |||||
} | } | ||||
let offsets = new Array(s.datasetLength).fill(0); | let offsets = new Array(s.datasetLength).fill(0); | ||||
if(stacked) { | |||||
if (stacked) { | |||||
offsets = d.yPositions.map((y, j) => y - d.cumulativeYPos[j]); | offsets = d.yPositions.map((y, j) => y - d.cumulativeYPos[j]); | ||||
} | } | ||||
@@ -299,13 +304,15 @@ export default class AxisChart extends BaseChart { | |||||
heatline: this.lineOptions.heatline, | heatline: this.lineOptions.heatline, | ||||
regionFill: this.lineOptions.regionFill, | regionFill: this.lineOptions.regionFill, | ||||
spline: this.lineOptions.spline, | spline: this.lineOptions.spline, | ||||
hideDots: this.lineOptions.hideDots, | |||||
showDots: this.lineOptions.showDots, | |||||
trailingDot: this.lineOptions.trailingDot, | |||||
hideDotBorder: this.lineOptions.hideDotBorder, | |||||
hideLine: this.lineOptions.hideLine, | hideLine: this.lineOptions.hideLine, | ||||
// same for all datasets | // same for all datasets | ||||
valuesOverPoints: this.config.valuesOverPoints, | valuesOverPoints: this.config.valuesOverPoints, | ||||
}, | }, | ||||
function() { | |||||
function () { | |||||
let s = this.state; | let s = this.state; | ||||
let d = s.datasets[index]; | let d = s.datasets[index]; | ||||
let minLine = s.yAxis.positions[0] < s.yAxis.zeroLine | let minLine = s.yAxis.positions[0] < s.yAxis.zeroLine | ||||
@@ -331,7 +338,7 @@ export default class AxisChart extends BaseChart { | |||||
width: this.width, | width: this.width, | ||||
pos: 'right' | pos: 'right' | ||||
}, | }, | ||||
function() { | |||||
function () { | |||||
return this.state.yMarkers; | return this.state.yMarkers; | ||||
}.bind(this) | }.bind(this) | ||||
] | ] | ||||
@@ -346,7 +353,7 @@ export default class AxisChart extends BaseChart { | |||||
.filter(args => !optionals.includes(args[0]) || this.state[args[0]]) | .filter(args => !optionals.includes(args[0]) || this.state[args[0]]) | ||||
.map(args => { | .map(args => { | ||||
let component = getComponent(...args); | let component = getComponent(...args); | ||||
if(args[0].includes('lineGraph') || args[0].includes('barGraph')) { | |||||
if (args[0].includes('lineGraph') || args[0].includes('barGraph')) { | |||||
this.dataUnitComponents.push(component); | this.dataUnitComponents.push(component); | ||||
} | } | ||||
return [args[0], component]; | return [args[0], component]; | ||||
@@ -391,8 +398,8 @@ export default class AxisChart extends BaseChart { | |||||
let relX = e.pageX - o.left - getLeftOffset(m); | let relX = e.pageX - o.left - getLeftOffset(m); | ||||
let relY = e.pageY - o.top; | let relY = e.pageY - o.top; | ||||
if(relY < this.height + getTopOffset(m) | |||||
&& relY > getTopOffset(m)) { | |||||
if (relY < this.height + getTopOffset(m) | |||||
&& relY > getTopOffset(m)) { | |||||
this.mapTooltipXPosition(relX); | this.mapTooltipXPosition(relX); | ||||
} else { | } else { | ||||
this.tip.hideTip(); | this.tip.hideTip(); | ||||
@@ -402,7 +409,7 @@ export default class AxisChart extends BaseChart { | |||||
mapTooltipXPosition(relX) { | mapTooltipXPosition(relX) { | ||||
let s = this.state; | let s = this.state; | ||||
if(!s.yExtremes) return; | |||||
if (!s.yExtremes) return; | |||||
let index = getClosestInArray(relX, s.xAxis.positions, true); | let index = getClosestInArray(relX, s.xAxis.positions, true); | ||||
if (index >= 0) { | if (index >= 0) { | ||||
@@ -411,7 +418,7 @@ export default class AxisChart extends BaseChart { | |||||
this.tip.setValues( | this.tip.setValues( | ||||
dbi.xPos + this.tip.offset.x, | dbi.xPos + this.tip.offset.x, | ||||
dbi.yExtreme + this.tip.offset.y, | dbi.yExtreme + this.tip.offset.y, | ||||
{name: dbi.formattedLabel, value: ''}, | |||||
{ name: dbi.formattedLabel, value: '' }, | |||||
dbi.values, | dbi.values, | ||||
index | index | ||||
); | ); | ||||
@@ -422,34 +429,32 @@ export default class AxisChart extends BaseChart { | |||||
renderLegend() { | renderLegend() { | ||||
let s = this.data; | let s = this.data; | ||||
if(s.datasets.length > 1) { | |||||
this.legendArea.textContent = ''; | |||||
s.datasets.map((d, i) => { | |||||
let barWidth = AXIS_LEGEND_BAR_SIZE; | |||||
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right; | |||||
// let multiplier = s.datasets.length - i; | |||||
let rect = legendBar( | |||||
// rightEndPoint - multiplier * barWidth, // To right align | |||||
barWidth * i, | |||||
'0', | |||||
barWidth, | |||||
this.colors[i], | |||||
d.name, | |||||
this.config.truncateLegends); | |||||
this.legendArea.appendChild(rect); | |||||
}); | |||||
if (s.datasets.length > 1) { | |||||
super.renderLegend(s.datasets); | |||||
} | } | ||||
} | } | ||||
makeLegend(data, index, x_pos, y_pos) { | |||||
return legendDot( | |||||
x_pos, | |||||
y_pos + 5, // Extra offset | |||||
12, // size | |||||
3, // dot radius | |||||
this.colors[index], // fill | |||||
data.name, //label | |||||
null, // value | |||||
8.75, // base_font_size | |||||
this.config.truncateLegends // truncate legends | |||||
); | |||||
} | |||||
// Overlay | // Overlay | ||||
makeOverlay() { | makeOverlay() { | ||||
if(this.init) { | |||||
if (this.init) { | |||||
this.init = 0; | this.init = 0; | ||||
return; | return; | ||||
} | } | ||||
if(this.overlayGuides) { | |||||
if (this.overlayGuides) { | |||||
this.overlayGuides.forEach(g => { | this.overlayGuides.forEach(g => { | ||||
let o = g.overlay; | let o = g.overlay; | ||||
o.parentNode.removeChild(o); | o.parentNode.removeChild(o); | ||||
@@ -464,7 +469,7 @@ export default class AxisChart extends BaseChart { | |||||
}; | }; | ||||
}); | }); | ||||
if(this.state.currentIndex === undefined) { | |||||
if (this.state.currentIndex === undefined) { | |||||
this.state.currentIndex = this.state.datasetLength - 1; | this.state.currentIndex = this.state.datasetLength - 1; | ||||
} | } | ||||
@@ -478,7 +483,7 @@ export default class AxisChart extends BaseChart { | |||||
} | } | ||||
updateOverlayGuides() { | updateOverlayGuides() { | ||||
if(this.overlayGuides) { | |||||
if (this.overlayGuides) { | |||||
this.overlayGuides.forEach(g => { | this.overlayGuides.forEach(g => { | ||||
let o = g.overlay; | let o = g.overlay; | ||||
o.parentNode.removeChild(o); | o.parentNode.removeChild(o); | ||||
@@ -524,7 +529,7 @@ export default class AxisChart extends BaseChart { | |||||
this.setCurrentDataPoint(this.state.currentIndex + 1); | this.setCurrentDataPoint(this.state.currentIndex + 1); | ||||
} | } | ||||
getDataPoint(index=this.state.currentIndex) { | |||||
getDataPoint(index = this.state.currentIndex) { | |||||
let s = this.state; | let s = this.state; | ||||
let data_point = { | let data_point = { | ||||
index: index, | index: index, | ||||
@@ -537,9 +542,9 @@ export default class AxisChart extends BaseChart { | |||||
setCurrentDataPoint(index) { | setCurrentDataPoint(index) { | ||||
let s = this.state; | let s = this.state; | ||||
index = parseInt(index); | index = parseInt(index); | ||||
if(index < 0) index = 0; | |||||
if(index >= s.xAxis.labels.length) index = s.xAxis.labels.length - 1; | |||||
if(index === s.currentIndex) return; | |||||
if (index < 0) index = 0; | |||||
if (index >= s.xAxis.labels.length) index = s.xAxis.labels.length - 1; | |||||
if (index === s.currentIndex) return; | |||||
s.currentIndex = index; | s.currentIndex = index; | ||||
fire(this.parent, "data-select", this.getDataPoint()); | fire(this.parent, "data-select", this.getDataPoint()); | ||||
} | } | ||||
@@ -547,7 +552,7 @@ export default class AxisChart extends BaseChart { | |||||
// API | // API | ||||
addDataPoint(label, datasetValues, index=this.state.datasetLength) { | |||||
addDataPoint(label, datasetValues, index = this.state.datasetLength) { | |||||
super.addDataPoint(label, datasetValues, index); | super.addDataPoint(label, datasetValues, index); | ||||
this.data.labels.splice(index, 0, label); | this.data.labels.splice(index, 0, label); | ||||
this.data.datasets.map((d, i) => { | this.data.datasets.map((d, i) => { | ||||
@@ -556,7 +561,7 @@ export default class AxisChart extends BaseChart { | |||||
this.update(this.data); | this.update(this.data); | ||||
} | } | ||||
removeDataPoint(index = this.state.datasetLength-1) { | |||||
removeDataPoint(index = this.state.datasetLength - 1) { | |||||
if (this.data.labels.length <= 1) { | if (this.data.labels.length <= 1) { | ||||
return; | return; | ||||
} | } | ||||
@@ -568,7 +573,7 @@ export default class AxisChart extends BaseChart { | |||||
this.update(this.data); | this.update(this.data); | ||||
} | } | ||||
updateDataset(datasetValues, index=0) { | |||||
updateDataset(datasetValues, index = 0) { | |||||
this.data.datasets[index].values = datasetValues; | this.data.datasets[index].values = datasetValues; | ||||
this.update(this.data); | this.update(this.data); | ||||
} | } | ||||
@@ -577,7 +582,7 @@ export default class AxisChart extends BaseChart { | |||||
updateDatasets(datasets) { | updateDatasets(datasets) { | ||||
this.data.datasets.map((d, i) => { | this.data.datasets.map((d, i) => { | ||||
if(datasets[i]) { | |||||
if (datasets[i]) { | |||||
d.values = datasets[i]; | d.values = datasets[i]; | ||||
} | } | ||||
}); | }); | ||||
@@ -1,14 +1,19 @@ | |||||
import SvgTip from '../objects/SvgTip'; | import SvgTip from '../objects/SvgTip'; | ||||
import { $, isElementInViewport, getElementContentWidth, isHidden } from '../utils/dom'; | import { $, isElementInViewport, getElementContentWidth, isHidden } from '../utils/dom'; | ||||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText } from '../utils/draw'; | import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText } from '../utils/draw'; | ||||
import { BASE_MEASURES, getExtraHeight, getExtraWidth, getTopOffset, getLeftOffset, | |||||
INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS} from '../utils/constants'; | |||||
import { LEGEND_ITEM_WIDTH } from '../utils/constants'; | |||||
import { | |||||
BASE_MEASURES, getExtraHeight, getExtraWidth, getTopOffset, getLeftOffset, | |||||
INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS | |||||
} from '../utils/constants'; | |||||
import { getColor, isValidColor } from '../utils/colors'; | import { getColor, isValidColor } from '../utils/colors'; | ||||
import { runSMILAnimation } from '../utils/animation'; | import { runSMILAnimation } from '../utils/animation'; | ||||
import { downloadFile, prepareForExport } from '../utils/export'; | import { downloadFile, prepareForExport } from '../utils/export'; | ||||
import { deepClone } from '../utils/helpers'; | |||||
export default class BaseChart { | export default class BaseChart { | ||||
constructor(parent, options) { | constructor(parent, options) { | ||||
options = deepClone(options); | |||||
this.parent = typeof parent === 'string' | this.parent = typeof parent === 'string' | ||||
? document.querySelector(parent) | ? document.querySelector(parent) | ||||
@@ -30,17 +35,18 @@ export default class BaseChart { | |||||
this.config = { | this.config = { | ||||
showTooltip: 1, // calculate | showTooltip: 1, // calculate | ||||
showLegend: 1, // calculate | |||||
showLegend: (typeof options.showLegend !== 'undefined') ? options.showLegend : 1, | |||||
isNavigable: options.isNavigable || 0, | isNavigable: options.isNavigable || 0, | ||||
animate: (typeof options.animate !== 'undefined') ? options.animate : 1, | animate: (typeof options.animate !== 'undefined') ? options.animate : 1, | ||||
disableEntryAnimation: options.disableEntryAnimation || 0, | |||||
truncateLegends: options.truncateLegends || 1 | truncateLegends: options.truncateLegends || 1 | ||||
}; | }; | ||||
this.measures = JSON.parse(JSON.stringify(BASE_MEASURES)); | this.measures = JSON.parse(JSON.stringify(BASE_MEASURES)); | ||||
let m = this.measures; | let m = this.measures; | ||||
this.setMeasures(options); | this.setMeasures(options); | ||||
if(!this.title.length) { m.titleHeight = 0; } | |||||
if(!this.config.showLegend) m.legendHeight = 0; | |||||
if (!this.title.length) { m.titleHeight = 0; } | |||||
if (!this.config.showLegend) m.legendHeight = 0; | |||||
this.argHeight = options.height || m.baseHeight; | this.argHeight = options.height || m.baseHeight; | ||||
this.state = {}; | this.state = {}; | ||||
@@ -48,7 +54,7 @@ export default class BaseChart { | |||||
this.initTimeout = INIT_CHART_UPDATE_TIMEOUT; | this.initTimeout = INIT_CHART_UPDATE_TIMEOUT; | ||||
if(this.config.isNavigable) { | |||||
if (this.config.isNavigable) { | |||||
this.overlays = []; | this.overlays = []; | ||||
} | } | ||||
@@ -68,7 +74,7 @@ export default class BaseChart { | |||||
colors = (colors || []).concat(DEFAULT_COLORS[type]); | colors = (colors || []).concat(DEFAULT_COLORS[type]); | ||||
colors.forEach((string) => { | colors.forEach((string) => { | ||||
const color = getColor(string); | const color = getColor(string); | ||||
if(!isValidColor(color)) { | |||||
if (!isValidColor(color)) { | |||||
console.warn('"' + string + '" is not a valid color.'); | console.warn('"' + string + '" is not a valid color.'); | ||||
} else { | } else { | ||||
validColors.push(color); | validColors.push(color); | ||||
@@ -89,11 +95,16 @@ export default class BaseChart { | |||||
// Bind window events | // Bind window events | ||||
this.boundDrawFn = () => this.draw(true); | this.boundDrawFn = () => this.draw(true); | ||||
if (ResizeObserver) { | |||||
this.resizeObserver = new ResizeObserver(this.boundDrawFn); | |||||
this.resizeObserver.observe(this.parent); | |||||
} | |||||
window.addEventListener('resize', this.boundDrawFn); | window.addEventListener('resize', this.boundDrawFn); | ||||
window.addEventListener('orientationchange', this.boundDrawFn); | window.addEventListener('orientationchange', this.boundDrawFn); | ||||
} | } | ||||
destroy() { | destroy() { | ||||
if (this.resizeObserver) this.resizeObserver.disconnect(); | |||||
window.removeEventListener('resize', this.boundDrawFn); | window.removeEventListener('resize', this.boundDrawFn); | ||||
window.removeEventListener('orientationchange', this.boundDrawFn); | window.removeEventListener('orientationchange', this.boundDrawFn); | ||||
} | } | ||||
@@ -116,7 +127,7 @@ export default class BaseChart { | |||||
className: 'chart-container' | className: 'chart-container' | ||||
}; | }; | ||||
if(this.independentWidth) { | |||||
if (this.independentWidth) { | |||||
args.styles = { width: this.independentWidth + 'px' }; | args.styles = { width: this.independentWidth + 'px' }; | ||||
} | } | ||||
@@ -131,9 +142,9 @@ export default class BaseChart { | |||||
this.bindTooltip(); | this.bindTooltip(); | ||||
} | } | ||||
bindTooltip() {} | |||||
bindTooltip() { } | |||||
draw(onlyWidthChange=false, init=false) { | |||||
draw(onlyWidthChange = false, init = false) { | |||||
if (onlyWidthChange && isHidden(this.parent)) { | if (onlyWidthChange && isHidden(this.parent)) { | ||||
// Don't update anything if the chart is hidden | // Don't update anything if the chart is hidden | ||||
return; | return; | ||||
@@ -148,17 +159,19 @@ export default class BaseChart { | |||||
// this.components.forEach(c => c.make()); | // this.components.forEach(c => c.make()); | ||||
this.render(this.components, false); | this.render(this.components, false); | ||||
if(init) { | |||||
if (init) { | |||||
this.data = this.realData; | this.data = this.realData; | ||||
setTimeout(() => {this.update(this.data);}, this.initTimeout); | |||||
setTimeout(() => { this.update(this.data, true); }, this.initTimeout); | |||||
} | |||||
if (this.config.showLegend) { | |||||
this.renderLegend(); | |||||
} | } | ||||
this.renderLegend(); | |||||
this.setupNavigation(init); | this.setupNavigation(init); | ||||
} | } | ||||
calc() {} // builds state | |||||
calc() { } // builds state | |||||
updateWidth() { | updateWidth() { | ||||
this.baseWidth = getElementContentWidth(this.parent); | this.baseWidth = getElementContentWidth(this.parent); | ||||
@@ -166,7 +179,7 @@ export default class BaseChart { | |||||
} | } | ||||
makeChartArea() { | makeChartArea() { | ||||
if(this.svg) { | |||||
if (this.svg) { | |||||
this.container.removeChild(this.svg); | this.container.removeChild(this.svg); | ||||
} | } | ||||
let m = this.measures; | let m = this.measures; | ||||
@@ -179,7 +192,7 @@ export default class BaseChart { | |||||
); | ); | ||||
this.svgDefs = makeSVGDefs(this.svg); | this.svgDefs = makeSVGDefs(this.svg); | ||||
if(this.title.length) { | |||||
if (this.title.length) { | |||||
this.titleEL = makeText( | this.titleEL = makeText( | ||||
'title', | 'title', | ||||
m.margins.left, | m.margins.left, | ||||
@@ -199,7 +212,7 @@ export default class BaseChart { | |||||
`translate(${getLeftOffset(m)}, ${top})` | `translate(${getLeftOffset(m)}, ${top})` | ||||
); | ); | ||||
if(this.config.showLegend) { | |||||
if (this.config.showLegend) { | |||||
top += this.height + m.paddings.bottom; | top += this.height + m.paddings.bottom; | ||||
this.legendArea = makeSVGGroup( | this.legendArea = makeSVGGroup( | ||||
'chart-legend', | 'chart-legend', | ||||
@@ -207,9 +220,9 @@ export default class BaseChart { | |||||
); | ); | ||||
} | } | ||||
if(this.title.length) { this.svg.appendChild(this.titleEL); } | |||||
if (this.title.length) { this.svg.appendChild(this.titleEL); } | |||||
this.svg.appendChild(this.drawArea); | this.svg.appendChild(this.drawArea); | ||||
if(this.config.showLegend) { this.svg.appendChild(this.legendArea); } | |||||
if (this.config.showLegend) { this.svg.appendChild(this.legendArea); } | |||||
this.updateTipOffset(getLeftOffset(m), getTopOffset(m)); | this.updateTipOffset(getLeftOffset(m), getTopOffset(m)); | ||||
} | } | ||||
@@ -223,17 +236,18 @@ export default class BaseChart { | |||||
setupComponents() { this.components = new Map(); } | setupComponents() { this.components = new Map(); } | ||||
update(data) { | |||||
if(!data) { | |||||
console.error('No data to update.'); | |||||
} | |||||
update(data, drawing = false) { | |||||
if (!data) console.error('No data to update.'); | |||||
if (!drawing) data = deepClone(data); | |||||
const animate = drawing ? !this.config.disableEntryAnimation : this.config.animate; | |||||
this.data = this.prepareData(data); | this.data = this.prepareData(data); | ||||
this.calc(); // builds state | this.calc(); // builds state | ||||
this.render(this.components, this.config.animate); | |||||
this.render(this.components, animate); | |||||
} | } | ||||
render(components=this.components, animate=true) { | |||||
if(this.config.isNavigable) { | |||||
render(components = this.components, animate = true) { | |||||
if (this.config.isNavigable) { | |||||
// Remove all existing overlays | // Remove all existing overlays | ||||
this.overlays.map(o => o.parentNode.removeChild(o)); | this.overlays.map(o => o.parentNode.removeChild(o)); | ||||
// ref.parentNode.insertBefore(element, ref); | // ref.parentNode.insertBefore(element, ref); | ||||
@@ -243,7 +257,7 @@ export default class BaseChart { | |||||
components.forEach(c => { | components.forEach(c => { | ||||
elementsToAnimate = elementsToAnimate.concat(c.update(animate)); | elementsToAnimate = elementsToAnimate.concat(c.update(animate)); | ||||
}); | }); | ||||
if(elementsToAnimate.length > 0) { | |||||
if (elementsToAnimate.length > 0) { | |||||
runSMILAnimation(this.container, this.svg, elementsToAnimate); | runSMILAnimation(this.container, this.svg, elementsToAnimate); | ||||
setTimeout(() => { | setTimeout(() => { | ||||
components.forEach(c => c.make()); | components.forEach(c => c.make()); | ||||
@@ -256,18 +270,37 @@ export default class BaseChart { | |||||
} | } | ||||
updateNav() { | updateNav() { | ||||
if(this.config.isNavigable) { | |||||
if (this.config.isNavigable) { | |||||
this.makeOverlay(); | this.makeOverlay(); | ||||
this.bindUnits(); | this.bindUnits(); | ||||
} | } | ||||
} | } | ||||
renderLegend() {} | |||||
renderLegend(dataset) { | |||||
this.legendArea.textContent = ''; | |||||
let count = 0; | |||||
let y = 0; | |||||
dataset.map((data, index) => { | |||||
let divisor = Math.floor(this.width / LEGEND_ITEM_WIDTH); | |||||
if (count > divisor) { | |||||
count = 0; | |||||
y += this.config.legendRowHeight; | |||||
} | |||||
let x = LEGEND_ITEM_WIDTH * count; | |||||
let dot = this.makeLegend(data, index, x, y); | |||||
this.legendArea.appendChild(dot); | |||||
count++; | |||||
}); | |||||
} | |||||
makeLegend() { } | |||||
setupNavigation(init=false) { | |||||
if(!this.config.isNavigable) return; | |||||
setupNavigation(init = false) { | |||||
if (!this.config.isNavigable) return; | |||||
if(init) { | |||||
if (init) { | |||||
this.bindOverlay(); | this.bindOverlay(); | ||||
this.keyActions = { | this.keyActions = { | ||||
@@ -279,9 +312,9 @@ export default class BaseChart { | |||||
}; | }; | ||||
document.addEventListener('keydown', (e) => { | document.addEventListener('keydown', (e) => { | ||||
if(isElementInViewport(this.container)) { | |||||
if (isElementInViewport(this.container)) { | |||||
e = e || window.event; | e = e || window.event; | ||||
if(this.keyActions[e.keyCode]) { | |||||
if (this.keyActions[e.keyCode]) { | |||||
this.keyActions[e.keyCode](); | this.keyActions[e.keyCode](); | ||||
} | } | ||||
} | } | ||||
@@ -289,24 +322,24 @@ export default class BaseChart { | |||||
} | } | ||||
} | } | ||||
makeOverlay() {} | |||||
updateOverlay() {} | |||||
bindOverlay() {} | |||||
bindUnits() {} | |||||
makeOverlay() { } | |||||
updateOverlay() { } | |||||
bindOverlay() { } | |||||
bindUnits() { } | |||||
onLeftArrow() {} | |||||
onRightArrow() {} | |||||
onUpArrow() {} | |||||
onDownArrow() {} | |||||
onEnterKey() {} | |||||
onLeftArrow() { } | |||||
onRightArrow() { } | |||||
onUpArrow() { } | |||||
onDownArrow() { } | |||||
onEnterKey() { } | |||||
addDataPoint() {} | |||||
removeDataPoint() {} | |||||
addDataPoint() { } | |||||
removeDataPoint() { } | |||||
getDataPoint() {} | |||||
setCurrentDataPoint() {} | |||||
getDataPoint() { } | |||||
setCurrentDataPoint() { } | |||||
updateDataset() {} | |||||
updateDataset() { } | |||||
export() { | export() { | ||||
let chartSvg = prepareForExport(this.svg); | let chartSvg = prepareForExport(this.svg); | ||||
@@ -1,86 +1,35 @@ | |||||
import AggregationChart from './AggregationChart'; | |||||
import PieChart from './PieChart'; | |||||
import { getComponent } from '../objects/ChartComponents'; | import { getComponent } from '../objects/ChartComponents'; | ||||
import { getOffset } from '../utils/dom'; | |||||
import { getPositionByAngle } from '../utils/helpers'; | |||||
import { makeArcStrokePathStr, makeStrokeCircleStr } from '../utils/draw'; | import { makeArcStrokePathStr, makeStrokeCircleStr } from '../utils/draw'; | ||||
import { lightenDarkenColor } from '../utils/colors'; | |||||
import { transform } from '../utils/animation'; | import { transform } from '../utils/animation'; | ||||
import { FULL_ANGLE } from '../utils/constants'; | |||||
export default class DonutChart extends AggregationChart { | |||||
export default class DonutChart extends PieChart { | |||||
constructor(parent, args) { | constructor(parent, args) { | ||||
super(parent, args); | super(parent, args); | ||||
this.type = 'donut'; | |||||
this.initTimeout = 0; | |||||
this.init = 1; | |||||
this.setup(); | |||||
} | } | ||||
configure(args) { | configure(args) { | ||||
super.configure(args); | super.configure(args); | ||||
this.mouseMove = this.mouseMove.bind(this); | |||||
this.mouseLeave = this.mouseLeave.bind(this); | |||||
this.hoverRadio = args.hoverRadio || 0.1; | |||||
this.config.startAngle = args.startAngle || 0; | |||||
this.type = 'donut'; | |||||
this.sliceName = 'donutSlices'; | |||||
this.arcFunc = makeArcStrokePathStr; | |||||
this.shapeFunc = makeStrokeCircleStr; | |||||
this.clockWise = args.clockWise || false; | |||||
this.strokeWidth = args.strokeWidth || 30; | this.strokeWidth = args.strokeWidth || 30; | ||||
} | } | ||||
calc() { | |||||
super.calc(); | |||||
let s = this.state; | |||||
this.radius = | |||||
this.height > this.width | |||||
? this.center.x - this.strokeWidth / 2 | |||||
: this.center.y - this.strokeWidth / 2; | |||||
const { radius, clockWise } = this; | |||||
const prevSlicesProperties = s.slicesProperties || []; | |||||
s.sliceStrings = []; | |||||
s.slicesProperties = []; | |||||
let curAngle = 180 - this.config.startAngle; | |||||
s.sliceTotals.map((total, i) => { | |||||
const startAngle = curAngle; | |||||
const originDiffAngle = (total / s.grandTotal) * FULL_ANGLE; | |||||
const largeArc = originDiffAngle > 180 ? 1: 0; | |||||
const diffAngle = clockWise ? -originDiffAngle : originDiffAngle; | |||||
const endAngle = curAngle = curAngle + diffAngle; | |||||
const startPosition = getPositionByAngle(startAngle, radius); | |||||
const endPosition = getPositionByAngle(endAngle, radius); | |||||
const prevProperty = this.init && prevSlicesProperties[i]; | |||||
let curStart,curEnd; | |||||
if(this.init) { | |||||
curStart = prevProperty ? prevProperty.startPosition : startPosition; | |||||
curEnd = prevProperty ? prevProperty.endPosition : startPosition; | |||||
} else { | |||||
curStart = startPosition; | |||||
curEnd = endPosition; | |||||
} | |||||
const curPath = | |||||
originDiffAngle === 360 | |||||
? makeStrokeCircleStr(curStart, curEnd, this.center, this.radius, this.clockWise, largeArc) | |||||
: makeArcStrokePathStr(curStart, curEnd, this.center, this.radius, this.clockWise, largeArc); | |||||
s.sliceStrings.push(curPath); | |||||
s.slicesProperties.push({ | |||||
startPosition, | |||||
endPosition, | |||||
value: total, | |||||
total: s.grandTotal, | |||||
startAngle, | |||||
endAngle, | |||||
angle: diffAngle | |||||
}); | |||||
getRadius() { | |||||
return this.height > this.width | |||||
? this.center.x - this.strokeWidth / 2 | |||||
: this.center.y - this.strokeWidth / 2; | |||||
} | |||||
}); | |||||
this.init = 0; | |||||
resetHover(path, color) { | |||||
transform(path,'translate3d(0,0,0)'); | |||||
this.tip.hideTip(); | |||||
path.style.stroke = color; | |||||
} | } | ||||
setupComponents() { | setupComponents() { | ||||
@@ -88,9 +37,9 @@ export default class DonutChart extends AggregationChart { | |||||
let componentConfigs = [ | let componentConfigs = [ | ||||
[ | [ | ||||
'donutSlices', | |||||
{ }, | |||||
function() { | |||||
this.sliceName, | |||||
{}, | |||||
function () { | |||||
return { | return { | ||||
sliceStrings: s.sliceStrings, | sliceStrings: s.sliceStrings, | ||||
colors: this.colors, | colors: this.colors, | ||||
@@ -106,56 +55,4 @@ export default class DonutChart extends AggregationChart { | |||||
return [args[0], component]; | return [args[0], component]; | ||||
})); | })); | ||||
} | } | ||||
calTranslateByAngle(property){ | |||||
const{ radius, hoverRadio } = this; | |||||
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius); | |||||
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`; | |||||
} | |||||
hoverSlice(path,i,flag,e){ | |||||
if(!path) return; | |||||
const color = this.colors[i]; | |||||
if(flag) { | |||||
transform(path, this.calTranslateByAngle(this.state.slicesProperties[i])); | |||||
path.style.stroke = lightenDarkenColor(color, 50); | |||||
let g_off = getOffset(this.svg); | |||||
let x = e.pageX - g_off.left + 10; | |||||
let y = e.pageY - g_off.top - 10; | |||||
let title = (this.formatted_labels && this.formatted_labels.length > 0 | |||||
? this.formatted_labels[i] : this.state.labels[i]) + ': '; | |||||
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1); | |||||
this.tip.setValues(x, y, {name: title, value: percent + "%"}); | |||||
this.tip.showTip(); | |||||
} else { | |||||
transform(path,'translate3d(0,0,0)'); | |||||
this.tip.hideTip(); | |||||
path.style.stroke = color; | |||||
} | |||||
} | |||||
bindTooltip() { | |||||
this.container.addEventListener('mousemove', this.mouseMove); | |||||
this.container.addEventListener('mouseleave', this.mouseLeave); | |||||
} | |||||
mouseMove(e){ | |||||
const target = e.target; | |||||
let slices = this.components.get('donutSlices').store; | |||||
let prevIndex = this.curActiveSliceIndex; | |||||
let prevAcitve = this.curActiveSlice; | |||||
if(slices.includes(target)) { | |||||
let i = slices.indexOf(target); | |||||
this.hoverSlice(prevAcitve, prevIndex,false); | |||||
this.curActiveSlice = target; | |||||
this.curActiveSliceIndex = i; | |||||
this.hoverSlice(target, i, true, e); | |||||
} else { | |||||
this.mouseLeave(); | |||||
} | |||||
} | |||||
mouseLeave(){ | |||||
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false); | |||||
} | |||||
} | } |
@@ -1,11 +1,15 @@ | |||||
import BaseChart from './BaseChart'; | import BaseChart from './BaseChart'; | ||||
import { getComponent } from '../objects/ChartComponents'; | import { getComponent } from '../objects/ChartComponents'; | ||||
import { makeText, heatSquare } from '../utils/draw'; | import { makeText, heatSquare } from '../utils/draw'; | ||||
import { DAY_NAMES_SHORT, addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone, | |||||
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils'; | |||||
import { | |||||
DAY_NAMES_SHORT, addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone, | |||||
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK | |||||
} from '../utils/date-utils'; | |||||
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals'; | import { calcDistribution, getMaxCheckpoint } from '../utils/intervals'; | ||||
import { getExtraHeight, getExtraWidth, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE, | |||||
HEATMAP_GUTTER_SIZE } from '../utils/constants'; | |||||
import { | |||||
getExtraHeight, getExtraWidth, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE, | |||||
HEATMAP_GUTTER_SIZE | |||||
} from '../utils/constants'; | |||||
const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE; | const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE; | ||||
const ROW_HEIGHT = COL_WIDTH; | const ROW_HEIGHT = COL_WIDTH; | ||||
@@ -49,19 +53,19 @@ export default class Heatmap extends BaseChart { | |||||
+ getExtraWidth(this.measures); | + getExtraWidth(this.measures); | ||||
} | } | ||||
prepareData(data=this.data) { | |||||
if(data.start && data.end && data.start > data.end) { | |||||
prepareData(data = this.data) { | |||||
if (data.start && data.end && data.start > data.end) { | |||||
throw new Error('Start date cannot be greater than end date.'); | throw new Error('Start date cannot be greater than end date.'); | ||||
} | } | ||||
if(!data.start) { | |||||
if (!data.start) { | |||||
data.start = new Date(); | data.start = new Date(); | ||||
data.start.setFullYear( data.start.getFullYear() - 1 ); | |||||
data.start.setFullYear(data.start.getFullYear() - 1); | |||||
} | } | ||||
if(!data.end) { data.end = new Date(); } | |||||
if (!data.end) { data.end = new Date(); } | |||||
data.dataPoints = data.dataPoints || {}; | data.dataPoints = data.dataPoints || {}; | ||||
if(parseInt(Object.keys(data.dataPoints)[0]) > 100000) { | |||||
if (parseInt(Object.keys(data.dataPoints)[0]) > 100000) { | |||||
let points = {}; | let points = {}; | ||||
Object.keys(data.dataPoints).forEach(timestampSec => { | Object.keys(data.dataPoints).forEach(timestampSec => { | ||||
let date = new Date(timestampSec * NO_OF_MILLIS); | let date = new Date(timestampSec * NO_OF_MILLIS); | ||||
@@ -105,7 +109,7 @@ export default class Heatmap extends BaseChart { | |||||
.reduce((a, b) => a + b, 0) | .reduce((a, b) => a + b, 0) | ||||
* COL_WIDTH | * COL_WIDTH | ||||
}, | }, | ||||
function() { | |||||
function () { | |||||
return s.domainConfigs[i]; | return s.domainConfigs[i]; | ||||
}.bind(this) | }.bind(this) | ||||
@@ -120,8 +124,8 @@ export default class Heatmap extends BaseChart { | |||||
let y = 0; | let y = 0; | ||||
DAY_NAMES_SHORT.forEach((dayName, i) => { | DAY_NAMES_SHORT.forEach((dayName, i) => { | ||||
if([1, 3, 5].includes(i)) { | |||||
let dayText = makeText('subdomain-name', -COL_WIDTH/2, y, dayName, | |||||
if ([1, 3, 5].includes(i)) { | |||||
let dayText = makeText('subdomain-name', -COL_WIDTH / 2, y, dayName, | |||||
{ | { | ||||
fontSize: HEATMAP_SQUARE_SIZE, | fontSize: HEATMAP_SQUARE_SIZE, | ||||
dy: 8, | dy: 8, | ||||
@@ -135,7 +139,7 @@ export default class Heatmap extends BaseChart { | |||||
} | } | ||||
update(data) { | update(data) { | ||||
if(!data) { | |||||
if (!data) { | |||||
console.error('No data to update.'); | console.error('No data to update.'); | ||||
} | } | ||||
@@ -149,22 +153,22 @@ export default class Heatmap extends BaseChart { | |||||
this.components.forEach(comp => { | this.components.forEach(comp => { | ||||
let daySquares = comp.store; | let daySquares = comp.store; | ||||
let daySquare = e.target; | let daySquare = e.target; | ||||
if(daySquares.includes(daySquare)) { | |||||
if (daySquares.includes(daySquare)) { | |||||
let count = daySquare.getAttribute('data-value'); | let count = daySquare.getAttribute('data-value'); | ||||
let dateParts = daySquare.getAttribute('data-date').split('-'); | let dateParts = daySquare.getAttribute('data-date').split('-'); | ||||
let month = getMonthName(parseInt(dateParts[1])-1, true); | |||||
let month = getMonthName(parseInt(dateParts[1]) - 1, true); | |||||
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect(); | let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect(); | ||||
let width = parseInt(e.target.getAttribute('width')); | let width = parseInt(e.target.getAttribute('width')); | ||||
let x = pOff.left - gOff.left + width/2; | |||||
let x = pOff.left - gOff.left + width / 2; | |||||
let y = pOff.top - gOff.top; | let y = pOff.top - gOff.top; | ||||
let value = count + ' ' + this.countLabel; | let value = count + ' ' + this.countLabel; | ||||
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; | let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2]; | ||||
this.tip.setValues(x, y, {name: name, value: value, valueFirst: 1}, []); | |||||
this.tip.setValues(x, y, { name: name, value: value, valueFirst: 1 }, []); | |||||
this.tip.showTip(); | this.tip.showTip(); | ||||
} | } | ||||
}); | }); | ||||
@@ -183,7 +187,7 @@ export default class Heatmap extends BaseChart { | |||||
dy: 9 | dy: 9 | ||||
} | } | ||||
); | ); | ||||
x = (COL_WIDTH * 2) + COL_WIDTH/2; | |||||
x = (COL_WIDTH * 2) + COL_WIDTH / 2; | |||||
this.legendArea.appendChild(lessText); | this.legendArea.appendChild(lessText); | ||||
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => { | this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => { | ||||
@@ -192,7 +196,7 @@ export default class Heatmap extends BaseChart { | |||||
this.legendArea.appendChild(square); | this.legendArea.appendChild(square); | ||||
}); | }); | ||||
let moreTextX = x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH/4; | |||||
let moreTextX = x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH / 4; | |||||
let moreText = makeText('subdomain-name', moreTextX, y, 'More', | let moreText = makeText('subdomain-name', moreTextX, y, 'More', | ||||
{ | { | ||||
fontSize: HEATMAP_SQUARE_SIZE + 1, | fontSize: HEATMAP_SQUARE_SIZE + 1, | ||||
@@ -212,9 +216,9 @@ export default class Heatmap extends BaseChart { | |||||
let domainConfigs = []; | let domainConfigs = []; | ||||
let startOfMonth = clone(s.start); | let startOfMonth = clone(s.start); | ||||
for(var i = 0; i < noOfMonths; i++) { | |||||
for (var i = 0; i < noOfMonths; i++) { | |||||
let endDate = s.end; | let endDate = s.end; | ||||
if(!areInSameMonth(startOfMonth, s.end)) { | |||||
if (!areInSameMonth(startOfMonth, s.end)) { | |||||
let [month, year] = [startOfMonth.getMonth(), startOfMonth.getFullYear()]; | let [month, year] = [startOfMonth.getMonth(), startOfMonth.getFullYear()]; | ||||
endDate = getLastDateInMonth(month, year); | endDate = getLastDateInMonth(month, year); | ||||
} | } | ||||
@@ -227,7 +231,7 @@ export default class Heatmap extends BaseChart { | |||||
return domainConfigs; | return domainConfigs; | ||||
} | } | ||||
getDomainConfig(startDate, endDate='') { | |||||
getDomainConfig(startDate, endDate = '') { | |||||
let [month, year] = [startDate.getMonth(), startDate.getFullYear()]; | let [month, year] = [startDate.getMonth(), startDate.getFullYear()]; | ||||
let startOfWeek = setDayToSunday(startDate); // TODO: Monday as well | let startOfWeek = setDayToSunday(startDate); // TODO: Monday as well | ||||
endDate = clone(endDate) || getLastDateInMonth(month, year); | endDate = clone(endDate) || getLastDateInMonth(month, year); | ||||
@@ -241,7 +245,7 @@ export default class Heatmap extends BaseChart { | |||||
let noOfMonthWeeks = getWeeksBetween(startOfWeek, endDate); | let noOfMonthWeeks = getWeeksBetween(startOfWeek, endDate); | ||||
let cols = [], col; | let cols = [], col; | ||||
for(var i = 0; i < noOfMonthWeeks; i++) { | |||||
for (var i = 0; i < noOfMonthWeeks; i++) { | |||||
col = this.getCol(startOfWeek, month); | col = this.getCol(startOfWeek, month); | ||||
cols.push(col); | cols.push(col); | ||||
@@ -249,7 +253,7 @@ export default class Heatmap extends BaseChart { | |||||
addDays(startOfWeek, 1); | addDays(startOfWeek, 1); | ||||
} | } | ||||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue !== undefined) { | |||||
if (col[NO_OF_DAYS_IN_WEEK - 1].dataValue !== undefined) { | |||||
addDays(startOfWeek, 1); | addDays(startOfWeek, 1); | ||||
cols.push(this.getCol(startOfWeek, month, true)); | cols.push(this.getCol(startOfWeek, month, true)); | ||||
} | } | ||||
@@ -266,13 +270,13 @@ export default class Heatmap extends BaseChart { | |||||
let currentDate = clone(startDate); | let currentDate = clone(startDate); | ||||
let col = []; | let col = []; | ||||
for(var i = 0; i < NO_OF_DAYS_IN_WEEK; i++, addDays(currentDate, 1)) { | |||||
for (var i = 0; i < NO_OF_DAYS_IN_WEEK; i++, addDays(currentDate, 1)) { | |||||
let config = {}; | let config = {}; | ||||
// Non-generic adjustment for entire heatmap, needs state | // Non-generic adjustment for entire heatmap, needs state | ||||
let currentDateWithinData = currentDate >= s.start && currentDate <= s.end; | let currentDateWithinData = currentDate >= s.start && currentDate <= s.end; | ||||
if(empty || currentDate.getMonth() !== month || !currentDateWithinData) { | |||||
if (empty || currentDate.getMonth() !== month || !currentDateWithinData) { | |||||
config.yyyyMmDd = getYyyyMmDd(currentDate); | config.yyyyMmDd = getYyyyMmDd(currentDate); | ||||
} else { | } else { | ||||
config = this.getSubDomainConfig(currentDate); | config = this.getSubDomainConfig(currentDate); | ||||
@@ -1,173 +0,0 @@ | |||||
import AxisChart from './AxisChart'; | |||||
import { Y_AXIS_MARGIN } from '../utils/constants'; | |||||
// import { ChartComponent } from '../objects/ChartComponents'; | |||||
import { floatTwo } from '../utils/helpers'; | |||||
export default class MultiAxisChart extends AxisChart { | |||||
constructor(args) { | |||||
super(args); | |||||
// this.unitType = args.unitType || 'line'; | |||||
// this.setup(); | |||||
} | |||||
preSetup() { | |||||
this.type = 'multiaxis'; | |||||
} | |||||
setMeasures() { | |||||
super.setMeasures(); | |||||
let noOfLeftAxes = this.data.datasets.filter(d => d.axisPosition === 'left').length; | |||||
this.measures.margins.left = (noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN; | |||||
this.measures.margins.right = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN; | |||||
} | |||||
prepareYAxis() { } | |||||
prepareData(data) { | |||||
super.prepareData(data); | |||||
let sets = this.state.datasets; | |||||
// let axesLeft = sets.filter(d => d.axisPosition === 'left'); | |||||
// let axesRight = sets.filter(d => d.axisPosition === 'right'); | |||||
// let axesNone = sets.filter(d => !d.axisPosition || | |||||
// !['left', 'right'].includes(d.axisPosition)); | |||||
let leftCount = 0, rightCount = 0; | |||||
sets.forEach((d, i) => { | |||||
d.yAxis = { | |||||
position: d.axisPosition, | |||||
index: d.axisPosition === 'left' ? leftCount++ : rightCount++ | |||||
}; | |||||
}); | |||||
} | |||||
configure(args) { | |||||
super.configure(args); | |||||
this.config.xAxisMode = args.xAxisMode || 'tick'; | |||||
this.config.yAxisMode = args.yAxisMode || 'span'; | |||||
} | |||||
// setUnitWidthAndXOffset() { | |||||
// this.state.unitWidth = this.width/(this.state.datasetLength); | |||||
// this.state.xOffset = this.state.unitWidth/2; | |||||
// } | |||||
configUnits() { | |||||
this.unitArgs = { | |||||
type: 'bar', | |||||
args: { | |||||
spaceWidth: this.state.unitWidth/2, | |||||
} | |||||
}; | |||||
} | |||||
setYAxis() { | |||||
this.state.datasets.map(d => { | |||||
this.calcYAxisParameters(d.yAxis, d.values, this.unitType === 'line'); | |||||
}); | |||||
} | |||||
calcYUnits() { | |||||
this.state.datasets.map(d => { | |||||
d.positions = d.values.map(val => floatTwo(d.yAxis.zeroLine - val * d.yAxis.scaleMultiplier)); | |||||
}); | |||||
} | |||||
// TODO: function doesn't exist, handle with components | |||||
renderConstants() { | |||||
this.state.datasets.map(d => { | |||||
let guidePos = d.yAxis.position === 'left' | |||||
? -1 * d.yAxis.index * Y_AXIS_MARGIN | |||||
: this.width + d.yAxis.index * Y_AXIS_MARGIN; | |||||
this.renderer.xLine(guidePos, '', { | |||||
pos:'top', | |||||
mode: 'span', | |||||
stroke: this.colors[i], | |||||
className: 'y-axis-guide' | |||||
}) | |||||
}); | |||||
} | |||||
getYAxesComponents() { | |||||
return this.data.datasets.map((e, i) => { | |||||
return new ChartComponent({ | |||||
layerClass: 'y axis y-axis-' + i, | |||||
make: () => { | |||||
let yAxis = this.state.datasets[i].yAxis; | |||||
this.renderer.setZeroline(yAxis.zeroline); | |||||
let options = { | |||||
pos: yAxis.position, | |||||
mode: 'tick', | |||||
offset: yAxis.index * Y_AXIS_MARGIN, | |||||
stroke: this.colors[i] | |||||
}; | |||||
return yAxis.positions.map((position, j) => | |||||
this.renderer.yLine(position, yAxis.labels[j], options) | |||||
); | |||||
}, | |||||
animate: () => {} | |||||
}); | |||||
}); | |||||
} | |||||
// TODO remove renderer zeroline from above and below | |||||
getChartComponents() { | |||||
return this.data.datasets.map((d, index) => { | |||||
return new ChartComponent({ | |||||
layerClass: 'dataset-units dataset-' + index, | |||||
make: () => { | |||||
let d = this.state.datasets[index]; | |||||
let unitType = this.unitArgs; | |||||
// the only difference, should be tied to datasets or default | |||||
this.renderer.setZeroline(d.yAxis.zeroLine); | |||||
return d.positions.map((y, j) => { | |||||
return this.renderer[unitType.type]( | |||||
this.state.xAxisPositions[j], | |||||
y, | |||||
unitType.args, | |||||
this.colors[index], | |||||
j, | |||||
index, | |||||
this.state.datasetLength | |||||
); | |||||
}); | |||||
}, | |||||
animate: (svgUnits) => { | |||||
let d = this.state.datasets[index]; | |||||
let unitType = this.unitArgs.type; | |||||
// have been updated in axis render; | |||||
let newX = this.state.xAxisPositions; | |||||
let newY = this.state.datasets[index].positions; | |||||
let lastUnit = svgUnits[svgUnits.length - 1]; | |||||
let parentNode = lastUnit.parentNode; | |||||
if(this.oldState.xExtra > 0) { | |||||
for(var i = 0; i<this.oldState.xExtra; i++) { | |||||
let unit = lastUnit.cloneNode(true); | |||||
parentNode.appendChild(unit); | |||||
svgUnits.push(unit); | |||||
} | |||||
} | |||||
this.renderer.setZeroline(d.yAxis.zeroLine); | |||||
svgUnits.map((unit, i) => { | |||||
if(newX[i] === undefined || newY[i] === undefined) return; | |||||
this.elementsToAnimate.push(this.renderer['animate' + unitType]( | |||||
unit, // unit, with info to replace where it came from in the data | |||||
newX[i], | |||||
newY[i], | |||||
index, | |||||
this.state.noOfDatasets | |||||
)); | |||||
}); | |||||
} | |||||
}); | |||||
}); | |||||
} | |||||
} |
@@ -1,7 +1,7 @@ | |||||
import AggregationChart from './AggregationChart'; | import AggregationChart from './AggregationChart'; | ||||
import { getOffset } from '../utils/dom'; | import { getOffset } from '../utils/dom'; | ||||
import { getComponent } from '../objects/ChartComponents'; | import { getComponent } from '../objects/ChartComponents'; | ||||
import { PERCENTAGE_BAR_DEFAULT_HEIGHT, PERCENTAGE_BAR_DEFAULT_DEPTH } from '../utils/constants'; | |||||
import { PERCENTAGE_BAR_DEFAULT_HEIGHT, getExtraHeight } from '../utils/constants'; | |||||
export default class PercentageChart extends AggregationChart { | export default class PercentageChart extends AggregationChart { | ||||
constructor(parent, args) { | constructor(parent, args) { | ||||
@@ -16,11 +16,13 @@ export default class PercentageChart extends AggregationChart { | |||||
let b = this.barOptions; | let b = this.barOptions; | ||||
b.height = b.height || PERCENTAGE_BAR_DEFAULT_HEIGHT; | b.height = b.height || PERCENTAGE_BAR_DEFAULT_HEIGHT; | ||||
b.depth = b.depth || PERCENTAGE_BAR_DEFAULT_DEPTH; | |||||
m.paddings.right = 30; | m.paddings.right = 30; | ||||
m.legendHeight = 60; | |||||
m.baseHeight = (b.height + b.depth * 0.5) * 8; | |||||
m.paddings.top = 60; | |||||
m.paddings.bottom = 0; | |||||
m.legendHeight = 80; | |||||
m.baseHeight = (b.height) * 8 + getExtraHeight(m); | |||||
} | } | ||||
setupComponents() { | setupComponents() { | ||||
@@ -31,9 +33,8 @@ export default class PercentageChart extends AggregationChart { | |||||
'percentageBars', | 'percentageBars', | ||||
{ | { | ||||
barHeight: this.barOptions.height, | barHeight: this.barOptions.height, | ||||
barDepth: this.barOptions.depth, | |||||
}, | }, | ||||
function() { | |||||
function () { | |||||
return { | return { | ||||
xPositions: s.xPositions, | xPositions: s.xPositions, | ||||
widths: s.widths, | widths: s.widths, | ||||
@@ -73,18 +74,19 @@ export default class PercentageChart extends AggregationChart { | |||||
this.container.addEventListener('mousemove', (e) => { | this.container.addEventListener('mousemove', (e) => { | ||||
let bars = this.components.get('percentageBars').store; | let bars = this.components.get('percentageBars').store; | ||||
let bar = e.target; | let bar = e.target; | ||||
if(bars.includes(bar)) { | |||||
if (bars.includes(bar)) { | |||||
let i = bars.indexOf(bar); | let i = bars.indexOf(bar); | ||||
let gOff = getOffset(this.container), pOff = getOffset(bar); | let gOff = getOffset(this.container), pOff = getOffset(bar); | ||||
let x = pOff.left - gOff.left + parseInt(bar.getAttribute('width'))/2; | |||||
let width = bar.getAttribute('width') || bar.getBoundingClientRect().width; | |||||
let x = pOff.left - gOff.left + parseInt(width) / 2; | |||||
let y = pOff.top - gOff.top; | let y = pOff.top - gOff.top; | ||||
let title = (this.formattedLabels && this.formattedLabels.length>0 | |||||
let title = (this.formattedLabels && this.formattedLabels.length > 0 | |||||
? this.formattedLabels[i] : this.state.labels[i]) + ': '; | ? this.formattedLabels[i] : this.state.labels[i]) + ': '; | ||||
let fraction = s.sliceTotals[i]/s.grandTotal; | |||||
let fraction = s.sliceTotals[i] / s.grandTotal; | |||||
this.tip.setValues(x, y, {name: title, value: (fraction*100).toFixed(1) + "%"}); | |||||
this.tip.setValues(x, y, { name: title, value: (fraction * 100).toFixed(1) + "%" }); | |||||
this.tip.showTip(); | this.tip.showTip(); | ||||
} | } | ||||
}); | }); | ||||
@@ -3,14 +3,12 @@ import { getComponent } from '../objects/ChartComponents'; | |||||
import { getOffset } from '../utils/dom'; | import { getOffset } from '../utils/dom'; | ||||
import { getPositionByAngle } from '../utils/helpers'; | import { getPositionByAngle } from '../utils/helpers'; | ||||
import { makeArcPathStr, makeCircleStr } from '../utils/draw'; | import { makeArcPathStr, makeCircleStr } from '../utils/draw'; | ||||
import { lightenDarkenColor } from '../utils/colors'; | |||||
import { transform } from '../utils/animation'; | import { transform } from '../utils/animation'; | ||||
import { FULL_ANGLE } from '../utils/constants'; | import { FULL_ANGLE } from '../utils/constants'; | ||||
export default class PieChart extends AggregationChart { | export default class PieChart extends AggregationChart { | ||||
constructor(parent, args) { | constructor(parent, args) { | ||||
super(parent, args); | super(parent, args); | ||||
this.type = 'pie'; | |||||
this.initTimeout = 0; | this.initTimeout = 0; | ||||
this.init = 1; | this.init = 1; | ||||
@@ -25,13 +23,23 @@ export default class PieChart extends AggregationChart { | |||||
this.hoverRadio = args.hoverRadio || 0.1; | this.hoverRadio = args.hoverRadio || 0.1; | ||||
this.config.startAngle = args.startAngle || 0; | this.config.startAngle = args.startAngle || 0; | ||||
this.type = 'pie'; | |||||
this.sliceName = 'pieSlices'; | |||||
this.arcFunc = makeArcPathStr; | |||||
this.shapeFunc = makeCircleStr; | |||||
this.clockWise = args.clockWise || false; | this.clockWise = args.clockWise || false; | ||||
} | } | ||||
getRadius() { | |||||
return this.height > this.width ? this.center.x : this.center.y; | |||||
} | |||||
calc() { | calc() { | ||||
super.calc(); | super.calc(); | ||||
let s = this.state; | let s = this.state; | ||||
this.radius = (this.height > this.width ? this.center.x : this.center.y); | |||||
this.radius = this.getRadius(); | |||||
const { radius, clockWise } = this; | const { radius, clockWise } = this; | ||||
@@ -42,7 +50,7 @@ export default class PieChart extends AggregationChart { | |||||
s.sliceTotals.map((total, i) => { | s.sliceTotals.map((total, i) => { | ||||
const startAngle = curAngle; | const startAngle = curAngle; | ||||
const originDiffAngle = (total / s.grandTotal) * FULL_ANGLE; | const originDiffAngle = (total / s.grandTotal) * FULL_ANGLE; | ||||
const largeArc = originDiffAngle > 180 ? 1: 0; | |||||
const largeArc = originDiffAngle > 180 ? 1 : 0; | |||||
const diffAngle = clockWise ? -originDiffAngle : originDiffAngle; | const diffAngle = clockWise ? -originDiffAngle : originDiffAngle; | ||||
const endAngle = curAngle = curAngle + diffAngle; | const endAngle = curAngle = curAngle + diffAngle; | ||||
const startPosition = getPositionByAngle(startAngle, radius); | const startPosition = getPositionByAngle(startAngle, radius); | ||||
@@ -50,8 +58,8 @@ export default class PieChart extends AggregationChart { | |||||
const prevProperty = this.init && prevSlicesProperties[i]; | const prevProperty = this.init && prevSlicesProperties[i]; | ||||
let curStart,curEnd; | |||||
if(this.init) { | |||||
let curStart, curEnd; | |||||
if (this.init) { | |||||
curStart = prevProperty ? prevProperty.startPosition : startPosition; | curStart = prevProperty ? prevProperty.startPosition : startPosition; | ||||
curEnd = prevProperty ? prevProperty.endPosition : startPosition; | curEnd = prevProperty ? prevProperty.endPosition : startPosition; | ||||
} else { | } else { | ||||
@@ -60,8 +68,8 @@ export default class PieChart extends AggregationChart { | |||||
} | } | ||||
const curPath = | const curPath = | ||||
originDiffAngle === 360 | originDiffAngle === 360 | ||||
? makeCircleStr(curStart, curEnd, this.center, this.radius, clockWise, largeArc) | |||||
: makeArcPathStr(curStart, curEnd, this.center, this.radius, clockWise, largeArc); | |||||
? this.shapeFunc(curStart, curEnd, this.center, this.radius, clockWise, largeArc) | |||||
: this.arcFunc(curStart, curEnd, this.center, this.radius, clockWise, largeArc); | |||||
s.sliceStrings.push(curPath); | s.sliceStrings.push(curPath); | ||||
s.slicesProperties.push({ | s.slicesProperties.push({ | ||||
@@ -84,8 +92,8 @@ export default class PieChart extends AggregationChart { | |||||
let componentConfigs = [ | let componentConfigs = [ | ||||
[ | [ | ||||
'pieSlices', | 'pieSlices', | ||||
{ }, | |||||
function() { | |||||
{}, | |||||
function () { | |||||
return { | return { | ||||
sliceStrings: s.sliceStrings, | sliceStrings: s.sliceStrings, | ||||
colors: this.colors | colors: this.colors | ||||
@@ -101,46 +109,51 @@ export default class PieChart extends AggregationChart { | |||||
})); | })); | ||||
} | } | ||||
calTranslateByAngle(property){ | |||||
const{radius,hoverRadio} = this; | |||||
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius); | |||||
calTranslateByAngle(property) { | |||||
const { radius, hoverRadio } = this; | |||||
const position = getPositionByAngle(property.startAngle + (property.angle / 2), radius); | |||||
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`; | return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`; | ||||
} | } | ||||
hoverSlice(path,i,flag,e){ | |||||
if(!path) return; | |||||
hoverSlice(path, i, flag, e) { | |||||
if (!path) return; | |||||
const color = this.colors[i]; | const color = this.colors[i]; | ||||
if(flag) { | |||||
if (flag) { | |||||
transform(path, this.calTranslateByAngle(this.state.slicesProperties[i])); | transform(path, this.calTranslateByAngle(this.state.slicesProperties[i])); | ||||
path.style.fill = lightenDarkenColor(color, 50); | |||||
// path.style.fill = lightenDarkenColor(color, 50); | |||||
// path.style.stroke = lightenDarkenColor(color, 50); | |||||
let g_off = getOffset(this.svg); | let g_off = getOffset(this.svg); | ||||
let x = e.pageX - g_off.left + 10; | let x = e.pageX - g_off.left + 10; | ||||
let y = e.pageY - g_off.top - 10; | let y = e.pageY - g_off.top - 10; | ||||
let title = (this.formatted_labels && this.formatted_labels.length > 0 | let title = (this.formatted_labels && this.formatted_labels.length > 0 | ||||
? this.formatted_labels[i] : this.state.labels[i]) + ': '; | ? this.formatted_labels[i] : this.state.labels[i]) + ': '; | ||||
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1); | let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1); | ||||
this.tip.setValues(x, y, {name: title, value: percent + "%"}); | |||||
this.tip.setValues(x, y, { name: title, value: percent + "%" }); | |||||
this.tip.showTip(); | this.tip.showTip(); | ||||
} else { | } else { | ||||
transform(path,'translate3d(0,0,0)'); | |||||
this.tip.hideTip(); | |||||
path.style.fill = color; | |||||
this.resetHover(path, color) | |||||
} | } | ||||
} | } | ||||
resetHover(path, color) { | |||||
transform(path, 'translate3d(0,0,0)'); | |||||
this.tip.hideTip(); | |||||
path.style.fill = color; | |||||
} | |||||
bindTooltip() { | bindTooltip() { | ||||
this.container.addEventListener('mousemove', this.mouseMove); | this.container.addEventListener('mousemove', this.mouseMove); | ||||
this.container.addEventListener('mouseleave', this.mouseLeave); | this.container.addEventListener('mouseleave', this.mouseLeave); | ||||
} | } | ||||
mouseMove(e){ | |||||
mouseMove(e) { | |||||
const target = e.target; | const target = e.target; | ||||
let slices = this.components.get('pieSlices').store; | |||||
let slices = this.components.get(this.sliceName).store; | |||||
let prevIndex = this.curActiveSliceIndex; | let prevIndex = this.curActiveSliceIndex; | ||||
let prevAcitve = this.curActiveSlice; | |||||
if(slices.includes(target)) { | |||||
let prevActive = this.curActiveSlice; | |||||
if (slices.includes(target)) { | |||||
let i = slices.indexOf(target); | let i = slices.indexOf(target); | ||||
this.hoverSlice(prevAcitve, prevIndex,false); | |||||
this.hoverSlice(prevActive, prevIndex, false); | |||||
this.curActiveSlice = target; | this.curActiveSlice = target; | ||||
this.curActiveSliceIndex = i; | this.curActiveSliceIndex = i; | ||||
this.hoverSlice(target, i, true, e); | this.hoverSlice(target, i, true, e); | ||||
@@ -149,7 +162,7 @@ export default class PieChart extends AggregationChart { | |||||
} | } | ||||
} | } | ||||
mouseLeave(){ | |||||
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false); | |||||
mouseLeave() { | |||||
this.hoverSlice(this.curActiveSlice, this.curActiveSliceIndex, false); | |||||
} | } | ||||
} | } |
@@ -3,7 +3,7 @@ import * as Charts from './chart'; | |||||
let frappe = { }; | let frappe = { }; | ||||
frappe.NAME = 'Frappe Charts'; | frappe.NAME = 'Frappe Charts'; | ||||
frappe.VERSION = '1.5.5'; | |||||
frappe.VERSION = '2.0.0-rc22'; | |||||
frappe = Object.assign({ }, frappe, Charts); | frappe = Object.assign({ }, frappe, Charts); | ||||
@@ -1,8 +1,10 @@ | |||||
import { makeSVGGroup } from '../utils/draw'; | import { makeSVGGroup } from '../utils/draw'; | ||||
import { makeText, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw'; | import { makeText, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw'; | ||||
import { equilizeNoOfElements } from '../utils/draw-utils'; | import { equilizeNoOfElements } from '../utils/draw-utils'; | ||||
import { translateHoriLine, translateVertLine, animateRegion, animateBar, | |||||
animateDot, animatePath, animatePathStr } from '../utils/animate'; | |||||
import { | |||||
translateHoriLine, translateVertLine, animateRegion, animateBar, | |||||
animateDot, animatePath, animatePathStr | |||||
} from '../utils/animate'; | |||||
import { getMonthName } from '../utils/date-utils'; | import { getMonthName } from '../utils/date-utils'; | ||||
class ChartComponent { | class ChartComponent { | ||||
@@ -27,7 +29,7 @@ class ChartComponent { | |||||
this.labels = []; | this.labels = []; | ||||
this.layerClass = layerClass; | this.layerClass = layerClass; | ||||
this.layerClass = typeof(this.layerClass) === 'function' | |||||
this.layerClass = typeof (this.layerClass) === 'function' | |||||
? this.layerClass() : this.layerClass; | ? this.layerClass() : this.layerClass; | ||||
this.refresh(); | this.refresh(); | ||||
@@ -61,7 +63,7 @@ class ChartComponent { | |||||
update(animate = true) { | update(animate = true) { | ||||
this.refresh(); | this.refresh(); | ||||
let animateElements = []; | let animateElements = []; | ||||
if(animate) { | |||||
if (animate) { | |||||
animateElements = this.animateElements(this.data) || []; | animateElements = this.animateElements(this.data) || []; | ||||
} | } | ||||
return animateElements; | return animateElements; | ||||
@@ -86,7 +88,7 @@ let componentConfigs = { | |||||
pieSlices: { | pieSlices: { | ||||
layerClass: 'pie-slices', | layerClass: 'pie-slices', | ||||
makeElements(data) { | makeElements(data) { | ||||
return data.sliceStrings.map((s, i) =>{ | |||||
return data.sliceStrings.map((s, i) => { | |||||
let slice = makePath(s, 'pie-path', 'none', data.colors[i]); | let slice = makePath(s, 'pie-path', 'none', data.colors[i]); | ||||
slice.style.transition = 'transform .3s;'; | slice.style.transition = 'transform .3s;'; | ||||
return slice; | return slice; | ||||
@@ -102,16 +104,20 @@ let componentConfigs = { | |||||
percentageBars: { | percentageBars: { | ||||
layerClass: 'percentage-bars', | layerClass: 'percentage-bars', | ||||
makeElements(data) { | makeElements(data) { | ||||
return data.xPositions.map((x, i) =>{ | |||||
const numberOfPoints = data.xPositions.length; | |||||
return data.xPositions.map((x, i) => { | |||||
let y = 0; | let y = 0; | ||||
let bar = percentageBar(x, y, data.widths[i], | |||||
this.constants.barHeight, this.constants.barDepth, data.colors[i]); | |||||
let isLast = i == numberOfPoints - 1; | |||||
let isFirst = i == 0; | |||||
let bar = percentageBar(x, y, data.widths[i], this.constants.barHeight, isFirst, isLast, data.colors[i]); | |||||
return bar; | return bar; | ||||
}); | }); | ||||
}, | }, | ||||
animateElements(newData) { | animateElements(newData) { | ||||
if(newData) return []; | |||||
if (newData) return []; | |||||
} | } | ||||
}, | }, | ||||
yAxis: { | yAxis: { | ||||
@@ -119,7 +125,12 @@ let componentConfigs = { | |||||
makeElements(data) { | makeElements(data) { | ||||
return data.positions.map((position, i) => | return data.positions.map((position, i) => | ||||
yLine(position, data.labels[i], this.constants.width, | yLine(position, data.labels[i], this.constants.width, | ||||
{mode: this.constants.mode, pos: this.constants.pos, shortenNumbers: this.constants.shortenNumbers}) | |||||
{ | |||||
mode: this.constants.mode, | |||||
pos: this.constants.pos, | |||||
shortenNumbers: this.constants.shortenNumbers, | |||||
numberFormatter: this.constants.numberFormatter | |||||
}) | |||||
); | ); | ||||
}, | }, | ||||
@@ -150,7 +161,7 @@ let componentConfigs = { | |||||
makeElements(data) { | makeElements(data) { | ||||
return data.positions.map((position, i) => | return data.positions.map((position, i) => | ||||
xLine(position, data.calcLabels[i], this.constants.height, | xLine(position, data.calcLabels[i], this.constants.height, | ||||
{mode: this.constants.mode, pos: this.constants.pos}) | |||||
{ mode: this.constants.mode, pos: this.constants.pos }) | |||||
); | ); | ||||
}, | }, | ||||
@@ -181,7 +192,7 @@ let componentConfigs = { | |||||
makeElements(data) { | makeElements(data) { | ||||
return data.map(m => | return data.map(m => | ||||
yMarker(m.position, m.label, this.constants.width, | yMarker(m.position, m.label, this.constants.width, | ||||
{labelPos: m.options.labelPos, mode: 'span', lineType: 'dashed'}) | |||||
{ labelPos: m.options.labelPos, stroke: m.options.stroke, mode: 'span', lineType: m.options.lineType }) | |||||
); | ); | ||||
}, | }, | ||||
animateElements(newData) { | animateElements(newData) { | ||||
@@ -214,7 +225,7 @@ let componentConfigs = { | |||||
makeElements(data) { | makeElements(data) { | ||||
return data.map(r => | return data.map(r => | ||||
yRegion(r.startPos, r.endPos, this.constants.width, | yRegion(r.startPos, r.endPos, this.constants.width, | ||||
r.label, {labelPos: r.options.labelPos}) | |||||
r.label, { labelPos: r.options.labelPos, stroke: r.options.stroke, fill: r.options.fill }) | |||||
); | ); | ||||
}, | }, | ||||
animateElements(newData) { | animateElements(newData) { | ||||
@@ -250,16 +261,16 @@ let componentConfigs = { | |||||
}, | }, | ||||
heatDomain: { | heatDomain: { | ||||
layerClass: function() { return 'heat-domain domain-' + this.constants.index; }, | |||||
layerClass: function () { return 'heat-domain domain-' + this.constants.index; }, | |||||
makeElements(data) { | makeElements(data) { | ||||
let {index, colWidth, rowHeight, squareSize, radius, xTranslate} = this.constants; | |||||
let { index, colWidth, rowHeight, squareSize, radius, xTranslate } = this.constants; | |||||
let monthNameHeight = -12; | let monthNameHeight = -12; | ||||
let x = xTranslate, y = 0; | let x = xTranslate, y = 0; | ||||
this.serializedSubDomains = []; | this.serializedSubDomains = []; | ||||
data.cols.map((week, weekNo) => { | data.cols.map((week, weekNo) => { | ||||
if(weekNo === 1) { | |||||
if (weekNo === 1) { | |||||
this.labels.push( | this.labels.push( | ||||
makeText('domain-name', x, monthNameHeight, getMonthName(index, true).toUpperCase(), | makeText('domain-name', x, monthNameHeight, getMonthName(index, true).toUpperCase(), | ||||
{ | { | ||||
@@ -269,7 +280,7 @@ let componentConfigs = { | |||||
); | ); | ||||
} | } | ||||
week.map((day, i) => { | week.map((day, i) => { | ||||
if(day.fill) { | |||||
if (day.fill) { | |||||
let data = { | let data = { | ||||
'data-date': day.yyyyMmDd, | 'data-date': day.yyyyMmDd, | ||||
'data-value': day.dataValue, | 'data-value': day.dataValue, | ||||
@@ -288,12 +299,12 @@ let componentConfigs = { | |||||
}, | }, | ||||
animateElements(newData) { | animateElements(newData) { | ||||
if(newData) return []; | |||||
if (newData) return []; | |||||
} | } | ||||
}, | }, | ||||
barGraph: { | barGraph: { | ||||
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; }, | |||||
layerClass: function () { return 'dataset-units dataset-bars dataset-' + this.constants.index; }, | |||||
makeElements(data) { | makeElements(data) { | ||||
let c = this.constants; | let c = this.constants; | ||||
this.unitType = 'bar'; | this.unitType = 'bar'; | ||||
@@ -347,7 +358,7 @@ let componentConfigs = { | |||||
this.store.map((bar, i) => { | this.store.map((bar, i) => { | ||||
animateElements = animateElements.concat(animateBar( | animateElements = animateElements.concat(animateBar( | ||||
bar, newXPos[i], newYPos[i], newData.barWidth, newOffsets[i], | bar, newXPos[i], newYPos[i], newData.barWidth, newOffsets[i], | ||||
{zeroLine: newData.zeroLine} | |||||
{ zeroLine: newData.zeroLine } | |||||
)); | )); | ||||
}); | }); | ||||
@@ -356,12 +367,12 @@ let componentConfigs = { | |||||
}, | }, | ||||
lineGraph: { | lineGraph: { | ||||
layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; }, | |||||
layerClass: function () { return 'dataset-units dataset-line dataset-' + this.constants.index; }, | |||||
makeElements(data) { | makeElements(data) { | ||||
let c = this.constants; | let c = this.constants; | ||||
this.unitType = 'dot'; | this.unitType = 'dot'; | ||||
this.paths = {}; | this.paths = {}; | ||||
if(!c.hideLine) { | |||||
if (!c.hideLine) { | |||||
this.paths = getPaths( | this.paths = getPaths( | ||||
data.xPositions, | data.xPositions, | ||||
data.yPositions, | data.yPositions, | ||||
@@ -378,8 +389,8 @@ let componentConfigs = { | |||||
); | ); | ||||
} | } | ||||
this.units = []; | |||||
if(!c.hideDots) { | |||||
this.units = []; | |||||
if (c.showDots) { | |||||
this.units = data.yPositions.map((y, j) => { | this.units = data.yPositions.map((y, j) => { | ||||
return datasetDot( | return datasetDot( | ||||
data.xPositions[j], | data.xPositions[j], | ||||
@@ -387,11 +398,27 @@ let componentConfigs = { | |||||
data.radius, | data.radius, | ||||
c.color, | c.color, | ||||
(c.valuesOverPoints ? data.values[j] : ''), | (c.valuesOverPoints ? data.values[j] : ''), | ||||
j | |||||
j, | |||||
c.hideDotBorder | |||||
); | ); | ||||
}); | }); | ||||
} | } | ||||
if (c.trailingDot && !c.showDots) { | |||||
const lastIndex = data.yPositions.length - 1; | |||||
const dot = datasetDot( | |||||
data.xPositions[lastIndex], | |||||
data.yPositions[lastIndex], | |||||
data.radius, | |||||
c.color, | |||||
(c.valuesOverPoints ? data.values[lastIndex] : ''), | |||||
lastIndex, | |||||
c.hideDotBorder | |||||
); | |||||
this.units.push(dot); | |||||
} | |||||
return Object.values(this.paths).concat(this.units); | return Object.values(this.paths).concat(this.units); | ||||
}, | }, | ||||
animateElements(newData) { | animateElements(newData) { | ||||
@@ -418,12 +445,12 @@ let componentConfigs = { | |||||
let animateElements = []; | let animateElements = []; | ||||
if(Object.keys(this.paths).length) { | |||||
if (Object.keys(this.paths).length) { | |||||
animateElements = animateElements.concat(animatePath( | animateElements = animateElements.concat(animatePath( | ||||
this.paths, newXPos, newYPos, newData.zeroLine, this.constants.spline)); | this.paths, newXPos, newYPos, newData.zeroLine, this.constants.spline)); | ||||
} | } | ||||
if(this.units.length) { | |||||
if (this.units.length) { | |||||
this.units.map((dot, i) => { | this.units.map((dot, i) => { | ||||
animateElements = animateElements.concat(animateDot( | animateElements = animateElements.concat(animateDot( | ||||
dot, newXPos[i], newYPos[i])); | dot, newXPos[i], newYPos[i])); | ||||
@@ -42,6 +42,7 @@ export default class SvgTip { | |||||
this.hideTip(); | this.hideTip(); | ||||
this.title = this.container.querySelector('.title'); | this.title = this.container.querySelector('.title'); | ||||
this.list = this.container.querySelector('.data-point-list'); | |||||
this.dataPointList = this.container.querySelector('.data-point-list'); | this.dataPointList = this.container.querySelector('.data-point-list'); | ||||
this.parent.addEventListener('mouseleave', () => { | this.parent.addEventListener('mouseleave', () => { | ||||
@@ -51,27 +52,33 @@ export default class SvgTip { | |||||
fill() { | fill() { | ||||
let title; | let title; | ||||
if(this.index) { | |||||
if (this.index) { | |||||
this.container.setAttribute('data-point-index', this.index); | this.container.setAttribute('data-point-index', this.index); | ||||
} | } | ||||
if(this.titleValueFirst) { | |||||
if (this.titleValueFirst) { | |||||
title = `<strong>${this.titleValue}</strong>${this.titleName}`; | title = `<strong>${this.titleValue}</strong>${this.titleName}`; | ||||
} else { | } else { | ||||
title = `${this.titleName}<strong>${this.titleValue}</strong>`; | title = `${this.titleName}<strong>${this.titleValue}</strong>`; | ||||
} | } | ||||
if (this.listValues.length > 4) { | |||||
this.list.classList.add('tooltip-grid'); | |||||
} else { | |||||
this.list.classList.remove('tooltip-grid'); | |||||
} | |||||
this.title.innerHTML = title; | this.title.innerHTML = title; | ||||
this.dataPointList.innerHTML = ''; | this.dataPointList.innerHTML = ''; | ||||
this.listValues.map((set, i) => { | this.listValues.map((set, i) => { | ||||
const color = this.colors[i] || 'black'; | const color = this.colors[i] || 'black'; | ||||
let value = set.formatted === 0 || set.formatted ? set.formatted : set.value; | let value = set.formatted === 0 || set.formatted ? set.formatted : set.value; | ||||
let li = $.create('li', { | let li = $.create('li', { | ||||
styles: { | |||||
'border-top': `3px solid ${color}` | |||||
}, | |||||
innerHTML: `<strong style="display: block;">${ value === 0 || value ? value : '' }</strong> | |||||
${set.title ? set.title : '' }` | |||||
innerHTML: `<div class="tooltip-legend" style="background: ${color};"></div> | |||||
<div> | |||||
<div class="tooltip-value">${value === 0 || value ? value : ''}</div> | |||||
<div class="tooltip-label">${set.title ? set.title : ''}</div> | |||||
</div>` | |||||
}); | }); | ||||
this.dataPointList.appendChild(li); | this.dataPointList.appendChild(li); | ||||
@@ -83,15 +90,15 @@ export default class SvgTip { | |||||
this.top = this.y - this.container.offsetHeight | this.top = this.y - this.container.offsetHeight | ||||
- TOOLTIP_POINTER_TRIANGLE_HEIGHT; | - TOOLTIP_POINTER_TRIANGLE_HEIGHT; | ||||
this.left = this.x - width/2; | |||||
this.left = this.x - width / 2; | |||||
let maxLeft = this.parent.offsetWidth - width; | let maxLeft = this.parent.offsetWidth - width; | ||||
let pointer = this.container.querySelector('.svg-pointer'); | let pointer = this.container.querySelector('.svg-pointer'); | ||||
if(this.left < 0) { | |||||
if (this.left < 0) { | |||||
pointer.style.left = `calc(50% - ${-1 * this.left}px)`; | pointer.style.left = `calc(50% - ${-1 * this.left}px)`; | ||||
this.left = 0; | this.left = 0; | ||||
} else if(this.left > maxLeft) { | |||||
} else if (this.left > maxLeft) { | |||||
let delta = this.left - maxLeft; | let delta = this.left - maxLeft; | ||||
let pointerOffset = `calc(50% + ${delta}px)`; | let pointerOffset = `calc(50% + ${delta}px)`; | ||||
pointer.style.left = pointerOffset; | pointer.style.left = pointerOffset; | ||||
@@ -11,11 +11,11 @@ export function translate(unit, oldCoord, newCoord, duration) { | |||||
let old = typeof oldCoord === 'string' ? oldCoord : oldCoord.join(', '); | let old = typeof oldCoord === 'string' ? oldCoord : oldCoord.join(', '); | ||||
return [ | return [ | ||||
unit, | unit, | ||||
{transform: newCoord.join(', ')}, | |||||
{ transform: newCoord.join(', ') }, | |||||
duration, | duration, | ||||
STD_EASING, | STD_EASING, | ||||
"translate", | "translate", | ||||
{transform: old} | |||||
{ transform: old } | |||||
]; | ]; | ||||
} | } | ||||
@@ -42,14 +42,14 @@ export function animateRegion(rectGroup, newY1, newY2, oldY2) { | |||||
return [rectAnim, groupAnim]; | return [rectAnim, groupAnim]; | ||||
} | } | ||||
export function animateBar(bar, x, yTop, width, offset=0, meta={}) { | |||||
export function animateBar(bar, x, yTop, width, offset = 0, meta = {}) { | |||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | ||||
y -= offset; | y -= offset; | ||||
if(bar.nodeName !== 'rect') { | |||||
if (bar.nodeName !== 'rect') { | |||||
let rect = bar.childNodes[0]; | let rect = bar.childNodes[0]; | ||||
let rectAnim = [ | let rectAnim = [ | ||||
rect, | rect, | ||||
{width: width, height: height}, | |||||
{ width: width, height: height }, | |||||
UNIT_ANIM_DUR, | UNIT_ANIM_DUR, | ||||
STD_EASING | STD_EASING | ||||
]; | ]; | ||||
@@ -58,18 +58,18 @@ export function animateBar(bar, x, yTop, width, offset=0, meta={}) { | |||||
let groupAnim = translate(bar, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR); | let groupAnim = translate(bar, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR); | ||||
return [rectAnim, groupAnim]; | return [rectAnim, groupAnim]; | ||||
} else { | } else { | ||||
return [[bar, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING]]; | |||||
return [[bar, { width: width, height: height, x: x, y: y }, UNIT_ANIM_DUR, STD_EASING]]; | |||||
} | } | ||||
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein); | // bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein); | ||||
} | } | ||||
export function animateDot(dot, x, y) { | export function animateDot(dot, x, y) { | ||||
if(dot.nodeName !== 'circle') { | |||||
if (dot.nodeName !== 'circle') { | |||||
let oldCoordStr = dot.getAttribute("transform").split("(")[1].slice(0, -1); | let oldCoordStr = dot.getAttribute("transform").split("(")[1].slice(0, -1); | ||||
let groupAnim = translate(dot, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR); | let groupAnim = translate(dot, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR); | ||||
return [groupAnim]; | return [groupAnim]; | ||||
} else { | } else { | ||||
return [[dot, {cx: x, cy: y}, UNIT_ANIM_DUR, STD_EASING]]; | |||||
return [[dot, { cx: x, cy: y }, UNIT_ANIM_DUR, STD_EASING]]; | |||||
} | } | ||||
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein); | // dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein); | ||||
} | } | ||||
@@ -81,16 +81,16 @@ export function animatePath(paths, newXList, newYList, zeroLine, spline) { | |||||
if (spline) | if (spline) | ||||
pointsStr = getSplineCurvePointsStr(newXList, newYList); | pointsStr = getSplineCurvePointsStr(newXList, newYList); | ||||
const animPath = [paths.path, {d:"M" + pointsStr}, PATH_ANIM_DUR, STD_EASING]; | |||||
const animPath = [paths.path, { d: "M" + pointsStr }, PATH_ANIM_DUR, STD_EASING]; | |||||
pathComponents.push(animPath); | pathComponents.push(animPath); | ||||
if(paths.region) { | |||||
if (paths.region) { | |||||
let regStartPt = `${newXList[0]},${zeroLine}L`; | let regStartPt = `${newXList[0]},${zeroLine}L`; | ||||
let regEndPt = `L${newXList.slice(-1)[0]}, ${zeroLine}`; | let regEndPt = `L${newXList.slice(-1)[0]}, ${zeroLine}`; | ||||
const animRegion = [ | const animRegion = [ | ||||
paths.region, | paths.region, | ||||
{d:"M" + regStartPt + pointsStr + regEndPt}, | |||||
{ d: "M" + regStartPt + pointsStr + regEndPt }, | |||||
PATH_ANIM_DUR, | PATH_ANIM_DUR, | ||||
STD_EASING | STD_EASING | ||||
]; | ]; | ||||
@@ -101,5 +101,5 @@ export function animatePath(paths, newXList, newYList, zeroLine, spline) { | |||||
} | } | ||||
export function animatePathStr(oldPath, pathStr) { | export function animatePathStr(oldPath, pathStr) { | ||||
return [oldPath, {d: pathStr}, UNIT_ANIM_DUR, STD_EASING]; | |||||
return [oldPath, { d: pathStr }, UNIT_ANIM_DUR, STD_EASING]; | |||||
} | } |
@@ -11,14 +11,14 @@ const EASING = { | |||||
easeinout: "0.42 0 0.58 1" | easeinout: "0.42 0 0.58 1" | ||||
}; | }; | ||||
function animateSVGElement(element, props, dur, easingType="linear", type=undefined, oldValues={}) { | |||||
function animateSVGElement(element, props, dur, easingType = "linear", type = undefined, oldValues = {}) { | |||||
let animElement = element.cloneNode(true); | let animElement = element.cloneNode(true); | ||||
let newElement = element.cloneNode(true); | let newElement = element.cloneNode(true); | ||||
for(var attributeName in props) { | |||||
for (var attributeName in props) { | |||||
let animateElement; | let animateElement; | ||||
if(attributeName === 'transform') { | |||||
if (attributeName === 'transform') { | |||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform"); | animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform"); | ||||
} else { | } else { | ||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animate"); | animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animate"); | ||||
@@ -31,7 +31,7 @@ function animateSVGElement(element, props, dur, easingType="linear", type=undefi | |||||
from: currentValue, | from: currentValue, | ||||
to: value, | to: value, | ||||
begin: "0s", | begin: "0s", | ||||
dur: dur/1000 + "s", | |||||
dur: dur / 1000 + "s", | |||||
values: currentValue + ";" + value, | values: currentValue + ";" + value, | ||||
keySplines: EASING[easingType], | keySplines: EASING[easingType], | ||||
keyTimes: "0;1", | keyTimes: "0;1", | ||||
@@ -39,7 +39,7 @@ function animateSVGElement(element, props, dur, easingType="linear", type=undefi | |||||
fill: 'freeze' | fill: 'freeze' | ||||
}; | }; | ||||
if(type) { | |||||
if (type) { | |||||
animAttr["type"] = type; | animAttr["type"] = type; | ||||
} | } | ||||
@@ -49,7 +49,7 @@ function animateSVGElement(element, props, dur, easingType="linear", type=undefi | |||||
animElement.appendChild(animateElement); | animElement.appendChild(animateElement); | ||||
if(type) { | |||||
if (type) { | |||||
newElement.setAttribute(attributeName, `translate(${value})`); | newElement.setAttribute(attributeName, `translate(${value})`); | ||||
} else { | } else { | ||||
newElement.setAttribute(attributeName, value); | newElement.setAttribute(attributeName, value); | ||||
@@ -97,10 +97,10 @@ function animateSVG(svgContainer, elements) { | |||||
} | } | ||||
export function runSMILAnimation(parent, svgElement, elementsToAnimate) { | export function runSMILAnimation(parent, svgElement, elementsToAnimate) { | ||||
if(elementsToAnimate.length === 0) return; | |||||
if (elementsToAnimate.length === 0) return; | |||||
let animSvgElement = animateSVG(svgElement, elementsToAnimate); | let animSvgElement = animateSVG(svgElement, elementsToAnimate); | ||||
if(svgElement.parentNode == parent) { | |||||
if (svgElement.parentNode == parent) { | |||||
parent.removeChild(svgElement); | parent.removeChild(svgElement); | ||||
parent.appendChild(animSvgElement); | parent.appendChild(animSvgElement); | ||||
@@ -108,7 +108,7 @@ export function runSMILAnimation(parent, svgElement, elementsToAnimate) { | |||||
// Replace the new svgElement (data has already been replaced) | // Replace the new svgElement (data has already been replaced) | ||||
setTimeout(() => { | setTimeout(() => { | ||||
if(animSvgElement.parentNode == parent) { | |||||
if (animSvgElement.parentNode == parent) { | |||||
parent.removeChild(animSvgElement); | parent.removeChild(animSvgElement); | ||||
parent.appendChild(svgElement); | parent.appendChild(svgElement); | ||||
} | } | ||||
@@ -1,5 +1,8 @@ | |||||
import { fillArray } from '../utils/helpers'; | import { fillArray } from '../utils/helpers'; | ||||
import { DEFAULT_AXIS_CHART_TYPE, AXIS_DATASET_CHART_TYPES, DEFAULT_CHAR_WIDTH } from '../utils/constants'; | |||||
import { | |||||
DEFAULT_AXIS_CHART_TYPE, AXIS_DATASET_CHART_TYPES, DEFAULT_CHAR_WIDTH, | |||||
SERIES_LABEL_SPACE_RATIO | |||||
} from '../utils/constants'; | |||||
export function dataPrep(data, type) { | export function dataPrep(data, type) { | ||||
data.labels = data.labels || []; | data.labels = data.labels || []; | ||||
@@ -9,16 +12,16 @@ export function dataPrep(data, type) { | |||||
// Datasets | // Datasets | ||||
let datasets = data.datasets; | let datasets = data.datasets; | ||||
let zeroArray = new Array(datasetLength).fill(0); | let zeroArray = new Array(datasetLength).fill(0); | ||||
if(!datasets) { | |||||
if (!datasets) { | |||||
// default | // default | ||||
datasets = [{ | datasets = [{ | ||||
values: zeroArray | values: zeroArray | ||||
}]; | }]; | ||||
} | } | ||||
datasets.map(d=> { | |||||
datasets.map(d => { | |||||
// Set values | // Set values | ||||
if(!d.values) { | |||||
if (!d.values) { | |||||
d.values = zeroArray; | d.values = zeroArray; | ||||
} else { | } else { | ||||
// Check for non values | // Check for non values | ||||
@@ -26,19 +29,17 @@ export function dataPrep(data, type) { | |||||
vals = vals.map(val => (!isNaN(val) ? val : 0)); | vals = vals.map(val => (!isNaN(val) ? val : 0)); | ||||
// Trim or extend | // Trim or extend | ||||
if(vals.length > datasetLength) { | |||||
if (vals.length > datasetLength) { | |||||
vals = vals.slice(0, datasetLength); | vals = vals.slice(0, datasetLength); | ||||
} else { | } else { | ||||
vals = fillArray(vals, datasetLength - vals.length, 0); | vals = fillArray(vals, datasetLength - vals.length, 0); | ||||
} | } | ||||
d.values = vals; | |||||
} | } | ||||
// Set labels | |||||
// | |||||
// Set type | // Set type | ||||
if(!d.chartType ) { | |||||
if(!AXIS_DATASET_CHART_TYPES.includes(type)) type === DEFAULT_AXIS_CHART_TYPE; | |||||
if (!d.chartType) { | |||||
if (!AXIS_DATASET_CHART_TYPES.includes(type)) type === DEFAULT_AXIS_CHART_TYPE; | |||||
d.chartType = type; | d.chartType = type; | ||||
} | } | ||||
@@ -48,9 +49,9 @@ export function dataPrep(data, type) { | |||||
// Regions | // Regions | ||||
// data.yRegions = data.yRegions || []; | // data.yRegions = data.yRegions || []; | ||||
if(data.yRegions) { | |||||
if (data.yRegions) { | |||||
data.yRegions.map(d => { | data.yRegions.map(d => { | ||||
if(d.end < d.start) { | |||||
if (d.end < d.start) { | |||||
[d.start, d.end] = [d.end, d.start]; | [d.start, d.end] = [d.end, d.start]; | ||||
} | } | ||||
}); | }); | ||||
@@ -74,7 +75,7 @@ export function zeroDataPrep(realData) { | |||||
}), | }), | ||||
}; | }; | ||||
if(realData.yMarkers) { | |||||
if (realData.yMarkers) { | |||||
zeroData.yMarkers = [ | zeroData.yMarkers = [ | ||||
{ | { | ||||
value: 0, | value: 0, | ||||
@@ -83,7 +84,7 @@ export function zeroDataPrep(realData) { | |||||
]; | ]; | ||||
} | } | ||||
if(realData.yRegions) { | |||||
if (realData.yRegions) { | |||||
zeroData.yRegions = [ | zeroData.yRegions = [ | ||||
{ | { | ||||
start: 0, | start: 0, | ||||
@@ -96,31 +97,37 @@ export function zeroDataPrep(realData) { | |||||
return zeroData; | return zeroData; | ||||
} | } | ||||
export function getShortenedLabels(chartWidth, labels=[], isSeries=true) { | |||||
let allowedSpace = chartWidth / labels.length; | |||||
if(allowedSpace <= 0) allowedSpace = 1; | |||||
export function getShortenedLabels(chartWidth, labels = [], isSeries = true) { | |||||
let allowedSpace = (chartWidth / labels.length) * SERIES_LABEL_SPACE_RATIO; | |||||
if (allowedSpace <= 0) allowedSpace = 1; | |||||
let allowedLetters = allowedSpace / DEFAULT_CHAR_WIDTH; | let allowedLetters = allowedSpace / DEFAULT_CHAR_WIDTH; | ||||
let seriesMultiple; | let seriesMultiple; | ||||
if(isSeries) { | |||||
if (isSeries) { | |||||
// Find the maximum label length for spacing calculations | // Find the maximum label length for spacing calculations | ||||
let maxLabelLength = Math.max(...labels.map(label => label.length)); | let maxLabelLength = Math.max(...labels.map(label => label.length)); | ||||
seriesMultiple = Math.ceil(maxLabelLength/allowedLetters); | |||||
seriesMultiple = Math.ceil(maxLabelLength / allowedLetters); | |||||
} | } | ||||
let calcLabels = labels.map((label, i) => { | let calcLabels = labels.map((label, i) => { | ||||
label += ""; | label += ""; | ||||
if(label.length > allowedLetters) { | |||||
if (label.length > allowedLetters) { | |||||
if(!isSeries) { | |||||
if(allowedLetters-3 > 0) { | |||||
label = label.slice(0, allowedLetters-3) + " ..."; | |||||
if (!isSeries) { | |||||
if (allowedLetters - 3 > 0) { | |||||
label = label.slice(0, allowedLetters - 3) + " ..."; | |||||
} else { | } else { | ||||
label = label.slice(0, allowedLetters) + '..'; | label = label.slice(0, allowedLetters) + '..'; | ||||
} | } | ||||
} else { | } else { | ||||
if(i % seriesMultiple !== 0) { | |||||
label = ""; | |||||
if (i % seriesMultiple !== 0) { | |||||
if (i !== (labels.length - 1)) { | |||||
label = ""; | |||||
} | |||||
} else { | |||||
if (i > (labels.length - (seriesMultiple / 2))) { | |||||
label = ""; | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -1,21 +1,27 @@ | |||||
const PRESET_COLOR_MAP = { | const PRESET_COLOR_MAP = { | ||||
'light-blue': '#7cd6fd', | |||||
'blue': '#5e64ff', | |||||
'violet': '#743ee2', | |||||
'red': '#ff5858', | |||||
'orange': '#ffa00a', | |||||
'yellow': '#feef72', | |||||
'green': '#28a745', | |||||
'light-green': '#98d85b', | |||||
'purple': '#b554ff', | |||||
'magenta': '#ffa3ef', | |||||
'black': '#36114C', | |||||
'grey': '#bdd3e6', | |||||
'light-grey': '#f0f4f7', | |||||
'dark-grey': '#b8c2cc' | |||||
'pink': '#F683AE', | |||||
'blue': '#318AD8', | |||||
'green': '#48BB74', | |||||
'grey': '#A6B1B9', | |||||
'red': '#F56B6B', | |||||
'yellow': '#FACF7A', | |||||
'purple': '#44427B', | |||||
'teal': '#5FD8C4', | |||||
'cyan': '#15CCEF', | |||||
'orange': '#F8814F', | |||||
'light-pink': '#FED7E5', | |||||
'light-blue': '#BFDDF7', | |||||
'light-green': '#48BB74', | |||||
'light-grey': '#F4F5F6', | |||||
'light-red': '#F6DFDF', | |||||
'light-yellow': '#FEE9BF', | |||||
'light-purple': '#E8E8F7', | |||||
'light-teal': '#D3FDF6', | |||||
'light-cyan': '#DDF8FD', | |||||
'light-orange': '#FECDB8' | |||||
}; | }; | ||||
function limitColor(r){ | |||||
function limitColor(r) { | |||||
if (r > 255) return 255; | if (r > 255) return 255; | ||||
else if (r < 0) return 0; | else if (r < 0) return 0; | ||||
return r; | return r; | ||||
@@ -28,11 +34,11 @@ export function lightenDarkenColor(color, amt) { | |||||
col = col.slice(1); | col = col.slice(1); | ||||
usePound = true; | usePound = true; | ||||
} | } | ||||
let num = parseInt(col,16); | |||||
let num = parseInt(col, 16); | |||||
let r = limitColor((num >> 16) + amt); | let r = limitColor((num >> 16) + amt); | ||||
let b = limitColor(((num >> 8) & 0x00FF) + amt); | let b = limitColor(((num >> 8) & 0x00FF) + amt); | ||||
let g = limitColor((num & 0x0000FF) + amt); | let g = limitColor((num & 0x0000FF) + amt); | ||||
return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16); | |||||
return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16); | |||||
} | } | ||||
export function isValidColor(string) { | export function isValidColor(string) { | ||||
@@ -65,7 +65,8 @@ export const CHART_POST_ANIMATE_TIMEOUT = 400; | |||||
export const DEFAULT_AXIS_CHART_TYPE = 'line'; | export const DEFAULT_AXIS_CHART_TYPE = 'line'; | ||||
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar']; | export const AXIS_DATASET_CHART_TYPES = ['line', 'bar']; | ||||
export const AXIS_LEGEND_BAR_SIZE = 100; | |||||
export const LEGEND_ITEM_WIDTH = 150; | |||||
export const SERIES_LABEL_SPACE_RATIO = 0.6; | |||||
export const BAR_CHART_SPACE_RATIO = 0.5; | export const BAR_CHART_SPACE_RATIO = 0.5; | ||||
export const MIN_BAR_PERCENT_HEIGHT = 0.00; | export const MIN_BAR_PERCENT_HEIGHT = 0.00; | ||||
@@ -73,8 +74,7 @@ export const MIN_BAR_PERCENT_HEIGHT = 0.00; | |||||
export const LINE_CHART_DOT_SIZE = 4; | export const LINE_CHART_DOT_SIZE = 4; | ||||
export const DOT_OVERLAY_SIZE_INCR = 4; | export const DOT_OVERLAY_SIZE_INCR = 4; | ||||
export const PERCENTAGE_BAR_DEFAULT_HEIGHT = 20; | |||||
export const PERCENTAGE_BAR_DEFAULT_DEPTH = 2; | |||||
export const PERCENTAGE_BAR_DEFAULT_HEIGHT = 16; | |||||
// Fixed 5-color theme, | // Fixed 5-color theme, | ||||
// More colors are difficult to parse visually | // More colors are difficult to parse visually | ||||
@@ -85,10 +85,8 @@ export const HEATMAP_GUTTER_SIZE = 2; | |||||
export const DEFAULT_CHAR_WIDTH = 7; | export const DEFAULT_CHAR_WIDTH = 7; | ||||
export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5; | |||||
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange', | |||||
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey']; | |||||
export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 7.48; | |||||
const DEFAULT_CHART_COLORS = ['pink', 'blue', 'green', 'grey', 'red', 'yellow', 'purple', 'teal', 'cyan', 'orange']; | |||||
const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']; | ||||
export const HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e']; | export const HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e']; | ||||
export const HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | export const HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']; | ||||
@@ -27,8 +27,8 @@ export function getYyyyMmDd(date) { | |||||
let mm = date.getMonth() + 1; // getMonth() is zero-based | let mm = date.getMonth() + 1; // getMonth() is zero-based | ||||
return [ | return [ | ||||
date.getFullYear(), | date.getFullYear(), | ||||
(mm>9 ? '' : '0') + mm, | |||||
(dd>9 ? '' : '0') + dd | |||||
(mm > 9 ? '' : '0') + mm, | |||||
(dd > 9 ? '' : '0') + dd | |||||
].join('-'); | ].join('-'); | ||||
} | } | ||||
@@ -37,12 +37,12 @@ export function clone(date) { | |||||
} | } | ||||
export function timestampSec(date) { | export function timestampSec(date) { | ||||
return date.getTime()/NO_OF_MILLIS; | |||||
return date.getTime() / NO_OF_MILLIS; | |||||
} | } | ||||
export function timestampToMidnight(timestamp, roundAhead = false) { | export function timestampToMidnight(timestamp, roundAhead = false) { | ||||
let midnightTs = Math.floor(timestamp - (timestamp % SEC_IN_DAY)); | let midnightTs = Math.floor(timestamp - (timestamp % SEC_IN_DAY)); | ||||
if(roundAhead) { | |||||
if (roundAhead) { | |||||
return midnightTs + SEC_IN_DAY; | return midnightTs + SEC_IN_DAY; | ||||
} | } | ||||
return midnightTs; | return midnightTs; | ||||
@@ -65,12 +65,12 @@ export function areInSameMonth(startDate, endDate) { | |||||
&& startDate.getFullYear() === endDate.getFullYear(); | && startDate.getFullYear() === endDate.getFullYear(); | ||||
} | } | ||||
export function getMonthName(i, short=false) { | |||||
export function getMonthName(i, short = false) { | |||||
let monthName = MONTH_NAMES[i]; | let monthName = MONTH_NAMES[i]; | ||||
return short ? monthName.slice(0, 3) : monthName; | return short ? monthName.slice(0, 3) : monthName; | ||||
} | } | ||||
export function getLastDateInMonth (month, year) { | |||||
export function getLastDateInMonth(month, year) { | |||||
return new Date(year, month + 1, 0); // 0: last day in previous month | return new Date(year, month + 1, 0); // 0: last day in previous month | ||||
} | } | ||||
@@ -78,7 +78,7 @@ export function getLastDateInMonth (month, year) { | |||||
export function setDayToSunday(date) { | export function setDayToSunday(date) { | ||||
let newDate = clone(date); | let newDate = clone(date); | ||||
const day = newDate.getDay(); | const day = newDate.getDay(); | ||||
if(day !== 0) { | |||||
if (day !== 0) { | |||||
addDays(newDate, (-1) * day); | addDays(newDate, (-1) * day); | ||||
} | } | ||||
return newDate; | return newDate; | ||||
@@ -1,9 +1,8 @@ | |||||
export function $(expr, con) { | export function $(expr, con) { | ||||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | |||||
return typeof expr === "string" ? (con || document).querySelector(expr) : expr || null; | |||||
} | } | ||||
export function findNodeIndex(node) | |||||
{ | |||||
export function findNodeIndex(node) { | |||||
var i = 0; | var i = 0; | ||||
while (node.previousSibling) { | while (node.previousSibling) { | ||||
node = node.previousSibling; | node = node.previousSibling; | ||||
@@ -27,12 +26,12 @@ $.create = (tag, o) => { | |||||
element.appendChild(ref); | element.appendChild(ref); | ||||
} else if (i === "styles") { | } else if (i === "styles") { | ||||
if(typeof val === "object") { | |||||
if (typeof val === "object") { | |||||
Object.keys(val).map(prop => { | Object.keys(val).map(prop => { | ||||
element.style[prop] = val[prop]; | element.style[prop] = val[prop]; | ||||
}); | }); | ||||
} | } | ||||
} else if (i in element ) { | |||||
} else if (i in element) { | |||||
element[i] = val; | element[i] = val; | ||||
} | } | ||||
else { | else { | ||||
@@ -67,9 +66,9 @@ export function isElementInViewport(el) { | |||||
return ( | return ( | ||||
rect.top >= 0 && | rect.top >= 0 && | ||||
rect.left >= 0 && | |||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ | |||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ | |||||
rect.left >= 0 && | |||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ | |||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ | |||||
); | ); | ||||
} | } | ||||
@@ -81,7 +80,7 @@ export function getElementContentWidth(element) { | |||||
return element.clientWidth - padding; | return element.clientWidth - padding; | ||||
} | } | ||||
export function bind(element, o){ | |||||
export function bind(element, o) { | |||||
if (element) { | if (element) { | ||||
for (var event in o) { | for (var event in o) { | ||||
var callback = o[event]; | var callback = o[event]; | ||||
@@ -93,12 +92,12 @@ export function bind(element, o){ | |||||
} | } | ||||
} | } | ||||
export function unbind(element, o){ | |||||
export function unbind(element, o) { | |||||
if (element) { | if (element) { | ||||
for (var event in o) { | for (var event in o) { | ||||
var callback = o[event]; | var callback = o[event]; | ||||
event.split(/\s+/).forEach(function(event) { | |||||
event.split(/\s+/).forEach(function (event) { | |||||
element.removeEventListener(event, callback); | element.removeEventListener(event, callback); | ||||
}); | }); | ||||
} | } | ||||
@@ -108,7 +107,7 @@ export function unbind(element, o){ | |||||
export function fire(target, type, properties) { | export function fire(target, type, properties) { | ||||
var evt = document.createEvent("HTMLEvents"); | var evt = document.createEvent("HTMLEvents"); | ||||
evt.initEvent(type, true, true ); | |||||
evt.initEvent(type, true, true); | |||||
for (var j in properties) { | for (var j in properties) { | ||||
evt[j] = properties[j]; | evt[j] = properties[j]; | ||||
@@ -119,17 +118,17 @@ export function fire(target, type, properties) { | |||||
// https://css-tricks.com/snippets/javascript/loop-queryselectorall-matches/ | // https://css-tricks.com/snippets/javascript/loop-queryselectorall-matches/ | ||||
export function forEachNode(nodeList, callback, scope) { | export function forEachNode(nodeList, callback, scope) { | ||||
if(!nodeList) return; | |||||
if (!nodeList) return; | |||||
for (var i = 0; i < nodeList.length; i++) { | for (var i = 0; i < nodeList.length; i++) { | ||||
callback.call(scope, nodeList[i], i); | callback.call(scope, nodeList[i], i); | ||||
} | } | ||||
} | } | ||||
export function activate($parent, $child, commonClass, activeClass='active', index = -1) { | |||||
export function activate($parent, $child, commonClass, activeClass = 'active', index = -1) { | |||||
let $children = $parent.querySelectorAll(`.${commonClass}.${activeClass}`); | let $children = $parent.querySelectorAll(`.${commonClass}.${activeClass}`); | ||||
forEachNode($children, (node, i) => { | forEachNode($children, (node, i) => { | ||||
if(index >= 0 && i <= index) return; | |||||
if (index >= 0 && i <= index) return; | |||||
node.classList.remove(activeClass); | node.classList.remove(activeClass); | ||||
}); | }); | ||||
@@ -17,7 +17,7 @@ export function equilizeNoOfElements(array1, array2, | |||||
extraCount = array2.length - array1.length) { | extraCount = array2.length - array1.length) { | ||||
// Doesn't work if either has zero elements. | // Doesn't work if either has zero elements. | ||||
if(extraCount > 0) { | |||||
if (extraCount > 0) { | |||||
array1 = fillArray(array1, extraCount); | array1 = fillArray(array1, extraCount); | ||||
} else { | } else { | ||||
array2 = fillArray(array2, extraCount); | array2 = fillArray(array2, extraCount); | ||||
@@ -30,7 +30,7 @@ export function truncateString(txt, len) { | |||||
return; | return; | ||||
} | } | ||||
if (txt.length > len) { | if (txt.length > len) { | ||||
return txt.slice(0, len-3) + '...'; | |||||
return txt.slice(0, len - 3) + '...'; | |||||
} else { | } else { | ||||
return txt; | return txt; | ||||
} | } | ||||
@@ -47,18 +47,20 @@ export function shortenLargeNumber(label) { | |||||
// Using absolute since log wont work for negative numbers | // Using absolute since log wont work for negative numbers | ||||
let p = Math.floor(Math.log10(Math.abs(number))); | let p = Math.floor(Math.log10(Math.abs(number))); | ||||
if (p <= 2) return number; // Return as is for a 3 digit number of less | if (p <= 2) return number; // Return as is for a 3 digit number of less | ||||
let l = Math.floor(p / 3); | |||||
let l = Math.floor(p / 3); | |||||
let shortened = (Math.pow(10, p - l * 3) * +(number / Math.pow(10, p)).toFixed(1)); | let shortened = (Math.pow(10, p - l * 3) * +(number / Math.pow(10, p)).toFixed(1)); | ||||
// Correct for floating point error upto 2 decimal places | // Correct for floating point error upto 2 decimal places | ||||
return Math.round(shortened*100)/100 + ' ' + ['', 'K', 'M', 'B', 'T'][l]; | |||||
return Math.round(shortened * 100) / 100 + ['', 'K', 'M', 'B', 'T'][l]; | |||||
} | } | ||||
// cubic bezier curve calculation (from example by François Romain) | // cubic bezier curve calculation (from example by François Romain) | ||||
export function getSplineCurvePointsStr(xList, yList) { | export function getSplineCurvePointsStr(xList, yList) { | ||||
let points=[]; | |||||
for(let i=0;i<xList.length;i++){ | |||||
let points = []; | |||||
const length = Math.min(xList.length, yList.length); | |||||
for (let i = 0; i < xList.length; i++) { | |||||
points.push([xList[i], yList[i]]); | points.push([xList[i], yList[i]]); | ||||
} | } | ||||
@@ -71,7 +73,7 @@ export function getSplineCurvePointsStr(xList, yList) { | |||||
angle: Math.atan2(lengthY, lengthX) | angle: Math.atan2(lengthY, lengthX) | ||||
}; | }; | ||||
}; | }; | ||||
let controlPoint = (current, previous, next, reverse) => { | let controlPoint = (current, previous, next, reverse) => { | ||||
let p = previous || current; | let p = previous || current; | ||||
let n = next || current; | let n = next || current; | ||||
@@ -82,18 +84,18 @@ export function getSplineCurvePointsStr(xList, yList) { | |||||
let y = current[1] + Math.sin(angle) * length; | let y = current[1] + Math.sin(angle) * length; | ||||
return [x, y]; | return [x, y]; | ||||
}; | }; | ||||
let bezierCommand = (point, i, a) => { | let bezierCommand = (point, i, a) => { | ||||
let cps = controlPoint(a[i - 1], a[i - 2], point); | let cps = controlPoint(a[i - 1], a[i - 2], point); | ||||
let cpe = controlPoint(point, a[i - 1], a[i + 1], true); | let cpe = controlPoint(point, a[i - 1], a[i + 1], true); | ||||
return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`; | return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`; | ||||
}; | }; | ||||
let pointStr = (points, command) => { | let pointStr = (points, command) => { | ||||
return points.reduce((acc, point, i, a) => i === 0 | return points.reduce((acc, point, i, a) => i === 0 | ||||
? `${point[0]},${point[1]}` | ? `${point[0]},${point[1]}` | ||||
: `${acc} ${command(point, i, a)}`, ''); | : `${acc} ${command(point, i, a)}`, ''); | ||||
}; | }; | ||||
return pointStr(points, bezierCommand); | return pointStr(points, bezierCommand); | ||||
} | } |
@@ -1,17 +1,15 @@ | |||||
import { getBarHeightAndYAttr, truncateString, shortenLargeNumber, getSplineCurvePointsStr } from './draw-utils'; | import { getBarHeightAndYAttr, truncateString, shortenLargeNumber, getSplineCurvePointsStr } from './draw-utils'; | ||||
import { getStringWidth, isValidNumber } from './helpers'; | |||||
import { DOT_OVERLAY_SIZE_INCR, PERCENTAGE_BAR_DEFAULT_DEPTH } from './constants'; | |||||
import { lightenDarkenColor } from './colors'; | |||||
import { getStringWidth, isValidNumber, round } from './helpers'; | |||||
import { DOT_OVERLAY_SIZE_INCR } from './constants'; | |||||
export const AXIS_TICK_LENGTH = 6; | export const AXIS_TICK_LENGTH = 6; | ||||
const LABEL_MARGIN = 4; | const LABEL_MARGIN = 4; | ||||
const LABEL_MAX_CHARS = 15; | |||||
const LABEL_MAX_CHARS = 18; | |||||
export const FONT_SIZE = 10; | export const FONT_SIZE = 10; | ||||
const BASE_LINE_COLOR = '#dadada'; | |||||
const FONT_FILL = '#555b51'; | |||||
const BASE_LINE_COLOR = '#E2E6E9'; | |||||
function $(expr, con) { | function $(expr, con) { | ||||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null; | |||||
return typeof expr === "string" ? (con || document).querySelector(expr) : expr || null; | |||||
} | } | ||||
export function createSVG(tag, o) { | export function createSVG(tag, o) { | ||||
@@ -29,14 +27,14 @@ export function createSVG(tag, o) { | |||||
element.appendChild(ref); | element.appendChild(ref); | ||||
} else if (i === "styles") { | } else if (i === "styles") { | ||||
if(typeof val === "object") { | |||||
if (typeof val === "object") { | |||||
Object.keys(val).map(prop => { | Object.keys(val).map(prop => { | ||||
element.style[prop] = val[prop]; | element.style[prop] = val[prop]; | ||||
}); | }); | ||||
} | } | ||||
} else { | } else { | ||||
if(i === "className") { i = "class"; } | |||||
if(i === "innerHTML") { | |||||
if (i === "className") { i = "class"; } | |||||
if (i === "innerHTML") { | |||||
element['textContent'] = val; | element['textContent'] = val; | ||||
} else { | } else { | ||||
element.setAttribute(i, val); | element.setAttribute(i, val); | ||||
@@ -82,16 +80,16 @@ export function makeSVGDefs(svgContainer) { | |||||
}); | }); | ||||
} | } | ||||
export function makeSVGGroup(className, transform='', parent=undefined) { | |||||
export function makeSVGGroup(className, transform = '', parent = undefined) { | |||||
let args = { | let args = { | ||||
className: className, | className: className, | ||||
transform: transform | transform: transform | ||||
}; | }; | ||||
if(parent) args.inside = parent; | |||||
if (parent) args.inside = parent; | |||||
return createSVG('g', args); | return createSVG('g', args); | ||||
} | } | ||||
export function wrapInSVGGroup(elements, className='') { | |||||
export function wrapInSVGGroup(elements, className = '') { | |||||
let g = createSVG('g', { | let g = createSVG('g', { | ||||
className: className | className: className | ||||
}); | }); | ||||
@@ -99,7 +97,7 @@ export function wrapInSVGGroup(elements, className='') { | |||||
return g; | return g; | ||||
} | } | ||||
export function makePath(pathStr, className='', stroke='none', fill='none', strokeWidth=2) { | |||||
export function makePath(pathStr, className = '', stroke = 'none', fill = 'none', strokeWidth = 2) { | |||||
return createSVG('path', { | return createSVG('path', { | ||||
className: className, | className: className, | ||||
d: pathStr, | d: pathStr, | ||||
@@ -111,7 +109,7 @@ export function makePath(pathStr, className='', stroke='none', fill='none', stro | |||||
}); | }); | ||||
} | } | ||||
export function makeArcPathStr(startPosition, endPosition, center, radius, clockWise=1, largeArc=0){ | |||||
export function makeArcPathStr(startPosition, endPosition, center, radius, clockWise = 1, largeArc = 0) { | |||||
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; | let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; | ||||
let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y]; | let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y]; | ||||
return `M${center.x} ${center.y} | return `M${center.x} ${center.y} | ||||
@@ -120,7 +118,7 @@ export function makeArcPathStr(startPosition, endPosition, center, radius, clock | |||||
${arcEndX} ${arcEndY} z`; | ${arcEndX} ${arcEndY} z`; | ||||
} | } | ||||
export function makeCircleStr(startPosition, endPosition, center, radius, clockWise=1, largeArc=0){ | |||||
export function makeCircleStr(startPosition, endPosition, center, radius, clockWise = 1, largeArc = 0) { | |||||
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; | let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; | ||||
let [arcEndX, midArc, arcEndY] = [center.x + endPosition.x, center.y * 2, center.y + endPosition.y]; | let [arcEndX, midArc, arcEndY] = [center.x + endPosition.x, center.y * 2, center.y + endPosition.y]; | ||||
return `M${center.x} ${center.y} | return `M${center.x} ${center.y} | ||||
@@ -132,7 +130,7 @@ export function makeCircleStr(startPosition, endPosition, center, radius, clockW | |||||
${arcEndX} ${arcEndY} z`; | ${arcEndX} ${arcEndY} z`; | ||||
} | } | ||||
export function makeArcStrokePathStr(startPosition, endPosition, center, radius, clockWise=1, largeArc=0){ | |||||
export function makeArcStrokePathStr(startPosition, endPosition, center, radius, clockWise = 1, largeArc = 0) { | |||||
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; | let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; | ||||
let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y]; | let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y]; | ||||
@@ -141,7 +139,7 @@ export function makeArcStrokePathStr(startPosition, endPosition, center, radius, | |||||
${arcEndX} ${arcEndY}`; | ${arcEndX} ${arcEndY}`; | ||||
} | } | ||||
export function makeStrokeCircleStr(startPosition, endPosition, center, radius, clockWise=1, largeArc=0){ | |||||
export function makeStrokeCircleStr(startPosition, endPosition, center, radius, clockWise = 1, largeArc = 0) { | |||||
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; | let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y]; | ||||
let [arcEndX, midArc, arcEndY] = [center.x + endPosition.x, radius * 2 + arcStartY, center.y + startPosition.y]; | let [arcEndX, midArc, arcEndY] = [center.x + endPosition.x, radius * 2 + arcStartY, center.y + startPosition.y]; | ||||
@@ -154,11 +152,11 @@ export function makeStrokeCircleStr(startPosition, endPosition, center, radius, | |||||
} | } | ||||
export function makeGradient(svgDefElem, color, lighter = false) { | export function makeGradient(svgDefElem, color, lighter = false) { | ||||
let gradientId ='path-fill-gradient' + '-' + color + '-' +(lighter ? 'lighter' : 'default'); | |||||
let gradientId = 'path-fill-gradient' + '-' + color + '-' + (lighter ? 'lighter' : 'default'); | |||||
let gradientDef = renderVerticalGradient(svgDefElem, gradientId); | let gradientDef = renderVerticalGradient(svgDefElem, gradientId); | ||||
let opacities = [1, 0.6, 0.2]; | let opacities = [1, 0.6, 0.2]; | ||||
if(lighter) { | |||||
opacities = [0.4, 0.2, 0]; | |||||
if (lighter) { | |||||
opacities = [0.15, 0.05, 0]; | |||||
} | } | ||||
setGradientStop(gradientDef, "0%", color, opacities[0]); | setGradientStop(gradientDef, "0%", color, opacities[0]); | ||||
@@ -168,8 +166,31 @@ export function makeGradient(svgDefElem, color, lighter = false) { | |||||
return gradientId; | return gradientId; | ||||
} | } | ||||
export function percentageBar(x, y, width, height, | |||||
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') { | |||||
export function rightRoundedBar(x, width, height) { | |||||
// https://medium.com/@dennismphil/one-side-rounded-rectangle-using-svg-fb31cf318d90 | |||||
let radius = height / 2; | |||||
let xOffset = width - radius; | |||||
return `M${x},0 h${xOffset} q${radius},0 ${radius},${radius} q0,${radius} -${radius},${radius} h-${xOffset} v${height}z`; | |||||
} | |||||
export function leftRoundedBar(x, width, height) { | |||||
let radius = height / 2; | |||||
let xOffset = width - radius; | |||||
return `M${x + radius},0 h${xOffset} v${height} h-${xOffset} q-${radius}, 0 -${radius},-${radius} q0,-${radius} ${radius},-${radius}z`; | |||||
} | |||||
export function percentageBar(x, y, width, height, isFirst, isLast, fill = 'none') { | |||||
if (isLast) { | |||||
let pathStr = rightRoundedBar(x, width, height); | |||||
return makePath(pathStr, 'percentage-bar', null, fill); | |||||
} | |||||
if (isFirst) { | |||||
let pathStr = leftRoundedBar(x, width, height); | |||||
return makePath(pathStr, 'percentage-bar', null, fill); | |||||
} | |||||
let args = { | let args = { | ||||
className: 'percentage-bar', | className: 'percentage-bar', | ||||
@@ -177,20 +198,13 @@ export function percentageBar(x, y, width, height, | |||||
y: y, | y: y, | ||||
width: width, | width: width, | ||||
height: height, | height: height, | ||||
fill: fill, | |||||
styles: { | |||||
'stroke': lightenDarkenColor(fill, -25), | |||||
// Diabolically good: https://stackoverflow.com/a/9000859 | |||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray | |||||
'stroke-dasharray': `0, ${height + width}, ${width}, ${height}`, | |||||
'stroke-width': depth | |||||
}, | |||||
fill: fill | |||||
}; | }; | ||||
return createSVG("rect", args); | return createSVG("rect", args); | ||||
} | } | ||||
export function heatSquare(className, x, y, size, radius, fill='none', data={}) { | |||||
export function heatSquare(className, x, y, size, radius, fill = 'none', data = {}) { | |||||
let args = { | let args = { | ||||
className: className, | className: className, | ||||
x: x, | x: x, | ||||
@@ -208,64 +222,54 @@ export function heatSquare(className, x, y, size, radius, fill='none', data={}) | |||||
return createSVG("rect", args); | return createSVG("rect", args); | ||||
} | } | ||||
export function legendBar(x, y, size, fill='none', label, truncate=false) { | |||||
export function legendDot(x, y, size, radius, fill = 'none', label, value, font_size = null, truncate = false) { | |||||
label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label; | label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label; | ||||
if (!font_size) font_size = FONT_SIZE; | |||||
let args = { | let args = { | ||||
className: 'legend-bar', | |||||
className: 'legend-dot', | |||||
x: 0, | x: 0, | ||||
y: 0, | |||||
y: 4 - size, | |||||
height: size, | |||||
width: size, | width: size, | ||||
height: '2px', | |||||
rx: radius, | |||||
fill: fill | fill: fill | ||||
}; | }; | ||||
let text = createSVG('text', { | |||||
className: 'legend-dataset-text', | |||||
x: 0, | |||||
let textLabel = createSVG('text', { | |||||
className: 'legend-dataset-label', | |||||
x: size, | |||||
y: 0, | y: 0, | ||||
dy: (FONT_SIZE * 2) + 'px', | |||||
'font-size': (FONT_SIZE * 1.2) + 'px', | |||||
dx: (font_size) + 'px', | |||||
dy: (font_size / 3) + 'px', | |||||
'font-size': (font_size * 1.6) + 'px', | |||||
'text-anchor': 'start', | 'text-anchor': 'start', | ||||
fill: FONT_FILL, | |||||
innerHTML: label | innerHTML: label | ||||
}); | }); | ||||
let textValue = null; | |||||
if (value) { | |||||
textValue = createSVG('text', { | |||||
className: 'legend-dataset-value', | |||||
x: size, | |||||
y: FONT_SIZE + 10, | |||||
dx: (FONT_SIZE) + 'px', | |||||
dy: (FONT_SIZE / 3) + 'px', | |||||
'font-size': (FONT_SIZE * 1.2) + 'px', | |||||
'text-anchor': 'start', | |||||
innerHTML: value | |||||
}); | |||||
} | |||||
let group = createSVG('g', { | let group = createSVG('g', { | ||||
transform: `translate(${x}, ${y})` | transform: `translate(${x}, ${y})` | ||||
}); | }); | ||||
group.appendChild(createSVG("rect", args)); | group.appendChild(createSVG("rect", args)); | ||||
group.appendChild(text); | |||||
return group; | |||||
} | |||||
group.appendChild(textLabel); | |||||
export function legendDot(x, y, size, fill='none', label, truncate=false) { | |||||
label = truncate ? truncateString(label, LABEL_MAX_CHARS) : label; | |||||
let args = { | |||||
className: 'legend-dot', | |||||
cx: 0, | |||||
cy: 0, | |||||
r: size, | |||||
fill: fill | |||||
}; | |||||
let text = createSVG('text', { | |||||
className: 'legend-dataset-text', | |||||
x: 0, | |||||
y: 0, | |||||
dx: (FONT_SIZE) + 'px', | |||||
dy: (FONT_SIZE/3) + 'px', | |||||
'font-size': (FONT_SIZE * 1.2) + 'px', | |||||
'text-anchor': 'start', | |||||
fill: FONT_FILL, | |||||
innerHTML: label | |||||
}); | |||||
let group = createSVG('g', { | |||||
transform: `translate(${x}, ${y})` | |||||
}); | |||||
group.appendChild(createSVG("circle", args)); | |||||
group.appendChild(text); | |||||
if (value && textValue) { | |||||
group.appendChild(textValue); | |||||
} | |||||
return group; | return group; | ||||
} | } | ||||
@@ -273,7 +277,7 @@ export function legendDot(x, y, size, fill='none', label, truncate=false) { | |||||
export function makeText(className, x, y, content, options = {}) { | export function makeText(className, x, y, content, options = {}) { | ||||
let fontSize = options.fontSize || FONT_SIZE; | let fontSize = options.fontSize || FONT_SIZE; | ||||
let dy = options.dy !== undefined ? options.dy : (fontSize / 2); | let dy = options.dy !== undefined ? options.dy : (fontSize / 2); | ||||
let fill = options.fill || FONT_FILL; | |||||
let fill = options.fill || "var(--charts-label-color)"; | |||||
let textAnchor = options.textAnchor || 'start'; | let textAnchor = options.textAnchor || 'start'; | ||||
return createSVG('text', { | return createSVG('text', { | ||||
className: className, | className: className, | ||||
@@ -287,8 +291,7 @@ export function makeText(className, x, y, content, options = {}) { | |||||
}); | }); | ||||
} | } | ||||
function makeVertLine(x, label, y1, y2, options={}) { | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
function makeVertLine(x, label, y1, y2, options = {}) { | |||||
let l = createSVG('line', { | let l = createSVG('line', { | ||||
className: 'line-vertical ' + options.className, | className: 'line-vertical ' + options.className, | ||||
x1: 0, | x1: 0, | ||||
@@ -310,7 +313,7 @@ function makeVertLine(x, label, y1, y2, options={}) { | |||||
}); | }); | ||||
let line = createSVG('g', { | let line = createSVG('g', { | ||||
transform: `translate(${ x }, 0)` | |||||
transform: `translate(${x}, 0)` | |||||
}); | }); | ||||
line.appendChild(l); | line.appendChild(l); | ||||
@@ -319,13 +322,18 @@ function makeVertLine(x, label, y1, y2, options={}) { | |||||
return line; | return line; | ||||
} | } | ||||
function makeHoriLine(y, label, x1, x2, options={}) { | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
if(!options.lineType) options.lineType = ''; | |||||
if (options.shortenNumbers) label = shortenLargeNumber(label); | |||||
function makeHoriLine(y, label, x1, x2, options = {}) { | |||||
if (!options.lineType) options.lineType = ''; | |||||
if (options.shortenNumbers) { | |||||
if (options.numberFormatter) { | |||||
label = options.numberFormatter(label); | |||||
} else { | |||||
label = shortenLargeNumber(label); | |||||
} | |||||
} | |||||
let className = 'line-horizontal ' + options.className + | let className = 'line-horizontal ' + options.className + | ||||
(options.lineType === "dashed" ? "dashed": ""); | |||||
(options.lineType === "dashed" ? "dashed" : ""); | |||||
let l = createSVG('line', { | let l = createSVG('line', { | ||||
className: className, | className: className, | ||||
@@ -344,7 +352,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
dy: (FONT_SIZE / 2 - 2) + 'px', | dy: (FONT_SIZE / 2 - 2) + 'px', | ||||
'font-size': FONT_SIZE + 'px', | 'font-size': FONT_SIZE + 'px', | ||||
'text-anchor': x1 < x2 ? 'end' : 'start', | 'text-anchor': x1 < x2 ? 'end' : 'start', | ||||
innerHTML: label+"" | |||||
innerHTML: label + "" | |||||
}); | }); | ||||
let line = createSVG('g', { | let line = createSVG('g', { | ||||
@@ -352,7 +360,7 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
'stroke-opacity': 1 | 'stroke-opacity': 1 | ||||
}); | }); | ||||
if(text === 0 || text === '0') { | |||||
if (text === 0 || text === '0') { | |||||
line.style.stroke = "rgba(27, 31, 35, 0.6)"; | line.style.stroke = "rgba(27, 31, 35, 0.6)"; | ||||
} | } | ||||
@@ -362,19 +370,19 @@ function makeHoriLine(y, label, x1, x2, options={}) { | |||||
return line; | return line; | ||||
} | } | ||||
export function yLine(y, label, width, options={}) { | |||||
export function yLine(y, label, width, options = {}) { | |||||
if (!isValidNumber(y)) y = 0; | if (!isValidNumber(y)) y = 0; | ||||
if(!options.pos) options.pos = 'left'; | |||||
if(!options.offset) options.offset = 0; | |||||
if(!options.mode) options.mode = 'span'; | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
if(!options.className) options.className = ''; | |||||
if (!options.pos) options.pos = 'left'; | |||||
if (!options.offset) options.offset = 0; | |||||
if (!options.mode) options.mode = 'span'; | |||||
if (!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
if (!options.className) options.className = ''; | |||||
let x1 = -1 * AXIS_TICK_LENGTH; | let x1 = -1 * AXIS_TICK_LENGTH; | ||||
let x2 = options.mode === 'span' ? width + AXIS_TICK_LENGTH : 0; | let x2 = options.mode === 'span' ? width + AXIS_TICK_LENGTH : 0; | ||||
if(options.mode === 'tick' && options.pos === 'right') { | |||||
if (options.mode === 'tick' && options.pos === 'right') { | |||||
x1 = width + AXIS_TICK_LENGTH; | x1 = width + AXIS_TICK_LENGTH; | ||||
x2 = width; | x2 = width; | ||||
} | } | ||||
@@ -384,22 +392,23 @@ export function yLine(y, label, width, options={}) { | |||||
x1 += options.offset; | x1 += options.offset; | ||||
x2 += options.offset; | x2 += options.offset; | ||||
if (typeof label === "number") label = round(label); | |||||
return makeHoriLine(y, label, x1, x2, { | return makeHoriLine(y, label, x1, x2, { | ||||
stroke: options.stroke, | |||||
className: options.className, | className: options.className, | ||||
lineType: options.lineType, | lineType: options.lineType, | ||||
shortenNumbers: options.shortenNumbers | |||||
shortenNumbers: options.shortenNumbers, | |||||
numberFormatter: options.numberFormatter, | |||||
}); | }); | ||||
} | } | ||||
export function xLine(x, label, height, options={}) { | |||||
export function xLine(x, label, height, options = {}) { | |||||
if (!isValidNumber(x)) x = 0; | if (!isValidNumber(x)) x = 0; | ||||
if(!options.pos) options.pos = 'bottom'; | |||||
if(!options.offset) options.offset = 0; | |||||
if(!options.mode) options.mode = 'span'; | |||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR; | |||||
if(!options.className) options.className = ''; | |||||
if (!options.pos) options.pos = 'bottom'; | |||||
if (!options.offset) options.offset = 0; | |||||
if (!options.mode) options.mode = 'span'; | |||||
if (!options.className) options.className = ''; | |||||
// Draw X axis line in span/tick mode with optional label | // Draw X axis line in span/tick mode with optional label | ||||
// y2(span) | // y2(span) | ||||
@@ -415,21 +424,23 @@ export function xLine(x, label, height, options={}) { | |||||
let y1 = height + AXIS_TICK_LENGTH; | let y1 = height + AXIS_TICK_LENGTH; | ||||
let y2 = options.mode === 'span' ? -1 * AXIS_TICK_LENGTH : height; | let y2 = options.mode === 'span' ? -1 * AXIS_TICK_LENGTH : height; | ||||
if(options.mode === 'tick' && options.pos === 'top') { | |||||
if (options.mode === 'tick' && options.pos === 'top') { | |||||
// top axis ticks | // top axis ticks | ||||
y1 = -1 * AXIS_TICK_LENGTH; | y1 = -1 * AXIS_TICK_LENGTH; | ||||
y2 = 0; | y2 = 0; | ||||
} | } | ||||
return makeVertLine(x, label, y1, y2, { | return makeVertLine(x, label, y1, y2, { | ||||
stroke: options.stroke, | |||||
className: options.className, | className: options.className, | ||||
lineType: options.lineType | lineType: options.lineType | ||||
}); | }); | ||||
} | } | ||||
export function yMarker(y, label, width, options={}) { | |||||
if(!options.labelPos) options.labelPos = 'right'; | |||||
export function yMarker(y, label, width, options = {}) { | |||||
if (!isValidNumber(y)) y = 0; | |||||
if (!options.labelPos) options.labelPos = 'right'; | |||||
if (!options.lineType) options.lineType = 'dashed'; | |||||
let x = options.labelPos === 'left' ? LABEL_MARGIN | let x = options.labelPos === 'left' ? LABEL_MARGIN | ||||
: width - getStringWidth(label, 5) - LABEL_MARGIN; | : width - getStringWidth(label, 5) - LABEL_MARGIN; | ||||
@@ -440,7 +451,7 @@ export function yMarker(y, label, width, options={}) { | |||||
dy: (FONT_SIZE / -2) + 'px', | dy: (FONT_SIZE / -2) + 'px', | ||||
'font-size': FONT_SIZE + 'px', | 'font-size': FONT_SIZE + 'px', | ||||
'text-anchor': 'start', | 'text-anchor': 'start', | ||||
innerHTML: label+"" | |||||
innerHTML: label + "" | |||||
}); | }); | ||||
let line = makeHoriLine(y, '', 0, width, { | let line = makeHoriLine(y, '', 0, width, { | ||||
@@ -454,15 +465,15 @@ export function yMarker(y, label, width, options={}) { | |||||
return line; | return line; | ||||
} | } | ||||
export function yRegion(y1, y2, width, label, options={}) { | |||||
export function yRegion(y1, y2, width, label, options = {}) { | |||||
// return a group | // return a group | ||||
let height = y1 - y2; | let height = y1 - y2; | ||||
let rect = createSVG('rect', { | let rect = createSVG('rect', { | ||||
className: `bar mini`, // remove class | className: `bar mini`, // remove class | ||||
styles: { | styles: { | ||||
fill: `rgba(228, 234, 239, 0.49)`, | |||||
stroke: BASE_LINE_COLOR, | |||||
fill: options.fill || `rgba(228, 234, 239, 0.49)`, | |||||
stroke: options.stroke || BASE_LINE_COLOR, | |||||
'stroke-dasharray': `${width}, ${height}` | 'stroke-dasharray': `${width}, ${height}` | ||||
}, | }, | ||||
// 'data-point-index': index, | // 'data-point-index': index, | ||||
@@ -472,9 +483,9 @@ export function yRegion(y1, y2, width, label, options={}) { | |||||
height: height | height: height | ||||
}); | }); | ||||
if(!options.labelPos) options.labelPos = 'right'; | |||||
if (!options.labelPos) options.labelPos = 'right'; | |||||
let x = options.labelPos === 'left' ? LABEL_MARGIN | let x = options.labelPos === 'left' ? LABEL_MARGIN | ||||
: width - getStringWidth(label+"", 4.5) - LABEL_MARGIN; | |||||
: width - getStringWidth(label + "", 4.5) - LABEL_MARGIN; | |||||
let labelSvg = createSVG('text', { | let labelSvg = createSVG('text', { | ||||
className: 'chart-label', | className: 'chart-label', | ||||
@@ -483,7 +494,7 @@ export function yRegion(y1, y2, width, label, options={}) { | |||||
dy: (FONT_SIZE / -2) + 'px', | dy: (FONT_SIZE / -2) + 'px', | ||||
'font-size': FONT_SIZE + 'px', | 'font-size': FONT_SIZE + 'px', | ||||
'text-anchor': 'start', | 'text-anchor': 'start', | ||||
innerHTML: label+"" | |||||
innerHTML: label + "" | |||||
}); | }); | ||||
let region = createSVG('g', { | let region = createSVG('g', { | ||||
@@ -496,11 +507,11 @@ export function yRegion(y1, y2, width, label, options={}) { | |||||
return region; | return region; | ||||
} | } | ||||
export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) { | |||||
export function datasetBar(x, yTop, width, color, label = '', index = 0, offset = 0, meta = {}) { | |||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine); | ||||
y -= offset; | y -= offset; | ||||
if(height === 0) { | |||||
if (height === 0) { | |||||
height = meta.minHeight; | height = meta.minHeight; | ||||
y -= meta.minHeight; | y -= meta.minHeight; | ||||
} | } | ||||
@@ -511,6 +522,26 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m | |||||
if (!isValidNumber(height, true)) height = 0; | if (!isValidNumber(height, true)) height = 0; | ||||
if (!isValidNumber(width, true)) width = 0; | if (!isValidNumber(width, true)) width = 0; | ||||
// x y h w | |||||
// M{x},{y+r} | |||||
// q0,-{r} {r},-{r} | |||||
// q{r},0 {r},{r} | |||||
// v{h-r} | |||||
// h-{w}z | |||||
// let radius = width/2; | |||||
// let pathStr = `M${x},${y+radius} q0,-${radius} ${radius},-${radius} q${radius},0 ${radius},${radius} v${height-radius} h-${width}z` | |||||
// let rect = createSVG('path', { | |||||
// className: 'bar mini', | |||||
// d: pathStr, | |||||
// styles: { fill: color }, | |||||
// x: x, | |||||
// y: y, | |||||
// 'data-point-index': index, | |||||
// }); | |||||
let rect = createSVG('rect', { | let rect = createSVG('rect', { | ||||
className: `bar mini`, | className: `bar mini`, | ||||
style: `fill: ${color}`, | style: `fill: ${color}`, | ||||
@@ -523,14 +554,14 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m | |||||
label += ""; | label += ""; | ||||
if(!label && !label.length) { | |||||
if (!label && !label.length) { | |||||
return rect; | return rect; | ||||
} else { | } else { | ||||
rect.setAttribute('y', 0); | rect.setAttribute('y', 0); | ||||
rect.setAttribute('x', 0); | rect.setAttribute('x', 0); | ||||
let text = createSVG('text', { | let text = createSVG('text', { | ||||
className: 'data-point-value', | className: 'data-point-value', | ||||
x: width/2, | |||||
x: width / 2, | |||||
y: 0, | y: 0, | ||||
dy: (FONT_SIZE / 2 * -1) + 'px', | dy: (FONT_SIZE / 2 * -1) + 'px', | ||||
'font-size': FONT_SIZE + 'px', | 'font-size': FONT_SIZE + 'px', | ||||
@@ -549,9 +580,9 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m | |||||
} | } | ||||
} | } | ||||
export function datasetDot(x, y, radius, color, label='', index=0) { | |||||
export function datasetDot(x, y, radius, color, label = '', index = 0, hideDotBorder = false) { | |||||
let dot = createSVG('circle', { | let dot = createSVG('circle', { | ||||
style: `fill: ${color}`, | |||||
style: `fill: ${color}; ${hideDotBorder ? `stroke: ${color}`: ''}`, | |||||
'data-point-index': index, | 'data-point-index': index, | ||||
cx: x, | cx: x, | ||||
cy: y, | cy: y, | ||||
@@ -560,7 +591,7 @@ export function datasetDot(x, y, radius, color, label='', index=0) { | |||||
label += ""; | label += ""; | ||||
if(!label && !label.length) { | |||||
if (!label && !label.length) { | |||||
return dot; | return dot; | ||||
} else { | } else { | ||||
dot.setAttribute('cy', 0); | dot.setAttribute('cy', 0); | ||||
@@ -587,7 +618,7 @@ export function datasetDot(x, y, radius, color, label='', index=0) { | |||||
} | } | ||||
} | } | ||||
export function getPaths(xList, yList, color, options={}, meta={}) { | |||||
export function getPaths(xList, yList, color, options = {}, meta = {}) { | |||||
let pointsList = yList.map((y, i) => (xList[i] + ',' + y)); | let pointsList = yList.map((y, i) => (xList[i] + ',' + y)); | ||||
let pointsStr = pointsList.join("L"); | let pointsStr = pointsList.join("L"); | ||||
@@ -595,10 +626,10 @@ export function getPaths(xList, yList, color, options={}, meta={}) { | |||||
if (options.spline) | if (options.spline) | ||||
pointsStr = getSplineCurvePointsStr(xList, yList); | pointsStr = getSplineCurvePointsStr(xList, yList); | ||||
let path = makePath("M"+pointsStr, 'line-graph-path', color); | |||||
let path = makePath("M" + pointsStr, 'line-graph-path', color); | |||||
// HeatLine | // HeatLine | ||||
if(options.heatline) { | |||||
if (options.heatline) { | |||||
let gradient_id = makeGradient(meta.svgDefs, color); | let gradient_id = makeGradient(meta.svgDefs, color); | ||||
path.style.stroke = `url(#${gradient_id})`; | path.style.stroke = `url(#${gradient_id})`; | ||||
} | } | ||||
@@ -608,7 +639,7 @@ export function getPaths(xList, yList, color, options={}, meta={}) { | |||||
}; | }; | ||||
// Region | // Region | ||||
if(options.regionFill) { | |||||
if (options.regionFill) { | |||||
let gradient_id_region = makeGradient(meta.svgDefs, color, true); | let gradient_id_region = makeGradient(meta.svgDefs, color, true); | ||||
let pathStr = "M" + `${xList[0]},${meta.zeroLine}L` + pointsStr + `L${xList.slice(-1)[0]},${meta.zeroLine}`; | let pathStr = "M" + `${xList[0]},${meta.zeroLine}L` + pointsStr + `L${xList.slice(-1)[0]},${meta.zeroLine}`; | ||||
@@ -621,7 +652,7 @@ export function getPaths(xList, yList, color, options={}, meta={}) { | |||||
export let makeOverlay = { | export let makeOverlay = { | ||||
'bar': (unit) => { | 'bar': (unit) => { | ||||
let transformValue; | let transformValue; | ||||
if(unit.nodeName !== 'rect') { | |||||
if (unit.nodeName !== 'rect') { | |||||
transformValue = unit.getAttribute('transform'); | transformValue = unit.getAttribute('transform'); | ||||
unit = unit.childNodes[0]; | unit = unit.childNodes[0]; | ||||
} | } | ||||
@@ -629,7 +660,7 @@ export let makeOverlay = { | |||||
overlay.style.fill = '#000000'; | overlay.style.fill = '#000000'; | ||||
overlay.style.opacity = '0.4'; | overlay.style.opacity = '0.4'; | ||||
if(transformValue) { | |||||
if (transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | overlay.setAttribute('transform', transformValue); | ||||
} | } | ||||
return overlay; | return overlay; | ||||
@@ -637,7 +668,7 @@ export let makeOverlay = { | |||||
'dot': (unit) => { | 'dot': (unit) => { | ||||
let transformValue; | let transformValue; | ||||
if(unit.nodeName !== 'circle') { | |||||
if (unit.nodeName !== 'circle') { | |||||
transformValue = unit.getAttribute('transform'); | transformValue = unit.getAttribute('transform'); | ||||
unit = unit.childNodes[0]; | unit = unit.childNodes[0]; | ||||
} | } | ||||
@@ -648,7 +679,7 @@ export let makeOverlay = { | |||||
overlay.setAttribute('fill', fill); | overlay.setAttribute('fill', fill); | ||||
overlay.style.opacity = '0.6'; | overlay.style.opacity = '0.6'; | ||||
if(transformValue) { | |||||
if (transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | overlay.setAttribute('transform', transformValue); | ||||
} | } | ||||
return overlay; | return overlay; | ||||
@@ -656,7 +687,7 @@ export let makeOverlay = { | |||||
'heat_square': (unit) => { | 'heat_square': (unit) => { | ||||
let transformValue; | let transformValue; | ||||
if(unit.nodeName !== 'circle') { | |||||
if (unit.nodeName !== 'circle') { | |||||
transformValue = unit.getAttribute('transform'); | transformValue = unit.getAttribute('transform'); | ||||
unit = unit.childNodes[0]; | unit = unit.childNodes[0]; | ||||
} | } | ||||
@@ -667,7 +698,7 @@ export let makeOverlay = { | |||||
overlay.setAttribute('fill', fill); | overlay.setAttribute('fill', fill); | ||||
overlay.style.opacity = '0.6'; | overlay.style.opacity = '0.6'; | ||||
if(transformValue) { | |||||
if (transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | overlay.setAttribute('transform', transformValue); | ||||
} | } | ||||
return overlay; | return overlay; | ||||
@@ -677,7 +708,7 @@ export let makeOverlay = { | |||||
export let updateOverlay = { | export let updateOverlay = { | ||||
'bar': (unit, overlay) => { | 'bar': (unit, overlay) => { | ||||
let transformValue; | let transformValue; | ||||
if(unit.nodeName !== 'rect') { | |||||
if (unit.nodeName !== 'rect') { | |||||
transformValue = unit.getAttribute('transform'); | transformValue = unit.getAttribute('transform'); | ||||
unit = unit.childNodes[0]; | unit = unit.childNodes[0]; | ||||
} | } | ||||
@@ -688,14 +719,14 @@ export let updateOverlay = { | |||||
overlay.setAttribute(attr.name, attr.nodeValue); | overlay.setAttribute(attr.name, attr.nodeValue); | ||||
}); | }); | ||||
if(transformValue) { | |||||
if (transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | overlay.setAttribute('transform', transformValue); | ||||
} | } | ||||
}, | }, | ||||
'dot': (unit, overlay) => { | 'dot': (unit, overlay) => { | ||||
let transformValue; | let transformValue; | ||||
if(unit.nodeName !== 'circle') { | |||||
if (unit.nodeName !== 'circle') { | |||||
transformValue = unit.getAttribute('transform'); | transformValue = unit.getAttribute('transform'); | ||||
unit = unit.childNodes[0]; | unit = unit.childNodes[0]; | ||||
} | } | ||||
@@ -706,14 +737,14 @@ export let updateOverlay = { | |||||
overlay.setAttribute(attr.name, attr.nodeValue); | overlay.setAttribute(attr.name, attr.nodeValue); | ||||
}); | }); | ||||
if(transformValue) { | |||||
if (transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | overlay.setAttribute('transform', transformValue); | ||||
} | } | ||||
}, | }, | ||||
'heat_square': (unit, overlay) => { | 'heat_square': (unit, overlay) => { | ||||
let transformValue; | let transformValue; | ||||
if(unit.nodeName !== 'circle') { | |||||
if (unit.nodeName !== 'circle') { | |||||
transformValue = unit.getAttribute('transform'); | transformValue = unit.getAttribute('transform'); | ||||
unit = unit.childNodes[0]; | unit = unit.childNodes[0]; | ||||
} | } | ||||
@@ -724,7 +755,7 @@ export let updateOverlay = { | |||||
overlay.setAttribute(attr.name, attr.nodeValue); | overlay.setAttribute(attr.name, attr.nodeValue); | ||||
}); | }); | ||||
if(transformValue) { | |||||
if (transformValue) { | |||||
overlay.setAttribute('transform', transformValue); | overlay.setAttribute('transform', transformValue); | ||||
} | } | ||||
}, | }, | ||||
@@ -4,13 +4,13 @@ import { CSSTEXT } from '../../css/chartsCss'; | |||||
export function downloadFile(filename, data) { | export function downloadFile(filename, data) { | ||||
var a = document.createElement('a'); | var a = document.createElement('a'); | ||||
a.style = "display: none"; | a.style = "display: none"; | ||||
var blob = new Blob(data, {type: "image/svg+xml; charset=utf-8"}); | |||||
var blob = new Blob(data, { type: "image/svg+xml; charset=utf-8" }); | |||||
var url = window.URL.createObjectURL(blob); | var url = window.URL.createObjectURL(blob); | ||||
a.href = url; | a.href = url; | ||||
a.download = filename; | a.download = filename; | ||||
document.body.appendChild(a); | document.body.appendChild(a); | ||||
a.click(); | a.click(); | ||||
setTimeout(function(){ | |||||
setTimeout(function () { | |||||
document.body.removeChild(a); | document.body.removeChild(a); | ||||
window.URL.revokeObjectURL(url); | window.URL.revokeObjectURL(url); | ||||
}, 300); | }, 300); | ||||
@@ -115,3 +115,29 @@ export function round(d) { | |||||
// https://www.jacklmoore.com/notes/rounding-in-javascript/ | // https://www.jacklmoore.com/notes/rounding-in-javascript/ | ||||
return Number(Math.round(d + 'e4') + 'e-4'); | return Number(Math.round(d + 'e4') + 'e-4'); | ||||
} | } | ||||
/** | |||||
* Creates a deep clone of an object | |||||
* @param {Object} candidate Any Object | |||||
*/ | |||||
export function deepClone(candidate) { | |||||
let cloned, value, key; | |||||
if (candidate instanceof Date) { | |||||
return new Date(candidate.getTime()); | |||||
} | |||||
if (typeof candidate !== "object" || candidate === null) { | |||||
return candidate; | |||||
} | |||||
cloned = Array.isArray(candidate) ? [] : {}; | |||||
for (key in candidate) { | |||||
value = candidate[key]; | |||||
cloned[key] = deepClone(value); | |||||
} | |||||
return cloned; | |||||
} |
@@ -5,25 +5,25 @@ function normalize(x) { | |||||
// Returns normalized number and exponent | // Returns normalized number and exponent | ||||
// https://stackoverflow.com/q/9383593/6495043 | // https://stackoverflow.com/q/9383593/6495043 | ||||
if(x===0) { | |||||
if (x === 0) { | |||||
return [0, 0]; | return [0, 0]; | ||||
} | } | ||||
if(isNaN(x)) { | |||||
return {mantissa: -6755399441055744, exponent: 972}; | |||||
if (isNaN(x)) { | |||||
return { mantissa: -6755399441055744, exponent: 972 }; | |||||
} | } | ||||
var sig = x > 0 ? 1 : -1; | var sig = x > 0 ? 1 : -1; | ||||
if(!isFinite(x)) { | |||||
return {mantissa: sig * 4503599627370496, exponent: 972}; | |||||
if (!isFinite(x)) { | |||||
return { mantissa: sig * 4503599627370496, exponent: 972 }; | |||||
} | } | ||||
x = Math.abs(x); | x = Math.abs(x); | ||||
var exp = Math.floor(Math.log10(x)); | var exp = Math.floor(Math.log10(x)); | ||||
var man = x/Math.pow(10, exp); | |||||
var man = x / Math.pow(10, exp); | |||||
return [sig * man, exp]; | return [sig * man, exp]; | ||||
} | } | ||||
function getChartRangeIntervals(max, min=0) { | |||||
function getChartRangeIntervals(max, min = 0) { | |||||
let upperBound = Math.ceil(max); | let upperBound = Math.ceil(max); | ||||
let lowerBound = Math.floor(min); | let lowerBound = Math.floor(min); | ||||
let range = upperBound - lowerBound; | let range = upperBound - lowerBound; | ||||
@@ -32,38 +32,38 @@ function getChartRangeIntervals(max, min=0) { | |||||
let partSize = 1; | let partSize = 1; | ||||
// To avoid too many partitions | // To avoid too many partitions | ||||
if(range > 5) { | |||||
if(range % 2 !== 0) { | |||||
if (range > 5) { | |||||
if (range % 2 !== 0) { | |||||
upperBound++; | upperBound++; | ||||
// Recalc range | // Recalc range | ||||
range = upperBound - lowerBound; | range = upperBound - lowerBound; | ||||
} | } | ||||
noOfParts = range/2; | |||||
noOfParts = range / 2; | |||||
partSize = 2; | partSize = 2; | ||||
} | } | ||||
// Special case: 1 and 2 | // Special case: 1 and 2 | ||||
if(range <= 2) { | |||||
if (range <= 2) { | |||||
noOfParts = 4; | noOfParts = 4; | ||||
partSize = range/noOfParts; | |||||
partSize = range / noOfParts; | |||||
} | } | ||||
// Special case: 0 | // Special case: 0 | ||||
if(range === 0) { | |||||
if (range === 0) { | |||||
noOfParts = 5; | noOfParts = 5; | ||||
partSize = 1; | partSize = 1; | ||||
} | } | ||||
let intervals = []; | let intervals = []; | ||||
for(var i = 0; i <= noOfParts; i++){ | |||||
for (var i = 0; i <= noOfParts; i++) { | |||||
intervals.push(lowerBound + partSize * i); | intervals.push(lowerBound + partSize * i); | ||||
} | } | ||||
return intervals; | return intervals; | ||||
} | } | ||||
function getChartIntervals(maxValue, minValue=0) { | |||||
function getChartIntervals(maxValue, minValue = 0) { | |||||
let [normalMaxValue, exponent] = normalize(maxValue); | let [normalMaxValue, exponent] = normalize(maxValue); | ||||
let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0; | |||||
let normalMinValue = minValue ? minValue / Math.pow(10, exponent) : 0; | |||||
// Allow only 7 significant digits | // Allow only 7 significant digits | ||||
normalMaxValue = normalMaxValue.toFixed(6); | normalMaxValue = normalMaxValue.toFixed(6); | ||||
@@ -73,7 +73,7 @@ function getChartIntervals(maxValue, minValue=0) { | |||||
return intervals; | return intervals; | ||||
} | } | ||||
export function calcChartIntervals(values, withMinimum=false) { | |||||
export function calcChartIntervals(values, withMinimum = false, range = {}) { | |||||
//*** Where the magic happens *** | //*** Where the magic happens *** | ||||
// Calculates best-fit y intervals from given values | // Calculates best-fit y intervals from given values | ||||
@@ -82,6 +82,14 @@ export function calcChartIntervals(values, withMinimum=false) { | |||||
let maxValue = Math.max(...values); | let maxValue = Math.max(...values); | ||||
let minValue = Math.min(...values); | let minValue = Math.min(...values); | ||||
if (range.max !== undefined) { | |||||
maxValue = maxValue > range.max ? maxValue : range.max; | |||||
} | |||||
if (range.min !== undefined) { | |||||
minValue = minValue < range.min ? minValue : range.min; | |||||
} | |||||
// Exponent to be used for pretty print | // Exponent to be used for pretty print | ||||
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars | let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars | ||||
@@ -92,7 +100,7 @@ export function calcChartIntervals(values, withMinimum=false) { | |||||
// Then unshift the negative values | // Then unshift the negative values | ||||
let value = 0; | let value = 0; | ||||
for(var i = 1; value < absMinValue; i++) { | |||||
for (var i = 1; value < absMinValue; i++) { | |||||
value += intervalSize; | value += intervalSize; | ||||
intervals.unshift((-1) * value); | intervals.unshift((-1) * value); | ||||
} | } | ||||
@@ -101,9 +109,9 @@ export function calcChartIntervals(values, withMinimum=false) { | |||||
// CASE I: Both non-negative | // CASE I: Both non-negative | ||||
if(maxValue >= 0 && minValue >= 0) { | |||||
if (maxValue >= 0 && minValue >= 0) { | |||||
exponent = normalize(maxValue)[1]; | exponent = normalize(maxValue)[1]; | ||||
if(!withMinimum) { | |||||
if (!withMinimum) { | |||||
intervals = getChartIntervals(maxValue); | intervals = getChartIntervals(maxValue); | ||||
} else { | } else { | ||||
intervals = getChartIntervals(maxValue, minValue); | intervals = getChartIntervals(maxValue, minValue); | ||||
@@ -112,7 +120,7 @@ export function calcChartIntervals(values, withMinimum=false) { | |||||
// CASE II: Only minValue negative | // CASE II: Only minValue negative | ||||
else if(maxValue > 0 && minValue < 0) { | |||||
else if (maxValue > 0 && minValue < 0) { | |||||
// `withMinimum` irrelevant in this case, | // `withMinimum` irrelevant in this case, | ||||
// We'll be handling both sides of zero separately | // We'll be handling both sides of zero separately | ||||
// (both starting from zero) | // (both starting from zero) | ||||
@@ -121,7 +129,7 @@ export function calcChartIntervals(values, withMinimum=false) { | |||||
let absMinValue = Math.abs(minValue); | let absMinValue = Math.abs(minValue); | ||||
if(maxValue >= absMinValue) { | |||||
if (maxValue >= absMinValue) { | |||||
exponent = normalize(maxValue)[1]; | exponent = normalize(maxValue)[1]; | ||||
intervals = getPositiveFirstIntervals(maxValue, absMinValue); | intervals = getPositiveFirstIntervals(maxValue, absMinValue); | ||||
} else { | } else { | ||||
@@ -135,7 +143,7 @@ export function calcChartIntervals(values, withMinimum=false) { | |||||
// CASE III: Both non-positive | // CASE III: Both non-positive | ||||
else if(maxValue <= 0 && minValue <= 0) { | |||||
else if (maxValue <= 0 && minValue <= 0) { | |||||
// Mirrored Case I: | // Mirrored Case I: | ||||
// Work with positives, then reverse the sign and array | // Work with positives, then reverse the sign and array | ||||
@@ -143,7 +151,7 @@ export function calcChartIntervals(values, withMinimum=false) { | |||||
let pseudoMinValue = Math.abs(maxValue); | let pseudoMinValue = Math.abs(maxValue); | ||||
exponent = normalize(pseudoMaxValue)[1]; | exponent = normalize(pseudoMaxValue)[1]; | ||||
if(!withMinimum) { | |||||
if (!withMinimum) { | |||||
intervals = getChartIntervals(pseudoMaxValue); | intervals = getChartIntervals(pseudoMaxValue); | ||||
} else { | } else { | ||||
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue); | intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue); | ||||
@@ -158,11 +166,11 @@ export function calcChartIntervals(values, withMinimum=false) { | |||||
export function getZeroIndex(yPts) { | export function getZeroIndex(yPts) { | ||||
let zeroIndex; | let zeroIndex; | ||||
let interval = getIntervalSize(yPts); | let interval = getIntervalSize(yPts); | ||||
if(yPts.indexOf(0) >= 0) { | |||||
if (yPts.indexOf(0) >= 0) { | |||||
// the range has a given zero | // the range has a given zero | ||||
// zero-line on the chart | // zero-line on the chart | ||||
zeroIndex = yPts.indexOf(0); | zeroIndex = yPts.indexOf(0); | ||||
} else if(yPts[0] > 0) { | |||||
} else if (yPts[0] > 0) { | |||||
// Minimum value is positive | // Minimum value is positive | ||||
// zero-line is off the chart: below | // zero-line is off the chart: below | ||||
let min = yPts[0]; | let min = yPts[0]; | ||||
@@ -181,7 +189,7 @@ export function getRealIntervals(max, noOfIntervals, min = 0, asc = 1) { | |||||
let part = range * 1.0 / noOfIntervals; | let part = range * 1.0 / noOfIntervals; | ||||
let intervals = []; | let intervals = []; | ||||
for(var i = 0; i <= noOfIntervals; i++) { | |||||
for (var i = 0; i <= noOfIntervals; i++) { | |||||
intervals.push(min + part * i); | intervals.push(min + part * i); | ||||
} | } | ||||
@@ -193,7 +201,7 @@ export function getIntervalSize(orderedArray) { | |||||
} | } | ||||
export function getValueRange(orderedArray) { | export function getValueRange(orderedArray) { | ||||
return orderedArray[orderedArray.length-1] - orderedArray[0]; | |||||
return orderedArray[orderedArray.length - 1] - orderedArray[0]; | |||||
} | } | ||||
export function scale(val, yAxis) { | export function scale(val, yAxis) { | ||||
@@ -210,7 +218,7 @@ export function isInRange2D(coord, minCoord, maxCoord) { | |||||
} | } | ||||
export function getClosestInArray(goal, arr, index = false) { | export function getClosestInArray(goal, arr, index = false) { | ||||
let closest = arr.reduce(function(prev, curr) { | |||||
let closest = arr.reduce(function (prev, curr) { | |||||
return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev); | return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev); | ||||
}, []); | }, []); | ||||
@@ -226,7 +234,7 @@ export function calcDistribution(values, distributionSize) { | |||||
let distributionStep = 1 / (distributionSize - 1); | let distributionStep = 1 / (distributionSize - 1); | ||||
let distribution = []; | let distribution = []; | ||||
for(var i = 0; i < distributionSize; i++) { | |||||
for (var i = 0; i < distributionSize; i++) { | |||||
let checkpoint = dataMaxValue * (distributionStep * i); | let checkpoint = dataMaxValue * (distributionStep * i); | ||||
distribution.push(checkpoint); | distribution.push(checkpoint); | ||||
} | } | ||||