Compare commits

...

97 Commits

Author SHA1 Message Date
Soham Kulkarni
d8ae43fac6
fix: rollup to generate css file with proper name (#427) 2025-03-27 20:05:28 +05:30
Faris Ansari
0ee7f9a30f v2.0.0-rc25 2025-03-26 14:08:59 +05:30
Faris Ansari
4967fc9e07 chore: update scss plugin 2025-03-26 14:04:57 +05:30
Soham Kulkarni
8cc3b45c18
chore: add correct build step (#426) 2025-03-26 13:58:07 +05:30
Soham Kulkarni
bd77462d01
chore: add build step in ci (#425) 2025-03-26 13:24:04 +05:30
Faris Ansari
17994abb92 chore: there are no tests 2025-03-25 20:05:18 +05:30
Faris Ansari
77cc7fde6c chore: add publish workflow 2025-03-25 20:04:06 +05:30
Soham Kulkarni
dc6ef83a7b
fix: make series label space ratio configurable (#424) 2025-03-25 19:55:03 +05:30
Arjun
f5fc2c1bf7
Merge pull request #390 from kimsreng/v2-beta
Fixed: Unrecognized falsy value of truncateLegends
2022-11-15 16:55:22 +05:30
Shivam Mishra
b6403b83c3
chore: update rc version 2022-11-14 12:47:46 +05:30
Shivam Mishra
2e3011c267
feat: allow more attributes in formatY (#401) 2022-11-14 12:33:03 +05:30
kimsreng
2aab8dd315
Fixed: Unrecognized falsy value of truncateLegends 2022-07-22 16:46:00 +07:00
Suraj Shetty
ea95aece5a chore: Bump version to fix faulty publish 2022-07-18 14:22:08 +05:30
Suraj Shetty
f2d2eb1e9e chore: bump version 2022-07-18 14:21:41 +05:30
Ankush Menat
2b455e95a6
Merge pull request #388 from frappe/custom_number_shortner
feat: custom number formatter function
2022-07-18 11:06:07 +05:30
Ankush Menat
624cf70f03 feat: custom number formatter function
You can now pass `numberFormatter` in `axisOptions` to customize
number shortening behaviour.

The interface for this function is pretty simple

```javascript
(value) => {
    // format value
    return value
};
```
2022-07-15 16:35:29 +05:30
Ankush Menat
5ea1d33fdf chore: add editorconfig 2022-07-15 16:28:30 +05:30
Shivam Mishra
46191e7a53 chore: upgrade can I use 2021-09-21 05:59:22 +00:00
Shivam Mishra
95a19f09a3 chore: bump version 2021-09-21 05:57:59 +00:00
Shivam Mishra
8bc8231b21 fix: imports 2021-09-21 05:56:27 +00:00
Shivam Mishra
c18c076adc chore: bump to rc19 2021-06-16 10:53:17 +00:00
Shivam Mishra
a765508bbd chore: bump to rc18 2021-06-16 10:49:01 +00:00
Shivam Mishra
cdf5fbe33b feat: better regionFill gradients 2021-06-16 10:48:38 +00:00
Shivam Mishra
a26a81c866 fix: style issue with dots 2021-06-03 09:39:49 +00:00
Shivam Mishra
f19d15f112 chore: bump version 2021-06-03 09:24:12 +00:00
Shivam Mishra
07597ebe87 feat: add trailingDot option 2021-06-03 09:08:57 +00:00
Shivam Mishra
9c0ecc89be chore: bump to rc15 2021-05-07 09:43:05 +00:00
Shivam Mishra
3d6a349425 fix: range clipping logic 2021-05-07 09:42:45 +00:00
Shivam Mishra
ce4126b3b1 chore: bump to rc14 2021-05-07 08:49:14 +00:00
Shivam Mishra
f145d6bbe9 fix: allow option to disable entry animation 2021-05-07 08:47:33 +00:00
Shivam Mishra
b7f20aab15 feat: allow clipping yAxis range 2021-05-07 08:34:50 +00:00
Shivam Mishra
7c9cf65385 refactor: remove space before shortened number 2021-05-07 08:31:08 +00:00
T3cH_W1z4rD
61717aee95 Fixed XSS (#339) 2021-05-07 08:27:54 +00:00
Shivam Mishra
304d23502a chore: bump to rc13 2021-04-18 11:47:31 +00:00
Shivam Mishra
db12dcf27c fix: path building error 2021-04-18 11:35:44 +00:00
Shivam Mishra
1b4e206a1e fix: chart labels spacing issue 2021-04-18 11:10:36 +00:00
Shivam Mishra
8d8096869f chore: bump to rc12 2021-04-18 10:39:44 +00:00
Shivam Mishra
fa559cc8bd fix: donut chart, single data point bug 2021-04-18 10:39:21 +00:00
Shivam Mishra
ed8f97efd5 feat: add resize observer 2021-04-18 10:28:18 +00:00
Suraj Shetty
8d68d9933e chore: Bump version to v2.0.0-rc11 2021-03-19 14:15:42 +05:30
prssanna
72d5507c6f chore: bump to rc10 2021-03-09 16:56:18 +05:30
prssanna
d6ab5b952e fix: name.replace exception 2021-03-09 14:03:02 +05:30
Shivam Mishra
4854591a89 chore: bump to rc9 2021-02-24 10:20:50 +00:00
Shivam Mishra
a0265677a2 fix: clone in update 2021-02-24 10:19:59 +00:00
Shivam Mishra
ba0d3703f5 chore: bump to rc8 2021-02-22 03:23:51 +00:00
Shivam Mishra
bbe91840f7 chore: update export 2021-02-22 03:20:59 +00:00
Shivam Mishra
94a7e02d75 chore: bump to rc7 2021-02-22 03:18:51 +00:00
Shivam Mishra
d8984ec5ae feat: allow setting lineType for yMarker 2021-02-22 03:18:14 +00:00
Shivam Mishra
067f5ea7f8 feat: allow hiding legends 2021-02-22 03:17:07 +00:00
Shivam Mishra
263f38c9ec chore: bump to rc6 2021-02-18 10:55:43 +00:00
Shivam Mishra
7e13f81063 feat: clone options before building 2021-02-18 10:54:33 +00:00
Suraj Shetty
9cc7bde398 chore: Bump version to rc5 2021-02-02 12:30:29 +05:30
prssanna
ce64f01a8b fix: import round function 2021-01-28 16:24:47 +05:30
Shivam Mishra
583d82a96e chore: build rc3 2020-12-15 16:38:44 +05:30
Shivam Mishra
8308b9fd90 feat: default to css vars in makeText 2020-12-15 16:37:13 +05:30
Shivam Mishra
f413c75932 chore: build rc2 for v2 2020-12-15 16:08:28 +05:30
Shivam Mishra
86bbb6d2aa chore: remove base font-fill 2020-12-15 16:07:49 +05:30
Shivam Mishra
d386d21e2f chore: build rc1 for v2 2020-12-14 16:36:40 +05:30
Shivam Mishra
8101b1822e fix: remove duplicate function 2020-12-14 15:32:42 +05:30
Shivam Mishra
cff7ce4c27 feat: add deepsource 2020-12-14 15:25:39 +05:30
Shivam Mishra
690a7f4467 refactor: validate y 2020-12-14 15:25:39 +05:30
Shivam Mishra
03f6436be4 feat: update variables 2020-12-14 15:25:39 +05:30
Shivam Mishra
8524e6cbd6 feat: allow custom yRegion colors 2020-12-14 15:25:39 +05:30
Shivam Mishra
1181660ed1 chore: remove console.log 2020-12-14 15:25:39 +05:30
Shivam Mishra
26671b143b chore: format codebase 2020-12-14 15:25:39 +05:30
Shivam Mishra
5d2c141f14 fix: floating point rounding fixes 2020-12-14 15:25:39 +05:30
Shivam Mishra
e2d7ce8b21 feat: allow stroke color for yMarker 2020-12-14 15:25:39 +05:30
Shivam Mishra
73f1d9b1e3 feat: DonutChart extends PieChart 2020-12-14 15:25:39 +05:30
Shivam Mishra
dc00b46a7a chore: format donut chart and pie chart 2020-12-14 15:25:39 +05:30
Shivam Mishra
fd28107795 chore: remove unused import 2020-12-14 15:25:39 +05:30
Shivam Mishra
7cfa35a418 feat: new legends
* merge legendBar and legendDots
* use BaseChart to render legends
2020-12-14 15:25:20 +05:30
Shivam Mishra
d84614cb65 feat: update legendBar 2020-12-14 15:25:20 +05:30
Shivam Mishra
610cfb1f6d feat: update main in package.json 2020-12-14 15:25:20 +05:30
Shivam Mishra
767a905967 feat: remove deprecated MultiAxis 2020-12-14 15:25:20 +05:30
Shivam Mishra
56a7374277 chore: formatting improvements 2020-12-14 15:25:20 +05:30
Shivam Mishra
562a9cba55 chore: update tooltip styles
* max-width for tooltip label
* remove ellipsis
* use normal white-space wrapping
2020-12-14 15:25:20 +05:30
Shivam Mishra
4f4dd4d36f chore: remove dist 2020-12-14 15:25:20 +05:30
Shivam Mishra
0257bc370e feat: ignore dist 2020-12-14 15:25:08 +05:30
Shivam Mishra
7dde276477 feat: generate es 2020-12-14 15:25:08 +05:30
Shivam Mishra
b12b916584 chore: update build 2020-12-14 15:25:08 +05:30
Shivam Mishra
a7b06bb027 feat: change axis line colors 2020-12-14 15:25:08 +05:30
Shivam Mishra
9dd24aa7a7 feat: don't override axis line colors 2020-12-14 15:25:08 +05:30
Shivam Mishra
4d8321e9be feat: always show last label when xIsSeries is toggled 2020-12-14 15:25:08 +05:30
Shivam Mishra
399ff37b6a feat: add space ratio option for x-axis series labels 2020-12-14 15:25:08 +05:30
Shivam Mishra
dc49b29d39 feat: [breaking] hide dots by default 2020-12-14 15:25:08 +05:30
Shivam Mishra
51a6ed33a5 feat: update opacity for light gradient 2020-12-14 15:25:08 +05:30
Shivam Mishra
f8c3f9d7b4 chore: reset space ratio 2020-12-14 15:25:08 +05:30
Shivam Mishra
d3909d49c1 feat: added function for rounded top rect 2020-12-14 15:25:08 +05:30
Shivam Mishra
255e806533 chore: cleanup rounded bar utility 2020-12-14 15:25:08 +05:30
Shivam Mishra
6cb9bf6bd3 chore: update build 2020-12-14 15:25:08 +05:30
Shivam Mishra
5e17dea6de feat: tune spacing for legends 2020-12-14 15:25:08 +05:30
Shivam Mishra
89fc842330 feat: update build 2020-12-14 15:25:08 +05:30
Shivam Mishra
63e0a0bdf4 feat: generate sourcemaps inn build 2020-12-14 15:25:08 +05:30
Shivam Mishra
d2dd27dfab feat: added helper function for rounded edges 2020-12-14 15:25:08 +05:30
Shivam Mishra
846a21ae6a feat: remove depth for percentage chart 2020-12-14 15:25:08 +05:30
Shivam Mishra
5b00da4974 chore: remove docs folder 2020-12-14 15:25:08 +05:30
Shivam Mishra
069a5f7451 feat: show bundle size 2020-12-14 15:24:41 +05:30
56 changed files with 1738 additions and 13804 deletions

9
.deepsource.toml Normal file
View File

@ -0,0 +1,9 @@
version = 1
[[analyzers]]
name = "javascript"
enabled = true
[analyzers.meta]
environment = ["browser"]
style_guide = "standard"

14
.editorconfig Normal file
View File

@ -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

19
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Publish on NPM
on:
push:
branches: [ v2-beta ]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm install
- run: npm run build
- uses: JS-DevTools/npm-publish@v3
with:
token: ${{ secrets.NPM_TOKEN }}
tag: next

3
.gitignore vendored
View File

@ -60,4 +60,7 @@ typings/
# next.js build output # next.js build output
.next .next
# npm build output
dist
.DS_Store .DS_Store

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +0,0 @@
.chart-container {
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; }
.chart-container .axis, .chart-container .chart-label {
fill: #313B44; }
.chart-container .axis line, .chart-container .chart-label line {
stroke: #E2E6E9; }
.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;
text-align: center;
background: #FFFFFF;
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; }
.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;
font-weight: 600; }
.graph-svg-tip .svg-pointer {
position: absolute;
height: 12px;
width: 12px;
border-radius: 2px;
background: white;
transform: rotate(45deg);
margin-top: -7px;
margin-left: -6px; }
.graph-svg-tip.comparison {
text-align: left;
padding: 0px;
pointer-events: none; }
.graph-svg-tip.comparison .title {
display: block;
padding: 16px;
margin: 0;
color: #313B44;
font-weight: 600;
line-height: 1;
pointer-events: none;
text-transform: uppercase; }
.graph-svg-tip.comparison ul {
margin: 0;
white-space: nowrap;
list-style: none; }
.graph-svg-tip.comparison ul.tooltip-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 5px; }
.graph-svg-tip.comparison li {
display: inline-block;
display: flex;
flex-direction: row;
font-weight: 600;
line-height: 1;
padding: 5px 15px 15px 15px; }
.graph-svg-tip.comparison li .tooltip-legend {
height: 12px;
width: 12px;
margin-right: 8px;
border-radius: 2px; }
.graph-svg-tip.comparison li .tooltip-label {
margin-top: 4px;
font-size: 11px;
max-width: 100px;
color: #313B44;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap; }
.graph-svg-tip.comparison li .tooltip-value {
color: #192734; }

File diff suppressed because one or more lines are too long

View File

@ -1,2 +0,0 @@
plugins:
- jekyll-redirect-from

File diff suppressed because one or more lines are too long

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 */

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@ -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],
};

View File

@ -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',
}
}
}
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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();
});

View File

@ -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

File diff suppressed because one or more lines are too long

View File

View File

@ -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"> &lt!--HTML--&gt;
&lt;div id="chart"&gt;&lt;/div&gt;</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"> &lt;script src="https://unpkg.com/frappe-charts@1.1.0"&gt;&lt;/script&gt;</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>

View File

@ -1,11 +1,12 @@
{ {
"name": "frappe-charts", "name": "frappe-charts",
"version": "1.5.2", "version": "2.0.0-rc26",
"main": "dist/frappe-charts.cjs.js", "main": "dist/frappe-charts.esm.js",
"common": "dist/frappe-charts.cjs.js",
"module": "dist/frappe-charts.esm.js", "module": "dist/frappe-charts.esm.js",
"browser": "dist/frappe-charts.umd.js", "browser": "dist/frappe-charts.umd.js",
"unpkg": "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",
"directories": { "directories": {
"doc": "docs" "doc": "docs"
@ -39,10 +40,12 @@
"@babel/preset-env": "^7.10.4", "@babel/preset-env": "^7.10.4",
"rollup": "^2.21.0", "rollup": "^2.21.0",
"rollup-plugin-babel": "^4.4.0", "rollup-plugin-babel": "^4.4.0",
"rollup-plugin-bundle-size": "^1.0.3",
"rollup-plugin-commonjs": "^10.1.0", "rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-eslint": "^7.0.0", "rollup-plugin-eslint": "^7.0.0",
"rollup-plugin-postcss": "^3.1.3", "rollup-plugin-postcss": "^3.1.3",
"rollup-plugin-scss": "^2.5.0", "rollup-plugin-scss": "^4.0.1",
"rollup-plugin-terser": "^6.1.0" "rollup-plugin-terser": "^6.1.0",
"sass": "^1.86.0"
} }
} }

View File

@ -4,6 +4,7 @@ import commonjs from 'rollup-plugin-commonjs';
import babel from 'rollup-plugin-babel'; import babel from 'rollup-plugin-babel';
import postcss from 'rollup-plugin-postcss'; import postcss from 'rollup-plugin-postcss';
import scss from 'rollup-plugin-scss'; import scss from 'rollup-plugin-scss';
import bundleSize from 'rollup-plugin-bundle-size';
import { terser } from 'rollup-plugin-terser'; import { terser } from 'rollup-plugin-terser';
@ -12,6 +13,7 @@ export default [
{ {
input: 'src/js/index.js', input: 'src/js/index.js',
output: { output: {
sourcemap: true,
name: 'frappe-charts', name: 'frappe-charts',
file: pkg.browser, file: pkg.browser,
format: 'umd' format: 'umd'
@ -22,7 +24,8 @@ export default [
exclude: ['node_modules/**'] exclude: ['node_modules/**']
}), }),
terser(), terser(),
scss({ output: 'dist/frappe-charts.min.css' }) scss({ fileName: 'frappe-charts.min.css' }),
bundleSize()
] ]
}, },
@ -30,14 +33,16 @@ export default [
{ {
input: 'src/js/chart.js', input: 'src/js/chart.js',
output: [ output: [
{ file: pkg.common, format: 'cjs' }, { file: pkg.common, format: 'cjs', sourcemap: true },
{ file: pkg.module, format: 'es' } { file: pkg.module, format: 'es', sourcemap: true },
], ],
plugins: [ plugins: [
babel({ babel({
exclude: ['node_modules/**'] exclude: ['node_modules/**']
}), }),
postcss() terser(),
postcss(),
bundleSize()
] ]
} }
]; ];

View File

@ -1,54 +1,58 @@
:root { :root {
--fr-label-color: #313b44; --charts-label-color: #313b44;
--fr-axis-line-color: #E2E6E9; --charts-axis-line-color: #f4f5f6;
--fr-stroke-width: 2px; --charts-tooltip-title: var(--charts-label-color);
--fr-dataset-circle-stroke: #FFFFFF; --charts-tooltip-label: var(--charts-label-color);
--fr-dataset-circle-stroke-width: var(--fr-stroke-width); --charts-tooltip-value: #192734;
--charts-tooltip-bg: #ffffff;
--fr-tooltip-title: var(--fr-label-color); --charts-stroke-width: 2px;
--fr-tooltip-label: var(--fr-label-color); --charts-dataset-circle-stroke: #ffffff;
--fr-tooltip-value: #192734; --charts-dataset-circle-stroke-width: var(--charts-stroke-width);
--fr-tooltip-bg: #FFFFFF;
--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 */
font-family: -apple-system, BlinkMacSystemFont, font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans",
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; "Helvetica Neue", sans-serif;
.axis, .chart-label { .axis,
fill: var(--fr-label-color); .chart-label {
fill: var(--charts-label-color);
line { line {
stroke: var(--fr-axis-line-color); stroke: var(--charts-axis-line-color);
} }
} }
.dataset-units { .dataset-units {
circle { circle {
stroke: var(--fr-dataset-circle-stroke); stroke: var(--charts-dataset-circle-stroke);
stroke-width: var(--fr-dataset-circle-stroke-width); stroke-width: var(--charts-dataset-circle-stroke-width);
} }
path { path {
fill: none; fill: none;
stroke-opacity: 1; stroke-opacity: 1;
stroke-width: var(--fr-stroke-width); stroke-width: var(--charts-stroke-width);
} }
} }
.dataset-path { .dataset-path {
stroke-width: var(--fr-stroke-width); stroke-width: var(--charts-stroke-width);
} }
.path-group { .path-group {
path { path {
fill: none; fill: none;
stroke-opacity: 1; stroke-opacity: 1;
stroke-width: var(--fr-stroke-width); stroke-width: var(--charts-stroke-width);
} }
} }
@ -69,12 +73,12 @@
} }
.legend-dataset-label { .legend-dataset-label {
fill: var(--fr-tooltip-label); fill: var(--charts-legend-label);
font-weight: 600; font-weight: 600;
} }
.legend-dataset-value { .legend-dataset-value {
fill: var(--fr-tooltip-value); fill: var(--charts-legend-value);
} }
} }
@ -84,8 +88,10 @@
padding: 10px; padding: 10px;
font-size: 12px; font-size: 12px;
text-align: center; text-align: center;
background: var(--fr-tooltip-bg); 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); 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; border-radius: 6px;
ul { ul {
@ -110,7 +116,7 @@
height: 12px; height: 12px;
width: 12px; width: 12px;
border-radius: 2px; border-radius: 2px;
background: var(--fr-tooltip-bg); background: var(--charts-tooltip-bg);
transform: rotate(45deg); transform: rotate(45deg);
margin-top: -7px; margin-top: -7px;
margin-left: -6px; margin-left: -6px;
@ -125,11 +131,15 @@
display: block; display: block;
padding: 16px; padding: 16px;
margin: 0; margin: 0;
color: var(--fr-tooltip-title); 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; text-transform: uppercase;
strong {
color: var(--charts-tooltip-value);
}
} }
ul { ul {
@ -163,16 +173,15 @@
.tooltip-label { .tooltip-label {
margin-top: 4px; margin-top: 4px;
font-size: 11px; font-size: 11px;
max-width: 100px; line-height: 1.25;
max-width: 150px;
white-space: normal;
color: var(--fr-tooltip-label); color: var(--charts-tooltip-label);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
.tooltip-value { .tooltip-value {
color: var(--fr-tooltip-value); color: var(--charts-tooltip-value);
} }
} }
} }

View File

@ -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,

View File

@ -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); totals = allTotals.slice(0, maxSlices - 1);
let remaining = allTotals.slice(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,36 +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; makeLegend(data, index, x_pos, y_pos) {
let y = 0; let formatted = this.config.formatTooltipY ? this.config.formatTooltipY(data) : data;
this.legendTotals.map((d, i) => {
let barWidth = 150; return legendDot(
let divisor = Math.floor( x_pos,
(this.width - getExtraWidth(this.measures))/barWidth y_pos,
); 12, // size
if (this.legendTotals.length < divisor) { 3, // dot radius
barWidth = this.width/this.legendTotals.length; this.colors[index], // fill
} this.state.labels[index], // label
if(count > divisor) { formatted, // value
count = 0; null, // base_font_size
y += 60; this.config.truncateLegends // truncate_legends
} );
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,
12,
3,
this.colors[i],
`${label}: ${formatted}`,
d,
false
);
this.legendArea.appendChild(dot);
count++;
});
} }
} }

View File

@ -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 { makeOverlay, updateOverlay, legendDot } from '../utils/draw';
import { getTopOffset, getLeftOffset, MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO, import {
LINE_CHART_DOT_SIZE } from '../utils/constants'; 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,29 @@ 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.seriesLabelSpaceRatio = options.axisOptions.seriesLabelSpaceRatio
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 +73,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 +90,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 +116,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 == '&' ? '&amp;' : char == '<' ? '&lt;' : '&gt;'), name: d.name && d.name.replace(/<|>|&/g, (char) => char == '&' ? '&amp;' : char == '<' ? '&lt;' : '&gt;'),
index: i, index: i,
chartType: d.chartType, chartType: d.chartType,
@ -125,14 +131,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 +147,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 +170,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 +180,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 +199,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 shortenNumbers: this.config.shortenYAxisNumbers,
// pos: 'right' numberFormatter: this.config.numberFormatter,
}, },
function() { function () {
return this.state.yAxis; return this.state.yAxis;
}.bind(this) }.bind(this)
], ],
@ -208,10 +214,10 @@ 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,this.config.seriesLabelSpaceRatio);
return s.xAxis; return s.xAxis;
}.bind(this) }.bind(this)
@ -223,7 +229,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 +251,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); let xPositions = s.xAxis.positions.map(x => x - barsWidth / 2);
if(!stacked) { 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 (this.config.valuesOverPoints) {
if(stacked && d.index === s.datasets.length - 1) { if (stacked && d.index === s.datasets.length - 1) {
labels = d.cumulativeYs; labels = d.cumulativeYs;
} else { } else {
labels = d.values; labels = d.values;
@ -269,7 +275,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 +305,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 +339,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 +354,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];
@ -369,7 +377,11 @@ export default class AxisChart extends BaseChart {
value: value, value: value,
yPos: set.yPositions[index], yPos: set.yPositions[index],
color: this.colors[i], color: this.colors[i],
formatted: formatY ? formatY(value) : value, formatted: formatY ? formatY(value, {
name: set.name,
index: set.index,
values: set.values
}) : value,
}; };
}); });
@ -391,8 +403,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) if (relY < this.height + getTopOffset(m)
&& relY > getTopOffset(m)) { && relY > getTopOffset(m)) {
this.mapTooltipXPosition(relX); this.mapTooltipXPosition(relX);
} else { } else {
this.tip.hideTip(); this.tip.hideTip();
@ -402,7 +414,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 +423,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 +434,32 @@ export default class AxisChart extends BaseChart {
renderLegend() { renderLegend() {
let s = this.data; let s = this.data;
if(s.datasets.length > 1) { if (s.datasets.length > 1) {
this.legendArea.textContent = ''; super.renderLegend(s.datasets);
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);
});
} }
} }
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 +474,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 +488,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 +534,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 +547,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 < 0) index = 0;
if(index >= s.xAxis.labels.length) index = s.xAxis.labels.length - 1; if (index >= s.xAxis.labels.length) index = s.xAxis.labels.length - 1;
if(index === s.currentIndex) return; 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 +557,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 +566,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 +578,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 +587,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];
} }
}); });

View File

@ -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, import { LEGEND_ITEM_WIDTH } from '../utils/constants';
INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS} 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,
truncateLegends: options.truncateLegends || 1 disableEntryAnimation: options.disableEntryAnimation || 0,
truncateLegends: (typeof options.truncateLegends !== 'undefined') ? 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.title.length) { m.titleHeight = 0; }
if(!this.config.showLegend) m.legendHeight = 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) { update(data, drawing = false) {
if(!data) { if (!data) console.error('No data to update.');
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) { render(components = this.components, animate = true) {
if(this.config.isNavigable) { 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;
setupNavigation(init=false) { dataset.map((data, index) => {
if(!this.config.isNavigable) return; 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++;
});
}
if(init) { makeLegend() { }
setupNavigation(init = false) {
if (!this.config.isNavigable) return;
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() {} makeOverlay() { }
updateOverlay() {} updateOverlay() { }
bindOverlay() {} bindOverlay() { }
bindUnits() {} bindUnits() { }
onLeftArrow() {} onLeftArrow() { }
onRightArrow() {} onRightArrow() { }
onUpArrow() {} onUpArrow() { }
onDownArrow() {} onDownArrow() { }
onEnterKey() {} onEnterKey() { }
addDataPoint() {} addDataPoint() { }
removeDataPoint() {} removeDataPoint() { }
getDataPoint() {} getDataPoint() { }
setCurrentDataPoint() {} setCurrentDataPoint() { }
updateDataset() {} updateDataset() { }
export() { export() {
let chartSvg = prepareForExport(this.svg); let chartSvg = prepareForExport(this.svg);

View File

@ -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.type = 'donut';
this.config.startAngle = args.startAngle || 0; 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() { getRadius() {
super.calc(); return this.height > this.width
let s = this.state; ? this.center.x - this.strokeWidth / 2
this.radius = : this.center.y - this.strokeWidth / 2;
this.height > this.width }
? this.center.x - this.strokeWidth / 2
: this.center.y - this.strokeWidth / 2;
const { radius, clockWise } = this; resetHover(path, color) {
transform(path,'translate3d(0,0,0)');
const prevSlicesProperties = s.slicesProperties || []; this.tip.hideTip();
s.sliceStrings = []; path.style.stroke = color;
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
});
});
this.init = 0;
} }
setupComponents() { setupComponents() {
@ -88,9 +37,9 @@ export default class DonutChart extends AggregationChart {
let componentConfigs = [ let componentConfigs = [
[ [
'donutSlices', this.sliceName,
{ }, {},
function() { 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);
}
} }

View File

@ -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, import {
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils'; 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, import {
HEATMAP_GUTTER_SIZE } from '../utils/constants'; 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) { prepareData(data = this.data) {
if(data.start && data.end && data.start > data.end) { 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)) { if ([1, 3, 5].includes(i)) {
let dayText = makeText('subdomain-name', -COL_WIDTH/2, y, dayName, 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);

View File

@ -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
));
});
}
});
});
}
}

View File

@ -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.paddings.top = 60;
m.baseHeight = (b.height + b.depth * 0.5) * 8; 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 y = pOff.top - gOff.top;
let title = (this.formattedLabels && this.formattedLabels.length>0
? this.formattedLabels[i] : this.state.labels[i]) + ': ';
let fraction = s.sliceTotals[i]/s.grandTotal;
this.tip.setValues(x, y, {name: title, value: (fraction*100).toFixed(1) + "%"}); let x = pOff.left - gOff.left + parseInt(width) / 2;
let y = pOff.top - gOff.top;
let title = (this.formattedLabels && this.formattedLabels.length > 0
? this.formattedLabels[i] : this.state.labels[i]) + ': ';
let fraction = s.sliceTotals[i] / s.grandTotal;
this.tip.setValues(x, y, { name: title, value: (fraction * 100).toFixed(1) + "%" });
this.tip.showTip(); this.tip.showTip();
} }
}); });

View File

@ -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; let curStart, curEnd;
if(this.init) { 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) ? this.shapeFunc(curStart, curEnd, this.center, this.radius, clockWise, largeArc)
: makeArcPathStr(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){ calTranslateByAngle(property) {
const{radius,hoverRadio} = this; const { radius, hoverRadio } = this;
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius); 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){ hoverSlice(path, i, flag, e) {
if(!path) return; 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.resetHover(path, color)
this.tip.hideTip();
path.style.fill = 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; let prevActive = this.curActiveSlice;
if(slices.includes(target)) { 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(){ mouseLeave() {
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false); this.hoverSlice(this.curActiveSlice, this.curActiveSliceIndex, false);
} }
} }

View File

@ -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);

View File

@ -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, import {
animateDot, animatePath, animatePathStr } from '../utils/animate'; 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 = []; this.units = [];
if(!c.hideDots) { 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]));

View File

@ -52,10 +52,10 @@ 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>`;
@ -76,8 +76,8 @@ export default class SvgTip {
let li = $.create('li', { let li = $.create('li', {
innerHTML: `<div class="tooltip-legend" style="background: ${color};"></div> innerHTML: `<div class="tooltip-legend" style="background: ${color};"></div>
<div> <div>
<div class="tooltip-value">${ value === 0 || value ? value : '' }</div> <div class="tooltip-value">${value === 0 || value ? value : ''}</div>
<div class="tooltip-label">${set.title ? set.title : '' }</div> <div class="tooltip-label">${set.title ? set.title : ''}</div>
</div>` </div>`
}); });
@ -90,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;

View File

@ -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];
} }

View File

@ -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);
} }

View File

@ -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 (!d.chartType) {
if(!AXIS_DATASET_CHART_TYPES.includes(type)) type === DEFAULT_AXIS_CHART_TYPE; 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) { export function getShortenedLabels(chartWidth, labels = [], isSeries = true, seriesLabelSpaceRatio) {
let allowedSpace = chartWidth / labels.length; let allowedSpace = (chartWidth / labels.length) * (seriesLabelSpaceRatio || SERIES_LABEL_SPACE_RATIO);
if(allowedSpace <= 0) allowedSpace = 1; 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 (!isSeries) {
if(allowedLetters-3 > 0) { if (allowedLetters - 3 > 0) {
label = label.slice(0, allowedLetters-3) + " ..."; label = label.slice(0, allowedLetters - 3) + " ...";
} else { } else {
label = label.slice(0, allowedLetters) + '..'; label = label.slice(0, allowedLetters) + '..';
} }
} else { } else {
if(i % seriesMultiple !== 0) { if (i % seriesMultiple !== 0) {
label = ""; if (i !== (labels.length - 1)) {
label = "";
}
} else {
if (i > (labels.length - (seriesMultiple / 2))) {
label = "";
}
} }
} }
} }

View File

@ -21,7 +21,7 @@ const PRESET_COLOR_MAP = {
'light-orange': '#FECDB8' '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;
@ -34,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) {

View File

@ -65,16 +65,16 @@ 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.8; export const BAR_CHART_SPACE_RATIO = 0.5;
export const MIN_BAR_PERCENT_HEIGHT = 0.00; 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_HEIGHT = 16;
export const PERCENTAGE_BAR_DEFAULT_DEPTH = 2;
// Fixed 5-color theme, // Fixed 5-color theme,
// More colors are difficult to parse visually // More colors are difficult to parse visually

View File

@ -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, (mm > 9 ? '' : '0') + mm,
(dd>9 ? '' : '0') + dd (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;

View File

@ -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.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ 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);
}); });

View File

@ -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=[]; let points = [];
for(let i=0;i<xList.length;i++){ 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);
} }

View File

@ -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 { getStringWidth, isValidNumber, round } from './helpers';
import { DOT_OVERLAY_SIZE_INCR, PERCENTAGE_BAR_DEFAULT_DEPTH } from './constants'; import { DOT_OVERLAY_SIZE_INCR } from './constants';
import { lightenDarkenColor } from './colors';
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 = '#E2E6E9'; const BASE_LINE_COLOR = '#E2E6E9';
const FONT_FILL = '#313B44';
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 === "className") { i = "class"; }
if(i === "innerHTML") { 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) { if (lighter) {
opacities = [0.4, 0.2, 0]; 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, export function rightRoundedBar(x, width, height) {
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') { // 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, 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
},
}; };
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,39 +222,9 @@ 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;
let args = {
className: 'legend-bar',
x: 0,
y: 0,
width: size,
height: '2px',
fill: fill
};
let text = createSVG('text', {
className: 'legend-dataset-text',
x: 0,
y: 0,
dy: (FONT_SIZE * 2) + '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("rect", args));
group.appendChild(text);
return group;
}
export function legendDot(x, y, size, radius, fill='none', label, value, 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-dot', className: 'legend-dot',
@ -256,32 +240,36 @@ export function legendDot(x, y, size, radius, fill='none', label, value, truncat
className: 'legend-dataset-label', className: 'legend-dataset-label',
x: size, x: size,
y: 0, y: 0,
dx: (FONT_SIZE) + 'px', dx: (font_size) + 'px',
dy: (FONT_SIZE/3) + 'px', dy: (font_size / 3) + 'px',
'font-size': (FONT_SIZE * 1.6) + 'px', 'font-size': (font_size * 1.6) + 'px',
'text-anchor': 'start', 'text-anchor': 'start',
fill: FONT_FILL,
innerHTML: label innerHTML: label
}); });
let textValue = createSVG('text', { let textValue = null;
className: 'legend-dataset-value', if (value) {
x: size, textValue = createSVG('text', {
y: FONT_SIZE + 10, className: 'legend-dataset-value',
dx: (FONT_SIZE) + 'px', x: size,
dy: (FONT_SIZE/3) + 'px', y: FONT_SIZE + 10,
'font-size': (FONT_SIZE * 1.2) + 'px', dx: (FONT_SIZE) + 'px',
'text-anchor': 'start', dy: (FONT_SIZE / 3) + 'px',
fill: FONT_FILL, 'font-size': (FONT_SIZE * 1.2) + 'px',
innerHTML: value '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(textLabel); group.appendChild(textLabel);
group.appendChild(textValue);
if (value && textValue) {
group.appendChild(textValue);
}
return group; return group;
} }
@ -289,7 +277,7 @@ export function legendDot(x, y, size, radius, fill='none', label, value, truncat
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,
@ -303,8 +291,7 @@ export function makeText(className, x, y, content, options = {}) {
}); });
} }
function makeVertLine(x, label, y1, y2, options={}) { function makeVertLine(x, label, y1, y2, options = {}) {
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
let l = createSVG('line', { let l = createSVG('line', {
className: 'line-vertical ' + options.className, className: 'line-vertical ' + options.className,
x1: 0, x1: 0,
@ -326,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);
@ -335,13 +322,18 @@ function makeVertLine(x, label, y1, y2, options={}) {
return line; return line;
} }
function makeHoriLine(y, label, x1, x2, options={}) { function makeHoriLine(y, label, x1, x2, options = {}) {
if(!options.stroke) options.stroke = BASE_LINE_COLOR; if (!options.lineType) options.lineType = '';
if(!options.lineType) options.lineType = ''; if (options.shortenNumbers) {
if (options.shortenNumbers) label = shortenLargeNumber(label); 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,
@ -360,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', {
@ -368,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)";
} }
@ -378,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.pos) options.pos = 'left';
if(!options.offset) options.offset = 0; if (!options.offset) options.offset = 0;
if(!options.mode) options.mode = 'span'; if (!options.mode) options.mode = 'span';
if(!options.stroke) options.stroke = BASE_LINE_COLOR; if (!options.stroke) options.stroke = BASE_LINE_COLOR;
if(!options.className) options.className = ''; 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;
} }
@ -400,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.pos) options.pos = 'bottom';
if(!options.offset) options.offset = 0; if (!options.offset) options.offset = 0;
if(!options.mode) options.mode = 'span'; if (!options.mode) options.mode = 'span';
if(!options.stroke) options.stroke = BASE_LINE_COLOR; if (!options.className) options.className = '';
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)
@ -431,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={}) { export function yMarker(y, label, width, options = {}) {
if(!options.labelPos) options.labelPos = 'right'; 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;
@ -456,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, {
@ -470,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)`, fill: options.fill || `rgba(228, 234, 239, 0.49)`,
stroke: BASE_LINE_COLOR, stroke: options.stroke || BASE_LINE_COLOR,
'stroke-dasharray': `${width}, ${height}` 'stroke-dasharray': `${width}, ${height}`
}, },
// 'data-point-index': index, // 'data-point-index': index,
@ -488,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',
@ -499,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', {
@ -512,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;
} }
@ -527,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}`,
@ -539,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',
@ -565,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,
@ -576,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);
@ -603,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");
@ -611,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})`;
} }
@ -624,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}`;
@ -637,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];
} }
@ -645,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;
@ -653,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];
} }
@ -664,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;
@ -672,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];
} }
@ -683,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;
@ -693,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];
} }
@ -704,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];
} }
@ -722,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];
} }
@ -740,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);
} }
}, },

View File

@ -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);

View File

@ -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;
}

View File

@ -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)) { if (isNaN(x)) {
return {mantissa: -6755399441055744, exponent: 972}; return { mantissa: -6755399441055744, exponent: 972 };
} }
var sig = x > 0 ? 1 : -1; var sig = x > 0 ? 1 : -1;
if(!isFinite(x)) { if (!isFinite(x)) {
return {mantissa: sig * 4503599627370496, exponent: 972}; 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 > 5) {
if(range % 2 !== 0) { 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);
} }

2622
yarn.lock

File diff suppressed because it is too large Load Diff