Merge pull request #154 from frappe/develop

Develop
This commit is contained in:
Prateeksha Singh 2018-04-20 12:39:07 +05:30 committed by GitHub
commit a47a4d0eb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 10743 additions and 3607 deletions

View File

@ -6,5 +6,9 @@
}
}]
],
"plugins": ["external-helpers"]
"env": {
"test": {
"presets": ["env"]
}
}
}

67
.gitignore vendored
View File

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

@ -0,0 +1,14 @@
language: node_js
node_js:
- "6"
- "8"
before_install:
- make install
script:
- make test
after_success:
- make coveralls

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

1
dist/frappe-charts.min.cjs.js.map vendored Normal file

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

1
dist/frappe-charts.min.esm.js.map vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

205
docs/assets/js/data.js Normal file
View 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
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -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"> &lt;script src="https://unpkg.com/frappe-charts@1.0.0/dist/frappe-charts.min.iife.js"&gt;&lt;/script&gt;</code></pre>
<pre><code class="hljs html"> &lt;script src="https://unpkg.com/frappe-charts@1.0.0"&gt;&lt;/script&gt;</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>

View File

@ -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"> &lt;script src="https://unpkg.com/frappe-charts@1.0.0/dist/frappe-charts.min.iife.js"&gt;&lt;/script&gt;</code></pre>
<p class="step-explain">Make a new Chart</p>
<pre><code class="hljs html"> &lt!--HTML--&gt;
&lt;div id="chart"&gt;&lt;/div&gt;</code></pre>
<pre><code class="hljs javascript"> // Javascript
let 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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

5282
yarn.lock Normal file

File diff suppressed because it is too large Load Diff