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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -8,18 +8,9 @@
|
|||||||
"sourceType": "module"
|
"sourceType": "module"
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"indent": [
|
"indent": ["error", "tab"],
|
||||||
"error",
|
"linebreak-style": ["error", "unix"],
|
||||||
"tab"
|
"semi": ["error", "always"],
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"no-console": [
|
"no-console": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|||||||
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}}
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -60,4 +60,9 @@ typings/
|
|||||||
# next.js build output
|
# next.js build output
|
||||||
.next
|
.next
|
||||||
|
|
||||||
|
# npm build output
|
||||||
|
dist
|
||||||
|
docs
|
||||||
|
docs/assets/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
154
README.md
154
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>
|
|
||||||
</a>
|
# Frappe Charts
|
||||||
|
**GitHub-inspired modern, intuitive and responsive charts with zero dependencies**
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<p>GitHub-inspired modern, intuitive and responsive charts with zero dependencies</p>
|
<a href="https://bundlephobia.com/result?p=frappe-charts">
|
||||||
<a href="https://frappe.github.io/charts">
|
<img src="https://img.shields.io/bundlephobia/minzip/frappe-charts">
|
||||||
<b>Explore Demos » </b>
|
|
||||||
</a>
|
|
||||||
<a href="https://codepen.io/pratu16x7/pen/wjKBoq">
|
|
||||||
<b> Edit at CodePen »</b>
|
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<img src=".github/example.gif">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
[Explore Demos](https://frappe.io/charts) - [Edit at CodeSandbox](https://codesandbox.io/s/frappe-charts-demo-viqud) - [Documentation](https://frappe.io/charts/docs)
|
||||||
|
|
||||||
</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):
|
|
||||||
|
### Key Features
|
||||||
|
|
||||||
|
- **Variety of chart types**: Frappe Charts supports various chart types, including Axis Charts, Area and Trends, Bar, Line, Pie, Percentage, Mixed Axis, and Heatmap.
|
||||||
|
- **Annotations and tooltips**: Charts can be annotated with x and y markers, regions, and tooltips for enhanced data context and clarity.
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ npm install frappe-charts
|
npm install frappe-charts
|
||||||
```
|
```
|
||||||
|
|
||||||
and include in your project:
|
Import in your project:
|
||||||
```js
|
|
||||||
import { Chart } from "frappe-charts"
|
|
||||||
```
|
|
||||||
|
|
||||||
...or include following for es-modules(eg:vuejs):
|
|
||||||
```js
|
```js
|
||||||
|
import { Chart } from 'frappe-charts'
|
||||||
|
// or esm import
|
||||||
import { Chart } from 'frappe-charts/dist/frappe-charts.esm.js'
|
import { Chart } from 'frappe-charts/dist/frappe-charts.esm.js'
|
||||||
// import css
|
// import css
|
||||||
import 'frappe-charts/dist/frappe-charts.min.css'
|
import 'frappe-charts/dist/frappe-charts.min.css'
|
||||||
```
|
```
|
||||||
|
|
||||||
* ...or include within your HTML
|
Or directly include script in your HTML
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.jsdelivr.net/npm/frappe-charts@1.1.0/dist/frappe-charts.min.iife.js"></script>
|
<script src="https://unpkg.com/frappe-charts@1.6.1/dist/frappe-charts.min.umd.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
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
11
package.json
11
package.json
@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"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",
|
||||||
|
"unnpkg": "dist/frappe-charts.umd.js",
|
||||||
"description": "https://frappe.github.io/charts",
|
"description": "https://frappe.github.io/charts",
|
||||||
"directories": {
|
"directories": {
|
||||||
"doc": "docs"
|
"doc": "docs"
|
||||||
@ -37,8 +38,10 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.10.5",
|
"@babel/core": "^7.10.5",
|
||||||
"@babel/preset-env": "^7.10.4",
|
"@babel/preset-env": "^7.10.4",
|
||||||
|
"node-sass": "^8.0.0",
|
||||||
"rollup": "^2.21.0",
|
"rollup": "^2.21.0",
|
||||||
"rollup-plugin-babel": "^4.4.0",
|
"rollup-plugin-babel": "^4.4.0",
|
||||||
|
"rollup-plugin-bundle-size": "^1.0.3",
|
||||||
"rollup-plugin-commonjs": "^10.1.0",
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
"rollup-plugin-eslint": "^7.0.0",
|
"rollup-plugin-eslint": "^7.0.0",
|
||||||
"rollup-plugin-postcss": "^3.1.3",
|
"rollup-plugin-postcss": "^3.1.3",
|
||||||
|
|||||||
@ -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,
|
||||||
|
name: "frappe",
|
||||||
file: pkg.browser,
|
file: pkg.browser,
|
||||||
format: 'umd'
|
format: "umd",
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
commonjs(),
|
commonjs(),
|
||||||
babel({
|
babel({
|
||||||
exclude: ['node_modules/**']
|
exclude: ["node_modules/**"],
|
||||||
}),
|
}),
|
||||||
terser(),
|
terser(),
|
||||||
scss({ output: 'dist/frappe-charts.min.css' })
|
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,54 +1,59 @@
|
|||||||
: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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,21 +65,23 @@
|
|||||||
.specific-value {
|
.specific-value {
|
||||||
text-anchor: start;
|
text-anchor: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.y-line {
|
.y-line {
|
||||||
text-anchor: end;
|
text-anchor: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.x-line {
|
.x-line {
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-dataset-label {
|
.legend-dataset-label {
|
||||||
fill: var(--fr-tooltip-label);
|
fill: var(--charts-legend-label);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-dataset-value {
|
.legend-dataset-value {
|
||||||
fill: var(--fr-tooltip-value);
|
fill: var(--charts-legend-value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,8 +91,10 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background: var(--fr-tooltip-bg);
|
background: var(--charts-tooltip-bg);
|
||||||
box-shadow: 0px 1px 4px rgba(17, 43, 66, 0.1), 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 40px 30px -30px rgba(17, 43, 66, 0.1);
|
box-shadow: 0px 1px 4px rgba(17, 43, 66, 0.1),
|
||||||
|
0px 2px 6px rgba(17, 43, 66, 0.08),
|
||||||
|
0px 40px 30px -30px rgba(17, 43, 66, 0.1);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
@ -110,7 +119,7 @@
|
|||||||
height: 12px;
|
height: 12px;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: var(--fr-tooltip-bg);
|
background: var(--charts-tooltip-bg);
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
margin-top: -7px;
|
margin-top: -7px;
|
||||||
margin-left: -6px;
|
margin-left: -6px;
|
||||||
@ -125,11 +134,15 @@
|
|||||||
display: block;
|
display: block;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--fr-tooltip-title);
|
color: var(--charts-tooltip-title);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: var(--charts-tooltip-value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
@ -172,7 +185,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-value {
|
.tooltip-value {
|
||||||
color: var(--fr-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,25 +1,23 @@
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
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) {
|
||||||
@ -15,6 +15,7 @@ export default class AggregationChart extends BaseChart {
|
|||||||
this.config.formatTooltipY = (args.tooltipOptions || {}).formatTooltipY;
|
this.config.formatTooltipY = (args.tooltipOptions || {}).formatTooltipY;
|
||||||
this.config.maxSlices = args.maxSlices || 20;
|
this.config.maxSlices = args.maxSlices || 20;
|
||||||
this.config.maxLegendPoints = args.maxLegendPoints || 20;
|
this.config.maxLegendPoints = args.maxLegendPoints || 20;
|
||||||
|
this.config.legendRowHeight = 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
calc() {
|
calc() {
|
||||||
@ -22,30 +23,38 @@ export default class AggregationChart extends BaseChart {
|
|||||||
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
|
||||||
|
.map((label, i) => {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
this.data.datasets.map(e => {
|
this.data.datasets.map((e) => {
|
||||||
total += e.values[i];
|
total += e.values[i];
|
||||||
});
|
});
|
||||||
return [total, label];
|
return [total, label];
|
||||||
}).filter(d => { return d[0] >= 0; }); // keep only positive results
|
})
|
||||||
|
.filter((d) => {
|
||||||
|
return d[0] >= 0;
|
||||||
|
}); // keep only positive results
|
||||||
|
|
||||||
let totals = allTotals;
|
let totals = allTotals;
|
||||||
if (allTotals.length > maxSlices) {
|
if (allTotals.length > maxSlices) {
|
||||||
// Prune and keep a grey area for rest as per maxSlices
|
// Prune and keep a grey area for rest as per maxSlices
|
||||||
allTotals.sort((a, b) => { return b[0] - a[0]; });
|
allTotals.sort((a, b) => {
|
||||||
|
return b[0] - a[0];
|
||||||
|
});
|
||||||
|
|
||||||
totals = allTotals.slice(0, maxSlices - 1);
|
totals = allTotals.slice(0, maxSlices - 1);
|
||||||
let remaining = allTotals.slice(maxSlices - 1);
|
let remaining = allTotals.slice(maxSlices - 1);
|
||||||
|
|
||||||
let sumOfRemaining = 0;
|
let sumOfRemaining = 0;
|
||||||
remaining.map(d => {sumOfRemaining += d[0];});
|
remaining.map((d) => {
|
||||||
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]);
|
||||||
});
|
});
|
||||||
@ -54,44 +63,32 @@ export default class AggregationChart extends BaseChart {
|
|||||||
|
|
||||||
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,
|
||||||
|
y_pos,
|
||||||
|
12, // size
|
||||||
|
3, // dot radius
|
||||||
|
this.colors[index], // fill
|
||||||
|
this.state.labels[index], // label
|
||||||
|
formatted, // value
|
||||||
|
null, // base_font_size
|
||||||
|
this.config.truncateLegends // truncate_legends
|
||||||
);
|
);
|
||||||
if (this.legendTotals.length < divisor) {
|
|
||||||
barWidth = this.width/this.legendTotals.length;
|
|
||||||
}
|
|
||||||
if(count > divisor) {
|
|
||||||
count = 0;
|
|
||||||
y += 60;
|
|
||||||
}
|
|
||||||
let x = barWidth * count + 5;
|
|
||||||
let label = this.config.truncateLegends ? truncateString(s.labels[i], barWidth/10) : s.labels[i];
|
|
||||||
let formatted = this.config.formatTooltipY ? this.config.formatTooltipY(d) : d;
|
|
||||||
let dot = legendDot(
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
12,
|
|
||||||
3,
|
|
||||||
this.colors[i],
|
|
||||||
`${label}: ${formatted}`,
|
|
||||||
d,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
this.legendArea.appendChild(dot);
|
|
||||||
count++;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,7 +32,7 @@ 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();
|
||||||
@ -31,23 +47,50 @@ 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) {
|
||||||
@ -57,7 +100,10 @@ export default class AxisChart extends BaseChart {
|
|||||||
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,7 +113,7 @@ 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;
|
||||||
|
|
||||||
@ -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;
|
||||||
|
|
||||||
|
yKeys = [];
|
||||||
|
yAxisConfigObject = this.config.yAxisMode || {};
|
||||||
|
yAxisAlignment = yAxisConfigObject.position
|
||||||
|
? yAxisConfigObject.position
|
||||||
|
: "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 = {
|
this.state.yAxis = {
|
||||||
labels: yPts,
|
labels: yPts,
|
||||||
positions: yPts.map(d => zeroLine - d * scaleMultiplier),
|
positions: yPts.map((d) => zeroLine - d * scaleMultiplier),
|
||||||
|
title: yAxisConfigObject.title || null,
|
||||||
|
pos: yAxisAlignment,
|
||||||
scaleMultiplier: scaleMultiplier,
|
scaleMultiplier: scaleMultiplier,
|
||||||
zeroLine: zeroLine,
|
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,21 +249,43 @@ 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),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -130,7 +297,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
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;
|
||||||
@ -142,7 +309,7 @@ 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(':')) {
|
||||||
@ -152,7 +319,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
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 = {};
|
||||||
@ -162,47 +329,74 @@ export default class AxisChart extends BaseChart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAllYValues() {
|
getAllYValues() {
|
||||||
let key = 'values';
|
let key = "values";
|
||||||
|
let multiAxis = this.config.yAxisConfig ? true : false;
|
||||||
|
let allValueLists = multiAxis ? {} : [];
|
||||||
|
|
||||||
|
let groupBy = (arr, property) => {
|
||||||
|
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);
|
||||||
|
arr.forEach((d, i) => {
|
||||||
|
let values = arr[i].values;
|
||||||
|
d[key] = cumulative = cumulative.map((c, i) => {
|
||||||
|
return c + values[i];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
if (this.barOptions.stacked) {
|
if (this.barOptions.stacked) {
|
||||||
key = 'cumulativeYs';
|
key = "cumulativeYs";
|
||||||
let cumulative = new Array(this.state.datasetLength).fill(0);
|
// we need to filter out the different yAxis ID's here.
|
||||||
this.data.datasets.map((d, i) => {
|
if (multiAxis) {
|
||||||
let values = this.data.datasets[i].values;
|
const groupedDataSets = groupBy(this.data.datasets, "axisID");
|
||||||
d[key] = cumulative = cumulative.map((c, i) => c + values[i]);
|
// 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,
|
||||||
@ -210,32 +404,73 @@ export default class AxisChart extends BaseChart {
|
|||||||
},
|
},
|
||||||
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],
|
||||||
@ -247,19 +482,38 @@ export default class AxisChart extends BaseChart {
|
|||||||
},
|
},
|
||||||
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(
|
||||||
|
(x) => x - barsWidth / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!stacked) {
|
||||||
|
xPositions = xPositions.map(
|
||||||
|
(p) => p + barWidth * barIndex - barWidth
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let labels = new Array(s.datasetLength).fill("");
|
||||||
if (this.config.valuesOverPoints) {
|
if (this.config.valuesOverPoints) {
|
||||||
if (stacked && d.index === s.datasets.length - 1) {
|
if (stacked && d.index === s.datasets.length - 1) {
|
||||||
labels = d.cumulativeYs;
|
labels = d.cumulativeYs;
|
||||||
@ -267,10 +521,11 @@ export default class AxisChart extends BaseChart {
|
|||||||
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],
|
||||||
@ -308,8 +563,17 @@ export default class AxisChart extends BaseChart {
|
|||||||
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(
|
||||||
|
(args) =>
|
||||||
|
!optionals.includes(args[0]) || this.state[args[0]]
|
||||||
|
)
|
||||||
|
.map((args) => {
|
||||||
let component = getComponent(...args);
|
let component = getComponent(...args);
|
||||||
if(args[0].includes('lineGraph') || args[0].includes('barGraph')) {
|
if (
|
||||||
|
args[0].includes("lineGraph") ||
|
||||||
|
args[0].includes("barGraph")
|
||||||
|
) {
|
||||||
this.dataUnitComponents.push(component);
|
this.dataUnitComponents.push(component);
|
||||||
}
|
}
|
||||||
return [args[0], component];
|
return [args[0], component];
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
@ -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
|
||||||
);
|
);
|
||||||
@ -423,26 +701,48 @@ 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) {
|
||||||
@ -450,13 +750,13 @@ export default class AxisChart extends BaseChart {
|
|||||||
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,
|
||||||
@ -469,7 +769,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
@ -479,7 +779,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
|
|
||||||
updateOverlayGuides() {
|
updateOverlayGuides() {
|
||||||
if (this.overlayGuides) {
|
if (this.overlayGuides) {
|
||||||
this.overlayGuides.forEach(g => {
|
this.overlayGuides.forEach((g) => {
|
||||||
let o = g.overlay;
|
let o = g.overlay;
|
||||||
o.parentNode.removeChild(o);
|
o.parentNode.removeChild(o);
|
||||||
});
|
});
|
||||||
@ -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);
|
||||||
});
|
});
|
||||||
@ -529,7 +829,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
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;
|
||||||
}
|
}
|
||||||
@ -544,8 +844,6 @@ export default class AxisChart extends BaseChart {
|
|||||||
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);
|
||||||
@ -562,7 +860,7 @@ export default class AxisChart extends BaseChart {
|
|||||||
}
|
}
|
||||||
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);
|
||||||
|
|||||||
@ -1,45 +1,83 @@
|
|||||||
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 =
|
||||||
|
typeof parent === "string"
|
||||||
? document.querySelector(parent)
|
? document.querySelector(parent)
|
||||||
: 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) {
|
||||||
|
m.titleHeight = 0;
|
||||||
|
}
|
||||||
if (!this.config.showLegend) m.legendHeight = 0;
|
if (!this.config.showLegend) m.legendHeight = 0;
|
||||||
this.argHeight = options.height || m.baseHeight;
|
this.argHeight = options.height || m.baseHeight;
|
||||||
|
|
||||||
@ -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,24 +153,24 @@ 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();
|
||||||
}
|
}
|
||||||
@ -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); */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.config.showLegend) {
|
||||||
this.renderLegend();
|
this.renderLegend();
|
||||||
|
}
|
||||||
|
|
||||||
this.setupNavigation(init);
|
this.setupNavigation(init);
|
||||||
}
|
}
|
||||||
@ -173,7 +223,7 @@ export default class BaseChart {
|
|||||||
|
|
||||||
this.svg = makeSVGContainer(
|
this.svg = makeSVGContainer(
|
||||||
this.container,
|
this.container,
|
||||||
'frappe-chart chart',
|
"frappe-chart chart",
|
||||||
this.baseWidth,
|
this.baseWidth,
|
||||||
this.baseHeight
|
this.baseHeight
|
||||||
);
|
);
|
||||||
@ -181,35 +231,39 @@ export default class BaseChart {
|
|||||||
|
|
||||||
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,17 +271,18 @@ 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) {
|
|
||||||
if(!data) {
|
|
||||||
console.error('No data to update.');
|
|
||||||
}
|
}
|
||||||
this.data = this.prepareData(data);
|
|
||||||
|
update(data, drawing = false, config) {
|
||||||
|
if (!data) console.error("No data to update.");
|
||||||
|
if (!drawing) data = deepClone(data);
|
||||||
|
this.data = this.prepareData(data, config);
|
||||||
this.calc(); // builds state
|
this.calc(); // builds state
|
||||||
this.render(this.components, this.config.animate);
|
this.render(this.components, this.config.animate);
|
||||||
}
|
}
|
||||||
@ -235,22 +290,22 @@ export default class BaseChart {
|
|||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,7 +317,25 @@ export default class BaseChart {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderLegend() {}
|
renderLegend(dataset) {
|
||||||
|
this.legendArea.textContent = "";
|
||||||
|
let count = 0;
|
||||||
|
let y = 0;
|
||||||
|
|
||||||
|
dataset.map((data, index) => {
|
||||||
|
let divisor = Math.floor(this.width / LEGEND_ITEM_WIDTH);
|
||||||
|
if (count > divisor) {
|
||||||
|
count = 0;
|
||||||
|
y += this.config.legendRowHeight;
|
||||||
|
}
|
||||||
|
let x = LEGEND_ITEM_WIDTH * count;
|
||||||
|
let dot = this.makeLegend(data, index, x, y);
|
||||||
|
this.legendArea.appendChild(dot);
|
||||||
|
count++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
makeLegend() {}
|
||||||
|
|
||||||
setupNavigation(init = false) {
|
setupNavigation(init = false) {
|
||||||
if (!this.config.isNavigable) return;
|
if (!this.config.isNavigable) return;
|
||||||
@ -271,14 +344,14 @@ export default class BaseChart {
|
|||||||
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]) {
|
||||||
@ -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,16 +1,16 @@
|
|||||||
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;
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ export default class DonutChart extends AggregationChart {
|
|||||||
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);
|
||||||
|
|
||||||
@ -65,8 +65,22 @@ export default class DonutChart extends AggregationChart {
|
|||||||
}
|
}
|
||||||
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({
|
||||||
@ -76,9 +90,8 @@ export default class DonutChart extends AggregationChart {
|
|||||||
total: s.grandTotal,
|
total: s.grandTotal,
|
||||||
startAngle,
|
startAngle,
|
||||||
endAngle,
|
endAngle,
|
||||||
angle: diffAngle
|
angle: diffAngle,
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
this.init = 0;
|
this.init = 0;
|
||||||
}
|
}
|
||||||
@ -88,7 +101,7 @@ export default class DonutChart extends AggregationChart {
|
|||||||
|
|
||||||
let componentConfigs = [
|
let componentConfigs = [
|
||||||
[
|
[
|
||||||
'donutSlices',
|
"donutSlices",
|
||||||
{},
|
{},
|
||||||
function () {
|
function () {
|
||||||
return {
|
return {
|
||||||
@ -96,21 +109,27 @@ export default class DonutChart extends AggregationChart {
|
|||||||
colors: this.colors,
|
colors: this.colors,
|
||||||
strokeWidth: this.strokeWidth,
|
strokeWidth: this.strokeWidth,
|
||||||
};
|
};
|
||||||
}.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) {
|
||||||
@ -122,26 +141,31 @@ export default class DonutChart extends AggregationChart {
|
|||||||
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.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.setValues(x, y, { name: title, value: percent + "%" });
|
||||||
this.tip.showTip();
|
this.tip.showTip();
|
||||||
} else {
|
} else {
|
||||||
transform(path,'translate3d(0,0,0)');
|
transform(path, "translate3d(0,0,0)");
|
||||||
this.tip.hideTip();
|
this.tip.hideTip();
|
||||||
path.style.stroke = color;
|
path.style.stroke = color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bindTooltip() {
|
bindTooltip() {
|
||||||
this.container.addEventListener('mousemove', this.mouseMove);
|
this.container.addEventListener("mousemove", this.mouseMove);
|
||||||
this.container.addEventListener('mouseleave', this.mouseLeave);
|
this.container.addEventListener("mouseleave", this.mouseLeave);
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseMove(e) {
|
mouseMove(e) {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
let slices = this.components.get('donutSlices').store;
|
let slices = this.components.get("donutSlices").store;
|
||||||
let prevIndex = this.curActiveSliceIndex;
|
let prevIndex = this.curActiveSliceIndex;
|
||||||
let prevAcitve = this.curActiveSlice;
|
let prevAcitve = this.curActiveSlice;
|
||||||
if (slices.includes(target)) {
|
if (slices.includes(target)) {
|
||||||
|
|||||||
@ -1,11 +1,29 @@
|
|||||||
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;
|
||||||
@ -14,13 +32,14 @@ const ROW_HEIGHT = COL_WIDTH;
|
|||||||
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
|
||||||
|
: "Sunday";
|
||||||
this.startSubDomainIndex = validStarts.indexOf(startSubDomain);
|
this.startSubDomainIndex = validStarts.indexOf(startSubDomain);
|
||||||
|
|
||||||
this.setup();
|
this.setup();
|
||||||
@ -33,37 +52,43 @@ export default class Heatmap extends BaseChart {
|
|||||||
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);
|
||||||
|
|
||||||
|
if (!data.end) {
|
||||||
|
data.end = new Date();
|
||||||
|
}
|
||||||
|
data.end = toMidnightUTC(data.end);
|
||||||
|
|
||||||
data.dataPoints = data.dataPoints || {};
|
data.dataPoints = data.dataPoints || {};
|
||||||
|
|
||||||
if (parseInt(Object.keys(data.dataPoints)[0]) > 100000) {
|
if (parseInt(Object.keys(data.dataPoints)[0]) > 100000) {
|
||||||
let points = {};
|
let points = {};
|
||||||
Object.keys(data.dataPoints).forEach(timestampSec => {
|
Object.keys(data.dataPoints).forEach((timestampSec) => {
|
||||||
let date = new Date(timestampSec * NO_OF_MILLIS);
|
let date = new Date(timestampSec * NO_OF_MILLIS);
|
||||||
points[getYyyyMmDd(date)] = data.dataPoints[timestampSec];
|
points[getYyyyMmDd(date)] = data.dataPoints[timestampSec];
|
||||||
});
|
});
|
||||||
@ -82,7 +107,9 @@ export default class Heatmap extends BaseChart {
|
|||||||
s.firstWeekStart = clone(s.start);
|
s.firstWeekStart = clone(s.start);
|
||||||
s.noOfWeeks = getWeeksBetween(s.start, s.end);
|
s.noOfWeeks = getWeeksBetween(s.start, s.end);
|
||||||
s.distribution = calcDistribution(
|
s.distribution = calcDistribution(
|
||||||
Object.values(this.data.dataPoints), HEATMAP_DISTRIBUTION_SIZE);
|
Object.values(this.data.dataPoints),
|
||||||
|
HEATMAP_DISTRIBUTION_SIZE
|
||||||
|
);
|
||||||
|
|
||||||
s.domainConfigs = this.getDomains();
|
s.domainConfigs = this.getDomains();
|
||||||
}
|
}
|
||||||
@ -92,42 +119,39 @@ export default class Heatmap extends BaseChart {
|
|||||||
let lessCol = this.discreteDomains ? 0 : 1;
|
let lessCol = this.discreteDomains ? 0 : 1;
|
||||||
|
|
||||||
let componentConfigs = s.domainConfigs.map((config, i) => [
|
let componentConfigs = s.domainConfigs.map((config, i) => [
|
||||||
'heatDomain',
|
"heatDomain",
|
||||||
{
|
{
|
||||||
index: config.index,
|
index: config.index,
|
||||||
colWidth: COL_WIDTH,
|
colWidth: COL_WIDTH,
|
||||||
rowHeight: ROW_HEIGHT,
|
rowHeight: ROW_HEIGHT,
|
||||||
squareSize: HEATMAP_SQUARE_SIZE,
|
squareSize: HEATMAP_SQUARE_SIZE,
|
||||||
radius: this.rawChartArgs.radius || 0,
|
radius: this.rawChartArgs.radius || 0,
|
||||||
xTranslate: s.domainConfigs
|
xTranslate:
|
||||||
|
s.domainConfigs
|
||||||
.filter((config, j) => j < i)
|
.filter((config, j) => j < i)
|
||||||
.map(config => config.cols.length - lessCol)
|
.map((config) => config.cols.length - lessCol)
|
||||||
.reduce((a, b) => a + b, 0)
|
.reduce((a, b) => a + b, 0) * COL_WIDTH,
|
||||||
* COL_WIDTH
|
|
||||||
},
|
},
|
||||||
function () {
|
function () {
|
||||||
return s.domainConfigs[i];
|
return s.domainConfigs[i];
|
||||||
}.bind(this)
|
}.bind(this),
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.components = new Map(componentConfigs
|
this.components = new Map(
|
||||||
.map((args, i) => {
|
componentConfigs.map((args, i) => {
|
||||||
let component = getComponent(...args);
|
let component = getComponent(...args);
|
||||||
return [args[0] + '-' + i, component];
|
return [args[0] + "-" + i, component];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
let y = 0;
|
let y = 0;
|
||||||
DAY_NAMES_SHORT.forEach((dayName, i) => {
|
DAY_NAMES_SHORT.forEach((dayName, i) => {
|
||||||
if ([1, 3, 5].includes(i)) {
|
if ([1, 3, 5].includes(i)) {
|
||||||
let dayText = makeText('subdomain-name', -COL_WIDTH/2, y, dayName,
|
let dayText = makeText("subdomain-name", -COL_WIDTH / 2, y, dayName, {
|
||||||
{
|
|
||||||
fontSize: HEATMAP_SQUARE_SIZE,
|
fontSize: HEATMAP_SQUARE_SIZE,
|
||||||
dy: 8,
|
dy: 8,
|
||||||
textAnchor: 'end'
|
textAnchor: "end",
|
||||||
}
|
});
|
||||||
);
|
|
||||||
this.drawArea.appendChild(dayText);
|
this.drawArea.appendChild(dayText);
|
||||||
}
|
}
|
||||||
y += ROW_HEIGHT;
|
y += ROW_HEIGHT;
|
||||||
@ -136,7 +160,7 @@ export default class Heatmap extends BaseChart {
|
|||||||
|
|
||||||
update(data) {
|
update(data) {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
console.error('No data to update.');
|
console.error("No data to update.");
|
||||||
}
|
}
|
||||||
|
|
||||||
this.data = this.prepareData(data);
|
this.data = this.prepareData(data);
|
||||||
@ -145,26 +169,31 @@ export default class Heatmap extends BaseChart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bindTooltip() {
|
bindTooltip() {
|
||||||
this.container.addEventListener('mousemove', (e) => {
|
this.container.addEventListener("mousemove", (e) => {
|
||||||
this.components.forEach(comp => {
|
this.components.forEach((comp) => {
|
||||||
let daySquares = comp.store;
|
let daySquares = comp.store;
|
||||||
let daySquare = e.target;
|
let daySquare = e.target;
|
||||||
if (daySquares.includes(daySquare)) {
|
if (daySquares.includes(daySquare)) {
|
||||||
|
let count = daySquare.getAttribute("data-value");
|
||||||
let count = daySquare.getAttribute('data-value');
|
let dateParts = daySquare.getAttribute("data-date").split("-");
|
||||||
let dateParts = daySquare.getAttribute('data-date').split('-');
|
|
||||||
|
|
||||||
let month = getMonthName(parseInt(dateParts[1]) - 1, true);
|
let month = getMonthName(parseInt(dateParts[1]) - 1, true);
|
||||||
|
|
||||||
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect();
|
let gOff = this.container.getBoundingClientRect(),
|
||||||
|
pOff = daySquare.getBoundingClientRect();
|
||||||
|
|
||||||
let width = parseInt(e.target.getAttribute('width'));
|
let width = parseInt(e.target.getAttribute("width"));
|
||||||
let x = pOff.left - gOff.left + width / 2;
|
let x = pOff.left - gOff.left + width / 2;
|
||||||
let y = pOff.top - gOff.top;
|
let y = pOff.top - gOff.top;
|
||||||
let value = count + ' ' + this.countLabel;
|
let value = count + " " + this.countLabel;
|
||||||
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2];
|
let name = " on " + month + " " + dateParts[0] + ", " + dateParts[2];
|
||||||
|
|
||||||
this.tip.setValues(x, y, {name: name, value: value, valueFirst: 1}, []);
|
this.tip.setValues(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
{ name: name, value: value, valueFirst: 1 },
|
||||||
|
[]
|
||||||
|
);
|
||||||
this.tip.showTip();
|
this.tip.showTip();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -172,33 +201,36 @@ export default class Heatmap extends BaseChart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
x = (COL_WIDTH * 2) + COL_WIDTH/2;
|
|
||||||
this.legendArea.appendChild(lessText);
|
this.legendArea.appendChild(lessText);
|
||||||
|
|
||||||
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => {
|
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => {
|
||||||
const square = heatSquare('heatmap-legend-unit', x + (COL_WIDTH + 3) * i,
|
const square = heatSquare(
|
||||||
y, HEATMAP_SQUARE_SIZE, radius, color);
|
"heatmap-legend-unit",
|
||||||
|
x + (COL_WIDTH + 3) * i,
|
||||||
|
y,
|
||||||
|
HEATMAP_SQUARE_SIZE,
|
||||||
|
radius,
|
||||||
|
color
|
||||||
|
);
|
||||||
this.legendArea.appendChild(square);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +239,7 @@ export default class Heatmap extends BaseChart {
|
|||||||
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 = [];
|
||||||
|
|
||||||
@ -215,7 +247,10 @@ export default class Heatmap extends BaseChart {
|
|||||||
for (var i = 0; i < noOfMonths; i++) {
|
for (var i = 0; i < noOfMonths; i++) {
|
||||||
let endDate = s.end;
|
let endDate = s.end;
|
||||||
if (!areInSameMonth(startOfMonth, s.end)) {
|
if (!areInSameMonth(startOfMonth, s.end)) {
|
||||||
let [month, year] = [startOfMonth.getMonth(), startOfMonth.getFullYear()];
|
let [month, year] = [
|
||||||
|
startOfMonth.getMonth(),
|
||||||
|
startOfMonth.getFullYear(),
|
||||||
|
];
|
||||||
endDate = getLastDateInMonth(month, year);
|
endDate = getLastDateInMonth(month, year);
|
||||||
}
|
}
|
||||||
domainConfigs.push(this.getDomainConfig(startOfMonth, endDate));
|
domainConfigs.push(this.getDomainConfig(startOfMonth, endDate));
|
||||||
@ -227,25 +262,30 @@ export default class Heatmap extends BaseChart {
|
|||||||
return domainConfigs;
|
return domainConfigs;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDomainConfig(startDate, endDate='') {
|
getDomainConfig(startDate, endDate = "") {
|
||||||
let [month, year] = [startDate.getMonth(), startDate.getFullYear()];
|
let [month, year] = [startDate.getMonth(), startDate.getFullYear()];
|
||||||
let startOfWeek = setDayToSunday(startDate); // TODO: Monday as well
|
let startOfWeek = setDayToSunday(startDate); // TODO: Monday as well
|
||||||
endDate = clone(endDate) || getLastDateInMonth(month, year);
|
endDate = 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 = [],
|
||||||
|
col;
|
||||||
for (var i = 0; i < noOfMonthWeeks; i++) {
|
for (var i = 0; i < noOfMonthWeeks; i++) {
|
||||||
col = this.getCol(startOfWeek, month);
|
col = this.getCol(startOfWeek, month);
|
||||||
cols.push(col);
|
cols.push(col);
|
||||||
|
|
||||||
startOfWeek = new Date(col[NO_OF_DAYS_IN_WEEK - 1].yyyyMmDd);
|
startOfWeek = toMidnightUTC(
|
||||||
|
new Date(col[NO_OF_DAYS_IN_WEEK - 1].yyyyMmDd)
|
||||||
|
);
|
||||||
addDays(startOfWeek, 1);
|
addDays(startOfWeek, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +310,8 @@ export default class Heatmap extends BaseChart {
|
|||||||
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);
|
||||||
@ -289,7 +330,7 @@ export default class Heatmap extends BaseChart {
|
|||||||
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,12 +1,12 @@
|
|||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,7 +16,6 @@ export default class PercentageChart extends AggregationChart {
|
|||||||
|
|
||||||
let b = this.barOptions;
|
let b = this.barOptions;
|
||||||
b.height = b.height || PERCENTAGE_BAR_DEFAULT_HEIGHT;
|
b.height = b.height || PERCENTAGE_BAR_DEFAULT_HEIGHT;
|
||||||
b.depth = b.depth || PERCENTAGE_BAR_DEFAULT_DEPTH;
|
|
||||||
|
|
||||||
m.paddings.right = 30;
|
m.paddings.right = 30;
|
||||||
m.legendHeight = 60;
|
m.legendHeight = 60;
|
||||||
@ -28,26 +27,26 @@ export default class PercentageChart extends AggregationChart {
|
|||||||
|
|
||||||
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() {
|
||||||
@ -59,7 +58,7 @@ export default class PercentageChart extends AggregationChart {
|
|||||||
|
|
||||||
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;
|
||||||
@ -70,21 +69,26 @@ export default class PercentageChart extends AggregationChart {
|
|||||||
|
|
||||||
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 i = bars.indexOf(bar);
|
||||||
let gOff = getOffset(this.container), pOff = getOffset(bar);
|
let gOff = getOffset(this.container),
|
||||||
|
pOff = getOffset(bar);
|
||||||
|
|
||||||
let x = pOff.left - gOff.left + parseInt(bar.getAttribute('width'))/2;
|
let x = pOff.left - gOff.left + parseInt(bar.getAttribute("width")) / 2;
|
||||||
let y = pOff.top - gOff.top;
|
let y = pOff.top - gOff.top;
|
||||||
let title = (this.formattedLabels && this.formattedLabels.length>0
|
let title =
|
||||||
? this.formattedLabels[i] : this.state.labels[i]) + ': ';
|
(this.formattedLabels && this.formattedLabels.length > 0
|
||||||
|
? this.formattedLabels[i]
|
||||||
|
: this.state.labels[i]) + ": ";
|
||||||
let fraction = s.sliceTotals[i] / s.grandTotal;
|
let fraction = s.sliceTotals[i] / s.grandTotal;
|
||||||
|
|
||||||
this.tip.setValues(x, y, {name: title, value: (fraction*100).toFixed(1) + "%"});
|
this.tip.setValues(x, y, {
|
||||||
|
name: title,
|
||||||
|
value: (fraction * 100).toFixed(1) + "%",
|
||||||
|
});
|
||||||
this.tip.showTip();
|
this.tip.showTip();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
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;
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export default class PieChart extends AggregationChart {
|
|||||||
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;
|
||||||
|
|
||||||
@ -39,12 +39,13 @@ export default class PieChart extends AggregationChart {
|
|||||||
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);
|
||||||
|
|
||||||
@ -60,8 +61,22 @@ export default class PieChart extends AggregationChart {
|
|||||||
}
|
}
|
||||||
const curPath =
|
const curPath =
|
||||||
originDiffAngle === 360
|
originDiffAngle === 360
|
||||||
? makeCircleStr(curStart, curEnd, this.center, this.radius, clockWise, largeArc)
|
? makeCircleStr(
|
||||||
: makeArcPathStr(curStart, curEnd, this.center, this.radius, clockWise, largeArc);
|
curStart,
|
||||||
|
curEnd,
|
||||||
|
this.center,
|
||||||
|
this.radius,
|
||||||
|
clockWise,
|
||||||
|
largeArc
|
||||||
|
)
|
||||||
|
: makeArcPathStr(
|
||||||
|
curStart,
|
||||||
|
curEnd,
|
||||||
|
this.center,
|
||||||
|
this.radius,
|
||||||
|
clockWise,
|
||||||
|
largeArc
|
||||||
|
);
|
||||||
|
|
||||||
s.sliceStrings.push(curPath);
|
s.sliceStrings.push(curPath);
|
||||||
s.slicesProperties.push({
|
s.slicesProperties.push({
|
||||||
@ -71,9 +86,8 @@ export default class PieChart extends AggregationChart {
|
|||||||
total: s.grandTotal,
|
total: s.grandTotal,
|
||||||
startAngle,
|
startAngle,
|
||||||
endAngle,
|
endAngle,
|
||||||
angle: diffAngle
|
angle: diffAngle,
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
this.init = 0;
|
this.init = 0;
|
||||||
}
|
}
|
||||||
@ -83,28 +97,34 @@ export default class PieChart extends AggregationChart {
|
|||||||
|
|
||||||
let componentConfigs = [
|
let componentConfigs = [
|
||||||
[
|
[
|
||||||
'pieSlices',
|
"pieSlices",
|
||||||
{},
|
{},
|
||||||
function () {
|
function () {
|
||||||
return {
|
return {
|
||||||
sliceStrings: s.sliceStrings,
|
sliceStrings: s.sliceStrings,
|
||||||
colors: this.colors
|
colors: this.colors,
|
||||||
};
|
};
|
||||||
}.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) {
|
||||||
@ -116,26 +136,58 @@ export default class PieChart extends AggregationChart {
|
|||||||
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.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.setValues(x, y, { name: title, value: percent + "%" });
|
||||||
this.tip.showTip();
|
this.tip.showTip();
|
||||||
} else {
|
} else {
|
||||||
transform(path,'translate3d(0,0,0)');
|
transform(path, "translate3d(0,0,0)");
|
||||||
this.tip.hideTip();
|
this.tip.hideTip();
|
||||||
path.style.fill = color;
|
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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bindUnits() {
|
||||||
|
const units = this.components.get("pieSlices").store;
|
||||||
|
if (!units) return;
|
||||||
|
units.forEach((unit, index) => {
|
||||||
|
unit.addEventListener("click", () => {
|
||||||
|
this.setCurrentDataPoint(index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
mouseMove(e) {
|
mouseMove(e) {
|
||||||
const target = e.target;
|
const target = e.target;
|
||||||
let slices = this.components.get('pieSlices').store;
|
let slices = this.components.get("pieSlices").store;
|
||||||
let prevIndex = this.curActiveSliceIndex;
|
let prevIndex = this.curActiveSliceIndex;
|
||||||
let prevAcitve = this.curActiveSlice;
|
let prevAcitve = this.curActiveSlice;
|
||||||
if (slices.includes(target)) {
|
if (slices.includes(target)) {
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
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);
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -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) {
|
||||||
|
const numberOfPoints = data.xPositions.length;
|
||||||
return data.xPositions.map((x, i) => {
|
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,105 +295,124 @@ 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(
|
||||||
|
oldPos.map((pos, i) => {
|
||||||
return {
|
return {
|
||||||
position: oldPos[i],
|
position: oldPos[i],
|
||||||
label: newLabels[i],
|
label: newLabels[i],
|
||||||
options: newOptions[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(
|
||||||
|
oldPos.map((pos, i) => {
|
||||||
return {
|
return {
|
||||||
startPos: oldStarts[i],
|
startPos: oldStarts[i],
|
||||||
endPos: oldPos[i],
|
endPos: oldPos[i],
|
||||||
label: newLabels[i],
|
label: newLabels[i],
|
||||||
options: newOptions[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,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -271,11 +420,19 @@ let componentConfigs = {
|
|||||||
week.map((day, i) => {
|
week.map((day, i) => {
|
||||||
if (day.fill) {
|
if (day.fill) {
|
||||||
let data = {
|
let data = {
|
||||||
'data-date': day.yyyyMmDd,
|
"data-date": day.yyyyMmDd,
|
||||||
'data-value': day.dataValue,
|
"data-value": day.dataValue,
|
||||||
'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;
|
||||||
@ -289,14 +446,16 @@ 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,21 +507,29 @@ 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(
|
||||||
|
bar,
|
||||||
|
newXPos[i],
|
||||||
|
newYPos[i],
|
||||||
|
newData.barWidth,
|
||||||
|
newOffsets[i],
|
||||||
{ zeroLine: newData.zeroLine }
|
{ 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(
|
||||||
@ -369,11 +539,11 @@ 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,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -386,7 +556,7 @@ let componentConfigs = {
|
|||||||
y,
|
y,
|
||||||
data.radius,
|
data.radius,
|
||||||
c.color,
|
c.color,
|
||||||
(c.valuesOverPoints ? data.values[j] : ''),
|
c.valuesOverPoints ? data.values[j] : "",
|
||||||
j
|
j
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -419,28 +589,36 @@ 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,15 +1,12 @@
|
|||||||
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,
|
|
||||||
colors = []
|
|
||||||
}) {
|
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
this.colors = colors;
|
this.colors = colors;
|
||||||
this.titleName = '';
|
this.titleName = "";
|
||||||
this.titleValue = '';
|
this.titleValue = "";
|
||||||
this.listValues = [];
|
this.listValues = [];
|
||||||
this.titleValueFirst = 0;
|
this.titleValueFirst = 0;
|
||||||
|
|
||||||
@ -32,20 +29,20 @@ export default class SvgTip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -53,7 +50,7 @@ export default class SvgTip {
|
|||||||
fill() {
|
fill() {
|
||||||
let title;
|
let title;
|
||||||
if (this.index) {
|
if (this.index) {
|
||||||
this.container.setAttribute('data-point-index', this.index);
|
this.container.setAttribute("data-point-index", this.index);
|
||||||
}
|
}
|
||||||
if (this.titleValueFirst) {
|
if (this.titleValueFirst) {
|
||||||
title = `<strong>${this.titleValue}</strong>${this.titleName}`;
|
title = `<strong>${this.titleValue}</strong>${this.titleName}`;
|
||||||
@ -62,23 +59,24 @@ export default class SvgTip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
let li = $.create("li", {
|
||||||
innerHTML: `<div class="tooltip-legend" style="background: ${color};"></div>
|
innerHTML: `<div class="tooltip-legend" style="background: ${color};"></div>
|
||||||
<div>
|
<div>
|
||||||
<div class="tooltip-value">${ value === 0 || value ? value : '' }</div>
|
<div class="tooltip-value">${value === 0 || value ? value : ""}</div>
|
||||||
<div class="tooltip-label">${set.title ? set.title : '' }</div>
|
<div class="tooltip-label">${set.title ? set.title : ""}</div>
|
||||||
</div>`
|
</div>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dataPointList.appendChild(li);
|
this.dataPointList.appendChild(li);
|
||||||
@ -88,12 +86,12 @@ export default class SvgTip {
|
|||||||
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)`;
|
||||||
@ -121,14 +119,14 @@ export default class SvgTip {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,21 +1,21 @@
|
|||||||
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 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,38 +33,50 @@ export function animateRegion(rectGroup, newY1, newY2, oldY2) {
|
|||||||
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(
|
||||||
|
rectGroup,
|
||||||
|
[0, oldY2],
|
||||||
|
[0, newY2],
|
||||||
|
MARKER_LINE_ANIM_DUR
|
||||||
|
);
|
||||||
return [rectAnim, groupAnim];
|
return [rectAnim, groupAnim];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function animateBar(bar, x, yTop, width, offset = 0, meta = {}) {
|
export function animateBar(bar, x, yTop, width, offset = 0, meta = {}) {
|
||||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
|
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
|
||||||
y -= offset;
|
y -= offset;
|
||||||
if(bar.nodeName !== 'rect') {
|
if (bar.nodeName !== "rect") {
|
||||||
let rect = bar.childNodes[0];
|
let rect = bar.childNodes[0];
|
||||||
let rectAnim = [
|
let rectAnim = [
|
||||||
rect,
|
rect,
|
||||||
{ width: width, height: height },
|
{ width: width, height: height },
|
||||||
UNIT_ANIM_DUR,
|
UNIT_ANIM_DUR,
|
||||||
STD_EASING
|
STD_EASING,
|
||||||
];
|
];
|
||||||
|
|
||||||
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,
|
||||||
|
{ width: width, height: height, x: x, y: y },
|
||||||
|
UNIT_ANIM_DUR,
|
||||||
|
STD_EASING,
|
||||||
|
],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function animateDot(dot, x, y) {
|
export function animateDot(dot, x, y) {
|
||||||
if(dot.nodeName !== 'circle') {
|
if (dot.nodeName !== "circle") {
|
||||||
let oldCoordStr = dot.getAttribute("transform").split("(")[1].slice(0, -1);
|
let oldCoordStr = dot.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||||
let groupAnim = translate(dot, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
let groupAnim = translate(dot, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||||
return [groupAnim];
|
return [groupAnim];
|
||||||
@ -76,12 +88,16 @@ export function animateDot(dot, x, y) {
|
|||||||
|
|
||||||
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 = [
|
||||||
|
paths.path,
|
||||||
|
{ d: "M" + pointsStr },
|
||||||
|
PATH_ANIM_DUR,
|
||||||
|
STD_EASING,
|
||||||
|
];
|
||||||
pathComponents.push(animPath);
|
pathComponents.push(animPath);
|
||||||
|
|
||||||
if (paths.region) {
|
if (paths.region) {
|
||||||
@ -92,7 +108,7 @@ export function animatePath(paths, newXList, newYList, zeroLine, spline) {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// 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",
|
||||||
@ -8,22 +8,35 @@ const EASING = {
|
|||||||
// 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 animElement = element.cloneNode(true);
|
||||||
let newElement = element.cloneNode(true);
|
let newElement = element.cloneNode(true);
|
||||||
|
|
||||||
for (var attributeName in props) {
|
for (var attributeName in props) {
|
||||||
let animateElement;
|
let animateElement;
|
||||||
if(attributeName === 'transform') {
|
if (attributeName === "transform") {
|
||||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
|
animateElement = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"animateTransform"
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animate");
|
animateElement = document.createElementNS(
|
||||||
|
"http://www.w3.org/2000/svg",
|
||||||
|
"animate"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let currentValue = oldValues[attributeName] || element.getAttribute(attributeName);
|
let currentValue =
|
||||||
|
oldValues[attributeName] || element.getAttribute(attributeName);
|
||||||
let value = props[attributeName];
|
let value = props[attributeName];
|
||||||
|
|
||||||
let animAttr = {
|
let animAttr = {
|
||||||
@ -36,7 +49,7 @@ function animateSVGElement(element, props, dur, easingType="linear", type=undefi
|
|||||||
keySplines: EASING[easingType],
|
keySplines: EASING[easingType],
|
||||||
keyTimes: "0;1",
|
keyTimes: "0;1",
|
||||||
calcMode: "spline",
|
calcMode: "spline",
|
||||||
fill: 'freeze'
|
fill: "freeze",
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type) {
|
if (type) {
|
||||||
@ -59,7 +72,8 @@ function animateSVGElement(element, props, dur, easingType="linear", type=undefi
|
|||||||
return [animElement, newElement];
|
return [animElement, newElement];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transform(element, style) { // eslint-disable-line no-unused-vars
|
export function transform(element, style) {
|
||||||
|
// eslint-disable-line no-unused-vars
|
||||||
element.style.transform = style;
|
element.style.transform = style;
|
||||||
element.style.webkitTransform = style;
|
element.style.webkitTransform = style;
|
||||||
element.style.msTransform = style;
|
element.style.msTransform = style;
|
||||||
@ -71,7 +85,7 @@ 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;
|
||||||
|
|
||||||
@ -83,14 +97,18 @@ function animateSVG(svgContainer, elements) {
|
|||||||
newElements.push(newElement);
|
newElements.push(newElement);
|
||||||
animElements.push([animElement, parent]);
|
animElements.push([animElement, parent]);
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
parent.replaceChild(animElement, unit);
|
parent.replaceChild(animElement, unit);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let animSvg = svgContainer.cloneNode(true);
|
let animSvg = svgContainer.cloneNode(true);
|
||||||
|
|
||||||
animElements.map((animElement, i) => {
|
animElements.map((animElement, i) => {
|
||||||
|
if (animElement[1]) {
|
||||||
animElement[1].replaceChild(newElements[i], animElement[0]);
|
animElement[1].replaceChild(newElements[i], animElement[0]);
|
||||||
elements[i][0] = newElements[i];
|
elements[i][0] = newElements[i];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return animSvg;
|
return animSvg;
|
||||||
@ -103,7 +121,6 @@ export function runSMILAnimation(parent, 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)
|
// Replace the new svgElement (data has already been replaced)
|
||||||
|
|||||||
@ -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;
|
||||||
@ -11,37 +16,40 @@ export function dataPrep(data, type) {
|
|||||||
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
|
||||||
@ -49,7 +57,7 @@ export function dataPrep(data, type) {
|
|||||||
// Regions
|
// Regions
|
||||||
// data.yRegions = data.yRegions || [];
|
// data.yRegions = data.yRegions || [];
|
||||||
if (data.yRegions) {
|
if (data.yRegions) {
|
||||||
data.yRegions.map(d => {
|
data.yRegions.map((d) => {
|
||||||
if (d.end < d.start) {
|
if (d.end < d.start) {
|
||||||
[d.start, d.end] = [d.end, d.start];
|
[d.start, d.end] = [d.end, d.start];
|
||||||
}
|
}
|
||||||
@ -65,11 +73,13 @@ 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,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@ -78,8 +88,8 @@ export function zeroDataPrep(realData) {
|
|||||||
zeroData.yMarkers = [
|
zeroData.yMarkers = [
|
||||||
{
|
{
|
||||||
value: 0,
|
value: 0,
|
||||||
label: ''
|
label: "",
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,8 +98,8 @@ export function zeroDataPrep(realData) {
|
|||||||
{
|
{
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
label: ''
|
label: "",
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,29 +107,28 @@ export function zeroDataPrep(realData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,24 +1,24 @@
|
|||||||
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) {
|
||||||
@ -36,23 +36,25 @@ export function lightenDarkenColor(color, amt) {
|
|||||||
}
|
}
|
||||||
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 =
|
||||||
|
/(^\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);
|
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)
|
||||||
|
.map((x, i) => (i !== 0 ? Number(x).toString(16) : "#"))
|
||||||
.reduce((c, ch) => `${c}${ch}`);
|
.reduce((c, ch) => `${c}${ch}`);
|
||||||
}
|
}
|
||||||
return PRESET_COLOR_MAP[color] || color;
|
return PRESET_COLOR_MAP[color] || color;
|
||||||
|
|||||||
@ -1,19 +1,26 @@
|
|||||||
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 = {
|
||||||
@ -21,13 +28,13 @@ export const BASE_MEASURES = {
|
|||||||
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,
|
||||||
@ -46,15 +53,19 @@ export function getLeftOffset(m) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 +
|
||||||
|
m.paddings.top +
|
||||||
|
m.paddings.bottom +
|
||||||
|
m.titleHeight +
|
||||||
|
m.legendHeight;
|
||||||
return totalExtraHeight;
|
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;
|
||||||
}
|
}
|
||||||
@ -62,19 +73,19 @@ export function getExtraWidth(m) {
|
|||||||
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,10 +97,39 @@ 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,
|
||||||
@ -97,7 +137,7 @@ export const DEFAULT_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,14 +6,53 @@ 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) {
|
||||||
@ -22,14 +61,20 @@ function treatAsUtc(date) {
|
|||||||
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) {
|
||||||
@ -61,8 +106,10 @@ export function getDaysBetween(startDate, endDate) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -79,7 +126,7 @@ export function setDayToSunday(date) {
|
|||||||
let newDate = clone(date);
|
let newDate = clone(date);
|
||||||
const day = newDate.getDay();
|
const day = newDate.getDay();
|
||||||
if (day !== 0) {
|
if (day !== 0) {
|
||||||
addDays(newDate, (-1) * day);
|
addDays(newDate, -1 * day);
|
||||||
}
|
}
|
||||||
return newDate;
|
return newDate;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
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;
|
||||||
@ -20,22 +21,19 @@ $.create = (tag, o) => {
|
|||||||
|
|
||||||
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") {
|
} else if (i === "styles") {
|
||||||
if (typeof val === "object") {
|
if (typeof val === "object") {
|
||||||
Object.keys(val).map(prop => {
|
Object.keys(val).map((prop) => {
|
||||||
element.style[prop] = val[prop];
|
element.style[prop] = val[prop];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (i in element) {
|
} else if (i in element) {
|
||||||
element[i] = val;
|
element[i] = val;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
element.setAttribute(i, val);
|
element.setAttribute(i, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,8 +47,12 @@ export function getOffset(element) {
|
|||||||
// 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),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +60,7 @@ export function getOffset(element) {
|
|||||||
// 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) {
|
||||||
@ -68,15 +70,19 @@ export function isElementInViewport(el) {
|
|||||||
return (
|
return (
|
||||||
rect.top >= 0 &&
|
rect.top >= 0 &&
|
||||||
rect.left >= 0 &&
|
rect.left >= 0 &&
|
||||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
|
rect.bottom <=
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -125,7 +131,13 @@ export function forEachNode(nodeList, callback, scope) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function activate($parent, $child, commonClass, activeClass='active', index = -1) {
|
export function activate(
|
||||||
|
$parent,
|
||||||
|
$child,
|
||||||
|
commonClass,
|
||||||
|
activeClass = "active",
|
||||||
|
index = -1
|
||||||
|
) {
|
||||||
let $children = $parent.querySelectorAll(`.${commonClass}.${activeClass}`);
|
let $children = $parent.querySelectorAll(`.${commonClass}.${activeClass}`);
|
||||||
|
|
||||||
forEachNode($children, (node, i) => {
|
forEachNode($children, (node, i) => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { fillArray } from './helpers';
|
import { fillArray } from "./helpers";
|
||||||
|
|
||||||
export function getBarHeightAndYAttr(yTop, zeroLine) {
|
export function getBarHeightAndYAttr(yTop, zeroLine) {
|
||||||
let height, y;
|
let height, y;
|
||||||
@ -13,9 +13,11 @@ export function getBarHeightAndYAttr(yTop, zeroLine) {
|
|||||||
return [height, y];
|
return [height, y];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function equilizeNoOfElements(array1, array2,
|
export function equilizeNoOfElements(
|
||||||
extraCount = array2.length - array1.length) {
|
array1,
|
||||||
|
array2,
|
||||||
|
extraCount = array2.length - array1.length
|
||||||
|
) {
|
||||||
// Doesn't work if either has zero elements.
|
// Doesn't work if either has zero elements.
|
||||||
if (extraCount > 0) {
|
if (extraCount > 0) {
|
||||||
array1 = fillArray(array1, extraCount);
|
array1 = fillArray(array1, extraCount);
|
||||||
@ -30,7 +32,7 @@ export function truncateString(txt, len) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (txt.length > len) {
|
if (txt.length > len) {
|
||||||
return txt.slice(0, len-3) + '...';
|
return txt.slice(0, len - 3) + "...";
|
||||||
} else {
|
} else {
|
||||||
return txt;
|
return txt;
|
||||||
}
|
}
|
||||||
@ -38,8 +40,8 @@ export function truncateString(txt, len) {
|
|||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
@ -48,15 +50,15 @@ export function shortenLargeNumber(label) {
|
|||||||
let p = Math.floor(Math.log10(Math.abs(number)));
|
let p = Math.floor(Math.log10(Math.abs(number)));
|
||||||
if (p <= 2) return number; // Return as is for a 3 digit number of less
|
if (p <= 2) return number; // Return as is for a 3 digit number of less
|
||||||
let l = Math.floor(p / 3);
|
let l = Math.floor(p / 3);
|
||||||
let shortened = (Math.pow(10, p - l * 3) * +(number / Math.pow(10, p)).toFixed(1));
|
let shortened =
|
||||||
|
Math.pow(10, p - l * 3) * +(number / Math.pow(10, p)).toFixed(1);
|
||||||
|
|
||||||
// Correct for floating point error upto 2 decimal places
|
// Correct for floating point error upto 2 decimal places
|
||||||
return Math.round(shortened*100)/100 + ' ' + ['', 'K', 'M', 'B', 'T'][l];
|
return Math.round(shortened * 100) / 100 + " " + ["", "K", "M", "B", "T"][l];
|
||||||
}
|
}
|
||||||
|
|
||||||
// cubic bezier curve calculation (from example by François Romain)
|
// cubic bezier curve calculation (from example by François Romain)
|
||||||
export function getSplineCurvePointsStr(xList, yList) {
|
export function getSplineCurvePointsStr(xList, yList) {
|
||||||
|
|
||||||
let points = [];
|
let points = [];
|
||||||
for (let i = 0; i < xList.length; i++) {
|
for (let i = 0; i < xList.length; i++) {
|
||||||
points.push([xList[i], yList[i]]);
|
points.push([xList[i], yList[i]]);
|
||||||
@ -68,7 +70,7 @@ export function getSplineCurvePointsStr(xList, yList) {
|
|||||||
let lengthY = pointB[1] - pointA[1];
|
let lengthY = pointB[1] - pointA[1];
|
||||||
return {
|
return {
|
||||||
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
|
length: Math.sqrt(Math.pow(lengthX, 2) + Math.pow(lengthY, 2)),
|
||||||
angle: Math.atan2(lengthY, lengthX)
|
angle: Math.atan2(lengthY, lengthX),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -90,9 +92,11 @@ export function getSplineCurvePointsStr(xList, yList) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let pointStr = (points, command) => {
|
let pointStr = (points, command) => {
|
||||||
return points.reduce((acc, point, i, a) => i === 0
|
return points.reduce(
|
||||||
? `${point[0]},${point[1]}`
|
(acc, point, i, a) =>
|
||||||
: `${acc} ${command(point, i, a)}`, '');
|
i === 0 ? `${point[0]},${point[1]}` : `${acc} ${command(point, i, a)}`,
|
||||||
|
""
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return pointStr(points, bezierCommand);
|
return pointStr(points, bezierCommand);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
|||||||
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);
|
||||||
@ -18,15 +18,15 @@ export function downloadFile(filename, data) {
|
|||||||
|
|
||||||
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.
|
||||||
@ -47,7 +47,7 @@ export function shuffle(array) {
|
|||||||
* @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);
|
||||||
@ -73,7 +73,7 @@ export function bindChange(obj, getFn, setFn) {
|
|||||||
get: function (target, prop) {
|
get: function (target, prop) {
|
||||||
getFn();
|
getFn();
|
||||||
return Reflect.get(target, prop);
|
return Reflect.get(target, prop);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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,4 +1,4 @@
|
|||||||
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
|
||||||
@ -69,11 +69,19 @@ function getChartIntervals(maxValue, minValue=0) {
|
|||||||
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) => {
|
||||||
|
// 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;
|
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
|
||||||
@ -82,8 +90,17 @@ export function calcChartIntervals(values, withMinimum=false) {
|
|||||||
let maxValue = Math.max(...values);
|
let maxValue = Math.max(...values);
|
||||||
let minValue = Math.min(...values);
|
let minValue = Math.min(...values);
|
||||||
|
|
||||||
|
if (overrideCeiling) {
|
||||||
|
maxValue = overrideCeiling
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overrideFloor) {
|
||||||
|
minValue = overrideFloor
|
||||||
|
}
|
||||||
|
|
||||||
// Exponent to be used for pretty print
|
// Exponent to be used for pretty print
|
||||||
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars
|
let exponent = 0,
|
||||||
|
intervals = []; // eslint-disable-line no-unused-vars
|
||||||
|
|
||||||
function getPositiveFirstIntervals(maxValue, absMinValue) {
|
function getPositiveFirstIntervals(maxValue, absMinValue) {
|
||||||
let intervals = getChartIntervals(maxValue);
|
let intervals = getChartIntervals(maxValue);
|
||||||
@ -94,7 +111,7 @@ export function calcChartIntervals(values, withMinimum=false) {
|
|||||||
let value = 0;
|
let value = 0;
|
||||||
for (var i = 1; value < absMinValue; i++) {
|
for (var i = 1; value < absMinValue; i++) {
|
||||||
value += intervalSize;
|
value += intervalSize;
|
||||||
intervals.unshift((-1) * value);
|
intervals.unshift(-1 * value);
|
||||||
}
|
}
|
||||||
return intervals;
|
return intervals;
|
||||||
}
|
}
|
||||||
@ -111,7 +128,6 @@ export function calcChartIntervals(values, withMinimum=false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CASE II: Only minValue negative
|
// CASE II: Only minValue negative
|
||||||
|
|
||||||
else if (maxValue > 0 && minValue < 0) {
|
else if (maxValue > 0 && minValue < 0) {
|
||||||
// `withMinimum` irrelevant in this case,
|
// `withMinimum` irrelevant in this case,
|
||||||
// We'll be handling both sides of zero separately
|
// We'll be handling both sides of zero separately
|
||||||
@ -128,13 +144,11 @@ export function calcChartIntervals(values, withMinimum=false) {
|
|||||||
// Mirror: maxValue => absMinValue, then change sign
|
// Mirror: maxValue => absMinValue, then change sign
|
||||||
exponent = normalize(absMinValue)[1];
|
exponent = normalize(absMinValue)[1];
|
||||||
let posIntervals = getPositiveFirstIntervals(absMinValue, maxValue);
|
let posIntervals = getPositiveFirstIntervals(absMinValue, maxValue);
|
||||||
intervals = posIntervals.reverse().map(d => d * (-1));
|
intervals = posIntervals.reverse().map((d) => d * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE III: Both non-positive
|
// CASE III: Both non-positive
|
||||||
|
|
||||||
else if (maxValue <= 0 && minValue <= 0) {
|
else if (maxValue <= 0 && minValue <= 0) {
|
||||||
// Mirrored Case I:
|
// Mirrored Case I:
|
||||||
// Work with positives, then reverse the sign and array
|
// Work with positives, then reverse the sign and array
|
||||||
@ -149,10 +163,10 @@ export function calcChartIntervals(values, withMinimum=false) {
|
|||||||
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue);
|
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
intervals = intervals.reverse().map(d => d * (-1));
|
intervals = intervals.reverse().map((d) => d * -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return intervals;
|
return intervals.sort((a, b) => a - b);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getZeroIndex(yPts) {
|
export function getZeroIndex(yPts) {
|
||||||
@ -166,19 +180,19 @@ export function getZeroIndex(yPts) {
|
|||||||
// 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++) {
|
||||||
@ -205,13 +219,15 @@ export function isInRange(val, min, 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;
|
||||||
@ -235,5 +251,5 @@ export function calcDistribution(values, distributionSize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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