Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b15424c3a | ||
|
|
6d05a89021 | ||
|
|
f333096012 | ||
|
|
3a16d6c30c | ||
|
|
8f69be05c1 | ||
|
|
b9551c1fa8 | ||
|
|
34436a951d | ||
|
|
4dabb9d577 | ||
|
|
ae2ae01ed7 | ||
|
|
0781eb015d | ||
|
|
1b955a9f19 | ||
|
|
256649fbcc | ||
|
|
9dce5cf5a8 | ||
|
|
2bb46a0c60 | ||
|
|
0a4a3a6530 | ||
|
|
c17e2c6c6f | ||
|
|
652913675c | ||
|
|
6a853ca29d | ||
|
|
10558f8cce | ||
|
|
26708dc26d | ||
|
|
9805e01d34 | ||
|
|
6db78c1c9b | ||
|
|
1ab2ca2bd4 | ||
|
|
e3710f0e16 | ||
|
|
a04dd7f433 | ||
|
|
f2262fac43 | ||
|
|
d4e78ebb05 | ||
|
|
1572ce07fb | ||
|
|
0291b8dbbe | ||
|
|
eecb6fdb3b | ||
|
|
fe7650f7de | ||
|
|
d5c3886301 | ||
|
|
692cdfb102 | ||
|
|
ec8f669385 | ||
|
|
428a447cba | ||
|
|
d139d15cb7 | ||
|
|
7918a8642a | ||
|
|
baff0ec35c | ||
|
|
5945cea9e0 | ||
|
|
6d38028791 | ||
|
|
e7a13d0e6a | ||
|
|
4aef8bf081 | ||
|
|
f2b37dceef | ||
|
|
eb1dc5d501 | ||
|
|
aefc788932 | ||
|
|
ac68baed9a | ||
|
|
030e674a0a | ||
|
|
79a9424be5 | ||
|
|
bf068ec6ea | ||
|
|
62683ea32d | ||
|
|
92c31af629 | ||
|
|
7b5af70d80 | ||
|
|
ddbd30ca07 | ||
|
|
a839277732 | ||
|
|
56d794470b | ||
|
|
fca5173948 | ||
|
|
b87061981c | ||
|
|
7adc904b08 | ||
|
|
d19f03ac99 | ||
|
|
f6e49097a0 | ||
|
|
9f68f1ac5f | ||
|
|
25207622ab | ||
|
|
31c8c7d008 | ||
|
|
ec86f7c39f | ||
|
|
c8727014c6 | ||
|
|
b4f3e77acb | ||
|
|
5034c7a954 | ||
|
|
a5a5fc051b | ||
|
|
539bc50883 | ||
|
|
10de973608 | ||
|
|
5a4857d6a8 | ||
|
|
3e63b1a2b4 | ||
|
|
67030024d4 | ||
|
|
17aec6de95 | ||
|
|
12533f3b37 | ||
|
|
10b1010d06 | ||
|
|
2f459bc345 | ||
|
|
d89bdb7571 | ||
|
|
0832c02184 | ||
|
|
c3ee7f306c | ||
|
|
51a07a12c6 | ||
|
|
8e1bd6859a | ||
|
|
d075c76ee2 | ||
|
|
3431257129 | ||
|
|
9fae7c62ad | ||
|
|
40ed23b102 | ||
|
|
ce477800db | ||
|
|
75038eeefb | ||
|
|
dadecb296e | ||
|
|
8bd53191b0 | ||
|
|
d073e14467 | ||
|
|
85d7e609c8 | ||
|
|
7f26c18ce7 | ||
|
|
4a9048f59f | ||
|
|
684ecdedc2 | ||
|
|
d357370b6a | ||
|
|
d034192373 | ||
|
|
a22db61492 |
13
.babelrc
Normal file
13
.babelrc
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"@babel/preset-env",
|
||||||
|
{
|
||||||
|
"targets": {
|
||||||
|
"browsers": ["last 2 versions", "safari >= 7"]
|
||||||
|
},
|
||||||
|
"modules": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,33 +1,24 @@
|
|||||||
{
|
{
|
||||||
"env": {
|
"env": {
|
||||||
"browser": true,
|
"browser": true,
|
||||||
"es6": true
|
"es6": true
|
||||||
},
|
},
|
||||||
"extends": "eslint:recommended",
|
"extends": "eslint:recommended",
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": [
|
"indent": ["error", "tab"],
|
||||||
"error",
|
"linebreak-style": ["error", "unix"],
|
||||||
"tab"
|
"semi": ["error", "always"],
|
||||||
],
|
"no-console": [
|
||||||
"linebreak-style": [
|
"error",
|
||||||
"error",
|
{
|
||||||
"unix"
|
"allow": ["warn", "error"]
|
||||||
],
|
}
|
||||||
"semi": [
|
]
|
||||||
"error",
|
},
|
||||||
"always"
|
"globals": {
|
||||||
],
|
"ENV": true
|
||||||
"no-console": [
|
}
|
||||||
"error",
|
}
|
||||||
{
|
|
||||||
"allow": ["warn", "error"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"globals": {
|
|
||||||
"ENV": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
33
.github/workflows/npm-publish.yml
vendored
Normal file
33
.github/workflows/npm-publish.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
||||||
|
|
||||||
|
name: Node.js Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm build
|
||||||
|
|
||||||
|
publish-npm:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12
|
||||||
|
registry-url: https://registry.npmjs.org/
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{secrets.NPM_PUBLISH_TOKEN}}
|
||||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -60,4 +60,9 @@ typings/
|
|||||||
# next.js build output
|
# next.js build output
|
||||||
.next
|
.next
|
||||||
|
|
||||||
.DS_Store
|
# npm build output
|
||||||
|
dist
|
||||||
|
docs
|
||||||
|
docs/assets/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@ -11,4 +11,4 @@ script:
|
|||||||
- make test
|
- make test
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
- make coveralls
|
- make coveralls
|
||||||
|
|||||||
174
README.md
174
README.md
@ -1,69 +1,62 @@
|
|||||||
<div align="center">
|
<div align="center" markdown="1">
|
||||||
<img src="https://github.com/frappe/design/blob/master/logos/logo-2019/frappe-charts-logo.png" height="128">
|
|
||||||
<a href="https://frappe.github.io/charts">
|
<img width="80" alt="charts-logo" src="https://github.com/user-attachments/assets/37b7ffaf-8354-48f2-8b9c-fa04fae0135b" />
|
||||||
<h2>Frappe Charts</h2>
|
|
||||||
|
# Frappe Charts
|
||||||
|
**GitHub-inspired modern, intuitive and responsive charts with zero dependencies**
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://bundlephobia.com/result?p=frappe-charts">
|
||||||
|
<img src="https://img.shields.io/bundlephobia/minzip/frappe-charts">
|
||||||
</a>
|
</a>
|
||||||
<p align="center">
|
</p>
|
||||||
<p>GitHub-inspired modern, intuitive and responsive charts with zero dependencies</p>
|
|
||||||
<a href="https://frappe.github.io/charts">
|
<img src=".github/example.gif">
|
||||||
<b>Explore Demos » </b>
|
|
||||||
</a>
|
<div>
|
||||||
<a href="https://codepen.io/pratu16x7/pen/wjKBoq">
|
|
||||||
<b> Edit at CodePen »</b>
|
[Explore Demos](https://frappe.io/charts) - [Edit at CodeSandbox](https://codesandbox.io/s/frappe-charts-demo-viqud) - [Documentation](https://frappe.io/charts/docs)
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
</div>
|
||||||
<a href="https://travis-ci.org/frappe/charts">
|
|
||||||
<img src="https://img.shields.io/travis/frappe/charts.svg?style=flat-square">
|
|
||||||
</a>
|
|
||||||
<a href="http://github.com/frappe/charts/tree/master/dist/js/frappe-charts.min.iife.js">
|
|
||||||
<img src="http://img.badgesize.io/frappe/charts/master/dist/frappe-charts.min.iife.js.svg?compression=gzip">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
## Frappe Charts
|
||||||
<a href="https://frappe.github.io/charts">
|
Frappe Charts is a simple charting library with a focus on a simple API. The design is inspired by various charts you see on GitHub.
|
||||||
<img src=".github/example.gif">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
### Contents
|
### Motivation
|
||||||
* [Installation](#installation)
|
|
||||||
* [Usage](#usage)
|
|
||||||
* [Contribute](https://frappe.io/charts/docs/contributing)
|
|
||||||
* [Updates](#updates)
|
|
||||||
* [License](#license)
|
|
||||||
|
|
||||||
#### Installation
|
ERPNext needed a simple sales history graph for its user company master to help users track sales. While using c3.js for reports, the library didn’t align well with our product’s classic design. Existing JS libraries were either too complex or rigid in their structure and behavior. To address this, I decided to create a library for translating value pairs into relative shapes or positions, focusing on simplicity.
|
||||||
* Install via [`npm`](https://www.npmjs.com/get-npm):
|
|
||||||
|
|
||||||
```sh
|
### Key Features
|
||||||
$ npm install frappe-charts
|
|
||||||
```
|
|
||||||
|
|
||||||
and include in your project:
|
- **Variety of chart types**: Frappe Charts supports various chart types, including Axis Charts, Area and Trends, Bar, Line, Pie, Percentage, Mixed Axis, and Heatmap.
|
||||||
```js
|
- **Annotations and tooltips**: Charts can be annotated with x and y markers, regions, and tooltips for enhanced data context and clarity.
|
||||||
import { Chart } from "frappe-charts"
|
- **Dynamic data handling**: Add, remove, or update individual data points in place, or refresh the entire dataset to reflect changes.
|
||||||
```
|
- **Customizable configurations**: Flexible options like colors, animations, and custom titles allow for a highly personalized chart experience.
|
||||||
|
|
||||||
...or include following for es-modules(eg:vuejs):
|
## Usage
|
||||||
```js
|
|
||||||
import { Chart } from 'frappe-charts/dist/frappe-charts.esm.js'
|
|
||||||
// import css
|
|
||||||
import 'frappe-charts/dist/frappe-charts.min.css'
|
|
||||||
```
|
|
||||||
|
|
||||||
* ...or include within your HTML
|
```sh
|
||||||
|
npm install frappe-charts
|
||||||
|
```
|
||||||
|
|
||||||
|
Import in your project:
|
||||||
|
```js
|
||||||
|
import { Chart } from 'frappe-charts'
|
||||||
|
// or esm import
|
||||||
|
import { Chart } from 'frappe-charts/dist/frappe-charts.esm.js'
|
||||||
|
// import css
|
||||||
|
import 'frappe-charts/dist/frappe-charts.min.css'
|
||||||
|
```
|
||||||
|
|
||||||
|
Or directly include script in your HTML
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://unpkg.com/frappe-charts@1.6.1/dist/frappe-charts.min.umd.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/frappe-charts@1.1.0/dist/frappe-charts.min.iife.js"></script>
|
|
||||||
<!-- or -->
|
|
||||||
<script src="https://unpkg.com/frappe-charts@1.1.0/dist/frappe-charts.min.iife.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Usage
|
|
||||||
```js
|
```js
|
||||||
const data = {
|
const data = {
|
||||||
labels: ["12am-3am", "3am-6pm", "6am-9am", "9am-12am",
|
labels: ["12am-3am", "3am-6pm", "6am-9am", "9am-12am",
|
||||||
@ -71,11 +64,11 @@ const data = {
|
|||||||
],
|
],
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
name: "Some Data", type: "bar",
|
name: "Some Data", chartType: "bar",
|
||||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Another Set", type: "line",
|
name: "Another Set", chartType: "line",
|
||||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -91,65 +84,26 @@ const chart = new frappe.Chart("#chart", { // or a DOM element,
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
...or for es-modules (replace `new frappe.Chart()` with `new Chart()`):
|
## Contributing
|
||||||
```diff
|
|
||||||
- const chart = new frappe.Chart("#chart", {
|
|
||||||
+ const chart = new Chart("#chart", { // or a DOM element,
|
|
||||||
// new Chart() in case of ES6 module with above usage
|
|
||||||
title: "My Awesome Chart",
|
|
||||||
data: data,
|
|
||||||
type: 'axis-mixed', // or 'bar', 'line', 'scatter', 'pie', 'percentage'
|
|
||||||
height: 250,
|
|
||||||
colors: ['#7cd6fd', '#743ee2']
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
If you want to contribute:
|
|
||||||
|
|
||||||
1. Clone this repo.
|
1. Clone this repo.
|
||||||
2. `cd` into project directory
|
2. `cd` into project directory
|
||||||
3. `npm install`
|
3. `npm install`
|
||||||
4. `npm run dev`
|
4. `npm i npm-run-all -D` (*optional --> might be required for some developers*)
|
||||||
|
5. `npm run dev`
|
||||||
|
|
||||||
#### Updates
|
## Links
|
||||||
|
|
||||||
##### v1.0.0
|
- [Read the blog](https://medium.com/@pratu16x7/so-we-decided-to-create-our-own-charts-a95cb5032c97)
|
||||||
- Major rewrite out. Some new features include:
|
|
||||||
- Mixed type axis datasets
|
|
||||||
- Stacked bar charts
|
|
||||||
- Value over data points
|
|
||||||
- Y Markers and regions
|
|
||||||
- Dot size, Bar space size, and other options
|
|
||||||
- Legend for axis charts
|
|
||||||
- We would be looking to incorporate existing PRs and issues in the meantime.
|
|
||||||
|
|
||||||
##### Please read [#93](https://github.com/frappe/charts/issues/93) for v0.1.0 updates on rework and development.
|
|
||||||
|
|
||||||
##### v0.0.7
|
|
||||||
- [Custom color values](https://github.com/frappe/charts/pull/71) for charts as hex codes. The API now takes an array of colors for all charts instead of a color for each dataset.
|
|
||||||
- [@iamkdev's](https://github.com/iamkdev) blog on [usage with Angular](https://medium.com/@iamkdev/frappé-charts-with-angular-c9c5dd075d9f).
|
|
||||||
|
|
||||||
##### v0.0.5
|
|
||||||
- More [flexible Y values](https://github.com/frappe/charts/commit/3de049c451194dcd8e61ff91ceeb998ce131c709): independent from exponent, minimum Y axis point for line graphs.
|
|
||||||
- Customisable [Heatmap colors](https://github.com/frappe/charts/pull/53); check out the Halloween demo on the [website](https://frappe.github.io/charts) :D
|
|
||||||
- Tooltip values can be [formatted](https://github.com/frappe/charts/commit/e3d9ed0eae14b65044dca0542cdd4d12af3f2b44).
|
|
||||||
|
|
||||||
##### v0.0.4
|
|
||||||
- Build update: [Shipped](https://github.com/frappe/charts/pull/35) an ES6 module, along with the browser friendly IIFE.
|
|
||||||
|
|
||||||
##### v0.0.2
|
|
||||||
- We have an animated [Pie Chart](https://github.com/frappe/charts/issues/29)! Thanks [@sheweichun](https://github.com/sheweichun).
|
|
||||||
- [@tobiaslins](https://github.com/tobiaslins) contributed tweaks for his quest to make these easy to use with React. Check out his [repo](https://github.com/tobiaslins/frappe-charts-react-example) and updates at [#24](https://github.com/frappe/charts/issues/24) to learn more :)
|
|
||||||
- A new logo.
|
|
||||||
|
|
||||||
##### v0.0.1
|
|
||||||
- The very first version out, with animatable bars and lines, a percentage chart and a heatmap. GitHub-style.
|
|
||||||
|
|
||||||
#### License
|
|
||||||
This repository has been released under the [MIT License](LICENSE)
|
|
||||||
|
|
||||||
------------------
|
|
||||||
Project maintained by [Frappe](https://frappe.io).
|
|
||||||
Used in [ERPNext](https://erpnext.com). Read the [blog post](https://medium.com/@pratu16x7/so-we-decided-to-create-our-own-charts-a95cb5032c97).
|
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://frappe.io" target="_blank">
|
||||||
|
<picture>
|
||||||
|
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/Frappe-white.png">
|
||||||
|
<img src="https://frappe.io/files/Frappe-black.png" alt="Frappe Technologies" height="28"/>
|
||||||
|
</picture>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|||||||
4459
dist/frappe-charts.cjs.js
vendored
4459
dist/frappe-charts.cjs.js
vendored
File diff suppressed because it is too large
Load Diff
4467
dist/frappe-charts.esm.js
vendored
4467
dist/frappe-charts.esm.js
vendored
File diff suppressed because it is too large
Load Diff
104
dist/frappe-charts.min.css
vendored
104
dist/frappe-charts.min.css
vendored
@ -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; }
|
|
||||||
1
dist/frappe-charts.umd.js
vendored
1
dist/frappe-charts.umd.js
vendored
File diff suppressed because one or more lines are too long
@ -275,4 +275,4 @@ export const moonData = {
|
|||||||
masses: [14819000, 10759000, 8931900, 4800000],
|
masses: [14819000, 10759000, 8931900, 4800000],
|
||||||
distances: [1070.412, 1882.709, 421.7, 671.034],
|
distances: [1070.412, 1882.709, 421.7, 671.034],
|
||||||
diameters: [5262.4, 4820.6, 3637.4, 3121.6],
|
diameters: [5262.4, 4820.6, 3637.4, 3121.6],
|
||||||
};
|
};
|
||||||
|
|||||||
2
docs/assets/js/frappe-charts.min.js
vendored
2
docs/assets/js/frappe-charts.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
87
docs/assets/js/index.min.js
vendored
87
docs/assets/js/index.min.js
vendored
@ -1,36 +1,11 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'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_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'];
|
||||||
var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Universal constants
|
// Universal constants
|
||||||
|
var ANGLE_RATIO = Math.PI / 180;
|
||||||
/**
|
|
||||||
* 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
|
* Shuffles array in place. ES6 version
|
||||||
@ -51,24 +26,6 @@ function shuffle(array) {
|
|||||||
return array;
|
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
|
// https://stackoverflow.com/a/29325222
|
||||||
function getRandomBias(min, max, bias, influence) {
|
function getRandomBias(min, max, bias, influence) {
|
||||||
var range = max - min;
|
var range = max - min;
|
||||||
@ -79,36 +36,11 @@ function getRandomBias(min, max, bias, influence) {
|
|||||||
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
|
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
|
// Playing around with dates
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var NO_OF_MILLIS = 1000;
|
var NO_OF_MILLIS = 1000;
|
||||||
var SEC_IN_DAY = 86400;
|
var SEC_IN_DAY = 86400;
|
||||||
|
|
||||||
|
|
||||||
var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function clone(date) {
|
function clone(date) {
|
||||||
return new Date(date.getTime());
|
return new Date(date.getTime());
|
||||||
}
|
}
|
||||||
@ -127,21 +59,6 @@ function timestampToMidnight(timestamp) {
|
|||||||
return midnightTs;
|
return midnightTs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function getMonthsBetween(startDate, endDate) {}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// mutates
|
|
||||||
|
|
||||||
|
|
||||||
// mutates
|
// mutates
|
||||||
function addDays(date, numberOfDays) {
|
function addDays(date, numberOfDays) {
|
||||||
date.setDate(date.getDate() + numberOfDays);
|
date.setDate(date.getDate() + numberOfDays);
|
||||||
@ -294,8 +211,6 @@ var demoConfig = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* eslint-disable no-unused-vars */
|
|
||||||
/* eslint-enable no-unused-vars */
|
|
||||||
// import { lineComposite, barComposite } from './demoConfig';
|
// import { lineComposite, barComposite } from './demoConfig';
|
||||||
// ================================================================================
|
// ================================================================================
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
26921
package-lock.json
generated
Normal file
26921
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
97
package.json
97
package.json
@ -1,48 +1,51 @@
|
|||||||
{
|
{
|
||||||
"name": "frappe-charts",
|
"name": "frappe-charts",
|
||||||
"version": "1.5.2",
|
"version": "v1.6.3",
|
||||||
"main": "dist/frappe-charts.cjs.js",
|
"type": "module",
|
||||||
"common": "dist/frappe-charts.cjs.js",
|
"main": "dist/frappe-charts.esm.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",
|
||||||
"description": "https://frappe.github.io/charts",
|
"unnpkg": "dist/frappe-charts.umd.js",
|
||||||
"directories": {
|
"description": "https://frappe.github.io/charts",
|
||||||
"doc": "docs"
|
"directories": {
|
||||||
},
|
"doc": "docs"
|
||||||
"files": [
|
},
|
||||||
"src",
|
"files": [
|
||||||
"dist"
|
"src",
|
||||||
],
|
"dist"
|
||||||
"scripts": {
|
],
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"scripts": {
|
||||||
"watch": "rollup -c --watch",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"dev": "npm-run-all --parallel watch",
|
"watch": "rollup -c --watch",
|
||||||
"build": "rollup -c"
|
"dev": "npm-run-all --parallel watch",
|
||||||
},
|
"build": "rollup -c"
|
||||||
"repository": {
|
},
|
||||||
"type": "git",
|
"repository": {
|
||||||
"url": "git+https://github.com/frappe/charts.git"
|
"type": "git",
|
||||||
},
|
"url": "git+https://github.com/frappe/charts.git"
|
||||||
"keywords": [
|
},
|
||||||
"js",
|
"keywords": [
|
||||||
"charts"
|
"js",
|
||||||
],
|
"charts"
|
||||||
"author": "Prateeksha Singh",
|
],
|
||||||
"license": "MIT",
|
"author": "Prateeksha Singh",
|
||||||
"bugs": {
|
"license": "MIT",
|
||||||
"url": "https://github.com/frappe/charts/issues"
|
"bugs": {
|
||||||
},
|
"url": "https://github.com/frappe/charts/issues"
|
||||||
"homepage": "https://github.com/frappe/charts#readme",
|
},
|
||||||
"devDependencies": {
|
"homepage": "https://github.com/frappe/charts#readme",
|
||||||
"@babel/core": "^7.10.5",
|
"devDependencies": {
|
||||||
"@babel/preset-env": "^7.10.4",
|
"@babel/core": "^7.10.5",
|
||||||
"rollup": "^2.21.0",
|
"@babel/preset-env": "^7.10.4",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
"node-sass": "^8.0.0",
|
||||||
"rollup-plugin-commonjs": "^10.1.0",
|
"rollup": "^2.21.0",
|
||||||
"rollup-plugin-eslint": "^7.0.0",
|
"rollup-plugin-babel": "^4.4.0",
|
||||||
"rollup-plugin-postcss": "^3.1.3",
|
"rollup-plugin-bundle-size": "^1.0.3",
|
||||||
"rollup-plugin-scss": "^2.5.0",
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
"rollup-plugin-terser": "^6.1.0"
|
"rollup-plugin-eslint": "^7.0.0",
|
||||||
}
|
"rollup-plugin-postcss": "^3.1.3",
|
||||||
}
|
"rollup-plugin-scss": "^2.5.0",
|
||||||
|
"rollup-plugin-terser": "^6.1.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,43 +1,47 @@
|
|||||||
import pkg from './package.json';
|
import pkg from "./package.json";
|
||||||
|
|
||||||
import commonjs from 'rollup-plugin-commonjs';
|
|
||||||
import babel from 'rollup-plugin-babel';
|
|
||||||
import postcss from 'rollup-plugin-postcss';
|
|
||||||
import scss from 'rollup-plugin-scss';
|
|
||||||
import { terser } from 'rollup-plugin-terser';
|
|
||||||
|
|
||||||
|
import commonjs from "rollup-plugin-commonjs";
|
||||||
|
import babel from "rollup-plugin-babel";
|
||||||
|
import postcss from "rollup-plugin-postcss";
|
||||||
|
import scss from "rollup-plugin-scss";
|
||||||
|
import bundleSize from "rollup-plugin-bundle-size";
|
||||||
|
import { terser } from "rollup-plugin-terser";
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
// browser-friendly UMD build
|
// browser-friendly UMD build
|
||||||
{
|
{
|
||||||
input: 'src/js/index.js',
|
input: "src/js/index.js",
|
||||||
output: {
|
output: {
|
||||||
name: 'frappe-charts',
|
sourcemap: true,
|
||||||
file: pkg.browser,
|
name: "frappe",
|
||||||
format: 'umd'
|
file: pkg.browser,
|
||||||
},
|
format: "umd",
|
||||||
plugins: [
|
},
|
||||||
commonjs(),
|
plugins: [
|
||||||
babel({
|
commonjs(),
|
||||||
exclude: ['node_modules/**']
|
babel({
|
||||||
}),
|
exclude: ["node_modules/**"],
|
||||||
terser(),
|
}),
|
||||||
scss({ output: 'dist/frappe-charts.min.css' })
|
terser(),
|
||||||
]
|
scss({ output: "dist/frappe-charts.min.css" }),
|
||||||
},
|
bundleSize(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
// CommonJS (for Node) and ES module (for bundlers) build.
|
// CommonJS (for Node) and ES module (for bundlers) build.
|
||||||
{
|
{
|
||||||
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(),
|
||||||
];
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
10
src/.babelrc
10
src/.babelrc
@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": [
|
|
||||||
["@babel/preset-env", {
|
|
||||||
"targets": {
|
|
||||||
"browsers": ["last 2 versions", "safari >= 7"]
|
|
||||||
},
|
|
||||||
"modules": false
|
|
||||||
}]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -1,179 +1,192 @@
|
|||||||
: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", "Oxygen",
|
||||||
'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
|
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||||
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
line.dashed {
|
line.dashed {
|
||||||
stroke-dasharray: 5, 3;
|
stroke-dasharray: 5, 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.axis-line {
|
.axis-line {
|
||||||
.specific-value {
|
.specific-value {
|
||||||
text-anchor: start;
|
text-anchor: start;
|
||||||
}
|
}
|
||||||
.y-line {
|
|
||||||
text-anchor: end;
|
|
||||||
}
|
|
||||||
.x-line {
|
|
||||||
text-anchor: middle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-dataset-label {
|
.y-line {
|
||||||
fill: var(--fr-tooltip-label);
|
text-anchor: end;
|
||||||
font-weight: 600;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.legend-dataset-value {
|
.x-line {
|
||||||
fill: var(--fr-tooltip-value);
|
text-anchor: middle;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dataset-label {
|
||||||
|
fill: var(--charts-legend-label);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dataset-value {
|
||||||
|
fill: var(--charts-legend-value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.graph-svg-tip {
|
.graph-svg-tip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 99999;
|
z-index: 99999;
|
||||||
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),
|
||||||
border-radius: 6px;
|
0px 2px 6px rgba(17, 43, 66, 0.08),
|
||||||
|
0px 40px 30px -30px rgba(17, 43, 66, 0.1);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.data-point-list {
|
ul.data-point-list {
|
||||||
li {
|
li {
|
||||||
min-width: 90px;
|
min-width: 90px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-pointer {
|
.svg-pointer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.comparison {
|
&.comparison {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
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;
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
strong {
|
||||||
margin: 0;
|
color: var(--charts-tooltip-value);
|
||||||
white-space: nowrap;
|
}
|
||||||
list-style: none;
|
}
|
||||||
|
|
||||||
&.tooltip-grid {
|
ul {
|
||||||
display: grid;
|
margin: 0;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
white-space: nowrap;
|
||||||
gap: 5px;
|
list-style: none;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
&.tooltip-grid {
|
||||||
display: inline-block;
|
display: grid;
|
||||||
display: flex;
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
flex-direction: row;
|
gap: 5px;
|
||||||
font-weight: 600;
|
}
|
||||||
line-height: 1;
|
}
|
||||||
|
|
||||||
padding: 5px 15px 15px 15px;
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
|
||||||
.tooltip-legend {
|
padding: 5px 15px 15px 15px;
|
||||||
height: 12px;
|
|
||||||
width: 12px;
|
|
||||||
margin-right: 8px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-label {
|
.tooltip-legend {
|
||||||
margin-top: 4px;
|
height: 12px;
|
||||||
font-size: 11px;
|
width: 12px;
|
||||||
max-width: 100px;
|
margin-right: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
color: var(--fr-tooltip-label);
|
.tooltip-label {
|
||||||
overflow: hidden;
|
margin-top: 4px;
|
||||||
text-overflow: ellipsis;
|
font-size: 11px;
|
||||||
white-space: nowrap;
|
max-width: 100px;
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-value {
|
color: var(--fr-tooltip-label);
|
||||||
color: var(--fr-tooltip-value);
|
overflow: hidden;
|
||||||
}
|
text-overflow: ellipsis;
|
||||||
}
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.tooltip-value {
|
||||||
|
color: var(--charts-tooltip-value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
export const CSSTEXT = ".chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}";
|
export const CSSTEXT =
|
||||||
|
".chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}";
|
||||||
|
|||||||
@ -1,40 +1,38 @@
|
|||||||
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';
|
import AxisChart from "./charts/AxisChart";
|
||||||
import AxisChart from './charts/AxisChart';
|
import DonutChart from "./charts/DonutChart";
|
||||||
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,
|
donut: DonutChart,
|
||||||
donut: DonutChart,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getChartByType(chartType = 'line', parent, options) {
|
function getChartByType(chartType = "line", parent, options) {
|
||||||
if (chartType === 'axis-mixed') {
|
if (chartType === "axis-mixed") {
|
||||||
options.type = 'line';
|
options.type = "line";
|
||||||
return new AxisChart(parent, options);
|
return new AxisChart(parent, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!chartTypes[chartType]) {
|
if (!chartTypes[chartType]) {
|
||||||
console.error("Undefined chart type: " + chartType);
|
console.error("Undefined chart type: " + chartType);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new chartTypes[chartType](parent, options);
|
return new chartTypes[chartType](parent, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
class Chart {
|
class Chart {
|
||||||
constructor(parent, options) {
|
constructor(parent, options) {
|
||||||
return getChartByType(options.type, parent, options);
|
return getChartByType(options.type, parent, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Chart, PercentageChart, PieChart, Heatmap, AxisChart };
|
export { Chart, PercentageChart, PieChart, Heatmap, AxisChart };
|
||||||
|
|||||||
@ -1,97 +1,94 @@
|
|||||||
import BaseChart from './BaseChart';
|
import BaseChart from "./BaseChart";
|
||||||
import { truncateString } from '../utils/draw-utils';
|
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';
|
import { getExtraWidth } from "../utils/constants";
|
||||||
|
|
||||||
export default class AggregationChart extends BaseChart {
|
export default class AggregationChart extends BaseChart {
|
||||||
constructor(parent, args) {
|
constructor(parent, args) {
|
||||||
super(parent, args);
|
super(parent, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
configure(args) {
|
configure(args) {
|
||||||
super.configure(args);
|
super.configure(args);
|
||||||
|
|
||||||
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() {
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
let maxSlices = this.config.maxSlices;
|
let maxSlices = this.config.maxSlices;
|
||||||
s.sliceTotals = [];
|
s.sliceTotals = [];
|
||||||
|
|
||||||
let allTotals = this.data.labels.map((label, i) => {
|
let allTotals = this.data.labels
|
||||||
let total = 0;
|
.map((label, i) => {
|
||||||
this.data.datasets.map(e => {
|
let total = 0;
|
||||||
total += e.values[i];
|
this.data.datasets.map((e) => {
|
||||||
});
|
total += e.values[i];
|
||||||
return [total, label];
|
});
|
||||||
}).filter(d => { return d[0] >= 0; }); // keep only positive results
|
return [total, label];
|
||||||
|
})
|
||||||
|
.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) => {
|
||||||
totals.push([sumOfRemaining, 'Rest']);
|
sumOfRemaining += d[0];
|
||||||
this.colors[maxSlices-1] = 'grey';
|
});
|
||||||
}
|
totals.push([sumOfRemaining, "Rest"]);
|
||||||
|
this.colors[maxSlices - 1] = "grey";
|
||||||
|
}
|
||||||
|
|
||||||
s.labels = [];
|
s.labels = [];
|
||||||
totals.map(d => {
|
totals.map((d) => {
|
||||||
s.sliceTotals.push(round(d[0]));
|
s.sliceTotals.push(round(d[0]));
|
||||||
s.labels.push(d[1]);
|
s.labels.push(d[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0);
|
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
this.center = {
|
this.center = {
|
||||||
x: this.width / 2,
|
x: this.width / 2,
|
||||||
y: this.height / 2
|
y: this.height / 2,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLegend() {
|
renderLegend() {
|
||||||
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.legendTotals.map((d, i) => {
|
? this.config.formatTooltipY(data)
|
||||||
let barWidth = 150;
|
: data;
|
||||||
let divisor = Math.floor(
|
|
||||||
(this.width - getExtraWidth(this.measures))/barWidth
|
return legendDot(
|
||||||
);
|
x_pos,
|
||||||
if (this.legendTotals.length < divisor) {
|
y_pos,
|
||||||
barWidth = this.width/this.legendTotals.length;
|
12, // size
|
||||||
}
|
3, // dot radius
|
||||||
if(count > divisor) {
|
this.colors[index], // fill
|
||||||
count = 0;
|
this.state.labels[index], // label
|
||||||
y += 60;
|
formatted, // value
|
||||||
}
|
null, // base_font_size
|
||||||
let x = barWidth * count + 5;
|
this.config.truncateLegends // truncate_legends
|
||||||
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++;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,29 @@
|
|||||||
import BaseChart from './BaseChart';
|
import BaseChart from "./BaseChart";
|
||||||
import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils';
|
import {
|
||||||
import { AXIS_LEGEND_BAR_SIZE } from '../utils/constants';
|
dataPrep,
|
||||||
import { getComponent } from '../objects/ChartComponents';
|
zeroDataPrep,
|
||||||
import { getOffset, fire } from '../utils/dom';
|
getShortenedLabels,
|
||||||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals';
|
} from "../utils/axis-chart-utils";
|
||||||
import { floatTwo } from '../utils/helpers';
|
import { getComponent } from "../objects/ChartComponents";
|
||||||
import { makeOverlay, updateOverlay, legendBar } from '../utils/draw';
|
import { getOffset, fire } from "../utils/dom";
|
||||||
import { getTopOffset, getLeftOffset, MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO,
|
import {
|
||||||
LINE_CHART_DOT_SIZE } from '../utils/constants';
|
calcChartIntervals,
|
||||||
|
getIntervalSize,
|
||||||
|
getValueRange,
|
||||||
|
getZeroIndex,
|
||||||
|
scale,
|
||||||
|
getClosestInArray,
|
||||||
|
} from "../utils/intervals";
|
||||||
|
import { floatTwo } from "../utils/helpers";
|
||||||
|
import { makeOverlay, updateOverlay, legendDot } from "../utils/draw";
|
||||||
|
import {
|
||||||
|
getTopOffset,
|
||||||
|
getLeftOffset,
|
||||||
|
MIN_BAR_PERCENT_HEIGHT,
|
||||||
|
BAR_CHART_SPACE_RATIO,
|
||||||
|
LINE_CHART_DOT_SIZE,
|
||||||
|
LEGEND_ITEM_WIDTH,
|
||||||
|
} from "../utils/constants";
|
||||||
|
|
||||||
export default class AxisChart extends BaseChart {
|
export default class AxisChart extends BaseChart {
|
||||||
constructor(parent, args) {
|
constructor(parent, args) {
|
||||||
@ -16,14 +32,14 @@ export default class AxisChart extends BaseChart {
|
|||||||
this.barOptions = args.barOptions || {};
|
this.barOptions = args.barOptions || {};
|
||||||
this.lineOptions = args.lineOptions || {};
|
this.lineOptions = args.lineOptions || {};
|
||||||
|
|
||||||
this.type = args.type || 'line';
|
this.type = args.type || "line";
|
||||||
this.init = 1;
|
this.init = 1;
|
||||||
|
|
||||||
this.setup();
|
this.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -31,33 +47,63 @@ export default class AxisChart extends BaseChart {
|
|||||||
|
|
||||||
configure(options) {
|
configure(options) {
|
||||||
super.configure(options);
|
super.configure(options);
|
||||||
|
const { axisOptions = {} } = options;
|
||||||
|
const { xAxis, yAxis } = axisOptions || {};
|
||||||
|
|
||||||
options.axisOptions = options.axisOptions || {};
|
|
||||||
options.tooltipOptions = options.tooltipOptions || {};
|
options.tooltipOptions = options.tooltipOptions || {};
|
||||||
|
|
||||||
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span';
|
this.config.xAxisMode = xAxis
|
||||||
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span';
|
? xAxis.xAxisMode
|
||||||
this.config.xIsSeries = options.axisOptions.xIsSeries || 0;
|
: axisOptions.xAxisMode || "span";
|
||||||
this.config.shortenYAxisNumbers = options.axisOptions.shortenYAxisNumbers || 0;
|
|
||||||
|
// this will pass an array
|
||||||
|
// lets determine if we need two yAxis based on if there is length
|
||||||
|
// to the yAxis array
|
||||||
|
if (yAxis && yAxis.length) {
|
||||||
|
this.config.yAxisConfig = yAxis.map((item) => {
|
||||||
|
return {
|
||||||
|
yAxisMode: item.yAxisMode,
|
||||||
|
id: item.id,
|
||||||
|
position: item.position,
|
||||||
|
title: item.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.config.yAxisMode = yAxis
|
||||||
|
? yAxis.yAxisMode
|
||||||
|
: axisOptions.yAxisMode || "span";
|
||||||
|
|
||||||
|
// if we have yAxis config settings lets populate a yAxis config array.
|
||||||
|
if (yAxis && yAxis.id && yAxis.position) {
|
||||||
|
this.config.yAxisConfig = [yAxis];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config.xIsSeries = axisOptions.xIsSeries || 0;
|
||||||
|
this.config.shortenYAxisNumbers = axisOptions.shortenYAxisNumbers || 0;
|
||||||
|
|
||||||
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, config = this.config) {
|
||||||
return dataPrep(data, this.type);
|
return dataPrep(data, this.type, config.continuous);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +113,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);
|
||||||
@ -79,22 +125,121 @@ export default class AxisChart extends BaseChart {
|
|||||||
labels: labels,
|
labels: labels,
|
||||||
positions: labels.map((d, i) =>
|
positions: labels.map((d, i) =>
|
||||||
floatTwo(s.xOffset + i * s.unitWidth)
|
floatTwo(s.xOffset + i * s.unitWidth)
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
calcYAxisParameters(dataValues, withMinimum = 'false') {
|
calcYAxisParameters(dataValues, withMinimum = "false") {
|
||||||
const yPts = calcChartIntervals(dataValues, withMinimum);
|
let yPts,
|
||||||
const scaleMultiplier = this.height / getValueRange(yPts);
|
scaleMultiplier,
|
||||||
const intervalHeight = getIntervalSize(yPts) * scaleMultiplier;
|
intervalHeight,
|
||||||
const zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight);
|
zeroLine,
|
||||||
|
positions,
|
||||||
|
yAxisConfigObject,
|
||||||
|
yAxisAlignment,
|
||||||
|
yKeys;
|
||||||
|
|
||||||
this.state.yAxis = {
|
yKeys = [];
|
||||||
labels: yPts,
|
yAxisConfigObject = this.config.yAxisMode || {};
|
||||||
positions: yPts.map(d => zeroLine - d * scaleMultiplier),
|
yAxisAlignment = yAxisConfigObject.position
|
||||||
scaleMultiplier: scaleMultiplier,
|
? yAxisConfigObject.position
|
||||||
zeroLine: zeroLine,
|
: "left";
|
||||||
};
|
|
||||||
|
// if we have an object we have multiple yAxisParameters.
|
||||||
|
if (dataValues instanceof Array) {
|
||||||
|
yPts = calcChartIntervals(dataValues, withMinimum, this.config.overrideCeiling, this.config.overrideFloor);
|
||||||
|
scaleMultiplier = this.height / getValueRange(yPts);
|
||||||
|
intervalHeight = getIntervalSize(yPts) * scaleMultiplier;
|
||||||
|
zeroLine = this.height - getZeroIndex(yPts) * intervalHeight;
|
||||||
|
|
||||||
|
this.state.yAxis = {
|
||||||
|
labels: yPts,
|
||||||
|
positions: yPts.map((d) => zeroLine - d * scaleMultiplier),
|
||||||
|
title: yAxisConfigObject.title || null,
|
||||||
|
pos: yAxisAlignment,
|
||||||
|
scaleMultiplier: scaleMultiplier,
|
||||||
|
zeroLine: zeroLine,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
this.state.yAxis = [];
|
||||||
|
for (let key in dataValues) {
|
||||||
|
const dataValue = dataValues[key];
|
||||||
|
yAxisConfigObject =
|
||||||
|
this.config.yAxisConfig.find((item) => key === item.id) ||
|
||||||
|
[];
|
||||||
|
yAxisAlignment = yAxisConfigObject.position
|
||||||
|
? yAxisConfigObject.position
|
||||||
|
: "left";
|
||||||
|
yPts = calcChartIntervals(dataValue, withMinimum, this.config.overrideCeiling, this.config.overrideFloor);
|
||||||
|
scaleMultiplier = this.height / getValueRange(yPts);
|
||||||
|
intervalHeight = getIntervalSize(yPts) * scaleMultiplier;
|
||||||
|
zeroLine = this.height - getZeroIndex(yPts) * intervalHeight;
|
||||||
|
positions = yPts.map((d) => zeroLine - d * scaleMultiplier);
|
||||||
|
yKeys.push(key);
|
||||||
|
|
||||||
|
if (this.state.yAxis.length > 1) {
|
||||||
|
const yPtsArray = [];
|
||||||
|
const firstArr = this.state.yAxis[0];
|
||||||
|
|
||||||
|
// we need to calculate the scaleMultiplier.
|
||||||
|
|
||||||
|
// now that we have an accurate scaleMultiplier we can
|
||||||
|
// we need to loop through original positions.
|
||||||
|
scaleMultiplier = this.height / getValueRange(yPts);
|
||||||
|
firstArr.positions.forEach((pos) => {
|
||||||
|
yPtsArray.push(Math.ceil(pos / scaleMultiplier));
|
||||||
|
});
|
||||||
|
yPts = yPtsArray.reverse();
|
||||||
|
zeroLine =
|
||||||
|
this.height - getZeroIndex(yPts) * intervalHeight;
|
||||||
|
positions = firstArr.positions;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state.yAxis.push({
|
||||||
|
axisID: key || "left-axis",
|
||||||
|
labels: yPts,
|
||||||
|
title: yAxisConfigObject.title,
|
||||||
|
pos: yAxisAlignment,
|
||||||
|
scaleMultiplier,
|
||||||
|
zeroLine,
|
||||||
|
positions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// the labels are not aligned in length between the two yAxis objects,
|
||||||
|
// we need to run some new calculations.
|
||||||
|
if (
|
||||||
|
this.state.yAxis[1] &&
|
||||||
|
this.state.yAxis[0].labels.length !==
|
||||||
|
this.state.yAxis[1].labels.length
|
||||||
|
) {
|
||||||
|
const newYptsArr = [];
|
||||||
|
// find the shorter array
|
||||||
|
const shortest = this.state.yAxis.reduce(
|
||||||
|
(p, c) => {
|
||||||
|
return p.length > c.labels.length ? c : p;
|
||||||
|
},
|
||||||
|
{ length: Infinity }
|
||||||
|
);
|
||||||
|
// return the longest
|
||||||
|
const longest = this.state.yAxis.reduce(
|
||||||
|
(p, c) => {
|
||||||
|
return p.length < c.labels.length ? p : c;
|
||||||
|
},
|
||||||
|
{ length: Infinity }
|
||||||
|
);
|
||||||
|
|
||||||
|
// we now need to populate the shortest obj with the new scale multiplier
|
||||||
|
// with the positions of the longest obj.
|
||||||
|
longest.positions.forEach((pos) => {
|
||||||
|
// calculate a new yPts
|
||||||
|
newYptsArr.push(Math.ceil(pos / shortest.scaleMultiplier));
|
||||||
|
});
|
||||||
|
|
||||||
|
shortest.labels = newYptsArr.reverse();
|
||||||
|
shortest.positions = longest.positions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Dependent if above changes
|
// Dependent if above changes
|
||||||
this.calcDatasetPoints();
|
this.calcDatasetPoints();
|
||||||
@ -104,35 +249,57 @@ export default class AxisChart extends BaseChart {
|
|||||||
|
|
||||||
calcDatasetPoints() {
|
calcDatasetPoints() {
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
let scaleAll = values => values.map(val => scale(val, s.yAxis));
|
let scaleAll = (values, id) => {
|
||||||
|
return values.map((val) => {
|
||||||
|
let { yAxis } = s;
|
||||||
|
|
||||||
|
if (yAxis instanceof Array) {
|
||||||
|
yAxis =
|
||||||
|
yAxis.length > 1
|
||||||
|
? yAxis.find((axis) => id === axis.axisID)
|
||||||
|
: s.yAxis[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return scale(val, yAxis);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
s.barChartIndex = 1;
|
||||||
s.datasets = this.data.datasets.map((d, i) => {
|
s.datasets = this.data.datasets.map((d, i) => {
|
||||||
let values = d.values;
|
let values = d.values;
|
||||||
let cumulativeYs = d.cumulativeYs || [];
|
let cumulativeYs = d.cumulativeYs || [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: d.name.replace(/<|>|&/g, (char) => char == '&' ? '&' : char == '<' ? '<' : '>'),
|
name:
|
||||||
|
d.name &&
|
||||||
|
d.name.replace(/<|>|&/g, (char) =>
|
||||||
|
char == "&" ? "&" : char == "<" ? "<" : ">"
|
||||||
|
),
|
||||||
index: i,
|
index: i,
|
||||||
|
barIndex:
|
||||||
|
d.chartType === "bar" ? s.barChartIndex++ : s.barChartIndex,
|
||||||
chartType: d.chartType,
|
chartType: d.chartType,
|
||||||
|
|
||||||
values: values,
|
values: values,
|
||||||
yPositions: scaleAll(values),
|
yPositions: scaleAll(values, d.axisID),
|
||||||
|
id: d.axisID,
|
||||||
|
|
||||||
cumulativeYs: cumulativeYs,
|
cumulativeYs: cumulativeYs,
|
||||||
cumulativeYPos: scaleAll(cumulativeYs),
|
cumulativeYPos: scaleAll(cumulativeYs, d.axisID),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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,101 +308,169 @@ 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;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllYValues() {
|
getAllYValues() {
|
||||||
let key = 'values';
|
let key = "values";
|
||||||
|
let multiAxis = this.config.yAxisConfig ? true : false;
|
||||||
|
let allValueLists = multiAxis ? {} : [];
|
||||||
|
|
||||||
if(this.barOptions.stacked) {
|
let groupBy = (arr, property) => {
|
||||||
key = 'cumulativeYs';
|
return arr.reduce((acc, cur) => {
|
||||||
|
acc[cur[property]] = [...(acc[cur[property]] || []), cur];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
};
|
||||||
|
|
||||||
|
let generateCumulative = (arr) => {
|
||||||
let cumulative = new Array(this.state.datasetLength).fill(0);
|
let cumulative = new Array(this.state.datasetLength).fill(0);
|
||||||
this.data.datasets.map((d, i) => {
|
arr.forEach((d, i) => {
|
||||||
let values = this.data.datasets[i].values;
|
let values = arr[i].values;
|
||||||
d[key] = cumulative = cumulative.map((c, i) => c + values[i]);
|
d[key] = cumulative = cumulative.map((c, i) => {
|
||||||
|
return c + values[i];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.barOptions.stacked) {
|
||||||
|
key = "cumulativeYs";
|
||||||
|
// we need to filter out the different yAxis ID's here.
|
||||||
|
if (multiAxis) {
|
||||||
|
const groupedDataSets = groupBy(this.data.datasets, "axisID");
|
||||||
|
// const dataSetsByAxis = this.data.dd
|
||||||
|
for (var axisID in groupedDataSets) {
|
||||||
|
generateCumulative(groupedDataSets[axisID]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
generateCumulative(this.data.datasets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the trouble maker, we don't want to merge all
|
||||||
|
// datasets since we are trying to run two yAxis.
|
||||||
|
if (multiAxis) {
|
||||||
|
this.data.datasets.forEach((d) => {
|
||||||
|
// if the array exists already just push more data into it.
|
||||||
|
// otherwise create a new array into the object.
|
||||||
|
allValueLists[d.axisID || key]
|
||||||
|
? allValueLists[d.axisID || key].push(...d[key])
|
||||||
|
: (allValueLists[d.axisID || key] = [...d[key]]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
allValueLists = this.data.datasets.map((d) => {
|
||||||
|
return d[key];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let allValueLists = this.data.datasets.map(d => d[key]);
|
if (this.data.yMarkers && !multiAxis) {
|
||||||
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) {
|
|
||||||
this.data.yRegions.map(d => {
|
if (this.data.yRegions && !multiAxis) {
|
||||||
|
this.data.yRegions.map((d) => {
|
||||||
allValueLists.push([d.end, d.start]);
|
allValueLists.push([d.end, d.start]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return [].concat(...allValueLists);
|
return multiAxis ? allValueLists : [].concat(...allValueLists); //return [].concat(...allValueLists); master
|
||||||
}
|
}
|
||||||
|
|
||||||
setupComponents() {
|
setupComponents() {
|
||||||
let componentConfigs = [
|
let componentConfigs = [
|
||||||
[
|
[
|
||||||
'yAxis',
|
"xAxis",
|
||||||
{
|
|
||||||
mode: this.config.yAxisMode,
|
|
||||||
width: this.width,
|
|
||||||
shortenNumbers: this.config.shortenYAxisNumbers
|
|
||||||
// pos: 'right'
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
return this.state.yAxis;
|
|
||||||
}.bind(this)
|
|
||||||
],
|
|
||||||
|
|
||||||
[
|
|
||||||
'xAxis',
|
|
||||||
{
|
{
|
||||||
mode: this.config.xAxisMode,
|
mode: this.config.xAxisMode,
|
||||||
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(
|
||||||
s.xAxis.labels, this.config.xIsSeries);
|
this.width,
|
||||||
|
s.xAxis.labels,
|
||||||
|
this.config.xIsSeries
|
||||||
|
);
|
||||||
|
|
||||||
return s.xAxis;
|
return s.xAxis;
|
||||||
}.bind(this)
|
}.bind(this),
|
||||||
],
|
],
|
||||||
|
|
||||||
[
|
[
|
||||||
'yRegions',
|
"yRegions",
|
||||||
{
|
{
|
||||||
width: this.width,
|
width: this.width,
|
||||||
pos: 'right'
|
pos: "right",
|
||||||
},
|
},
|
||||||
function() {
|
function () {
|
||||||
return this.state.yRegions;
|
return this.state.yRegions;
|
||||||
}.bind(this)
|
}.bind(this),
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
let barDatasets = this.state.datasets.filter(d => d.chartType === 'bar');
|
// if we have multiple yAxisConfigs we need to update the yAxisDefault
|
||||||
let lineDatasets = this.state.datasets.filter(d => d.chartType === 'line');
|
// components to multiple yAxis components.
|
||||||
|
if (this.config.yAxisConfig && this.config.yAxisConfig.length) {
|
||||||
|
this.config.yAxisConfig.forEach((yAxis) => {
|
||||||
|
componentConfigs.push([
|
||||||
|
"yAxis",
|
||||||
|
{
|
||||||
|
mode: yAxis.yAxisMode || "span",
|
||||||
|
width: this.width,
|
||||||
|
height: this.baseHeight,
|
||||||
|
shortenNumbers: this.config.shortenYAxisNumbers,
|
||||||
|
pos: yAxis.position || "left",
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
return this.state.yAxis;
|
||||||
|
}.bind(this),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
componentConfigs.push([
|
||||||
|
"yAxis",
|
||||||
|
{
|
||||||
|
mode: this.config.yAxisMode,
|
||||||
|
width: this.width,
|
||||||
|
height: this.baseHeight,
|
||||||
|
shortenNumbers: this.config.shortenYAxisNumbers,
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
return this.state.yAxis;
|
||||||
|
}.bind(this),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
let barsConfigs = barDatasets.map(d => {
|
let barDatasets = this.state.datasets.filter(
|
||||||
|
(d) => d.chartType === "bar"
|
||||||
|
);
|
||||||
|
let lineDatasets = this.state.datasets.filter(
|
||||||
|
(d) => d.chartType === "line"
|
||||||
|
);
|
||||||
|
|
||||||
|
let barsConfigs = barDatasets.map((d) => {
|
||||||
let index = d.index;
|
let index = d.index;
|
||||||
|
let barIndex = d.barIndex || index;
|
||||||
return [
|
return [
|
||||||
'barGraph' + '-' + d.index,
|
"barGraph" + "-" + d.index,
|
||||||
{
|
{
|
||||||
index: index,
|
index: index,
|
||||||
color: this.colors[index],
|
color: this.colors[index],
|
||||||
@ -245,32 +480,52 @@ 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 { yAxis } = s;
|
||||||
let d = s.datasets[index];
|
let d = s.datasets[index];
|
||||||
|
let { id = "left-axis" } = d;
|
||||||
let stacked = this.barOptions.stacked;
|
let stacked = this.barOptions.stacked;
|
||||||
|
|
||||||
let spaceRatio = this.barOptions.spaceRatio || BAR_CHART_SPACE_RATIO;
|
let spaceRatio =
|
||||||
|
this.barOptions.spaceRatio || BAR_CHART_SPACE_RATIO;
|
||||||
let barsWidth = s.unitWidth * (1 - spaceRatio);
|
let barsWidth = s.unitWidth * (1 - spaceRatio);
|
||||||
let barWidth = barsWidth/(stacked ? 1 : barDatasets.length);
|
let barWidth =
|
||||||
|
barsWidth / (stacked ? 1 : barDatasets.length);
|
||||||
|
|
||||||
let xPositions = s.xAxis.positions.map(x => x - barsWidth/2);
|
// if there are multiple yAxis we need to return the yAxis with the
|
||||||
if(!stacked) {
|
// proper ID.
|
||||||
xPositions = xPositions.map(p => p + barWidth * index);
|
if (yAxis instanceof Array) {
|
||||||
|
// if the person only configured one yAxis in the array return the first.
|
||||||
|
yAxis =
|
||||||
|
yAxis.length > 1
|
||||||
|
? yAxis.find((axis) => id === axis.axisID)
|
||||||
|
: s.yAxis[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
let labels = new Array(s.datasetLength).fill('');
|
let xPositions = s.xAxis.positions.map(
|
||||||
if(this.config.valuesOverPoints) {
|
(x) => x - barsWidth / 2
|
||||||
if(stacked && d.index === s.datasets.length - 1) {
|
);
|
||||||
|
|
||||||
|
if (!stacked) {
|
||||||
|
xPositions = xPositions.map(
|
||||||
|
(p) => p + barWidth * barIndex - barWidth
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let labels = new Array(s.datasetLength).fill("");
|
||||||
|
if (this.config.valuesOverPoints) {
|
||||||
|
if (stacked && d.index === s.datasets.length - 1) {
|
||||||
labels = d.cumulativeYs;
|
labels = d.cumulativeYs;
|
||||||
} else {
|
} else {
|
||||||
labels = d.values;
|
labels = d.values;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -280,18 +535,18 @@ export default class AxisChart extends BaseChart {
|
|||||||
// values: d.values,
|
// values: d.values,
|
||||||
labels: labels,
|
labels: labels,
|
||||||
|
|
||||||
zeroLine: s.yAxis.zeroLine,
|
zeroLine: yAxis.zeroLine,
|
||||||
barsWidth: barsWidth,
|
barsWidth: barsWidth,
|
||||||
barWidth: barWidth,
|
barWidth: barWidth,
|
||||||
};
|
};
|
||||||
}.bind(this)
|
}.bind(this),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
let lineConfigs = lineDatasets.map(d => {
|
let lineConfigs = lineDatasets.map((d) => {
|
||||||
let index = d.index;
|
let index = d.index;
|
||||||
return [
|
return [
|
||||||
'lineGraph' + '-' + d.index,
|
"lineGraph" + "-" + d.index,
|
||||||
{
|
{
|
||||||
index: index,
|
index: index,
|
||||||
color: this.colors[index],
|
color: this.colors[index],
|
||||||
@ -305,11 +560,20 @@ export default class AxisChart extends BaseChart {
|
|||||||
// 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
|
|
||||||
? s.yAxis.positions[0] : s.yAxis.zeroLine;
|
// if we have more than one yindex lets map the values
|
||||||
|
const yAxis = s.yAxis.length
|
||||||
|
? s.yAxis.find((axis) => d.id === axis.axisID) ||
|
||||||
|
s.yAxis[0]
|
||||||
|
: s.yAxis;
|
||||||
|
|
||||||
|
let minLine =
|
||||||
|
yAxis.positions[0] < yAxis.zeroLine
|
||||||
|
? yAxis.positions[0]
|
||||||
|
: yAxis.zeroLine;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
xPositions: s.xAxis.positions,
|
xPositions: s.xAxis.positions,
|
||||||
@ -320,37 +584,49 @@ export default class AxisChart extends BaseChart {
|
|||||||
zeroLine: minLine,
|
zeroLine: minLine,
|
||||||
radius: this.lineOptions.dotSize || LINE_CHART_DOT_SIZE,
|
radius: this.lineOptions.dotSize || LINE_CHART_DOT_SIZE,
|
||||||
};
|
};
|
||||||
}.bind(this)
|
}.bind(this),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
let markerConfigs = [
|
let markerConfigs = [
|
||||||
[
|
[
|
||||||
'yMarkers',
|
"yMarkers",
|
||||||
{
|
{
|
||||||
width: this.width,
|
width: this.width,
|
||||||
pos: 'right'
|
pos: "right",
|
||||||
},
|
},
|
||||||
function() {
|
function () {
|
||||||
return this.state.yMarkers;
|
return this.state.yMarkers;
|
||||||
}.bind(this)
|
}.bind(this),
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
componentConfigs = componentConfigs.concat(barsConfigs, lineConfigs, markerConfigs);
|
componentConfigs = componentConfigs.concat(
|
||||||
|
barsConfigs,
|
||||||
|
lineConfigs,
|
||||||
|
markerConfigs
|
||||||
|
);
|
||||||
|
|
||||||
let optionals = ['yMarkers', 'yRegions'];
|
let optionals = ["yMarkers", "yRegions"];
|
||||||
this.dataUnitComponents = [];
|
this.dataUnitComponents = [];
|
||||||
|
|
||||||
this.components = new Map(componentConfigs
|
this.components = new Map(
|
||||||
.filter(args => !optionals.includes(args[0]) || this.state[args[0]])
|
componentConfigs
|
||||||
.map(args => {
|
.filter(
|
||||||
let component = getComponent(...args);
|
(args) =>
|
||||||
if(args[0].includes('lineGraph') || args[0].includes('barGraph')) {
|
!optionals.includes(args[0]) || this.state[args[0]]
|
||||||
this.dataUnitComponents.push(component);
|
)
|
||||||
}
|
.map((args) => {
|
||||||
return [args[0], component];
|
let component = getComponent(...args);
|
||||||
}));
|
if (
|
||||||
|
args[0].includes("lineGraph") ||
|
||||||
|
args[0].includes("barGraph")
|
||||||
|
) {
|
||||||
|
this.dataUnitComponents.push(component);
|
||||||
|
}
|
||||||
|
return [args[0], component];
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeDataByIndex() {
|
makeDataByIndex() {
|
||||||
@ -385,14 +661,16 @@ export default class AxisChart extends BaseChart {
|
|||||||
|
|
||||||
bindTooltip() {
|
bindTooltip() {
|
||||||
// NOTE: could be in tooltip itself, as it is a given functionality for its parent
|
// NOTE: could be in tooltip itself, as it is a given functionality for its parent
|
||||||
this.container.addEventListener('mousemove', (e) => {
|
this.container.addEventListener("mousemove", (e) => {
|
||||||
let m = this.measures;
|
let m = this.measures;
|
||||||
let o = getOffset(this.container);
|
let o = getOffset(this.container);
|
||||||
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 > getTopOffset(m)) {
|
relY < this.height + getTopOffset(m) &&
|
||||||
|
relY > getTopOffset(m)
|
||||||
|
) {
|
||||||
this.mapTooltipXPosition(relX);
|
this.mapTooltipXPosition(relX);
|
||||||
} else {
|
} else {
|
||||||
this.tip.hideTip();
|
this.tip.hideTip();
|
||||||
@ -402,7 +680,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 +689,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,41 +700,63 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
/* renderLegend() {
|
||||||
|
let s = this.data;
|
||||||
|
if (s.datasets.length > 1) {
|
||||||
|
this.legendArea.textContent = "";
|
||||||
|
console.log(s.datasets);
|
||||||
s.datasets.map((d, i) => {
|
s.datasets.map((d, i) => {
|
||||||
let barWidth = AXIS_LEGEND_BAR_SIZE;
|
let barWidth = LEGEND_ITEM_WIDTH;
|
||||||
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right;
|
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right;
|
||||||
// let multiplier = s.datasets.length - i;
|
// let multiplier = s.datasets.length - i;
|
||||||
let rect = legendBar(
|
let rect = legendBar(
|
||||||
// rightEndPoint - multiplier * barWidth, // To right align
|
// rightEndPoint - multiplier * barWidth, // To right align
|
||||||
barWidth * i,
|
barWidth * i,
|
||||||
'0',
|
"0",
|
||||||
barWidth,
|
barWidth,
|
||||||
this.colors[i],
|
this.colors[i],
|
||||||
d.name,
|
d.name,
|
||||||
this.config.truncateLegends);
|
this.config.truncateLegends
|
||||||
|
);
|
||||||
this.legendArea.appendChild(rect);
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.overlayGuides = this.dataUnitComponents.map(c => {
|
this.overlayGuides = this.dataUnitComponents.map((c) => {
|
||||||
return {
|
return {
|
||||||
type: c.unitType,
|
type: c.unitType,
|
||||||
overlay: undefined,
|
overlay: undefined,
|
||||||
@ -464,12 +764,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render overlays
|
// Render overlays
|
||||||
this.overlayGuides.map(d => {
|
this.overlayGuides.map((d) => {
|
||||||
let currentUnit = d.units[this.state.currentIndex];
|
let currentUnit = d.units[this.state.currentIndex];
|
||||||
|
|
||||||
d.overlay = makeOverlay[d.type](currentUnit);
|
d.overlay = makeOverlay[d.type](currentUnit);
|
||||||
@ -478,8 +778,8 @@ 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);
|
||||||
});
|
});
|
||||||
@ -487,30 +787,30 @@ export default class AxisChart extends BaseChart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bindOverlay() {
|
bindOverlay() {
|
||||||
this.parent.addEventListener('data-select', () => {
|
this.parent.addEventListener("data-select", () => {
|
||||||
this.updateOverlay();
|
this.updateOverlay();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bindUnits() {
|
bindUnits() {
|
||||||
this.dataUnitComponents.map(c => {
|
this.dataUnitComponents.map((c) => {
|
||||||
c.units.map(unit => {
|
c.units.map((unit) => {
|
||||||
unit.addEventListener('click', () => {
|
unit.addEventListener("click", () => {
|
||||||
let index = unit.getAttribute('data-point-index');
|
let index = unit.getAttribute("data-point-index");
|
||||||
this.setCurrentDataPoint(index);
|
this.setCurrentDataPoint(index);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Note: Doesn't work as tooltip is absolutely positioned
|
// Note: Doesn't work as tooltip is absolutely positioned
|
||||||
this.tip.container.addEventListener('click', () => {
|
this.tip.container.addEventListener("click", () => {
|
||||||
let index = this.tip.container.getAttribute('data-point-index');
|
let index = this.tip.container.getAttribute("data-point-index");
|
||||||
this.setCurrentDataPoint(index);
|
this.setCurrentDataPoint(index);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOverlay() {
|
updateOverlay() {
|
||||||
this.overlayGuides.map(d => {
|
this.overlayGuides.map((d) => {
|
||||||
let currentUnit = d.units[this.state.currentIndex];
|
let currentUnit = d.units[this.state.currentIndex];
|
||||||
updateOverlay[d.type](currentUnit, d.overlay);
|
updateOverlay[d.type](currentUnit, d.overlay);
|
||||||
});
|
});
|
||||||
@ -524,12 +824,12 @@ 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,
|
||||||
label: s.xAxis.labels[index],
|
label: s.xAxis.labels[index],
|
||||||
values: s.datasets.map(d => d.values[index])
|
values: s.datasets.map((d) => d.values[index]),
|
||||||
};
|
};
|
||||||
return data_point;
|
return data_point;
|
||||||
}
|
}
|
||||||
@ -537,17 +837,15 @@ 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 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,19 +854,19 @@ 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;
|
||||||
}
|
}
|
||||||
super.removeDataPoint(index);
|
super.removeDataPoint(index);
|
||||||
this.data.labels.splice(index, 1);
|
this.data.labels.splice(index, 1);
|
||||||
this.data.datasets.map(d => {
|
this.data.datasets.map((d) => {
|
||||||
d.values.splice(index, 1);
|
d.values.splice(index, 1);
|
||||||
});
|
});
|
||||||
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 +875,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
|
|
||||||
updateDatasets(datasets) {
|
updateDatasets(datasets) {
|
||||||
this.data.datasets.map((d, i) => {
|
this.data.datasets.map((d, i) => {
|
||||||
if(datasets[i]) {
|
if (datasets[i]) {
|
||||||
d.values = datasets[i];
|
d.values = datasets[i];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,46 +1,84 @@
|
|||||||
import SvgTip from '../objects/SvgTip';
|
import SvgTip from "../objects/SvgTip";
|
||||||
import { $, isElementInViewport, getElementContentWidth, isHidden } from '../utils/dom';
|
import {
|
||||||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText } from '../utils/draw';
|
$,
|
||||||
import { BASE_MEASURES, getExtraHeight, getExtraWidth, getTopOffset, getLeftOffset,
|
isElementInViewport,
|
||||||
INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS} from '../utils/constants';
|
getElementContentWidth,
|
||||||
import { getColor, isValidColor } from '../utils/colors';
|
isHidden,
|
||||||
import { runSMILAnimation } from '../utils/animation';
|
} from "../utils/dom";
|
||||||
import { downloadFile, prepareForExport } from '../utils/export';
|
import {
|
||||||
|
makeSVGContainer,
|
||||||
|
makeSVGDefs,
|
||||||
|
makeSVGGroup,
|
||||||
|
makeText,
|
||||||
|
} from "../utils/draw";
|
||||||
|
import { LEGEND_ITEM_WIDTH } from "../utils/constants";
|
||||||
|
import {
|
||||||
|
BASE_MEASURES,
|
||||||
|
getExtraHeight,
|
||||||
|
getExtraWidth,
|
||||||
|
getTopOffset,
|
||||||
|
getLeftOffset,
|
||||||
|
INIT_CHART_UPDATE_TIMEOUT,
|
||||||
|
CHART_POST_ANIMATE_TIMEOUT,
|
||||||
|
DEFAULT_COLORS,
|
||||||
|
} from "../utils/constants";
|
||||||
|
import { getColor, isValidColor } from "../utils/colors";
|
||||||
|
import { runSMILAnimation } from "../utils/animation";
|
||||||
|
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) {
|
||||||
|
// deepclone options to avoid making changes to orignal object
|
||||||
|
options = deepClone(options);
|
||||||
|
|
||||||
this.parent = typeof parent === 'string'
|
this.parent =
|
||||||
? document.querySelector(parent)
|
typeof parent === "string"
|
||||||
: parent;
|
? document.querySelector(parent)
|
||||||
|
: parent;
|
||||||
|
|
||||||
if (!(this.parent instanceof HTMLElement)) {
|
if (!(this.parent instanceof HTMLElement)) {
|
||||||
throw new Error('No `parent` element to render on was provided.');
|
throw new Error("No `parent` element to render on was provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rawChartArgs = options;
|
this.rawChartArgs = options;
|
||||||
|
|
||||||
this.title = options.title || '';
|
this.title = options.title || "";
|
||||||
this.type = options.type || '';
|
this.type = options.type || "";
|
||||||
|
|
||||||
this.realData = this.prepareData(options.data);
|
|
||||||
this.data = this.prepareFirstData(this.realData);
|
|
||||||
|
|
||||||
this.colors = this.validateColors(options.colors, this.type);
|
this.colors = this.validateColors(options.colors, this.type);
|
||||||
|
|
||||||
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: 0,
|
||||||
truncateLegends: options.truncateLegends || 1
|
overrideCeiling: options.overrideCeiling || false,
|
||||||
|
overrideFloor: options.overrideFloor || false,
|
||||||
|
truncateLegends:
|
||||||
|
typeof options.truncateLegends !== "undefined"
|
||||||
|
? options.truncateLegends
|
||||||
|
: 1,
|
||||||
|
continuous:
|
||||||
|
typeof options.continuous !== "undefined"
|
||||||
|
? options.continuous
|
||||||
|
: 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.realData = this.prepareData(options.data, this.config);
|
||||||
|
this.data = this.prepareFirstData(this.realData);
|
||||||
|
|
||||||
this.setMeasures(options);
|
this.setMeasures(options);
|
||||||
if(!this.title.length) { m.titleHeight = 0; }
|
if (!this.title.length) {
|
||||||
if(!this.config.showLegend) m.legendHeight = 0;
|
m.titleHeight = 0;
|
||||||
|
}
|
||||||
|
if (!this.config.showLegend) m.legendHeight = 0;
|
||||||
this.argHeight = options.height || m.baseHeight;
|
this.argHeight = options.height || m.baseHeight;
|
||||||
|
|
||||||
this.state = {};
|
this.state = {};
|
||||||
@ -48,7 +86,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 +106,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,13 +127,19 @@ export default class BaseChart {
|
|||||||
|
|
||||||
// Bind window events
|
// Bind window events
|
||||||
this.boundDrawFn = () => this.draw(true);
|
this.boundDrawFn = () => this.draw(true);
|
||||||
window.addEventListener('resize', this.boundDrawFn);
|
// Look into improving responsiveness
|
||||||
window.addEventListener('orientationchange', this.boundDrawFn);
|
//if (ResizeObserver) {
|
||||||
|
// this.resizeObserver = new ResizeObserver(this.boundDrawFn);
|
||||||
|
// this.resizeObserver.observe(this.parent);
|
||||||
|
//}
|
||||||
|
window.addEventListener("resize", this.boundDrawFn);
|
||||||
|
window.addEventListener("orientationchange", this.boundDrawFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
window.removeEventListener('resize', this.boundDrawFn);
|
//if (this.resizeObserver) this.resizeObserver.disconnect();
|
||||||
window.removeEventListener('orientationchange', this.boundDrawFn);
|
window.removeEventListener("resize", this.boundDrawFn);
|
||||||
|
window.removeEventListener("orientationchange", this.boundDrawFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Has to be called manually
|
// Has to be called manually
|
||||||
@ -109,31 +153,31 @@ export default class BaseChart {
|
|||||||
|
|
||||||
makeContainer() {
|
makeContainer() {
|
||||||
// Chart needs a dedicated parent element
|
// Chart needs a dedicated parent element
|
||||||
this.parent.innerHTML = '';
|
this.parent.innerHTML = "";
|
||||||
|
|
||||||
let args = {
|
let args = {
|
||||||
inside: this.parent,
|
inside: this.parent,
|
||||||
className: 'chart-container'
|
className: "chart-container",
|
||||||
};
|
};
|
||||||
|
|
||||||
if(this.independentWidth) {
|
if (this.independentWidth) {
|
||||||
args.styles = { width: this.independentWidth + 'px' };
|
args.styles = { width: this.independentWidth + "px" };
|
||||||
}
|
}
|
||||||
|
|
||||||
this.container = $.create('div', args);
|
this.container = $.create("div", args);
|
||||||
}
|
}
|
||||||
|
|
||||||
makeTooltip() {
|
makeTooltip() {
|
||||||
this.tip = new SvgTip({
|
this.tip = new SvgTip({
|
||||||
parent: this.container,
|
parent: this.container,
|
||||||
colors: this.colors
|
colors: this.colors,
|
||||||
});
|
});
|
||||||
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;
|
||||||
@ -144,16 +188,22 @@ export default class BaseChart {
|
|||||||
this.makeChartArea();
|
this.makeChartArea();
|
||||||
this.setupComponents();
|
this.setupComponents();
|
||||||
|
|
||||||
this.components.forEach(c => c.setup(this.drawArea));
|
this.components.forEach((c) => c.setup(this.drawArea));
|
||||||
// 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);
|
this.update(this.data, true);
|
||||||
|
// Not needed anymore since animate defaults to 0 and might potentially be refactored or deprecated
|
||||||
|
/* setTimeout(() => {
|
||||||
|
this.update(this.data, true);
|
||||||
|
}, this.initTimeout); */
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderLegend();
|
if (this.config.showLegend) {
|
||||||
|
this.renderLegend();
|
||||||
|
}
|
||||||
|
|
||||||
this.setupNavigation(init);
|
this.setupNavigation(init);
|
||||||
}
|
}
|
||||||
@ -166,50 +216,54 @@ 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;
|
||||||
|
|
||||||
this.svg = makeSVGContainer(
|
this.svg = makeSVGContainer(
|
||||||
this.container,
|
this.container,
|
||||||
'frappe-chart chart',
|
"frappe-chart chart",
|
||||||
this.baseWidth,
|
this.baseWidth,
|
||||||
this.baseHeight
|
this.baseHeight
|
||||||
);
|
);
|
||||||
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,
|
||||||
m.margins.top,
|
m.margins.top,
|
||||||
this.title,
|
this.title,
|
||||||
{
|
{
|
||||||
fontSize: m.titleFontSize,
|
fontSize: m.titleFontSize,
|
||||||
fill: '#666666',
|
fill: "#666666",
|
||||||
dy: m.titleFontSize
|
dy: m.titleFontSize,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let top = getTopOffset(m);
|
let top = getTopOffset(m);
|
||||||
this.drawArea = makeSVGGroup(
|
this.drawArea = makeSVGGroup(
|
||||||
this.type + '-chart chart-draw-area',
|
this.type + "-chart chart-draw-area",
|
||||||
`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",
|
||||||
`translate(${getLeftOffset(m)}, ${top})`
|
`translate(${getLeftOffset(m)}, ${top})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
@ -217,71 +271,90 @@ export default class BaseChart {
|
|||||||
updateTipOffset(x, y) {
|
updateTipOffset(x, y) {
|
||||||
this.tip.offset = {
|
this.tip.offset = {
|
||||||
x: x,
|
x: x,
|
||||||
y: y
|
y: y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
setupComponents() { this.components = new Map(); }
|
setupComponents() {
|
||||||
|
this.components = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
update(data) {
|
update(data, drawing = false, config) {
|
||||||
if(!data) {
|
if (!data) console.error("No data to update.");
|
||||||
console.error('No data to update.');
|
if (!drawing) data = deepClone(data);
|
||||||
}
|
this.data = this.prepareData(data, config);
|
||||||
this.data = this.prepareData(data);
|
|
||||||
this.calc(); // builds state
|
this.calc(); // builds state
|
||||||
this.render(this.components, this.config.animate);
|
this.render(this.components, this.config.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);
|
||||||
}
|
}
|
||||||
let elementsToAnimate = [];
|
let elementsToAnimate = [];
|
||||||
// Can decouple to this.refreshComponents() first to save animation timeout
|
// Can decouple to this.refreshComponents() first to save animation timeout
|
||||||
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());
|
||||||
this.updateNav();
|
this.updateNav();
|
||||||
}, CHART_POST_ANIMATE_TIMEOUT);
|
}, CHART_POST_ANIMATE_TIMEOUT);
|
||||||
} else {
|
} else {
|
||||||
components.forEach(c => c.make());
|
components.forEach((c) => c.make());
|
||||||
this.updateNav();
|
this.updateNav();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = {
|
||||||
'13': this.onEnterKey.bind(this),
|
13: this.onEnterKey.bind(this),
|
||||||
'37': this.onLeftArrow.bind(this),
|
37: this.onLeftArrow.bind(this),
|
||||||
'38': this.onUpArrow.bind(this),
|
38: this.onUpArrow.bind(this),
|
||||||
'39': this.onRightArrow.bind(this),
|
39: this.onRightArrow.bind(this),
|
||||||
'40': this.onDownArrow.bind(this),
|
40: this.onDownArrow.bind(this),
|
||||||
};
|
};
|
||||||
|
|
||||||
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]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,6 +383,6 @@ export default class BaseChart {
|
|||||||
|
|
||||||
export() {
|
export() {
|
||||||
let chartSvg = prepareForExport(this.svg);
|
let chartSvg = prepareForExport(this.svg);
|
||||||
downloadFile(this.title || 'Chart', [chartSvg]);
|
downloadFile(this.title || "Chart", [chartSvg]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,161 +1,185 @@
|
|||||||
import AggregationChart from './AggregationChart';
|
import AggregationChart from "./AggregationChart";
|
||||||
import { getComponent } from '../objects/ChartComponents';
|
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 { makeArcStrokePathStr, makeStrokeCircleStr } from '../utils/draw';
|
import { makeArcStrokePathStr, makeStrokeCircleStr } from "../utils/draw";
|
||||||
import { lightenDarkenColor } from '../utils/colors';
|
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 DonutChart extends AggregationChart {
|
export default class DonutChart extends AggregationChart {
|
||||||
constructor(parent, args) {
|
constructor(parent, args) {
|
||||||
super(parent, args);
|
super(parent, args);
|
||||||
this.type = 'donut';
|
this.type = "donut";
|
||||||
this.initTimeout = 0;
|
this.initTimeout = 0;
|
||||||
this.init = 1;
|
this.init = 1;
|
||||||
|
|
||||||
this.setup();
|
this.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
configure(args) {
|
configure(args) {
|
||||||
super.configure(args);
|
super.configure(args);
|
||||||
this.mouseMove = this.mouseMove.bind(this);
|
this.mouseMove = this.mouseMove.bind(this);
|
||||||
this.mouseLeave = this.mouseLeave.bind(this);
|
this.mouseLeave = this.mouseLeave.bind(this);
|
||||||
|
|
||||||
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.clockWise = args.clockWise || false;
|
this.clockWise = args.clockWise || false;
|
||||||
this.strokeWidth = args.strokeWidth || 30;
|
this.strokeWidth = args.strokeWidth || 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
calc() {
|
calc() {
|
||||||
super.calc();
|
super.calc();
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
this.radius =
|
this.radius =
|
||||||
this.height > this.width
|
this.height > this.width
|
||||||
? this.center.x - this.strokeWidth / 2
|
? this.center.x - this.strokeWidth / 2
|
||||||
: this.center.y - this.strokeWidth / 2;
|
: this.center.y - this.strokeWidth / 2;
|
||||||
|
|
||||||
const { radius, clockWise } = this;
|
const { radius, clockWise } = this;
|
||||||
|
|
||||||
const prevSlicesProperties = s.slicesProperties || [];
|
const prevSlicesProperties = s.slicesProperties || [];
|
||||||
s.sliceStrings = [];
|
s.sliceStrings = [];
|
||||||
s.slicesProperties = [];
|
s.slicesProperties = [];
|
||||||
let curAngle = 180 - this.config.startAngle;
|
let curAngle = 180 - this.config.startAngle;
|
||||||
|
|
||||||
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);
|
||||||
const endPosition = getPositionByAngle(endAngle, radius);
|
const endPosition = getPositionByAngle(endAngle, radius);
|
||||||
|
|
||||||
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 {
|
||||||
curStart = startPosition;
|
curStart = startPosition;
|
||||||
curEnd = endPosition;
|
curEnd = endPosition;
|
||||||
}
|
}
|
||||||
const curPath =
|
const curPath =
|
||||||
originDiffAngle === 360
|
originDiffAngle === 360
|
||||||
? makeStrokeCircleStr(curStart, curEnd, this.center, this.radius, this.clockWise, largeArc)
|
? makeStrokeCircleStr(
|
||||||
: makeArcStrokePathStr(curStart, curEnd, this.center, this.radius, this.clockWise, largeArc);
|
curStart,
|
||||||
|
curEnd,
|
||||||
|
this.center,
|
||||||
|
this.radius,
|
||||||
|
this.clockWise,
|
||||||
|
largeArc
|
||||||
|
)
|
||||||
|
: makeArcStrokePathStr(
|
||||||
|
curStart,
|
||||||
|
curEnd,
|
||||||
|
this.center,
|
||||||
|
this.radius,
|
||||||
|
this.clockWise,
|
||||||
|
largeArc
|
||||||
|
);
|
||||||
|
|
||||||
s.sliceStrings.push(curPath);
|
s.sliceStrings.push(curPath);
|
||||||
s.slicesProperties.push({
|
s.slicesProperties.push({
|
||||||
startPosition,
|
startPosition,
|
||||||
endPosition,
|
endPosition,
|
||||||
value: total,
|
value: total,
|
||||||
total: s.grandTotal,
|
total: s.grandTotal,
|
||||||
startAngle,
|
startAngle,
|
||||||
endAngle,
|
endAngle,
|
||||||
angle: diffAngle
|
angle: diffAngle,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
this.init = 0;
|
||||||
|
}
|
||||||
|
|
||||||
});
|
setupComponents() {
|
||||||
this.init = 0;
|
let s = this.state;
|
||||||
}
|
|
||||||
|
|
||||||
setupComponents() {
|
let componentConfigs = [
|
||||||
let s = this.state;
|
[
|
||||||
|
"donutSlices",
|
||||||
|
{},
|
||||||
|
function () {
|
||||||
|
return {
|
||||||
|
sliceStrings: s.sliceStrings,
|
||||||
|
colors: this.colors,
|
||||||
|
strokeWidth: this.strokeWidth,
|
||||||
|
};
|
||||||
|
}.bind(this),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
let componentConfigs = [
|
this.components = new Map(
|
||||||
[
|
componentConfigs.map((args) => {
|
||||||
'donutSlices',
|
let component = getComponent(...args);
|
||||||
{ },
|
return [args[0], component];
|
||||||
function() {
|
})
|
||||||
return {
|
);
|
||||||
sliceStrings: s.sliceStrings,
|
}
|
||||||
colors: this.colors,
|
|
||||||
strokeWidth: this.strokeWidth,
|
|
||||||
};
|
|
||||||
}.bind(this)
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
this.components = new Map(componentConfigs
|
calTranslateByAngle(property) {
|
||||||
.map(args => {
|
const { radius, hoverRadio } = this;
|
||||||
let component = getComponent(...args);
|
const position = getPositionByAngle(
|
||||||
return [args[0], component];
|
property.startAngle + property.angle / 2,
|
||||||
}));
|
radius
|
||||||
}
|
);
|
||||||
|
return `translate3d(${position.x * hoverRadio}px,${
|
||||||
|
position.y * hoverRadio
|
||||||
|
}px,0)`;
|
||||||
|
}
|
||||||
|
|
||||||
calTranslateByAngle(property){
|
hoverSlice(path, i, flag, e) {
|
||||||
const{ radius, hoverRadio } = this;
|
if (!path) return;
|
||||||
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius);
|
const color = this.colors[i];
|
||||||
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hoverSlice(path,i,flag,e){
|
bindTooltip() {
|
||||||
if(!path) return;
|
this.container.addEventListener("mousemove", this.mouseMove);
|
||||||
const color = this.colors[i];
|
this.container.addEventListener("mouseleave", this.mouseLeave);
|
||||||
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() {
|
mouseMove(e) {
|
||||||
this.container.addEventListener('mousemove', this.mouseMove);
|
const target = e.target;
|
||||||
this.container.addEventListener('mouseleave', this.mouseLeave);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mouseMove(e){
|
mouseLeave() {
|
||||||
const target = e.target;
|
this.hoverSlice(this.curActiveSlice, this.curActiveSliceIndex, false);
|
||||||
let slices = this.components.get('donutSlices').store;
|
}
|
||||||
let prevIndex = this.curActiveSliceIndex;
|
|
||||||
let prevAcitve = this.curActiveSlice;
|
|
||||||
if(slices.includes(target)) {
|
|
||||||
let i = slices.indexOf(target);
|
|
||||||
this.hoverSlice(prevAcitve, prevIndex,false);
|
|
||||||
this.curActiveSlice = target;
|
|
||||||
this.curActiveSliceIndex = i;
|
|
||||||
this.hoverSlice(target, i, true, e);
|
|
||||||
} else {
|
|
||||||
this.mouseLeave();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mouseLeave(){
|
|
||||||
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,296 +1,337 @@
|
|||||||
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,
|
||||||
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals';
|
toMidnightUTC,
|
||||||
import { getExtraHeight, getExtraWidth, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE,
|
addDays,
|
||||||
HEATMAP_GUTTER_SIZE } from '../utils/constants';
|
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 {
|
||||||
|
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;
|
||||||
// const DAY_INCR = 1;
|
// const DAY_INCR = 1;
|
||||||
|
|
||||||
export default class Heatmap extends BaseChart {
|
export default class Heatmap extends BaseChart {
|
||||||
constructor(parent, options) {
|
constructor(parent, options) {
|
||||||
super(parent, options);
|
super(parent, options);
|
||||||
this.type = 'heatmap';
|
this.type = "heatmap";
|
||||||
|
|
||||||
this.countLabel = options.countLabel || '';
|
this.countLabel = options.countLabel || "";
|
||||||
|
|
||||||
let validStarts = ['Sunday', 'Monday'];
|
let validStarts = ["Sunday", "Monday"];
|
||||||
let startSubDomain = validStarts.includes(options.startSubDomain)
|
let startSubDomain = validStarts.includes(options.startSubDomain)
|
||||||
? options.startSubDomain : 'Sunday';
|
? options.startSubDomain
|
||||||
this.startSubDomainIndex = validStarts.indexOf(startSubDomain);
|
: "Sunday";
|
||||||
|
this.startSubDomainIndex = validStarts.indexOf(startSubDomain);
|
||||||
|
|
||||||
this.setup();
|
this.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
setMeasures(options) {
|
setMeasures(options) {
|
||||||
let m = this.measures;
|
let m = this.measures;
|
||||||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1;
|
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1;
|
||||||
|
|
||||||
m.paddings.top = ROW_HEIGHT * 3;
|
m.paddings.top = ROW_HEIGHT * 3;
|
||||||
m.paddings.bottom = 0;
|
m.paddings.bottom = 0;
|
||||||
m.legendHeight = ROW_HEIGHT * 2;
|
m.legendHeight = ROW_HEIGHT * 2;
|
||||||
m.baseHeight = ROW_HEIGHT * NO_OF_DAYS_IN_WEEK
|
m.baseHeight = ROW_HEIGHT * NO_OF_DAYS_IN_WEEK + getExtraHeight(m);
|
||||||
+ getExtraHeight(m);
|
|
||||||
|
|
||||||
let d = this.data;
|
let d = this.data;
|
||||||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
||||||
this.independentWidth = (getWeeksBetween(d.start, d.end)
|
this.independentWidth =
|
||||||
+ spacing) * COL_WIDTH + getExtraWidth(m);
|
(getWeeksBetween(d.start, d.end) + spacing) * COL_WIDTH +
|
||||||
}
|
getExtraWidth(m);
|
||||||
|
}
|
||||||
|
|
||||||
updateWidth() {
|
updateWidth() {
|
||||||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
||||||
let noOfWeeks = this.state.noOfWeeks ? this.state.noOfWeeks : 52;
|
let noOfWeeks = this.state.noOfWeeks ? this.state.noOfWeeks : 52;
|
||||||
this.baseWidth = (noOfWeeks + spacing) * COL_WIDTH
|
this.baseWidth =
|
||||||
+ getExtraWidth(this.measures);
|
(noOfWeeks + spacing) * COL_WIDTH + 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(); }
|
data.start = toMidnightUTC(data.start);
|
||||||
data.dataPoints = data.dataPoints || {};
|
|
||||||
|
|
||||||
if(parseInt(Object.keys(data.dataPoints)[0]) > 100000) {
|
if (!data.end) {
|
||||||
let points = {};
|
data.end = new Date();
|
||||||
Object.keys(data.dataPoints).forEach(timestampSec => {
|
}
|
||||||
let date = new Date(timestampSec * NO_OF_MILLIS);
|
data.end = toMidnightUTC(data.end);
|
||||||
points[getYyyyMmDd(date)] = data.dataPoints[timestampSec];
|
|
||||||
});
|
|
||||||
data.dataPoints = points;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
data.dataPoints = data.dataPoints || {};
|
||||||
}
|
|
||||||
|
|
||||||
calc() {
|
if (parseInt(Object.keys(data.dataPoints)[0]) > 100000) {
|
||||||
let s = this.state;
|
let points = {};
|
||||||
|
Object.keys(data.dataPoints).forEach((timestampSec) => {
|
||||||
|
let date = new Date(timestampSec * NO_OF_MILLIS);
|
||||||
|
points[getYyyyMmDd(date)] = data.dataPoints[timestampSec];
|
||||||
|
});
|
||||||
|
data.dataPoints = points;
|
||||||
|
}
|
||||||
|
|
||||||
s.start = clone(this.data.start);
|
return data;
|
||||||
s.end = clone(this.data.end);
|
}
|
||||||
|
|
||||||
s.firstWeekStart = clone(s.start);
|
calc() {
|
||||||
s.noOfWeeks = getWeeksBetween(s.start, s.end);
|
let s = this.state;
|
||||||
s.distribution = calcDistribution(
|
|
||||||
Object.values(this.data.dataPoints), HEATMAP_DISTRIBUTION_SIZE);
|
|
||||||
|
|
||||||
s.domainConfigs = this.getDomains();
|
s.start = clone(this.data.start);
|
||||||
}
|
s.end = clone(this.data.end);
|
||||||
|
|
||||||
setupComponents() {
|
s.firstWeekStart = clone(s.start);
|
||||||
let s = this.state;
|
s.noOfWeeks = getWeeksBetween(s.start, s.end);
|
||||||
let lessCol = this.discreteDomains ? 0 : 1;
|
s.distribution = calcDistribution(
|
||||||
|
Object.values(this.data.dataPoints),
|
||||||
|
HEATMAP_DISTRIBUTION_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
let componentConfigs = s.domainConfigs.map((config, i) => [
|
s.domainConfigs = this.getDomains();
|
||||||
'heatDomain',
|
}
|
||||||
{
|
|
||||||
index: config.index,
|
|
||||||
colWidth: COL_WIDTH,
|
|
||||||
rowHeight: ROW_HEIGHT,
|
|
||||||
squareSize: HEATMAP_SQUARE_SIZE,
|
|
||||||
radius: this.rawChartArgs.radius || 0,
|
|
||||||
xTranslate: s.domainConfigs
|
|
||||||
.filter((config, j) => j < i)
|
|
||||||
.map(config => config.cols.length - lessCol)
|
|
||||||
.reduce((a, b) => a + b, 0)
|
|
||||||
* COL_WIDTH
|
|
||||||
},
|
|
||||||
function() {
|
|
||||||
return s.domainConfigs[i];
|
|
||||||
}.bind(this)
|
|
||||||
|
|
||||||
]);
|
setupComponents() {
|
||||||
|
let s = this.state;
|
||||||
|
let lessCol = this.discreteDomains ? 0 : 1;
|
||||||
|
|
||||||
this.components = new Map(componentConfigs
|
let componentConfigs = s.domainConfigs.map((config, i) => [
|
||||||
.map((args, i) => {
|
"heatDomain",
|
||||||
let component = getComponent(...args);
|
{
|
||||||
return [args[0] + '-' + i, component];
|
index: config.index,
|
||||||
})
|
colWidth: COL_WIDTH,
|
||||||
);
|
rowHeight: ROW_HEIGHT,
|
||||||
|
squareSize: HEATMAP_SQUARE_SIZE,
|
||||||
|
radius: this.rawChartArgs.radius || 0,
|
||||||
|
xTranslate:
|
||||||
|
s.domainConfigs
|
||||||
|
.filter((config, j) => j < i)
|
||||||
|
.map((config) => config.cols.length - lessCol)
|
||||||
|
.reduce((a, b) => a + b, 0) * COL_WIDTH,
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
return s.domainConfigs[i];
|
||||||
|
}.bind(this),
|
||||||
|
]);
|
||||||
|
|
||||||
let y = 0;
|
this.components = new Map(
|
||||||
DAY_NAMES_SHORT.forEach((dayName, i) => {
|
componentConfigs.map((args, i) => {
|
||||||
if([1, 3, 5].includes(i)) {
|
let component = getComponent(...args);
|
||||||
let dayText = makeText('subdomain-name', -COL_WIDTH/2, y, dayName,
|
return [args[0] + "-" + i, component];
|
||||||
{
|
})
|
||||||
fontSize: HEATMAP_SQUARE_SIZE,
|
);
|
||||||
dy: 8,
|
|
||||||
textAnchor: 'end'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
this.drawArea.appendChild(dayText);
|
|
||||||
}
|
|
||||||
y += ROW_HEIGHT;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
update(data) {
|
let y = 0;
|
||||||
if(!data) {
|
DAY_NAMES_SHORT.forEach((dayName, i) => {
|
||||||
console.error('No data to update.');
|
if ([1, 3, 5].includes(i)) {
|
||||||
}
|
let dayText = makeText("subdomain-name", -COL_WIDTH / 2, y, dayName, {
|
||||||
|
fontSize: HEATMAP_SQUARE_SIZE,
|
||||||
|
dy: 8,
|
||||||
|
textAnchor: "end",
|
||||||
|
});
|
||||||
|
this.drawArea.appendChild(dayText);
|
||||||
|
}
|
||||||
|
y += ROW_HEIGHT;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.data = this.prepareData(data);
|
update(data) {
|
||||||
this.draw();
|
if (!data) {
|
||||||
this.bindTooltip();
|
console.error("No data to update.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bindTooltip() {
|
this.data = this.prepareData(data);
|
||||||
this.container.addEventListener('mousemove', (e) => {
|
this.draw();
|
||||||
this.components.forEach(comp => {
|
this.bindTooltip();
|
||||||
let daySquares = comp.store;
|
}
|
||||||
let daySquare = e.target;
|
|
||||||
if(daySquares.includes(daySquare)) {
|
|
||||||
|
|
||||||
let count = daySquare.getAttribute('data-value');
|
bindTooltip() {
|
||||||
let dateParts = daySquare.getAttribute('data-date').split('-');
|
this.container.addEventListener("mousemove", (e) => {
|
||||||
|
this.components.forEach((comp) => {
|
||||||
|
let daySquares = comp.store;
|
||||||
|
let daySquare = e.target;
|
||||||
|
if (daySquares.includes(daySquare)) {
|
||||||
|
let count = daySquare.getAttribute("data-value");
|
||||||
|
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(
|
||||||
this.tip.showTip();
|
x,
|
||||||
}
|
y,
|
||||||
});
|
{ name: name, value: value, valueFirst: 1 },
|
||||||
});
|
[]
|
||||||
}
|
);
|
||||||
|
this.tip.showTip();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
renderLegend() {
|
renderLegend() {
|
||||||
this.legendArea.textContent = '';
|
this.legendArea.textContent = "";
|
||||||
let x = 0;
|
let x = 0;
|
||||||
let y = ROW_HEIGHT;
|
let y = ROW_HEIGHT;
|
||||||
let radius = this.rawChartArgs.radius || 0;
|
let radius = this.rawChartArgs.radius || 0;
|
||||||
|
|
||||||
let lessText = makeText('subdomain-name', x, y, 'Less',
|
let lessText = makeText("subdomain-name", x, y, "Less", {
|
||||||
{
|
fontSize: HEATMAP_SQUARE_SIZE + 1,
|
||||||
fontSize: HEATMAP_SQUARE_SIZE + 1,
|
dy: 9,
|
||||||
dy: 9
|
});
|
||||||
}
|
x = COL_WIDTH * 2 + COL_WIDTH / 2;
|
||||||
);
|
this.legendArea.appendChild(lessText);
|
||||||
x = (COL_WIDTH * 2) + COL_WIDTH/2;
|
|
||||||
this.legendArea.appendChild(lessText);
|
|
||||||
|
|
||||||
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => {
|
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => {
|
||||||
const square = heatSquare('heatmap-legend-unit', x + (COL_WIDTH + 3) * i,
|
const square = heatSquare(
|
||||||
y, HEATMAP_SQUARE_SIZE, radius, color);
|
"heatmap-legend-unit",
|
||||||
this.legendArea.appendChild(square);
|
x + (COL_WIDTH + 3) * i,
|
||||||
});
|
y,
|
||||||
|
HEATMAP_SQUARE_SIZE,
|
||||||
|
radius,
|
||||||
|
color
|
||||||
|
);
|
||||||
|
this.legendArea.appendChild(square);
|
||||||
|
});
|
||||||
|
|
||||||
let moreTextX = x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH/4;
|
let moreTextX =
|
||||||
let moreText = makeText('subdomain-name', moreTextX, y, 'More',
|
x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH / 4;
|
||||||
{
|
let moreText = makeText("subdomain-name", moreTextX, y, "More", {
|
||||||
fontSize: HEATMAP_SQUARE_SIZE + 1,
|
fontSize: HEATMAP_SQUARE_SIZE + 1,
|
||||||
dy: 9
|
dy: 9,
|
||||||
}
|
});
|
||||||
);
|
this.legendArea.appendChild(moreText);
|
||||||
this.legendArea.appendChild(moreText);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
getDomains() {
|
getDomains() {
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()];
|
const [startMonth, startYear] = [s.start.getMonth(), s.start.getFullYear()];
|
||||||
const [endMonth, endYear] = [s.end.getMonth(), s.end.getFullYear()];
|
const [endMonth, endYear] = [s.end.getMonth(), s.end.getFullYear()];
|
||||||
|
|
||||||
const noOfMonths = (endMonth - startMonth + 1) + (endYear - startYear) * 12;
|
const noOfMonths = endMonth - startMonth + 1 + (endYear - startYear) * 12;
|
||||||
|
|
||||||
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] = [
|
||||||
endDate = getLastDateInMonth(month, year);
|
startOfMonth.getMonth(),
|
||||||
}
|
startOfMonth.getFullYear(),
|
||||||
domainConfigs.push(this.getDomainConfig(startOfMonth, endDate));
|
];
|
||||||
|
endDate = getLastDateInMonth(month, year);
|
||||||
|
}
|
||||||
|
domainConfigs.push(this.getDomainConfig(startOfMonth, endDate));
|
||||||
|
|
||||||
addDays(endDate, 1);
|
addDays(endDate, 1);
|
||||||
startOfMonth = endDate;
|
startOfMonth = endDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = endDate
|
||||||
|
? clone(endDate)
|
||||||
|
: toMidnightUTC(getLastDateInMonth(month, year));
|
||||||
|
|
||||||
let domainConfig = {
|
let domainConfig = {
|
||||||
index: month,
|
index: month,
|
||||||
cols: []
|
cols: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
addDays(endDate, 1);
|
addDays(endDate, 1);
|
||||||
let noOfMonthWeeks = getWeeksBetween(startOfWeek, endDate);
|
let noOfMonthWeeks = getWeeksBetween(startOfWeek, endDate);
|
||||||
|
|
||||||
let cols = [], col;
|
let cols = [],
|
||||||
for(var i = 0; i < noOfMonthWeeks; i++) {
|
col;
|
||||||
col = this.getCol(startOfWeek, month);
|
for (var i = 0; i < noOfMonthWeeks; i++) {
|
||||||
cols.push(col);
|
col = this.getCol(startOfWeek, month);
|
||||||
|
cols.push(col);
|
||||||
|
|
||||||
startOfWeek = new Date(col[NO_OF_DAYS_IN_WEEK - 1].yyyyMmDd);
|
startOfWeek = toMidnightUTC(
|
||||||
addDays(startOfWeek, 1);
|
new Date(col[NO_OF_DAYS_IN_WEEK - 1].yyyyMmDd)
|
||||||
}
|
);
|
||||||
|
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
domainConfig.cols = cols;
|
domainConfig.cols = cols;
|
||||||
|
|
||||||
return domainConfig;
|
return domainConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCol(startDate, month, empty = false) {
|
getCol(startDate, month, empty = false) {
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
|
|
||||||
// startDate is the start of week
|
// startDate is the start of week
|
||||||
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);
|
||||||
}
|
}
|
||||||
col.push(config);
|
col.push(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return col;
|
return col;
|
||||||
}
|
}
|
||||||
|
|
||||||
getSubDomainConfig(date) {
|
getSubDomainConfig(date) {
|
||||||
let yyyyMmDd = getYyyyMmDd(date);
|
let yyyyMmDd = getYyyyMmDd(date);
|
||||||
let dataValue = this.data.dataPoints[yyyyMmDd];
|
let dataValue = this.data.dataPoints[yyyyMmDd];
|
||||||
let config = {
|
let config = {
|
||||||
yyyyMmDd: yyyyMmDd,
|
yyyyMmDd: yyyyMmDd,
|
||||||
dataValue: dataValue || 0,
|
dataValue: dataValue || 0,
|
||||||
fill: this.colors[getMaxCheckpoint(dataValue, this.state.distribution)]
|
fill: this.colors[getMaxCheckpoint(dataValue, this.state.distribution)],
|
||||||
};
|
};
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,173 +0,0 @@
|
|||||||
import AxisChart from './AxisChart';
|
|
||||||
import { Y_AXIS_MARGIN } from '../utils/constants';
|
|
||||||
// import { ChartComponent } from '../objects/ChartComponents';
|
|
||||||
import { floatTwo } from '../utils/helpers';
|
|
||||||
|
|
||||||
export default class MultiAxisChart extends AxisChart {
|
|
||||||
constructor(args) {
|
|
||||||
super(args);
|
|
||||||
// this.unitType = args.unitType || 'line';
|
|
||||||
// this.setup();
|
|
||||||
}
|
|
||||||
|
|
||||||
preSetup() {
|
|
||||||
this.type = 'multiaxis';
|
|
||||||
}
|
|
||||||
|
|
||||||
setMeasures() {
|
|
||||||
super.setMeasures();
|
|
||||||
let noOfLeftAxes = this.data.datasets.filter(d => d.axisPosition === 'left').length;
|
|
||||||
this.measures.margins.left = (noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
|
||||||
this.measures.margins.right = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareYAxis() { }
|
|
||||||
|
|
||||||
prepareData(data) {
|
|
||||||
super.prepareData(data);
|
|
||||||
let sets = this.state.datasets;
|
|
||||||
// let axesLeft = sets.filter(d => d.axisPosition === 'left');
|
|
||||||
// let axesRight = sets.filter(d => d.axisPosition === 'right');
|
|
||||||
// let axesNone = sets.filter(d => !d.axisPosition ||
|
|
||||||
// !['left', 'right'].includes(d.axisPosition));
|
|
||||||
|
|
||||||
let leftCount = 0, rightCount = 0;
|
|
||||||
|
|
||||||
sets.forEach((d, i) => {
|
|
||||||
d.yAxis = {
|
|
||||||
position: d.axisPosition,
|
|
||||||
index: d.axisPosition === 'left' ? leftCount++ : rightCount++
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
configure(args) {
|
|
||||||
super.configure(args);
|
|
||||||
this.config.xAxisMode = args.xAxisMode || 'tick';
|
|
||||||
this.config.yAxisMode = args.yAxisMode || 'span';
|
|
||||||
}
|
|
||||||
|
|
||||||
// setUnitWidthAndXOffset() {
|
|
||||||
// this.state.unitWidth = this.width/(this.state.datasetLength);
|
|
||||||
// this.state.xOffset = this.state.unitWidth/2;
|
|
||||||
// }
|
|
||||||
|
|
||||||
configUnits() {
|
|
||||||
this.unitArgs = {
|
|
||||||
type: 'bar',
|
|
||||||
args: {
|
|
||||||
spaceWidth: this.state.unitWidth/2,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
setYAxis() {
|
|
||||||
this.state.datasets.map(d => {
|
|
||||||
this.calcYAxisParameters(d.yAxis, d.values, this.unitType === 'line');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
calcYUnits() {
|
|
||||||
this.state.datasets.map(d => {
|
|
||||||
d.positions = d.values.map(val => floatTwo(d.yAxis.zeroLine - val * d.yAxis.scaleMultiplier));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: function doesn't exist, handle with components
|
|
||||||
renderConstants() {
|
|
||||||
this.state.datasets.map(d => {
|
|
||||||
let guidePos = d.yAxis.position === 'left'
|
|
||||||
? -1 * d.yAxis.index * Y_AXIS_MARGIN
|
|
||||||
: this.width + d.yAxis.index * Y_AXIS_MARGIN;
|
|
||||||
this.renderer.xLine(guidePos, '', {
|
|
||||||
pos:'top',
|
|
||||||
mode: 'span',
|
|
||||||
stroke: this.colors[i],
|
|
||||||
className: 'y-axis-guide'
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getYAxesComponents() {
|
|
||||||
return this.data.datasets.map((e, i) => {
|
|
||||||
return new ChartComponent({
|
|
||||||
layerClass: 'y axis y-axis-' + i,
|
|
||||||
make: () => {
|
|
||||||
let yAxis = this.state.datasets[i].yAxis;
|
|
||||||
this.renderer.setZeroline(yAxis.zeroline);
|
|
||||||
let options = {
|
|
||||||
pos: yAxis.position,
|
|
||||||
mode: 'tick',
|
|
||||||
offset: yAxis.index * Y_AXIS_MARGIN,
|
|
||||||
stroke: this.colors[i]
|
|
||||||
};
|
|
||||||
|
|
||||||
return yAxis.positions.map((position, j) =>
|
|
||||||
this.renderer.yLine(position, yAxis.labels[j], options)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
animate: () => {}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO remove renderer zeroline from above and below
|
|
||||||
getChartComponents() {
|
|
||||||
return this.data.datasets.map((d, index) => {
|
|
||||||
return new ChartComponent({
|
|
||||||
layerClass: 'dataset-units dataset-' + index,
|
|
||||||
make: () => {
|
|
||||||
let d = this.state.datasets[index];
|
|
||||||
let unitType = this.unitArgs;
|
|
||||||
|
|
||||||
// the only difference, should be tied to datasets or default
|
|
||||||
this.renderer.setZeroline(d.yAxis.zeroLine);
|
|
||||||
|
|
||||||
return d.positions.map((y, j) => {
|
|
||||||
return this.renderer[unitType.type](
|
|
||||||
this.state.xAxisPositions[j],
|
|
||||||
y,
|
|
||||||
unitType.args,
|
|
||||||
this.colors[index],
|
|
||||||
j,
|
|
||||||
index,
|
|
||||||
this.state.datasetLength
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
animate: (svgUnits) => {
|
|
||||||
let d = this.state.datasets[index];
|
|
||||||
let unitType = this.unitArgs.type;
|
|
||||||
|
|
||||||
// have been updated in axis render;
|
|
||||||
let newX = this.state.xAxisPositions;
|
|
||||||
let newY = this.state.datasets[index].positions;
|
|
||||||
|
|
||||||
let lastUnit = svgUnits[svgUnits.length - 1];
|
|
||||||
let parentNode = lastUnit.parentNode;
|
|
||||||
|
|
||||||
if(this.oldState.xExtra > 0) {
|
|
||||||
for(var i = 0; i<this.oldState.xExtra; i++) {
|
|
||||||
let unit = lastUnit.cloneNode(true);
|
|
||||||
parentNode.appendChild(unit);
|
|
||||||
svgUnits.push(unit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.renderer.setZeroline(d.yAxis.zeroLine);
|
|
||||||
|
|
||||||
svgUnits.map((unit, i) => {
|
|
||||||
if(newX[i] === undefined || newY[i] === undefined) return;
|
|
||||||
this.elementsToAnimate.push(this.renderer['animate' + unitType](
|
|
||||||
unit, // unit, with info to replace where it came from in the data
|
|
||||||
newX[i],
|
|
||||||
newY[i],
|
|
||||||
index,
|
|
||||||
this.state.noOfDatasets
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,92 +1,96 @@
|
|||||||
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 } from "../utils/constants";
|
||||||
|
|
||||||
export default class PercentageChart extends AggregationChart {
|
export default class PercentageChart extends AggregationChart {
|
||||||
constructor(parent, args) {
|
constructor(parent, args) {
|
||||||
super(parent, args);
|
super(parent, args);
|
||||||
this.type = 'percentage';
|
this.type = "percentage";
|
||||||
this.setup();
|
this.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
setMeasures(options) {
|
setMeasures(options) {
|
||||||
let m = this.measures;
|
let m = this.measures;
|
||||||
this.barOptions = options.barOptions || {};
|
this.barOptions = options.barOptions || {};
|
||||||
|
|
||||||
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.legendHeight = 60;
|
||||||
m.baseHeight = (b.height + b.depth * 0.5) * 8;
|
m.baseHeight = (b.height + b.depth * 0.5) * 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupComponents() {
|
setupComponents() {
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
|
|
||||||
let componentConfigs = [
|
let componentConfigs = [
|
||||||
[
|
[
|
||||||
'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,
|
colors: this.colors,
|
||||||
colors: this.colors
|
};
|
||||||
};
|
}.bind(this),
|
||||||
}.bind(this)
|
],
|
||||||
]
|
];
|
||||||
];
|
|
||||||
|
|
||||||
this.components = new Map(componentConfigs
|
this.components = new Map(
|
||||||
.map(args => {
|
componentConfigs.map((args) => {
|
||||||
let component = getComponent(...args);
|
let component = getComponent(...args);
|
||||||
return [args[0], component];
|
return [args[0], component];
|
||||||
}));
|
})
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
calc() {
|
calc() {
|
||||||
super.calc();
|
super.calc();
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
|
|
||||||
s.xPositions = [];
|
s.xPositions = [];
|
||||||
s.widths = [];
|
s.widths = [];
|
||||||
|
|
||||||
let xPos = 0;
|
let xPos = 0;
|
||||||
s.sliceTotals.map((value) => {
|
s.sliceTotals.map((value) => {
|
||||||
let width = this.width * value / s.grandTotal;
|
let width = (this.width * value) / s.grandTotal;
|
||||||
s.widths.push(width);
|
s.widths.push(width);
|
||||||
s.xPositions.push(xPos);
|
s.xPositions.push(xPos);
|
||||||
xPos += width;
|
xPos += width;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
makeDataByIndex() { }
|
makeDataByIndex() {}
|
||||||
|
|
||||||
bindTooltip() {
|
bindTooltip() {
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
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 gOff = getOffset(this.container),
|
||||||
|
pOff = getOffset(bar);
|
||||||
|
|
||||||
let i = bars.indexOf(bar);
|
let x = pOff.left - gOff.left + parseInt(bar.getAttribute("width")) / 2;
|
||||||
let gOff = getOffset(this.container), pOff = getOffset(bar);
|
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;
|
||||||
|
|
||||||
let x = pOff.left - gOff.left + parseInt(bar.getAttribute('width'))/2;
|
this.tip.setValues(x, y, {
|
||||||
let y = pOff.top - gOff.top;
|
name: title,
|
||||||
let title = (this.formattedLabels && this.formattedLabels.length>0
|
value: (fraction * 100).toFixed(1) + "%",
|
||||||
? this.formattedLabels[i] : this.state.labels[i]) + ': ';
|
});
|
||||||
let fraction = s.sliceTotals[i]/s.grandTotal;
|
this.tip.showTip();
|
||||||
|
}
|
||||||
this.tip.setValues(x, y, {name: title, value: (fraction*100).toFixed(1) + "%"});
|
});
|
||||||
this.tip.showTip();
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,155 +1,207 @@
|
|||||||
import AggregationChart from './AggregationChart';
|
import AggregationChart from "./AggregationChart";
|
||||||
import { getComponent } from '../objects/ChartComponents';
|
import { getComponent } from "../objects/ChartComponents";
|
||||||
import { getOffset } from '../utils/dom';
|
import { getOffset, fire } 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 { 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.type = "pie";
|
||||||
this.initTimeout = 0;
|
this.initTimeout = 0;
|
||||||
this.init = 1;
|
this.init = 1;
|
||||||
|
|
||||||
this.setup();
|
this.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
configure(args) {
|
configure(args) {
|
||||||
super.configure(args);
|
super.configure(args);
|
||||||
this.mouseMove = this.mouseMove.bind(this);
|
this.mouseMove = this.mouseMove.bind(this);
|
||||||
this.mouseLeave = this.mouseLeave.bind(this);
|
this.mouseLeave = this.mouseLeave.bind(this);
|
||||||
|
|
||||||
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.clockWise = args.clockWise || false;
|
this.clockWise = args.clockWise || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
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.height > this.width ? this.center.x : this.center.y;
|
||||||
|
|
||||||
const { radius, clockWise } = this;
|
const { radius, clockWise } = this;
|
||||||
|
|
||||||
const prevSlicesProperties = s.slicesProperties || [];
|
const prevSlicesProperties = s.slicesProperties || [];
|
||||||
s.sliceStrings = [];
|
s.sliceStrings = [];
|
||||||
s.slicesProperties = [];
|
s.slicesProperties = [];
|
||||||
let curAngle = 180 - this.config.startAngle;
|
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];
|
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);
|
||||||
|
|
||||||
let curStart,curEnd;
|
const prevProperty = this.init && prevSlicesProperties[i];
|
||||||
if(this.init) {
|
|
||||||
curStart = prevProperty ? prevProperty.startPosition : startPosition;
|
|
||||||
curEnd = prevProperty ? prevProperty.endPosition : startPosition;
|
|
||||||
} else {
|
|
||||||
curStart = startPosition;
|
|
||||||
curEnd = endPosition;
|
|
||||||
}
|
|
||||||
const curPath =
|
|
||||||
originDiffAngle === 360
|
|
||||||
? makeCircleStr(curStart, curEnd, this.center, this.radius, clockWise, largeArc)
|
|
||||||
: makeArcPathStr(curStart, curEnd, this.center, this.radius, clockWise, largeArc);
|
|
||||||
|
|
||||||
s.sliceStrings.push(curPath);
|
let curStart, curEnd;
|
||||||
s.slicesProperties.push({
|
if (this.init) {
|
||||||
startPosition,
|
curStart = prevProperty ? prevProperty.startPosition : startPosition;
|
||||||
endPosition,
|
curEnd = prevProperty ? prevProperty.endPosition : startPosition;
|
||||||
value: total,
|
} else {
|
||||||
total: s.grandTotal,
|
curStart = startPosition;
|
||||||
startAngle,
|
curEnd = endPosition;
|
||||||
endAngle,
|
}
|
||||||
angle: diffAngle
|
const curPath =
|
||||||
});
|
originDiffAngle === 360
|
||||||
|
? makeCircleStr(
|
||||||
|
curStart,
|
||||||
|
curEnd,
|
||||||
|
this.center,
|
||||||
|
this.radius,
|
||||||
|
clockWise,
|
||||||
|
largeArc
|
||||||
|
)
|
||||||
|
: makeArcPathStr(
|
||||||
|
curStart,
|
||||||
|
curEnd,
|
||||||
|
this.center,
|
||||||
|
this.radius,
|
||||||
|
clockWise,
|
||||||
|
largeArc
|
||||||
|
);
|
||||||
|
|
||||||
});
|
s.sliceStrings.push(curPath);
|
||||||
this.init = 0;
|
s.slicesProperties.push({
|
||||||
}
|
startPosition,
|
||||||
|
endPosition,
|
||||||
|
value: total,
|
||||||
|
total: s.grandTotal,
|
||||||
|
startAngle,
|
||||||
|
endAngle,
|
||||||
|
angle: diffAngle,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.init = 0;
|
||||||
|
}
|
||||||
|
|
||||||
setupComponents() {
|
setupComponents() {
|
||||||
let s = this.state;
|
let s = this.state;
|
||||||
|
|
||||||
let componentConfigs = [
|
let componentConfigs = [
|
||||||
[
|
[
|
||||||
'pieSlices',
|
"pieSlices",
|
||||||
{ },
|
{},
|
||||||
function() {
|
function () {
|
||||||
return {
|
return {
|
||||||
sliceStrings: s.sliceStrings,
|
sliceStrings: s.sliceStrings,
|
||||||
colors: this.colors
|
colors: this.colors,
|
||||||
};
|
};
|
||||||
}.bind(this)
|
}.bind(this),
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
this.components = new Map(componentConfigs
|
this.components = new Map(
|
||||||
.map(args => {
|
componentConfigs.map((args) => {
|
||||||
let component = getComponent(...args);
|
let component = getComponent(...args);
|
||||||
return [args[0], component];
|
return [args[0], component];
|
||||||
}));
|
})
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
calTranslateByAngle(property){
|
calTranslateByAngle(property) {
|
||||||
const{radius,hoverRadio} = this;
|
const { radius, hoverRadio } = this;
|
||||||
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius);
|
const position = getPositionByAngle(
|
||||||
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`;
|
property.startAngle + property.angle / 2,
|
||||||
}
|
radius
|
||||||
|
);
|
||||||
|
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);
|
||||||
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[i] : this.state.labels[i]) + ': ';
|
(this.formatted_labels && this.formatted_labels.length > 0
|
||||||
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1);
|
? this.formatted_labels[i]
|
||||||
this.tip.setValues(x, y, {name: title, value: percent + "%"});
|
: this.state.labels[i]) + ": ";
|
||||||
this.tip.showTip();
|
let percent = (
|
||||||
} else {
|
(this.state.sliceTotals[i] * 100) /
|
||||||
transform(path,'translate3d(0,0,0)');
|
this.state.grandTotal
|
||||||
this.tip.hideTip();
|
).toFixed(1);
|
||||||
path.style.fill = color;
|
this.tip.setValues(x, y, { name: title, value: percent + "%" });
|
||||||
}
|
this.tip.showTip();
|
||||||
}
|
} else {
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
getDataPoint(index = this.state.currentIndex) {
|
||||||
|
let s = this.state;
|
||||||
|
let data_point = {
|
||||||
|
index: index,
|
||||||
|
label: s.labels[index],
|
||||||
|
values: s.sliceTotals[index],
|
||||||
|
};
|
||||||
|
return data_point;
|
||||||
|
}
|
||||||
|
setCurrentDataPoint(index) {
|
||||||
|
let s = this.state;
|
||||||
|
index = parseInt(index);
|
||||||
|
if (index < 0) index = 0;
|
||||||
|
if (index >= s.labels.length) index = s.labels.length - 1;
|
||||||
|
if (index === s.currentIndex) return;
|
||||||
|
s.currentIndex = index;
|
||||||
|
fire(this.parent, "data-select", this.getDataPoint());
|
||||||
|
}
|
||||||
|
|
||||||
mouseMove(e){
|
bindUnits() {
|
||||||
const target = e.target;
|
const units = this.components.get("pieSlices").store;
|
||||||
let slices = this.components.get('pieSlices').store;
|
if (!units) return;
|
||||||
let prevIndex = this.curActiveSliceIndex;
|
units.forEach((unit, index) => {
|
||||||
let prevAcitve = this.curActiveSlice;
|
unit.addEventListener("click", () => {
|
||||||
if(slices.includes(target)) {
|
this.setCurrentDataPoint(index);
|
||||||
let i = slices.indexOf(target);
|
});
|
||||||
this.hoverSlice(prevAcitve, prevIndex,false);
|
});
|
||||||
this.curActiveSlice = target;
|
}
|
||||||
this.curActiveSliceIndex = i;
|
mouseMove(e) {
|
||||||
this.hoverSlice(target, i, true, e);
|
const target = e.target;
|
||||||
} else {
|
let slices = this.components.get("pieSlices").store;
|
||||||
this.mouseLeave();
|
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(){
|
mouseLeave() {
|
||||||
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false);
|
this.hoverSlice(this.curActiveSlice, this.curActiveSliceIndex, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
import * as Charts from './chart';
|
import * as Charts from "./chart";
|
||||||
|
|
||||||
let frappe = { };
|
let frappe = {};
|
||||||
|
|
||||||
frappe.NAME = 'Frappe Charts';
|
frappe.NAME = "Frappe Charts";
|
||||||
frappe.VERSION = '1.5.5';
|
frappe.VERSION = "1.6.2";
|
||||||
|
|
||||||
frappe = Object.assign({ }, frappe, Charts);
|
frappe = Object.assign({}, frappe, Charts);
|
||||||
|
|
||||||
export default frappe;
|
export default frappe;
|
||||||
|
|||||||
@ -1,19 +1,39 @@
|
|||||||
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 {
|
||||||
import { equilizeNoOfElements } from '../utils/draw-utils';
|
makeText,
|
||||||
import { translateHoriLine, translateVertLine, animateRegion, animateBar,
|
makePath,
|
||||||
animateDot, animatePath, animatePathStr } from '../utils/animate';
|
xLine,
|
||||||
import { getMonthName } from '../utils/date-utils';
|
yLine,
|
||||||
|
generateAxisLabel,
|
||||||
|
yMarker,
|
||||||
|
yRegion,
|
||||||
|
datasetBar,
|
||||||
|
datasetDot,
|
||||||
|
percentageBar,
|
||||||
|
getPaths,
|
||||||
|
heatSquare,
|
||||||
|
} from "../utils/draw";
|
||||||
|
import { equilizeNoOfElements } from "../utils/draw-utils";
|
||||||
|
import {
|
||||||
|
translateHoriLine,
|
||||||
|
translateVertLine,
|
||||||
|
animateRegion,
|
||||||
|
animateBar,
|
||||||
|
animateDot,
|
||||||
|
animatePath,
|
||||||
|
animatePathStr,
|
||||||
|
} from "../utils/animate";
|
||||||
|
import { getMonthName } from "../utils/date-utils";
|
||||||
|
|
||||||
class ChartComponent {
|
class ChartComponent {
|
||||||
constructor({
|
constructor({
|
||||||
layerClass = '',
|
layerClass = "",
|
||||||
layerTransform = '',
|
layerTransform = "",
|
||||||
constants,
|
constants,
|
||||||
|
|
||||||
getData,
|
getData,
|
||||||
makeElements,
|
makeElements,
|
||||||
animateElements
|
animateElements,
|
||||||
}) {
|
}) {
|
||||||
this.layerTransform = layerTransform;
|
this.layerTransform = layerTransform;
|
||||||
this.constants = constants;
|
this.constants = constants;
|
||||||
@ -27,8 +47,10 @@ class ChartComponent {
|
|||||||
this.labels = [];
|
this.labels = [];
|
||||||
|
|
||||||
this.layerClass = layerClass;
|
this.layerClass = layerClass;
|
||||||
this.layerClass = typeof(this.layerClass) === 'function'
|
this.layerClass =
|
||||||
? this.layerClass() : this.layerClass;
|
typeof this.layerClass === "function"
|
||||||
|
? this.layerClass()
|
||||||
|
: this.layerClass;
|
||||||
|
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
@ -49,11 +71,15 @@ class ChartComponent {
|
|||||||
render(data) {
|
render(data) {
|
||||||
this.store = this.makeElements(data);
|
this.store = this.makeElements(data);
|
||||||
|
|
||||||
this.layer.textContent = '';
|
this.layer.textContent = "";
|
||||||
this.store.forEach(element => {
|
this.store.forEach((element) => {
|
||||||
this.layer.appendChild(element);
|
element.length
|
||||||
|
? element.forEach((el) => {
|
||||||
|
this.layer.appendChild(el);
|
||||||
|
})
|
||||||
|
: this.layer.appendChild(element);
|
||||||
});
|
});
|
||||||
this.labels.forEach(element => {
|
this.labels.forEach((element) => {
|
||||||
this.layer.appendChild(element);
|
this.layer.appendChild(element);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -61,7 +87,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;
|
||||||
@ -70,25 +96,17 @@ class ChartComponent {
|
|||||||
|
|
||||||
let componentConfigs = {
|
let componentConfigs = {
|
||||||
donutSlices: {
|
donutSlices: {
|
||||||
layerClass: 'donut-slices',
|
layerClass: "donut-slices",
|
||||||
makeElements(data) {
|
makeElements(data) {
|
||||||
return data.sliceStrings.map((s, i) => {
|
return data.sliceStrings.map((s, i) => {
|
||||||
let slice = makePath(s, 'donut-path', data.colors[i], 'none', data.strokeWidth);
|
let slice = makePath(
|
||||||
slice.style.transition = 'transform .3s;';
|
s,
|
||||||
return slice;
|
"donut-path",
|
||||||
});
|
data.colors[i],
|
||||||
},
|
"none",
|
||||||
|
data.strokeWidth
|
||||||
animateElements(newData) {
|
);
|
||||||
return this.store.map((slice, i) => animatePathStr(slice, newData.sliceStrings[i]));
|
slice.style.transition = "transform .3s;";
|
||||||
},
|
|
||||||
},
|
|
||||||
pieSlices: {
|
|
||||||
layerClass: 'pie-slices',
|
|
||||||
makeElements(data) {
|
|
||||||
return data.sliceStrings.map((s, i) =>{
|
|
||||||
let slice = makePath(s, 'pie-path', 'none', data.colors[i]);
|
|
||||||
slice.style.transition = 'transform .3s;';
|
|
||||||
return slice;
|
return slice;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -97,33 +115,145 @@ let componentConfigs = {
|
|||||||
return this.store.map((slice, i) =>
|
return this.store.map((slice, i) =>
|
||||||
animatePathStr(slice, newData.sliceStrings[i])
|
animatePathStr(slice, newData.sliceStrings[i])
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
pieSlices: {
|
||||||
|
layerClass: "pie-slices",
|
||||||
|
makeElements(data) {
|
||||||
|
return data.sliceStrings.map((s, i) => {
|
||||||
|
let slice = makePath(s, "pie-path", "none", data.colors[i]);
|
||||||
|
slice.style.transition = "transform .3s;";
|
||||||
|
return slice;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
animateElements(newData) {
|
||||||
|
return this.store.map((slice, i) =>
|
||||||
|
animatePathStr(slice, newData.sliceStrings[i])
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
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: {
|
||||||
layerClass: 'y axis',
|
layerClass: "y axis",
|
||||||
makeElements(data) {
|
makeElements(data) {
|
||||||
return data.positions.map((position, i) =>
|
let elements = [];
|
||||||
yLine(position, data.labels[i], this.constants.width,
|
// will loop through each yaxis dataset if it exists
|
||||||
{mode: this.constants.mode, pos: this.constants.pos, shortenNumbers: this.constants.shortenNumbers})
|
if (data.length) {
|
||||||
);
|
data.forEach((item, i) => {
|
||||||
|
item.positions.map((position, i) => {
|
||||||
|
elements.push(
|
||||||
|
yLine(
|
||||||
|
position,
|
||||||
|
item.labels[i],
|
||||||
|
this.constants.width,
|
||||||
|
{
|
||||||
|
mode: this.constants.mode,
|
||||||
|
pos: item.pos || this.constants.pos,
|
||||||
|
shortenNumbers:
|
||||||
|
this.constants.shortenNumbers,
|
||||||
|
title: item.title,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// we need to make yAxis titles if they are defined
|
||||||
|
if (item.title) {
|
||||||
|
elements.push(
|
||||||
|
generateAxisLabel({
|
||||||
|
title: item.title,
|
||||||
|
position: item.pos,
|
||||||
|
height: this.constants.height || data.zeroLine,
|
||||||
|
width: this.constants.width,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.positions.forEach((position, i) => {
|
||||||
|
elements.push(
|
||||||
|
yLine(position, data.labels[i], this.constants.width, {
|
||||||
|
mode: this.constants.mode,
|
||||||
|
pos: data.pos || this.constants.pos,
|
||||||
|
shortenNumbers: this.constants.shortenNumbers,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data.title) {
|
||||||
|
elements.push(
|
||||||
|
generateAxisLabel({
|
||||||
|
title: data.title,
|
||||||
|
position: data.pos,
|
||||||
|
height: this.constants.height || data.zeroLine,
|
||||||
|
width: this.constants.width,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
},
|
},
|
||||||
|
|
||||||
animateElements(newData) {
|
animateElements(newData) {
|
||||||
|
const animateMultipleElements = (oldData, newData) => {
|
||||||
|
let newPos = newData.positions;
|
||||||
|
let newLabels = newData.labels;
|
||||||
|
let oldPos = oldData.positions;
|
||||||
|
let oldLabels = oldData.labels;
|
||||||
|
|
||||||
|
[oldPos, newPos] = equilizeNoOfElements(oldPos, newPos);
|
||||||
|
[oldLabels, newLabels] = equilizeNoOfElements(
|
||||||
|
oldLabels,
|
||||||
|
newLabels
|
||||||
|
);
|
||||||
|
|
||||||
|
this.render({
|
||||||
|
positions: oldPos,
|
||||||
|
labels: newLabels,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.store.map((line, i) => {
|
||||||
|
return translateHoriLine(line, newPos[i], oldPos[i]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// we will need to animate both axis if we have more than one.
|
||||||
|
// so check if the oldData is an array of values.
|
||||||
|
if (this.oldData instanceof Array) {
|
||||||
|
return this.oldData.forEach((old, i) => {
|
||||||
|
animateMultipleElements(old, newData[i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let newPos = newData.positions;
|
let newPos = newData.positions;
|
||||||
let newLabels = newData.labels;
|
let newLabels = newData.labels;
|
||||||
let oldPos = this.oldData.positions;
|
let oldPos = this.oldData.positions;
|
||||||
@ -134,23 +264,23 @@ let componentConfigs = {
|
|||||||
|
|
||||||
this.render({
|
this.render({
|
||||||
positions: oldPos,
|
positions: oldPos,
|
||||||
labels: newLabels
|
labels: newLabels,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.store.map((line, i) => {
|
return this.store.map((line, i) => {
|
||||||
return translateHoriLine(
|
return translateHoriLine(line, newPos[i], oldPos[i]);
|
||||||
line, newPos[i], oldPos[i]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
xAxis: {
|
xAxis: {
|
||||||
layerClass: 'x axis',
|
layerClass: "x axis",
|
||||||
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,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -165,117 +295,144 @@ let componentConfigs = {
|
|||||||
|
|
||||||
this.render({
|
this.render({
|
||||||
positions: oldPos,
|
positions: oldPos,
|
||||||
calcLabels: newLabels
|
calcLabels: newLabels,
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.store.map((line, i) => {
|
return this.store.map((line, i) => {
|
||||||
return translateVertLine(
|
return translateVertLine(line, newPos[i], oldPos[i]);
|
||||||
line, newPos[i], oldPos[i]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
yMarkers: {
|
yMarkers: {
|
||||||
layerClass: 'y-markers',
|
layerClass: "y-markers",
|
||||||
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) {
|
||||||
[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
|
[this.oldData, newData] = equilizeNoOfElements(
|
||||||
|
this.oldData,
|
||||||
|
newData
|
||||||
|
);
|
||||||
|
|
||||||
let newPos = newData.map(d => d.position);
|
let newPos = newData.map((d) => d.position);
|
||||||
let newLabels = newData.map(d => d.label);
|
let newLabels = newData.map((d) => d.label);
|
||||||
let newOptions = newData.map(d => d.options);
|
let newOptions = newData.map((d) => d.options);
|
||||||
|
|
||||||
let oldPos = this.oldData.map(d => d.position);
|
let oldPos = this.oldData.map((d) => d.position);
|
||||||
|
|
||||||
this.render(oldPos.map((pos, i) => {
|
this.render(
|
||||||
return {
|
oldPos.map((pos, i) => {
|
||||||
position: oldPos[i],
|
return {
|
||||||
label: newLabels[i],
|
position: oldPos[i],
|
||||||
options: newOptions[i]
|
label: newLabels[i],
|
||||||
};
|
options: newOptions[i],
|
||||||
}));
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return this.store.map((line, i) => {
|
return this.store.map((line, i) => {
|
||||||
return translateHoriLine(
|
return translateHoriLine(line, newPos[i], oldPos[i]);
|
||||||
line, newPos[i], oldPos[i]
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
yRegions: {
|
yRegions: {
|
||||||
layerClass: 'y-regions',
|
layerClass: "y-regions",
|
||||||
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, {
|
||||||
r.label, {labelPos: r.options.labelPos})
|
labelPos: r.options.labelPos,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
animateElements(newData) {
|
animateElements(newData) {
|
||||||
[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
|
[this.oldData, newData] = equilizeNoOfElements(
|
||||||
|
this.oldData,
|
||||||
|
newData
|
||||||
|
);
|
||||||
|
|
||||||
let newPos = newData.map(d => d.endPos);
|
let newPos = newData.map((d) => d.endPos);
|
||||||
let newLabels = newData.map(d => d.label);
|
let newLabels = newData.map((d) => d.label);
|
||||||
let newStarts = newData.map(d => d.startPos);
|
let newStarts = newData.map((d) => d.startPos);
|
||||||
let newOptions = newData.map(d => d.options);
|
let newOptions = newData.map((d) => d.options);
|
||||||
|
|
||||||
let oldPos = this.oldData.map(d => d.endPos);
|
let oldPos = this.oldData.map((d) => d.endPos);
|
||||||
let oldStarts = this.oldData.map(d => d.startPos);
|
let oldStarts = this.oldData.map((d) => d.startPos);
|
||||||
|
|
||||||
this.render(oldPos.map((pos, i) => {
|
this.render(
|
||||||
return {
|
oldPos.map((pos, i) => {
|
||||||
startPos: oldStarts[i],
|
return {
|
||||||
endPos: oldPos[i],
|
startPos: oldStarts[i],
|
||||||
label: newLabels[i],
|
endPos: oldPos[i],
|
||||||
options: newOptions[i]
|
label: newLabels[i],
|
||||||
};
|
options: newOptions[i],
|
||||||
}));
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
let animateElements = [];
|
let animateElements = [];
|
||||||
|
|
||||||
this.store.map((rectGroup, i) => {
|
this.store.map((rectGroup, i) => {
|
||||||
animateElements = animateElements.concat(animateRegion(
|
animateElements = animateElements.concat(
|
||||||
rectGroup, newStarts[i], newPos[i], oldPos[i]
|
animateRegion(rectGroup, newStarts[i], newPos[i], oldPos[i])
|
||||||
));
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return animateElements;
|
return animateElements;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
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(),
|
||||||
{
|
{
|
||||||
fontSize: 9
|
fontSize: 9,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
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,
|
||||||
'data-day': i
|
"data-day": i,
|
||||||
};
|
};
|
||||||
let square = heatSquare('day', x, y, squareSize, radius, day.fill, data);
|
let square = heatSquare(
|
||||||
|
"day",
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
squareSize,
|
||||||
|
radius,
|
||||||
|
day.fill,
|
||||||
|
data
|
||||||
|
);
|
||||||
this.serializedSubDomains.push(square);
|
this.serializedSubDomains.push(square);
|
||||||
}
|
}
|
||||||
y += rowHeight;
|
y += rowHeight;
|
||||||
@ -288,15 +445,17 @@ 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";
|
||||||
this.units = data.yPositions.map((y, j) => {
|
this.units = data.yPositions.map((y, j) => {
|
||||||
return datasetBar(
|
return datasetBar(
|
||||||
data.xPositions[j],
|
data.xPositions[j],
|
||||||
@ -309,7 +468,7 @@ let componentConfigs = {
|
|||||||
{
|
{
|
||||||
zeroLine: data.zeroLine,
|
zeroLine: data.zeroLine,
|
||||||
barsWidth: data.barsWidth,
|
barsWidth: data.barsWidth,
|
||||||
minHeight: c.minHeight
|
minHeight: c.minHeight,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -328,7 +487,10 @@ let componentConfigs = {
|
|||||||
|
|
||||||
[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
|
[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
|
||||||
[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
|
[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
|
||||||
[oldOffsets, newOffsets] = equilizeNoOfElements(oldOffsets, newOffsets);
|
[oldOffsets, newOffsets] = equilizeNoOfElements(
|
||||||
|
oldOffsets,
|
||||||
|
newOffsets
|
||||||
|
);
|
||||||
[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
|
[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
|
||||||
|
|
||||||
this.render({
|
this.render({
|
||||||
@ -345,23 +507,31 @@ let componentConfigs = {
|
|||||||
let animateElements = [];
|
let animateElements = [];
|
||||||
|
|
||||||
this.store.map((bar, i) => {
|
this.store.map((bar, i) => {
|
||||||
animateElements = animateElements.concat(animateBar(
|
animateElements = animateElements.concat(
|
||||||
bar, newXPos[i], newYPos[i], newData.barWidth, newOffsets[i],
|
animateBar(
|
||||||
{zeroLine: newData.zeroLine}
|
bar,
|
||||||
));
|
newXPos[i],
|
||||||
|
newYPos[i],
|
||||||
|
newData.barWidth,
|
||||||
|
newOffsets[i],
|
||||||
|
{ zeroLine: newData.zeroLine }
|
||||||
|
)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
return animateElements;
|
return animateElements;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
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,
|
||||||
@ -369,24 +539,24 @@ let componentConfigs = {
|
|||||||
{
|
{
|
||||||
heatline: c.heatline,
|
heatline: c.heatline,
|
||||||
regionFill: c.regionFill,
|
regionFill: c.regionFill,
|
||||||
spline: c.spline
|
spline: c.spline,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
svgDefs: c.svgDefs,
|
svgDefs: c.svgDefs,
|
||||||
zeroLine: data.zeroLine
|
zeroLine: data.zeroLine,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.units = [];
|
this.units = [];
|
||||||
if(!c.hideDots) {
|
if (!c.hideDots) {
|
||||||
this.units = data.yPositions.map((y, j) => {
|
this.units = data.yPositions.map((y, j) => {
|
||||||
return datasetDot(
|
return datasetDot(
|
||||||
data.xPositions[j],
|
data.xPositions[j],
|
||||||
y,
|
y,
|
||||||
data.radius,
|
data.radius,
|
||||||
c.color,
|
c.color,
|
||||||
(c.valuesOverPoints ? data.values[j] : ''),
|
c.valuesOverPoints ? data.values[j] : "",
|
||||||
j
|
j
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -418,29 +588,37 @@ 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(
|
||||||
this.paths, newXPos, newYPos, newData.zeroLine, this.constants.spline));
|
animatePath(
|
||||||
|
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(
|
||||||
dot, newXPos[i], newYPos[i]));
|
animateDot(dot, newXPos[i], newYPos[i])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return animateElements;
|
return animateElements;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getComponent(name, constants, getData) {
|
export function getComponent(name, constants, getData) {
|
||||||
let keys = Object.keys(componentConfigs).filter(k => name.includes(k));
|
let keys = Object.keys(componentConfigs).filter((k) => name.includes(k));
|
||||||
let config = componentConfigs[keys[0]];
|
let config = componentConfigs[keys[0]];
|
||||||
Object.assign(config, {
|
Object.assign(config, {
|
||||||
constants: constants,
|
constants: constants,
|
||||||
getData: getData
|
getData: getData,
|
||||||
});
|
});
|
||||||
return new ChartComponent(config);
|
return new ChartComponent(config);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,134 +1,132 @@
|
|||||||
import { $ } from '../utils/dom';
|
import { $ } from "../utils/dom";
|
||||||
import { TOOLTIP_POINTER_TRIANGLE_HEIGHT } from '../utils/constants';
|
import { TOOLTIP_POINTER_TRIANGLE_HEIGHT } from "../utils/constants";
|
||||||
|
|
||||||
export default class SvgTip {
|
export default class SvgTip {
|
||||||
constructor({
|
constructor({ parent = null, colors = [] }) {
|
||||||
parent = null,
|
this.parent = parent;
|
||||||
colors = []
|
this.colors = colors;
|
||||||
}) {
|
this.titleName = "";
|
||||||
this.parent = parent;
|
this.titleValue = "";
|
||||||
this.colors = colors;
|
this.listValues = [];
|
||||||
this.titleName = '';
|
this.titleValueFirst = 0;
|
||||||
this.titleValue = '';
|
|
||||||
this.listValues = [];
|
|
||||||
this.titleValueFirst = 0;
|
|
||||||
|
|
||||||
this.x = 0;
|
this.x = 0;
|
||||||
this.y = 0;
|
this.y = 0;
|
||||||
|
|
||||||
this.top = 0;
|
this.top = 0;
|
||||||
this.left = 0;
|
this.left = 0;
|
||||||
|
|
||||||
this.setup();
|
this.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.makeTooltip();
|
this.makeTooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
this.fill();
|
this.fill();
|
||||||
this.calcPosition();
|
this.calcPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
makeTooltip() {
|
makeTooltip() {
|
||||||
this.container = $.create('div', {
|
this.container = $.create("div", {
|
||||||
inside: this.parent,
|
inside: this.parent,
|
||||||
className: 'graph-svg-tip comparison',
|
className: "graph-svg-tip comparison",
|
||||||
innerHTML: `<span class="title"></span>
|
innerHTML: `<span class="title"></span>
|
||||||
<ul class="data-point-list"></ul>
|
<ul class="data-point-list"></ul>
|
||||||
<div class="svg-pointer"></div>`
|
<div class="svg-pointer"></div>`,
|
||||||
});
|
});
|
||||||
this.hideTip();
|
this.hideTip();
|
||||||
|
|
||||||
this.title = this.container.querySelector('.title');
|
this.title = this.container.querySelector(".title");
|
||||||
this.list = this.container.querySelector('.data-point-list');
|
this.list = this.container.querySelector(".data-point-list");
|
||||||
this.dataPointList = this.container.querySelector('.data-point-list');
|
this.dataPointList = this.container.querySelector(".data-point-list");
|
||||||
|
|
||||||
this.parent.addEventListener('mouseleave', () => {
|
this.parent.addEventListener("mouseleave", () => {
|
||||||
this.hideTip();
|
this.hideTip();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fill() {
|
fill() {
|
||||||
let title;
|
let title;
|
||||||
if(this.index) {
|
if (this.index) {
|
||||||
this.container.setAttribute('data-point-index', this.index);
|
this.container.setAttribute("data-point-index", this.index);
|
||||||
}
|
}
|
||||||
if(this.titleValueFirst) {
|
if (this.titleValueFirst) {
|
||||||
title = `<strong>${this.titleValue}</strong>${this.titleName}`;
|
title = `<strong>${this.titleValue}</strong>${this.titleName}`;
|
||||||
} else {
|
} else {
|
||||||
title = `${this.titleName}<strong>${this.titleValue}</strong>`;
|
title = `${this.titleName}<strong>${this.titleValue}</strong>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.listValues.length > 4) {
|
if (this.listValues.length > 4) {
|
||||||
this.list.classList.add('tooltip-grid');
|
this.list.classList.add("tooltip-grid");
|
||||||
} else {
|
} else {
|
||||||
this.list.classList.remove('tooltip-grid');
|
this.list.classList.remove("tooltip-grid");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.title.innerHTML = title;
|
this.title.innerHTML = title;
|
||||||
this.dataPointList.innerHTML = '';
|
this.dataPointList.innerHTML = "";
|
||||||
|
|
||||||
this.listValues.map((set, i) => {
|
this.listValues.map((set, i) => {
|
||||||
const color = this.colors[i] || 'black';
|
const color = this.colors[i] || "black";
|
||||||
let value = set.formatted === 0 || set.formatted ? set.formatted : set.value;
|
let value =
|
||||||
let li = $.create('li', {
|
set.formatted === 0 || set.formatted ? set.formatted : set.value;
|
||||||
innerHTML: `<div class="tooltip-legend" style="background: ${color};"></div>
|
let li = $.create("li", {
|
||||||
|
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>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dataPointList.appendChild(li);
|
this.dataPointList.appendChild(li);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
calcPosition() {
|
calcPosition() {
|
||||||
let width = this.container.offsetWidth;
|
let width = this.container.offsetWidth;
|
||||||
|
|
||||||
this.top = this.y - this.container.offsetHeight
|
this.top =
|
||||||
- TOOLTIP_POINTER_TRIANGLE_HEIGHT;
|
this.y - this.container.offsetHeight - 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;
|
||||||
|
|
||||||
this.left = maxLeft;
|
this.left = maxLeft;
|
||||||
} else {
|
} else {
|
||||||
pointer.style.left = `50%`;
|
pointer.style.left = `50%`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setValues(x, y, title = {}, listValues = [], index = -1) {
|
setValues(x, y, title = {}, listValues = [], index = -1) {
|
||||||
this.titleName = title.name;
|
this.titleName = title.name;
|
||||||
this.titleValue = title.value;
|
this.titleValue = title.value;
|
||||||
this.listValues = listValues;
|
this.listValues = listValues;
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
this.titleValueFirst = title.valueFirst || 0;
|
this.titleValueFirst = title.valueFirst || 0;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
hideTip() {
|
hideTip() {
|
||||||
this.container.style.top = '0px';
|
this.container.style.top = "0px";
|
||||||
this.container.style.left = '0px';
|
this.container.style.left = "0px";
|
||||||
this.container.style.opacity = '0';
|
this.container.style.opacity = "0";
|
||||||
}
|
}
|
||||||
|
|
||||||
showTip() {
|
showTip() {
|
||||||
this.container.style.top = this.top + 'px';
|
this.container.style.top = this.top + "px";
|
||||||
this.container.style.left = this.left + 'px';
|
this.container.style.left = this.left + "px";
|
||||||
this.container.style.opacity = '1';
|
this.container.style.opacity = "1";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,105 +1,121 @@
|
|||||||
import { getBarHeightAndYAttr, getSplineCurvePointsStr } from './draw-utils';
|
import { getBarHeightAndYAttr, getSplineCurvePointsStr } from "./draw-utils";
|
||||||
|
|
||||||
export const UNIT_ANIM_DUR = 350;
|
export const UNIT_ANIM_DUR = 350;
|
||||||
export const PATH_ANIM_DUR = 350;
|
export const PATH_ANIM_DUR = 350;
|
||||||
export const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
export const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
||||||
export const REPLACE_ALL_NEW_DUR = 250;
|
export const REPLACE_ALL_NEW_DUR = 250;
|
||||||
|
|
||||||
export const STD_EASING = 'easein';
|
export const STD_EASING = "easein";
|
||||||
|
|
||||||
export function translate(unit, oldCoord, newCoord, duration) {
|
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 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function translateVertLine(xLine, newX, oldX) {
|
export function translateVertLine(xLine, newX, oldX) {
|
||||||
return translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR);
|
return translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function translateHoriLine(yLine, newY, oldY) {
|
export function translateHoriLine(yLine, newY, oldY) {
|
||||||
return translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR);
|
return translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function animateRegion(rectGroup, newY1, newY2, oldY2) {
|
export function animateRegion(rectGroup, newY1, newY2, oldY2) {
|
||||||
let newHeight = newY1 - newY2;
|
let newHeight = newY1 - newY2;
|
||||||
let rect = rectGroup.childNodes[0];
|
let rect = rectGroup.childNodes[0];
|
||||||
let width = rect.getAttribute("width");
|
let width = rect.getAttribute("width");
|
||||||
let rectAnim = [
|
let rectAnim = [
|
||||||
rect,
|
rect,
|
||||||
{ height: newHeight, 'stroke-dasharray': `${width}, ${newHeight}` },
|
{ height: newHeight, "stroke-dasharray": `${width}, ${newHeight}` },
|
||||||
MARKER_LINE_ANIM_DUR,
|
MARKER_LINE_ANIM_DUR,
|
||||||
STD_EASING
|
STD_EASING,
|
||||||
];
|
];
|
||||||
|
|
||||||
let groupAnim = translate(rectGroup, [0, oldY2], [0, newY2], MARKER_LINE_ANIM_DUR);
|
let groupAnim = translate(
|
||||||
return [rectAnim, groupAnim];
|
rectGroup,
|
||||||
|
[0, oldY2],
|
||||||
|
[0, newY2],
|
||||||
|
MARKER_LINE_ANIM_DUR
|
||||||
|
);
|
||||||
|
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,
|
||||||
];
|
];
|
||||||
|
|
||||||
let oldCoordStr = bar.getAttribute("transform").split("(")[1].slice(0, -1);
|
let oldCoordStr = bar.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||||
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.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function animatePath(paths, newXList, newYList, zeroLine, spline) {
|
export function animatePath(paths, newXList, newYList, zeroLine, spline) {
|
||||||
let pathComponents = [];
|
let pathComponents = [];
|
||||||
let pointsStr = newYList.map((y, i) => (newXList[i] + ',' + y)).join("L");
|
let pointsStr = newYList.map((y, i) => newXList[i] + "," + y).join("L");
|
||||||
|
|
||||||
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 = [
|
||||||
pathComponents.push(animPath);
|
paths.path,
|
||||||
|
{ d: "M" + pointsStr },
|
||||||
|
PATH_ANIM_DUR,
|
||||||
|
STD_EASING,
|
||||||
|
];
|
||||||
|
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,
|
||||||
];
|
];
|
||||||
pathComponents.push(animRegion);
|
pathComponents.push(animRegion);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pathComponents;
|
return pathComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,116 +1,133 @@
|
|||||||
// Leveraging SMIL Animations
|
// Leveraging SMIL Animations
|
||||||
|
|
||||||
import { REPLACE_ALL_NEW_DUR } from './animate';
|
import { REPLACE_ALL_NEW_DUR } from "./animate";
|
||||||
|
|
||||||
const EASING = {
|
const EASING = {
|
||||||
ease: "0.25 0.1 0.25 1",
|
ease: "0.25 0.1 0.25 1",
|
||||||
linear: "0 0 1 1",
|
linear: "0 0 1 1",
|
||||||
// easein: "0.42 0 1 1",
|
// easein: "0.42 0 1 1",
|
||||||
easein: "0.1 0.8 0.2 1",
|
easein: "0.1 0.8 0.2 1",
|
||||||
easeout: "0 0 0.58 1",
|
easeout: "0 0 0.58 1",
|
||||||
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 newElement = element.cloneNode(true);
|
||||||
|
|
||||||
let animElement = element.cloneNode(true);
|
for (var attributeName in props) {
|
||||||
let newElement = element.cloneNode(true);
|
let animateElement;
|
||||||
|
if (attributeName === "transform") {
|
||||||
|
animateElement = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"animateTransform"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
animateElement = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"animate"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let currentValue =
|
||||||
|
oldValues[attributeName] || element.getAttribute(attributeName);
|
||||||
|
let value = props[attributeName];
|
||||||
|
|
||||||
for(var attributeName in props) {
|
let animAttr = {
|
||||||
let animateElement;
|
attributeName: attributeName,
|
||||||
if(attributeName === 'transform') {
|
from: currentValue,
|
||||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
|
to: value,
|
||||||
} else {
|
begin: "0s",
|
||||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animate");
|
dur: dur / 1000 + "s",
|
||||||
}
|
values: currentValue + ";" + value,
|
||||||
let currentValue = oldValues[attributeName] || element.getAttribute(attributeName);
|
keySplines: EASING[easingType],
|
||||||
let value = props[attributeName];
|
keyTimes: "0;1",
|
||||||
|
calcMode: "spline",
|
||||||
|
fill: "freeze",
|
||||||
|
};
|
||||||
|
|
||||||
let animAttr = {
|
if (type) {
|
||||||
attributeName: attributeName,
|
animAttr["type"] = type;
|
||||||
from: currentValue,
|
}
|
||||||
to: value,
|
|
||||||
begin: "0s",
|
|
||||||
dur: dur/1000 + "s",
|
|
||||||
values: currentValue + ";" + value,
|
|
||||||
keySplines: EASING[easingType],
|
|
||||||
keyTimes: "0;1",
|
|
||||||
calcMode: "spline",
|
|
||||||
fill: 'freeze'
|
|
||||||
};
|
|
||||||
|
|
||||||
if(type) {
|
for (var i in animAttr) {
|
||||||
animAttr["type"] = type;
|
animateElement.setAttribute(i, animAttr[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i in animAttr) {
|
animElement.appendChild(animateElement);
|
||||||
animateElement.setAttribute(i, animAttr[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
animElement.appendChild(animateElement);
|
if (type) {
|
||||||
|
newElement.setAttribute(attributeName, `translate(${value})`);
|
||||||
|
} else {
|
||||||
|
newElement.setAttribute(attributeName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(type) {
|
return [animElement, newElement];
|
||||||
newElement.setAttribute(attributeName, `translate(${value})`);
|
|
||||||
} else {
|
|
||||||
newElement.setAttribute(attributeName, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [animElement, newElement];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transform(element, style) { // eslint-disable-line no-unused-vars
|
export function transform(element, style) {
|
||||||
element.style.transform = style;
|
// eslint-disable-line no-unused-vars
|
||||||
element.style.webkitTransform = style;
|
element.style.transform = style;
|
||||||
element.style.msTransform = style;
|
element.style.webkitTransform = style;
|
||||||
element.style.mozTransform = style;
|
element.style.msTransform = style;
|
||||||
element.style.oTransform = style;
|
element.style.mozTransform = style;
|
||||||
|
element.style.oTransform = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
function animateSVG(svgContainer, elements) {
|
function animateSVG(svgContainer, elements) {
|
||||||
let newElements = [];
|
let newElements = [];
|
||||||
let animElements = [];
|
let animElements = [];
|
||||||
|
|
||||||
elements.map(element => {
|
elements.map((element) => {
|
||||||
let unit = element[0];
|
let unit = element[0];
|
||||||
let parent = unit.parentNode;
|
let parent = unit.parentNode;
|
||||||
|
|
||||||
let animElement, newElement;
|
let animElement, newElement;
|
||||||
|
|
||||||
element[0] = unit;
|
element[0] = unit;
|
||||||
[animElement, newElement] = animateSVGElement(...element);
|
[animElement, newElement] = animateSVGElement(...element);
|
||||||
|
|
||||||
newElements.push(newElement);
|
newElements.push(newElement);
|
||||||
animElements.push([animElement, parent]);
|
animElements.push([animElement, parent]);
|
||||||
|
|
||||||
parent.replaceChild(animElement, unit);
|
if (parent) {
|
||||||
});
|
parent.replaceChild(animElement, unit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let animSvg = svgContainer.cloneNode(true);
|
let animSvg = svgContainer.cloneNode(true);
|
||||||
|
|
||||||
animElements.map((animElement, i) => {
|
animElements.map((animElement, i) => {
|
||||||
animElement[1].replaceChild(newElements[i], animElement[0]);
|
if (animElement[1]) {
|
||||||
elements[i][0] = newElements[i];
|
animElement[1].replaceChild(newElements[i], animElement[0]);
|
||||||
});
|
elements[i][0] = newElements[i];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return animSvg;
|
return animSvg;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
// Replace the new svgElement (data has already been replaced)
|
||||||
|
setTimeout(() => {
|
||||||
// Replace the new svgElement (data has already been replaced)
|
if (animSvgElement.parentNode == parent) {
|
||||||
setTimeout(() => {
|
parent.removeChild(animSvgElement);
|
||||||
if(animSvgElement.parentNode == parent) {
|
parent.appendChild(svgElement);
|
||||||
parent.removeChild(animSvgElement);
|
}
|
||||||
parent.appendChild(svgElement);
|
}, REPLACE_ALL_NEW_DUR);
|
||||||
}
|
|
||||||
}, REPLACE_ALL_NEW_DUR);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
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, config) {
|
||||||
data.labels = data.labels || [];
|
data.labels = data.labels || [];
|
||||||
|
|
||||||
let datasetLength = data.labels.length;
|
let datasetLength = data.labels.length;
|
||||||
@ -9,48 +14,51 @@ 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
|
||||||
let vals = d.values;
|
let vals = d.values;
|
||||||
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);
|
||||||
|
}
|
||||||
|
if (config) {
|
||||||
|
vals = fillArray(vals, datasetLength - vals.length, null);
|
||||||
} 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Markers
|
// Markers
|
||||||
|
|
||||||
// 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];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -65,61 +73,62 @@ export function zeroDataPrep(realData) {
|
|||||||
|
|
||||||
let zeroData = {
|
let zeroData = {
|
||||||
labels: realData.labels.slice(0, -1),
|
labels: realData.labels.slice(0, -1),
|
||||||
datasets: realData.datasets.map(d => {
|
datasets: realData.datasets.map((d) => {
|
||||||
|
const { axisID } = d;
|
||||||
return {
|
return {
|
||||||
name: '',
|
axisID,
|
||||||
|
name: "",
|
||||||
values: zeroArray.slice(0, -1),
|
values: zeroArray.slice(0, -1),
|
||||||
chartType: d.chartType
|
chartType: d.chartType,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
if(realData.yMarkers) {
|
if (realData.yMarkers) {
|
||||||
zeroData.yMarkers = [
|
zeroData.yMarkers = [
|
||||||
{
|
{
|
||||||
value: 0,
|
value: 0,
|
||||||
label: ''
|
label: "",
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(realData.yRegions) {
|
if (realData.yRegions) {
|
||||||
zeroData.yRegions = [
|
zeroData.yRegions = [
|
||||||
{
|
{
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
label: ''
|
label: "",
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return zeroData;
|
return zeroData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShortenedLabels(chartWidth, labels=[], isSeries=true) {
|
export function getShortenedLabels(chartWidth, labels = [], isSeries = true) {
|
||||||
let allowedSpace = chartWidth / labels.length;
|
let allowedSpace = (chartWidth / labels.length) * 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 && i !== labels.length - 1) {
|
||||||
label = "";
|
label = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,59 +1,61 @@
|
|||||||
const PRESET_COLOR_MAP = {
|
const PRESET_COLOR_MAP = {
|
||||||
'pink': '#F683AE',
|
pink: "#F683AE",
|
||||||
'blue': '#318AD8',
|
blue: "#318AD8",
|
||||||
'green': '#48BB74',
|
green: "#48BB74",
|
||||||
'grey': '#A6B1B9',
|
grey: "#A6B1B9",
|
||||||
'red': '#F56B6B',
|
red: "#F56B6B",
|
||||||
'yellow': '#FACF7A',
|
yellow: "#FACF7A",
|
||||||
'purple': '#44427B',
|
purple: "#44427B",
|
||||||
'teal': '#5FD8C4',
|
teal: "#5FD8C4",
|
||||||
'cyan': '#15CCEF',
|
cyan: "#15CCEF",
|
||||||
'orange': '#F8814F',
|
orange: "#F8814F",
|
||||||
'light-pink': '#FED7E5',
|
"light-pink": "#FED7E5",
|
||||||
'light-blue': '#BFDDF7',
|
"light-blue": "#BFDDF7",
|
||||||
'light-green': '#48BB74',
|
"light-green": "#48BB74",
|
||||||
'light-grey': '#F4F5F6',
|
"light-grey": "#F4F5F6",
|
||||||
'light-red': '#F6DFDF',
|
"light-red": "#F6DFDF",
|
||||||
'light-yellow': '#FEE9BF',
|
"light-yellow": "#FEE9BF",
|
||||||
'light-purple': '#E8E8F7',
|
"light-purple": "#E8E8F7",
|
||||||
'light-teal': '#D3FDF6',
|
"light-teal": "#D3FDF6",
|
||||||
'light-cyan': '#DDF8FD',
|
"light-cyan": "#DDF8FD",
|
||||||
'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;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function lightenDarkenColor(color, amt) {
|
export function lightenDarkenColor(color, amt) {
|
||||||
let col = getColor(color);
|
let col = getColor(color);
|
||||||
let usePound = false;
|
let usePound = false;
|
||||||
if (col[0] == "#") {
|
if (col[0] == "#") {
|
||||||
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) {
|
||||||
// https://stackoverflow.com/a/32685393
|
// https://stackoverflow.com/a/32685393
|
||||||
let HEX_RE = /(^\s*)(#)((?:[A-Fa-f0-9]{3}){1,2})$/i;
|
let HEX_RE = /(^\s*)(#)((?:[A-Fa-f0-9]{3}){1,2})$/i;
|
||||||
let RGB_RE = /(^\s*)(rgb|hsl)(a?)[(]\s*([\d.]+\s*%?)\s*,\s*([\d.]+\s*%?)\s*,\s*([\d.]+\s*%?)\s*(?:,\s*([\d.]+)\s*)?[)]$/i;
|
let RGB_RE =
|
||||||
return HEX_RE.test(string) || RGB_RE.test(string);
|
/(^\s*)(rgb|hsl)(a?)[(]\s*([\d.]+\s*%?)\s*,\s*([\d.]+\s*%?)\s*,\s*([\d.]+\s*%?)\s*(?:,\s*([\d.]+)\s*)?[)]$/i;
|
||||||
|
return HEX_RE.test(string) || RGB_RE.test(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getColor = (color) => {
|
export const getColor = (color) => {
|
||||||
// When RGB color, convert to hexadecimal (alpha value is omitted)
|
// When RGB color, convert to hexadecimal (alpha value is omitted)
|
||||||
if((/rgb[a]{0,1}\([\d, ]+\)/gim).test(color)) {
|
if (/rgb[a]{0,1}\([\d, ]+\)/gim.test(color)) {
|
||||||
return (/\D+(\d*)\D+(\d*)\D+(\d*)/gim).exec(color)
|
return /\D+(\d*)\D+(\d*)\D+(\d*)/gim
|
||||||
.map((x, i) => (i !== 0 ? Number(x).toString(16) : '#'))
|
.exec(color)
|
||||||
.reduce((c, ch) => `${c}${ch}`);
|
.map((x, i) => (i !== 0 ? Number(x).toString(16) : "#"))
|
||||||
}
|
.reduce((c, ch) => `${c}${ch}`);
|
||||||
return PRESET_COLOR_MAP[color] || color;
|
}
|
||||||
|
return PRESET_COLOR_MAP[color] || color;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,80 +1,91 @@
|
|||||||
export const ALL_CHART_TYPES = ['line', 'scatter', 'bar', 'percentage', 'heatmap', 'pie'];
|
export const ALL_CHART_TYPES = [
|
||||||
|
"line",
|
||||||
|
"scatter",
|
||||||
|
"bar",
|
||||||
|
"percentage",
|
||||||
|
"heatmap",
|
||||||
|
"pie",
|
||||||
|
];
|
||||||
|
|
||||||
export const COMPATIBLE_CHARTS = {
|
export const COMPATIBLE_CHARTS = {
|
||||||
bar: ['line', 'scatter', 'percentage', 'pie'],
|
bar: ["line", "scatter", "percentage", "pie"],
|
||||||
line: ['scatter', 'bar', 'percentage', 'pie'],
|
line: ["scatter", "bar", "percentage", "pie"],
|
||||||
pie: ['line', 'scatter', 'percentage', 'bar'],
|
pie: ["line", "scatter", "percentage", "bar"],
|
||||||
percentage: ['bar', 'line', 'scatter', 'pie'],
|
percentage: ["bar", "line", "scatter", "pie"],
|
||||||
heatmap: []
|
heatmap: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DATA_COLOR_DIVISIONS = {
|
export const DATA_COLOR_DIVISIONS = {
|
||||||
bar: 'datasets',
|
bar: "datasets",
|
||||||
line: 'datasets',
|
line: "datasets",
|
||||||
pie: 'labels',
|
pie: "labels",
|
||||||
percentage: 'labels',
|
percentage: "labels",
|
||||||
heatmap: HEATMAP_DISTRIBUTION_SIZE
|
heatmap: HEATMAP_DISTRIBUTION_SIZE,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BASE_MEASURES = {
|
export const BASE_MEASURES = {
|
||||||
margins: {
|
margins: {
|
||||||
top: 10,
|
top: 10,
|
||||||
bottom: 10,
|
bottom: 10,
|
||||||
left: 20,
|
left: 20,
|
||||||
right: 20
|
right: 20,
|
||||||
},
|
},
|
||||||
paddings: {
|
paddings: {
|
||||||
top: 20,
|
top: 20,
|
||||||
bottom: 40,
|
bottom: 40,
|
||||||
left: 30,
|
left: 30,
|
||||||
right: 10
|
right: 10,
|
||||||
},
|
},
|
||||||
|
|
||||||
baseHeight: 240,
|
baseHeight: 240,
|
||||||
titleHeight: 20,
|
titleHeight: 20,
|
||||||
legendHeight: 30,
|
legendHeight: 30,
|
||||||
|
|
||||||
titleFontSize: 12,
|
titleFontSize: 12,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getTopOffset(m) {
|
export function getTopOffset(m) {
|
||||||
return m.titleHeight + m.margins.top + m.paddings.top;
|
return m.titleHeight + m.margins.top + m.paddings.top;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getLeftOffset(m) {
|
export function getLeftOffset(m) {
|
||||||
return m.margins.left + m.paddings.left;
|
return m.margins.left + m.paddings.left;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExtraHeight(m) {
|
export function getExtraHeight(m) {
|
||||||
let totalExtraHeight = m.margins.top + m.margins.bottom
|
let totalExtraHeight =
|
||||||
+ m.paddings.top + m.paddings.bottom
|
m.margins.top +
|
||||||
+ m.titleHeight + m.legendHeight;
|
m.margins.bottom +
|
||||||
return totalExtraHeight;
|
m.paddings.top +
|
||||||
|
m.paddings.bottom +
|
||||||
|
m.titleHeight +
|
||||||
|
m.legendHeight;
|
||||||
|
return totalExtraHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getExtraWidth(m) {
|
export function getExtraWidth(m) {
|
||||||
let totalExtraWidth = m.margins.left + m.margins.right
|
let totalExtraWidth =
|
||||||
+ m.paddings.left + m.paddings.right;
|
m.margins.left + m.margins.right + m.paddings.left + m.paddings.right;
|
||||||
|
|
||||||
return totalExtraWidth;
|
return totalExtraWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const INIT_CHART_UPDATE_TIMEOUT = 700;
|
export const INIT_CHART_UPDATE_TIMEOUT = 700;
|
||||||
export const CHART_POST_ANIMATE_TIMEOUT = 400;
|
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.0;
|
||||||
|
|
||||||
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
|
||||||
@ -86,18 +97,47 @@ export const HEATMAP_GUTTER_SIZE = 2;
|
|||||||
export const DEFAULT_CHAR_WIDTH = 7;
|
export const DEFAULT_CHAR_WIDTH = 7;
|
||||||
|
|
||||||
export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 7.48;
|
export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 7.48;
|
||||||
const DEFAULT_CHART_COLORS = ['pink', 'blue', 'green', 'grey', 'red', 'yellow', 'purple', 'teal', 'cyan', 'orange'];
|
const DEFAULT_CHART_COLORS = [
|
||||||
const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
"pink",
|
||||||
export const HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'];
|
"blue",
|
||||||
export const HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
"green",
|
||||||
|
"grey",
|
||||||
|
"red",
|
||||||
|
"yellow",
|
||||||
|
"purple",
|
||||||
|
"teal",
|
||||||
|
"cyan",
|
||||||
|
"orange",
|
||||||
|
];
|
||||||
|
const HEATMAP_COLORS_GREEN = [
|
||||||
|
"#ebedf0",
|
||||||
|
"#c6e48b",
|
||||||
|
"#7bc96f",
|
||||||
|
"#239a3b",
|
||||||
|
"#196127",
|
||||||
|
];
|
||||||
|
export const HEATMAP_COLORS_BLUE = [
|
||||||
|
"#ebedf0",
|
||||||
|
"#c0ddf9",
|
||||||
|
"#73b3f3",
|
||||||
|
"#3886e1",
|
||||||
|
"#17459e",
|
||||||
|
];
|
||||||
|
export const HEATMAP_COLORS_YELLOW = [
|
||||||
|
"#ebedf0",
|
||||||
|
"#fdf436",
|
||||||
|
"#ffc700",
|
||||||
|
"#ff9100",
|
||||||
|
"#06001c",
|
||||||
|
];
|
||||||
|
|
||||||
export const DEFAULT_COLORS = {
|
export const DEFAULT_COLORS = {
|
||||||
bar: DEFAULT_CHART_COLORS,
|
bar: DEFAULT_CHART_COLORS,
|
||||||
line: DEFAULT_CHART_COLORS,
|
line: DEFAULT_CHART_COLORS,
|
||||||
pie: DEFAULT_CHART_COLORS,
|
pie: DEFAULT_CHART_COLORS,
|
||||||
percentage: DEFAULT_CHART_COLORS,
|
percentage: DEFAULT_CHART_COLORS,
|
||||||
heatmap: HEATMAP_COLORS_GREEN,
|
heatmap: HEATMAP_COLORS_GREEN,
|
||||||
donut: DEFAULT_CHART_COLORS
|
donut: DEFAULT_CHART_COLORS,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Universal constants
|
// Universal constants
|
||||||
|
|||||||
@ -6,85 +6,132 @@ export const DAYS_IN_YEAR = 375;
|
|||||||
export const NO_OF_MILLIS = 1000;
|
export const NO_OF_MILLIS = 1000;
|
||||||
export const SEC_IN_DAY = 86400;
|
export const SEC_IN_DAY = 86400;
|
||||||
|
|
||||||
export const MONTH_NAMES = ["January", "February", "March", "April", "May",
|
export const MONTH_NAMES = [
|
||||||
"June", "July", "August", "September", "October", "November", "December"];
|
"January",
|
||||||
export const MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
"February",
|
||||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
];
|
||||||
|
export const MONTH_NAMES_SHORT = [
|
||||||
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Dec",
|
||||||
|
];
|
||||||
|
|
||||||
export const DAY_NAMES_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
export const DAY_NAMES_SHORT = [
|
||||||
export const DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday",
|
"Sun",
|
||||||
"Thursday", "Friday", "Saturday"];
|
"Mon",
|
||||||
|
"Tue",
|
||||||
|
"Wed",
|
||||||
|
"Thu",
|
||||||
|
"Fri",
|
||||||
|
"Sat",
|
||||||
|
];
|
||||||
|
export const DAY_NAMES = [
|
||||||
|
"Sunday",
|
||||||
|
"Monday",
|
||||||
|
"Tuesday",
|
||||||
|
"Wednesday",
|
||||||
|
"Thursday",
|
||||||
|
"Friday",
|
||||||
|
"Saturday",
|
||||||
|
];
|
||||||
|
|
||||||
// https://stackoverflow.com/a/11252167/6495043
|
// https://stackoverflow.com/a/11252167/6495043
|
||||||
function treatAsUtc(date) {
|
function treatAsUtc(date) {
|
||||||
let result = new Date(date);
|
let result = new Date(date);
|
||||||
result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
|
result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toMidnightUTC(date) {
|
||||||
|
let result = new Date(date);
|
||||||
|
result.setUTCHours(0, result.getTimezoneOffset(), 0, 0);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getYyyyMmDd(date) {
|
export function getYyyyMmDd(date) {
|
||||||
let dd = date.getDate();
|
let dd = date.getDate();
|
||||||
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("-");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clone(date) {
|
export function clone(date) {
|
||||||
return new Date(date.getTime());
|
return new Date(date.getTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function getMonthsBetween(startDate, endDate) {}
|
// export function getMonthsBetween(startDate, endDate) {}
|
||||||
|
|
||||||
export function getWeeksBetween(startDate, endDate) {
|
export function getWeeksBetween(startDate, endDate) {
|
||||||
let weekStartDate = setDayToSunday(startDate);
|
let weekStartDate = setDayToSunday(startDate);
|
||||||
return Math.ceil(getDaysBetween(weekStartDate, endDate) / NO_OF_DAYS_IN_WEEK);
|
return Math.ceil(getDaysBetween(weekStartDate, endDate) / NO_OF_DAYS_IN_WEEK);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDaysBetween(startDate, endDate) {
|
export function getDaysBetween(startDate, endDate) {
|
||||||
let millisecondsPerDay = SEC_IN_DAY * NO_OF_MILLIS;
|
let millisecondsPerDay = SEC_IN_DAY * NO_OF_MILLIS;
|
||||||
return (treatAsUtc(endDate) - treatAsUtc(startDate)) / millisecondsPerDay;
|
return (treatAsUtc(endDate) - treatAsUtc(startDate)) / millisecondsPerDay;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function areInSameMonth(startDate, endDate) {
|
export function areInSameMonth(startDate, endDate) {
|
||||||
return startDate.getMonth() === endDate.getMonth()
|
return (
|
||||||
&& startDate.getFullYear() === endDate.getFullYear();
|
startDate.getMonth() === endDate.getMonth() &&
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// mutates
|
// mutates
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mutates
|
// mutates
|
||||||
export function addDays(date, numberOfDays) {
|
export function addDays(date, numberOfDays) {
|
||||||
date.setDate(date.getDate() + numberOfDays);
|
date.setDate(date.getDate() + numberOfDays);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,137 +1,149 @@
|
|||||||
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;
|
i++;
|
||||||
i++;
|
}
|
||||||
}
|
return i;
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$.create = (tag, o) => {
|
$.create = (tag, o) => {
|
||||||
var element = document.createElement(tag);
|
var element = document.createElement(tag);
|
||||||
|
|
||||||
for (var i in o) {
|
for (var i in o) {
|
||||||
var val = o[i];
|
var val = o[i];
|
||||||
|
|
||||||
if (i === "inside") {
|
if (i === "inside") {
|
||||||
$(val).appendChild(element);
|
$(val).appendChild(element);
|
||||||
}
|
} else if (i === "around") {
|
||||||
else if (i === "around") {
|
var ref = $(val);
|
||||||
var ref = $(val);
|
ref.parentNode.insertBefore(element, ref);
|
||||||
ref.parentNode.insertBefore(element, ref);
|
element.appendChild(ref);
|
||||||
element.appendChild(ref);
|
} else if (i === "styles") {
|
||||||
|
if (typeof val === "object") {
|
||||||
|
Object.keys(val).map((prop) => {
|
||||||
|
element.style[prop] = val[prop];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (i in element) {
|
||||||
|
element[i] = val;
|
||||||
|
} else {
|
||||||
|
element.setAttribute(i, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if (i === "styles") {
|
return element;
|
||||||
if(typeof val === "object") {
|
|
||||||
Object.keys(val).map(prop => {
|
|
||||||
element.style[prop] = val[prop];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (i in element ) {
|
|
||||||
element[i] = val;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
element.setAttribute(i, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getOffset(element) {
|
export function getOffset(element) {
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
return {
|
return {
|
||||||
// https://stackoverflow.com/a/7436602/6495043
|
// https://stackoverflow.com/a/7436602/6495043
|
||||||
// rect.top varies with scroll, so we add whatever has been
|
// rect.top varies with scroll, so we add whatever has been
|
||||||
// scrolled to it to get absolute distance from actual page top
|
// scrolled to it to get absolute distance from actual page top
|
||||||
top: rect.top + (document.documentElement.scrollTop || document.body.scrollTop),
|
top:
|
||||||
left: rect.left + (document.documentElement.scrollLeft || document.body.scrollLeft)
|
rect.top +
|
||||||
};
|
(document.documentElement.scrollTop || document.body.scrollTop),
|
||||||
|
left:
|
||||||
|
rect.left +
|
||||||
|
(document.documentElement.scrollLeft || document.body.scrollLeft),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
|
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
|
||||||
// an element's offsetParent property will return null whenever it, or any of its parents,
|
// an element's offsetParent property will return null whenever it, or any of its parents,
|
||||||
// is hidden via the display style property.
|
// is hidden via the display style property.
|
||||||
export function isHidden(el) {
|
export function isHidden(el) {
|
||||||
return (el.offsetParent === null);
|
return el.offsetParent === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isElementInViewport(el) {
|
export function isElementInViewport(el) {
|
||||||
// Although straightforward: https://stackoverflow.com/a/7557433/6495043
|
// Although straightforward: https://stackoverflow.com/a/7557433/6495043
|
||||||
var rect = el.getBoundingClientRect();
|
var rect = el.getBoundingClientRect();
|
||||||
|
|
||||||
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 <=
|
||||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
|
(window.innerHeight ||
|
||||||
);
|
document.documentElement.clientHeight) /*or $(window).height() */ &&
|
||||||
|
rect.right <=
|
||||||
|
(window.innerWidth ||
|
||||||
|
document.documentElement.clientWidth) /*or $(window).width() */
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getElementContentWidth(element) {
|
export function getElementContentWidth(element) {
|
||||||
var styles = window.getComputedStyle(element);
|
var styles = window.getComputedStyle(element);
|
||||||
var padding = parseFloat(styles.paddingLeft) +
|
var padding =
|
||||||
parseFloat(styles.paddingRight);
|
parseFloat(styles.paddingLeft) + parseFloat(styles.paddingRight);
|
||||||
|
|
||||||
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];
|
||||||
|
|
||||||
event.split(/\s+/).forEach(function (event) {
|
event.split(/\s+/).forEach(function (event) {
|
||||||
element.addEventListener(event, callback);
|
element.addEventListener(event, callback);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
return target.dispatchEvent(evt);
|
return target.dispatchEvent(evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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(
|
||||||
let $children = $parent.querySelectorAll(`.${commonClass}.${activeClass}`);
|
$parent,
|
||||||
|
$child,
|
||||||
|
commonClass,
|
||||||
|
activeClass = "active",
|
||||||
|
index = -1
|
||||||
|
) {
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
$child.classList.add(activeClass);
|
$child.classList.add(activeClass);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,99 +1,103 @@
|
|||||||
import { fillArray } from './helpers';
|
import { fillArray } from "./helpers";
|
||||||
|
|
||||||
export function getBarHeightAndYAttr(yTop, zeroLine) {
|
export function getBarHeightAndYAttr(yTop, zeroLine) {
|
||||||
let height, y;
|
let height, y;
|
||||||
if (yTop <= zeroLine) {
|
if (yTop <= zeroLine) {
|
||||||
height = zeroLine - yTop;
|
height = zeroLine - yTop;
|
||||||
y = yTop;
|
y = yTop;
|
||||||
} else {
|
} else {
|
||||||
height = yTop - zeroLine;
|
height = yTop - zeroLine;
|
||||||
y = zeroLine;
|
y = zeroLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [height, y];
|
return [height, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function equilizeNoOfElements(array1, array2,
|
export function equilizeNoOfElements(
|
||||||
extraCount = array2.length - array1.length) {
|
array1,
|
||||||
|
array2,
|
||||||
// Doesn't work if either has zero elements.
|
extraCount = array2.length - array1.length
|
||||||
if(extraCount > 0) {
|
) {
|
||||||
array1 = fillArray(array1, extraCount);
|
// Doesn't work if either has zero elements.
|
||||||
} else {
|
if (extraCount > 0) {
|
||||||
array2 = fillArray(array2, extraCount);
|
array1 = fillArray(array1, extraCount);
|
||||||
}
|
} else {
|
||||||
return [array1, array2];
|
array2 = fillArray(array2, extraCount);
|
||||||
|
}
|
||||||
|
return [array1, array2];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function truncateString(txt, len) {
|
export function truncateString(txt, len) {
|
||||||
if (!txt) {
|
if (!txt) {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shortenLargeNumber(label) {
|
export function shortenLargeNumber(label) {
|
||||||
let number;
|
let number;
|
||||||
if (typeof label === 'number') number = label;
|
if (typeof label === "number") number = label;
|
||||||
else if (typeof label === 'string') {
|
else if (typeof label === "string") {
|
||||||
number = Number(label);
|
number = Number(label);
|
||||||
if (Number.isNaN(number)) return label;
|
if (Number.isNaN(number)) return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using absolute since log wont work for negative numbers
|
// Using absolute since log wont work for negative numbers
|
||||||
let p = Math.floor(Math.log10(Math.abs(number)));
|
let p = Math.floor(Math.log10(Math.abs(number)));
|
||||||
if (p <= 2) return number; // Return as is for a 3 digit number of less
|
if (p <= 2) return number; // Return as is for a 3 digit number of less
|
||||||
let l = Math.floor(p / 3);
|
let l = Math.floor(p / 3);
|
||||||
let shortened = (Math.pow(10, p - l * 3) * +(number / Math.pow(10, p)).toFixed(1));
|
let shortened =
|
||||||
|
Math.pow(10, p - l * 3) * +(number / Math.pow(10, p)).toFixed(1);
|
||||||
|
|
||||||
// Correct for floating point error upto 2 decimal places
|
// Correct for floating point error upto 2 decimal places
|
||||||
return Math.round(shortened*100)/100 + ' ' + ['', 'K', 'M', 'B', 'T'][l];
|
return Math.round(shortened * 100) / 100 + " " + ["", "K", "M", "B", "T"][l];
|
||||||
}
|
}
|
||||||
|
|
||||||
// cubic bezier curve calculation (from example by François Romain)
|
// cubic bezier curve calculation (from example by François Romain)
|
||||||
export function getSplineCurvePointsStr(xList, yList) {
|
export function getSplineCurvePointsStr(xList, yList) {
|
||||||
|
let points = [];
|
||||||
|
for (let i = 0; i < xList.length; i++) {
|
||||||
|
points.push([xList[i], yList[i]]);
|
||||||
|
}
|
||||||
|
|
||||||
let points=[];
|
let smoothing = 0.2;
|
||||||
for(let i=0;i<xList.length;i++){
|
let line = (pointA, pointB) => {
|
||||||
points.push([xList[i], yList[i]]);
|
let lengthX = pointB[0] - pointA[0];
|
||||||
}
|
let lengthY = pointB[1] - pointA[1];
|
||||||
|
return {
|
||||||
|
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
|
||||||
|
angle: Math.atan2(lengthY, lengthX),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
let smoothing = 0.2;
|
let controlPoint = (current, previous, next, reverse) => {
|
||||||
let line = (pointA, pointB) => {
|
let p = previous || current;
|
||||||
let lengthX = pointB[0] - pointA[0];
|
let n = next || current;
|
||||||
let lengthY = pointB[1] - pointA[1];
|
let o = line(p, n);
|
||||||
return {
|
let angle = o.angle + (reverse ? Math.PI : 0);
|
||||||
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
|
let length = o.length * smoothing;
|
||||||
angle: Math.atan2(lengthY, lengthX)
|
let x = current[0] + Math.cos(angle) * length;
|
||||||
};
|
let y = current[1] + Math.sin(angle) * length;
|
||||||
};
|
return [x, y];
|
||||||
|
};
|
||||||
let controlPoint = (current, previous, next, reverse) => {
|
|
||||||
let p = previous || current;
|
let bezierCommand = (point, i, a) => {
|
||||||
let n = next || current;
|
let cps = controlPoint(a[i - 1], a[i - 2], point);
|
||||||
let o = line(p, n);
|
let cpe = controlPoint(point, a[i - 1], a[i + 1], true);
|
||||||
let angle = o.angle + (reverse ? Math.PI : 0);
|
return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`;
|
||||||
let length = o.length * smoothing;
|
};
|
||||||
let x = current[0] + Math.cos(angle) * length;
|
|
||||||
let y = current[1] + Math.sin(angle) * length;
|
let pointStr = (points, command) => {
|
||||||
return [x, y];
|
return points.reduce(
|
||||||
};
|
(acc, point, i, a) =>
|
||||||
|
i === 0 ? `${point[0]},${point[1]}` : `${acc} ${command(point, i, a)}`,
|
||||||
let bezierCommand = (point, i, a) => {
|
""
|
||||||
let cps = controlPoint(a[i - 1], a[i - 2], point);
|
);
|
||||||
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 pointStr(points, bezierCommand);
|
||||||
|
|
||||||
let pointStr = (points, command) => {
|
|
||||||
return points.reduce((acc, point, i, a) => i === 0
|
|
||||||
? `${point[0]},${point[1]}`
|
|
||||||
: `${acc} ${command(point, i, a)}`, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
return pointStr(points, bezierCommand);
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,33 +1,33 @@
|
|||||||
import { $ } from '../utils/dom';
|
import { $ } from "../utils/dom";
|
||||||
import { CSSTEXT } from '../../css/chartsCss';
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function prepareForExport(svg) {
|
export function prepareForExport(svg) {
|
||||||
let clone = svg.cloneNode(true);
|
let clone = svg.cloneNode(true);
|
||||||
clone.classList.add('chart-container');
|
clone.classList.add("chart-container");
|
||||||
clone.setAttribute('xmlns', "http://www.w3.org/2000/svg");
|
clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
||||||
clone.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
|
clone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
||||||
let styleEl = $.create('style', {
|
let styleEl = $.create("style", {
|
||||||
'innerHTML': CSSTEXT
|
innerHTML: CSSTEXT,
|
||||||
});
|
});
|
||||||
clone.insertBefore(styleEl, clone.firstChild);
|
clone.insertBefore(styleEl, clone.firstChild);
|
||||||
|
|
||||||
let container = $.create('div');
|
let container = $.create("div");
|
||||||
container.appendChild(clone);
|
container.appendChild(clone);
|
||||||
|
|
||||||
return container.innerHTML;
|
return container.innerHTML;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ANGLE_RATIO } from './constants';
|
import { ANGLE_RATIO } from "./constants";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value of a number upto 2 decimal places.
|
* Returns the value of a number upto 2 decimal places.
|
||||||
@ -14,10 +14,10 @@ export function floatTwo(d) {
|
|||||||
* @param {Array} arr2 Second array
|
* @param {Array} arr2 Second array
|
||||||
*/
|
*/
|
||||||
export function arraysEqual(arr1, arr2) {
|
export function arraysEqual(arr1, arr2) {
|
||||||
if(arr1.length !== arr2.length) return false;
|
if (arr1.length !== arr2.length) return false;
|
||||||
let areEqual = true;
|
let areEqual = true;
|
||||||
arr1.map((d, i) => {
|
arr1.map((d, i) => {
|
||||||
if(arr2[i] !== d) areEqual = false;
|
if (arr2[i] !== d) areEqual = false;
|
||||||
});
|
});
|
||||||
return areEqual;
|
return areEqual;
|
||||||
}
|
}
|
||||||
@ -46,8 +46,8 @@ export function shuffle(array) {
|
|||||||
* @param {Object} element element to fill with
|
* @param {Object} element element to fill with
|
||||||
* @param {Boolean} start fill at start?
|
* @param {Boolean} start fill at start?
|
||||||
*/
|
*/
|
||||||
export function fillArray(array, count, element, start=false) {
|
export function fillArray(array, count, element, start = false) {
|
||||||
if(!element) {
|
if (element == undefined) {
|
||||||
element = start ? array[0] : array[array.length - 1];
|
element = start ? array[0] : array[array.length - 1];
|
||||||
}
|
}
|
||||||
let fillerArray = new Array(Math.abs(count)).fill(element);
|
let fillerArray = new Array(Math.abs(count)).fill(element);
|
||||||
@ -61,19 +61,19 @@ export function fillArray(array, count, element, start=false) {
|
|||||||
* @param {Number} charWidth Width of single char in pixels
|
* @param {Number} charWidth Width of single char in pixels
|
||||||
*/
|
*/
|
||||||
export function getStringWidth(string, charWidth) {
|
export function getStringWidth(string, charWidth) {
|
||||||
return (string+"").length * charWidth;
|
return (string + "").length * charWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function bindChange(obj, getFn, setFn) {
|
export function bindChange(obj, getFn, setFn) {
|
||||||
return new Proxy(obj, {
|
return new Proxy(obj, {
|
||||||
set: function(target, prop, value) {
|
set: function (target, prop, value) {
|
||||||
setFn();
|
setFn();
|
||||||
return Reflect.set(target, prop, value);
|
return Reflect.set(target, prop, value);
|
||||||
},
|
},
|
||||||
get: function(target, prop) {
|
get: function (target, prop) {
|
||||||
getFn();
|
getFn();
|
||||||
return Reflect.get(target, prop);
|
return Reflect.get(target, prop);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,9 +81,9 @@ export function bindChange(obj, getFn, setFn) {
|
|||||||
export function getRandomBias(min, max, bias, influence) {
|
export function getRandomBias(min, max, bias, influence) {
|
||||||
const range = max - min;
|
const range = max - min;
|
||||||
const biasValue = range * bias + min;
|
const biasValue = range * bias + min;
|
||||||
var rnd = Math.random() * range + min, // random in range
|
var rnd = Math.random() * range + min, // random in range
|
||||||
mix = Math.random() * influence; // random mixer
|
mix = Math.random() * influence; // random mixer
|
||||||
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
|
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPositionByAngle(angle, radius) {
|
export function getPositionByAngle(angle, radius) {
|
||||||
@ -98,7 +98,7 @@ export function getPositionByAngle(angle, radius) {
|
|||||||
* @param {object} candidate Candidate to test
|
* @param {object} candidate Candidate to test
|
||||||
* @param {Boolean} nonNegative flag to treat negative number as invalid
|
* @param {Boolean} nonNegative flag to treat negative number as invalid
|
||||||
*/
|
*/
|
||||||
export function isValidNumber(candidate, nonNegative=false) {
|
export function isValidNumber(candidate, nonNegative = false) {
|
||||||
if (Number.isNaN(candidate)) return false;
|
if (Number.isNaN(candidate)) return false;
|
||||||
else if (candidate === undefined) return false;
|
else if (candidate === undefined) return false;
|
||||||
else if (!Number.isFinite(candidate)) return false;
|
else if (!Number.isFinite(candidate)) return false;
|
||||||
@ -113,5 +113,31 @@ export function isValidNumber(candidate, nonNegative=false) {
|
|||||||
export function round(d) {
|
export function round(d) {
|
||||||
// https://floating-point-gui.de/
|
// https://floating-point-gui.de/
|
||||||
// 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,239 +1,255 @@
|
|||||||
import { floatTwo } from './helpers';
|
import { floatTwo } from "./helpers";
|
||||||
|
|
||||||
function normalize(x) {
|
function normalize(x) {
|
||||||
// Calculates mantissa and exponent of a number
|
// Calculates mantissa and exponent of a number
|
||||||
// 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;
|
||||||
|
|
||||||
let noOfParts = range;
|
let noOfParts = range;
|
||||||
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);
|
||||||
|
|
||||||
let intervals = getChartRangeIntervals(normalMaxValue, normalMinValue);
|
let intervals = getChartRangeIntervals(normalMaxValue, normalMinValue);
|
||||||
intervals = intervals.map(value => value * Math.pow(10, exponent));
|
intervals = intervals.map((value) => {
|
||||||
return intervals;
|
// For negative exponents we want to divide by 10^-exponent to avoid
|
||||||
|
// floating point arithmetic bugs. For instance, in javascript
|
||||||
|
// 6 * 10^-1 == 0.6000000000000001, we instead want 6 / 10^1 == 0.6
|
||||||
|
if (exponent < 0) {
|
||||||
|
return value / Math.pow(10, -exponent);
|
||||||
|
}
|
||||||
|
return value * Math.pow(10, exponent);
|
||||||
|
});
|
||||||
|
return intervals;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calcChartIntervals(values, withMinimum=false) {
|
export function calcChartIntervals(values, withMinimum = true, overrideCeiling=false, overrideFloor=false) {
|
||||||
//*** Where the magic happens ***
|
//*** Where the magic happens ***
|
||||||
|
|
||||||
// Calculates best-fit y intervals from given values
|
// Calculates best-fit y intervals from given values
|
||||||
// and returns the interval array
|
// and returns the interval array
|
||||||
|
|
||||||
let maxValue = Math.max(...values);
|
let maxValue = Math.max(...values);
|
||||||
let minValue = Math.min(...values);
|
let minValue = Math.min(...values);
|
||||||
|
|
||||||
// Exponent to be used for pretty print
|
if (overrideCeiling) {
|
||||||
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars
|
maxValue = overrideCeiling
|
||||||
|
}
|
||||||
|
|
||||||
function getPositiveFirstIntervals(maxValue, absMinValue) {
|
if (overrideFloor) {
|
||||||
let intervals = getChartIntervals(maxValue);
|
minValue = overrideFloor
|
||||||
|
}
|
||||||
|
|
||||||
let intervalSize = intervals[1] - intervals[0];
|
// Exponent to be used for pretty print
|
||||||
|
let exponent = 0,
|
||||||
|
intervals = []; // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
// Then unshift the negative values
|
function getPositiveFirstIntervals(maxValue, absMinValue) {
|
||||||
let value = 0;
|
let intervals = getChartIntervals(maxValue);
|
||||||
for(var i = 1; value < absMinValue; i++) {
|
|
||||||
value += intervalSize;
|
|
||||||
intervals.unshift((-1) * value);
|
|
||||||
}
|
|
||||||
return intervals;
|
|
||||||
}
|
|
||||||
|
|
||||||
// CASE I: Both non-negative
|
let intervalSize = intervals[1] - intervals[0];
|
||||||
|
|
||||||
if(maxValue >= 0 && minValue >= 0) {
|
// Then unshift the negative values
|
||||||
exponent = normalize(maxValue)[1];
|
let value = 0;
|
||||||
if(!withMinimum) {
|
for (var i = 1; value < absMinValue; i++) {
|
||||||
intervals = getChartIntervals(maxValue);
|
value += intervalSize;
|
||||||
} else {
|
intervals.unshift(-1 * value);
|
||||||
intervals = getChartIntervals(maxValue, minValue);
|
}
|
||||||
}
|
return intervals;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE II: Only minValue negative
|
// CASE I: Both non-negative
|
||||||
|
|
||||||
else if(maxValue > 0 && minValue < 0) {
|
if (maxValue >= 0 && minValue >= 0) {
|
||||||
// `withMinimum` irrelevant in this case,
|
exponent = normalize(maxValue)[1];
|
||||||
// We'll be handling both sides of zero separately
|
if (!withMinimum) {
|
||||||
// (both starting from zero)
|
intervals = getChartIntervals(maxValue);
|
||||||
// Because ceil() and floor() behave differently
|
} else {
|
||||||
// in those two regions
|
intervals = getChartIntervals(maxValue, minValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let absMinValue = Math.abs(minValue);
|
// CASE II: Only minValue negative
|
||||||
|
else if (maxValue > 0 && minValue < 0) {
|
||||||
|
// `withMinimum` irrelevant in this case,
|
||||||
|
// We'll be handling both sides of zero separately
|
||||||
|
// (both starting from zero)
|
||||||
|
// Because ceil() and floor() behave differently
|
||||||
|
// in those two regions
|
||||||
|
|
||||||
if(maxValue >= absMinValue) {
|
let absMinValue = Math.abs(minValue);
|
||||||
exponent = normalize(maxValue)[1];
|
|
||||||
intervals = getPositiveFirstIntervals(maxValue, absMinValue);
|
|
||||||
} else {
|
|
||||||
// Mirror: maxValue => absMinValue, then change sign
|
|
||||||
exponent = normalize(absMinValue)[1];
|
|
||||||
let posIntervals = getPositiveFirstIntervals(absMinValue, maxValue);
|
|
||||||
intervals = posIntervals.reverse().map(d => d * (-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
if (maxValue >= absMinValue) {
|
||||||
|
exponent = normalize(maxValue)[1];
|
||||||
|
intervals = getPositiveFirstIntervals(maxValue, absMinValue);
|
||||||
|
} else {
|
||||||
|
// Mirror: maxValue => absMinValue, then change sign
|
||||||
|
exponent = normalize(absMinValue)[1];
|
||||||
|
let posIntervals = getPositiveFirstIntervals(absMinValue, maxValue);
|
||||||
|
intervals = posIntervals.reverse().map((d) => d * -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CASE III: Both non-positive
|
// CASE III: Both non-positive
|
||||||
|
else if (maxValue <= 0 && minValue <= 0) {
|
||||||
|
// Mirrored Case I:
|
||||||
|
// Work with positives, then reverse the sign and array
|
||||||
|
|
||||||
else if(maxValue <= 0 && minValue <= 0) {
|
let pseudoMaxValue = Math.abs(minValue);
|
||||||
// Mirrored Case I:
|
let pseudoMinValue = Math.abs(maxValue);
|
||||||
// Work with positives, then reverse the sign and array
|
|
||||||
|
|
||||||
let pseudoMaxValue = Math.abs(minValue);
|
exponent = normalize(pseudoMaxValue)[1];
|
||||||
let pseudoMinValue = Math.abs(maxValue);
|
if (!withMinimum) {
|
||||||
|
intervals = getChartIntervals(pseudoMaxValue);
|
||||||
|
} else {
|
||||||
|
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue);
|
||||||
|
}
|
||||||
|
|
||||||
exponent = normalize(pseudoMaxValue)[1];
|
intervals = intervals.reverse().map((d) => d * -1);
|
||||||
if(!withMinimum) {
|
}
|
||||||
intervals = getChartIntervals(pseudoMaxValue);
|
|
||||||
} else {
|
|
||||||
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
intervals = intervals.reverse().map(d => d * (-1));
|
return intervals.sort((a, b) => a - b);
|
||||||
}
|
|
||||||
|
|
||||||
return intervals;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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];
|
||||||
zeroIndex = (-1) * min / interval;
|
zeroIndex = (-1 * min) / interval;
|
||||||
} else {
|
} else {
|
||||||
// Maximum value is negative
|
// Maximum value is negative
|
||||||
// zero-line is off the chart: above
|
// zero-line is off the chart: above
|
||||||
let max = yPts[yPts.length - 1];
|
let max = yPts[yPts.length - 1];
|
||||||
zeroIndex = (-1) * max / interval + (yPts.length - 1);
|
zeroIndex = (-1 * max) / interval + (yPts.length - 1);
|
||||||
}
|
}
|
||||||
return zeroIndex;
|
return zeroIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRealIntervals(max, noOfIntervals, min = 0, asc = 1) {
|
export function getRealIntervals(max, noOfIntervals, min = 0, asc = 1) {
|
||||||
let range = max - min;
|
let range = max - min;
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
return asc ? intervals : intervals.reverse();
|
return asc ? intervals : intervals.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getIntervalSize(orderedArray) {
|
export function getIntervalSize(orderedArray) {
|
||||||
return orderedArray[1] - orderedArray[0];
|
return orderedArray[1] - orderedArray[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
return floatTwo(yAxis.zeroLine - val * yAxis.scaleMultiplier);
|
return floatTwo(yAxis.zeroLine - val * yAxis.scaleMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isInRange(val, min, max) {
|
export function isInRange(val, min, max) {
|
||||||
return val > min && val < max;
|
return val > min && val < max;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isInRange2D(coord, minCoord, maxCoord) {
|
export function isInRange2D(coord, minCoord, maxCoord) {
|
||||||
return isInRange(coord[0], minCoord[0], maxCoord[0])
|
return (
|
||||||
&& isInRange(coord[1], minCoord[1], maxCoord[1]);
|
isInRange(coord[0], minCoord[0], maxCoord[0]) &&
|
||||||
|
isInRange(coord[1], minCoord[1], maxCoord[1])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return index ? arr.indexOf(closest) : closest;
|
return index ? arr.indexOf(closest) : closest;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function calcDistribution(values, distributionSize) {
|
export function calcDistribution(values, distributionSize) {
|
||||||
// Assume non-negative values,
|
// Assume non-negative values,
|
||||||
// implying distribution minimum at zero
|
// implying distribution minimum at zero
|
||||||
|
|
||||||
let dataMaxValue = Math.max(...values);
|
let dataMaxValue = Math.max(...values);
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
return distribution;
|
return distribution;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMaxCheckpoint(value, distribution) {
|
export function getMaxCheckpoint(value, distribution) {
|
||||||
return distribution.filter(d => d < value).length;
|
return distribution.filter((d) => d < value).length;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
const assert = require('assert');
|
const assert = require("assert");
|
||||||
const colors = require('../colors');
|
const colors = require("../colors");
|
||||||
|
|
||||||
describe('utils.colors', () => {
|
describe("utils.colors", () => {
|
||||||
it('should return #aaabac for RGB()', () => {
|
it("should return #aaabac for RGB()", () => {
|
||||||
assert.equal(colors.getColor('rgb(170, 171, 172)'), '#aaabac');
|
assert.equal(colors.getColor("rgb(170, 171, 172)"), "#aaabac");
|
||||||
});
|
});
|
||||||
it('should return #ff5858 for the named color red', () => {
|
it("should return #ff5858 for the named color red", () => {
|
||||||
assert.equal(colors.getColor('red'), '#ff5858d');
|
assert.equal(colors.getColor("red"), "#ff5858d");
|
||||||
});
|
});
|
||||||
it('should return #1a5c29 for the hex color #1a5c29', () => {
|
it("should return #1a5c29 for the hex color #1a5c29", () => {
|
||||||
assert.equal(colors.getColor('#1a5c29'), '#1a5c29');
|
assert.equal(colors.getColor("#1a5c29"), "#1a5c29");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
const assert = require('assert');
|
const assert = require("assert");
|
||||||
const helpers = require('../helpers');
|
const helpers = require("../helpers");
|
||||||
|
|
||||||
describe('utils.helpers', () => {
|
describe("utils.helpers", () => {
|
||||||
it('should return a value fixed upto 2 decimals', () => {
|
it("should return a value fixed upto 2 decimals", () => {
|
||||||
assert.equal(helpers.floatTwo(1.234), 1.23);
|
assert.equal(helpers.floatTwo(1.234), 1.23);
|
||||||
assert.equal(helpers.floatTwo(1.456), 1.46);
|
assert.equal(helpers.floatTwo(1.456), 1.46);
|
||||||
assert.equal(helpers.floatTwo(1), 1.00);
|
assert.equal(helpers.floatTwo(1), 1.0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user