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"
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
"tab"
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"indent": ["error", "tab"],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"semi": ["error", "always"],
|
||||
"no-console": [
|
||||
"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
|
||||
|
||||
# npm build output
|
||||
dist
|
||||
docs
|
||||
docs/assets/
|
||||
|
||||
.DS_Store
|
||||
154
README.md
154
README.md
@ -1,69 +1,62 @@
|
||||
<div align="center">
|
||||
<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">
|
||||
<h2>Frappe Charts</h2>
|
||||
</a>
|
||||
<div align="center" markdown="1">
|
||||
|
||||
<img width="80" alt="charts-logo" src="https://github.com/user-attachments/assets/37b7ffaf-8354-48f2-8b9c-fa04fae0135b" />
|
||||
|
||||
# Frappe Charts
|
||||
**GitHub-inspired modern, intuitive and responsive charts with zero dependencies**
|
||||
|
||||
<p align="center">
|
||||
<p>GitHub-inspired modern, intuitive and responsive charts with zero dependencies</p>
|
||||
<a href="https://frappe.github.io/charts">
|
||||
<b>Explore Demos » </b>
|
||||
</a>
|
||||
<a href="https://codepen.io/pratu16x7/pen/wjKBoq">
|
||||
<b> Edit at CodePen »</b>
|
||||
<a href="https://bundlephobia.com/result?p=frappe-charts">
|
||||
<img src="https://img.shields.io/bundlephobia/minzip/frappe-charts">
|
||||
</a>
|
||||
</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>
|
||||
|
||||
<p align="center">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://frappe.github.io/charts">
|
||||
<img src=".github/example.gif">
|
||||
</a>
|
||||
</p>
|
||||
## Frappe 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.
|
||||
|
||||
### Contents
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [Contribute](https://frappe.io/charts/docs/contributing)
|
||||
* [Updates](#updates)
|
||||
* [License](#license)
|
||||
### Motivation
|
||||
|
||||
#### Installation
|
||||
* Install via [`npm`](https://www.npmjs.com/get-npm):
|
||||
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.
|
||||
|
||||
### 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
|
||||
$ npm install frappe-charts
|
||||
npm install frappe-charts
|
||||
```
|
||||
|
||||
and include in your project:
|
||||
```js
|
||||
import { Chart } from "frappe-charts"
|
||||
```
|
||||
|
||||
...or include following for es-modules(eg:vuejs):
|
||||
Import in your project:
|
||||
```js
|
||||
import { Chart } from 'frappe-charts'
|
||||
// or esm import
|
||||
import { Chart } from 'frappe-charts/dist/frappe-charts.esm.js'
|
||||
// import css
|
||||
import 'frappe-charts/dist/frappe-charts.min.css'
|
||||
```
|
||||
|
||||
* ...or include within your HTML
|
||||
Or directly include script in your HTML
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/frappe-charts@1.1.0/dist/frappe-charts.min.iife.js"></script>
|
||||
<!-- or -->
|
||||
<script src="https://unpkg.com/frappe-charts@1.1.0/dist/frappe-charts.min.iife.js"></script>
|
||||
<script src="https://unpkg.com/frappe-charts@1.6.1/dist/frappe-charts.min.umd.js"></script>
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
```js
|
||||
const data = {
|
||||
labels: ["12am-3am", "3am-6pm", "6am-9am", "9am-12am",
|
||||
@ -71,11 +64,11 @@ const data = {
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
name: "Some Data", type: "bar",
|
||||
name: "Some Data", chartType: "bar",
|
||||
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]
|
||||
}
|
||||
]
|
||||
@ -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()`):
|
||||
```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:
|
||||
## Contributing
|
||||
|
||||
1. Clone this repo.
|
||||
2. `cd` into project directory
|
||||
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
|
||||
- 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.
|
||||
- [Read the blog](https://medium.com/@pratu16x7/so-we-decided-to-create-our-own-charts-a95cb5032c97)
|
||||
|
||||
##### 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 () {
|
||||
'use strict';
|
||||
|
||||
// Fixed 5-color theme,
|
||||
// More colors are difficult to parse visually
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'];
|
||||
var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
|
||||
|
||||
|
||||
// Universal constants
|
||||
|
||||
/**
|
||||
* Returns the value of a number upto 2 decimal places.
|
||||
* @param {Number} d Any number
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not two given arrays are equal.
|
||||
* @param {Array} arr1 First array
|
||||
* @param {Array} arr2 Second array
|
||||
*/
|
||||
|
||||
var ANGLE_RATIO = Math.PI / 180;
|
||||
|
||||
/**
|
||||
* Shuffles array in place. ES6 version
|
||||
@ -51,24 +26,6 @@ function shuffle(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
|
||||
function getRandomBias(min, max, bias, influence) {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Check if a number is valid for svg attributes
|
||||
* @param {object} candidate Candidate to test
|
||||
* @param {Boolean} nonNegative flag to treat negative number as invalid
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Round a number to the closes precision, max max precision 4
|
||||
* @param {Number} d Any Number
|
||||
*/
|
||||
|
||||
// Playing around with dates
|
||||
|
||||
|
||||
|
||||
|
||||
var NO_OF_MILLIS = 1000;
|
||||
var SEC_IN_DAY = 86400;
|
||||
|
||||
|
||||
var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function clone(date) {
|
||||
return new Date(date.getTime());
|
||||
}
|
||||
@ -127,21 +59,6 @@ function timestampToMidnight(timestamp) {
|
||||
return midnightTs;
|
||||
}
|
||||
|
||||
// export function getMonthsBetween(startDate, endDate) {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// mutates
|
||||
|
||||
|
||||
// mutates
|
||||
function addDays(date, 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';
|
||||
// ================================================================================
|
||||
|
||||
|
||||
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",
|
||||
"version": "1.5.2",
|
||||
"main": "dist/frappe-charts.cjs.js",
|
||||
"common": "dist/frappe-charts.cjs.js",
|
||||
"version": "v1.6.3",
|
||||
"type": "module",
|
||||
"main": "dist/frappe-charts.esm.js",
|
||||
"module": "dist/frappe-charts.esm.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",
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
@ -37,8 +38,10 @@
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.5",
|
||||
"@babel/preset-env": "^7.10.4",
|
||||
"node-sass": "^8.0.0",
|
||||
"rollup": "^2.21.0",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
"rollup-plugin-bundle-size": "^1.0.3",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-eslint": "^7.0.0",
|
||||
"rollup-plugin-postcss": "^3.1.3",
|
||||
|
||||
@ -1,43 +1,47 @@
|
||||
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 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 bundleSize from "rollup-plugin-bundle-size";
|
||||
import { terser } from "rollup-plugin-terser";
|
||||
|
||||
export default [
|
||||
// browser-friendly UMD build
|
||||
{
|
||||
input: 'src/js/index.js',
|
||||
input: "src/js/index.js",
|
||||
output: {
|
||||
name: 'frappe-charts',
|
||||
sourcemap: true,
|
||||
name: "frappe",
|
||||
file: pkg.browser,
|
||||
format: 'umd'
|
||||
format: "umd",
|
||||
},
|
||||
plugins: [
|
||||
commonjs(),
|
||||
babel({
|
||||
exclude: ['node_modules/**']
|
||||
exclude: ["node_modules/**"],
|
||||
}),
|
||||
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.
|
||||
{
|
||||
input: 'src/js/chart.js',
|
||||
input: "src/js/chart.js",
|
||||
output: [
|
||||
{ file: pkg.common, format: 'cjs' },
|
||||
{ file: pkg.module, format: 'es' }
|
||||
{ file: pkg.common, format: "cjs", sourcemap: true },
|
||||
{ file: pkg.module, format: "es", sourcemap: true },
|
||||
],
|
||||
plugins: [
|
||||
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 {
|
||||
--fr-label-color: #313b44;
|
||||
--fr-axis-line-color: #E2E6E9;
|
||||
--charts-label-color: #313b44;
|
||||
--charts-axis-line-color: #f4f5f6;
|
||||
|
||||
--fr-stroke-width: 2px;
|
||||
--fr-dataset-circle-stroke: #FFFFFF;
|
||||
--fr-dataset-circle-stroke-width: var(--fr-stroke-width);
|
||||
--charts-tooltip-title: var(--charts-label-color);
|
||||
--charts-tooltip-label: var(--charts-label-color);
|
||||
--charts-tooltip-value: #192734;
|
||||
--charts-tooltip-bg: #ffffff;
|
||||
|
||||
--fr-tooltip-title: var(--fr-label-color);
|
||||
--fr-tooltip-label: var(--fr-label-color);
|
||||
--fr-tooltip-value: #192734;
|
||||
--fr-tooltip-bg: #FFFFFF;
|
||||
--charts-stroke-width: 2px;
|
||||
--charts-dataset-circle-stroke: #ffffff;
|
||||
--charts-dataset-circle-stroke-width: var(--charts-stroke-width);
|
||||
|
||||
--charts-legend-label: var(--charts-label-color);
|
||||
--charts-legend-value: var(--charts-label-color);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
position: relative; /* for absolutely positioned tooltip */
|
||||
position: relative;
|
||||
/* for absolutely positioned tooltip */
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell',
|
||||
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
|
||||
.axis, .chart-label {
|
||||
fill: var(--fr-label-color);
|
||||
.axis,
|
||||
.chart-label {
|
||||
fill: var(--charts-label-color);
|
||||
|
||||
line {
|
||||
stroke: var(--fr-axis-line-color);
|
||||
stroke: var(--charts-axis-line-color);
|
||||
}
|
||||
}
|
||||
|
||||
.dataset-units {
|
||||
circle {
|
||||
stroke: var(--fr-dataset-circle-stroke);
|
||||
stroke-width: var(--fr-dataset-circle-stroke-width);
|
||||
stroke: var(--charts-dataset-circle-stroke);
|
||||
stroke-width: var(--charts-dataset-circle-stroke-width);
|
||||
}
|
||||
|
||||
path {
|
||||
fill: none;
|
||||
stroke-opacity: 1;
|
||||
stroke-width: var(--fr-stroke-width);
|
||||
stroke-width: var(--charts-stroke-width);
|
||||
}
|
||||
}
|
||||
|
||||
.dataset-path {
|
||||
stroke-width: var(--fr-stroke-width);
|
||||
stroke-width: var(--charts-stroke-width);
|
||||
}
|
||||
|
||||
.path-group {
|
||||
path {
|
||||
fill: none;
|
||||
stroke-opacity: 1;
|
||||
stroke-width: var(--fr-stroke-width);
|
||||
stroke-width: var(--charts-stroke-width);
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,21 +65,23 @@
|
||||
.specific-value {
|
||||
text-anchor: start;
|
||||
}
|
||||
|
||||
.y-line {
|
||||
text-anchor: end;
|
||||
}
|
||||
|
||||
.x-line {
|
||||
text-anchor: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.legend-dataset-label {
|
||||
fill: var(--fr-tooltip-label);
|
||||
fill: var(--charts-legend-label);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.legend-dataset-value {
|
||||
fill: var(--fr-tooltip-value);
|
||||
fill: var(--charts-legend-value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,8 +91,10 @@
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
background: var(--fr-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);
|
||||
background: var(--charts-tooltip-bg);
|
||||
box-shadow: 0px 1px 4px rgba(17, 43, 66, 0.1),
|
||||
0px 2px 6px rgba(17, 43, 66, 0.08),
|
||||
0px 40px 30px -30px rgba(17, 43, 66, 0.1);
|
||||
border-radius: 6px;
|
||||
|
||||
ul {
|
||||
@ -110,7 +119,7 @@
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border-radius: 2px;
|
||||
background: var(--fr-tooltip-bg);
|
||||
background: var(--charts-tooltip-bg);
|
||||
transform: rotate(45deg);
|
||||
margin-top: -7px;
|
||||
margin-left: -6px;
|
||||
@ -125,11 +134,15 @@
|
||||
display: block;
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
color: var(--fr-tooltip-title);
|
||||
color: var(--charts-tooltip-title);
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
text-transform: uppercase;
|
||||
|
||||
strong {
|
||||
color: var(--charts-tooltip-value);
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
@ -172,7 +185,7 @@
|
||||
}
|
||||
|
||||
.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 PieChart from './charts/PieChart';
|
||||
import Heatmap from './charts/Heatmap';
|
||||
import AxisChart from './charts/AxisChart';
|
||||
import DonutChart from './charts/DonutChart';
|
||||
import PercentageChart from "./charts/PercentageChart";
|
||||
import PieChart from "./charts/PieChart";
|
||||
import Heatmap from "./charts/Heatmap";
|
||||
import AxisChart from "./charts/AxisChart";
|
||||
import DonutChart from "./charts/DonutChart";
|
||||
|
||||
const chartTypes = {
|
||||
bar: AxisChart,
|
||||
line: AxisChart,
|
||||
// multiaxis: MultiAxisChart,
|
||||
percentage: PercentageChart,
|
||||
heatmap: Heatmap,
|
||||
pie: PieChart,
|
||||
donut: DonutChart,
|
||||
};
|
||||
|
||||
function getChartByType(chartType = 'line', parent, options) {
|
||||
if (chartType === 'axis-mixed') {
|
||||
options.type = 'line';
|
||||
function getChartByType(chartType = "line", parent, options) {
|
||||
if (chartType === "axis-mixed") {
|
||||
options.type = "line";
|
||||
return new AxisChart(parent, options);
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { truncateString } from '../utils/draw-utils';
|
||||
import { legendDot } from '../utils/draw';
|
||||
import { round } from '../utils/helpers';
|
||||
import { getExtraWidth } from '../utils/constants';
|
||||
import BaseChart from "./BaseChart";
|
||||
import { truncateString } from "../utils/draw-utils";
|
||||
import { legendDot } from "../utils/draw";
|
||||
import { round } from "../utils/helpers";
|
||||
import { getExtraWidth } from "../utils/constants";
|
||||
|
||||
export default class AggregationChart extends BaseChart {
|
||||
constructor(parent, args) {
|
||||
@ -15,6 +15,7 @@ export default class AggregationChart extends BaseChart {
|
||||
this.config.formatTooltipY = (args.tooltipOptions || {}).formatTooltipY;
|
||||
this.config.maxSlices = args.maxSlices || 20;
|
||||
this.config.maxLegendPoints = args.maxLegendPoints || 20;
|
||||
this.config.legendRowHeight = 60;
|
||||
}
|
||||
|
||||
calc() {
|
||||
@ -22,30 +23,38 @@ export default class AggregationChart extends BaseChart {
|
||||
let maxSlices = this.config.maxSlices;
|
||||
s.sliceTotals = [];
|
||||
|
||||
let allTotals = this.data.labels.map((label, i) => {
|
||||
let allTotals = this.data.labels
|
||||
.map((label, i) => {
|
||||
let total = 0;
|
||||
this.data.datasets.map(e => {
|
||||
this.data.datasets.map((e) => {
|
||||
total += e.values[i];
|
||||
});
|
||||
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;
|
||||
if (allTotals.length > maxSlices) {
|
||||
// Prune and keep a grey area for rest as per maxSlices
|
||||
allTotals.sort((a, b) => { return b[0] - a[0]; });
|
||||
allTotals.sort((a, b) => {
|
||||
return b[0] - a[0];
|
||||
});
|
||||
|
||||
totals = allTotals.slice(0, maxSlices - 1);
|
||||
let remaining = allTotals.slice(maxSlices - 1);
|
||||
|
||||
let sumOfRemaining = 0;
|
||||
remaining.map(d => {sumOfRemaining += d[0];});
|
||||
totals.push([sumOfRemaining, 'Rest']);
|
||||
this.colors[maxSlices-1] = 'grey';
|
||||
remaining.map((d) => {
|
||||
sumOfRemaining += d[0];
|
||||
});
|
||||
totals.push([sumOfRemaining, "Rest"]);
|
||||
this.colors[maxSlices - 1] = "grey";
|
||||
}
|
||||
|
||||
s.labels = [];
|
||||
totals.map(d => {
|
||||
totals.map((d) => {
|
||||
s.sliceTotals.push(round(d[0]));
|
||||
s.labels.push(d[1]);
|
||||
});
|
||||
@ -54,44 +63,32 @@ export default class AggregationChart extends BaseChart {
|
||||
|
||||
this.center = {
|
||||
x: this.width / 2,
|
||||
y: this.height / 2
|
||||
y: this.height / 2,
|
||||
};
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
let s = this.state;
|
||||
this.legendArea.textContent = '';
|
||||
this.legendArea.textContent = "";
|
||||
this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints);
|
||||
super.renderLegend(this.legendTotals);
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
let y = 0;
|
||||
this.legendTotals.map((d, i) => {
|
||||
let barWidth = 150;
|
||||
let divisor = Math.floor(
|
||||
(this.width - getExtraWidth(this.measures))/barWidth
|
||||
makeLegend(data, index, x_pos, y_pos) {
|
||||
let formatted = this.config.formatTooltipY
|
||||
? this.config.formatTooltipY(data)
|
||||
: data;
|
||||
|
||||
return legendDot(
|
||||
x_pos,
|
||||
y_pos,
|
||||
12, // size
|
||||
3, // dot radius
|
||||
this.colors[index], // fill
|
||||
this.state.labels[index], // label
|
||||
formatted, // value
|
||||
null, // base_font_size
|
||||
this.config.truncateLegends // truncate_legends
|
||||
);
|
||||
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 { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils';
|
||||
import { AXIS_LEGEND_BAR_SIZE } from '../utils/constants';
|
||||
import { getComponent } from '../objects/ChartComponents';
|
||||
import { getOffset, fire } from '../utils/dom';
|
||||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals';
|
||||
import { floatTwo } from '../utils/helpers';
|
||||
import { makeOverlay, updateOverlay, legendBar } from '../utils/draw';
|
||||
import { getTopOffset, getLeftOffset, MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO,
|
||||
LINE_CHART_DOT_SIZE } from '../utils/constants';
|
||||
import BaseChart from "./BaseChart";
|
||||
import {
|
||||
dataPrep,
|
||||
zeroDataPrep,
|
||||
getShortenedLabels,
|
||||
} from "../utils/axis-chart-utils";
|
||||
import { getComponent } from "../objects/ChartComponents";
|
||||
import { getOffset, fire } from "../utils/dom";
|
||||
import {
|
||||
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 {
|
||||
constructor(parent, args) {
|
||||
@ -16,7 +32,7 @@ export default class AxisChart extends BaseChart {
|
||||
this.barOptions = args.barOptions || {};
|
||||
this.lineOptions = args.lineOptions || {};
|
||||
|
||||
this.type = args.type || 'line';
|
||||
this.type = args.type || "line";
|
||||
this.init = 1;
|
||||
|
||||
this.setup();
|
||||
@ -31,23 +47,50 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
configure(options) {
|
||||
super.configure(options);
|
||||
const { axisOptions = {} } = options;
|
||||
const { xAxis, yAxis } = axisOptions || {};
|
||||
|
||||
options.axisOptions = options.axisOptions || {};
|
||||
options.tooltipOptions = options.tooltipOptions || {};
|
||||
|
||||
this.config.xAxisMode = options.axisOptions.xAxisMode || 'span';
|
||||
this.config.yAxisMode = options.axisOptions.yAxisMode || 'span';
|
||||
this.config.xIsSeries = options.axisOptions.xIsSeries || 0;
|
||||
this.config.shortenYAxisNumbers = options.axisOptions.shortenYAxisNumbers || 0;
|
||||
this.config.xAxisMode = xAxis
|
||||
? xAxis.xAxisMode
|
||||
: axisOptions.xAxisMode || "span";
|
||||
|
||||
// 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.formatTooltipY = options.tooltipOptions.formatTooltipY;
|
||||
|
||||
this.config.valuesOverPoints = options.valuesOverPoints;
|
||||
this.config.legendRowHeight = 30;
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
return dataPrep(data, this.type);
|
||||
prepareData(data = this.data, config = this.config) {
|
||||
return dataPrep(data, this.type, config.continuous);
|
||||
}
|
||||
|
||||
prepareFirstData(data = this.data) {
|
||||
@ -57,7 +100,10 @@ export default class AxisChart extends BaseChart {
|
||||
calc(onlyWidthChange = false) {
|
||||
this.calcXPositions();
|
||||
if (!onlyWidthChange) {
|
||||
this.calcYAxisParameters(this.getAllYValues(), this.type === 'line');
|
||||
this.calcYAxisParameters(
|
||||
this.getAllYValues(),
|
||||
this.type === "line"
|
||||
);
|
||||
}
|
||||
this.makeDataByIndex();
|
||||
}
|
||||
@ -67,7 +113,7 @@ export default class AxisChart extends BaseChart {
|
||||
let labels = this.data.labels;
|
||||
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
|
||||
s.xOffset = s.unitWidth / 2;
|
||||
|
||||
@ -79,22 +125,121 @@ export default class AxisChart extends BaseChart {
|
||||
labels: labels,
|
||||
positions: labels.map((d, i) =>
|
||||
floatTwo(s.xOffset + i * s.unitWidth)
|
||||
)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
calcYAxisParameters(dataValues, withMinimum = 'false') {
|
||||
const yPts = calcChartIntervals(dataValues, withMinimum);
|
||||
const scaleMultiplier = this.height / getValueRange(yPts);
|
||||
const intervalHeight = getIntervalSize(yPts) * scaleMultiplier;
|
||||
const zeroLine = this.height - (getZeroIndex(yPts) * intervalHeight);
|
||||
calcYAxisParameters(dataValues, withMinimum = "false") {
|
||||
let yPts,
|
||||
scaleMultiplier,
|
||||
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 = {
|
||||
labels: yPts,
|
||||
positions: yPts.map(d => zeroLine - d * scaleMultiplier),
|
||||
positions: yPts.map((d) => zeroLine - d * scaleMultiplier),
|
||||
title: yAxisConfigObject.title || null,
|
||||
pos: yAxisAlignment,
|
||||
scaleMultiplier: scaleMultiplier,
|
||||
zeroLine: zeroLine,
|
||||
};
|
||||
} else {
|
||||
this.state.yAxis = [];
|
||||
for (let key in dataValues) {
|
||||
const dataValue = dataValues[key];
|
||||
yAxisConfigObject =
|
||||
this.config.yAxisConfig.find((item) => key === item.id) ||
|
||||
[];
|
||||
yAxisAlignment = yAxisConfigObject.position
|
||||
? yAxisConfigObject.position
|
||||
: "left";
|
||||
yPts = calcChartIntervals(dataValue, withMinimum, this.config.overrideCeiling, this.config.overrideFloor);
|
||||
scaleMultiplier = this.height / getValueRange(yPts);
|
||||
intervalHeight = getIntervalSize(yPts) * scaleMultiplier;
|
||||
zeroLine = this.height - getZeroIndex(yPts) * intervalHeight;
|
||||
positions = yPts.map((d) => zeroLine - d * scaleMultiplier);
|
||||
yKeys.push(key);
|
||||
|
||||
if (this.state.yAxis.length > 1) {
|
||||
const yPtsArray = [];
|
||||
const firstArr = this.state.yAxis[0];
|
||||
|
||||
// we need to calculate the scaleMultiplier.
|
||||
|
||||
// now that we have an accurate scaleMultiplier we can
|
||||
// we need to loop through original positions.
|
||||
scaleMultiplier = this.height / getValueRange(yPts);
|
||||
firstArr.positions.forEach((pos) => {
|
||||
yPtsArray.push(Math.ceil(pos / scaleMultiplier));
|
||||
});
|
||||
yPts = yPtsArray.reverse();
|
||||
zeroLine =
|
||||
this.height - getZeroIndex(yPts) * intervalHeight;
|
||||
positions = firstArr.positions;
|
||||
}
|
||||
|
||||
this.state.yAxis.push({
|
||||
axisID: key || "left-axis",
|
||||
labels: yPts,
|
||||
title: yAxisConfigObject.title,
|
||||
pos: yAxisAlignment,
|
||||
scaleMultiplier,
|
||||
zeroLine,
|
||||
positions,
|
||||
});
|
||||
}
|
||||
|
||||
// the labels are not aligned in length between the two yAxis objects,
|
||||
// we need to run some new calculations.
|
||||
if (
|
||||
this.state.yAxis[1] &&
|
||||
this.state.yAxis[0].labels.length !==
|
||||
this.state.yAxis[1].labels.length
|
||||
) {
|
||||
const newYptsArr = [];
|
||||
// find the shorter array
|
||||
const shortest = this.state.yAxis.reduce(
|
||||
(p, c) => {
|
||||
return p.length > c.labels.length ? c : p;
|
||||
},
|
||||
{ length: Infinity }
|
||||
);
|
||||
// return the longest
|
||||
const longest = this.state.yAxis.reduce(
|
||||
(p, c) => {
|
||||
return p.length < c.labels.length ? p : c;
|
||||
},
|
||||
{ length: Infinity }
|
||||
);
|
||||
|
||||
// we now need to populate the shortest obj with the new scale multiplier
|
||||
// with the positions of the longest obj.
|
||||
longest.positions.forEach((pos) => {
|
||||
// calculate a new yPts
|
||||
newYptsArr.push(Math.ceil(pos / shortest.scaleMultiplier));
|
||||
});
|
||||
|
||||
shortest.labels = newYptsArr.reverse();
|
||||
shortest.positions = longest.positions;
|
||||
}
|
||||
}
|
||||
|
||||
// Dependent if above changes
|
||||
this.calcDatasetPoints();
|
||||
@ -104,21 +249,43 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
calcDatasetPoints() {
|
||||
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) => {
|
||||
let values = d.values;
|
||||
let cumulativeYs = d.cumulativeYs || [];
|
||||
|
||||
return {
|
||||
name: d.name.replace(/<|>|&/g, (char) => char == '&' ? '&' : char == '<' ? '<' : '>'),
|
||||
name:
|
||||
d.name &&
|
||||
d.name.replace(/<|>|&/g, (char) =>
|
||||
char == "&" ? "&" : char == "<" ? "<" : ">"
|
||||
),
|
||||
index: i,
|
||||
barIndex:
|
||||
d.chartType === "bar" ? s.barChartIndex++ : s.barChartIndex,
|
||||
chartType: d.chartType,
|
||||
|
||||
values: values,
|
||||
yPositions: scaleAll(values),
|
||||
yPositions: scaleAll(values, d.axisID),
|
||||
id: d.axisID,
|
||||
|
||||
cumulativeYs: cumulativeYs,
|
||||
cumulativeYPos: scaleAll(cumulativeYs),
|
||||
cumulativeYPos: scaleAll(cumulativeYs, d.axisID),
|
||||
};
|
||||
});
|
||||
}
|
||||
@ -130,7 +297,7 @@ export default class AxisChart extends BaseChart {
|
||||
return;
|
||||
}
|
||||
s.yExtremes = new Array(s.datasetLength).fill(9999);
|
||||
s.datasets.map(d => {
|
||||
s.datasets.map((d) => {
|
||||
d.yPositions.map((pos, j) => {
|
||||
if (pos < s.yExtremes[j]) {
|
||||
s.yExtremes[j] = pos;
|
||||
@ -142,7 +309,7 @@ export default class AxisChart extends BaseChart {
|
||||
calcYRegions() {
|
||||
let s = this.state;
|
||||
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);
|
||||
if (!d.options) d.options = {};
|
||||
// if(!d.label.includes(':')) {
|
||||
@ -152,7 +319,7 @@ export default class AxisChart extends BaseChart {
|
||||
});
|
||||
}
|
||||
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.endPos = scale(d.end, s.yAxis);
|
||||
if (!d.options) d.options = {};
|
||||
@ -162,47 +329,74 @@ export default class AxisChart extends BaseChart {
|
||||
}
|
||||
|
||||
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) {
|
||||
key = 'cumulativeYs';
|
||||
let cumulative = new Array(this.state.datasetLength).fill(0);
|
||||
this.data.datasets.map((d, i) => {
|
||||
let values = this.data.datasets[i].values;
|
||||
d[key] = cumulative = cumulative.map((c, i) => c + values[i]);
|
||||
key = "cumulativeYs";
|
||||
// we need to filter out the different yAxis ID's here.
|
||||
if (multiAxis) {
|
||||
const groupedDataSets = groupBy(this.data.datasets, "axisID");
|
||||
// const dataSetsByAxis = this.data.dd
|
||||
for (var axisID in groupedDataSets) {
|
||||
generateCumulative(groupedDataSets[axisID]);
|
||||
}
|
||||
} else {
|
||||
generateCumulative(this.data.datasets);
|
||||
}
|
||||
}
|
||||
|
||||
// this is the trouble maker, we don't want to merge all
|
||||
// datasets since we are trying to run two yAxis.
|
||||
if (multiAxis) {
|
||||
this.data.datasets.forEach((d) => {
|
||||
// if the array exists already just push more data into it.
|
||||
// otherwise create a new array into the object.
|
||||
allValueLists[d.axisID || key]
|
||||
? allValueLists[d.axisID || key].push(...d[key])
|
||||
: (allValueLists[d.axisID || key] = [...d[key]]);
|
||||
});
|
||||
} else {
|
||||
allValueLists = this.data.datasets.map((d) => {
|
||||
return d[key];
|
||||
});
|
||||
}
|
||||
|
||||
let allValueLists = this.data.datasets.map(d => d[key]);
|
||||
if(this.data.yMarkers) {
|
||||
allValueLists.push(this.data.yMarkers.map(d => d.value));
|
||||
if (this.data.yMarkers && !multiAxis) {
|
||||
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]);
|
||||
});
|
||||
}
|
||||
|
||||
return [].concat(...allValueLists);
|
||||
return multiAxis ? allValueLists : [].concat(...allValueLists); //return [].concat(...allValueLists); master
|
||||
}
|
||||
|
||||
setupComponents() {
|
||||
let componentConfigs = [
|
||||
[
|
||||
'yAxis',
|
||||
{
|
||||
mode: this.config.yAxisMode,
|
||||
width: this.width,
|
||||
shortenNumbers: this.config.shortenYAxisNumbers
|
||||
// pos: 'right'
|
||||
},
|
||||
function() {
|
||||
return this.state.yAxis;
|
||||
}.bind(this)
|
||||
],
|
||||
|
||||
[
|
||||
'xAxis',
|
||||
"xAxis",
|
||||
{
|
||||
mode: this.config.xAxisMode,
|
||||
height: this.height,
|
||||
@ -210,32 +404,73 @@ export default class AxisChart extends BaseChart {
|
||||
},
|
||||
function () {
|
||||
let s = this.state;
|
||||
s.xAxis.calcLabels = getShortenedLabels(this.width,
|
||||
s.xAxis.labels, this.config.xIsSeries);
|
||||
s.xAxis.calcLabels = getShortenedLabels(
|
||||
this.width,
|
||||
s.xAxis.labels,
|
||||
this.config.xIsSeries
|
||||
);
|
||||
|
||||
return s.xAxis;
|
||||
}.bind(this)
|
||||
}.bind(this),
|
||||
],
|
||||
|
||||
[
|
||||
'yRegions',
|
||||
"yRegions",
|
||||
{
|
||||
width: this.width,
|
||||
pos: 'right'
|
||||
pos: "right",
|
||||
},
|
||||
function () {
|
||||
return this.state.yRegions;
|
||||
}.bind(this)
|
||||
}.bind(this),
|
||||
],
|
||||
];
|
||||
|
||||
let barDatasets = this.state.datasets.filter(d => d.chartType === 'bar');
|
||||
let lineDatasets = this.state.datasets.filter(d => d.chartType === 'line');
|
||||
// if we have multiple yAxisConfigs we need to update the yAxisDefault
|
||||
// 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 barIndex = d.barIndex || index;
|
||||
return [
|
||||
'barGraph' + '-' + d.index,
|
||||
"barGraph" + "-" + d.index,
|
||||
{
|
||||
index: index,
|
||||
color: this.colors[index],
|
||||
@ -247,19 +482,38 @@ export default class AxisChart extends BaseChart {
|
||||
},
|
||||
function () {
|
||||
let s = this.state;
|
||||
let { yAxis } = s;
|
||||
let d = s.datasets[index];
|
||||
let { id = "left-axis" } = d;
|
||||
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 barWidth = barsWidth/(stacked ? 1 : barDatasets.length);
|
||||
let barWidth =
|
||||
barsWidth / (stacked ? 1 : barDatasets.length);
|
||||
|
||||
let xPositions = s.xAxis.positions.map(x => x - barsWidth/2);
|
||||
if(!stacked) {
|
||||
xPositions = xPositions.map(p => p + barWidth * index);
|
||||
// if there are multiple yAxis we need to return the yAxis with the
|
||||
// proper ID.
|
||||
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 (stacked && d.index === s.datasets.length - 1) {
|
||||
labels = d.cumulativeYs;
|
||||
@ -267,10 +521,11 @@ export default class AxisChart extends BaseChart {
|
||||
labels = d.values;
|
||||
}
|
||||
}
|
||||
|
||||
let offsets = new Array(s.datasetLength).fill(0);
|
||||
if (stacked) {
|
||||
offsets = d.yPositions.map((y, j) => y - d.cumulativeYPos[j]);
|
||||
offsets = d.yPositions.map(
|
||||
(y, j) => y - d.cumulativeYPos[j]
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -280,18 +535,18 @@ export default class AxisChart extends BaseChart {
|
||||
// values: d.values,
|
||||
labels: labels,
|
||||
|
||||
zeroLine: s.yAxis.zeroLine,
|
||||
zeroLine: yAxis.zeroLine,
|
||||
barsWidth: barsWidth,
|
||||
barWidth: barWidth,
|
||||
};
|
||||
}.bind(this)
|
||||
}.bind(this),
|
||||
];
|
||||
});
|
||||
|
||||
let lineConfigs = lineDatasets.map(d => {
|
||||
let lineConfigs = lineDatasets.map((d) => {
|
||||
let index = d.index;
|
||||
return [
|
||||
'lineGraph' + '-' + d.index,
|
||||
"lineGraph" + "-" + d.index,
|
||||
{
|
||||
index: index,
|
||||
color: this.colors[index],
|
||||
@ -308,8 +563,17 @@ export default class AxisChart extends BaseChart {
|
||||
function () {
|
||||
let s = this.state;
|
||||
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 {
|
||||
xPositions: s.xAxis.positions,
|
||||
@ -320,37 +584,49 @@ export default class AxisChart extends BaseChart {
|
||||
zeroLine: minLine,
|
||||
radius: this.lineOptions.dotSize || LINE_CHART_DOT_SIZE,
|
||||
};
|
||||
}.bind(this)
|
||||
}.bind(this),
|
||||
];
|
||||
});
|
||||
|
||||
let markerConfigs = [
|
||||
[
|
||||
'yMarkers',
|
||||
"yMarkers",
|
||||
{
|
||||
width: this.width,
|
||||
pos: 'right'
|
||||
pos: "right",
|
||||
},
|
||||
function () {
|
||||
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.components = new Map(componentConfigs
|
||||
.filter(args => !optionals.includes(args[0]) || this.state[args[0]])
|
||||
.map(args => {
|
||||
this.components = new Map(
|
||||
componentConfigs
|
||||
.filter(
|
||||
(args) =>
|
||||
!optionals.includes(args[0]) || this.state[args[0]]
|
||||
)
|
||||
.map((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);
|
||||
}
|
||||
return [args[0], component];
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
makeDataByIndex() {
|
||||
@ -385,14 +661,16 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
bindTooltip() {
|
||||
// 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 o = getOffset(this.container);
|
||||
let relX = e.pageX - o.left - getLeftOffset(m);
|
||||
let relY = e.pageY - o.top;
|
||||
|
||||
if(relY < this.height + getTopOffset(m)
|
||||
&& relY > getTopOffset(m)) {
|
||||
if (
|
||||
relY < this.height + getTopOffset(m) &&
|
||||
relY > getTopOffset(m)
|
||||
) {
|
||||
this.mapTooltipXPosition(relX);
|
||||
} else {
|
||||
this.tip.hideTip();
|
||||
@ -411,7 +689,7 @@ export default class AxisChart extends BaseChart {
|
||||
this.tip.setValues(
|
||||
dbi.xPos + this.tip.offset.x,
|
||||
dbi.yExtreme + this.tip.offset.y,
|
||||
{name: dbi.formattedLabel, value: ''},
|
||||
{ name: dbi.formattedLabel, value: "" },
|
||||
dbi.values,
|
||||
index
|
||||
);
|
||||
@ -423,26 +701,48 @@ export default class AxisChart extends BaseChart {
|
||||
renderLegend() {
|
||||
let s = this.data;
|
||||
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) => {
|
||||
let barWidth = AXIS_LEGEND_BAR_SIZE;
|
||||
let barWidth = LEGEND_ITEM_WIDTH;
|
||||
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right;
|
||||
// let multiplier = s.datasets.length - i;
|
||||
let rect = legendBar(
|
||||
// rightEndPoint - multiplier * barWidth, // To right align
|
||||
barWidth * i,
|
||||
'0',
|
||||
"0",
|
||||
barWidth,
|
||||
this.colors[i],
|
||||
d.name,
|
||||
this.config.truncateLegends);
|
||||
this.config.truncateLegends
|
||||
);
|
||||
this.legendArea.appendChild(rect);
|
||||
});
|
||||
}
|
||||
} */
|
||||
|
||||
makeLegend(data, index, x_pos, y_pos) {
|
||||
return legendDot(
|
||||
x_pos,
|
||||
y_pos + 5, // Extra offset
|
||||
12, // size
|
||||
3, // dot radius
|
||||
this.colors[index], // fill
|
||||
data.name, //label
|
||||
null, // value
|
||||
8.75, // base_font_size
|
||||
this.config.truncateLegends // truncate legends
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Overlay
|
||||
makeOverlay() {
|
||||
if (this.init) {
|
||||
@ -450,13 +750,13 @@ export default class AxisChart extends BaseChart {
|
||||
return;
|
||||
}
|
||||
if (this.overlayGuides) {
|
||||
this.overlayGuides.forEach(g => {
|
||||
this.overlayGuides.forEach((g) => {
|
||||
let o = g.overlay;
|
||||
o.parentNode.removeChild(o);
|
||||
});
|
||||
}
|
||||
|
||||
this.overlayGuides = this.dataUnitComponents.map(c => {
|
||||
this.overlayGuides = this.dataUnitComponents.map((c) => {
|
||||
return {
|
||||
type: c.unitType,
|
||||
overlay: undefined,
|
||||
@ -469,7 +769,7 @@ export default class AxisChart extends BaseChart {
|
||||
}
|
||||
|
||||
// Render overlays
|
||||
this.overlayGuides.map(d => {
|
||||
this.overlayGuides.map((d) => {
|
||||
let currentUnit = d.units[this.state.currentIndex];
|
||||
|
||||
d.overlay = makeOverlay[d.type](currentUnit);
|
||||
@ -479,7 +779,7 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
updateOverlayGuides() {
|
||||
if (this.overlayGuides) {
|
||||
this.overlayGuides.forEach(g => {
|
||||
this.overlayGuides.forEach((g) => {
|
||||
let o = g.overlay;
|
||||
o.parentNode.removeChild(o);
|
||||
});
|
||||
@ -487,30 +787,30 @@ export default class AxisChart extends BaseChart {
|
||||
}
|
||||
|
||||
bindOverlay() {
|
||||
this.parent.addEventListener('data-select', () => {
|
||||
this.parent.addEventListener("data-select", () => {
|
||||
this.updateOverlay();
|
||||
});
|
||||
}
|
||||
|
||||
bindUnits() {
|
||||
this.dataUnitComponents.map(c => {
|
||||
c.units.map(unit => {
|
||||
unit.addEventListener('click', () => {
|
||||
let index = unit.getAttribute('data-point-index');
|
||||
this.dataUnitComponents.map((c) => {
|
||||
c.units.map((unit) => {
|
||||
unit.addEventListener("click", () => {
|
||||
let index = unit.getAttribute("data-point-index");
|
||||
this.setCurrentDataPoint(index);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Note: Doesn't work as tooltip is absolutely positioned
|
||||
this.tip.container.addEventListener('click', () => {
|
||||
let index = this.tip.container.getAttribute('data-point-index');
|
||||
this.tip.container.addEventListener("click", () => {
|
||||
let index = this.tip.container.getAttribute("data-point-index");
|
||||
this.setCurrentDataPoint(index);
|
||||
});
|
||||
}
|
||||
|
||||
updateOverlay() {
|
||||
this.overlayGuides.map(d => {
|
||||
this.overlayGuides.map((d) => {
|
||||
let currentUnit = d.units[this.state.currentIndex];
|
||||
updateOverlay[d.type](currentUnit, d.overlay);
|
||||
});
|
||||
@ -529,7 +829,7 @@ export default class AxisChart extends BaseChart {
|
||||
let data_point = {
|
||||
index: 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;
|
||||
}
|
||||
@ -544,8 +844,6 @@ export default class AxisChart extends BaseChart {
|
||||
fire(this.parent, "data-select", this.getDataPoint());
|
||||
}
|
||||
|
||||
|
||||
|
||||
// API
|
||||
addDataPoint(label, datasetValues, index = this.state.datasetLength) {
|
||||
super.addDataPoint(label, datasetValues, index);
|
||||
@ -562,7 +860,7 @@ export default class AxisChart extends BaseChart {
|
||||
}
|
||||
super.removeDataPoint(index);
|
||||
this.data.labels.splice(index, 1);
|
||||
this.data.datasets.map(d => {
|
||||
this.data.datasets.map((d) => {
|
||||
d.values.splice(index, 1);
|
||||
});
|
||||
this.update(this.data);
|
||||
|
||||
@ -1,45 +1,83 @@
|
||||
import SvgTip from '../objects/SvgTip';
|
||||
import { $, isElementInViewport, getElementContentWidth, isHidden } from '../utils/dom';
|
||||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup, makeText } from '../utils/draw';
|
||||
import { BASE_MEASURES, getExtraHeight, getExtraWidth, getTopOffset, getLeftOffset,
|
||||
INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT, DEFAULT_COLORS} from '../utils/constants';
|
||||
import { getColor, isValidColor } from '../utils/colors';
|
||||
import { runSMILAnimation } from '../utils/animation';
|
||||
import { downloadFile, prepareForExport } from '../utils/export';
|
||||
import SvgTip from "../objects/SvgTip";
|
||||
import {
|
||||
$,
|
||||
isElementInViewport,
|
||||
getElementContentWidth,
|
||||
isHidden,
|
||||
} from "../utils/dom";
|
||||
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 {
|
||||
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)
|
||||
: parent;
|
||||
|
||||
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.title = options.title || '';
|
||||
this.type = options.type || '';
|
||||
|
||||
this.realData = this.prepareData(options.data);
|
||||
this.data = this.prepareFirstData(this.realData);
|
||||
this.title = options.title || "";
|
||||
this.type = options.type || "";
|
||||
|
||||
this.colors = this.validateColors(options.colors, this.type);
|
||||
|
||||
this.config = {
|
||||
showTooltip: 1, // calculate
|
||||
showLegend: 1, // calculate
|
||||
showLegend:
|
||||
typeof options.showLegend !== "undefined"
|
||||
? options.showLegend
|
||||
: 1,
|
||||
isNavigable: options.isNavigable || 0,
|
||||
animate: (typeof options.animate !== 'undefined') ? options.animate : 1,
|
||||
truncateLegends: options.truncateLegends || 1
|
||||
animate: 0,
|
||||
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));
|
||||
let m = this.measures;
|
||||
|
||||
this.realData = this.prepareData(options.data, this.config);
|
||||
this.data = this.prepareFirstData(this.realData);
|
||||
|
||||
this.setMeasures(options);
|
||||
if(!this.title.length) { m.titleHeight = 0; }
|
||||
if (!this.title.length) {
|
||||
m.titleHeight = 0;
|
||||
}
|
||||
if (!this.config.showLegend) m.legendHeight = 0;
|
||||
this.argHeight = options.height || m.baseHeight;
|
||||
|
||||
@ -89,13 +127,19 @@ export default class BaseChart {
|
||||
|
||||
// Bind window events
|
||||
this.boundDrawFn = () => this.draw(true);
|
||||
window.addEventListener('resize', this.boundDrawFn);
|
||||
window.addEventListener('orientationchange', this.boundDrawFn);
|
||||
// Look into improving responsiveness
|
||||
//if (ResizeObserver) {
|
||||
// this.resizeObserver = new ResizeObserver(this.boundDrawFn);
|
||||
// this.resizeObserver.observe(this.parent);
|
||||
//}
|
||||
window.addEventListener("resize", this.boundDrawFn);
|
||||
window.addEventListener("orientationchange", this.boundDrawFn);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
window.removeEventListener('resize', this.boundDrawFn);
|
||||
window.removeEventListener('orientationchange', this.boundDrawFn);
|
||||
//if (this.resizeObserver) this.resizeObserver.disconnect();
|
||||
window.removeEventListener("resize", this.boundDrawFn);
|
||||
window.removeEventListener("orientationchange", this.boundDrawFn);
|
||||
}
|
||||
|
||||
// Has to be called manually
|
||||
@ -109,24 +153,24 @@ export default class BaseChart {
|
||||
|
||||
makeContainer() {
|
||||
// Chart needs a dedicated parent element
|
||||
this.parent.innerHTML = '';
|
||||
this.parent.innerHTML = "";
|
||||
|
||||
let args = {
|
||||
inside: this.parent,
|
||||
className: 'chart-container'
|
||||
className: "chart-container",
|
||||
};
|
||||
|
||||
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() {
|
||||
this.tip = new SvgTip({
|
||||
parent: this.container,
|
||||
colors: this.colors
|
||||
colors: this.colors,
|
||||
});
|
||||
this.bindTooltip();
|
||||
}
|
||||
@ -144,16 +188,22 @@ export default class BaseChart {
|
||||
this.makeChartArea();
|
||||
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.render(this.components, false);
|
||||
|
||||
if (init) {
|
||||
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.setupNavigation(init);
|
||||
}
|
||||
@ -173,7 +223,7 @@ export default class BaseChart {
|
||||
|
||||
this.svg = makeSVGContainer(
|
||||
this.container,
|
||||
'frappe-chart chart',
|
||||
"frappe-chart chart",
|
||||
this.baseWidth,
|
||||
this.baseHeight
|
||||
);
|
||||
@ -181,35 +231,39 @@ export default class BaseChart {
|
||||
|
||||
if (this.title.length) {
|
||||
this.titleEL = makeText(
|
||||
'title',
|
||||
"title",
|
||||
m.margins.left,
|
||||
m.margins.top,
|
||||
this.title,
|
||||
{
|
||||
fontSize: m.titleFontSize,
|
||||
fill: '#666666',
|
||||
dy: m.titleFontSize
|
||||
fill: "#666666",
|
||||
dy: m.titleFontSize,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let top = getTopOffset(m);
|
||||
this.drawArea = makeSVGGroup(
|
||||
this.type + '-chart chart-draw-area',
|
||||
this.type + "-chart chart-draw-area",
|
||||
`translate(${getLeftOffset(m)}, ${top})`
|
||||
);
|
||||
|
||||
if (this.config.showLegend) {
|
||||
top += this.height + m.paddings.bottom;
|
||||
this.legendArea = makeSVGGroup(
|
||||
'chart-legend',
|
||||
"chart-legend",
|
||||
`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);
|
||||
if(this.config.showLegend) { this.svg.appendChild(this.legendArea); }
|
||||
if (this.config.showLegend) {
|
||||
this.svg.appendChild(this.legendArea);
|
||||
}
|
||||
|
||||
this.updateTipOffset(getLeftOffset(m), getTopOffset(m));
|
||||
}
|
||||
@ -217,17 +271,18 @@ export default class BaseChart {
|
||||
updateTipOffset(x, y) {
|
||||
this.tip.offset = {
|
||||
x: x,
|
||||
y: y
|
||||
y: y,
|
||||
};
|
||||
}
|
||||
|
||||
setupComponents() { this.components = new Map(); }
|
||||
|
||||
update(data) {
|
||||
if(!data) {
|
||||
console.error('No data to update.');
|
||||
setupComponents() {
|
||||
this.components = new Map();
|
||||
}
|
||||
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.render(this.components, this.config.animate);
|
||||
}
|
||||
@ -235,22 +290,22 @@ export default class BaseChart {
|
||||
render(components = this.components, animate = true) {
|
||||
if (this.config.isNavigable) {
|
||||
// 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);
|
||||
}
|
||||
let elementsToAnimate = [];
|
||||
// Can decouple to this.refreshComponents() first to save animation timeout
|
||||
components.forEach(c => {
|
||||
components.forEach((c) => {
|
||||
elementsToAnimate = elementsToAnimate.concat(c.update(animate));
|
||||
});
|
||||
if (elementsToAnimate.length > 0) {
|
||||
runSMILAnimation(this.container, this.svg, elementsToAnimate);
|
||||
setTimeout(() => {
|
||||
components.forEach(c => c.make());
|
||||
components.forEach((c) => c.make());
|
||||
this.updateNav();
|
||||
}, CHART_POST_ANIMATE_TIMEOUT);
|
||||
} else {
|
||||
components.forEach(c => c.make());
|
||||
components.forEach((c) => c.make());
|
||||
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) {
|
||||
if (!this.config.isNavigable) return;
|
||||
@ -271,14 +344,14 @@ export default class BaseChart {
|
||||
this.bindOverlay();
|
||||
|
||||
this.keyActions = {
|
||||
'13': this.onEnterKey.bind(this),
|
||||
'37': this.onLeftArrow.bind(this),
|
||||
'38': this.onUpArrow.bind(this),
|
||||
'39': this.onRightArrow.bind(this),
|
||||
'40': this.onDownArrow.bind(this),
|
||||
13: this.onEnterKey.bind(this),
|
||||
37: this.onLeftArrow.bind(this),
|
||||
38: this.onUpArrow.bind(this),
|
||||
39: this.onRightArrow.bind(this),
|
||||
40: this.onDownArrow.bind(this),
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (isElementInViewport(this.container)) {
|
||||
e = e || window.event;
|
||||
if (this.keyActions[e.keyCode]) {
|
||||
@ -310,6 +383,6 @@ export default class BaseChart {
|
||||
|
||||
export() {
|
||||
let chartSvg = prepareForExport(this.svg);
|
||||
downloadFile(this.title || 'Chart', [chartSvg]);
|
||||
downloadFile(this.title || "Chart", [chartSvg]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import AggregationChart from './AggregationChart';
|
||||
import { getComponent } from '../objects/ChartComponents';
|
||||
import { getOffset } from '../utils/dom';
|
||||
import { getPositionByAngle } from '../utils/helpers';
|
||||
import { makeArcStrokePathStr, makeStrokeCircleStr } from '../utils/draw';
|
||||
import { lightenDarkenColor } from '../utils/colors';
|
||||
import { transform } from '../utils/animation';
|
||||
import { FULL_ANGLE } from '../utils/constants';
|
||||
import AggregationChart from "./AggregationChart";
|
||||
import { getComponent } from "../objects/ChartComponents";
|
||||
import { getOffset } from "../utils/dom";
|
||||
import { getPositionByAngle } from "../utils/helpers";
|
||||
import { makeArcStrokePathStr, makeStrokeCircleStr } from "../utils/draw";
|
||||
import { lightenDarkenColor } from "../utils/colors";
|
||||
import { transform } from "../utils/animation";
|
||||
import { FULL_ANGLE } from "../utils/constants";
|
||||
|
||||
export default class DonutChart extends AggregationChart {
|
||||
constructor(parent, args) {
|
||||
super(parent, args);
|
||||
this.type = 'donut';
|
||||
this.type = "donut";
|
||||
this.initTimeout = 0;
|
||||
this.init = 1;
|
||||
|
||||
@ -49,7 +49,7 @@ export default class DonutChart extends AggregationChart {
|
||||
const originDiffAngle = (total / s.grandTotal) * FULL_ANGLE;
|
||||
const largeArc = originDiffAngle > 180 ? 1 : 0;
|
||||
const diffAngle = clockWise ? -originDiffAngle : originDiffAngle;
|
||||
const endAngle = curAngle = curAngle + diffAngle;
|
||||
const endAngle = (curAngle = curAngle + diffAngle);
|
||||
const startPosition = getPositionByAngle(startAngle, radius);
|
||||
const endPosition = getPositionByAngle(endAngle, radius);
|
||||
|
||||
@ -65,8 +65,22 @@ export default class DonutChart extends AggregationChart {
|
||||
}
|
||||
const curPath =
|
||||
originDiffAngle === 360
|
||||
? makeStrokeCircleStr(curStart, curEnd, this.center, this.radius, this.clockWise, largeArc)
|
||||
: makeArcStrokePathStr(curStart, curEnd, this.center, this.radius, this.clockWise, largeArc);
|
||||
? makeStrokeCircleStr(
|
||||
curStart,
|
||||
curEnd,
|
||||
this.center,
|
||||
this.radius,
|
||||
this.clockWise,
|
||||
largeArc
|
||||
)
|
||||
: makeArcStrokePathStr(
|
||||
curStart,
|
||||
curEnd,
|
||||
this.center,
|
||||
this.radius,
|
||||
this.clockWise,
|
||||
largeArc
|
||||
);
|
||||
|
||||
s.sliceStrings.push(curPath);
|
||||
s.slicesProperties.push({
|
||||
@ -76,9 +90,8 @@ export default class DonutChart extends AggregationChart {
|
||||
total: s.grandTotal,
|
||||
startAngle,
|
||||
endAngle,
|
||||
angle: diffAngle
|
||||
angle: diffAngle,
|
||||
});
|
||||
|
||||
});
|
||||
this.init = 0;
|
||||
}
|
||||
@ -88,7 +101,7 @@ export default class DonutChart extends AggregationChart {
|
||||
|
||||
let componentConfigs = [
|
||||
[
|
||||
'donutSlices',
|
||||
"donutSlices",
|
||||
{},
|
||||
function () {
|
||||
return {
|
||||
@ -96,21 +109,27 @@ export default class DonutChart extends AggregationChart {
|
||||
colors: this.colors,
|
||||
strokeWidth: this.strokeWidth,
|
||||
};
|
||||
}.bind(this)
|
||||
]
|
||||
}.bind(this),
|
||||
],
|
||||
];
|
||||
|
||||
this.components = new Map(componentConfigs
|
||||
.map(args => {
|
||||
this.components = new Map(
|
||||
componentConfigs.map((args) => {
|
||||
let component = getComponent(...args);
|
||||
return [args[0], component];
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
calTranslateByAngle(property) {
|
||||
const { radius, hoverRadio } = this;
|
||||
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius);
|
||||
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`;
|
||||
const position = getPositionByAngle(
|
||||
property.startAngle + property.angle / 2,
|
||||
radius
|
||||
);
|
||||
return `translate3d(${position.x * hoverRadio}px,${
|
||||
position.y * hoverRadio
|
||||
}px,0)`;
|
||||
}
|
||||
|
||||
hoverSlice(path, i, flag, e) {
|
||||
@ -122,26 +141,31 @@ export default class DonutChart extends AggregationChart {
|
||||
let g_off = getOffset(this.svg);
|
||||
let x = e.pageX - g_off.left + 10;
|
||||
let y = e.pageY - g_off.top - 10;
|
||||
let title = (this.formatted_labels && this.formatted_labels.length > 0
|
||||
? this.formatted_labels[i] : this.state.labels[i]) + ': ';
|
||||
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1);
|
||||
let title =
|
||||
(this.formatted_labels && this.formatted_labels.length > 0
|
||||
? this.formatted_labels[i]
|
||||
: this.state.labels[i]) + ": ";
|
||||
let percent = (
|
||||
(this.state.sliceTotals[i] * 100) /
|
||||
this.state.grandTotal
|
||||
).toFixed(1);
|
||||
this.tip.setValues(x, y, { name: title, value: percent + "%" });
|
||||
this.tip.showTip();
|
||||
} else {
|
||||
transform(path,'translate3d(0,0,0)');
|
||||
transform(path, "translate3d(0,0,0)");
|
||||
this.tip.hideTip();
|
||||
path.style.stroke = color;
|
||||
}
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
this.container.addEventListener('mousemove', this.mouseMove);
|
||||
this.container.addEventListener('mouseleave', this.mouseLeave);
|
||||
this.container.addEventListener("mousemove", this.mouseMove);
|
||||
this.container.addEventListener("mouseleave", this.mouseLeave);
|
||||
}
|
||||
|
||||
mouseMove(e) {
|
||||
const target = e.target;
|
||||
let slices = this.components.get('donutSlices').store;
|
||||
let slices = this.components.get("donutSlices").store;
|
||||
let prevIndex = this.curActiveSliceIndex;
|
||||
let prevAcitve = this.curActiveSlice;
|
||||
if (slices.includes(target)) {
|
||||
|
||||
@ -1,11 +1,29 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { getComponent } from '../objects/ChartComponents';
|
||||
import { makeText, heatSquare } from '../utils/draw';
|
||||
import { DAY_NAMES_SHORT, addDays, areInSameMonth, getLastDateInMonth, setDayToSunday, getYyyyMmDd, getWeeksBetween, getMonthName, clone,
|
||||
NO_OF_MILLIS, NO_OF_YEAR_MONTHS, NO_OF_DAYS_IN_WEEK } from '../utils/date-utils';
|
||||
import { calcDistribution, getMaxCheckpoint } from '../utils/intervals';
|
||||
import { getExtraHeight, getExtraWidth, HEATMAP_DISTRIBUTION_SIZE, HEATMAP_SQUARE_SIZE,
|
||||
HEATMAP_GUTTER_SIZE } from '../utils/constants';
|
||||
import BaseChart from "./BaseChart";
|
||||
import { getComponent } from "../objects/ChartComponents";
|
||||
import { makeText, heatSquare } from "../utils/draw";
|
||||
import {
|
||||
DAY_NAMES_SHORT,
|
||||
toMidnightUTC,
|
||||
addDays,
|
||||
areInSameMonth,
|
||||
getLastDateInMonth,
|
||||
setDayToSunday,
|
||||
getYyyyMmDd,
|
||||
getWeeksBetween,
|
||||
getMonthName,
|
||||
clone,
|
||||
NO_OF_MILLIS,
|
||||
NO_OF_YEAR_MONTHS,
|
||||
NO_OF_DAYS_IN_WEEK,
|
||||
} from "../utils/date-utils";
|
||||
import { calcDistribution, getMaxCheckpoint } from "../utils/intervals";
|
||||
import {
|
||||
getExtraHeight,
|
||||
getExtraWidth,
|
||||
HEATMAP_DISTRIBUTION_SIZE,
|
||||
HEATMAP_SQUARE_SIZE,
|
||||
HEATMAP_GUTTER_SIZE,
|
||||
} from "../utils/constants";
|
||||
|
||||
const COL_WIDTH = HEATMAP_SQUARE_SIZE + HEATMAP_GUTTER_SIZE;
|
||||
const ROW_HEIGHT = COL_WIDTH;
|
||||
@ -14,13 +32,14 @@ const ROW_HEIGHT = COL_WIDTH;
|
||||
export default class Heatmap extends BaseChart {
|
||||
constructor(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)
|
||||
? options.startSubDomain : 'Sunday';
|
||||
? options.startSubDomain
|
||||
: "Sunday";
|
||||
this.startSubDomainIndex = validStarts.indexOf(startSubDomain);
|
||||
|
||||
this.setup();
|
||||
@ -33,37 +52,43 @@ export default class Heatmap extends BaseChart {
|
||||
m.paddings.top = ROW_HEIGHT * 3;
|
||||
m.paddings.bottom = 0;
|
||||
m.legendHeight = ROW_HEIGHT * 2;
|
||||
m.baseHeight = ROW_HEIGHT * NO_OF_DAYS_IN_WEEK
|
||||
+ getExtraHeight(m);
|
||||
m.baseHeight = ROW_HEIGHT * NO_OF_DAYS_IN_WEEK + getExtraHeight(m);
|
||||
|
||||
let d = this.data;
|
||||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
||||
this.independentWidth = (getWeeksBetween(d.start, d.end)
|
||||
+ spacing) * COL_WIDTH + getExtraWidth(m);
|
||||
this.independentWidth =
|
||||
(getWeeksBetween(d.start, d.end) + spacing) * COL_WIDTH +
|
||||
getExtraWidth(m);
|
||||
}
|
||||
|
||||
updateWidth() {
|
||||
let spacing = this.discreteDomains ? NO_OF_YEAR_MONTHS : 0;
|
||||
let noOfWeeks = this.state.noOfWeeks ? this.state.noOfWeeks : 52;
|
||||
this.baseWidth = (noOfWeeks + spacing) * COL_WIDTH
|
||||
+ getExtraWidth(this.measures);
|
||||
this.baseWidth =
|
||||
(noOfWeeks + spacing) * COL_WIDTH + getExtraWidth(this.measures);
|
||||
}
|
||||
|
||||
prepareData(data = this.data) {
|
||||
if (data.start && data.end && data.start > data.end) {
|
||||
throw new Error('Start date cannot be greater than end date.');
|
||||
throw new Error("Start date cannot be greater than end date.");
|
||||
}
|
||||
|
||||
if (!data.start) {
|
||||
data.start = new Date();
|
||||
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 || {};
|
||||
|
||||
if (parseInt(Object.keys(data.dataPoints)[0]) > 100000) {
|
||||
let points = {};
|
||||
Object.keys(data.dataPoints).forEach(timestampSec => {
|
||||
Object.keys(data.dataPoints).forEach((timestampSec) => {
|
||||
let date = new Date(timestampSec * NO_OF_MILLIS);
|
||||
points[getYyyyMmDd(date)] = data.dataPoints[timestampSec];
|
||||
});
|
||||
@ -82,7 +107,9 @@ export default class Heatmap extends BaseChart {
|
||||
s.firstWeekStart = clone(s.start);
|
||||
s.noOfWeeks = getWeeksBetween(s.start, s.end);
|
||||
s.distribution = calcDistribution(
|
||||
Object.values(this.data.dataPoints), HEATMAP_DISTRIBUTION_SIZE);
|
||||
Object.values(this.data.dataPoints),
|
||||
HEATMAP_DISTRIBUTION_SIZE
|
||||
);
|
||||
|
||||
s.domainConfigs = this.getDomains();
|
||||
}
|
||||
@ -92,42 +119,39 @@ export default class Heatmap extends BaseChart {
|
||||
let lessCol = this.discreteDomains ? 0 : 1;
|
||||
|
||||
let componentConfigs = s.domainConfigs.map((config, i) => [
|
||||
'heatDomain',
|
||||
"heatDomain",
|
||||
{
|
||||
index: config.index,
|
||||
colWidth: COL_WIDTH,
|
||||
rowHeight: ROW_HEIGHT,
|
||||
squareSize: HEATMAP_SQUARE_SIZE,
|
||||
radius: this.rawChartArgs.radius || 0,
|
||||
xTranslate: s.domainConfigs
|
||||
xTranslate:
|
||||
s.domainConfigs
|
||||
.filter((config, j) => j < i)
|
||||
.map(config => config.cols.length - lessCol)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
* COL_WIDTH
|
||||
.map((config) => config.cols.length - lessCol)
|
||||
.reduce((a, b) => a + b, 0) * COL_WIDTH,
|
||||
},
|
||||
function () {
|
||||
return s.domainConfigs[i];
|
||||
}.bind(this)
|
||||
|
||||
}.bind(this),
|
||||
]);
|
||||
|
||||
this.components = new Map(componentConfigs
|
||||
.map((args, i) => {
|
||||
this.components = new Map(
|
||||
componentConfigs.map((args, i) => {
|
||||
let component = getComponent(...args);
|
||||
return [args[0] + '-' + i, component];
|
||||
return [args[0] + "-" + i, component];
|
||||
})
|
||||
);
|
||||
|
||||
let y = 0;
|
||||
DAY_NAMES_SHORT.forEach((dayName, 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,
|
||||
dy: 8,
|
||||
textAnchor: 'end'
|
||||
}
|
||||
);
|
||||
textAnchor: "end",
|
||||
});
|
||||
this.drawArea.appendChild(dayText);
|
||||
}
|
||||
y += ROW_HEIGHT;
|
||||
@ -136,7 +160,7 @@ export default class Heatmap extends BaseChart {
|
||||
|
||||
update(data) {
|
||||
if (!data) {
|
||||
console.error('No data to update.');
|
||||
console.error("No data to update.");
|
||||
}
|
||||
|
||||
this.data = this.prepareData(data);
|
||||
@ -145,26 +169,31 @@ export default class Heatmap extends BaseChart {
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
this.container.addEventListener('mousemove', (e) => {
|
||||
this.components.forEach(comp => {
|
||||
this.container.addEventListener("mousemove", (e) => {
|
||||
this.components.forEach((comp) => {
|
||||
let daySquares = comp.store;
|
||||
let daySquare = e.target;
|
||||
if (daySquares.includes(daySquare)) {
|
||||
|
||||
let count = daySquare.getAttribute('data-value');
|
||||
let dateParts = daySquare.getAttribute('data-date').split('-');
|
||||
let count = daySquare.getAttribute("data-value");
|
||||
let dateParts = daySquare.getAttribute("data-date").split("-");
|
||||
|
||||
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 y = pOff.top - gOff.top;
|
||||
let value = count + ' ' + this.countLabel;
|
||||
let name = ' on ' + month + ' ' + dateParts[0] + ', ' + dateParts[2];
|
||||
let value = count + " " + this.countLabel;
|
||||
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();
|
||||
}
|
||||
});
|
||||
@ -172,33 +201,36 @@ export default class Heatmap extends BaseChart {
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
this.legendArea.textContent = '';
|
||||
this.legendArea.textContent = "";
|
||||
let x = 0;
|
||||
let y = ROW_HEIGHT;
|
||||
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,
|
||||
dy: 9
|
||||
}
|
||||
);
|
||||
x = (COL_WIDTH * 2) + COL_WIDTH/2;
|
||||
dy: 9,
|
||||
});
|
||||
x = COL_WIDTH * 2 + COL_WIDTH / 2;
|
||||
this.legendArea.appendChild(lessText);
|
||||
|
||||
this.colors.slice(0, HEATMAP_DISTRIBUTION_SIZE).map((color, i) => {
|
||||
const square = heatSquare('heatmap-legend-unit', x + (COL_WIDTH + 3) * i,
|
||||
y, HEATMAP_SQUARE_SIZE, radius, color);
|
||||
const square = heatSquare(
|
||||
"heatmap-legend-unit",
|
||||
x + (COL_WIDTH + 3) * i,
|
||||
y,
|
||||
HEATMAP_SQUARE_SIZE,
|
||||
radius,
|
||||
color
|
||||
);
|
||||
this.legendArea.appendChild(square);
|
||||
});
|
||||
|
||||
let moreTextX = x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH/4;
|
||||
let moreText = makeText('subdomain-name', moreTextX, y, 'More',
|
||||
{
|
||||
let moreTextX =
|
||||
x + HEATMAP_DISTRIBUTION_SIZE * (COL_WIDTH + 3) + COL_WIDTH / 4;
|
||||
let moreText = makeText("subdomain-name", moreTextX, y, "More", {
|
||||
fontSize: HEATMAP_SQUARE_SIZE + 1,
|
||||
dy: 9
|
||||
}
|
||||
);
|
||||
dy: 9,
|
||||
});
|
||||
this.legendArea.appendChild(moreText);
|
||||
}
|
||||
|
||||
@ -207,7 +239,7 @@ export default class Heatmap extends BaseChart {
|
||||
const [startMonth, startYear] = [s.start.getMonth(), s.start.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 = [];
|
||||
|
||||
@ -215,7 +247,10 @@ export default class Heatmap extends BaseChart {
|
||||
for (var i = 0; i < noOfMonths; i++) {
|
||||
let endDate = 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);
|
||||
}
|
||||
domainConfigs.push(this.getDomainConfig(startOfMonth, endDate));
|
||||
@ -227,25 +262,30 @@ export default class Heatmap extends BaseChart {
|
||||
return domainConfigs;
|
||||
}
|
||||
|
||||
getDomainConfig(startDate, endDate='') {
|
||||
getDomainConfig(startDate, endDate = "") {
|
||||
let [month, year] = [startDate.getMonth(), startDate.getFullYear()];
|
||||
let startOfWeek = setDayToSunday(startDate); // TODO: Monday as well
|
||||
endDate = clone(endDate) || getLastDateInMonth(month, year);
|
||||
endDate = endDate
|
||||
? clone(endDate)
|
||||
: toMidnightUTC(getLastDateInMonth(month, year));
|
||||
|
||||
let domainConfig = {
|
||||
index: month,
|
||||
cols: []
|
||||
cols: [],
|
||||
};
|
||||
|
||||
addDays(endDate, 1);
|
||||
let noOfMonthWeeks = getWeeksBetween(startOfWeek, endDate);
|
||||
|
||||
let cols = [], col;
|
||||
let cols = [],
|
||||
col;
|
||||
for (var i = 0; i < noOfMonthWeeks; i++) {
|
||||
col = this.getCol(startOfWeek, month);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -270,7 +310,8 @@ export default class Heatmap extends BaseChart {
|
||||
let config = {};
|
||||
|
||||
// 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) {
|
||||
config.yyyyMmDd = getYyyyMmDd(currentDate);
|
||||
@ -289,7 +330,7 @@ export default class Heatmap extends BaseChart {
|
||||
let config = {
|
||||
yyyyMmDd: yyyyMmDd,
|
||||
dataValue: dataValue || 0,
|
||||
fill: this.colors[getMaxCheckpoint(dataValue, this.state.distribution)]
|
||||
fill: this.colors[getMaxCheckpoint(dataValue, this.state.distribution)],
|
||||
};
|
||||
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 { getOffset } from '../utils/dom';
|
||||
import { getComponent } from '../objects/ChartComponents';
|
||||
import { PERCENTAGE_BAR_DEFAULT_HEIGHT, PERCENTAGE_BAR_DEFAULT_DEPTH } from '../utils/constants';
|
||||
import AggregationChart from "./AggregationChart";
|
||||
import { getOffset } from "../utils/dom";
|
||||
import { getComponent } from "../objects/ChartComponents";
|
||||
import { PERCENTAGE_BAR_DEFAULT_HEIGHT } from "../utils/constants";
|
||||
|
||||
export default class PercentageChart extends AggregationChart {
|
||||
constructor(parent, args) {
|
||||
super(parent, args);
|
||||
this.type = 'percentage';
|
||||
this.type = "percentage";
|
||||
this.setup();
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ export default class PercentageChart extends AggregationChart {
|
||||
|
||||
let b = this.barOptions;
|
||||
b.height = b.height || PERCENTAGE_BAR_DEFAULT_HEIGHT;
|
||||
b.depth = b.depth || PERCENTAGE_BAR_DEFAULT_DEPTH;
|
||||
|
||||
m.paddings.right = 30;
|
||||
m.legendHeight = 60;
|
||||
@ -28,26 +27,26 @@ export default class PercentageChart extends AggregationChart {
|
||||
|
||||
let componentConfigs = [
|
||||
[
|
||||
'percentageBars',
|
||||
"percentageBars",
|
||||
{
|
||||
barHeight: this.barOptions.height,
|
||||
barDepth: this.barOptions.depth,
|
||||
},
|
||||
function () {
|
||||
return {
|
||||
xPositions: s.xPositions,
|
||||
widths: s.widths,
|
||||
colors: this.colors
|
||||
colors: this.colors,
|
||||
};
|
||||
}.bind(this)
|
||||
]
|
||||
}.bind(this),
|
||||
],
|
||||
];
|
||||
|
||||
this.components = new Map(componentConfigs
|
||||
.map(args => {
|
||||
this.components = new Map(
|
||||
componentConfigs.map((args) => {
|
||||
let component = getComponent(...args);
|
||||
return [args[0], component];
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
calc() {
|
||||
@ -59,7 +58,7 @@ export default class PercentageChart extends AggregationChart {
|
||||
|
||||
let xPos = 0;
|
||||
s.sliceTotals.map((value) => {
|
||||
let width = this.width * value / s.grandTotal;
|
||||
let width = (this.width * value) / s.grandTotal;
|
||||
s.widths.push(width);
|
||||
s.xPositions.push(xPos);
|
||||
xPos += width;
|
||||
@ -70,21 +69,26 @@ export default class PercentageChart extends AggregationChart {
|
||||
|
||||
bindTooltip() {
|
||||
let s = this.state;
|
||||
this.container.addEventListener('mousemove', (e) => {
|
||||
let bars = this.components.get('percentageBars').store;
|
||||
this.container.addEventListener("mousemove", (e) => {
|
||||
let bars = this.components.get("percentageBars").store;
|
||||
let bar = e.target;
|
||||
if (bars.includes(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 title = (this.formattedLabels && this.formattedLabels.length>0
|
||||
? this.formattedLabels[i] : this.state.labels[i]) + ': ';
|
||||
let title =
|
||||
(this.formattedLabels && this.formattedLabels.length > 0
|
||||
? this.formattedLabels[i]
|
||||
: this.state.labels[i]) + ": ";
|
||||
let fraction = s.sliceTotals[i] / s.grandTotal;
|
||||
|
||||
this.tip.setValues(x, y, {name: title, value: (fraction*100).toFixed(1) + "%"});
|
||||
this.tip.setValues(x, y, {
|
||||
name: title,
|
||||
value: (fraction * 100).toFixed(1) + "%",
|
||||
});
|
||||
this.tip.showTip();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
import AggregationChart from './AggregationChart';
|
||||
import { getComponent } from '../objects/ChartComponents';
|
||||
import { getOffset } from '../utils/dom';
|
||||
import { getPositionByAngle } from '../utils/helpers';
|
||||
import { makeArcPathStr, makeCircleStr } from '../utils/draw';
|
||||
import { lightenDarkenColor } from '../utils/colors';
|
||||
import { transform } from '../utils/animation';
|
||||
import { FULL_ANGLE } from '../utils/constants';
|
||||
import AggregationChart from "./AggregationChart";
|
||||
import { getComponent } from "../objects/ChartComponents";
|
||||
import { getOffset, fire } from "../utils/dom";
|
||||
import { getPositionByAngle } from "../utils/helpers";
|
||||
import { makeArcPathStr, makeCircleStr } from "../utils/draw";
|
||||
import { lightenDarkenColor } from "../utils/colors";
|
||||
import { transform } from "../utils/animation";
|
||||
import { FULL_ANGLE } from "../utils/constants";
|
||||
|
||||
export default class PieChart extends AggregationChart {
|
||||
constructor(parent, args) {
|
||||
super(parent, args);
|
||||
this.type = 'pie';
|
||||
this.type = "pie";
|
||||
this.initTimeout = 0;
|
||||
this.init = 1;
|
||||
|
||||
@ -31,7 +31,7 @@ export default class PieChart extends AggregationChart {
|
||||
calc() {
|
||||
super.calc();
|
||||
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;
|
||||
|
||||
@ -39,12 +39,13 @@ export default class PieChart extends AggregationChart {
|
||||
s.sliceStrings = [];
|
||||
s.slicesProperties = [];
|
||||
let curAngle = 180 - this.config.startAngle;
|
||||
|
||||
s.sliceTotals.map((total, i) => {
|
||||
const startAngle = curAngle;
|
||||
const originDiffAngle = (total / s.grandTotal) * FULL_ANGLE;
|
||||
const largeArc = originDiffAngle > 180 ? 1 : 0;
|
||||
const diffAngle = clockWise ? -originDiffAngle : originDiffAngle;
|
||||
const endAngle = curAngle = curAngle + diffAngle;
|
||||
const endAngle = (curAngle = curAngle + diffAngle);
|
||||
const startPosition = getPositionByAngle(startAngle, radius);
|
||||
const endPosition = getPositionByAngle(endAngle, radius);
|
||||
|
||||
@ -60,8 +61,22 @@ export default class PieChart extends AggregationChart {
|
||||
}
|
||||
const curPath =
|
||||
originDiffAngle === 360
|
||||
? makeCircleStr(curStart, curEnd, this.center, this.radius, clockWise, largeArc)
|
||||
: makeArcPathStr(curStart, curEnd, this.center, this.radius, clockWise, largeArc);
|
||||
? makeCircleStr(
|
||||
curStart,
|
||||
curEnd,
|
||||
this.center,
|
||||
this.radius,
|
||||
clockWise,
|
||||
largeArc
|
||||
)
|
||||
: makeArcPathStr(
|
||||
curStart,
|
||||
curEnd,
|
||||
this.center,
|
||||
this.radius,
|
||||
clockWise,
|
||||
largeArc
|
||||
);
|
||||
|
||||
s.sliceStrings.push(curPath);
|
||||
s.slicesProperties.push({
|
||||
@ -71,9 +86,8 @@ export default class PieChart extends AggregationChart {
|
||||
total: s.grandTotal,
|
||||
startAngle,
|
||||
endAngle,
|
||||
angle: diffAngle
|
||||
angle: diffAngle,
|
||||
});
|
||||
|
||||
});
|
||||
this.init = 0;
|
||||
}
|
||||
@ -83,28 +97,34 @@ export default class PieChart extends AggregationChart {
|
||||
|
||||
let componentConfigs = [
|
||||
[
|
||||
'pieSlices',
|
||||
"pieSlices",
|
||||
{},
|
||||
function () {
|
||||
return {
|
||||
sliceStrings: s.sliceStrings,
|
||||
colors: this.colors
|
||||
colors: this.colors,
|
||||
};
|
||||
}.bind(this)
|
||||
]
|
||||
}.bind(this),
|
||||
],
|
||||
];
|
||||
|
||||
this.components = new Map(componentConfigs
|
||||
.map(args => {
|
||||
this.components = new Map(
|
||||
componentConfigs.map((args) => {
|
||||
let component = getComponent(...args);
|
||||
return [args[0], component];
|
||||
}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
calTranslateByAngle(property) {
|
||||
const { radius, hoverRadio } = this;
|
||||
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius);
|
||||
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`;
|
||||
const position = getPositionByAngle(
|
||||
property.startAngle + property.angle / 2,
|
||||
radius
|
||||
);
|
||||
return `translate3d(${position.x * hoverRadio}px,${
|
||||
position.y * hoverRadio
|
||||
}px,0)`;
|
||||
}
|
||||
|
||||
hoverSlice(path, i, flag, e) {
|
||||
@ -116,26 +136,58 @@ export default class PieChart extends AggregationChart {
|
||||
let g_off = getOffset(this.svg);
|
||||
let x = e.pageX - g_off.left + 10;
|
||||
let y = e.pageY - g_off.top - 10;
|
||||
let title = (this.formatted_labels && this.formatted_labels.length > 0
|
||||
? this.formatted_labels[i] : this.state.labels[i]) + ': ';
|
||||
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1);
|
||||
let title =
|
||||
(this.formatted_labels && this.formatted_labels.length > 0
|
||||
? this.formatted_labels[i]
|
||||
: this.state.labels[i]) + ": ";
|
||||
let percent = (
|
||||
(this.state.sliceTotals[i] * 100) /
|
||||
this.state.grandTotal
|
||||
).toFixed(1);
|
||||
this.tip.setValues(x, y, { name: title, value: percent + "%" });
|
||||
this.tip.showTip();
|
||||
} else {
|
||||
transform(path,'translate3d(0,0,0)');
|
||||
transform(path, "translate3d(0,0,0)");
|
||||
this.tip.hideTip();
|
||||
path.style.fill = color;
|
||||
}
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
this.container.addEventListener('mousemove', this.mouseMove);
|
||||
this.container.addEventListener('mouseleave', this.mouseLeave);
|
||||
this.container.addEventListener("mousemove", this.mouseMove);
|
||||
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) {
|
||||
const target = e.target;
|
||||
let slices = this.components.get('pieSlices').store;
|
||||
let slices = this.components.get("pieSlices").store;
|
||||
let prevIndex = this.curActiveSliceIndex;
|
||||
let prevAcitve = this.curActiveSlice;
|
||||
if (slices.includes(target)) {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import * as Charts from './chart';
|
||||
import * as Charts from "./chart";
|
||||
|
||||
let frappe = {};
|
||||
|
||||
frappe.NAME = 'Frappe Charts';
|
||||
frappe.VERSION = '1.5.5';
|
||||
frappe.NAME = "Frappe Charts";
|
||||
frappe.VERSION = "1.6.2";
|
||||
|
||||
frappe = Object.assign({}, frappe, Charts);
|
||||
|
||||
|
||||
@ -1,19 +1,39 @@
|
||||
import { makeSVGGroup } from '../utils/draw';
|
||||
import { makeText, makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, percentageBar, getPaths, heatSquare } from '../utils/draw';
|
||||
import { equilizeNoOfElements } from '../utils/draw-utils';
|
||||
import { translateHoriLine, translateVertLine, animateRegion, animateBar,
|
||||
animateDot, animatePath, animatePathStr } from '../utils/animate';
|
||||
import { getMonthName } from '../utils/date-utils';
|
||||
import { makeSVGGroup } from "../utils/draw";
|
||||
import {
|
||||
makeText,
|
||||
makePath,
|
||||
xLine,
|
||||
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 {
|
||||
constructor({
|
||||
layerClass = '',
|
||||
layerTransform = '',
|
||||
layerClass = "",
|
||||
layerTransform = "",
|
||||
constants,
|
||||
|
||||
getData,
|
||||
makeElements,
|
||||
animateElements
|
||||
animateElements,
|
||||
}) {
|
||||
this.layerTransform = layerTransform;
|
||||
this.constants = constants;
|
||||
@ -27,8 +47,10 @@ class ChartComponent {
|
||||
this.labels = [];
|
||||
|
||||
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();
|
||||
}
|
||||
@ -49,11 +71,15 @@ class ChartComponent {
|
||||
render(data) {
|
||||
this.store = this.makeElements(data);
|
||||
|
||||
this.layer.textContent = '';
|
||||
this.store.forEach(element => {
|
||||
this.layer.appendChild(element);
|
||||
this.layer.textContent = "";
|
||||
this.store.forEach((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);
|
||||
});
|
||||
}
|
||||
@ -70,25 +96,17 @@ class ChartComponent {
|
||||
|
||||
let componentConfigs = {
|
||||
donutSlices: {
|
||||
layerClass: 'donut-slices',
|
||||
layerClass: "donut-slices",
|
||||
makeElements(data) {
|
||||
return data.sliceStrings.map((s, i) => {
|
||||
let slice = makePath(s, 'donut-path', data.colors[i], 'none', data.strokeWidth);
|
||||
slice.style.transition = 'transform .3s;';
|
||||
return slice;
|
||||
});
|
||||
},
|
||||
|
||||
animateElements(newData) {
|
||||
return this.store.map((slice, 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;';
|
||||
let slice = makePath(
|
||||
s,
|
||||
"donut-path",
|
||||
data.colors[i],
|
||||
"none",
|
||||
data.strokeWidth
|
||||
);
|
||||
slice.style.transition = "transform .3s;";
|
||||
return slice;
|
||||
});
|
||||
},
|
||||
@ -97,33 +115,145 @@ let componentConfigs = {
|
||||
return this.store.map((slice, 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: {
|
||||
layerClass: 'percentage-bars',
|
||||
layerClass: "percentage-bars",
|
||||
makeElements(data) {
|
||||
const numberOfPoints = data.xPositions.length;
|
||||
return data.xPositions.map((x, i) => {
|
||||
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;
|
||||
});
|
||||
},
|
||||
|
||||
animateElements(newData) {
|
||||
if (newData) return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
layerClass: 'y axis',
|
||||
layerClass: "y axis",
|
||||
makeElements(data) {
|
||||
return data.positions.map((position, i) =>
|
||||
yLine(position, data.labels[i], this.constants.width,
|
||||
{mode: this.constants.mode, pos: this.constants.pos, shortenNumbers: this.constants.shortenNumbers})
|
||||
let elements = [];
|
||||
// will loop through each yaxis dataset if it exists
|
||||
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) {
|
||||
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 newLabels = newData.labels;
|
||||
let oldPos = this.oldData.positions;
|
||||
@ -134,23 +264,23 @@ let componentConfigs = {
|
||||
|
||||
this.render({
|
||||
positions: oldPos,
|
||||
labels: newLabels
|
||||
labels: newLabels,
|
||||
});
|
||||
|
||||
return this.store.map((line, i) => {
|
||||
return translateHoriLine(
|
||||
line, newPos[i], oldPos[i]
|
||||
);
|
||||
return translateHoriLine(line, newPos[i], oldPos[i]);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
xAxis: {
|
||||
layerClass: 'x axis',
|
||||
layerClass: "x axis",
|
||||
makeElements(data) {
|
||||
return data.positions.map((position, i) =>
|
||||
xLine(position, data.calcLabels[i], this.constants.height,
|
||||
{mode: this.constants.mode, pos: this.constants.pos})
|
||||
xLine(position, data.calcLabels[i], this.constants.height, {
|
||||
mode: this.constants.mode,
|
||||
pos: this.constants.pos,
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
@ -165,105 +295,124 @@ let componentConfigs = {
|
||||
|
||||
this.render({
|
||||
positions: oldPos,
|
||||
calcLabels: newLabels
|
||||
calcLabels: newLabels,
|
||||
});
|
||||
|
||||
return this.store.map((line, i) => {
|
||||
return translateVertLine(
|
||||
line, newPos[i], oldPos[i]
|
||||
);
|
||||
return translateVertLine(line, newPos[i], oldPos[i]);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
yMarkers: {
|
||||
layerClass: 'y-markers',
|
||||
layerClass: "y-markers",
|
||||
makeElements(data) {
|
||||
return data.map(m =>
|
||||
yMarker(m.position, m.label, this.constants.width,
|
||||
{labelPos: m.options.labelPos, mode: 'span', lineType: 'dashed'})
|
||||
return data.map((m) =>
|
||||
yMarker(m.position, m.label, this.constants.width, {
|
||||
labelPos: m.options.labelPos,
|
||||
stroke: m.options.stroke,
|
||||
mode: "span",
|
||||
lineType: m.options.lineType,
|
||||
})
|
||||
);
|
||||
},
|
||||
animateElements(newData) {
|
||||
[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
|
||||
[this.oldData, newData] = equilizeNoOfElements(
|
||||
this.oldData,
|
||||
newData
|
||||
);
|
||||
|
||||
let newPos = newData.map(d => d.position);
|
||||
let newLabels = newData.map(d => d.label);
|
||||
let newOptions = newData.map(d => d.options);
|
||||
let newPos = newData.map((d) => d.position);
|
||||
let newLabels = newData.map((d) => d.label);
|
||||
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 {
|
||||
position: oldPos[i],
|
||||
label: newLabels[i],
|
||||
options: newOptions[i]
|
||||
options: newOptions[i],
|
||||
};
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
return this.store.map((line, i) => {
|
||||
return translateHoriLine(
|
||||
line, newPos[i], oldPos[i]
|
||||
);
|
||||
return translateHoriLine(line, newPos[i], oldPos[i]);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
yRegions: {
|
||||
layerClass: 'y-regions',
|
||||
layerClass: "y-regions",
|
||||
makeElements(data) {
|
||||
return data.map(r =>
|
||||
yRegion(r.startPos, r.endPos, this.constants.width,
|
||||
r.label, {labelPos: r.options.labelPos})
|
||||
return data.map((r) =>
|
||||
yRegion(r.startPos, r.endPos, this.constants.width, r.label, {
|
||||
labelPos: r.options.labelPos,
|
||||
})
|
||||
);
|
||||
},
|
||||
animateElements(newData) {
|
||||
[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
|
||||
[this.oldData, newData] = equilizeNoOfElements(
|
||||
this.oldData,
|
||||
newData
|
||||
);
|
||||
|
||||
let newPos = newData.map(d => d.endPos);
|
||||
let newLabels = newData.map(d => d.label);
|
||||
let newStarts = newData.map(d => d.startPos);
|
||||
let newOptions = newData.map(d => d.options);
|
||||
let newPos = newData.map((d) => d.endPos);
|
||||
let newLabels = newData.map((d) => d.label);
|
||||
let newStarts = newData.map((d) => d.startPos);
|
||||
let newOptions = newData.map((d) => d.options);
|
||||
|
||||
let oldPos = this.oldData.map(d => d.endPos);
|
||||
let oldStarts = this.oldData.map(d => d.startPos);
|
||||
let oldPos = this.oldData.map((d) => d.endPos);
|
||||
let oldStarts = this.oldData.map((d) => d.startPos);
|
||||
|
||||
this.render(oldPos.map((pos, i) => {
|
||||
this.render(
|
||||
oldPos.map((pos, i) => {
|
||||
return {
|
||||
startPos: oldStarts[i],
|
||||
endPos: oldPos[i],
|
||||
label: newLabels[i],
|
||||
options: newOptions[i]
|
||||
options: newOptions[i],
|
||||
};
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
||||
let animateElements = [];
|
||||
|
||||
this.store.map((rectGroup, i) => {
|
||||
animateElements = animateElements.concat(animateRegion(
|
||||
rectGroup, newStarts[i], newPos[i], oldPos[i]
|
||||
));
|
||||
animateElements = animateElements.concat(
|
||||
animateRegion(rectGroup, newStarts[i], newPos[i], oldPos[i])
|
||||
);
|
||||
});
|
||||
|
||||
return animateElements;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
heatDomain: {
|
||||
layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
|
||||
layerClass: function () {
|
||||
return "heat-domain domain-" + this.constants.index;
|
||||
},
|
||||
makeElements(data) {
|
||||
let {index, colWidth, rowHeight, squareSize, radius, xTranslate} = this.constants;
|
||||
let { index, colWidth, rowHeight, squareSize, radius, xTranslate } =
|
||||
this.constants;
|
||||
let monthNameHeight = -12;
|
||||
let x = xTranslate, y = 0;
|
||||
let x = xTranslate,
|
||||
y = 0;
|
||||
|
||||
this.serializedSubDomains = [];
|
||||
|
||||
data.cols.map((week, weekNo) => {
|
||||
if (weekNo === 1) {
|
||||
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) => {
|
||||
if (day.fill) {
|
||||
let data = {
|
||||
'data-date': day.yyyyMmDd,
|
||||
'data-value': day.dataValue,
|
||||
'data-day': i
|
||||
"data-date": day.yyyyMmDd,
|
||||
"data-value": day.dataValue,
|
||||
"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);
|
||||
}
|
||||
y += rowHeight;
|
||||
@ -289,14 +446,16 @@ let componentConfigs = {
|
||||
|
||||
animateElements(newData) {
|
||||
if (newData) return [];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
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) {
|
||||
let c = this.constants;
|
||||
this.unitType = 'bar';
|
||||
this.unitType = "bar";
|
||||
this.units = data.yPositions.map((y, j) => {
|
||||
return datasetBar(
|
||||
data.xPositions[j],
|
||||
@ -309,7 +468,7 @@ let componentConfigs = {
|
||||
{
|
||||
zeroLine: data.zeroLine,
|
||||
barsWidth: data.barsWidth,
|
||||
minHeight: c.minHeight
|
||||
minHeight: c.minHeight,
|
||||
}
|
||||
);
|
||||
});
|
||||
@ -328,7 +487,10 @@ let componentConfigs = {
|
||||
|
||||
[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
|
||||
[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
|
||||
[oldOffsets, newOffsets] = equilizeNoOfElements(oldOffsets, newOffsets);
|
||||
[oldOffsets, newOffsets] = equilizeNoOfElements(
|
||||
oldOffsets,
|
||||
newOffsets
|
||||
);
|
||||
[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
|
||||
|
||||
this.render({
|
||||
@ -345,21 +507,29 @@ let componentConfigs = {
|
||||
let animateElements = [];
|
||||
|
||||
this.store.map((bar, i) => {
|
||||
animateElements = animateElements.concat(animateBar(
|
||||
bar, newXPos[i], newYPos[i], newData.barWidth, newOffsets[i],
|
||||
animateElements = animateElements.concat(
|
||||
animateBar(
|
||||
bar,
|
||||
newXPos[i],
|
||||
newYPos[i],
|
||||
newData.barWidth,
|
||||
newOffsets[i],
|
||||
{ zeroLine: newData.zeroLine }
|
||||
));
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
return animateElements;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
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) {
|
||||
let c = this.constants;
|
||||
this.unitType = 'dot';
|
||||
this.unitType = "dot";
|
||||
this.paths = {};
|
||||
if (!c.hideLine) {
|
||||
this.paths = getPaths(
|
||||
@ -369,11 +539,11 @@ let componentConfigs = {
|
||||
{
|
||||
heatline: c.heatline,
|
||||
regionFill: c.regionFill,
|
||||
spline: c.spline
|
||||
spline: c.spline,
|
||||
},
|
||||
{
|
||||
svgDefs: c.svgDefs,
|
||||
zeroLine: data.zeroLine
|
||||
zeroLine: data.zeroLine,
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -386,7 +556,7 @@ let componentConfigs = {
|
||||
y,
|
||||
data.radius,
|
||||
c.color,
|
||||
(c.valuesOverPoints ? data.values[j] : ''),
|
||||
c.valuesOverPoints ? data.values[j] : "",
|
||||
j
|
||||
);
|
||||
});
|
||||
@ -419,28 +589,36 @@ let componentConfigs = {
|
||||
let animateElements = [];
|
||||
|
||||
if (Object.keys(this.paths).length) {
|
||||
animateElements = animateElements.concat(animatePath(
|
||||
this.paths, newXPos, newYPos, newData.zeroLine, this.constants.spline));
|
||||
animateElements = animateElements.concat(
|
||||
animatePath(
|
||||
this.paths,
|
||||
newXPos,
|
||||
newYPos,
|
||||
newData.zeroLine,
|
||||
this.constants.spline
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.units.length) {
|
||||
this.units.map((dot, i) => {
|
||||
animateElements = animateElements.concat(animateDot(
|
||||
dot, newXPos[i], newYPos[i]));
|
||||
animateElements = animateElements.concat(
|
||||
animateDot(dot, newXPos[i], newYPos[i])
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return animateElements;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
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]];
|
||||
Object.assign(config, {
|
||||
constants: constants,
|
||||
getData: getData
|
||||
getData: getData,
|
||||
});
|
||||
return new ChartComponent(config);
|
||||
}
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
import { $ } from '../utils/dom';
|
||||
import { TOOLTIP_POINTER_TRIANGLE_HEIGHT } from '../utils/constants';
|
||||
import { $ } from "../utils/dom";
|
||||
import { TOOLTIP_POINTER_TRIANGLE_HEIGHT } from "../utils/constants";
|
||||
|
||||
export default class SvgTip {
|
||||
constructor({
|
||||
parent = null,
|
||||
colors = []
|
||||
}) {
|
||||
constructor({ parent = null, colors = [] }) {
|
||||
this.parent = parent;
|
||||
this.colors = colors;
|
||||
this.titleName = '';
|
||||
this.titleValue = '';
|
||||
this.titleName = "";
|
||||
this.titleValue = "";
|
||||
this.listValues = [];
|
||||
this.titleValueFirst = 0;
|
||||
|
||||
@ -32,20 +29,20 @@ export default class SvgTip {
|
||||
}
|
||||
|
||||
makeTooltip() {
|
||||
this.container = $.create('div', {
|
||||
this.container = $.create("div", {
|
||||
inside: this.parent,
|
||||
className: 'graph-svg-tip comparison',
|
||||
className: "graph-svg-tip comparison",
|
||||
innerHTML: `<span class="title"></span>
|
||||
<ul class="data-point-list"></ul>
|
||||
<div class="svg-pointer"></div>`
|
||||
<div class="svg-pointer"></div>`,
|
||||
});
|
||||
this.hideTip();
|
||||
|
||||
this.title = this.container.querySelector('.title');
|
||||
this.list = this.container.querySelector('.data-point-list');
|
||||
this.dataPointList = this.container.querySelector('.data-point-list');
|
||||
this.title = this.container.querySelector(".title");
|
||||
this.list = this.container.querySelector(".data-point-list");
|
||||
this.dataPointList = this.container.querySelector(".data-point-list");
|
||||
|
||||
this.parent.addEventListener('mouseleave', () => {
|
||||
this.parent.addEventListener("mouseleave", () => {
|
||||
this.hideTip();
|
||||
});
|
||||
}
|
||||
@ -53,7 +50,7 @@ export default class SvgTip {
|
||||
fill() {
|
||||
let title;
|
||||
if (this.index) {
|
||||
this.container.setAttribute('data-point-index', this.index);
|
||||
this.container.setAttribute("data-point-index", this.index);
|
||||
}
|
||||
if (this.titleValueFirst) {
|
||||
title = `<strong>${this.titleValue}</strong>${this.titleName}`;
|
||||
@ -62,23 +59,24 @@ export default class SvgTip {
|
||||
}
|
||||
|
||||
if (this.listValues.length > 4) {
|
||||
this.list.classList.add('tooltip-grid');
|
||||
this.list.classList.add("tooltip-grid");
|
||||
} else {
|
||||
this.list.classList.remove('tooltip-grid');
|
||||
this.list.classList.remove("tooltip-grid");
|
||||
}
|
||||
|
||||
this.title.innerHTML = title;
|
||||
this.dataPointList.innerHTML = '';
|
||||
this.dataPointList.innerHTML = "";
|
||||
|
||||
this.listValues.map((set, i) => {
|
||||
const color = this.colors[i] || 'black';
|
||||
let value = set.formatted === 0 || set.formatted ? set.formatted : set.value;
|
||||
let li = $.create('li', {
|
||||
const color = this.colors[i] || "black";
|
||||
let value =
|
||||
set.formatted === 0 || set.formatted ? set.formatted : set.value;
|
||||
let li = $.create("li", {
|
||||
innerHTML: `<div class="tooltip-legend" style="background: ${color};"></div>
|
||||
<div>
|
||||
<div class="tooltip-value">${ value === 0 || value ? value : '' }</div>
|
||||
<div class="tooltip-label">${set.title ? set.title : '' }</div>
|
||||
</div>`
|
||||
<div class="tooltip-value">${value === 0 || value ? value : ""}</div>
|
||||
<div class="tooltip-label">${set.title ? set.title : ""}</div>
|
||||
</div>`,
|
||||
});
|
||||
|
||||
this.dataPointList.appendChild(li);
|
||||
@ -88,12 +86,12 @@ export default class SvgTip {
|
||||
calcPosition() {
|
||||
let width = this.container.offsetWidth;
|
||||
|
||||
this.top = this.y - this.container.offsetHeight
|
||||
- TOOLTIP_POINTER_TRIANGLE_HEIGHT;
|
||||
this.top =
|
||||
this.y - this.container.offsetHeight - TOOLTIP_POINTER_TRIANGLE_HEIGHT;
|
||||
this.left = this.x - width / 2;
|
||||
let maxLeft = this.parent.offsetWidth - width;
|
||||
|
||||
let pointer = this.container.querySelector('.svg-pointer');
|
||||
let pointer = this.container.querySelector(".svg-pointer");
|
||||
|
||||
if (this.left < 0) {
|
||||
pointer.style.left = `calc(50% - ${-1 * this.left}px)`;
|
||||
@ -121,14 +119,14 @@ export default class SvgTip {
|
||||
}
|
||||
|
||||
hideTip() {
|
||||
this.container.style.top = '0px';
|
||||
this.container.style.left = '0px';
|
||||
this.container.style.opacity = '0';
|
||||
this.container.style.top = "0px";
|
||||
this.container.style.left = "0px";
|
||||
this.container.style.opacity = "0";
|
||||
}
|
||||
|
||||
showTip() {
|
||||
this.container.style.top = this.top + 'px';
|
||||
this.container.style.left = this.left + 'px';
|
||||
this.container.style.opacity = '1';
|
||||
this.container.style.top = this.top + "px";
|
||||
this.container.style.left = this.left + "px";
|
||||
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 PATH_ANIM_DUR = 350;
|
||||
export const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
||||
export const REPLACE_ALL_NEW_DUR = 250;
|
||||
|
||||
export const STD_EASING = 'easein';
|
||||
export const STD_EASING = "easein";
|
||||
|
||||
export function translate(unit, oldCoord, newCoord, duration) {
|
||||
let old = typeof oldCoord === 'string' ? oldCoord : oldCoord.join(', ');
|
||||
let old = typeof oldCoord === "string" ? oldCoord : oldCoord.join(", ");
|
||||
return [
|
||||
unit,
|
||||
{transform: newCoord.join(', ')},
|
||||
{ transform: newCoord.join(", ") },
|
||||
duration,
|
||||
STD_EASING,
|
||||
"translate",
|
||||
{transform: old}
|
||||
{ transform: old },
|
||||
];
|
||||
}
|
||||
|
||||
@ -33,38 +33,50 @@ export function animateRegion(rectGroup, newY1, newY2, oldY2) {
|
||||
let width = rect.getAttribute("width");
|
||||
let rectAnim = [
|
||||
rect,
|
||||
{ height: newHeight, 'stroke-dasharray': `${width}, ${newHeight}` },
|
||||
{ height: newHeight, "stroke-dasharray": `${width}, ${newHeight}` },
|
||||
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];
|
||||
}
|
||||
|
||||
export function animateBar(bar, x, yTop, width, offset = 0, meta = {}) {
|
||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
|
||||
y -= offset;
|
||||
if(bar.nodeName !== 'rect') {
|
||||
if (bar.nodeName !== "rect") {
|
||||
let rect = bar.childNodes[0];
|
||||
let rectAnim = [
|
||||
rect,
|
||||
{ width: width, height: height },
|
||||
UNIT_ANIM_DUR,
|
||||
STD_EASING
|
||||
STD_EASING,
|
||||
];
|
||||
|
||||
let oldCoordStr = bar.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||
let groupAnim = translate(bar, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||
return [rectAnim, groupAnim];
|
||||
} 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);
|
||||
}
|
||||
|
||||
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 groupAnim = translate(dot, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||
return [groupAnim];
|
||||
@ -76,12 +88,16 @@ export function animateDot(dot, x, y) {
|
||||
|
||||
export function animatePath(paths, newXList, newYList, zeroLine, spline) {
|
||||
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)
|
||||
pointsStr = getSplineCurvePointsStr(newXList, newYList);
|
||||
if (spline) 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);
|
||||
|
||||
if (paths.region) {
|
||||
@ -92,7 +108,7 @@ export function animatePath(paths, newXList, newYList, zeroLine, spline) {
|
||||
paths.region,
|
||||
{ d: "M" + regStartPt + pointsStr + regEndPt },
|
||||
PATH_ANIM_DUR,
|
||||
STD_EASING
|
||||
STD_EASING,
|
||||
];
|
||||
pathComponents.push(animRegion);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// Leveraging SMIL Animations
|
||||
|
||||
import { REPLACE_ALL_NEW_DUR } from './animate';
|
||||
import { REPLACE_ALL_NEW_DUR } from "./animate";
|
||||
|
||||
const EASING = {
|
||||
ease: "0.25 0.1 0.25 1",
|
||||
@ -8,22 +8,35 @@ const EASING = {
|
||||
// easein: "0.42 0 1 1",
|
||||
easein: "0.1 0.8 0.2 1",
|
||||
easeout: "0 0 0.58 1",
|
||||
easeinout: "0.42 0 0.58 1"
|
||||
easeinout: "0.42 0 0.58 1",
|
||||
};
|
||||
|
||||
function animateSVGElement(element, props, dur, easingType="linear", type=undefined, oldValues={}) {
|
||||
|
||||
function animateSVGElement(
|
||||
element,
|
||||
props,
|
||||
dur,
|
||||
easingType = "linear",
|
||||
type = undefined,
|
||||
oldValues = {}
|
||||
) {
|
||||
let animElement = element.cloneNode(true);
|
||||
let newElement = element.cloneNode(true);
|
||||
|
||||
for (var attributeName in props) {
|
||||
let animateElement;
|
||||
if(attributeName === 'transform') {
|
||||
animateElement = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform");
|
||||
if (attributeName === "transform") {
|
||||
animateElement = document.createElementNS(
|
||||
"http://www.w3.org/2000/svg",
|
||||
"animateTransform"
|
||||
);
|
||||
} 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 animAttr = {
|
||||
@ -36,7 +49,7 @@ function animateSVGElement(element, props, dur, easingType="linear", type=undefi
|
||||
keySplines: EASING[easingType],
|
||||
keyTimes: "0;1",
|
||||
calcMode: "spline",
|
||||
fill: 'freeze'
|
||||
fill: "freeze",
|
||||
};
|
||||
|
||||
if (type) {
|
||||
@ -59,7 +72,8 @@ function animateSVGElement(element, props, dur, easingType="linear", type=undefi
|
||||
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.webkitTransform = style;
|
||||
element.style.msTransform = style;
|
||||
@ -71,7 +85,7 @@ function animateSVG(svgContainer, elements) {
|
||||
let newElements = [];
|
||||
let animElements = [];
|
||||
|
||||
elements.map(element => {
|
||||
elements.map((element) => {
|
||||
let unit = element[0];
|
||||
let parent = unit.parentNode;
|
||||
|
||||
@ -83,14 +97,18 @@ function animateSVG(svgContainer, elements) {
|
||||
newElements.push(newElement);
|
||||
animElements.push([animElement, parent]);
|
||||
|
||||
if (parent) {
|
||||
parent.replaceChild(animElement, unit);
|
||||
}
|
||||
});
|
||||
|
||||
let animSvg = svgContainer.cloneNode(true);
|
||||
|
||||
animElements.map((animElement, i) => {
|
||||
if (animElement[1]) {
|
||||
animElement[1].replaceChild(newElements[i], animElement[0]);
|
||||
elements[i][0] = newElements[i];
|
||||
}
|
||||
});
|
||||
|
||||
return animSvg;
|
||||
@ -103,7 +121,6 @@ export function runSMILAnimation(parent, svgElement, elementsToAnimate) {
|
||||
if (svgElement.parentNode == parent) {
|
||||
parent.removeChild(svgElement);
|
||||
parent.appendChild(animSvgElement);
|
||||
|
||||
}
|
||||
|
||||
// Replace the new svgElement (data has already been replaced)
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import { fillArray } from '../utils/helpers';
|
||||
import { DEFAULT_AXIS_CHART_TYPE, AXIS_DATASET_CHART_TYPES, DEFAULT_CHAR_WIDTH } from '../utils/constants';
|
||||
import { fillArray } from "../utils/helpers";
|
||||
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 || [];
|
||||
|
||||
let datasetLength = data.labels.length;
|
||||
@ -11,37 +16,40 @@ export function dataPrep(data, type) {
|
||||
let zeroArray = new Array(datasetLength).fill(0);
|
||||
if (!datasets) {
|
||||
// default
|
||||
datasets = [{
|
||||
values: zeroArray
|
||||
}];
|
||||
datasets = [
|
||||
{
|
||||
values: zeroArray,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
datasets.map(d=> {
|
||||
datasets.map((d) => {
|
||||
// Set values
|
||||
if (!d.values) {
|
||||
d.values = zeroArray;
|
||||
} else {
|
||||
// Check for non values
|
||||
let vals = d.values;
|
||||
vals = vals.map(val => (!isNaN(val) ? val : 0));
|
||||
vals = vals.map((val) => (!isNaN(val) ? val : 0));
|
||||
|
||||
// Trim or extend
|
||||
if (vals.length > datasetLength) {
|
||||
vals = vals.slice(0, datasetLength);
|
||||
}
|
||||
if (config) {
|
||||
vals = fillArray(vals, datasetLength - vals.length, null);
|
||||
} else {
|
||||
vals = fillArray(vals, datasetLength - vals.length, 0);
|
||||
}
|
||||
d.values = vals;
|
||||
}
|
||||
|
||||
// Set labels
|
||||
//
|
||||
|
||||
// Set type
|
||||
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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Markers
|
||||
@ -49,7 +57,7 @@ export function dataPrep(data, type) {
|
||||
// Regions
|
||||
// data.yRegions = data.yRegions || [];
|
||||
if (data.yRegions) {
|
||||
data.yRegions.map(d => {
|
||||
data.yRegions.map((d) => {
|
||||
if (d.end < d.start) {
|
||||
[d.start, d.end] = [d.end, d.start];
|
||||
}
|
||||
@ -65,11 +73,13 @@ export function zeroDataPrep(realData) {
|
||||
|
||||
let zeroData = {
|
||||
labels: realData.labels.slice(0, -1),
|
||||
datasets: realData.datasets.map(d => {
|
||||
datasets: realData.datasets.map((d) => {
|
||||
const { axisID } = d;
|
||||
return {
|
||||
name: '',
|
||||
axisID,
|
||||
name: "",
|
||||
values: zeroArray.slice(0, -1),
|
||||
chartType: d.chartType
|
||||
chartType: d.chartType,
|
||||
};
|
||||
}),
|
||||
};
|
||||
@ -78,8 +88,8 @@ export function zeroDataPrep(realData) {
|
||||
zeroData.yMarkers = [
|
||||
{
|
||||
value: 0,
|
||||
label: ''
|
||||
}
|
||||
label: "",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -88,8 +98,8 @@ export function zeroDataPrep(realData) {
|
||||
{
|
||||
start: 0,
|
||||
end: 0,
|
||||
label: ''
|
||||
}
|
||||
label: "",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -97,29 +107,28 @@ export function zeroDataPrep(realData) {
|
||||
}
|
||||
|
||||
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;
|
||||
let allowedLetters = allowedSpace / DEFAULT_CHAR_WIDTH;
|
||||
|
||||
let seriesMultiple;
|
||||
if (isSeries) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
let calcLabels = labels.map((label, i) => {
|
||||
label += "";
|
||||
if (label.length > allowedLetters) {
|
||||
|
||||
if (!isSeries) {
|
||||
if (allowedLetters - 3 > 0) {
|
||||
label = label.slice(0, allowedLetters - 3) + " ...";
|
||||
} else {
|
||||
label = label.slice(0, allowedLetters) + '..';
|
||||
label = label.slice(0, allowedLetters) + "..";
|
||||
}
|
||||
} else {
|
||||
if(i % seriesMultiple !== 0) {
|
||||
if (i % seriesMultiple !== 0 && i !== labels.length - 1) {
|
||||
label = "";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
const PRESET_COLOR_MAP = {
|
||||
'pink': '#F683AE',
|
||||
'blue': '#318AD8',
|
||||
'green': '#48BB74',
|
||||
'grey': '#A6B1B9',
|
||||
'red': '#F56B6B',
|
||||
'yellow': '#FACF7A',
|
||||
'purple': '#44427B',
|
||||
'teal': '#5FD8C4',
|
||||
'cyan': '#15CCEF',
|
||||
'orange': '#F8814F',
|
||||
'light-pink': '#FED7E5',
|
||||
'light-blue': '#BFDDF7',
|
||||
'light-green': '#48BB74',
|
||||
'light-grey': '#F4F5F6',
|
||||
'light-red': '#F6DFDF',
|
||||
'light-yellow': '#FEE9BF',
|
||||
'light-purple': '#E8E8F7',
|
||||
'light-teal': '#D3FDF6',
|
||||
'light-cyan': '#DDF8FD',
|
||||
'light-orange': '#FECDB8'
|
||||
pink: "#F683AE",
|
||||
blue: "#318AD8",
|
||||
green: "#48BB74",
|
||||
grey: "#A6B1B9",
|
||||
red: "#F56B6B",
|
||||
yellow: "#FACF7A",
|
||||
purple: "#44427B",
|
||||
teal: "#5FD8C4",
|
||||
cyan: "#15CCEF",
|
||||
orange: "#F8814F",
|
||||
"light-pink": "#FED7E5",
|
||||
"light-blue": "#BFDDF7",
|
||||
"light-green": "#48BB74",
|
||||
"light-grey": "#F4F5F6",
|
||||
"light-red": "#F6DFDF",
|
||||
"light-yellow": "#FEE9BF",
|
||||
"light-purple": "#E8E8F7",
|
||||
"light-teal": "#D3FDF6",
|
||||
"light-cyan": "#DDF8FD",
|
||||
"light-orange": "#FECDB8",
|
||||
};
|
||||
|
||||
function limitColor(r) {
|
||||
@ -36,23 +36,25 @@ export function lightenDarkenColor(color, amt) {
|
||||
}
|
||||
let num = parseInt(col, 16);
|
||||
let r = limitColor((num >> 16) + amt);
|
||||
let b = limitColor(((num >> 8) & 0x00FF) + amt);
|
||||
let g = limitColor((num & 0x0000FF) + amt);
|
||||
let b = limitColor(((num >> 8) & 0x00ff) + amt);
|
||||
let g = limitColor((num & 0x0000ff) + amt);
|
||||
return (usePound ? "#" : "") + (g | (b << 8) | (r << 16)).toString(16);
|
||||
}
|
||||
|
||||
export function isValidColor(string) {
|
||||
// https://stackoverflow.com/a/32685393
|
||||
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);
|
||||
}
|
||||
|
||||
export const getColor = (color) => {
|
||||
// When RGB color, convert to hexadecimal (alpha value is omitted)
|
||||
if((/rgb[a]{0,1}\([\d, ]+\)/gim).test(color)) {
|
||||
return (/\D+(\d*)\D+(\d*)\D+(\d*)/gim).exec(color)
|
||||
.map((x, i) => (i !== 0 ? Number(x).toString(16) : '#'))
|
||||
if (/rgb[a]{0,1}\([\d, ]+\)/gim.test(color)) {
|
||||
return /\D+(\d*)\D+(\d*)\D+(\d*)/gim
|
||||
.exec(color)
|
||||
.map((x, i) => (i !== 0 ? Number(x).toString(16) : "#"))
|
||||
.reduce((c, ch) => `${c}${ch}`);
|
||||
}
|
||||
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 = {
|
||||
bar: ['line', 'scatter', 'percentage', 'pie'],
|
||||
line: ['scatter', 'bar', 'percentage', 'pie'],
|
||||
pie: ['line', 'scatter', 'percentage', 'bar'],
|
||||
percentage: ['bar', 'line', 'scatter', 'pie'],
|
||||
heatmap: []
|
||||
bar: ["line", "scatter", "percentage", "pie"],
|
||||
line: ["scatter", "bar", "percentage", "pie"],
|
||||
pie: ["line", "scatter", "percentage", "bar"],
|
||||
percentage: ["bar", "line", "scatter", "pie"],
|
||||
heatmap: [],
|
||||
};
|
||||
|
||||
export const DATA_COLOR_DIVISIONS = {
|
||||
bar: 'datasets',
|
||||
line: 'datasets',
|
||||
pie: 'labels',
|
||||
percentage: 'labels',
|
||||
heatmap: HEATMAP_DISTRIBUTION_SIZE
|
||||
bar: "datasets",
|
||||
line: "datasets",
|
||||
pie: "labels",
|
||||
percentage: "labels",
|
||||
heatmap: HEATMAP_DISTRIBUTION_SIZE,
|
||||
};
|
||||
|
||||
export const BASE_MEASURES = {
|
||||
@ -21,13 +28,13 @@ export const BASE_MEASURES = {
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 20,
|
||||
right: 20
|
||||
right: 20,
|
||||
},
|
||||
paddings: {
|
||||
top: 20,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
right: 10
|
||||
right: 10,
|
||||
},
|
||||
|
||||
baseHeight: 240,
|
||||
@ -46,15 +53,19 @@ export function getLeftOffset(m) {
|
||||
}
|
||||
|
||||
export function getExtraHeight(m) {
|
||||
let totalExtraHeight = m.margins.top + m.margins.bottom
|
||||
+ m.paddings.top + m.paddings.bottom
|
||||
+ m.titleHeight + m.legendHeight;
|
||||
let totalExtraHeight =
|
||||
m.margins.top +
|
||||
m.margins.bottom +
|
||||
m.paddings.top +
|
||||
m.paddings.bottom +
|
||||
m.titleHeight +
|
||||
m.legendHeight;
|
||||
return totalExtraHeight;
|
||||
}
|
||||
|
||||
export function getExtraWidth(m) {
|
||||
let totalExtraWidth = m.margins.left + m.margins.right
|
||||
+ m.paddings.left + m.paddings.right;
|
||||
let totalExtraWidth =
|
||||
m.margins.left + m.margins.right + m.paddings.left + m.paddings.right;
|
||||
|
||||
return totalExtraWidth;
|
||||
}
|
||||
@ -62,19 +73,19 @@ export function getExtraWidth(m) {
|
||||
export const INIT_CHART_UPDATE_TIMEOUT = 700;
|
||||
export const CHART_POST_ANIMATE_TIMEOUT = 400;
|
||||
|
||||
export const DEFAULT_AXIS_CHART_TYPE = 'line';
|
||||
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
||||
export const DEFAULT_AXIS_CHART_TYPE = "line";
|
||||
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 MIN_BAR_PERCENT_HEIGHT = 0.00;
|
||||
export const BAR_CHART_SPACE_RATIO = 0.5;
|
||||
export const MIN_BAR_PERCENT_HEIGHT = 0.0;
|
||||
|
||||
export const LINE_CHART_DOT_SIZE = 4;
|
||||
export const DOT_OVERLAY_SIZE_INCR = 4;
|
||||
|
||||
export const PERCENTAGE_BAR_DEFAULT_HEIGHT = 20;
|
||||
export const PERCENTAGE_BAR_DEFAULT_DEPTH = 2;
|
||||
export const PERCENTAGE_BAR_DEFAULT_HEIGHT = 16;
|
||||
|
||||
// Fixed 5-color theme,
|
||||
// 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 TOOLTIP_POINTER_TRIANGLE_HEIGHT = 7.48;
|
||||
const DEFAULT_CHART_COLORS = ['pink', 'blue', 'green', 'grey', 'red', 'yellow', 'purple', 'teal', 'cyan', 'orange'];
|
||||
const HEATMAP_COLORS_GREEN = ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
export const HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'];
|
||||
export const HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
const DEFAULT_CHART_COLORS = [
|
||||
"pink",
|
||||
"blue",
|
||||
"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 = {
|
||||
bar: DEFAULT_CHART_COLORS,
|
||||
@ -97,7 +137,7 @@ export const DEFAULT_COLORS = {
|
||||
pie: DEFAULT_CHART_COLORS,
|
||||
percentage: DEFAULT_CHART_COLORS,
|
||||
heatmap: HEATMAP_COLORS_GREEN,
|
||||
donut: DEFAULT_CHART_COLORS
|
||||
donut: DEFAULT_CHART_COLORS,
|
||||
};
|
||||
|
||||
// Universal constants
|
||||
|
||||
@ -6,14 +6,53 @@ export const DAYS_IN_YEAR = 375;
|
||||
export const NO_OF_MILLIS = 1000;
|
||||
export const SEC_IN_DAY = 86400;
|
||||
|
||||
export const MONTH_NAMES = ["January", "February", "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 MONTH_NAMES = [
|
||||
"January",
|
||||
"February",
|
||||
"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 = ["Sunday", "Monday", "Tuesday", "Wednesday",
|
||||
"Thursday", "Friday", "Saturday"];
|
||||
export const DAY_NAMES_SHORT = [
|
||||
"Sun",
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
];
|
||||
export const DAY_NAMES = [
|
||||
"Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
];
|
||||
|
||||
// https://stackoverflow.com/a/11252167/6495043
|
||||
function treatAsUtc(date) {
|
||||
@ -22,14 +61,20 @@ function treatAsUtc(date) {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function toMidnightUTC(date) {
|
||||
let result = new Date(date);
|
||||
result.setUTCHours(0, result.getTimezoneOffset(), 0, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getYyyyMmDd(date) {
|
||||
let dd = date.getDate();
|
||||
let mm = date.getMonth() + 1; // getMonth() is zero-based
|
||||
return [
|
||||
date.getFullYear(),
|
||||
(mm>9 ? '' : '0') + mm,
|
||||
(dd>9 ? '' : '0') + dd
|
||||
].join('-');
|
||||
(mm > 9 ? "" : "0") + mm,
|
||||
(dd > 9 ? "" : "0") + dd,
|
||||
].join("-");
|
||||
}
|
||||
|
||||
export function clone(date) {
|
||||
@ -61,8 +106,10 @@ export function getDaysBetween(startDate, endDate) {
|
||||
}
|
||||
|
||||
export function areInSameMonth(startDate, endDate) {
|
||||
return startDate.getMonth() === endDate.getMonth()
|
||||
&& startDate.getFullYear() === endDate.getFullYear();
|
||||
return (
|
||||
startDate.getMonth() === endDate.getMonth() &&
|
||||
startDate.getFullYear() === endDate.getFullYear()
|
||||
);
|
||||
}
|
||||
|
||||
export function getMonthName(i, short = false) {
|
||||
@ -79,7 +126,7 @@ export function setDayToSunday(date) {
|
||||
let newDate = clone(date);
|
||||
const day = newDate.getDay();
|
||||
if (day !== 0) {
|
||||
addDays(newDate, (-1) * day);
|
||||
addDays(newDate, -1 * day);
|
||||
}
|
||||
return newDate;
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
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;
|
||||
while (node.previousSibling) {
|
||||
node = node.previousSibling;
|
||||
@ -20,22 +21,19 @@ $.create = (tag, o) => {
|
||||
|
||||
if (i === "inside") {
|
||||
$(val).appendChild(element);
|
||||
}
|
||||
else if (i === "around") {
|
||||
} else if (i === "around") {
|
||||
var ref = $(val);
|
||||
ref.parentNode.insertBefore(element, ref);
|
||||
element.appendChild(ref);
|
||||
|
||||
} else if (i === "styles") {
|
||||
if (typeof val === "object") {
|
||||
Object.keys(val).map(prop => {
|
||||
Object.keys(val).map((prop) => {
|
||||
element.style[prop] = val[prop];
|
||||
});
|
||||
}
|
||||
} else if (i in element) {
|
||||
element[i] = val;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
element.setAttribute(i, val);
|
||||
}
|
||||
}
|
||||
@ -49,8 +47,12 @@ export function getOffset(element) {
|
||||
// https://stackoverflow.com/a/7436602/6495043
|
||||
// rect.top varies with scroll, so we add whatever has been
|
||||
// scrolled to it to get absolute distance from actual page top
|
||||
top: rect.top + (document.documentElement.scrollTop || document.body.scrollTop),
|
||||
left: rect.left + (document.documentElement.scrollLeft || document.body.scrollLeft)
|
||||
top:
|
||||
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,
|
||||
// is hidden via the display style property.
|
||||
export function isHidden(el) {
|
||||
return (el.offsetParent === null);
|
||||
return el.offsetParent === null;
|
||||
}
|
||||
|
||||
export function isElementInViewport(el) {
|
||||
@ -68,15 +70,19 @@ export function isElementInViewport(el) {
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */
|
||||
rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */
|
||||
rect.bottom <=
|
||||
(window.innerHeight ||
|
||||
document.documentElement.clientHeight) /*or $(window).height() */ &&
|
||||
rect.right <=
|
||||
(window.innerWidth ||
|
||||
document.documentElement.clientWidth) /*or $(window).width() */
|
||||
);
|
||||
}
|
||||
|
||||
export function getElementContentWidth(element) {
|
||||
var styles = window.getComputedStyle(element);
|
||||
var padding = parseFloat(styles.paddingLeft) +
|
||||
parseFloat(styles.paddingRight);
|
||||
var padding =
|
||||
parseFloat(styles.paddingLeft) + parseFloat(styles.paddingRight);
|
||||
|
||||
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}`);
|
||||
|
||||
forEachNode($children, (node, i) => {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { fillArray } from './helpers';
|
||||
import { fillArray } from "./helpers";
|
||||
|
||||
export function getBarHeightAndYAttr(yTop, zeroLine) {
|
||||
let height, y;
|
||||
@ -13,9 +13,11 @@ export function getBarHeightAndYAttr(yTop, zeroLine) {
|
||||
return [height, y];
|
||||
}
|
||||
|
||||
export function equilizeNoOfElements(array1, array2,
|
||||
extraCount = array2.length - array1.length) {
|
||||
|
||||
export function equilizeNoOfElements(
|
||||
array1,
|
||||
array2,
|
||||
extraCount = array2.length - array1.length
|
||||
) {
|
||||
// Doesn't work if either has zero elements.
|
||||
if (extraCount > 0) {
|
||||
array1 = fillArray(array1, extraCount);
|
||||
@ -30,7 +32,7 @@ export function truncateString(txt, len) {
|
||||
return;
|
||||
}
|
||||
if (txt.length > len) {
|
||||
return txt.slice(0, len-3) + '...';
|
||||
return txt.slice(0, len - 3) + "...";
|
||||
} else {
|
||||
return txt;
|
||||
}
|
||||
@ -38,8 +40,8 @@ export function truncateString(txt, len) {
|
||||
|
||||
export function shortenLargeNumber(label) {
|
||||
let number;
|
||||
if (typeof label === 'number') number = label;
|
||||
else if (typeof label === 'string') {
|
||||
if (typeof label === "number") number = label;
|
||||
else if (typeof label === "string") {
|
||||
number = Number(label);
|
||||
if (Number.isNaN(number)) return label;
|
||||
}
|
||||
@ -48,15 +50,15 @@ export function shortenLargeNumber(label) {
|
||||
let p = Math.floor(Math.log10(Math.abs(number)));
|
||||
if (p <= 2) return number; // Return as is for a 3 digit number of less
|
||||
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
|
||||
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)
|
||||
export function getSplineCurvePointsStr(xList, yList) {
|
||||
|
||||
let points = [];
|
||||
for (let i = 0; i < xList.length; i++) {
|
||||
points.push([xList[i], yList[i]]);
|
||||
@ -68,7 +70,7 @@ export function getSplineCurvePointsStr(xList, yList) {
|
||||
let lengthY = pointB[1] - pointA[1];
|
||||
return {
|
||||
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) => {
|
||||
return points.reduce((acc, point, i, a) => i === 0
|
||||
? `${point[0]},${point[1]}`
|
||||
: `${acc} ${command(point, i, a)}`, '');
|
||||
return points.reduce(
|
||||
(acc, point, i, a) =>
|
||||
i === 0 ? `${point[0]},${point[1]}` : `${acc} ${command(point, i, a)}`,
|
||||
""
|
||||
);
|
||||
};
|
||||
|
||||
return pointStr(points, bezierCommand);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,8 @@
|
||||
import { $ } from '../utils/dom';
|
||||
import { CSSTEXT } from '../../css/chartsCss';
|
||||
import { $ } from "../utils/dom";
|
||||
import { CSSTEXT } from "../../css/chartsCss";
|
||||
|
||||
export function downloadFile(filename, data) {
|
||||
var a = document.createElement('a');
|
||||
var a = document.createElement("a");
|
||||
a.style = "display: none";
|
||||
var blob = new Blob(data, { type: "image/svg+xml; charset=utf-8" });
|
||||
var url = window.URL.createObjectURL(blob);
|
||||
@ -18,15 +18,15 @@ export function downloadFile(filename, data) {
|
||||
|
||||
export function prepareForExport(svg) {
|
||||
let clone = svg.cloneNode(true);
|
||||
clone.classList.add('chart-container');
|
||||
clone.setAttribute('xmlns', "http://www.w3.org/2000/svg");
|
||||
clone.setAttribute('xmlns:xlink', "http://www.w3.org/1999/xlink");
|
||||
let styleEl = $.create('style', {
|
||||
'innerHTML': CSSTEXT
|
||||
clone.classList.add("chart-container");
|
||||
clone.setAttribute("xmlns", "http://www.w3.org/2000/svg");
|
||||
clone.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
|
||||
let styleEl = $.create("style", {
|
||||
innerHTML: CSSTEXT,
|
||||
});
|
||||
clone.insertBefore(styleEl, clone.firstChild);
|
||||
|
||||
let container = $.create('div');
|
||||
let container = $.create("div");
|
||||
container.appendChild(clone);
|
||||
|
||||
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.
|
||||
@ -47,7 +47,7 @@ export function shuffle(array) {
|
||||
* @param {Boolean} start fill at start?
|
||||
*/
|
||||
export function fillArray(array, count, element, start = false) {
|
||||
if(!element) {
|
||||
if (element == undefined) {
|
||||
element = start ? array[0] : array[array.length - 1];
|
||||
}
|
||||
let fillerArray = new Array(Math.abs(count)).fill(element);
|
||||
@ -73,7 +73,7 @@ export function bindChange(obj, getFn, setFn) {
|
||||
get: function (target, prop) {
|
||||
getFn();
|
||||
return Reflect.get(target, prop);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -113,5 +113,31 @@ export function isValidNumber(candidate, nonNegative=false) {
|
||||
export function round(d) {
|
||||
// https://floating-point-gui.de/
|
||||
// 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) {
|
||||
// Calculates mantissa and exponent of a number
|
||||
@ -69,11 +69,19 @@ function getChartIntervals(maxValue, minValue=0) {
|
||||
normalMaxValue = normalMaxValue.toFixed(6);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export function calcChartIntervals(values, withMinimum=false) {
|
||||
export function calcChartIntervals(values, withMinimum = true, overrideCeiling=false, overrideFloor=false) {
|
||||
//*** Where the magic happens ***
|
||||
|
||||
// Calculates best-fit y intervals from given values
|
||||
@ -82,8 +90,17 @@ export function calcChartIntervals(values, withMinimum=false) {
|
||||
let maxValue = Math.max(...values);
|
||||
let minValue = Math.min(...values);
|
||||
|
||||
if (overrideCeiling) {
|
||||
maxValue = overrideCeiling
|
||||
}
|
||||
|
||||
if (overrideFloor) {
|
||||
minValue = overrideFloor
|
||||
}
|
||||
|
||||
// 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) {
|
||||
let intervals = getChartIntervals(maxValue);
|
||||
@ -94,7 +111,7 @@ export function calcChartIntervals(values, withMinimum=false) {
|
||||
let value = 0;
|
||||
for (var i = 1; value < absMinValue; i++) {
|
||||
value += intervalSize;
|
||||
intervals.unshift((-1) * value);
|
||||
intervals.unshift(-1 * value);
|
||||
}
|
||||
return intervals;
|
||||
}
|
||||
@ -111,7 +128,6 @@ export function calcChartIntervals(values, withMinimum=false) {
|
||||
}
|
||||
|
||||
// CASE II: Only minValue negative
|
||||
|
||||
else if (maxValue > 0 && minValue < 0) {
|
||||
// `withMinimum` irrelevant in this case,
|
||||
// We'll be handling both sides of zero separately
|
||||
@ -128,13 +144,11 @@ export function calcChartIntervals(values, withMinimum=false) {
|
||||
// Mirror: maxValue => absMinValue, then change sign
|
||||
exponent = normalize(absMinValue)[1];
|
||||
let posIntervals = getPositiveFirstIntervals(absMinValue, maxValue);
|
||||
intervals = posIntervals.reverse().map(d => d * (-1));
|
||||
intervals = posIntervals.reverse().map((d) => d * -1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// CASE III: Both non-positive
|
||||
|
||||
else if (maxValue <= 0 && minValue <= 0) {
|
||||
// Mirrored Case I:
|
||||
// Work with positives, then reverse the sign and array
|
||||
@ -149,10 +163,10 @@ export function calcChartIntervals(values, withMinimum=false) {
|
||||
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) {
|
||||
@ -166,19 +180,19 @@ export function getZeroIndex(yPts) {
|
||||
// Minimum value is positive
|
||||
// zero-line is off the chart: below
|
||||
let min = yPts[0];
|
||||
zeroIndex = (-1) * min / interval;
|
||||
zeroIndex = (-1 * min) / interval;
|
||||
} else {
|
||||
// Maximum value is negative
|
||||
// zero-line is off the chart: above
|
||||
let max = yPts[yPts.length - 1];
|
||||
zeroIndex = (-1) * max / interval + (yPts.length - 1);
|
||||
zeroIndex = (-1 * max) / interval + (yPts.length - 1);
|
||||
}
|
||||
return zeroIndex;
|
||||
}
|
||||
|
||||
export function getRealIntervals(max, noOfIntervals, min = 0, asc = 1) {
|
||||
let range = max - min;
|
||||
let part = range * 1.0 / noOfIntervals;
|
||||
let part = (range * 1.0) / noOfIntervals;
|
||||
let intervals = [];
|
||||
|
||||
for (var i = 0; i <= noOfIntervals; i++) {
|
||||
@ -205,13 +219,15 @@ export function isInRange(val, min, max) {
|
||||
}
|
||||
|
||||
export function isInRange2D(coord, minCoord, maxCoord) {
|
||||
return isInRange(coord[0], minCoord[0], maxCoord[0])
|
||||
&& isInRange(coord[1], minCoord[1], maxCoord[1]);
|
||||
return (
|
||||
isInRange(coord[0], minCoord[0], maxCoord[0]) &&
|
||||
isInRange(coord[1], minCoord[1], maxCoord[1])
|
||||
);
|
||||
}
|
||||
|
||||
export function getClosestInArray(goal, arr, index = false) {
|
||||
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;
|
||||
@ -235,5 +251,5 @@ export function calcDistribution(values, distributionSize) {
|
||||
}
|
||||
|
||||
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 colors = require('../colors');
|
||||
const assert = require("assert");
|
||||
const colors = require("../colors");
|
||||
|
||||
describe('utils.colors', () => {
|
||||
it('should return #aaabac for RGB()', () => {
|
||||
assert.equal(colors.getColor('rgb(170, 171, 172)'), '#aaabac');
|
||||
describe("utils.colors", () => {
|
||||
it("should return #aaabac for RGB()", () => {
|
||||
assert.equal(colors.getColor("rgb(170, 171, 172)"), "#aaabac");
|
||||
});
|
||||
it('should return #ff5858 for the named color red', () => {
|
||||
assert.equal(colors.getColor('red'), '#ff5858d');
|
||||
it("should return #ff5858 for the named color red", () => {
|
||||
assert.equal(colors.getColor("red"), "#ff5858d");
|
||||
});
|
||||
it('should return #1a5c29 for the hex color #1a5c29', () => {
|
||||
assert.equal(colors.getColor('#1a5c29'), '#1a5c29');
|
||||
it("should return #1a5c29 for the hex color #1a5c29", () => {
|
||||
assert.equal(colors.getColor("#1a5c29"), "#1a5c29");
|
||||
});
|
||||
});
|
||||
@ -1,10 +1,10 @@
|
||||
const assert = require('assert');
|
||||
const helpers = require('../helpers');
|
||||
const assert = require("assert");
|
||||
const helpers = require("../helpers");
|
||||
|
||||
describe('utils.helpers', () => {
|
||||
it('should return a value fixed upto 2 decimals', () => {
|
||||
describe("utils.helpers", () => {
|
||||
it("should return a value fixed upto 2 decimals", () => {
|
||||
assert.equal(helpers.floatTwo(1.234), 1.23);
|
||||
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