commit
a47a4d0eb7
6
.babelrc
6
.babelrc
@ -6,5 +6,9 @@
|
||||
}
|
||||
}]
|
||||
],
|
||||
"plugins": ["external-helpers"]
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": ["env"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
67
.gitignore
vendored
67
.gitignore
vendored
@ -1,6 +1,63 @@
|
||||
# cache
|
||||
node_modules
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
yarn.lock
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
.DS_Store
|
||||
14
.travis.yml
Normal file
14
.travis.yml
Normal file
@ -0,0 +1,14 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- "6"
|
||||
- "8"
|
||||
|
||||
before_install:
|
||||
- make install
|
||||
|
||||
script:
|
||||
- make test
|
||||
|
||||
after_success:
|
||||
- make coveralls
|
||||
45
Makefile
45
Makefile
@ -0,0 +1,45 @@
|
||||
-include .env
|
||||
|
||||
BASEDIR = $(realpath .)
|
||||
|
||||
SRCDIR = $(BASEDIR)/src
|
||||
DISTDIR = $(BASEDIR)/dist
|
||||
DOCSDIR = $(BASEDIR)/docs
|
||||
|
||||
PROJECT = frappe-charts
|
||||
|
||||
NODEMOD = $(BASEDIR)/node_modules
|
||||
NODEBIN = $(NODEMOD)/.bin
|
||||
|
||||
build: clean install
|
||||
$(NODEBIN)/rollup \
|
||||
--config $(BASEDIR)/rollup.config.js \
|
||||
--watch=$(watch)
|
||||
|
||||
clean:
|
||||
rm -rf \
|
||||
$(BASEDIR)/.nyc_output \
|
||||
$(BASEDIR)/.yarn-error.log
|
||||
|
||||
clear
|
||||
|
||||
install.dep:
|
||||
ifeq ($(shell command -v yarn),)
|
||||
@echo "Installing yarn..."
|
||||
npm install -g yarn
|
||||
endif
|
||||
|
||||
install: install.dep
|
||||
yarn --cwd $(BASEDIR)
|
||||
|
||||
test: clean
|
||||
$(NODEBIN)/cross-env \
|
||||
NODE_ENV=test \
|
||||
$(NODEBIN)/nyc \
|
||||
$(NODEBIN)/mocha \
|
||||
--require $(NODEMOD)/babel-register \
|
||||
--recursive \
|
||||
$(SRCDIR)/js/**/test/*.test.js
|
||||
|
||||
coveralls:
|
||||
$(NODEBIN)/nyc report --reporter text-lcov | $(NODEBIN)/coveralls
|
||||
14
README.md
14
README.md
@ -1,6 +1,8 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/frappe/design/blob/master/logos/charts-logo.svg" height="128">
|
||||
<h2>Frappe Charts</h2>
|
||||
<a href="https://frappe.github.io/charts">
|
||||
<h2>Frappe Charts</h2>
|
||||
</a>
|
||||
<p align="center">
|
||||
<p>GitHub-inspired modern, intuitive and responsive charts with zero dependencies</p>
|
||||
<a href="https://frappe.github.io/charts">
|
||||
@ -10,9 +12,15 @@
|
||||
</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>
|
||||
<a href="https://travis-ci.org/frappe/charts">
|
||||
<img src="https://img.shields.io/travis/frappe/charts.svg?style=flat-square">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@ -42,9 +50,9 @@
|
||||
* ...or include within your HTML
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/frappe-charts@1.0.0/dist/frappe-charts.min.iife.js"></script>
|
||||
<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.0.0/dist/frappe-charts.min.iife.js"></script>
|
||||
<script src="https://unpkg.com/frappe-charts@1.1.0/dist/frappe-charts.min.iife.js"></script>
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
1790
dist/frappe-charts.esm.js
vendored
1790
dist/frappe-charts.esm.js
vendored
File diff suppressed because it is too large
Load Diff
3
dist/frappe-charts.min.cjs.js
vendored
3
dist/frappe-charts.min.cjs.js
vendored
File diff suppressed because one or more lines are too long
1
dist/frappe-charts.min.cjs.js.map
vendored
Normal file
1
dist/frappe-charts.min.cjs.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.css
vendored
2
dist/frappe-charts.min.css
vendored
@ -1 +1 @@
|
||||
.chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .graph-focus-margin{margin:0 5%}.chart-container>.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-pack:distribute;justify-content:space-around;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding:10px}.chart-container .graph-stats-container:after,.chart-container .graph-stats-container:before{content:"";display:block}.chart-container .graph-stats-container .stats{padding-bottom:15px}.chart-container .graph-stats-container .stats-title{color:#8d99a6}.chart-container .graph-stats-container .stats-value{font-size:20px;font-weight:300}.chart-container .graph-stats-container .stats-description{font-size:12px;color:#8d99a6}.chart-container .graph-stats-container .graph-data .stats-value{color:#98d85b}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.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,.chart-container .multiaxis-chart .line-horizontal,.chart-container .multiaxis-chart .y-axis-guide{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 .progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.chart-container .progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#36414c;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.chart-container .graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.chart-container .graph-svg-tip ol,.chart-container .graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.chart-container .graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.chart-container .graph-svg-tip strong{color:#dfe2e5;font-weight:600}.chart-container .graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.chart-container .graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.chart-container .graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.chart-container .graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.chart-container .graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}.chart-container .indicator,.chart-container .indicator-right{background:none;font-size:12px;vertical-align:middle;font-weight:700;color:#6c7680}.chart-container .indicator i{content:"";display:inline-block;height:8px;width:8px;border-radius:8px}.chart-container .indicator:before,.chart-container .indicator i{margin:0 4px 0 0}.chart-container .indicator-right:after{margin:0 0 0 4px}
|
||||
.chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:1;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ol,.graph-svg-tip ul{padding-left:0;display:-webkit-box;display:-ms-flexbox;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;-webkit-box-flex:1;-ms-flex:1;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}
|
||||
3
dist/frappe-charts.min.esm.js
vendored
3
dist/frappe-charts.min.esm.js
vendored
File diff suppressed because one or more lines are too long
1
dist/frappe-charts.min.esm.js.map
vendored
Normal file
1
dist/frappe-charts.min.esm.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.iife.js
vendored
2
dist/frappe-charts.min.iife.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.iife.js.map
vendored
2
dist/frappe-charts.min.iife.js.map
vendored
File diff suppressed because one or more lines are too long
205
docs/assets/js/data.js
Normal file
205
docs/assets/js/data.js
Normal file
@ -0,0 +1,205 @@
|
||||
import { SEC_IN_DAY, MONTH_NAMES_SHORT, clone, timestampToMidnight, timestampSec, addDays } from '../../../src/js/utils/date-utils';
|
||||
import { getRandomBias } from '../../../src/js/utils/helpers';
|
||||
|
||||
// Composite Chart
|
||||
// ================================================================================
|
||||
const reportCountList = [152, 222, 199, 287, 534, 709,
|
||||
1179, 1256, 1632, 1856, 1850];
|
||||
|
||||
export const lineCompositeData = {
|
||||
labels: ["2007", "2008", "2009", "2010", "2011", "2012",
|
||||
"2013", "2014", "2015", "2016", "2017"],
|
||||
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Average 100 reports/month",
|
||||
value: 1200,
|
||||
options: { labelPos: 'left' }
|
||||
}
|
||||
],
|
||||
|
||||
datasets: [{
|
||||
"name": "Events",
|
||||
"values": reportCountList
|
||||
}]
|
||||
};
|
||||
|
||||
|
||||
export const fireball_5_25 = [
|
||||
[4, 0, 3, 1, 1, 2, 1, 1, 1, 0, 1, 1],
|
||||
[2, 3, 3, 2, 1, 3, 0, 1, 2, 7, 10, 4],
|
||||
[5, 6, 2, 4, 0, 1, 4, 3, 0, 2, 0, 1],
|
||||
[0, 2, 6, 2, 1, 1, 2, 3, 6, 3, 7, 8],
|
||||
[6, 8, 7, 7, 4, 5, 6, 5, 22, 12, 10, 11],
|
||||
[7, 10, 11, 7, 3, 2, 7, 7, 11, 15, 22, 20],
|
||||
[13, 16, 21, 18, 19, 17, 12, 17, 31, 28, 25, 29],
|
||||
[24, 14, 21, 14, 11, 15, 19, 21, 41, 22, 32, 18],
|
||||
[31, 20, 30, 22, 14, 17, 21, 35, 27, 50, 117, 24],
|
||||
[32, 24, 21, 27, 11, 27, 43, 37, 44, 40, 48, 32],
|
||||
[31, 38, 36, 26, 23, 23, 25, 29, 26, 47, 61, 50],
|
||||
];
|
||||
export const fireball_2_5 = [
|
||||
[22, 6, 6, 9, 7, 8, 6, 14, 19, 10, 8, 20],
|
||||
[11, 13, 12, 8, 9, 11, 9, 13, 10, 22, 40, 24],
|
||||
[20, 13, 13, 19, 13, 10, 14, 13, 20, 18, 5, 9],
|
||||
[7, 13, 16, 19, 12, 11, 21, 27, 27, 24, 33, 33],
|
||||
[38, 25, 28, 22, 31, 21, 35, 42, 37, 32, 46, 53],
|
||||
[50, 33, 36, 34, 35, 28, 27, 52, 58, 59, 75, 69],
|
||||
[54, 67, 67, 45, 66, 51, 38, 64, 90, 113, 116, 87],
|
||||
[84, 52, 56, 51, 55, 46, 50, 87, 114, 83, 152, 93],
|
||||
[73, 58, 59, 63, 56, 51, 83, 140, 103, 115, 265, 89],
|
||||
[106, 95, 94, 71, 77, 75, 99, 136, 129, 154, 168, 156],
|
||||
[81, 102, 95, 72, 58, 91, 89, 122, 124, 135, 183, 171],
|
||||
];
|
||||
export const fireballOver25 = [
|
||||
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
||||
[1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2],
|
||||
[3, 2, 1, 3, 2, 0, 2, 2, 2, 3, 0, 1],
|
||||
[2, 3, 5, 2, 1, 3, 0, 2, 3, 5, 1, 4],
|
||||
[7, 4, 6, 1, 9, 2, 2, 2, 20, 9, 4, 9],
|
||||
[5, 6, 1, 2, 5, 4, 5, 5, 16, 9, 14, 9],
|
||||
[5, 4, 7, 5, 1, 5, 3, 3, 5, 7, 22, 2],
|
||||
[5, 13, 11, 6, 1, 7, 9, 8, 14, 17, 16, 3],
|
||||
[8, 9, 8, 6, 4, 8, 5, 6, 14, 11, 21, 12]
|
||||
];
|
||||
|
||||
export const barCompositeData = {
|
||||
labels: MONTH_NAMES_SHORT,
|
||||
datasets: [
|
||||
{
|
||||
name: "Over 25 reports",
|
||||
values: fireballOver25[9],
|
||||
},
|
||||
{
|
||||
name: "5 to 25 reports",
|
||||
values: fireball_5_25[9],
|
||||
},
|
||||
{
|
||||
name: "2 to 5 reports",
|
||||
values: fireball_2_5[9]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Demo Chart multitype Chart
|
||||
// ================================================================================
|
||||
export const typeData = {
|
||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
|
||||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],
|
||||
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Marker",
|
||||
value: 43,
|
||||
options: { labelPos: 'left' }
|
||||
// type: 'dashed'
|
||||
}
|
||||
],
|
||||
|
||||
yRegions: [
|
||||
{
|
||||
label: "Region",
|
||||
start: -10,
|
||||
end: 50,
|
||||
options: { labelPos: 'right' }
|
||||
},
|
||||
],
|
||||
|
||||
datasets: [
|
||||
{
|
||||
name: "Some Data",
|
||||
values: [18, 40, 30, 35, 8, 52, 17, -4],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
},
|
||||
{
|
||||
name: "Another Set",
|
||||
values: [30, 50, -10, 15, 18, 32, 27, 14],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
},
|
||||
{
|
||||
name: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37],
|
||||
chartType: 'line'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const trendsData = {
|
||||
labels: [1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976,
|
||||
1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986,
|
||||
1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996,
|
||||
1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
|
||||
2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016] ,
|
||||
datasets: [
|
||||
{
|
||||
values: [132.9, 150.0, 149.4, 148.0, 94.4, 97.6, 54.1, 49.2, 22.5, 18.4,
|
||||
39.3, 131.0, 220.1, 218.9, 198.9, 162.4, 91.0, 60.5, 20.6, 14.8,
|
||||
33.9, 123.0, 211.1, 191.8, 203.3, 133.0, 76.1, 44.9, 25.1, 11.6,
|
||||
28.9, 88.3, 136.3, 173.9, 170.4, 163.6, 99.3, 65.3, 45.8, 24.7,
|
||||
12.6, 4.2, 4.8, 24.9, 80.8, 84.5, 94.0, 113.3, 69.8, 39.8]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const moonData = {
|
||||
names: ["Ganymede", "Callisto", "Io", "Europa"],
|
||||
masses: [14819000, 10759000, 8931900, 4800000],
|
||||
distances: [1070.412, 1882.709, 421.700, 671.034],
|
||||
diameters: [5262.4, 4820.6, 3637.4, 3121.6],
|
||||
};
|
||||
|
||||
// const jupiterMoons = {
|
||||
// 'Ganymede': {
|
||||
// mass: '14819000 x 10^16 kg',
|
||||
// 'semi-major-axis': '1070412 km',
|
||||
// 'diameter': '5262.4 km'
|
||||
// },
|
||||
// 'Callisto': {
|
||||
// mass: '10759000 x 10^16 kg',
|
||||
// 'semi-major-axis': '1882709 km',
|
||||
// 'diameter': '4820.6 km'
|
||||
// },
|
||||
// 'Io': {
|
||||
// mass: '8931900 x 10^16 kg',
|
||||
// 'semi-major-axis': '421700 km',
|
||||
// 'diameter': '3637.4 km'
|
||||
// },
|
||||
// 'Europa': {
|
||||
// mass: '4800000 x 10^16 kg',
|
||||
// 'semi-major-axis': '671034 km',
|
||||
// 'diameter': '3121.6 km'
|
||||
// },
|
||||
// };
|
||||
|
||||
// ================================================================================
|
||||
|
||||
let today = new Date();
|
||||
let start = clone(today);
|
||||
addDays(start, 4);
|
||||
let end = clone(start);
|
||||
start.setFullYear( start.getFullYear() - 2 );
|
||||
end.setFullYear( end.getFullYear() - 1 );
|
||||
|
||||
export let dataPoints = {};
|
||||
|
||||
let startTs = timestampSec(start);
|
||||
let endTs = timestampSec(end);
|
||||
|
||||
startTs = timestampToMidnight(startTs);
|
||||
endTs = timestampToMidnight(endTs, true);
|
||||
|
||||
while (startTs < endTs) {
|
||||
dataPoints[parseInt(startTs)] = Math.floor(getRandomBias(0, 5, 0.2, 1));
|
||||
startTs += SEC_IN_DAY;
|
||||
}
|
||||
|
||||
export const heatmapData = {
|
||||
dataPoints: dataPoints,
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
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
@ -1,91 +1,15 @@
|
||||
// Composite Chart
|
||||
import { shuffle, getRandomBias } from '../../../src/js/utils/helpers';
|
||||
import { HEATMAP_COLORS_YELLOW, HEATMAP_COLORS_BLUE } from '../../../src/js/utils/constants';
|
||||
import { fireballOver25, fireball_2_5, fireball_5_25, lineCompositeData,
|
||||
barCompositeData, typeData, trendsData, moonData, heatmapData } from './data';
|
||||
|
||||
// ================================================================================
|
||||
let reportCountList = [152, 222, 199, 287, 534, 709,
|
||||
1179, 1256, 1632, 1856, 1850];
|
||||
|
||||
let lineCompositeData = {
|
||||
labels: ["2007", "2008", "2009", "2010", "2011", "2012",
|
||||
"2013", "2014", "2015", "2016", "2017"],
|
||||
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Average 100 reports/month",
|
||||
value: 1200,
|
||||
}
|
||||
],
|
||||
|
||||
datasets: [{
|
||||
"name": "Events",
|
||||
"values": reportCountList
|
||||
}]
|
||||
};
|
||||
|
||||
|
||||
let fireball_5_25 = [
|
||||
[4, 0, 3, 1, 1, 2, 1, 1, 1, 0, 1, 1],
|
||||
[2, 3, 3, 2, 1, 3, 0, 1, 2, 7, 10, 4],
|
||||
[5, 6, 2, 4, 0, 1, 4, 3, 0, 2, 0, 1],
|
||||
[0, 2, 6, 2, 1, 1, 2, 3, 6, 3, 7, 8],
|
||||
[6, 8, 7, 7, 4, 5, 6, 5, 22, 12, 10, 11],
|
||||
[7, 10, 11, 7, 3, 2, 7, 7, 11, 15, 22, 20],
|
||||
[13, 16, 21, 18, 19, 17, 12, 17, 31, 28, 25, 29],
|
||||
[24, 14, 21, 14, 11, 15, 19, 21, 41, 22, 32, 18],
|
||||
[31, 20, 30, 22, 14, 17, 21, 35, 27, 50, 117, 24],
|
||||
[32, 24, 21, 27, 11, 27, 43, 37, 44, 40, 48, 32],
|
||||
[31, 38, 36, 26, 23, 23, 25, 29, 26, 47, 61, 50],
|
||||
];
|
||||
let fireball_2_5 = [
|
||||
[22, 6, 6, 9, 7, 8, 6, 14, 19, 10, 8, 20],
|
||||
[11, 13, 12, 8, 9, 11, 9, 13, 10, 22, 40, 24],
|
||||
[20, 13, 13, 19, 13, 10, 14, 13, 20, 18, 5, 9],
|
||||
[7, 13, 16, 19, 12, 11, 21, 27, 27, 24, 33, 33],
|
||||
[38, 25, 28, 22, 31, 21, 35, 42, 37, 32, 46, 53],
|
||||
[50, 33, 36, 34, 35, 28, 27, 52, 58, 59, 75, 69],
|
||||
[54, 67, 67, 45, 66, 51, 38, 64, 90, 113, 116, 87],
|
||||
[84, 52, 56, 51, 55, 46, 50, 87, 114, 83, 152, 93],
|
||||
[73, 58, 59, 63, 56, 51, 83, 140, 103, 115, 265, 89],
|
||||
[106, 95, 94, 71, 77, 75, 99, 136, 129, 154, 168, 156],
|
||||
[81, 102, 95, 72, 58, 91, 89, 122, 124, 135, 183, 171],
|
||||
];
|
||||
let fireballOver25 = [
|
||||
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0],
|
||||
[1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2],
|
||||
[3, 2, 1, 3, 2, 0, 2, 2, 2, 3, 0, 1],
|
||||
[2, 3, 5, 2, 1, 3, 0, 2, 3, 5, 1, 4],
|
||||
[7, 4, 6, 1, 9, 2, 2, 2, 20, 9, 4, 9],
|
||||
[5, 6, 1, 2, 5, 4, 5, 5, 16, 9, 14, 9],
|
||||
[5, 4, 7, 5, 1, 5, 3, 3, 5, 7, 22, 2],
|
||||
[5, 13, 11, 6, 1, 7, 9, 8, 14, 17, 16, 3],
|
||||
[8, 9, 8, 6, 4, 8, 5, 6, 14, 11, 21, 12]
|
||||
];
|
||||
|
||||
let monthNames = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"];
|
||||
|
||||
let barCompositeData = {
|
||||
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
datasets: [
|
||||
{
|
||||
name: "Over 25 reports",
|
||||
values: fireballOver25[9],
|
||||
},
|
||||
{
|
||||
name: "5 to 25 reports",
|
||||
values: fireball_5_25[9],
|
||||
},
|
||||
{
|
||||
name: "2 to 5 reports",
|
||||
values: fireball_2_5[9]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let c1 = document.querySelector("#chart-composite-1");
|
||||
let c2 = document.querySelector("#chart-composite-2");
|
||||
|
||||
let Chart = frappe.Chart; // eslint-disable-line no-undef
|
||||
|
||||
let lineCompositeChart = new Chart (c1, {
|
||||
title: "Fireball/Bolide Events - Yearly (reported)",
|
||||
data: lineCompositeData,
|
||||
@ -105,7 +29,7 @@ let lineCompositeChart = new Chart (c1, {
|
||||
let barCompositeChart = new Chart (c2, {
|
||||
data: barCompositeData,
|
||||
type: 'bar',
|
||||
height: 190,
|
||||
height: 210,
|
||||
colors: ['violet', 'light-blue', '#46a9f9'],
|
||||
valuesOverPoints: 1,
|
||||
axisOptions: {
|
||||
@ -124,82 +48,26 @@ lineCompositeChart.parent.addEventListener('data-select', (e) => {
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
// Demo Chart (bar, linepts, scatter(blobs), percentage)
|
||||
// ================================================================================
|
||||
let typeData = {
|
||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
|
||||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],
|
||||
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Marker",
|
||||
value: 43,
|
||||
// type: 'dashed'
|
||||
}
|
||||
],
|
||||
|
||||
yRegions: [
|
||||
{
|
||||
label: "Region",
|
||||
start: -10,
|
||||
end: 50
|
||||
},
|
||||
],
|
||||
|
||||
datasets: [
|
||||
{
|
||||
name: "Some Data",
|
||||
values: [18, 40, 30, 35, 8, 52, 17, -4],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
},
|
||||
{
|
||||
name: "Another Set",
|
||||
values: [30, 50, -10, 15, 18, 32, 27, 14],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
},
|
||||
{
|
||||
name: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37],
|
||||
chartType: 'line'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// let typeChart = new Chart("#chart-types", {
|
||||
// title: "My Awesome Chart",
|
||||
// data: typeData,
|
||||
// type: 'bar',
|
||||
// height: 250,
|
||||
// colors: ['purple', 'magenta', 'red'],
|
||||
// tooltipOptions: {
|
||||
// formatTooltipX: d => (d + '').toUpperCase(),
|
||||
// formatTooltipY: d => d + ' pts',
|
||||
// }
|
||||
// });
|
||||
|
||||
|
||||
|
||||
|
||||
// Aggregation chart
|
||||
// ================================================================================
|
||||
let args = {
|
||||
let customColors = ['purple', 'magenta', 'light-blue'];
|
||||
let typeChartArgs = {
|
||||
title: "My Awesome Chart",
|
||||
data: typeData,
|
||||
type: 'axis-mixed',
|
||||
height: 250,
|
||||
colors: ['purple', 'magenta', 'light-blue'],
|
||||
height: 300,
|
||||
colors: customColors,
|
||||
|
||||
maxLegendPoints: 6,
|
||||
// maxLegendPoints: 6,
|
||||
maxSlices: 10,
|
||||
|
||||
tooltipOptions: {
|
||||
formatTooltipX: d => (d + '').toUpperCase(),
|
||||
formatTooltipY: d => d + ' pts',
|
||||
}
|
||||
}
|
||||
let aggrChart = new Chart("#chart-aggr", args);
|
||||
};
|
||||
|
||||
let aggrChart = new Chart("#chart-aggr", typeChartArgs);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.aggr-type-buttons button')
|
||||
@ -207,9 +75,20 @@ Array.prototype.slice.call(
|
||||
el.addEventListener('click', (e) => {
|
||||
let btn = e.target;
|
||||
let type = btn.getAttribute('data-type');
|
||||
args.type = type;
|
||||
typeChartArgs.type = type;
|
||||
if(type !== 'axis-mixed') {
|
||||
typeChartArgs.colors = undefined;
|
||||
} else {
|
||||
typeChartArgs.colors = customColors;
|
||||
}
|
||||
|
||||
let newChart = new Chart("#chart-aggr", args);;
|
||||
if(type !== 'percentage') {
|
||||
typeChartArgs.height = 300;
|
||||
} else {
|
||||
typeChartArgs.height = undefined;
|
||||
}
|
||||
|
||||
let newChart = new Chart("#chart-aggr", typeChartArgs);
|
||||
if(newChart){
|
||||
aggrChart = newChart;
|
||||
}
|
||||
@ -221,27 +100,31 @@ Array.prototype.slice.call(
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('.export-aggr').addEventListener('click', () => {
|
||||
aggrChart.export();
|
||||
});
|
||||
|
||||
// Update values chart
|
||||
// ================================================================================
|
||||
let update_data_all_labels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue",
|
||||
let updateDataAllLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue",
|
||||
"Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
|
||||
"Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon"];
|
||||
|
||||
let getRandom = () => Math.floor(Math.random() * 75 - 15);
|
||||
let update_data_all_values = Array.from({length: 30}, getRandom);
|
||||
let getRandom = () => Math.floor(getRandomBias(-40, 60, 0.8, 1));
|
||||
let updateDataAllValues = Array.from({length: 30}, getRandom);
|
||||
|
||||
// We're gonna be shuffling this
|
||||
let update_data_all_indices = update_data_all_labels.map((d,i) => i);
|
||||
let updateDataAllIndices = updateDataAllLabels.map((d,i) => i);
|
||||
|
||||
let get_update_data = (source_array, length=10) => {
|
||||
let indices = update_data_all_indices.slice(0, length);
|
||||
let getUpdateData = (source_array, length=10) => {
|
||||
let indices = updateDataAllIndices.slice(0, length);
|
||||
return indices.map((index) => source_array[index]);
|
||||
};
|
||||
|
||||
let update_data = {
|
||||
labels: get_update_data(update_data_all_labels),
|
||||
let updateData = {
|
||||
labels: getUpdateData(updateDataAllLabels),
|
||||
datasets: [{
|
||||
"values": get_update_data(update_data_all_values)
|
||||
"values": getUpdateData(updateDataAllValues)
|
||||
}],
|
||||
yMarkers: [
|
||||
{
|
||||
@ -259,10 +142,10 @@ let update_data = {
|
||||
],
|
||||
};
|
||||
|
||||
let update_chart = new Chart("#chart-update", {
|
||||
data: update_data,
|
||||
let updateChart = new Chart("#chart-update", {
|
||||
data: updateData,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: ['#ff6c03'],
|
||||
lineOptions: {
|
||||
// hideLine: 1,
|
||||
@ -270,16 +153,16 @@ let update_chart = new Chart("#chart-update", {
|
||||
},
|
||||
});
|
||||
|
||||
let chart_update_buttons = document.querySelector('.chart-update-buttons');
|
||||
let chartUpdateButtons = document.querySelector('.chart-update-buttons');
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="random"]').addEventListener("click", (e) => {
|
||||
shuffle(update_data_all_indices);
|
||||
chartUpdateButtons.querySelector('[data-update="random"]').addEventListener("click", () => {
|
||||
shuffle(updateDataAllIndices);
|
||||
let value = getRandom();
|
||||
let start = getRandom();
|
||||
let end = getRandom();
|
||||
let data = {
|
||||
labels: update_data_all_labels.slice(0, 10),
|
||||
datasets: [{values: get_update_data(update_data_all_values)}],
|
||||
labels: updateDataAllLabels.slice(0, 10),
|
||||
datasets: [{values: getUpdateData(updateDataAllValues)}],
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Altitude",
|
||||
@ -294,46 +177,34 @@ chart_update_buttons.querySelector('[data-update="random"]').addEventListener("c
|
||||
end: end
|
||||
},
|
||||
],
|
||||
}
|
||||
update_chart.update(data);
|
||||
};
|
||||
updateChart.update(data);
|
||||
});
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="add"]').addEventListener("click", (e) => {
|
||||
let index = update_chart.state.datasetLength; // last index to add
|
||||
if(index >= update_data_all_indices.length) return;
|
||||
update_chart.addDataPoint(
|
||||
update_data_all_labels[index], [update_data_all_values[index]]
|
||||
chartUpdateButtons.querySelector('[data-update="add"]').addEventListener("click", () => {
|
||||
let index = updateChart.state.datasetLength; // last index to add
|
||||
if(index >= updateDataAllIndices.length) return;
|
||||
updateChart.addDataPoint(
|
||||
updateDataAllLabels[index], [updateDataAllValues[index]]
|
||||
);
|
||||
});
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="remove"]').addEventListener("click", (e) => {
|
||||
update_chart.removeDataPoint();
|
||||
chartUpdateButtons.querySelector('[data-update="remove"]').addEventListener("click", () => {
|
||||
updateChart.removeDataPoint();
|
||||
});
|
||||
|
||||
document.querySelector('.export-update').addEventListener('click', () => {
|
||||
updateChart.export();
|
||||
});
|
||||
|
||||
// Trends Chart
|
||||
// ================================================================================
|
||||
let trends_data = {
|
||||
labels: [1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976,
|
||||
1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986,
|
||||
1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996,
|
||||
1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
|
||||
2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016] ,
|
||||
datasets: [
|
||||
{
|
||||
values: [132.9, 150.0, 149.4, 148.0, 94.4, 97.6, 54.1, 49.2, 22.5, 18.4,
|
||||
39.3, 131.0, 220.1, 218.9, 198.9, 162.4, 91.0, 60.5, 20.6, 14.8,
|
||||
33.9, 123.0, 211.1, 191.8, 203.3, 133.0, 76.1, 44.9, 25.1, 11.6,
|
||||
28.9, 88.3, 136.3, 173.9, 170.4, 163.6, 99.3, 65.3, 45.8, 24.7,
|
||||
12.6, 4.2, 4.8, 24.9, 80.8, 84.5, 94.0, 113.3, 69.8, 39.8]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let plotChartArgs = {
|
||||
title: "Mean Total Sunspot Count - Yearly",
|
||||
data: trends_data,
|
||||
data: trendsData,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
height: 300,
|
||||
colors: ['#238e38'],
|
||||
lineOptions: {
|
||||
hideDots: 1,
|
||||
@ -346,7 +217,7 @@ let plotChartArgs = {
|
||||
}
|
||||
};
|
||||
|
||||
new Chart("#chart-trends", plotChartArgs);
|
||||
let trendsChart = new Chart("#chart-trends", plotChartArgs);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.chart-plot-buttons button')
|
||||
@ -374,89 +245,59 @@ Array.prototype.slice.call(
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('.export-trends').addEventListener('click', () => {
|
||||
trendsChart.export();
|
||||
});
|
||||
|
||||
|
||||
// Event chart
|
||||
// ================================================================================
|
||||
let moon_names = ["Ganymede", "Callisto", "Io", "Europa"];
|
||||
let masses = [14819000, 10759000, 8931900, 4800000];
|
||||
let distances = [1070.412, 1882.709, 421.700, 671.034];
|
||||
let diameters = [5262.4, 4820.6, 3637.4, 3121.6];
|
||||
|
||||
let jupiter_moons = {
|
||||
'Ganymede': {
|
||||
mass: '14819000 x 10^16 kg',
|
||||
'semi-major-axis': '1070412 km',
|
||||
'diameter': '5262.4 km'
|
||||
},
|
||||
'Callisto': {
|
||||
mass: '10759000 x 10^16 kg',
|
||||
'semi-major-axis': '1882709 km',
|
||||
'diameter': '4820.6 km'
|
||||
},
|
||||
'Io': {
|
||||
mass: '8931900 x 10^16 kg',
|
||||
'semi-major-axis': '421700 km',
|
||||
'diameter': '3637.4 km'
|
||||
},
|
||||
'Europa': {
|
||||
mass: '4800000 x 10^16 kg',
|
||||
'semi-major-axis': '671034 km',
|
||||
'diameter': '3121.6 km'
|
||||
},
|
||||
};
|
||||
|
||||
let events_data = {
|
||||
|
||||
let eventsData = {
|
||||
labels: ["Ganymede", "Callisto", "Io", "Europa"],
|
||||
datasets: [
|
||||
{
|
||||
"values": distances,
|
||||
"formatted": distances.map(d => d*1000 + " km")
|
||||
"values": moonData.distances,
|
||||
"formatted": moonData.distances.map(d => d*1000 + " km")
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let events_chart = new Chart("#chart-events", {
|
||||
let eventsChart = new Chart("#chart-events", {
|
||||
title: "Jupiter's Moons: Semi-major Axis (1000 km)",
|
||||
data: events_data,
|
||||
data: eventsData,
|
||||
type: 'bar',
|
||||
height: 250,
|
||||
height: 330,
|
||||
colors: ['grey'],
|
||||
isNavigable: 1,
|
||||
});
|
||||
|
||||
let data_div = document.querySelector('.chart-events-data');
|
||||
let dataDiv = document.querySelector('.chart-events-data');
|
||||
|
||||
events_chart.parent.addEventListener('data-select', (e) => {
|
||||
let name = moon_names[e.index];
|
||||
data_div.querySelector('.moon-name').innerHTML = name;
|
||||
data_div.querySelector('.semi-major-axis').innerHTML = distances[e.index] * 1000;
|
||||
data_div.querySelector('.mass').innerHTML = masses[e.index];
|
||||
data_div.querySelector('.diameter').innerHTML = diameters[e.index];
|
||||
data_div.querySelector('img').src = "./assets/img/" + name.toLowerCase() + ".jpg";
|
||||
eventsChart.parent.addEventListener('data-select', (e) => {
|
||||
let name = moonData.names[e.index];
|
||||
dataDiv.querySelector('.moon-name').innerHTML = name;
|
||||
dataDiv.querySelector('.semi-major-axis').innerHTML = moonData.distances[e.index] * 1000;
|
||||
dataDiv.querySelector('.mass').innerHTML = moonData.masses[e.index];
|
||||
dataDiv.querySelector('.diameter').innerHTML = moonData.diameters[e.index];
|
||||
dataDiv.querySelector('img').src = "./assets/img/" + name.toLowerCase() + ".jpg";
|
||||
});
|
||||
|
||||
// Heatmap
|
||||
// ================================================================================
|
||||
|
||||
let heatmapData = {};
|
||||
let current_date = new Date();
|
||||
let timestamp = current_date.getTime()/1000;
|
||||
timestamp = Math.floor(timestamp - (timestamp % 86400)).toFixed(1); // convert to midnight
|
||||
for (var i = 0; i< 375; i++) {
|
||||
heatmapData[parseInt(timestamp)] = Math.floor(Math.random() * 5);
|
||||
timestamp = Math.floor(timestamp - 86400).toFixed(1);
|
||||
}
|
||||
|
||||
let heatmap = new Chart("#chart-heatmap", {
|
||||
let heatmapArgs = {
|
||||
title: "Monthly Distribution",
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
legendScale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discreteDomains: 1,
|
||||
legendColors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']
|
||||
});
|
||||
|
||||
// console.log(heatmapData, heatmap);
|
||||
countLabel: 'Level',
|
||||
colors: HEATMAP_COLORS_BLUE,
|
||||
legendScale: [0, 1, 2, 4, 5]
|
||||
};
|
||||
let heatmapChart = new Chart("#chart-heatmap", heatmapArgs);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.heatmap-mode-buttons button')
|
||||
@ -475,17 +316,14 @@ Array.prototype.slice.call(
|
||||
.querySelector('.heatmap-color-buttons .active')
|
||||
.getAttribute('data-color');
|
||||
if(colors_mode === 'halloween') {
|
||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
colors = HEATMAP_COLORS_YELLOW;
|
||||
} else if (colors_mode === 'blue') {
|
||||
colors = HEATMAP_COLORS_BLUE;
|
||||
}
|
||||
|
||||
new Chart("#chart-heatmap", {
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
legendScale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discreteDomains: discreteDomains,
|
||||
legendColors: colors
|
||||
});
|
||||
heatmapArgs.discreteDomains = discreteDomains;
|
||||
heatmapArgs.colors = colors;
|
||||
new Chart("#chart-heatmap", heatmapArgs);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
btn.parentNode.querySelectorAll('button')).map(el => {
|
||||
@ -504,7 +342,9 @@ Array.prototype.slice.call(
|
||||
let colors = [];
|
||||
|
||||
if(colors_mode === 'halloween') {
|
||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
colors = HEATMAP_COLORS_YELLOW;
|
||||
} else if (colors_mode === 'blue') {
|
||||
colors = HEATMAP_COLORS_BLUE;
|
||||
}
|
||||
|
||||
let discreteDomains = 1;
|
||||
@ -516,14 +356,9 @@ Array.prototype.slice.call(
|
||||
discreteDomains = 0;
|
||||
}
|
||||
|
||||
new Chart("#chart-heatmap", {
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
legendScale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discreteDomains: discreteDomains,
|
||||
legendColors: colors
|
||||
});
|
||||
heatmapArgs.discreteDomains = discreteDomains;
|
||||
heatmapArgs.colors = colors;
|
||||
new Chart("#chart-heatmap", heatmapArgs);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
btn.parentNode.querySelectorAll('button')).map(el => {
|
||||
@ -533,28 +368,6 @@ Array.prototype.slice.call(
|
||||
});
|
||||
});
|
||||
|
||||
// Helpers
|
||||
// ================================================================================
|
||||
function shuffle(array) {
|
||||
// https://stackoverflow.com/a/2450976/6495043
|
||||
// Awesomeness: https://bost.ocks.org/mike/shuffle/
|
||||
|
||||
var currentIndex = array.length, temporaryValue, randomIndex;
|
||||
|
||||
// While there remain elements to shuffle...
|
||||
while (0 !== currentIndex) {
|
||||
|
||||
// Pick a remaining element...
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex -= 1;
|
||||
|
||||
// And swap it with the current element.
|
||||
temporaryValue = array[currentIndex];
|
||||
array[currentIndex] = array[randomIndex];
|
||||
array[randomIndex] = temporaryValue;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
document.querySelector('.export-heatmap').addEventListener('click', () => {
|
||||
heatmapChart.export();
|
||||
});
|
||||
|
||||
653
docs/assets/js/index.min.js
vendored
Normal file
653
docs/assets/js/index.min.js
vendored
Normal file
@ -0,0 +1,653 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function __$styleInject(css, ref) {
|
||||
if ( ref === void 0 ) ref = {};
|
||||
var insertAt = ref.insertAt;
|
||||
|
||||
if (!css || typeof document === 'undefined') { return; }
|
||||
|
||||
var head = document.head || document.getElementsByTagName('head')[0];
|
||||
var style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
|
||||
if (insertAt === 'top') {
|
||||
if (head.firstChild) {
|
||||
head.insertBefore(style, head.firstChild);
|
||||
} else {
|
||||
head.appendChild(style);
|
||||
}
|
||||
} else {
|
||||
head.appendChild(style);
|
||||
}
|
||||
|
||||
if (style.styleSheet) {
|
||||
style.styleSheet.cssText = css;
|
||||
} else {
|
||||
style.appendChild(document.createTextNode(css));
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed 5-color theme,
|
||||
// More colors are difficult to parse visually
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var HEATMAP_COLORS_BLUE = ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'];
|
||||
var HEATMAP_COLORS_YELLOW = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
|
||||
|
||||
|
||||
// Universal constants
|
||||
|
||||
/**
|
||||
* Returns the value of a number upto 2 decimal places.
|
||||
* @param {Number} d Any number
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether or not two given arrays are equal.
|
||||
* @param {Array} arr1 First array
|
||||
* @param {Array} arr2 Second array
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Shuffles array in place. ES6 version
|
||||
* @param {Array} array An array containing the items.
|
||||
*/
|
||||
function shuffle(array) {
|
||||
// Awesomeness: https://bost.ocks.org/mike/shuffle/
|
||||
// https://stackoverflow.com/a/2450976/6495043
|
||||
// https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array?noredirect=1&lq=1
|
||||
|
||||
for (var i = array.length - 1; i > 0; i--) {
|
||||
var j = Math.floor(Math.random() * (i + 1));
|
||||
var _ref = [array[j], array[i]];
|
||||
array[i] = _ref[0];
|
||||
array[j] = _ref[1];
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill an array with extra points
|
||||
* @param {Array} array Array
|
||||
* @param {Number} count number of filler elements
|
||||
* @param {Object} element element to fill with
|
||||
* @param {Boolean} start fill at start?
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Returns pixel width of string.
|
||||
* @param {String} string
|
||||
* @param {Number} charWidth Width of single char in pixels
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/29325222
|
||||
function getRandomBias(min, max, bias, influence) {
|
||||
var range = max - min;
|
||||
var biasValue = range * bias + min;
|
||||
var rnd = Math.random() * range + min,
|
||||
// random in range
|
||||
mix = Math.random() * influence; // random mixer
|
||||
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
|
||||
}
|
||||
|
||||
// Playing around with dates
|
||||
|
||||
|
||||
|
||||
|
||||
var NO_OF_MILLIS = 1000;
|
||||
var SEC_IN_DAY = 86400;
|
||||
|
||||
|
||||
var MONTH_NAMES_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function clone(date) {
|
||||
return new Date(date.getTime());
|
||||
}
|
||||
|
||||
function timestampSec(date) {
|
||||
return date.getTime() / NO_OF_MILLIS;
|
||||
}
|
||||
|
||||
function timestampToMidnight(timestamp) {
|
||||
var roundAhead = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
|
||||
|
||||
var midnightTs = Math.floor(timestamp - timestamp % SEC_IN_DAY);
|
||||
if (roundAhead) {
|
||||
return midnightTs + SEC_IN_DAY;
|
||||
}
|
||||
return midnightTs;
|
||||
}
|
||||
|
||||
// export function getMonthsBetween(startDate, endDate) {}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// mutates
|
||||
|
||||
|
||||
// mutates
|
||||
function addDays(date, numberOfDays) {
|
||||
date.setDate(date.getDate() + numberOfDays);
|
||||
}
|
||||
|
||||
// Composite Chart
|
||||
// ================================================================================
|
||||
var reportCountList = [152, 222, 199, 287, 534, 709, 1179, 1256, 1632, 1856, 1850];
|
||||
|
||||
var lineCompositeData = {
|
||||
labels: ["2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017"],
|
||||
|
||||
yMarkers: [{
|
||||
label: "Average 100 reports/month",
|
||||
value: 1200,
|
||||
options: { labelPos: 'left' }
|
||||
}],
|
||||
|
||||
datasets: [{
|
||||
"name": "Events",
|
||||
"values": reportCountList
|
||||
}]
|
||||
};
|
||||
|
||||
var fireball_5_25 = [[4, 0, 3, 1, 1, 2, 1, 1, 1, 0, 1, 1], [2, 3, 3, 2, 1, 3, 0, 1, 2, 7, 10, 4], [5, 6, 2, 4, 0, 1, 4, 3, 0, 2, 0, 1], [0, 2, 6, 2, 1, 1, 2, 3, 6, 3, 7, 8], [6, 8, 7, 7, 4, 5, 6, 5, 22, 12, 10, 11], [7, 10, 11, 7, 3, 2, 7, 7, 11, 15, 22, 20], [13, 16, 21, 18, 19, 17, 12, 17, 31, 28, 25, 29], [24, 14, 21, 14, 11, 15, 19, 21, 41, 22, 32, 18], [31, 20, 30, 22, 14, 17, 21, 35, 27, 50, 117, 24], [32, 24, 21, 27, 11, 27, 43, 37, 44, 40, 48, 32], [31, 38, 36, 26, 23, 23, 25, 29, 26, 47, 61, 50]];
|
||||
var fireball_2_5 = [[22, 6, 6, 9, 7, 8, 6, 14, 19, 10, 8, 20], [11, 13, 12, 8, 9, 11, 9, 13, 10, 22, 40, 24], [20, 13, 13, 19, 13, 10, 14, 13, 20, 18, 5, 9], [7, 13, 16, 19, 12, 11, 21, 27, 27, 24, 33, 33], [38, 25, 28, 22, 31, 21, 35, 42, 37, 32, 46, 53], [50, 33, 36, 34, 35, 28, 27, 52, 58, 59, 75, 69], [54, 67, 67, 45, 66, 51, 38, 64, 90, 113, 116, 87], [84, 52, 56, 51, 55, 46, 50, 87, 114, 83, 152, 93], [73, 58, 59, 63, 56, 51, 83, 140, 103, 115, 265, 89], [106, 95, 94, 71, 77, 75, 99, 136, 129, 154, 168, 156], [81, 102, 95, 72, 58, 91, 89, 122, 124, 135, 183, 171]];
|
||||
var fireballOver25 = [
|
||||
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0], [1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 2], [3, 2, 1, 3, 2, 0, 2, 2, 2, 3, 0, 1], [2, 3, 5, 2, 1, 3, 0, 2, 3, 5, 1, 4], [7, 4, 6, 1, 9, 2, 2, 2, 20, 9, 4, 9], [5, 6, 1, 2, 5, 4, 5, 5, 16, 9, 14, 9], [5, 4, 7, 5, 1, 5, 3, 3, 5, 7, 22, 2], [5, 13, 11, 6, 1, 7, 9, 8, 14, 17, 16, 3], [8, 9, 8, 6, 4, 8, 5, 6, 14, 11, 21, 12]];
|
||||
|
||||
var barCompositeData = {
|
||||
labels: MONTH_NAMES_SHORT,
|
||||
datasets: [{
|
||||
name: "Over 25 reports",
|
||||
values: fireballOver25[9]
|
||||
}, {
|
||||
name: "5 to 25 reports",
|
||||
values: fireball_5_25[9]
|
||||
}, {
|
||||
name: "2 to 5 reports",
|
||||
values: fireball_2_5[9]
|
||||
}]
|
||||
};
|
||||
|
||||
// Demo Chart multitype Chart
|
||||
// ================================================================================
|
||||
var typeData = {
|
||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm", "12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],
|
||||
|
||||
yMarkers: [{
|
||||
label: "Marker",
|
||||
value: 43,
|
||||
options: { labelPos: 'left'
|
||||
// type: 'dashed'
|
||||
} }],
|
||||
|
||||
yRegions: [{
|
||||
label: "Region",
|
||||
start: -10,
|
||||
end: 50,
|
||||
options: { labelPos: 'right' }
|
||||
}],
|
||||
|
||||
datasets: [{
|
||||
name: "Some Data",
|
||||
values: [18, 40, 30, 35, 8, 52, 17, -4],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
}, {
|
||||
name: "Another Set",
|
||||
values: [30, 50, -10, 15, 18, 32, 27, 14],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
}, {
|
||||
name: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37],
|
||||
chartType: 'line'
|
||||
}]
|
||||
};
|
||||
|
||||
var trendsData = {
|
||||
labels: [1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016],
|
||||
datasets: [{
|
||||
values: [132.9, 150.0, 149.4, 148.0, 94.4, 97.6, 54.1, 49.2, 22.5, 18.4, 39.3, 131.0, 220.1, 218.9, 198.9, 162.4, 91.0, 60.5, 20.6, 14.8, 33.9, 123.0, 211.1, 191.8, 203.3, 133.0, 76.1, 44.9, 25.1, 11.6, 28.9, 88.3, 136.3, 173.9, 170.4, 163.6, 99.3, 65.3, 45.8, 24.7, 12.6, 4.2, 4.8, 24.9, 80.8, 84.5, 94.0, 113.3, 69.8, 39.8]
|
||||
}]
|
||||
};
|
||||
|
||||
var moonData = {
|
||||
names: ["Ganymede", "Callisto", "Io", "Europa"],
|
||||
masses: [14819000, 10759000, 8931900, 4800000],
|
||||
distances: [1070.412, 1882.709, 421.700, 671.034],
|
||||
diameters: [5262.4, 4820.6, 3637.4, 3121.6]
|
||||
};
|
||||
|
||||
// const jupiterMoons = {
|
||||
// 'Ganymede': {
|
||||
// mass: '14819000 x 10^16 kg',
|
||||
// 'semi-major-axis': '1070412 km',
|
||||
// 'diameter': '5262.4 km'
|
||||
// },
|
||||
// 'Callisto': {
|
||||
// mass: '10759000 x 10^16 kg',
|
||||
// 'semi-major-axis': '1882709 km',
|
||||
// 'diameter': '4820.6 km'
|
||||
// },
|
||||
// 'Io': {
|
||||
// mass: '8931900 x 10^16 kg',
|
||||
// 'semi-major-axis': '421700 km',
|
||||
// 'diameter': '3637.4 km'
|
||||
// },
|
||||
// 'Europa': {
|
||||
// mass: '4800000 x 10^16 kg',
|
||||
// 'semi-major-axis': '671034 km',
|
||||
// 'diameter': '3121.6 km'
|
||||
// },
|
||||
// };
|
||||
|
||||
// ================================================================================
|
||||
|
||||
var today = new Date();
|
||||
var start = clone(today);
|
||||
addDays(start, 4);
|
||||
var end = clone(start);
|
||||
start.setFullYear(start.getFullYear() - 2);
|
||||
end.setFullYear(end.getFullYear() - 1);
|
||||
|
||||
var dataPoints = {};
|
||||
|
||||
var startTs = timestampSec(start);
|
||||
var endTs = timestampSec(end);
|
||||
|
||||
startTs = timestampToMidnight(startTs);
|
||||
endTs = timestampToMidnight(endTs, true);
|
||||
|
||||
while (startTs < endTs) {
|
||||
dataPoints[parseInt(startTs)] = Math.floor(getRandomBias(0, 5, 0.2, 1));
|
||||
startTs += SEC_IN_DAY;
|
||||
}
|
||||
|
||||
var heatmapData = {
|
||||
dataPoints: dataPoints,
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
|
||||
// ================================================================================
|
||||
|
||||
var c1 = document.querySelector("#chart-composite-1");
|
||||
var c2 = document.querySelector("#chart-composite-2");
|
||||
|
||||
var Chart = frappe.Chart; // eslint-disable-line no-undef
|
||||
|
||||
var lineCompositeChart = new Chart(c1, {
|
||||
title: "Fireball/Bolide Events - Yearly (reported)",
|
||||
data: lineCompositeData,
|
||||
type: 'line',
|
||||
height: 190,
|
||||
colors: ['green'],
|
||||
isNavigable: 1,
|
||||
valuesOverPoints: 1,
|
||||
|
||||
lineOptions: {
|
||||
dotSize: 8
|
||||
}
|
||||
// yAxisMode: 'tick'
|
||||
// regionFill: 1
|
||||
});
|
||||
|
||||
var barCompositeChart = new Chart(c2, {
|
||||
data: barCompositeData,
|
||||
type: 'bar',
|
||||
height: 210,
|
||||
colors: ['violet', 'light-blue', '#46a9f9'],
|
||||
valuesOverPoints: 1,
|
||||
axisOptions: {
|
||||
xAxisMode: 'tick'
|
||||
},
|
||||
barOptions: {
|
||||
stacked: 1
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
lineCompositeChart.parent.addEventListener('data-select', function (e) {
|
||||
var i = e.index;
|
||||
barCompositeChart.updateDatasets([fireballOver25[i], fireball_5_25[i], fireball_2_5[i]]);
|
||||
});
|
||||
|
||||
// ================================================================================
|
||||
|
||||
var customColors = ['purple', 'magenta', 'light-blue'];
|
||||
var typeChartArgs = {
|
||||
title: "My Awesome Chart",
|
||||
data: typeData,
|
||||
type: 'axis-mixed',
|
||||
height: 300,
|
||||
colors: customColors,
|
||||
|
||||
// maxLegendPoints: 6,
|
||||
maxSlices: 10,
|
||||
|
||||
tooltipOptions: {
|
||||
formatTooltipX: function formatTooltipX(d) {
|
||||
return (d + '').toUpperCase();
|
||||
},
|
||||
formatTooltipY: function formatTooltipY(d) {
|
||||
return d + ' pts';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var aggrChart = new Chart("#chart-aggr", typeChartArgs);
|
||||
|
||||
Array.prototype.slice.call(document.querySelectorAll('.aggr-type-buttons button')).map(function (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
var btn = e.target;
|
||||
var type = btn.getAttribute('data-type');
|
||||
typeChartArgs.type = type;
|
||||
if (type !== 'axis-mixed') {
|
||||
typeChartArgs.colors = undefined;
|
||||
} else {
|
||||
typeChartArgs.colors = customColors;
|
||||
}
|
||||
|
||||
if (type !== 'percentage') {
|
||||
typeChartArgs.height = 300;
|
||||
} else {
|
||||
typeChartArgs.height = undefined;
|
||||
}
|
||||
|
||||
var newChart = new Chart("#chart-aggr", typeChartArgs);
|
||||
if (newChart) {
|
||||
aggrChart = newChart;
|
||||
}
|
||||
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('.export-aggr').addEventListener('click', function () {
|
||||
aggrChart.export();
|
||||
});
|
||||
|
||||
// Update values chart
|
||||
// ================================================================================
|
||||
var updateDataAllLabels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon"];
|
||||
|
||||
var getRandom = function getRandom() {
|
||||
return Math.floor(getRandomBias(-40, 60, 0.8, 1));
|
||||
};
|
||||
var updateDataAllValues = Array.from({ length: 30 }, getRandom);
|
||||
|
||||
// We're gonna be shuffling this
|
||||
var updateDataAllIndices = updateDataAllLabels.map(function (d, i) {
|
||||
return i;
|
||||
});
|
||||
|
||||
var getUpdateData = function getUpdateData(source_array) {
|
||||
var length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 10;
|
||||
|
||||
var indices = updateDataAllIndices.slice(0, length);
|
||||
return indices.map(function (index) {
|
||||
return source_array[index];
|
||||
});
|
||||
};
|
||||
|
||||
var updateData = {
|
||||
labels: getUpdateData(updateDataAllLabels),
|
||||
datasets: [{
|
||||
"values": getUpdateData(updateDataAllValues)
|
||||
}],
|
||||
yMarkers: [{
|
||||
label: "Altitude",
|
||||
value: 25,
|
||||
type: 'dashed'
|
||||
}],
|
||||
yRegions: [{
|
||||
label: "Range",
|
||||
start: 10,
|
||||
end: 45
|
||||
}]
|
||||
};
|
||||
|
||||
var updateChart = new Chart("#chart-update", {
|
||||
data: updateData,
|
||||
type: 'line',
|
||||
height: 300,
|
||||
colors: ['#ff6c03'],
|
||||
lineOptions: {
|
||||
// hideLine: 1,
|
||||
regionFill: 1
|
||||
}
|
||||
});
|
||||
|
||||
var chartUpdateButtons = document.querySelector('.chart-update-buttons');
|
||||
|
||||
chartUpdateButtons.querySelector('[data-update="random"]').addEventListener("click", function () {
|
||||
shuffle(updateDataAllIndices);
|
||||
var value = getRandom();
|
||||
var start = getRandom();
|
||||
var end = getRandom();
|
||||
var data = {
|
||||
labels: updateDataAllLabels.slice(0, 10),
|
||||
datasets: [{ values: getUpdateData(updateDataAllValues) }],
|
||||
yMarkers: [{
|
||||
label: "Altitude",
|
||||
value: value,
|
||||
type: 'dashed'
|
||||
}],
|
||||
yRegions: [{
|
||||
label: "Range",
|
||||
start: start,
|
||||
end: end
|
||||
}]
|
||||
};
|
||||
updateChart.update(data);
|
||||
});
|
||||
|
||||
chartUpdateButtons.querySelector('[data-update="add"]').addEventListener("click", function () {
|
||||
var index = updateChart.state.datasetLength; // last index to add
|
||||
if (index >= updateDataAllIndices.length) return;
|
||||
updateChart.addDataPoint(updateDataAllLabels[index], [updateDataAllValues[index]]);
|
||||
});
|
||||
|
||||
chartUpdateButtons.querySelector('[data-update="remove"]').addEventListener("click", function () {
|
||||
updateChart.removeDataPoint();
|
||||
});
|
||||
|
||||
document.querySelector('.export-update').addEventListener('click', function () {
|
||||
updateChart.export();
|
||||
});
|
||||
|
||||
// Trends Chart
|
||||
// ================================================================================
|
||||
|
||||
var plotChartArgs = {
|
||||
title: "Mean Total Sunspot Count - Yearly",
|
||||
data: trendsData,
|
||||
type: 'line',
|
||||
height: 300,
|
||||
colors: ['#238e38'],
|
||||
lineOptions: {
|
||||
hideDots: 1,
|
||||
heatline: 1
|
||||
},
|
||||
axisOptions: {
|
||||
xAxisMode: 'tick',
|
||||
yAxisMode: 'span',
|
||||
xIsSeries: 1
|
||||
}
|
||||
};
|
||||
|
||||
var trendsChart = new Chart("#chart-trends", plotChartArgs);
|
||||
|
||||
Array.prototype.slice.call(document.querySelectorAll('.chart-plot-buttons button')).map(function (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
var btn = e.target;
|
||||
var type = btn.getAttribute('data-type');
|
||||
var config = {};
|
||||
config[type] = 1;
|
||||
|
||||
if (['regionFill', 'heatline'].includes(type)) {
|
||||
config.hideDots = 1;
|
||||
}
|
||||
|
||||
// plotChartArgs.init = false;
|
||||
plotChartArgs.lineOptions = config;
|
||||
|
||||
new Chart("#chart-trends", plotChartArgs);
|
||||
|
||||
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('.export-trends').addEventListener('click', function () {
|
||||
trendsChart.export();
|
||||
});
|
||||
|
||||
// Event chart
|
||||
// ================================================================================
|
||||
|
||||
|
||||
var eventsData = {
|
||||
labels: ["Ganymede", "Callisto", "Io", "Europa"],
|
||||
datasets: [{
|
||||
"values": moonData.distances,
|
||||
"formatted": moonData.distances.map(function (d) {
|
||||
return d * 1000 + " km";
|
||||
})
|
||||
}]
|
||||
};
|
||||
|
||||
var eventsChart = new Chart("#chart-events", {
|
||||
title: "Jupiter's Moons: Semi-major Axis (1000 km)",
|
||||
data: eventsData,
|
||||
type: 'bar',
|
||||
height: 330,
|
||||
colors: ['grey'],
|
||||
isNavigable: 1
|
||||
});
|
||||
|
||||
var dataDiv = document.querySelector('.chart-events-data');
|
||||
|
||||
eventsChart.parent.addEventListener('data-select', function (e) {
|
||||
var name = moonData.names[e.index];
|
||||
dataDiv.querySelector('.moon-name').innerHTML = name;
|
||||
dataDiv.querySelector('.semi-major-axis').innerHTML = moonData.distances[e.index] * 1000;
|
||||
dataDiv.querySelector('.mass').innerHTML = moonData.masses[e.index];
|
||||
dataDiv.querySelector('.diameter').innerHTML = moonData.diameters[e.index];
|
||||
dataDiv.querySelector('img').src = "./assets/img/" + name.toLowerCase() + ".jpg";
|
||||
});
|
||||
|
||||
// Heatmap
|
||||
// ================================================================================
|
||||
|
||||
var heatmapArgs = {
|
||||
title: "Monthly Distribution",
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
discreteDomains: 1,
|
||||
countLabel: 'Level',
|
||||
colors: HEATMAP_COLORS_BLUE,
|
||||
legendScale: [0, 1, 2, 4, 5]
|
||||
};
|
||||
var heatmapChart = new Chart("#chart-heatmap", heatmapArgs);
|
||||
|
||||
Array.prototype.slice.call(document.querySelectorAll('.heatmap-mode-buttons button')).map(function (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
var btn = e.target;
|
||||
var mode = btn.getAttribute('data-mode');
|
||||
var discreteDomains = 0;
|
||||
|
||||
if (mode === 'discrete') {
|
||||
discreteDomains = 1;
|
||||
}
|
||||
|
||||
var colors = [];
|
||||
var colors_mode = document.querySelector('.heatmap-color-buttons .active').getAttribute('data-color');
|
||||
if (colors_mode === 'halloween') {
|
||||
colors = HEATMAP_COLORS_YELLOW;
|
||||
} else if (colors_mode === 'blue') {
|
||||
colors = HEATMAP_COLORS_BLUE;
|
||||
}
|
||||
|
||||
heatmapArgs.discreteDomains = discreteDomains;
|
||||
heatmapArgs.colors = colors;
|
||||
new Chart("#chart-heatmap", heatmapArgs);
|
||||
|
||||
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(document.querySelectorAll('.heatmap-color-buttons button')).map(function (el) {
|
||||
el.addEventListener('click', function (e) {
|
||||
var btn = e.target;
|
||||
var colors_mode = btn.getAttribute('data-color');
|
||||
var colors = [];
|
||||
|
||||
if (colors_mode === 'halloween') {
|
||||
colors = HEATMAP_COLORS_YELLOW;
|
||||
} else if (colors_mode === 'blue') {
|
||||
colors = HEATMAP_COLORS_BLUE;
|
||||
}
|
||||
|
||||
var discreteDomains = 1;
|
||||
|
||||
var view_mode = document.querySelector('.heatmap-mode-buttons .active').getAttribute('data-mode');
|
||||
if (view_mode === 'continuous') {
|
||||
discreteDomains = 0;
|
||||
}
|
||||
|
||||
heatmapArgs.discreteDomains = discreteDomains;
|
||||
heatmapArgs.colors = colors;
|
||||
new Chart("#chart-heatmap", heatmapArgs);
|
||||
|
||||
Array.prototype.slice.call(btn.parentNode.querySelectorAll('button')).map(function (el) {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector('.export-heatmap').addEventListener('click', function () {
|
||||
heatmapChart.export();
|
||||
});
|
||||
|
||||
}());
|
||||
//# sourceMappingURL=index.min.js.map
|
||||
1
docs/assets/js/index.min.js.map
Normal file
1
docs/assets/js/index.min.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,559 +0,0 @@
|
||||
// Composite Chart
|
||||
// ================================================================================
|
||||
let report_count_list = [17, 40, 33, 44, 126, 156,
|
||||
324, 333, 478, 495, 176];
|
||||
|
||||
let bar_composite_data = {
|
||||
labels: ["2007", "2008", "2009", "2010", "2011", "2012",
|
||||
"2013", "2014", "2015", "2016", "2017"],
|
||||
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Marker 1",
|
||||
value: 420,
|
||||
},
|
||||
{
|
||||
label: "Marker 2",
|
||||
value: 250,
|
||||
}
|
||||
],
|
||||
|
||||
yRegions: [
|
||||
{
|
||||
label: "Region Y 1",
|
||||
start: 100,
|
||||
end: 300
|
||||
},
|
||||
],
|
||||
|
||||
datasets: [{
|
||||
"name": "Events",
|
||||
"values": report_count_list,
|
||||
// "formatted": report_count_list.map(d => d + " reports")
|
||||
}]
|
||||
};
|
||||
|
||||
let line_composite_data = {
|
||||
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
||||
datasets: [{
|
||||
"values": [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 0, 0],
|
||||
// "values": [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40],
|
||||
// "values": [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40],
|
||||
}]
|
||||
};
|
||||
|
||||
let more_line_data = [
|
||||
[4, 0, 3, 1, 1, 2, 1, 2, 1, 0, 1, 1],
|
||||
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[2, 3, 3, 2, 1, 4, 0, 1, 2, 7, 11, 4],
|
||||
[7, 7, 2, 4, 0, 1, 5, 3, 1, 2, 0, 1],
|
||||
[0, 2, 6, 2, 2, 1, 2, 3, 6, 3, 7, 10],
|
||||
[9, 10, 8, 10, 6, 5, 8, 8, 24, 15, 10, 13],
|
||||
[9, 13, 16, 9, 4, 5, 7, 10, 14, 22, 23, 24],
|
||||
[20, 22, 28, 19, 28, 19, 14, 19, 51, 37, 29, 38],
|
||||
[29, 20, 22, 16, 16, 19, 24, 26, 57, 31, 46, 27],
|
||||
[36, 24, 38, 27, 15, 22, 24, 38, 32, 57, 139, 26],
|
||||
[37, 36, 32, 33, 12, 34, 52, 45, 58, 57, 64, 35],
|
||||
[36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 0, 0],
|
||||
// [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40]
|
||||
// [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40]
|
||||
];
|
||||
|
||||
let c1 = document.querySelector("#chart-composite-1");
|
||||
let c2 = document.querySelector("#chart-composite-2");
|
||||
|
||||
let bar_composite_chart = new Chart (c1, {
|
||||
title: "Fireball/Bolide Events - Yearly (more than 5 reports)",
|
||||
data: bar_composite_data,
|
||||
type: 'bar',
|
||||
height: 180,
|
||||
colors: ['orange'],
|
||||
isNavigable: 1,
|
||||
isSeries: 1,
|
||||
valuesOverPoints: 1,
|
||||
yAxisMode: 'tick'
|
||||
// regionFill: 1
|
||||
});
|
||||
|
||||
let line_composite_chart = new Chart (c2, {
|
||||
data: line_composite_data,
|
||||
type: 'line',
|
||||
lineOptions: {
|
||||
dotSize: 10
|
||||
},
|
||||
height: 180,
|
||||
colors: ['green'],
|
||||
isSeries: 1,
|
||||
valuesOverPoints: 1,
|
||||
});
|
||||
|
||||
bar_composite_chart.parent.addEventListener('data-select', (e) => {
|
||||
line_composite_chart.updateDataset(more_line_data[e.index]);
|
||||
});
|
||||
|
||||
|
||||
// Demo Chart (bar, linepts, scatter(blobs), percentage)
|
||||
// ================================================================================
|
||||
let type_data = {
|
||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
|
||||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],
|
||||
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Marker 1",
|
||||
value: 42,
|
||||
type: 'dashed'
|
||||
},
|
||||
{
|
||||
label: "Marker 2",
|
||||
value: 25,
|
||||
type: 'dashed'
|
||||
}
|
||||
],
|
||||
|
||||
yRegions: [
|
||||
{
|
||||
label: "Region Y 1",
|
||||
start: -10,
|
||||
end: 50
|
||||
},
|
||||
],
|
||||
|
||||
// will depend on series code for calculating X values
|
||||
// xRegions: [
|
||||
// {
|
||||
// label: "Region X 2",
|
||||
// start: ,
|
||||
// end: ,
|
||||
// }
|
||||
// ],
|
||||
|
||||
datasets: [
|
||||
{
|
||||
name: "Some Data",
|
||||
values: [18, 40, 30, 35, 8, 52, 17, -4],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
},
|
||||
{
|
||||
name: "Another Set",
|
||||
values: [30, 50, -10, 15, 18, 32, 27, 14],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
},
|
||||
{
|
||||
name: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37],
|
||||
chartType: 'line'
|
||||
}
|
||||
|
||||
// temp : Stacked
|
||||
// {
|
||||
// name: "Some Data",
|
||||
// values:[25, 30, 50, 45, 18, 12, 27, 14]
|
||||
// },
|
||||
// {
|
||||
// name: "Another Set",
|
||||
// values: [18, 20, 30, 35, 8, 7, 17, 4]
|
||||
// },
|
||||
// {
|
||||
// name: "Another Set",
|
||||
// values: [11, 8, 19, 15, 3, 4, 10, 2]
|
||||
// },
|
||||
]
|
||||
};
|
||||
|
||||
let type_chart = new Chart("#chart-types", {
|
||||
// title: "My Awesome Chart",
|
||||
data: type_data,
|
||||
type: 'bar',
|
||||
height: 250,
|
||||
colors: ['purple', 'magenta', 'light-blue'],
|
||||
isSeries: 1,
|
||||
xAxisMode: 'tick',
|
||||
yAxisMode: 'span',
|
||||
valuesOverPoints: 1,
|
||||
barOptions: {
|
||||
stacked: 1
|
||||
}
|
||||
// formatTooltipX: d => (d + '').toUpperCase(),
|
||||
// formatTooltipY: d => d + ' pts'
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.chart-type-buttons button')
|
||||
).map(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
let btn = e.target;
|
||||
let type = btn.getAttribute('data-type');
|
||||
|
||||
let newChart = type_chart.getDifferentChart(type);
|
||||
if(newChart){
|
||||
type_chart = newChart;
|
||||
}
|
||||
Array.prototype.slice.call(
|
||||
btn.parentNode.querySelectorAll('button')).map(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Trends Chart
|
||||
// ================================================================================
|
||||
let trends_data = {
|
||||
labels: [1967, 1968, 1969, 1970, 1971, 1972, 1973, 1974, 1975, 1976,
|
||||
1977, 1978, 1979, 1980, 1981, 1982, 1983, 1984, 1985, 1986,
|
||||
1987, 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996,
|
||||
1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
|
||||
2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016] ,
|
||||
datasets: [
|
||||
{
|
||||
"values": [132.9, 150.0, 149.4, 148.0, 94.4, 97.6, 54.1, 49.2, 22.5, 18.4,
|
||||
39.3, 131.0, 220.1, 218.9, 198.9, 162.4, 91.0, 60.5, 20.6, 14.8,
|
||||
33.9, 123.0, 211.1, 191.8, 203.3, 133.0, 76.1, 44.9, 25.1, 11.6,
|
||||
28.9, 88.3, 136.3, 173.9, 170.4, 163.6, 99.3, 65.3, 45.8, 24.7,
|
||||
12.6, 4.2, 4.8, 24.9, 80.8, 84.5, 94.0, 113.3, 69.8, 39.8]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let plot_chart_args = {
|
||||
title: "Mean Total Sunspot Count - Yearly",
|
||||
data: trends_data,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
colors: ['blue'],
|
||||
isSeries: 1,
|
||||
lineOptions: {
|
||||
hideDots: 1,
|
||||
heatline: 1,
|
||||
},
|
||||
xAxisMode: 'tick',
|
||||
yAxisMode: 'span'
|
||||
};
|
||||
|
||||
new Chart("#chart-trends", plot_chart_args);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.chart-plot-buttons button')
|
||||
).map(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
let btn = e.target;
|
||||
let type = btn.getAttribute('data-type');
|
||||
let config = [];
|
||||
|
||||
if(type === 'line') {
|
||||
config = [0, 0, 0];
|
||||
} else if(type === 'region') {
|
||||
config = [0, 0, 1];
|
||||
} else {
|
||||
config = [0, 1, 0];
|
||||
}
|
||||
|
||||
plot_chart_args.hideDots = config[0];
|
||||
plot_chart_args.heatline = config[1];
|
||||
plot_chart_args.regionFill = config[2];
|
||||
|
||||
plot_chart_args.init = false;
|
||||
|
||||
new Chart("#chart-trends", plot_chart_args);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
btn.parentNode.querySelectorAll('button')).map(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Update values chart
|
||||
// ================================================================================
|
||||
let update_data_all_labels = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue",
|
||||
"Wed", "Thu", "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
|
||||
"Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun", "Mon"];
|
||||
let update_data_all_values = Array.from({length: 30}, () => Math.floor(Math.random() * 75 - 15));
|
||||
|
||||
// We're gonna be shuffling this
|
||||
let update_data_all_indices = update_data_all_labels.map((d,i) => i);
|
||||
|
||||
let get_update_data = (source_array, length=10) => {
|
||||
let indices = update_data_all_indices.slice(0, length);
|
||||
return indices.map((index) => source_array[index]);
|
||||
};
|
||||
|
||||
let update_data = {
|
||||
labels: get_update_data(update_data_all_labels),
|
||||
datasets: [{
|
||||
"values": get_update_data(update_data_all_values)
|
||||
}],
|
||||
"specific_values": [
|
||||
{
|
||||
name: "Altitude",
|
||||
// name: "A very long text",
|
||||
line_type: "dashed",
|
||||
value: 38
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
let update_chart = new Chart("#chart-update", {
|
||||
data: update_data,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
colors: ['red'],
|
||||
isSeries: 1,
|
||||
lineOptions: {
|
||||
regionFill: 1
|
||||
},
|
||||
});
|
||||
|
||||
let chart_update_buttons = document.querySelector('.chart-update-buttons');
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="random"]').addEventListener("click", (e) => {
|
||||
shuffle(update_data_all_indices);
|
||||
let data = {
|
||||
labels: update_data_all_labels.slice(0, 10),
|
||||
datasets: [{values: get_update_data(update_data_all_values)}],
|
||||
}
|
||||
update_chart.update(data);
|
||||
});
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="add"]').addEventListener("click", (e) => {
|
||||
let index = update_chart.state.datasetLength; // last index to add
|
||||
if(index >= update_data_all_indices.length) return;
|
||||
update_chart.addDataPoint(
|
||||
update_data_all_labels[index], [update_data_all_values[index]]
|
||||
);
|
||||
});
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="remove"]').addEventListener("click", (e) => {
|
||||
update_chart.removeDataPoint();
|
||||
});
|
||||
|
||||
|
||||
// Event chart
|
||||
// ================================================================================
|
||||
let moon_names = ["Ganymede", "Callisto", "Io", "Europa"];
|
||||
let masses = [14819000, 10759000, 8931900, 4800000];
|
||||
let distances = [1070.412, 1882.709, 421.700, 671.034];
|
||||
let diameters = [5262.4, 4820.6, 3637.4, 3121.6];
|
||||
|
||||
let jupiter_moons = {
|
||||
'Ganymede': {
|
||||
mass: '14819000 x 10^16 kg',
|
||||
'semi-major-axis': '1070412 km',
|
||||
'diameter': '5262.4 km'
|
||||
},
|
||||
'Callisto': {
|
||||
mass: '10759000 x 10^16 kg',
|
||||
'semi-major-axis': '1882709 km',
|
||||
'diameter': '4820.6 km'
|
||||
},
|
||||
'Io': {
|
||||
mass: '8931900 x 10^16 kg',
|
||||
'semi-major-axis': '421700 km',
|
||||
'diameter': '3637.4 km'
|
||||
},
|
||||
'Europa': {
|
||||
mass: '4800000 x 10^16 kg',
|
||||
'semi-major-axis': '671034 km',
|
||||
'diameter': '3121.6 km'
|
||||
},
|
||||
};
|
||||
|
||||
let events_data = {
|
||||
labels: ["Ganymede", "Callisto", "Io", "Europa"],
|
||||
datasets: [
|
||||
{
|
||||
"values": distances,
|
||||
"formatted": distances.map(d => d*1000 + " km")
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let events_chart = new Chart("#chart-events", {
|
||||
title: "Jupiter's Moons: Semi-major Axis (1000 km)",
|
||||
data: events_data,
|
||||
type: 'bar',
|
||||
height: 250,
|
||||
colors: ['grey'],
|
||||
isNavigable: 1,
|
||||
});
|
||||
|
||||
let data_div = document.querySelector('.chart-events-data');
|
||||
|
||||
events_chart.parent.addEventListener('data-select', (e) => {
|
||||
let name = moon_names[e.index];
|
||||
data_div.querySelector('.moon-name').innerHTML = name;
|
||||
data_div.querySelector('.semi-major-axis').innerHTML = distances[e.index] * 1000;
|
||||
data_div.querySelector('.mass').innerHTML = masses[e.index];
|
||||
data_div.querySelector('.diameter').innerHTML = diameters[e.index];
|
||||
data_div.querySelector('img').src = "./assets/img/" + name.toLowerCase() + ".jpg";
|
||||
});
|
||||
|
||||
// Aggregation chart
|
||||
// ================================================================================
|
||||
let aggr_data = {
|
||||
labels: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
|
||||
datasets: [
|
||||
{
|
||||
"values": [25, 40, 30, 35, 8, 52, 17]
|
||||
},
|
||||
{
|
||||
"values": [25, 50, 10, 15, 18, 32, 27],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let aggr_chart = new Chart("#chart-aggr", {
|
||||
data: aggr_data,
|
||||
type: 'bar',
|
||||
height: 250,
|
||||
colors: ['light-green', 'blue'],
|
||||
valuesOverPoints: 1,
|
||||
barOptions: {
|
||||
// stacked: 1
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('[data-aggregation="sums"]').addEventListener("click", (e) => {
|
||||
if(e.target.innerHTML === "Show Sums") {
|
||||
aggr_chart.show_sums();
|
||||
e.target.innerHTML = "Hide Sums";
|
||||
} else {
|
||||
aggr_chart.hide_sums();
|
||||
e.target.innerHTML = "Show Sums";
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('[data-aggregation="average"]').addEventListener("click", (e) => {
|
||||
if(e.target.innerHTML === "Show Averages") {
|
||||
aggr_chart.show_averages();
|
||||
e.target.innerHTML = "Hide Averages";
|
||||
} else {
|
||||
aggr_chart.hide_averages();
|
||||
e.target.innerHTML = "Show Averages";
|
||||
}
|
||||
});
|
||||
|
||||
// Heatmap
|
||||
// ================================================================================
|
||||
|
||||
let heatmap_data = {};
|
||||
let current_date = new Date();
|
||||
let timestamp = current_date.getTime()/1000;
|
||||
timestamp = Math.floor(timestamp - (timestamp % 86400)).toFixed(1); // convert to midnight
|
||||
for (var i = 0; i< 375; i++) {
|
||||
heatmap_data[parseInt(timestamp)] = Math.floor(Math.random() * 5);
|
||||
timestamp = Math.floor(timestamp - 86400).toFixed(1);
|
||||
}
|
||||
|
||||
new Chart("#chart-heatmap", {
|
||||
data: heatmap_data,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: 1
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.heatmap-mode-buttons button')
|
||||
).map(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
let btn = e.target;
|
||||
let mode = btn.getAttribute('data-mode');
|
||||
let discrete_domains = 0;
|
||||
|
||||
if(mode === 'discrete') {
|
||||
discrete_domains = 1;
|
||||
}
|
||||
|
||||
let colors = [];
|
||||
let colors_mode = document
|
||||
.querySelector('.heatmap-color-buttons .active')
|
||||
.getAttribute('data-color');
|
||||
if(colors_mode === 'halloween') {
|
||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
}
|
||||
|
||||
new Chart("#chart-heatmap", {
|
||||
data: heatmap_data,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: discrete_domains,
|
||||
legend_colors: colors
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
btn.parentNode.querySelectorAll('button')).map(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.heatmap-color-buttons button')
|
||||
).map(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
let btn = e.target;
|
||||
let colors_mode = btn.getAttribute('data-color');
|
||||
let colors = [];
|
||||
|
||||
if(colors_mode === 'halloween') {
|
||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
}
|
||||
|
||||
let discrete_domains = 1;
|
||||
|
||||
let view_mode = document
|
||||
.querySelector('.heatmap-mode-buttons .active')
|
||||
.getAttribute('data-mode');
|
||||
if(view_mode === 'continuous') {
|
||||
discrete_domains = 0;
|
||||
}
|
||||
|
||||
new Chart("#chart-heatmap", {
|
||||
data: heatmap_data,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: discrete_domains,
|
||||
legend_colors: colors
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
btn.parentNode.querySelectorAll('button')).map(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Helpers
|
||||
// ================================================================================
|
||||
function shuffle(array) {
|
||||
// https://stackoverflow.com/a/2450976/6495043
|
||||
// Awesomeness: https://bost.ocks.org/mike/shuffle/
|
||||
|
||||
var currentIndex = array.length, temporaryValue, randomIndex;
|
||||
|
||||
// While there remain elements to shuffle...
|
||||
while (0 !== currentIndex) {
|
||||
|
||||
// Pick a remaining element...
|
||||
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex -= 1;
|
||||
|
||||
// And swap it with the current element.
|
||||
temporaryValue = array[currentIndex];
|
||||
array[currentIndex] = array[randomIndex];
|
||||
array[randomIndex] = temporaryValue;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
@ -27,9 +27,8 @@
|
||||
<div class="row hero" style="padding-top: 30px; padding-bottom: 0px;">
|
||||
<div class="jumbotron" style="background: transparent;">
|
||||
<h1>Frappe Charts</h1>
|
||||
<p class="mt-2">GitHub-inspired simple and modern charts for the web</p>
|
||||
<p class="mt-2">GitHub-inspired simple and modern SVG charts for the web</p>
|
||||
<p class="mt-2">with zero dependencies.</p>
|
||||
<!--<p class="mt-2">Because dumb charts are hard to come by.</p>-->
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1 later" style="font-size: 14px;">
|
||||
@ -57,28 +56,38 @@
|
||||
|
||||
datasets: [
|
||||
{
|
||||
label: "Some Data", type: 'bar',
|
||||
label: "Some Data", chartType: 'bar',
|
||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||
},
|
||||
{
|
||||
label: "Another Set", type: 'bar',
|
||||
label: "Another Set", chartType: 'bar',
|
||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||
},
|
||||
{
|
||||
label: "Yet Another", type: 'line',
|
||||
label: "Yet Another", chartType: 'line',
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37]
|
||||
}
|
||||
],
|
||||
|
||||
yMarkers: [{ label: "Marker", value: 70 }],
|
||||
yRegions: [{ label: "Region", start: -10, end: 50 }]
|
||||
yMarkers: [{ label: "Marker", value: 70,
|
||||
options: { labelPos: 'left' }}],
|
||||
yRegions: [{ label: "Region", start: -10, end: 50,
|
||||
options: { labelPos: 'right' }}]
|
||||
},
|
||||
|
||||
title: "My Awesome Chart",
|
||||
type: 'axis-mixed', // or 'bar', 'line', 'pie', 'percentage'
|
||||
height: 250,
|
||||
colors: ['purple', '#ffa3ef', 'red']
|
||||
});</code></pre>
|
||||
height: 300,
|
||||
colors: ['purple', '#ffa3ef', 'red'],
|
||||
|
||||
tooltipOptions: {
|
||||
formatTooltipX: d => (d + '').toUpperCase(),
|
||||
formatTooltipY: d => d + ' pts',
|
||||
}
|
||||
});
|
||||
|
||||
chart.export();
|
||||
</code></pre>
|
||||
<!-- <div id="chart-types" class="border" style="margin-bottom: 15px"></div> -->
|
||||
<!-- <div >
|
||||
<div class="btn-group x-axis-buttons margin-vertical-px" role="group">
|
||||
@ -101,6 +110,9 @@
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button>
|
||||
</div>
|
||||
<div class="btn-group export-buttons margin-vertical-px mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary export-aggr">Export ...</button>
|
||||
</div>
|
||||
<!-- <p class="text-muted">
|
||||
<a target="_blank" href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts">Why Percentage?</a>
|
||||
</p> -->
|
||||
@ -117,6 +129,7 @@
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="random">Random Data</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="add">Add Value</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="remove">Remove Value</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary export-update">Export ...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -133,6 +146,9 @@
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-type="heatline">HeatLine</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="regionFill">Region</button>
|
||||
</div>
|
||||
<div class="btn-group export-buttons mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary export-trends">Export ...</button>
|
||||
</div>
|
||||
<!-- <pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
lineOptions: 'line', // Line Chart specific properties:
|
||||
|
||||
@ -182,32 +198,34 @@
|
||||
And a Month-wise Heatmap
|
||||
</h6>
|
||||
<div id="chart-heatmap" class="border"
|
||||
style="overflow: scroll; text-align: center; padding: 20px;"></div>
|
||||
style="overflow: scroll;"></div>
|
||||
<div class="heatmap-mode-buttons btn-group mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-mode="discrete">Discrete</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-mode="continuous">Continuous</button>
|
||||
</div>
|
||||
<div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-color="default">Default green</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-color="halloween">GitHub's Halloween</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-color="default">Green (Default)</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-color="blue">Blue</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-color="halloween">GitHub's Halloween</button>
|
||||
</div>
|
||||
<div class="btn-group export-buttons mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary export-heatmap">Export ...</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart("#heatmap", {
|
||||
type: 'heatmap',
|
||||
height: 115,
|
||||
data: heatmapData, // object with date/timestamp-value pairs
|
||||
|
||||
discreteDomains: 1 // default: 0
|
||||
|
||||
start: startDate,
|
||||
// A Date object;
|
||||
// default: today's date in past year
|
||||
// for an annual heatmap
|
||||
|
||||
legendColors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'],
|
||||
title: "Monthly Distribution",
|
||||
data: {
|
||||
dataPoints: {'1524064033': 8, /* ... */},
|
||||
// object with timestamp-value pairs
|
||||
start: startDate
|
||||
end: endDate // Date objects
|
||||
},
|
||||
countLabel: 'Level',
|
||||
discreteDomains: 0 // default: 1
|
||||
colors: ['#ebedf0', '#c0ddf9', '#73b3f3', '#3886e1', '#17459e'],
|
||||
// Set of five incremental colors,
|
||||
// beginning with a low-saturation color for zero data;
|
||||
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
|
||||
|
||||
// preferably with a low-saturation color for zero data;
|
||||
// def: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
|
||||
});</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
@ -237,6 +255,7 @@
|
||||
isNavigable: 1, // default: 0
|
||||
valuesOverPoints: 1, // default: 0
|
||||
barOptions: {
|
||||
spaceRatio: 1 // default: 0.5
|
||||
stacked: 1 // default: 0
|
||||
}
|
||||
|
||||
@ -256,15 +275,17 @@
|
||||
},
|
||||
|
||||
// Pie/Percentage charts
|
||||
|
||||
maxLegendPoints: 6, // default: 20
|
||||
maxSlices: 10, // default: 20
|
||||
|
||||
// Heatmap
|
||||
// Percentage chart
|
||||
barOptions: {
|
||||
height: 15 // default: 20
|
||||
depth: 5 // default: 2
|
||||
}
|
||||
|
||||
// Heatmap
|
||||
discreteDomains: 1, // default: 1
|
||||
start: startDate, // Date object
|
||||
legendColors: []
|
||||
}
|
||||
...
|
||||
|
||||
@ -276,6 +297,12 @@
|
||||
chart.removeDataPoint(index)
|
||||
chart.updateDataset(datasetValues, index)
|
||||
|
||||
// Exporting
|
||||
chart.export();
|
||||
|
||||
// Unbind window-resize events
|
||||
chart.unbindWindowEvents();
|
||||
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
@ -286,9 +313,9 @@
|
||||
<p class="step-explain">Install via npm</p>
|
||||
<pre><code class="hljs console"> npm install frappe-charts</code></pre>
|
||||
<p class="step-explain">And include it in your project</p>
|
||||
<pre><code class="hljs javascript"> import Chart from "frappe-charts/dist/frappe-charts.min.esm"</code></pre>
|
||||
<pre><code class="hljs javascript"> import { Chart } from "frappe-charts"</code></pre>
|
||||
<p class="step-explain">... or include it directly in your HTML</p>
|
||||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@1.0.0/dist/frappe-charts.min.iife.js"></script></code></pre>
|
||||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@1.0.0"></script></code></pre>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -336,6 +363,6 @@
|
||||
</a>
|
||||
|
||||
<script src="assets/js/frappe-charts.min.js"></script>
|
||||
<script src="assets/js/index.js"></script>
|
||||
<script src="assets/js/index.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,312 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Frappe Charts</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="keywords" content="open source javascript js charts library svg zero-dependency interactive data visualization beautiful drag resize">
|
||||
<meta name="description" content="A simple, responsive, modern charts library for the web.">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/normalize.css" media="screen">
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:400,700' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/bootstrap.min.css" media="screen">
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/frappe_theme.css" media="screen">
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/index.css" media="screen">
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/default.css" media="screen">
|
||||
<script src="assets/js/highlight.pack.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
|
||||
<link rel="shortcut icon" href="https://frappe.github.io/frappe/assets/img/favicon.png" type="image/x-icon">
|
||||
<link rel="icon" href="https://frappe.github.io/frappe/assets/img/favicon.png" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row hero" style="padding-top: 30px; padding-bottom: 0px;">
|
||||
<div class="jumbotron" style="background: transparent;">
|
||||
<h1>Frappe Charts</h1>
|
||||
<p class="mt-2">GitHub-inspired simple and modern charts for the web</p>
|
||||
<p class="mt-2">with zero dependencies.</p>
|
||||
<!--<p class="mt-2">Because dumb charts are hard to come by.</p>-->
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1 later" style="font-size: 14px;">
|
||||
<div id="chart-composite-1" class="border"><svg height=225></svg></div>
|
||||
<p class="mt-1">Click or use arrow keys to navigate data points</p>
|
||||
</div>
|
||||
<div class="col-sm-10 push-sm-1 later" style="font-size: 14px;">
|
||||
<div id="chart-composite-2" class="border"><svg height=225></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group later">
|
||||
<div class="row section">
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
<!--Bars, Lines or <a href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts" target="_blank">Percentages</a>-->
|
||||
Create a chart
|
||||
</h6>
|
||||
<p class="step-explain">Install</p>
|
||||
<pre><code class="hljs console"> npm install frappe-charts</code></pre>
|
||||
<p class="step-explain">And include it in your project</p>
|
||||
<pre><code class="hljs javascript"> import Chart from "frappe-charts/dist/frappe-charts.min.esm"</code></pre>
|
||||
<p class="step-explain">... or include it directly in your HTML</p>
|
||||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@1.0.0/dist/frappe-charts.min.iife.js"></script></code></pre>
|
||||
<p class="step-explain">Make a new Chart</p>
|
||||
<pre><code class="hljs html"> <!--HTML-->
|
||||
<div id="chart"></div></code></pre>
|
||||
<pre><code class="hljs javascript"> // Javascript
|
||||
let data = {
|
||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
|
||||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],
|
||||
|
||||
datasets: [
|
||||
{
|
||||
label: "Some Data",
|
||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||
},
|
||||
{
|
||||
label: "Another Set",
|
||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||
},
|
||||
{
|
||||
label: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let chart = new Chart({
|
||||
parent: "#chart", // or a DOM element
|
||||
title: "My Awesome Chart",
|
||||
data: data,
|
||||
type: 'bar', // or 'line', 'scatter', 'pie', 'percentage'
|
||||
height: 250,
|
||||
|
||||
colors: ['#7cd6fd', 'violet', 'blue'],
|
||||
// hex-codes or these preset colors;
|
||||
// defaults (in order):
|
||||
// ['light-blue', 'blue', 'violet', 'red',
|
||||
// 'orange', 'yellow', 'green', 'light-green',
|
||||
// 'purple', 'magenta', 'grey', 'dark-grey']
|
||||
|
||||
format_tooltip_x: d => (d + '').toUpperCase(),
|
||||
format_tooltip_y: d => d + ' pts'
|
||||
});</code></pre>
|
||||
<div id="chart-types" class="border"></div>
|
||||
<div class="btn-group chart-type-buttons margin-vertical-px mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-type='bar'>Bar Chart</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='line'>Line Chart</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button>
|
||||
</div>
|
||||
<p class="text-muted">
|
||||
<a target="_blank" href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts">Why Percentage?</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
Update Values
|
||||
</h6>
|
||||
<pre><code class="hljs javascript"> // Update entire datasets
|
||||
chart.updateData(
|
||||
[
|
||||
{values: new_dataset_1_values},
|
||||
{values: new_dataset_2_values}
|
||||
],
|
||||
new_labels
|
||||
);
|
||||
|
||||
// Add a new data point
|
||||
chart.add_data_point(
|
||||
[new_value_1, new_value_2],
|
||||
new_label,
|
||||
index // defaults to last index
|
||||
);
|
||||
|
||||
// Remove a data point
|
||||
chart.remove_data_point(index);</code></pre>
|
||||
<div id="chart-update" class="border"></div>
|
||||
<div class="chart-update-buttons mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="random">Random Data</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="add">Add Value</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="remove">Remove Value</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
// Include specific Y values in input data to be displayed as lines
|
||||
// (before passing data to a new chart):
|
||||
|
||||
data.specific_values = [
|
||||
{
|
||||
label: "Altitude",
|
||||
line_type: "dashed", // or "solid"
|
||||
value: 38
|
||||
}
|
||||
]
|
||||
...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
Plot Trends
|
||||
</h6>
|
||||
<pre><code class="hljs javascript"> ...
|
||||
xAxisMode: 'tick', // for short label ticks
|
||||
// or 'span' for long spanning vertical axis lines
|
||||
yAxisMode: 'span', // for long horizontal lines, or 'tick'
|
||||
isSeries: 1, // to allow for skipping of X values
|
||||
...</code></pre>
|
||||
<div id="chart-trends" class="border"></div>
|
||||
<div class="btn-group chart-plot-buttons mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="line">Line</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="dots">Dots</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-type="heatline">HeatLine</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="region">Region</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
type: 'line', // Line Chart specific properties:
|
||||
|
||||
hideDots: 1, // Hide data points on the line; default 0
|
||||
heatline: 1, // Show a value-wise line gradient; default 0
|
||||
regionFill: 1, // Fill the area under the graph; default 0
|
||||
...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
Listen to state change
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div id="chart-events" class="border"></div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="chart-events-data" class="border data-container">
|
||||
<div class="image-container border">
|
||||
<img class="moon-image" src="./assets/img/europa.jpg">
|
||||
</div>
|
||||
<div class="data margin-vertical-px">
|
||||
<h6 class="moon-name">Europa</h6>
|
||||
<p>Semi-major-axis: <span class="semi-major-axis">671034</span> km</p>
|
||||
<p>Mass: <span class="mass">4800000</span> x 10^16 kg</p>
|
||||
<p>Diameter: <span class="diameter">3121.6</span> km</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
type: 'bar', // Bar Chart specific properties:
|
||||
isNavigable: 1, // Navigate across bars; default 0
|
||||
...
|
||||
|
||||
chart.parent.addEventListener('data-select', (e) => {
|
||||
update_moon_data(e.index); // e contains index and value of current datapoint
|
||||
});</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
Simple Aggregations
|
||||
</h6>
|
||||
<div id="chart-aggr" class="border"></div>
|
||||
<div class="chart-aggr-buttons mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-aggregation="sums">Show Sums</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-aggregation="average">Show Averages</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> chart.show_sums(); // and `hide_sums()`
|
||||
|
||||
chart.show_averages(); // and `hide_averages()`</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
And a Month-wise Heatmap
|
||||
</h6>
|
||||
<div id="chart-heatmap" class="border"
|
||||
style="overflow: scroll; text-align: center; padding: 20px;"></div>
|
||||
<div class="heatmap-mode-buttons btn-group mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-mode="discrete">Discrete</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-mode="continuous">Continuous</button>
|
||||
</div>
|
||||
<div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-color="default">Default green</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-color="halloween">GitHub's Halloween</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart({
|
||||
parent: "#heatmap",
|
||||
type: 'heatmap',
|
||||
height: 115,
|
||||
data: heatmap_data, // object with date/timestamp-value pairs
|
||||
|
||||
discrete_domains: 1 // default: 0
|
||||
|
||||
start: start_date,
|
||||
// A Date object;
|
||||
// default: today's date in past year
|
||||
// for an annual heatmap
|
||||
|
||||
legend_colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'],
|
||||
// Set of five incremental colors,
|
||||
// beginning with a low-saturation color for zero data;
|
||||
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
|
||||
|
||||
});</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<!-- Closing -->
|
||||
<div class="text-center" style="margin-top: 70px">
|
||||
<a href="https://github.com/frappe/charts/archive/master.zip"><button class="large blue button">Download</button></a>
|
||||
<p style="margin-top: 3rem;margin-bottom: 1.5rem;"><a href="https://github.com/frappe/charts" target="_blank">View on GitHub</a></p>
|
||||
<p style="margin-top: 1rem;"><iframe src="https://ghbtns.com/github-btn.html?user=frappe&repo=charts&type=star&count=true" frameborder="0" scrolling="0" width="94px" height="20px"></iframe></p>
|
||||
<p>License: MIT</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="built-with-frappe text-center" style="margin-top: -20px">
|
||||
<img style="padding: 5px; width: 40px; background: #fff" class="frappe-bird" src="https://frappe.github.io/frappe/assets/img/frappe-bird-grey.svg">
|
||||
<p style="margin: 24px 0 0px 0; font-size: 15px">
|
||||
Project maintained by <a href="https://frappe.io" target="_blank">Frappe</a>.
|
||||
Used in <a href="https://erpnext.com" target="_blank">ERPNext</a>.
|
||||
Read the <a href="https://medium.com/@pratu16x7/so-we-decided-to-create-our-own-charts-a95cb5032c97" target="_blank">blog post</a>.
|
||||
</p>
|
||||
<p style="margin: 24px 0 80px 0; font-size: 12px">
|
||||
Data from the <a href="https://www.amsmeteors.org" target="_blank">American Meteor Society</a>,
|
||||
<a href="http://www.sidc.be/silso" target="_blank">SILSO</a> and
|
||||
<a href="https://api.nasa.gov/index.html" target="_blank">NASA Open APIs</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a href="https://github.com/frappe/charts" target="_blank" class="github-corner" aria-label="View source on Github">
|
||||
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#9a9a9a; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
||||
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
|
||||
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<script src="assets/js/frappe-charts.min.js"></script>
|
||||
<script src="assets/js/old_index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
2734
package-lock.json
generated
2734
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
15
package.json
15
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "frappe-charts",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"description": "https://frappe.github.io/charts",
|
||||
"main": "dist/frappe-charts.min.cjs.js",
|
||||
"module": "dist/frappe-charts.min.esm.js",
|
||||
@ -33,16 +33,28 @@
|
||||
},
|
||||
"homepage": "https://github.com/frappe/charts#readme",
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^8.2.0",
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-istanbul": "^4.1.5",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-latest": "^6.24.1",
|
||||
"clean-css": "^4.1.11",
|
||||
"babel-register": "^6.26.0",
|
||||
"coveralls": "^3.0.0",
|
||||
"cross-env": "^5.1.4",
|
||||
"cssnano": "^3.10.0",
|
||||
"eslint": "^4.18.2",
|
||||
"fs": "0.0.1-security",
|
||||
"livereload": "^0.6.3",
|
||||
"mocha": "^5.0.5",
|
||||
"node-sass": "^4.7.2",
|
||||
"npm-run-all": "^4.1.1",
|
||||
"postcss": "^6.0.21",
|
||||
"nyc": "^11.6.0",
|
||||
"postcss-cssnext": "^3.0.2",
|
||||
"postcss-nested": "^2.1.2",
|
||||
"precss": "^3.1.2",
|
||||
"rollup": "^0.50.0",
|
||||
"rollup-plugin-babel": "^3.0.2",
|
||||
"rollup-plugin-eslint": "^4.0.0",
|
||||
@ -54,6 +66,5 @@
|
||||
"rollup-watch": "^4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^4.18.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,21 +1,45 @@
|
||||
import pkg from './package.json';
|
||||
|
||||
// Rollup plugins
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import eslint from 'rollup-plugin-eslint';
|
||||
import replace from 'rollup-plugin-replace';
|
||||
import uglify from 'rollup-plugin-uglify-es';
|
||||
import sass from 'node-sass';
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
|
||||
// PostCSS plugins
|
||||
import postcssPlugin from 'rollup-plugin-postcss';
|
||||
import nested from 'postcss-nested';
|
||||
import cssnext from 'postcss-cssnext';
|
||||
import cssnano from 'cssnano';
|
||||
|
||||
import pkg from './package.json';
|
||||
import postcss from 'postcss';
|
||||
import precss from 'precss';
|
||||
import CleanCSS from 'clean-css';
|
||||
import autoprefixer from 'autoprefixer';
|
||||
import fs from 'fs';
|
||||
|
||||
fs.readFile('src/css/charts.scss', (err, css) => {
|
||||
postcss([precss, autoprefixer])
|
||||
.process(css, { from: 'src/css/charts.scss', to: 'src/css/charts.css' })
|
||||
.then(result => {
|
||||
let options = {
|
||||
level: {
|
||||
1: {
|
||||
removeQuotes: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
let output = new CleanCSS(options).minify(result.css);
|
||||
let res = JSON.stringify(output.styles).replace(/"/g, "'");
|
||||
let js = `export const CSSTEXT = "${res.slice(1, -1)}";`;
|
||||
fs.writeFile('src/css/chartsCss.js', js);
|
||||
});
|
||||
});
|
||||
|
||||
export default [
|
||||
{
|
||||
input: 'src/js/chart.js',
|
||||
input: 'src/js/index.js',
|
||||
sourcemap: true,
|
||||
output: [
|
||||
{
|
||||
@ -27,9 +51,9 @@ export default [
|
||||
format: 'iife',
|
||||
}
|
||||
],
|
||||
name: 'Chart',
|
||||
name: 'frappe',
|
||||
plugins: [
|
||||
postcss({
|
||||
postcssPlugin({
|
||||
preprocessor: (content, id) => new Promise((resolve, reject) => {
|
||||
const result = sass.renderSync({ file: id })
|
||||
resolve({ code: result.css.toString() })
|
||||
@ -43,11 +67,12 @@ export default [
|
||||
}),
|
||||
eslint({
|
||||
exclude: [
|
||||
'src/scss/**'
|
||||
'src/css/**'
|
||||
]
|
||||
}),
|
||||
babel({
|
||||
exclude: 'node_modules/**'
|
||||
exclude: 'node_modules/**',
|
||||
plugins: ['external-helpers']
|
||||
}),
|
||||
replace({
|
||||
exclude: 'node_modules/**',
|
||||
@ -56,8 +81,46 @@ export default [
|
||||
uglify()
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'docs/assets/js/index.js',
|
||||
sourcemap: true,
|
||||
output: [
|
||||
{
|
||||
file: 'docs/assets/js/index.min.js',
|
||||
format: 'iife',
|
||||
}
|
||||
],
|
||||
name: 'frappe',
|
||||
plugins: [
|
||||
postcssPlugin({
|
||||
preprocessor: (content, id) => new Promise((resolve, reject) => {
|
||||
const result = sass.renderSync({ file: id })
|
||||
resolve({ code: result.css.toString() })
|
||||
}),
|
||||
extensions: [ '.scss' ],
|
||||
plugins: [
|
||||
nested(),
|
||||
cssnext({ warnForDuplicates: false }),
|
||||
cssnano()
|
||||
]
|
||||
}),
|
||||
eslint({
|
||||
exclude: [
|
||||
'src/css/**'
|
||||
]
|
||||
}),
|
||||
babel({
|
||||
exclude: 'node_modules/**'
|
||||
}),
|
||||
replace({
|
||||
exclude: 'node_modules/**',
|
||||
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'src/js/chart.js',
|
||||
sourcemap: true,
|
||||
output: [
|
||||
{
|
||||
file: pkg.main,
|
||||
@ -69,7 +132,7 @@ export default [
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
postcss({
|
||||
postcssPlugin({
|
||||
preprocessor: (content, id) => new Promise((resolve, reject) => {
|
||||
const result = sass.renderSync({ file: id })
|
||||
resolve({ code: result.css.toString() })
|
||||
@ -83,7 +146,7 @@ export default [
|
||||
}),
|
||||
eslint({
|
||||
exclude: [
|
||||
'src/scss/**',
|
||||
'src/css/**',
|
||||
]
|
||||
}),
|
||||
babel({
|
||||
@ -105,7 +168,7 @@ export default [
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
postcss({
|
||||
postcssPlugin({
|
||||
preprocessor: (content, id) => new Promise((resolve, reject) => {
|
||||
const result = sass.renderSync({ file: id })
|
||||
resolve({ code: result.css.toString() })
|
||||
@ -120,7 +183,7 @@ export default [
|
||||
}),
|
||||
eslint({
|
||||
exclude: [
|
||||
'src/scss/**',
|
||||
'src/css/**',
|
||||
]
|
||||
}),
|
||||
replace({
|
||||
|
||||
116
src/css/charts.scss
Normal file
116
src/css/charts.scss
Normal file
@ -0,0 +1,116 @@
|
||||
.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;
|
||||
|
||||
.axis, .chart-label {
|
||||
fill: #555b51;
|
||||
|
||||
line {
|
||||
stroke: #dadada;
|
||||
}
|
||||
}
|
||||
.dataset-units {
|
||||
circle {
|
||||
stroke: #fff;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: none;
|
||||
stroke-opacity: 1;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
.dataset-path {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.path-group {
|
||||
path {
|
||||
fill: none;
|
||||
stroke-opacity: 1;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5, 3;
|
||||
}
|
||||
.axis-line {
|
||||
.specific-value {
|
||||
text-anchor: start;
|
||||
}
|
||||
.y-line {
|
||||
text-anchor: end;
|
||||
}
|
||||
.x-line {
|
||||
text-anchor: middle;
|
||||
}
|
||||
}
|
||||
.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, 0.8);
|
||||
border-radius: 3px;
|
||||
ul {
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
}
|
||||
ol {
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
}
|
||||
ul.data-point-list {
|
||||
li {
|
||||
min-width: 90px;
|
||||
flex: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
strong {
|
||||
color: #dfe2e5;
|
||||
font-weight: 600;
|
||||
}
|
||||
.svg-pointer {
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
margin: 0 0 0 -5px;
|
||||
content: ' ';
|
||||
border: 5px solid transparent;
|
||||
border-top-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
&.comparison {
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
pointer-events: none;
|
||||
.title {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
list-style: none;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/css/chartsCss.js
Normal file
1
src/css/chartsCss.js
Normal file
@ -0,0 +1 @@
|
||||
export const CSSTEXT = ".chart-container{position:relative;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif}.chart-container .axis,.chart-container .chart-label{fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .dataset-units circle{stroke:#fff;stroke-width:2}.chart-container .dataset-units path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container .dataset-path{stroke-width:2px}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .axis-line .specific-value{text-anchor:start}.chart-container .axis-line .y-line{text-anchor:end}.chart-container .axis-line .x-line{text-anchor:middle}.chart-container .legend-dataset-text{fill:#6c7680;font-weight:600}.graph-svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#959da5;text-align:center;background:rgba(0,0,0,.8);border-radius:3px}.graph-svg-tip ul{padding-left:0;display:flex}.graph-svg-tip ol{padding-left:0;display:flex}.graph-svg-tip ul.data-point-list li{min-width:90px;flex:1;font-weight:600}.graph-svg-tip strong{color:#dfe2e5;font-weight:600}.graph-svg-tip .svg-pointer{position:absolute;height:5px;margin:0 0 0 -5px;content:' ';border:5px solid transparent;border-top-color:rgba(0,0,0,.8)}.graph-svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.graph-svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:600;line-height:1;pointer-events:none}.graph-svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.graph-svg-tip.comparison li{display:inline-block;padding:5px 10px}";
|
||||
@ -1,4 +1,4 @@
|
||||
import '../scss/charts.scss';
|
||||
import '../css/charts.scss';
|
||||
|
||||
// import MultiAxisChart from './charts/MultiAxisChart';
|
||||
import PercentageChart from './charts/PercentageChart';
|
||||
@ -7,6 +7,8 @@ import Heatmap from './charts/Heatmap';
|
||||
import AxisChart from './charts/AxisChart';
|
||||
|
||||
const chartTypes = {
|
||||
bar: AxisChart,
|
||||
line: AxisChart,
|
||||
// multiaxis: MultiAxisChart,
|
||||
percentage: PercentageChart,
|
||||
heatmap: Heatmap,
|
||||
@ -14,13 +16,7 @@ const chartTypes = {
|
||||
};
|
||||
|
||||
function getChartByType(chartType = 'line', parent, options) {
|
||||
if(chartType === 'line') {
|
||||
options.type = 'line';
|
||||
return new AxisChart(parent, options);
|
||||
} else if (chartType === 'bar') {
|
||||
options.type = 'bar';
|
||||
return new AxisChart(parent, options);
|
||||
} else if (chartType === 'axis-mixed') {
|
||||
if (chartType === 'axis-mixed') {
|
||||
options.type = 'line';
|
||||
return new AxisChart(parent, options);
|
||||
}
|
||||
@ -33,8 +29,10 @@ function getChartByType(chartType = 'line', parent, options) {
|
||||
return new chartTypes[chartType](parent, options);
|
||||
}
|
||||
|
||||
export default class Chart {
|
||||
class Chart {
|
||||
constructor(parent, options) {
|
||||
return getChartByType(options.type, parent, options);
|
||||
}
|
||||
}
|
||||
|
||||
export { Chart, PercentageChart, PieChart, Heatmap, AxisChart };
|
||||
@ -1,5 +1,6 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { $ } from '../utils/dom';
|
||||
import { legendDot } from '../utils/draw';
|
||||
import { getExtraWidth } from '../utils/constants';
|
||||
|
||||
export default class AggregationChart extends BaseChart {
|
||||
constructor(parent, args) {
|
||||
@ -24,7 +25,7 @@ export default class AggregationChart extends BaseChart {
|
||||
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) {
|
||||
@ -45,28 +46,41 @@ export default class AggregationChart extends BaseChart {
|
||||
s.sliceTotals.push(d[0]);
|
||||
s.labels.push(d[1]);
|
||||
});
|
||||
|
||||
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0);
|
||||
|
||||
this.center = {
|
||||
x: this.width / 2,
|
||||
y: this.height / 2
|
||||
};
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
let s = this.state;
|
||||
|
||||
this.statsWrapper.textContent = '';
|
||||
|
||||
this.legendArea.textContent = '';
|
||||
this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints);
|
||||
|
||||
let xValues = s.labels;
|
||||
let count = 0;
|
||||
let y = 0;
|
||||
this.legendTotals.map((d, i) => {
|
||||
if(d) {
|
||||
let stats = $.create('div', {
|
||||
className: 'stats',
|
||||
inside: this.statsWrapper
|
||||
});
|
||||
stats.innerHTML = `<span class="indicator">
|
||||
<i style="background: ${this.colors[i]}"></i>
|
||||
<span class="text-muted">${xValues[i]}:</span>
|
||||
${d}
|
||||
</span>`;
|
||||
let barWidth = 110;
|
||||
let divisor = Math.floor(
|
||||
(this.width - getExtraWidth(this.measures))/barWidth
|
||||
);
|
||||
if(count > divisor) {
|
||||
count = 0;
|
||||
y += 20;
|
||||
}
|
||||
let x = barWidth * count + 5;
|
||||
let dot = legendDot(
|
||||
x,
|
||||
y,
|
||||
5,
|
||||
this.colors[i],
|
||||
`${s.labels[i]}: ${d}`
|
||||
);
|
||||
this.legendArea.appendChild(dot);
|
||||
count++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { dataPrep, zeroDataPrep, getShortenedLabels } from '../utils/axis-chart-utils';
|
||||
import { Y_AXIS_MARGIN } from '../utils/constants';
|
||||
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 } from '../utils/intervals';
|
||||
import { getOffset, fire } from '../utils/dom';
|
||||
import { calcChartIntervals, getIntervalSize, getValueRange, getZeroIndex, scale, getClosestInArray } from '../utils/intervals';
|
||||
import { floatTwo } from '../utils/helpers';
|
||||
import { makeOverlay, updateOverlay } from '../utils/draw';
|
||||
import { MIN_BAR_PERCENT_HEIGHT, BAR_CHART_SPACE_RATIO, LINE_CHART_DOT_SIZE } from '../utils/constants';
|
||||
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';
|
||||
|
||||
export default class AxisChart extends BaseChart {
|
||||
constructor(parent, args) {
|
||||
@ -21,26 +22,27 @@ export default class AxisChart extends BaseChart {
|
||||
this.setup();
|
||||
}
|
||||
|
||||
configure(args) {
|
||||
super.configure();
|
||||
|
||||
args.axisOptions = args.axisOptions || {};
|
||||
args.tooltipOptions = args.tooltipOptions || {};
|
||||
|
||||
this.config.xAxisMode = args.axisOptions.xAxisMode || 'span';
|
||||
this.config.yAxisMode = args.axisOptions.yAxisMode || 'span';
|
||||
this.config.xIsSeries = args.axisOptions.xIsSeries || 0;
|
||||
|
||||
this.config.formatTooltipX = args.tooltipOptions.formatTooltipX;
|
||||
this.config.formatTooltipY = args.tooltipOptions.formatTooltipY;
|
||||
|
||||
this.config.valuesOverPoints = args.valuesOverPoints;
|
||||
setMeasures() {
|
||||
if(this.data.datasets.length <= 1) {
|
||||
this.config.showLegend = 0;
|
||||
this.measures.paddings.bottom = 30;
|
||||
}
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
this.leftMargin = Y_AXIS_MARGIN;
|
||||
this.rightMargin = Y_AXIS_MARGIN;
|
||||
configure(options) {
|
||||
super.configure(options);
|
||||
|
||||
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.formatTooltipX = options.tooltipOptions.formatTooltipX;
|
||||
this.config.formatTooltipY = options.tooltipOptions.formatTooltipY;
|
||||
|
||||
this.config.valuesOverPoints = options.valuesOverPoints;
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
@ -53,8 +55,10 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
calc(onlyWidthChange = false) {
|
||||
this.calcXPositions();
|
||||
if(onlyWidthChange) return;
|
||||
this.calcYAxisParameters(this.getAllYValues(), this.type === 'line');
|
||||
if(!onlyWidthChange) {
|
||||
this.calcYAxisParameters(this.getAllYValues(), this.type === 'line');
|
||||
}
|
||||
this.makeDataByIndex();
|
||||
}
|
||||
|
||||
calcXPositions() {
|
||||
@ -139,6 +143,7 @@ export default class AxisChart extends BaseChart {
|
||||
if(this.data.yMarkers) {
|
||||
this.state.yMarkers = this.data.yMarkers.map(d => {
|
||||
d.position = scale(d.value, s.yAxis);
|
||||
if(!d.options) d.options = {};
|
||||
// if(!d.label.includes(':')) {
|
||||
// d.label += ': ' + d.value;
|
||||
// }
|
||||
@ -149,13 +154,13 @@ export default class AxisChart extends BaseChart {
|
||||
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 = {};
|
||||
return d;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getAllYValues() {
|
||||
// TODO: yMarkers, regions, sums, every Y value ever
|
||||
let key = 'values';
|
||||
|
||||
if(this.barOptions.stacked) {
|
||||
@ -300,6 +305,8 @@ 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;
|
||||
|
||||
return {
|
||||
xPositions: s.xAxis.positions,
|
||||
@ -307,7 +314,7 @@ export default class AxisChart extends BaseChart {
|
||||
|
||||
values: d.values,
|
||||
|
||||
zeroLine: s.yAxis.zeroLine,
|
||||
zeroLine: minLine,
|
||||
radius: this.lineOptions.dotSize || LINE_CHART_DOT_SIZE,
|
||||
};
|
||||
}.bind(this)
|
||||
@ -343,14 +350,46 @@ export default class AxisChart extends BaseChart {
|
||||
}));
|
||||
}
|
||||
|
||||
makeDataByIndex() {
|
||||
this.dataByIndex = {};
|
||||
|
||||
let s = this.state;
|
||||
let formatX = this.config.formatTooltipX;
|
||||
let formatY = this.config.formatTooltipY;
|
||||
let titles = s.xAxis.labels;
|
||||
|
||||
titles.map((label, index) => {
|
||||
let values = this.state.datasets.map((set, i) => {
|
||||
let value = set.values[index];
|
||||
return {
|
||||
title: set.name,
|
||||
value: value,
|
||||
yPos: set.yPositions[index],
|
||||
color: this.colors[i],
|
||||
formatted: formatY ? formatY(value) : value,
|
||||
};
|
||||
});
|
||||
|
||||
this.dataByIndex[index] = {
|
||||
label: label,
|
||||
formattedLabel: formatX ? formatX(label) : label,
|
||||
xPos: s.xAxis.positions[index],
|
||||
values: values,
|
||||
yExtreme: s.yExtremes[index],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
// NOTE: could be in tooltip itself, as it is a given functionality for its parent
|
||||
this.chartWrapper.addEventListener('mousemove', (e) => {
|
||||
let o = getOffset(this.chartWrapper);
|
||||
let relX = e.pageX - o.left - this.leftMargin;
|
||||
let relY = e.pageY - o.top - this.translateY;
|
||||
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 + this.translateY * 2) {
|
||||
if(relY < this.height + getTopOffset(m)
|
||||
&& relY > getTopOffset(m)) {
|
||||
this.mapTooltipXPosition(relX);
|
||||
} else {
|
||||
this.tip.hideTip();
|
||||
@ -362,56 +401,43 @@ export default class AxisChart extends BaseChart {
|
||||
let s = this.state;
|
||||
if(!s.yExtremes) return;
|
||||
|
||||
let formatY = this.config.formatTooltipY;
|
||||
let formatX = this.config.formatTooltipX;
|
||||
let index = getClosestInArray(relX, s.xAxis.positions, true);
|
||||
let dbi = this.dataByIndex[index];
|
||||
|
||||
let titles = s.xAxis.labels;
|
||||
if(formatX && formatX(titles[0])) {
|
||||
titles = titles.map(d=>formatX(d));
|
||||
}
|
||||
this.tip.setValues(
|
||||
dbi.xPos + this.tip.offset.x,
|
||||
dbi.yExtreme + this.tip.offset.y,
|
||||
{name: dbi.formattedLabel, value: ''},
|
||||
dbi.values,
|
||||
index
|
||||
);
|
||||
|
||||
formatY = formatY && formatY(s.yAxis.labels[0]) ? formatY : 0;
|
||||
|
||||
for(var i=s.datasetLength - 1; i >= 0 ; i--) {
|
||||
let xVal = s.xAxis.positions[i];
|
||||
// let delta = i === 0 ? s.unitWidth : xVal - s.xAxis.positions[i-1];
|
||||
if(relX > xVal - s.unitWidth/2) {
|
||||
let x = xVal + this.leftMargin;
|
||||
let y = s.yExtremes[i] + this.translateY;
|
||||
|
||||
let values = this.data.datasets.map((set, j) => {
|
||||
return {
|
||||
title: set.name,
|
||||
value: formatY ? formatY(set.values[i]) : set.values[i],
|
||||
color: this.colors[j],
|
||||
};
|
||||
});
|
||||
|
||||
this.tip.setValues(x, y, {name: titles[i], value: ''}, values, i);
|
||||
this.tip.showTip();
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.tip.showTip();
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
let s = this.data;
|
||||
this.statsWrapper.textContent = '';
|
||||
|
||||
if(s.datasets.length > 1) {
|
||||
this.legendArea.textContent = '';
|
||||
s.datasets.map((d, i) => {
|
||||
let stats = $.create('div', {
|
||||
className: 'stats',
|
||||
inside: this.statsWrapper
|
||||
});
|
||||
stats.innerHTML = `<span class="indicator">
|
||||
<i style="background: ${this.colors[i]}"></i>
|
||||
${d.name}
|
||||
</span>`;
|
||||
let barWidth = AXIS_LEGEND_BAR_SIZE;
|
||||
// let rightEndPoint = this.baseWidth - this.measures.margins.left - this.measures.margins.right;
|
||||
// let multiplier = s.datasets.length - i;
|
||||
let rect = legendBar(
|
||||
// rightEndPoint - multiplier * barWidth, // To right align
|
||||
barWidth * i,
|
||||
'0',
|
||||
barWidth,
|
||||
this.colors[i],
|
||||
d.name);
|
||||
this.legendArea.appendChild(rect);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Overlay
|
||||
makeOverlay() {
|
||||
if(this.init) {
|
||||
this.init = 0;
|
||||
@ -512,6 +538,8 @@ 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);
|
||||
|
||||
@ -1,35 +1,49 @@
|
||||
import SvgTip from '../objects/SvgTip';
|
||||
import { $, isElementInViewport, getElementContentWidth } from '../utils/dom';
|
||||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup } from '../utils/draw';
|
||||
import { VERT_SPACE_OUTSIDE_BASE_CHART, TRANSLATE_Y_BASE_CHART, LEFT_MARGIN_BASE_CHART,
|
||||
RIGHT_MARGIN_BASE_CHART, INIT_CHART_UPDATE_TIMEOUT, CHART_POST_ANIMATE_TIMEOUT } from '../utils/constants';
|
||||
import { getColor, DEFAULT_COLORS } from '../utils/colors';
|
||||
import { getDifferentChart } from '../config';
|
||||
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';
|
||||
|
||||
let BOUND_DRAW_FN;
|
||||
|
||||
export default class BaseChart {
|
||||
constructor(parent, options) {
|
||||
this.rawChartArgs = options;
|
||||
|
||||
this.parent = typeof parent === 'string' ? document.querySelector(parent) : parent;
|
||||
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.');
|
||||
}
|
||||
|
||||
this.rawChartArgs = options;
|
||||
|
||||
this.title = options.title || '';
|
||||
this.subtitle = options.subtitle || '';
|
||||
this.argHeight = options.height || 240;
|
||||
this.type = options.type || '';
|
||||
|
||||
this.realData = this.prepareData(options.data);
|
||||
this.data = this.prepareFirstData(this.realData);
|
||||
this.colors = [];
|
||||
|
||||
this.colors = this.validateColors(options.colors, this.type);
|
||||
|
||||
this.config = {
|
||||
showTooltip: 1, // calculate
|
||||
showLegend: options.showLegend || 1,
|
||||
showLegend: 1, // calculate
|
||||
isNavigable: options.isNavigable || 0,
|
||||
animate: 1
|
||||
};
|
||||
|
||||
this.measures = JSON.parse(JSON.stringify(BASE_MEASURES));
|
||||
let m = this.measures;
|
||||
this.setMeasures(options);
|
||||
if(!this.title.length) { m.titleHeight = 0; }
|
||||
if(!this.config.showLegend) m.legendHeight = 0;
|
||||
this.argHeight = options.height || m.baseHeight;
|
||||
|
||||
this.state = {};
|
||||
this.options = {};
|
||||
|
||||
@ -42,84 +56,81 @@ export default class BaseChart {
|
||||
this.configure(options);
|
||||
}
|
||||
|
||||
configure(args) {
|
||||
this.setColors(args);
|
||||
this.setMargins();
|
||||
|
||||
// Bind window events
|
||||
window.addEventListener('resize', () => this.draw(true));
|
||||
window.addEventListener('orientationchange', () => this.draw(true));
|
||||
prepareData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
setColors() {
|
||||
let args = this.rawChartArgs;
|
||||
|
||||
// Needs structure as per only labels/datasets, from config
|
||||
const list = args.type === 'percentage' || args.type === 'pie'
|
||||
? args.data.labels
|
||||
: args.data.datasets;
|
||||
|
||||
if(!args.colors || (list && args.colors.length < list.length)) {
|
||||
this.colors = DEFAULT_COLORS;
|
||||
} else {
|
||||
this.colors = args.colors;
|
||||
}
|
||||
|
||||
this.colors = this.colors.map(color => getColor(color));
|
||||
prepareFirstData(data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
validateColors(colors, type) {
|
||||
const validColors = [];
|
||||
colors = (colors || []).concat(DEFAULT_COLORS[type]);
|
||||
colors.forEach((string) => {
|
||||
const color = getColor(string);
|
||||
if(!isValidColor(color)) {
|
||||
console.warn('"' + string + '" is not a valid color.');
|
||||
} else {
|
||||
validColors.push(color);
|
||||
}
|
||||
});
|
||||
return validColors;
|
||||
}
|
||||
|
||||
setMeasures() {
|
||||
// Override measures, including those for title and legend
|
||||
// set config for legend and title
|
||||
}
|
||||
|
||||
configure() {
|
||||
let height = this.argHeight;
|
||||
this.baseHeight = height;
|
||||
this.height = height - VERT_SPACE_OUTSIDE_BASE_CHART;
|
||||
this.translateY = TRANSLATE_Y_BASE_CHART;
|
||||
this.height = height - getExtraHeight(this.measures);
|
||||
|
||||
// Horizontal margins
|
||||
this.leftMargin = LEFT_MARGIN_BASE_CHART;
|
||||
this.rightMargin = RIGHT_MARGIN_BASE_CHART;
|
||||
// Bind window events
|
||||
BOUND_DRAW_FN = this.boundDrawFn.bind(this);
|
||||
window.addEventListener('resize', BOUND_DRAW_FN);
|
||||
window.addEventListener('orientationchange', this.boundDrawFn.bind(this));
|
||||
}
|
||||
|
||||
validate() {
|
||||
return true;
|
||||
boundDrawFn() {
|
||||
this.draw(true);
|
||||
}
|
||||
|
||||
unbindWindowEvents() {
|
||||
window.removeEventListener('resize', BOUND_DRAW_FN);
|
||||
window.removeEventListener('orientationchange', this.boundDrawFn.bind(this));
|
||||
}
|
||||
|
||||
// Has to be called manually
|
||||
setup() {
|
||||
if(this.validate()) {
|
||||
this._setup();
|
||||
}
|
||||
}
|
||||
|
||||
_setup() {
|
||||
this.makeContainer();
|
||||
this.updateWidth();
|
||||
this.makeTooltip();
|
||||
|
||||
this.draw(false, true);
|
||||
}
|
||||
|
||||
setupComponents() {
|
||||
this.components = new Map();
|
||||
}
|
||||
|
||||
makeContainer() {
|
||||
this.container = $.create('div', {
|
||||
className: 'chart-container',
|
||||
innerHTML: `<h6 class="title">${this.title}</h6>
|
||||
<h6 class="sub-title uppercase">${this.subtitle}</h6>
|
||||
<div class="frappe-chart graphics"></div>
|
||||
<div class="graph-stats-container"></div>`
|
||||
});
|
||||
|
||||
// Chart needs a dedicated parent element
|
||||
this.parent.innerHTML = '';
|
||||
this.parent.appendChild(this.container);
|
||||
|
||||
this.chartWrapper = this.container.querySelector('.frappe-chart');
|
||||
this.statsWrapper = this.container.querySelector('.graph-stats-container');
|
||||
let args = {
|
||||
inside: this.parent,
|
||||
className: 'chart-container'
|
||||
};
|
||||
|
||||
if(this.independentWidth) {
|
||||
args.styles = { width: this.independentWidth + 'px' };
|
||||
}
|
||||
|
||||
this.container = $.create('div', args);
|
||||
}
|
||||
|
||||
makeTooltip() {
|
||||
this.tip = new SvgTip({
|
||||
parent: this.chartWrapper,
|
||||
parent: this.container,
|
||||
colors: this.colors
|
||||
});
|
||||
this.bindTooltip();
|
||||
@ -128,7 +139,8 @@ export default class BaseChart {
|
||||
bindTooltip() {}
|
||||
|
||||
draw(onlyWidthChange=false, init=false) {
|
||||
this.calcWidth();
|
||||
this.updateWidth();
|
||||
|
||||
this.calc(onlyWidthChange);
|
||||
this.makeChartArea();
|
||||
this.setupComponents();
|
||||
@ -139,37 +151,88 @@ export default class BaseChart {
|
||||
|
||||
if(init) {
|
||||
this.data = this.realData;
|
||||
setTimeout(() => {this.update();}, this.initTimeout);
|
||||
setTimeout(() => {this.update(this.data);}, this.initTimeout);
|
||||
}
|
||||
|
||||
if(!onlyWidthChange) {
|
||||
this.renderLegend();
|
||||
}
|
||||
this.renderLegend();
|
||||
|
||||
this.setupNavigation(init);
|
||||
}
|
||||
|
||||
calcWidth() {
|
||||
calc() {} // builds state
|
||||
|
||||
updateWidth() {
|
||||
this.baseWidth = getElementContentWidth(this.parent);
|
||||
this.width = this.baseWidth - (this.leftMargin + this.rightMargin);
|
||||
this.width = this.baseWidth - getExtraWidth(this.measures);
|
||||
}
|
||||
|
||||
update(data=this.data) {
|
||||
makeChartArea() {
|
||||
if(this.svg) {
|
||||
this.container.removeChild(this.svg);
|
||||
}
|
||||
let m = this.measures;
|
||||
|
||||
this.svg = makeSVGContainer(
|
||||
this.container,
|
||||
'frappe-chart chart',
|
||||
this.baseWidth,
|
||||
this.baseHeight
|
||||
);
|
||||
this.svgDefs = makeSVGDefs(this.svg);
|
||||
|
||||
if(this.title.length) {
|
||||
this.titleEL = makeText(
|
||||
'title',
|
||||
m.margins.left,
|
||||
m.margins.top,
|
||||
this.title,
|
||||
{
|
||||
fontSize: m.titleFontSize,
|
||||
fill: '#666666',
|
||||
dy: m.titleFontSize
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let top = getTopOffset(m);
|
||||
this.drawArea = makeSVGGroup(
|
||||
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',
|
||||
`translate(${getLeftOffset(m)}, ${top})`
|
||||
);
|
||||
}
|
||||
|
||||
if(this.title.length) { this.svg.appendChild(this.titleEL); }
|
||||
this.svg.appendChild(this.drawArea);
|
||||
if(this.config.showLegend) { this.svg.appendChild(this.legendArea); }
|
||||
|
||||
this.updateTipOffset(getLeftOffset(m), getTopOffset(m));
|
||||
}
|
||||
|
||||
updateTipOffset(x, y) {
|
||||
this.tip.offset = {
|
||||
x: x,
|
||||
y: y
|
||||
};
|
||||
}
|
||||
|
||||
setupComponents() { this.components = new Map(); }
|
||||
|
||||
update(data) {
|
||||
if(!data) {
|
||||
console.error('No data to update.');
|
||||
}
|
||||
this.data = this.prepareData(data);
|
||||
this.calc(); // builds state
|
||||
this.render();
|
||||
}
|
||||
|
||||
prepareData(data=this.data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
prepareFirstData(data=this.data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
calc() {} // builds state
|
||||
|
||||
render(components=this.components, animate=true) {
|
||||
if(this.config.isNavigable) {
|
||||
// Remove all existing overlays
|
||||
@ -182,7 +245,7 @@ export default class BaseChart {
|
||||
elementsToAnimate = elementsToAnimate.concat(c.update(animate));
|
||||
});
|
||||
if(elementsToAnimate.length > 0) {
|
||||
runSMILAnimation(this.chartWrapper, this.svg, elementsToAnimate);
|
||||
runSMILAnimation(this.container, this.svg, elementsToAnimate);
|
||||
setTimeout(() => {
|
||||
components.forEach(c => c.make());
|
||||
this.updateNav();
|
||||
@ -195,41 +258,11 @@ export default class BaseChart {
|
||||
|
||||
updateNav() {
|
||||
if(this.config.isNavigable) {
|
||||
// if(!this.overlayGuides){
|
||||
this.makeOverlay();
|
||||
this.bindUnits();
|
||||
// } else {
|
||||
// this.updateOverlay();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
makeChartArea() {
|
||||
if(this.svg) {
|
||||
this.chartWrapper.removeChild(this.svg);
|
||||
}
|
||||
this.svg = makeSVGContainer(
|
||||
this.chartWrapper,
|
||||
'chart',
|
||||
this.baseWidth,
|
||||
this.baseHeight
|
||||
);
|
||||
this.svgDefs = makeSVGDefs(this.svg);
|
||||
|
||||
// I WISH !!!
|
||||
// this.svg = makeSVGGroup(
|
||||
// svgContainer,
|
||||
// 'flipped-coord-system',
|
||||
// `translate(0, ${this.baseHeight}) scale(1, -1)`
|
||||
// );
|
||||
|
||||
this.drawArea = makeSVGGroup(
|
||||
this.svg,
|
||||
this.type + '-chart',
|
||||
`translate(${this.leftMargin}, ${this.translateY})`
|
||||
);
|
||||
}
|
||||
|
||||
renderLegend() {}
|
||||
|
||||
setupNavigation(init=false) {
|
||||
@ -247,7 +280,7 @@ export default class BaseChart {
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if(isElementInViewport(this.chartWrapper)) {
|
||||
if(isElementInViewport(this.container)) {
|
||||
e = e || window.event;
|
||||
if(this.keyActions[e.keyCode]) {
|
||||
this.keyActions[e.keyCode]();
|
||||
@ -276,7 +309,8 @@ export default class BaseChart {
|
||||
|
||||
updateDataset() {}
|
||||
|
||||
getDifferentChart(type) {
|
||||
return getDifferentChart(type, this.type, this.parent, this.rawChartArgs);
|
||||
export() {
|
||||
let chartSvg = prepareForExport(this.svg);
|
||||
downloadFile(this.title || 'Chart', [chartSvg]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,263 +1,294 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { makeSVGGroup, makeHeatSquare, makeText } from '../utils/draw';
|
||||
import { addDays, getDdMmYyyy, getWeeksBetween } from '../utils/date-utils';
|
||||
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 { isValidColor } from '../utils/colors';
|
||||
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;
|
||||
// const DAY_INCR = 1;
|
||||
|
||||
export default class Heatmap extends BaseChart {
|
||||
constructor(parent, options) {
|
||||
super(parent, options);
|
||||
|
||||
this.type = 'heatmap';
|
||||
|
||||
this.domain = options.domain || '';
|
||||
this.subdomain = options.subdomain || '';
|
||||
this.data = options.data || {};
|
||||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1;
|
||||
this.countLabel = options.countLabel || '';
|
||||
|
||||
let today = new Date();
|
||||
this.start = options.start || addDays(today, 365);
|
||||
let validStarts = ['Sunday', 'Monday'];
|
||||
let startSubDomain = validStarts.includes(options.startSubDomain)
|
||||
? options.startSubDomain : 'Sunday';
|
||||
this.startSubDomainIndex = validStarts.indexOf(startSubDomain);
|
||||
|
||||
let legendColors = (options.legendColors || []).slice(0, 5);
|
||||
this.legendColors = this.validate_colors(legendColors)
|
||||
? legendColors
|
||||
: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127'];
|
||||
|
||||
// Fixed 5-color theme,
|
||||
// More colors are difficult to parse visually
|
||||
this.distribution_size = 5;
|
||||
|
||||
this.translateX = 0;
|
||||
this.setup();
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
this.leftMargin = 10;
|
||||
this.translateY = 10;
|
||||
setMeasures(options) {
|
||||
let m = this.measures;
|
||||
this.discreteDomains = options.discreteDomains === 0 ? 0 : 1;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
validate_colors(colors) {
|
||||
if(colors.length < 5) return 0;
|
||||
|
||||
let valid = 1;
|
||||
colors.forEach(function(string) {
|
||||
if(!isValidColor(string)) {
|
||||
valid = 0;
|
||||
console.warn('"' + string + '" is not a valid color.');
|
||||
}
|
||||
}, this);
|
||||
|
||||
return valid;
|
||||
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);
|
||||
}
|
||||
|
||||
configure() {
|
||||
super.configure();
|
||||
this.today = new Date();
|
||||
|
||||
if(!this.start) {
|
||||
this.start = new Date();
|
||||
this.start.setFullYear( this.start.getFullYear() - 1 );
|
||||
prepareData(data=this.data) {
|
||||
if(data.start && data.end && data.start > data.end) {
|
||||
throw new Error('Start date cannot be greater than end date.');
|
||||
}
|
||||
this.firstWeekStart = new Date(this.start.toDateString());
|
||||
this.lastWeekStart = new Date(this.today.toDateString());
|
||||
if(this.firstWeekStart.getDay() !== 7) {
|
||||
addDays(this.firstWeekStart, (-1) * this.firstWeekStart.getDay());
|
||||
|
||||
if(!data.start) {
|
||||
data.start = new Date();
|
||||
data.start.setFullYear( data.start.getFullYear() - 1 );
|
||||
}
|
||||
if(this.lastWeekStart.getDay() !== 7) {
|
||||
addDays(this.lastWeekStart, (-1) * this.lastWeekStart.getDay());
|
||||
if(!data.end) { data.end = new Date(); }
|
||||
data.dataPoints = data.dataPoints || {};
|
||||
|
||||
if(parseInt(Object.keys(data.dataPoints)[0]) > 100000) {
|
||||
let points = {};
|
||||
Object.keys(data.dataPoints).forEach(timestampSec => {
|
||||
let date = new Date(timestampSec * NO_OF_MILLIS);
|
||||
points[getYyyyMmDd(date)] = data.dataPoints[timestampSec];
|
||||
});
|
||||
data.dataPoints = points;
|
||||
}
|
||||
this.no_of_cols = getWeeksBetween(this.firstWeekStart + '', this.lastWeekStart + '') + 1;
|
||||
}
|
||||
|
||||
calcWidth() {
|
||||
this.baseWidth = (this.no_of_cols + 3) * 12 ;
|
||||
|
||||
if(this.discreteDomains) {
|
||||
this.baseWidth += (12 * 12);
|
||||
}
|
||||
}
|
||||
|
||||
makeChartArea() {
|
||||
super.makeChartArea();
|
||||
this.domainLabelGroup = makeSVGGroup(this.drawArea,
|
||||
'domain-label-group chart-label');
|
||||
|
||||
this.dataGroups = makeSVGGroup(this.drawArea,
|
||||
'data-groups',
|
||||
`translate(0, 20)`
|
||||
);
|
||||
|
||||
this.container.querySelector('.title').style.display = 'None';
|
||||
this.container.querySelector('.sub-title').style.display = 'None';
|
||||
this.container.querySelector('.graph-stats-container').style.display = 'None';
|
||||
this.chartWrapper.style.marginTop = '0px';
|
||||
this.chartWrapper.style.paddingTop = '0px';
|
||||
return data;
|
||||
}
|
||||
|
||||
calc() {
|
||||
let s = this.state;
|
||||
|
||||
let dataValues = Object.keys(this.data).map(key => this.data[key]);
|
||||
this.distribution = calcDistribution(dataValues, this.distribution_size);
|
||||
s.start = clone(this.data.start);
|
||||
s.end = clone(this.data.end);
|
||||
|
||||
this.monthNames = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
s.firstWeekStart = clone(s.start);
|
||||
s.noOfWeeks = getWeeksBetween(s.start, s.end);
|
||||
s.distribution = calcDistribution(
|
||||
Object.values(this.data.dataPoints), HEATMAP_DISTRIBUTION_SIZE);
|
||||
|
||||
s.domainConfigs = this.getDomains();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.renderAllWeeksAndStoreXValues(this.no_of_cols);
|
||||
}
|
||||
setupComponents() {
|
||||
let s = this.state;
|
||||
let lessCol = this.discreteDomains ? 0 : 1;
|
||||
|
||||
renderAllWeeksAndStoreXValues(no_of_weeks) {
|
||||
// renderAllWeeksAndStoreXValues
|
||||
this.domainLabelGroup.textContent = '';
|
||||
this.dataGroups.textContent = '';
|
||||
let componentConfigs = s.domainConfigs.map((config, i) => [
|
||||
'heatDomain',
|
||||
{
|
||||
index: config.index,
|
||||
colWidth: COL_WIDTH,
|
||||
rowHeight: ROW_HEIGHT,
|
||||
squareSize: HEATMAP_SQUARE_SIZE,
|
||||
xTranslate: s.domainConfigs
|
||||
.filter((config, j) => j < i)
|
||||
.map(config => config.cols.length - lessCol)
|
||||
.reduce((a, b) => a + b, 0)
|
||||
* COL_WIDTH
|
||||
},
|
||||
function() {
|
||||
return s.domainConfigs[i];
|
||||
}.bind(this)
|
||||
|
||||
let currentWeekSunday = new Date(this.firstWeekStart);
|
||||
this.weekCol = 0;
|
||||
this.currentMonth = currentWeekSunday.getMonth();
|
||||
]);
|
||||
|
||||
this.months = [this.currentMonth + ''];
|
||||
this.monthWeeks = {}, this.monthStartPoints = [];
|
||||
this.monthWeeks[this.currentMonth] = 0;
|
||||
this.monthStartPoints.push(13);
|
||||
this.components = new Map(componentConfigs
|
||||
.map((args, i) => {
|
||||
let component = getComponent(...args);
|
||||
return [args[0] + '-' + i, component];
|
||||
})
|
||||
);
|
||||
|
||||
for(var i = 0; i < no_of_weeks; i++) {
|
||||
let dataGroup, monthChange = 0;
|
||||
let day = new Date(currentWeekSunday);
|
||||
|
||||
[dataGroup, monthChange] = this.get_week_squares_group(day, this.weekCol);
|
||||
this.dataGroups.appendChild(dataGroup);
|
||||
this.weekCol += 1 + parseInt(this.discreteDomains && monthChange);
|
||||
this.monthWeeks[this.currentMonth]++;
|
||||
if(monthChange) {
|
||||
this.currentMonth = (this.currentMonth + 1) % 12;
|
||||
this.months.push(this.currentMonth + '');
|
||||
this.monthWeeks[this.currentMonth] = 1;
|
||||
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,
|
||||
{
|
||||
fontSize: HEATMAP_SQUARE_SIZE,
|
||||
dy: 8,
|
||||
textAnchor: 'end'
|
||||
}
|
||||
);
|
||||
this.drawArea.appendChild(dayText);
|
||||
}
|
||||
addDays(currentWeekSunday, 7);
|
||||
}
|
||||
this.render_month_labels();
|
||||
}
|
||||
|
||||
get_week_squares_group(currentDate, index) {
|
||||
const noOfWeekdays = 7;
|
||||
const squareSide = 10;
|
||||
const cellPadding = 2;
|
||||
const step = 1;
|
||||
const todayTime = this.today.getTime();
|
||||
|
||||
let monthChange = 0;
|
||||
let weekColChange = 0;
|
||||
|
||||
let dataGroup = makeSVGGroup(this.dataGroups, 'data-group');
|
||||
|
||||
for(var y = 0, i = 0; i < noOfWeekdays; i += step, y += (squareSide + cellPadding)) {
|
||||
let dataValue = 0;
|
||||
let colorIndex = 0;
|
||||
|
||||
let currentTimestamp = currentDate.getTime()/1000;
|
||||
let timestamp = Math.floor(currentTimestamp - (currentTimestamp % 86400)).toFixed(1);
|
||||
|
||||
if(this.data[timestamp]) {
|
||||
dataValue = this.data[timestamp];
|
||||
}
|
||||
|
||||
if(this.data[Math.round(timestamp)]) {
|
||||
dataValue = this.data[Math.round(timestamp)];
|
||||
}
|
||||
|
||||
if(dataValue) {
|
||||
colorIndex = getMaxCheckpoint(dataValue, this.distribution);
|
||||
}
|
||||
|
||||
let x = 13 + (index + weekColChange) * 12;
|
||||
|
||||
let dataAttr = {
|
||||
'data-date': getDdMmYyyy(currentDate),
|
||||
'data-value': dataValue,
|
||||
'data-day': currentDate.getDay()
|
||||
};
|
||||
|
||||
let heatSquare = makeHeatSquare('day', x, y, squareSide,
|
||||
this.legendColors[colorIndex], dataAttr);
|
||||
|
||||
dataGroup.appendChild(heatSquare);
|
||||
|
||||
let nextDate = new Date(currentDate);
|
||||
addDays(nextDate, 1);
|
||||
if(nextDate.getTime() > todayTime) break;
|
||||
|
||||
|
||||
if(nextDate.getMonth() - currentDate.getMonth()) {
|
||||
monthChange = 1;
|
||||
if(this.discreteDomains) {
|
||||
weekColChange = 1;
|
||||
}
|
||||
|
||||
this.monthStartPoints.push(13 + (index + weekColChange) * 12);
|
||||
}
|
||||
currentDate = nextDate;
|
||||
}
|
||||
|
||||
return [dataGroup, monthChange];
|
||||
}
|
||||
|
||||
render_month_labels() {
|
||||
// this.first_month_label = 1;
|
||||
// if (this.firstWeekStart.getDate() > 8) {
|
||||
// this.first_month_label = 0;
|
||||
// }
|
||||
// this.last_month_label = 1;
|
||||
|
||||
// let first_month = this.months.shift();
|
||||
// let first_month_start = this.monthStartPoints.shift();
|
||||
// render first month if
|
||||
|
||||
// let last_month = this.months.pop();
|
||||
// let last_month_start = this.monthStartPoints.pop();
|
||||
// render last month if
|
||||
|
||||
this.months.shift();
|
||||
this.monthStartPoints.shift();
|
||||
this.months.pop();
|
||||
this.monthStartPoints.pop();
|
||||
|
||||
this.monthStartPoints.map((start, i) => {
|
||||
let month_name = this.monthNames[this.months[i]].substring(0, 3);
|
||||
let text = makeText('y-value-text', start+12, 10, month_name);
|
||||
this.domainLabelGroup.appendChild(text);
|
||||
});
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll(".data-group .day")
|
||||
).map(el => {
|
||||
el.addEventListener('mouseenter', (e) => {
|
||||
let count = e.target.getAttribute('data-value');
|
||||
let dateParts = e.target.getAttribute('data-date').split('-');
|
||||
|
||||
let month = this.monthNames[parseInt(dateParts[1])-1].substring(0, 3);
|
||||
|
||||
let gOff = this.chartWrapper.getBoundingClientRect(), pOff = e.target.getBoundingClientRect();
|
||||
|
||||
let width = parseInt(e.target.getAttribute('width'));
|
||||
let x = pOff.left - gOff.left + (width+2)/2;
|
||||
let y = pOff.top - gOff.top - (width+2)/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.showTip();
|
||||
});
|
||||
y += ROW_HEIGHT;
|
||||
});
|
||||
}
|
||||
|
||||
update(data) {
|
||||
super.update(data);
|
||||
if(!data) {
|
||||
console.error('No data to update.');
|
||||
}
|
||||
|
||||
this.data = this.prepareData(data);
|
||||
this.draw();
|
||||
this.bindTooltip();
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
this.container.addEventListener('mousemove', (e) => {
|
||||
this.components.forEach(comp => {
|
||||
let daySquares = comp.store;
|
||||
let daySquare = e.target;
|
||||
if(daySquares.includes(daySquare)) {
|
||||
|
||||
let count = daySquare.getAttribute('data-value');
|
||||
let dateParts = daySquare.getAttribute('data-date').split('-');
|
||||
|
||||
let month = getMonthName(parseInt(dateParts[1])-1, true);
|
||||
|
||||
let gOff = this.container.getBoundingClientRect(), pOff = daySquare.getBoundingClientRect();
|
||||
|
||||
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];
|
||||
|
||||
this.tip.setValues(x, y, {name: name, value: value, valueFirst: 1}, []);
|
||||
this.tip.showTip();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
this.legendArea.textContent = '';
|
||||
let x = 0;
|
||||
let y = ROW_HEIGHT;
|
||||
|
||||
let lessText = makeText('subdomain-name', x, y, 'Less',
|
||||
{
|
||||
fontSize: HEATMAP_SQUARE_SIZE + 1,
|
||||
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, 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',
|
||||
{
|
||||
fontSize: HEATMAP_SQUARE_SIZE + 1,
|
||||
dy: 9
|
||||
}
|
||||
);
|
||||
this.legendArea.appendChild(moreText);
|
||||
}
|
||||
|
||||
getDomains() {
|
||||
let s = this.state;
|
||||
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;
|
||||
|
||||
let domainConfigs = [];
|
||||
|
||||
let startOfMonth = clone(s.start);
|
||||
for(var i = 0; i < noOfMonths; i++) {
|
||||
let endDate = s.end;
|
||||
if(!areInSameMonth(startOfMonth, s.end)) {
|
||||
let [month, year] = [startOfMonth.getMonth(), startOfMonth.getFullYear()];
|
||||
endDate = getLastDateInMonth(month, year);
|
||||
}
|
||||
domainConfigs.push(this.getDomainConfig(startOfMonth, endDate));
|
||||
|
||||
addDays(endDate, 1);
|
||||
startOfMonth = endDate;
|
||||
}
|
||||
|
||||
return domainConfigs;
|
||||
}
|
||||
|
||||
getDomainConfig(startDate, endDate='') {
|
||||
let [month, year] = [startDate.getMonth(), startDate.getFullYear()];
|
||||
let startOfWeek = setDayToSunday(startDate); // TODO: Monday as well
|
||||
endDate = clone(endDate) || getLastDateInMonth(month, year);
|
||||
|
||||
let domainConfig = {
|
||||
index: month,
|
||||
cols: []
|
||||
};
|
||||
|
||||
addDays(endDate, 1);
|
||||
let noOfMonthWeeks = getWeeksBetween(startOfWeek, endDate);
|
||||
|
||||
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);
|
||||
addDays(startOfWeek, 1);
|
||||
}
|
||||
|
||||
if(col[NO_OF_DAYS_IN_WEEK - 1].dataValue !== undefined) {
|
||||
addDays(startOfWeek, 1);
|
||||
cols.push(this.getCol(startOfWeek, month, true));
|
||||
}
|
||||
|
||||
domainConfig.cols = cols;
|
||||
|
||||
return domainConfig;
|
||||
}
|
||||
|
||||
getCol(startDate, month, empty = false) {
|
||||
let s = this.state;
|
||||
|
||||
// startDate is the start of week
|
||||
let currentDate = clone(startDate);
|
||||
let col = [];
|
||||
|
||||
for(var i = 0; i < NO_OF_DAYS_IN_WEEK; i++, addDays(currentDate, 1)) {
|
||||
let config = {};
|
||||
|
||||
// Non-generic adjustment for entire heatmap, needs state
|
||||
let currentDateWithinData = currentDate >= s.start && currentDate <= s.end;
|
||||
|
||||
if(empty || currentDate.getMonth() !== month || !currentDateWithinData) {
|
||||
config.yyyyMmDd = getYyyyMmDd(currentDate);
|
||||
} else {
|
||||
config = this.getSubDomainConfig(currentDate);
|
||||
}
|
||||
col.push(config);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
getSubDomainConfig(date) {
|
||||
let yyyyMmDd = getYyyyMmDd(date);
|
||||
let dataValue = this.data.dataPoints[yyyyMmDd];
|
||||
let config = {
|
||||
yyyyMmDd: yyyyMmDd,
|
||||
dataValue: dataValue || 0,
|
||||
fill: this.colors[getMaxCheckpoint(dataValue, this.state.distribution)]
|
||||
};
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,11 +14,11 @@ export default class MultiAxisChart extends AxisChart {
|
||||
this.type = 'multiaxis';
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
setMeasures() {
|
||||
super.setMeasures();
|
||||
let noOfLeftAxes = this.data.datasets.filter(d => d.axisPosition === 'left').length;
|
||||
this.leftMargin = (noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
||||
this.rightMargin = (this.data.datasets.length - noOfLeftAxes) * Y_AXIS_MARGIN || Y_AXIS_MARGIN;
|
||||
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() { }
|
||||
|
||||
@ -1,73 +1,90 @@
|
||||
import AggregationChart from './AggregationChart';
|
||||
import { $, getOffset } from '../utils/dom';
|
||||
import { getOffset } from '../utils/dom';
|
||||
import { getComponent } from '../objects/ChartComponents';
|
||||
import { PERCENTAGE_BAR_DEFAULT_HEIGHT, PERCENTAGE_BAR_DEFAULT_DEPTH } from '../utils/constants';
|
||||
|
||||
export default class PercentageChart extends AggregationChart {
|
||||
constructor(parent, args) {
|
||||
super(parent, args);
|
||||
this.type = 'percentage';
|
||||
|
||||
this.setup();
|
||||
}
|
||||
|
||||
makeChartArea() {
|
||||
this.chartWrapper.className += ' ' + 'graph-focus-margin';
|
||||
this.chartWrapper.style.marginTop = '45px';
|
||||
setMeasures(options) {
|
||||
let m = this.measures;
|
||||
this.barOptions = options.barOptions || {};
|
||||
|
||||
this.statsWrapper.className += ' ' + 'graph-focus-margin';
|
||||
this.statsWrapper.style.marginBottom = '30px';
|
||||
this.statsWrapper.style.paddingTop = '0px';
|
||||
let b = this.barOptions;
|
||||
b.height = b.height || PERCENTAGE_BAR_DEFAULT_HEIGHT;
|
||||
b.depth = b.depth || PERCENTAGE_BAR_DEFAULT_DEPTH;
|
||||
|
||||
this.svg = $.create('div', {
|
||||
className: 'div',
|
||||
inside: this.chartWrapper
|
||||
});
|
||||
|
||||
this.chart = $.create('div', {
|
||||
className: 'progress-chart',
|
||||
inside: this.svg
|
||||
});
|
||||
|
||||
this.percentageBar = $.create('div', {
|
||||
className: 'progress',
|
||||
inside: this.chart
|
||||
});
|
||||
m.paddings.right = 30;
|
||||
m.legendHeight = 80;
|
||||
m.baseHeight = (b.height + b.depth * 0.5) * 8;
|
||||
}
|
||||
|
||||
render() {
|
||||
setupComponents() {
|
||||
let s = this.state;
|
||||
this.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0);
|
||||
s.slices = [];
|
||||
s.sliceTotals.map((total, i) => {
|
||||
let slice = $.create('div', {
|
||||
className: `progress-bar`,
|
||||
'data-index': i,
|
||||
inside: this.percentageBar,
|
||||
styles: {
|
||||
background: this.colors[i],
|
||||
width: total*100/this.grandTotal + "%"
|
||||
}
|
||||
});
|
||||
s.slices.push(slice);
|
||||
|
||||
let componentConfigs = [
|
||||
[
|
||||
'percentageBars',
|
||||
{
|
||||
barHeight: this.barOptions.height,
|
||||
barDepth: this.barOptions.depth,
|
||||
},
|
||||
function() {
|
||||
return {
|
||||
xPositions: s.xPositions,
|
||||
widths: s.widths,
|
||||
colors: this.colors
|
||||
};
|
||||
}.bind(this)
|
||||
]
|
||||
];
|
||||
|
||||
this.components = new Map(componentConfigs
|
||||
.map(args => {
|
||||
let component = getComponent(...args);
|
||||
return [args[0], component];
|
||||
}));
|
||||
}
|
||||
|
||||
calc() {
|
||||
super.calc();
|
||||
let s = this.state;
|
||||
|
||||
s.xPositions = [];
|
||||
s.widths = [];
|
||||
|
||||
let xPos = 0;
|
||||
s.sliceTotals.map((value) => {
|
||||
let width = this.width * value / s.grandTotal;
|
||||
s.widths.push(width);
|
||||
s.xPositions.push(xPos);
|
||||
xPos += width;
|
||||
});
|
||||
}
|
||||
|
||||
makeDataByIndex() { }
|
||||
|
||||
bindTooltip() {
|
||||
let s = this.state;
|
||||
this.container.addEventListener('mousemove', (e) => {
|
||||
let bars = this.components.get('percentageBars').store;
|
||||
let bar = e.target;
|
||||
if(bars.includes(bar)) {
|
||||
|
||||
this.chartWrapper.addEventListener('mousemove', (e) => {
|
||||
let slice = e.target;
|
||||
if(slice.classList.contains('progress-bar')) {
|
||||
let i = bars.indexOf(bar);
|
||||
let gOff = getOffset(this.container), pOff = getOffset(bar);
|
||||
|
||||
let i = slice.getAttribute('data-index');
|
||||
let gOff = getOffset(this.chartWrapper), pOff = getOffset(slice);
|
||||
|
||||
let x = pOff.left - gOff.left + slice.offsetWidth/2;
|
||||
let y = pOff.top - gOff.top - 6;
|
||||
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 percent = (s.sliceTotals[i]*100/this.grandTotal).toFixed(1);
|
||||
let fraction = s.sliceTotals[i]/s.grandTotal;
|
||||
|
||||
this.tip.setValues(x, y, {name: title, value: percent + "%"});
|
||||
this.tip.setValues(x, y, {name: title, value: (fraction*100).toFixed(1) + "%"});
|
||||
this.tip.showTip();
|
||||
}
|
||||
});
|
||||
|
||||
@ -12,6 +12,7 @@ export default class PieChart extends AggregationChart {
|
||||
super(parent, args);
|
||||
this.type = 'pie';
|
||||
this.initTimeout = 0;
|
||||
this.init = 1;
|
||||
|
||||
this.setup();
|
||||
}
|
||||
@ -27,28 +28,11 @@ export default class PieChart extends AggregationChart {
|
||||
this.clockWise = args.clockWise || false;
|
||||
}
|
||||
|
||||
prepareFirstData(data=this.data) {
|
||||
this.init = 1;
|
||||
return data;
|
||||
}
|
||||
|
||||
calc() {
|
||||
super.calc();
|
||||
let s = this.state;
|
||||
|
||||
this.center = {
|
||||
x: this.width / 2,
|
||||
y: this.height / 2
|
||||
};
|
||||
this.radius = (this.height > this.width ? this.center.x : this.center.y);
|
||||
|
||||
s.grandTotal = s.sliceTotals.reduce((a, b) => a + b, 0);
|
||||
|
||||
this.calcSlices();
|
||||
}
|
||||
|
||||
calcSlices() {
|
||||
let s = this.state;
|
||||
const { radius, clockWise } = this;
|
||||
|
||||
const prevSlicesProperties = s.slicesProperties || [];
|
||||
@ -142,8 +126,8 @@ export default class PieChart extends AggregationChart {
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
this.chartWrapper.addEventListener('mousemove', this.mouseMove);
|
||||
this.chartWrapper.addEventListener('mouseleave', this.mouseLeave);
|
||||
this.container.addEventListener('mousemove', this.mouseMove);
|
||||
this.container.addEventListener('mouseleave', this.mouseLeave);
|
||||
}
|
||||
|
||||
mouseMove(e){
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
import Chart from './chart';
|
||||
|
||||
const ALL_CHART_TYPES = ['line', 'scatter', 'bar', 'percentage', 'heatmap', 'pie'];
|
||||
|
||||
const COMPATIBLE_CHARTS = {
|
||||
bar: ['line', 'scatter', 'percentage', 'pie'],
|
||||
line: ['scatter', 'bar', 'percentage', 'pie'],
|
||||
pie: ['line', 'scatter', 'percentage', 'bar'],
|
||||
scatter: ['line', 'bar', 'percentage', 'pie'],
|
||||
percentage: ['bar', 'line', 'scatter', 'pie'],
|
||||
heatmap: []
|
||||
};
|
||||
|
||||
// Needs structure as per only labels/datasets
|
||||
const COLOR_COMPATIBLE_CHARTS = {
|
||||
bar: ['line', 'scatter'],
|
||||
line: ['scatter', 'bar'],
|
||||
pie: ['percentage'],
|
||||
scatter: ['line', 'bar'],
|
||||
percentage: ['pie'],
|
||||
heatmap: []
|
||||
};
|
||||
|
||||
export function getDifferentChart(type, current_type, parent, args) {
|
||||
if(type === current_type) return;
|
||||
|
||||
if(!ALL_CHART_TYPES.includes(type)) {
|
||||
console.error(`'${type}' is not a valid chart type.`);
|
||||
}
|
||||
|
||||
if(!COMPATIBLE_CHARTS[current_type].includes(type)) {
|
||||
console.error(`'${current_type}' chart cannot be converted to a '${type}' chart.`);
|
||||
}
|
||||
|
||||
// whether the new chart can use the existing colors
|
||||
const useColor = COLOR_COMPATIBLE_CHARTS[current_type].includes(type);
|
||||
|
||||
// Okay, this is anticlimactic
|
||||
// this function will need to actually be 'changeChartType(type)'
|
||||
// that will update only the required elements, but for now ...
|
||||
|
||||
args.type = type;
|
||||
args.colors = useColor ? args.colors : undefined;
|
||||
|
||||
return new Chart(parent, args);
|
||||
}
|
||||
10
src/js/index.js
Normal file
10
src/js/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
import * as Charts from './chart';
|
||||
|
||||
let frappe = { };
|
||||
|
||||
frappe.NAME = 'Frappe Charts';
|
||||
frappe.VERSION = '1.1.0';
|
||||
|
||||
frappe = Object.assign({ }, frappe, Charts);
|
||||
|
||||
export default frappe;
|
||||
@ -1,8 +1,9 @@
|
||||
import { makeSVGGroup } from '../utils/draw';
|
||||
import { makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, getPaths } 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';
|
||||
|
||||
class ChartComponent {
|
||||
constructor({
|
||||
@ -23,6 +24,7 @@ class ChartComponent {
|
||||
this.animateElements = animateElements;
|
||||
|
||||
this.store = [];
|
||||
this.labels = [];
|
||||
|
||||
this.layerClass = layerClass;
|
||||
this.layerClass = typeof(this.layerClass) === 'function'
|
||||
@ -36,7 +38,7 @@ class ChartComponent {
|
||||
}
|
||||
|
||||
setup(parent) {
|
||||
this.layer = makeSVGGroup(parent, this.layerClass, this.layerTransform);
|
||||
this.layer = makeSVGGroup(this.layerClass, this.layerTransform, parent);
|
||||
}
|
||||
|
||||
make() {
|
||||
@ -51,13 +53,16 @@ class ChartComponent {
|
||||
this.store.forEach(element => {
|
||||
this.layer.appendChild(element);
|
||||
});
|
||||
this.labels.forEach(element => {
|
||||
this.layer.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
update(animate = true) {
|
||||
this.refresh();
|
||||
let animateElements = [];
|
||||
if(animate) {
|
||||
animateElements = this.animateElements(this.data);
|
||||
animateElements = this.animateElements(this.data) || [];
|
||||
}
|
||||
return animateElements;
|
||||
}
|
||||
@ -80,6 +85,21 @@ let componentConfigs = {
|
||||
);
|
||||
}
|
||||
},
|
||||
percentageBars: {
|
||||
layerClass: 'percentage-bars',
|
||||
makeElements(data) {
|
||||
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]);
|
||||
return bar;
|
||||
});
|
||||
},
|
||||
|
||||
animateElements(newData) {
|
||||
if(newData) return [];
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
layerClass: 'y axis',
|
||||
makeElements(data) {
|
||||
@ -145,9 +165,9 @@ let componentConfigs = {
|
||||
yMarkers: {
|
||||
layerClass: 'y-markers',
|
||||
makeElements(data) {
|
||||
return data.map(marker =>
|
||||
yMarker(marker.position, marker.label, this.constants.width,
|
||||
{pos:'right', mode: 'span', lineType: 'dashed'})
|
||||
return data.map(m =>
|
||||
yMarker(m.position, m.label, this.constants.width,
|
||||
{labelPos: m.options.labelPos, mode: 'span', lineType: 'dashed'})
|
||||
);
|
||||
},
|
||||
animateElements(newData) {
|
||||
@ -155,13 +175,15 @@ let componentConfigs = {
|
||||
|
||||
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);
|
||||
|
||||
this.render(oldPos.map((pos, i) => {
|
||||
return {
|
||||
position: oldPos[i],
|
||||
label: newLabels[i]
|
||||
label: newLabels[i],
|
||||
options: newOptions[i]
|
||||
};
|
||||
}));
|
||||
|
||||
@ -176,9 +198,9 @@ let componentConfigs = {
|
||||
yRegions: {
|
||||
layerClass: 'y-regions',
|
||||
makeElements(data) {
|
||||
return data.map(region =>
|
||||
yRegion(region.startPos, region.endPos, this.constants.width,
|
||||
region.label)
|
||||
return data.map(r =>
|
||||
yRegion(r.startPos, r.endPos, this.constants.width,
|
||||
r.label, {labelPos: r.options.labelPos})
|
||||
);
|
||||
},
|
||||
animateElements(newData) {
|
||||
@ -187,6 +209,7 @@ let componentConfigs = {
|
||||
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);
|
||||
@ -195,7 +218,8 @@ let componentConfigs = {
|
||||
return {
|
||||
startPos: oldStarts[i],
|
||||
endPos: oldPos[i],
|
||||
label: newLabels[i]
|
||||
label: newLabels[i],
|
||||
options: newOptions[i]
|
||||
};
|
||||
}));
|
||||
|
||||
@ -211,6 +235,49 @@ let componentConfigs = {
|
||||
}
|
||||
},
|
||||
|
||||
heatDomain: {
|
||||
layerClass: function() { return 'heat-domain domain-' + this.constants.index; },
|
||||
makeElements(data) {
|
||||
let {index, colWidth, rowHeight, squareSize, xTranslate} = this.constants;
|
||||
let monthNameHeight = -12;
|
||||
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(),
|
||||
{
|
||||
fontSize: 9
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
week.map((day, i) => {
|
||||
if(day.fill) {
|
||||
let data = {
|
||||
'data-date': day.yyyyMmDd,
|
||||
'data-value': day.dataValue,
|
||||
'data-day': i
|
||||
};
|
||||
let square = heatSquare('day', x, y, squareSize, day.fill, data);
|
||||
this.serializedSubDomains.push(square);
|
||||
}
|
||||
y += rowHeight;
|
||||
});
|
||||
y = 0;
|
||||
x += colWidth;
|
||||
});
|
||||
|
||||
return this.serializedSubDomains;
|
||||
},
|
||||
|
||||
animateElements(newData) {
|
||||
if(newData) return [];
|
||||
}
|
||||
},
|
||||
|
||||
barGraph: {
|
||||
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; },
|
||||
makeElements(data) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { $ } from '../utils/dom';
|
||||
import { TOOLTIP_POINTER_TRIANGLE_HEIGHT } from '../utils/constants';
|
||||
|
||||
export default class SvgTip {
|
||||
constructor({
|
||||
@ -28,7 +29,6 @@ export default class SvgTip {
|
||||
refresh() {
|
||||
this.fill();
|
||||
this.calcPosition();
|
||||
// this.showTip();
|
||||
}
|
||||
|
||||
makeTooltip() {
|
||||
@ -64,12 +64,13 @@ export default class SvgTip {
|
||||
|
||||
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', {
|
||||
styles: {
|
||||
'border-top': `3px solid ${color}`
|
||||
},
|
||||
innerHTML: `<strong style="display: block;">${ set.value === 0 || set.value ? set.value : '' }</strong>
|
||||
innerHTML: `<strong style="display: block;">${ value === 0 || value ? value : '' }</strong>
|
||||
${set.title ? set.title : '' }`
|
||||
});
|
||||
|
||||
@ -80,7 +81,8 @@ export default class SvgTip {
|
||||
calcPosition() {
|
||||
let width = this.container.offsetWidth;
|
||||
|
||||
this.top = this.y - this.container.offsetHeight;
|
||||
this.top = this.y - this.container.offsetHeight
|
||||
- TOOLTIP_POINTER_TRIANGLE_HEIGHT;
|
||||
this.left = this.x - width/2;
|
||||
let maxLeft = this.parent.offsetWidth - width;
|
||||
|
||||
|
||||
@ -102,4 +102,3 @@ export function animatePath(paths, newXList, newYList, zeroLine) {
|
||||
export function animatePathStr(oldPath, pathStr) {
|
||||
return [oldPath, {d: pathStr}, UNIT_ANIM_DUR, STD_EASING];
|
||||
}
|
||||
|
||||
|
||||
@ -98,6 +98,7 @@ export function zeroDataPrep(realData) {
|
||||
|
||||
export function getShortenedLabels(chartWidth, labels=[], isSeries=true) {
|
||||
let allowedSpace = chartWidth / labels.length;
|
||||
if(allowedSpace <= 0) allowedSpace = 1;
|
||||
let allowedLetters = allowedSpace / DEFAULT_CHAR_WIDTH;
|
||||
|
||||
let calcLabels = labels.map((label, i) => {
|
||||
@ -121,4 +122,4 @@ export function getShortenedLabels(chartWidth, labels=[], isSeries=true) {
|
||||
});
|
||||
|
||||
return calcLabels;
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,9 +15,6 @@ const PRESET_COLOR_MAP = {
|
||||
'dark-grey': '#b8c2cc'
|
||||
};
|
||||
|
||||
export const DEFAULT_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange',
|
||||
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey'];
|
||||
|
||||
function limitColor(r){
|
||||
if (r > 255) return 255;
|
||||
else if (r < 0) return 0;
|
||||
|
||||
@ -1,8 +1,63 @@
|
||||
export const VERT_SPACE_OUTSIDE_BASE_CHART = 50;
|
||||
export const TRANSLATE_Y_BASE_CHART = 20;
|
||||
export const LEFT_MARGIN_BASE_CHART = 60;
|
||||
export const RIGHT_MARGIN_BASE_CHART = 40;
|
||||
export const Y_AXIS_MARGIN = 60;
|
||||
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: []
|
||||
};
|
||||
|
||||
export const DATA_COLOR_DIVISIONS = {
|
||||
bar: 'datasets',
|
||||
line: 'datasets',
|
||||
pie: 'labels',
|
||||
percentage: 'labels',
|
||||
heatmap: HEATMAP_DISTRIBUTION_SIZE
|
||||
};
|
||||
|
||||
export const BASE_MEASURES = {
|
||||
margins: {
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 20,
|
||||
right: 20
|
||||
},
|
||||
paddings: {
|
||||
top: 20,
|
||||
bottom: 40,
|
||||
left: 30,
|
||||
right: 10
|
||||
},
|
||||
|
||||
baseHeight: 240,
|
||||
titleHeight: 20,
|
||||
legendHeight: 30,
|
||||
|
||||
titleFontSize: 12,
|
||||
};
|
||||
|
||||
export function getTopOffset(m) {
|
||||
return m.titleHeight + m.margins.top + m.paddings.top;
|
||||
}
|
||||
|
||||
export function getLeftOffset(m) {
|
||||
return m.margins.left + m.paddings.left;
|
||||
}
|
||||
|
||||
export function getExtraHeight(m) {
|
||||
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;
|
||||
|
||||
return totalExtraWidth;
|
||||
}
|
||||
|
||||
export const INIT_CHART_UPDATE_TIMEOUT = 700;
|
||||
export const CHART_POST_ANIMATE_TIMEOUT = 400;
|
||||
@ -10,14 +65,42 @@ export const CHART_POST_ANIMATE_TIMEOUT = 400;
|
||||
export const DEFAULT_AXIS_CHART_TYPE = 'line';
|
||||
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
||||
|
||||
export const AXIS_LEGEND_BAR_SIZE = 100;
|
||||
|
||||
export const BAR_CHART_SPACE_RATIO = 0.5;
|
||||
export const MIN_BAR_PERCENT_HEIGHT = 0.01;
|
||||
|
||||
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;
|
||||
|
||||
// Fixed 5-color theme,
|
||||
// More colors are difficult to parse visually
|
||||
export const HEATMAP_DISTRIBUTION_SIZE = 5;
|
||||
|
||||
export const HEATMAP_SQUARE_SIZE = 10;
|
||||
export const HEATMAP_GUTTER_SIZE = 2;
|
||||
|
||||
export const DEFAULT_CHAR_WIDTH = 7;
|
||||
|
||||
export const TOOLTIP_POINTER_TRIANGLE_HEIGHT = 5;
|
||||
|
||||
const DEFAULT_CHART_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange',
|
||||
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey'];
|
||||
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,
|
||||
line: DEFAULT_CHART_COLORS,
|
||||
pie: DEFAULT_CHART_COLORS,
|
||||
percentage: DEFAULT_CHART_COLORS,
|
||||
heatmap: HEATMAP_COLORS_GREEN
|
||||
};
|
||||
|
||||
// Universal constants
|
||||
export const ANGLE_RATIO = Math.PI / 180;
|
||||
export const FULL_ANGLE = 360;
|
||||
export const FULL_ANGLE = 360;
|
||||
|
||||
@ -1,39 +1,90 @@
|
||||
// Playing around with dates
|
||||
|
||||
export const NO_OF_YEAR_MONTHS = 12;
|
||||
export const NO_OF_DAYS_IN_WEEK = 7;
|
||||
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 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(dateStr) {
|
||||
let result = new Date(dateStr);
|
||||
function treatAsUtc(date) {
|
||||
let result = new Date(date);
|
||||
result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getDdMmYyyy(date) {
|
||||
export function getYyyyMmDd(date) {
|
||||
let dd = date.getDate();
|
||||
let mm = date.getMonth() + 1; // getMonth() is zero-based
|
||||
return [
|
||||
(dd>9 ? '' : '0') + dd,
|
||||
date.getFullYear(),
|
||||
(mm>9 ? '' : '0') + mm,
|
||||
date.getFullYear()
|
||||
(dd>9 ? '' : '0') + dd
|
||||
].join('-');
|
||||
}
|
||||
|
||||
export function getWeeksBetween(startDateStr, endDateStr) {
|
||||
return Math.ceil(getDaysBetween(startDateStr, endDateStr) / 7);
|
||||
export function clone(date) {
|
||||
return new Date(date.getTime());
|
||||
}
|
||||
|
||||
export function getDaysBetween(startDateStr, endDateStr) {
|
||||
let millisecondsPerDay = 24 * 60 * 60 * 1000;
|
||||
return (treatAsUtc(endDateStr) - treatAsUtc(startDateStr)) / millisecondsPerDay;
|
||||
export function timestampSec(date) {
|
||||
return date.getTime()/NO_OF_MILLIS;
|
||||
}
|
||||
|
||||
export function timestampToMidnight(timestamp, roundAhead = false) {
|
||||
let midnightTs = Math.floor(timestamp - (timestamp % SEC_IN_DAY));
|
||||
if(roundAhead) {
|
||||
return midnightTs + SEC_IN_DAY;
|
||||
}
|
||||
return midnightTs;
|
||||
}
|
||||
|
||||
// export function getMonthsBetween(startDate, endDate) {}
|
||||
|
||||
export function getWeeksBetween(startDate, endDate) {
|
||||
let weekStartDate = setDayToSunday(startDate);
|
||||
return Math.ceil(getDaysBetween(weekStartDate, endDate) / NO_OF_DAYS_IN_WEEK);
|
||||
}
|
||||
|
||||
export function getDaysBetween(startDate, endDate) {
|
||||
let millisecondsPerDay = SEC_IN_DAY * NO_OF_MILLIS;
|
||||
return (treatAsUtc(endDate) - treatAsUtc(startDate)) / millisecondsPerDay;
|
||||
}
|
||||
|
||||
export function areInSameMonth(startDate, endDate) {
|
||||
return startDate.getMonth() === endDate.getMonth()
|
||||
&& startDate.getFullYear() === endDate.getFullYear();
|
||||
}
|
||||
|
||||
export function getMonthName(i, short=false) {
|
||||
let monthName = MONTH_NAMES[i];
|
||||
return short ? monthName.slice(0, 3) : monthName;
|
||||
}
|
||||
|
||||
export function getLastDateInMonth (month, year) {
|
||||
return new Date(year, month + 1, 0); // 0: last day in previous month
|
||||
}
|
||||
|
||||
// mutates
|
||||
export function setDayToSunday(date) {
|
||||
let newDate = clone(date);
|
||||
const day = newDate.getDay();
|
||||
if(day !== 0) {
|
||||
addDays(newDate, (-1) * day);
|
||||
}
|
||||
return newDate;
|
||||
}
|
||||
|
||||
// mutates
|
||||
export function addDays(date, numberOfDays) {
|
||||
date.setDate(date.getDate() + numberOfDays);
|
||||
}
|
||||
|
||||
export function getMonthName(i) {
|
||||
let monthNames = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
return monthNames[i];
|
||||
}
|
||||
|
||||
@ -109,3 +109,22 @@ export function fire(target, type, properties) {
|
||||
|
||||
return target.dispatchEvent(evt);
|
||||
}
|
||||
|
||||
// https://css-tricks.com/snippets/javascript/loop-queryselectorall-matches/
|
||||
export function forEachNode(nodeList, callback, scope) {
|
||||
if(!nodeList) return;
|
||||
for (var i = 0; i < nodeList.length; i++) {
|
||||
callback.call(scope, nodeList[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
export function activate($parent, $child, commonClass, activeClass='active', index = -1) {
|
||||
let $children = $parent.querySelectorAll(`.${commonClass}.${activeClass}`);
|
||||
|
||||
forEachNode($children, (node, i) => {
|
||||
if(index >= 0 && i <= index) return;
|
||||
node.classList.remove(activeClass);
|
||||
});
|
||||
|
||||
$child.classList.add(activeClass);
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { getBarHeightAndYAttr } from './draw-utils';
|
||||
import { getStringWidth } from './helpers';
|
||||
import { DOT_OVERLAY_SIZE_INCR } from './constants';
|
||||
import { DOT_OVERLAY_SIZE_INCR, PERCENTAGE_BAR_DEFAULT_DEPTH } from './constants';
|
||||
import { lightenDarkenColor } from './colors';
|
||||
|
||||
const AXIS_TICK_LENGTH = 6;
|
||||
export const AXIS_TICK_LENGTH = 6;
|
||||
const LABEL_MARGIN = 4;
|
||||
export const FONT_SIZE = 10;
|
||||
const BASE_LINE_COLOR = '#dadada';
|
||||
const FONT_FILL = '#555b51';
|
||||
|
||||
function $(expr, con) {
|
||||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
|
||||
@ -79,12 +81,13 @@ export function makeSVGDefs(svgContainer) {
|
||||
});
|
||||
}
|
||||
|
||||
export function makeSVGGroup(parent, className, transform='') {
|
||||
return createSVG('g', {
|
||||
export function makeSVGGroup(className, transform='', parent=undefined) {
|
||||
let args = {
|
||||
className: className,
|
||||
inside: parent,
|
||||
transform: transform
|
||||
});
|
||||
};
|
||||
if(parent) args.inside = parent;
|
||||
return createSVG('g', args);
|
||||
}
|
||||
|
||||
export function wrapInSVGGroup(elements, className='') {
|
||||
@ -131,7 +134,29 @@ export function makeGradient(svgDefElem, color, lighter = false) {
|
||||
return gradientId;
|
||||
}
|
||||
|
||||
export function makeHeatSquare(className, x, y, size, fill='none', data={}) {
|
||||
export function percentageBar(x, y, width, height,
|
||||
depth=PERCENTAGE_BAR_DEFAULT_DEPTH, fill='none') {
|
||||
|
||||
let args = {
|
||||
className: 'percentage-bar',
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height,
|
||||
fill: fill,
|
||||
styles: {
|
||||
'stroke': lightenDarkenColor(fill, -25),
|
||||
// Diabolically good: https://stackoverflow.com/a/9000859
|
||||
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray
|
||||
'stroke-dasharray': `0, ${height + width}, ${width}, ${height}`,
|
||||
'stroke-width': depth
|
||||
},
|
||||
};
|
||||
|
||||
return createSVG("rect", args);
|
||||
}
|
||||
|
||||
export function heatSquare(className, x, y, size, fill='none', data={}) {
|
||||
let args = {
|
||||
className: className,
|
||||
x: x,
|
||||
@ -148,13 +173,77 @@ export function makeHeatSquare(className, x, y, size, fill='none', data={}) {
|
||||
return createSVG("rect", args);
|
||||
}
|
||||
|
||||
export function makeText(className, x, y, content) {
|
||||
export function legendBar(x, y, size, fill='none', label) {
|
||||
let args = {
|
||||
className: 'legend-bar',
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: size,
|
||||
height: '2px',
|
||||
fill: fill
|
||||
};
|
||||
let text = createSVG('text', {
|
||||
className: 'legend-dataset-text',
|
||||
x: 0,
|
||||
y: 0,
|
||||
dy: (FONT_SIZE * 2) + 'px',
|
||||
'font-size': (FONT_SIZE * 1.2) + 'px',
|
||||
'text-anchor': 'start',
|
||||
fill: FONT_FILL,
|
||||
innerHTML: label
|
||||
});
|
||||
|
||||
let group = createSVG('g', {
|
||||
transform: `translate(${x}, ${y})`
|
||||
});
|
||||
group.appendChild(createSVG("rect", args));
|
||||
group.appendChild(text);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
export function legendDot(x, y, size, fill='none', label) {
|
||||
let args = {
|
||||
className: 'legend-dot',
|
||||
cx: 0,
|
||||
cy: 0,
|
||||
r: size,
|
||||
fill: fill
|
||||
};
|
||||
let text = createSVG('text', {
|
||||
className: 'legend-dataset-text',
|
||||
x: 0,
|
||||
y: 0,
|
||||
dx: (FONT_SIZE) + 'px',
|
||||
dy: (FONT_SIZE/3) + 'px',
|
||||
'font-size': (FONT_SIZE * 1.2) + 'px',
|
||||
'text-anchor': 'start',
|
||||
fill: FONT_FILL,
|
||||
innerHTML: label
|
||||
});
|
||||
|
||||
let group = createSVG('g', {
|
||||
transform: `translate(${x}, ${y})`
|
||||
});
|
||||
group.appendChild(createSVG("circle", args));
|
||||
group.appendChild(text);
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
export function makeText(className, x, y, content, options = {}) {
|
||||
let fontSize = options.fontSize || FONT_SIZE;
|
||||
let dy = options.dy !== undefined ? options.dy : (fontSize / 2);
|
||||
let fill = options.fill || FONT_FILL;
|
||||
let textAnchor = options.textAnchor || 'start';
|
||||
return createSVG('text', {
|
||||
className: className,
|
||||
x: x,
|
||||
y: y,
|
||||
dy: (FONT_SIZE / 2) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
dy: dy + 'px',
|
||||
'font-size': fontSize + 'px',
|
||||
fill: fill,
|
||||
'text-anchor': textAnchor,
|
||||
innerHTML: content
|
||||
});
|
||||
}
|
||||
@ -294,9 +383,13 @@ export function xLine(x, label, height, options={}) {
|
||||
}
|
||||
|
||||
export function yMarker(y, label, width, options={}) {
|
||||
if(!options.labelPos) options.labelPos = 'right';
|
||||
let x = options.labelPos === 'left' ? LABEL_MARGIN
|
||||
: width - getStringWidth(label, 5) - LABEL_MARGIN;
|
||||
|
||||
let labelSvg = createSVG('text', {
|
||||
className: 'chart-label',
|
||||
x: width - getStringWidth(label, 5) - LABEL_MARGIN,
|
||||
x: x,
|
||||
y: 0,
|
||||
dy: (FONT_SIZE / -2) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
@ -315,7 +408,7 @@ export function yMarker(y, label, width, options={}) {
|
||||
return line;
|
||||
}
|
||||
|
||||
export function yRegion(y1, y2, width, label) {
|
||||
export function yRegion(y1, y2, width, label, options={}) {
|
||||
// return a group
|
||||
let height = y1 - y2;
|
||||
|
||||
@ -333,9 +426,13 @@ export function yRegion(y1, y2, width, label) {
|
||||
height: height
|
||||
});
|
||||
|
||||
if(!options.labelPos) options.labelPos = 'right';
|
||||
let x = options.labelPos === 'left' ? LABEL_MARGIN
|
||||
: width - getStringWidth(label+"", 4.5) - LABEL_MARGIN;
|
||||
|
||||
let labelSvg = createSVG('text', {
|
||||
className: 'chart-label',
|
||||
x: width - getStringWidth(label+"", 4.5) - LABEL_MARGIN,
|
||||
x: x,
|
||||
y: 0,
|
||||
dy: (FONT_SIZE / -2) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
@ -357,6 +454,11 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m
|
||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
|
||||
y -= offset;
|
||||
|
||||
if(height === 0) {
|
||||
height = meta.minHeight;
|
||||
y -= meta.minHeight;
|
||||
}
|
||||
|
||||
let rect = createSVG('rect', {
|
||||
className: `bar mini`,
|
||||
style: `fill: ${color}`,
|
||||
@ -364,7 +466,7 @@ export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, m
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height || meta.minHeight // TODO: correct y for positive min height
|
||||
height: height
|
||||
});
|
||||
|
||||
label += "";
|
||||
@ -452,7 +554,6 @@ export function getPaths(xList, yList, color, options={}, meta={}) {
|
||||
if(options.regionFill) {
|
||||
let gradient_id_region = makeGradient(meta.svgDefs, color, true);
|
||||
|
||||
// TODO: use zeroLine OR minimum
|
||||
let pathStr = "M" + `${xList[0]},${meta.zeroLine}L` + pointsStr + `L${xList.slice(-1)[0]},${meta.zeroLine}`;
|
||||
paths.region = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id_region})`);
|
||||
}
|
||||
@ -490,6 +591,25 @@ export let makeOverlay = {
|
||||
overlay.setAttribute('fill', fill);
|
||||
overlay.style.opacity = '0.6';
|
||||
|
||||
if(transformValue) {
|
||||
overlay.setAttribute('transform', transformValue);
|
||||
}
|
||||
return overlay;
|
||||
},
|
||||
|
||||
'heat_square': (unit) => {
|
||||
let transformValue;
|
||||
if(unit.nodeName !== 'circle') {
|
||||
transformValue = unit.getAttribute('transform');
|
||||
unit = unit.childNodes[0];
|
||||
}
|
||||
let overlay = unit.cloneNode();
|
||||
let radius = unit.getAttribute('r');
|
||||
let fill = unit.getAttribute('fill');
|
||||
overlay.setAttribute('r', parseInt(radius) + DOT_OVERLAY_SIZE_INCR);
|
||||
overlay.setAttribute('fill', fill);
|
||||
overlay.style.opacity = '0.6';
|
||||
|
||||
if(transformValue) {
|
||||
overlay.setAttribute('transform', transformValue);
|
||||
}
|
||||
@ -532,5 +652,23 @@ export let updateOverlay = {
|
||||
if(transformValue) {
|
||||
overlay.setAttribute('transform', transformValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
'heat_square': (unit, overlay) => {
|
||||
let transformValue;
|
||||
if(unit.nodeName !== 'circle') {
|
||||
transformValue = unit.getAttribute('transform');
|
||||
unit = unit.childNodes[0];
|
||||
}
|
||||
let attributes = ['cx', 'cy'];
|
||||
Object.values(unit.attributes)
|
||||
.filter(attr => attributes.includes(attr.name) && attr.specified)
|
||||
.map(attr => {
|
||||
overlay.setAttribute(attr.name, attr.nodeValue);
|
||||
});
|
||||
|
||||
if(transformValue) {
|
||||
overlay.setAttribute('transform', transformValue);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
33
src/js/utils/export.js
Normal file
33
src/js/utils/export.js
Normal file
@ -0,0 +1,33 @@
|
||||
import { $ } from '../utils/dom';
|
||||
import { CSSTEXT } from '../../css/chartsCss';
|
||||
|
||||
export function downloadFile(filename, data) {
|
||||
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);
|
||||
a.href = url;
|
||||
a.download = filename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function(){
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 300);
|
||||
}
|
||||
|
||||
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.insertBefore(styleEl, clone.firstChild);
|
||||
|
||||
let container = $.create('div');
|
||||
container.appendChild(clone);
|
||||
|
||||
return container.innerHTML;
|
||||
}
|
||||
@ -77,9 +77,18 @@ export function bindChange(obj, getFn, setFn) {
|
||||
});
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/29325222
|
||||
export function getRandomBias(min, max, bias, influence) {
|
||||
const range = max - min;
|
||||
const biasValue = range * bias + min;
|
||||
var rnd = Math.random() * range + min, // random in range
|
||||
mix = Math.random() * influence; // random mixer
|
||||
return rnd * (1 - mix) + biasValue * mix; // mix full range and bias
|
||||
}
|
||||
|
||||
export function getPositionByAngle(angle, radius) {
|
||||
return {
|
||||
x:Math.sin(angle * ANGLE_RATIO) * radius,
|
||||
y:Math.cos(angle * ANGLE_RATIO) * radius,
|
||||
x: Math.sin(angle * ANGLE_RATIO) * radius,
|
||||
y: Math.cos(angle * ANGLE_RATIO) * radius,
|
||||
};
|
||||
}
|
||||
|
||||
@ -200,6 +200,23 @@ export function scale(val, yAxis) {
|
||||
return floatTwo(yAxis.zeroLine - val * yAxis.scaleMultiplier);
|
||||
}
|
||||
|
||||
export function isInRange(val, min, max) {
|
||||
return val > min && val < max;
|
||||
}
|
||||
|
||||
export function isInRange2D(coord, minCoord, maxCoord) {
|
||||
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 index ? arr.indexOf(closest) : closest;
|
||||
}
|
||||
|
||||
export function calcDistribution(values, distributionSize) {
|
||||
// Assume non-negative values,
|
||||
// implying distribution minimum at zero
|
||||
|
||||
10
src/js/utils/test/helpers.test.js
Normal file
10
src/js/utils/test/helpers.test.js
Normal file
@ -0,0 +1,10 @@
|
||||
const assert = require('assert')
|
||||
const helpers = require('../helpers')
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@ -1,225 +0,0 @@
|
||||
.chart-container {
|
||||
// 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;
|
||||
|
||||
.graph-focus-margin {
|
||||
margin: 0px 5%;
|
||||
}
|
||||
&>.title {
|
||||
margin-top: 25px;
|
||||
margin-left: 25px;
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
color: #6c7680;
|
||||
}
|
||||
.graphics {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.graph-stats-group {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
flex: 1;
|
||||
}
|
||||
.graph-stats-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
.stats {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
.stats-title {
|
||||
color: #8D99A6;
|
||||
}
|
||||
.stats-value {
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
}
|
||||
.stats-description {
|
||||
font-size: 12px;
|
||||
color: #8D99A6;
|
||||
}
|
||||
.graph-data {
|
||||
.stats-value {
|
||||
color: #98d85b;
|
||||
}
|
||||
}
|
||||
}
|
||||
.axis, .chart-label {
|
||||
fill: #555b51;
|
||||
// temp commented
|
||||
line {
|
||||
stroke: #dadada;
|
||||
}
|
||||
}
|
||||
.percentage-graph {
|
||||
.progress {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
.dataset-units {
|
||||
circle {
|
||||
stroke: #fff;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
// temp
|
||||
path {
|
||||
fill: none;
|
||||
stroke-opacity: 1;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
.multiaxis-chart {
|
||||
.line-horizontal, .y-axis-guide {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
.dataset-path {
|
||||
stroke-width: 2px;
|
||||
}
|
||||
.path-group {
|
||||
path {
|
||||
fill: none;
|
||||
stroke-opacity: 1;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
}
|
||||
line.dashed {
|
||||
stroke-dasharray: 5,3;
|
||||
}
|
||||
.axis-line {
|
||||
// &.x-axis-label {
|
||||
// display: block;
|
||||
// }
|
||||
// TODO: hack dy attr to be settable via styles
|
||||
.specific-value {
|
||||
text-anchor: start;
|
||||
}
|
||||
.y-line {
|
||||
text-anchor: end;
|
||||
}
|
||||
.x-line {
|
||||
text-anchor: middle;
|
||||
}
|
||||
}
|
||||
.progress {
|
||||
height: 20px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1);
|
||||
}
|
||||
.progress-bar {
|
||||
float: left;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
background-color: #36414c;
|
||||
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15);
|
||||
-webkit-transition: width .6s ease;
|
||||
-o-transition: width .6s ease;
|
||||
transition: width .6s ease;
|
||||
}
|
||||
|
||||
.graph-svg-tip {
|
||||
position: absolute;
|
||||
z-index: 99999;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
color: #959da5;
|
||||
text-align: center;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
border-radius: 3px;
|
||||
ul {
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
}
|
||||
ol {
|
||||
padding-left: 0;
|
||||
display: flex;
|
||||
}
|
||||
ul.data-point-list {
|
||||
li {
|
||||
min-width: 90px;
|
||||
flex: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
strong {
|
||||
color: #dfe2e5;
|
||||
font-weight: 600;
|
||||
}
|
||||
.svg-pointer {
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
margin: 0 0 0 -5px;
|
||||
content: " ";
|
||||
border: 5px solid transparent;
|
||||
border-top-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
&.comparison {
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
pointer-events: none;
|
||||
.title {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
ul {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
list-style: none;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*Indicators*/
|
||||
.indicator,
|
||||
.indicator-right {
|
||||
background: none;
|
||||
font-size: 12px;
|
||||
vertical-align: middle;
|
||||
font-weight: bold;
|
||||
color: #6c7680;
|
||||
}
|
||||
.indicator i {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.indicator::before,.indicator i {
|
||||
margin: 0 4px 0 0px;
|
||||
}
|
||||
.indicator-right::after {
|
||||
margin: 0 0 0 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user