commit
6a3fab09d4
@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
<img src="https://github.com/frappe/design/blob/master/logos/frappe-charts-symbol.svg" height="128">
|
||||
<h2>Frappé Charts</h2>
|
||||
<img src="https://github.com/frappe/design/blob/master/logos/charts-logo.svg" height="128">
|
||||
<h2>Frappe Charts</h2>
|
||||
<p align="center">
|
||||
<p>GitHub-inspired modern, intuitive and responsive charts with zero dependencies</p>
|
||||
<a href="https://frappe.github.io/charts">
|
||||
|
||||
4831
dist/frappe-charts.esm.js
vendored
4831
dist/frappe-charts.esm.js
vendored
File diff suppressed because it is too large
Load Diff
2
dist/frappe-charts.min.cjs.js
vendored
2
dist/frappe-charts.min.cjs.js
vendored
File diff suppressed because one or more lines are too long
2
dist/frappe-charts.min.css
vendored
2
dist/frappe-charts.min.css
vendored
@ -1 +1 @@
|
||||
.chart-container{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif}.chart-container .graph-focus-margin{margin:0 5%}.chart-container>.title{margin-top:25px;margin-left:25px;text-align:left;font-weight:400;font-size:12px;color:#6c7680}.chart-container .graphics{margin-top:10px;padding-top:10px;padding-bottom:10px;position:relative}.chart-container .graph-stats-group{-ms-flex-pack:distribute;-webkit-box-flex:1;-ms-flex:1;flex:1}.chart-container .graph-stats-container,.chart-container .graph-stats-group{display:-webkit-box;display:-ms-flexbox;display:flex;justify-content:space-around}.chart-container .graph-stats-container{-ms-flex-pack:distribute;padding-top:10px}.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{font-size:11px;fill:#555b51}.chart-container .axis line,.chart-container .chart-label line{stroke:#dadada}.chart-container .percentage-graph .progress{margin-bottom:0}.chart-container .data-points circle{stroke:#fff;stroke-width:2}.chart-container .path-group path{fill:none;stroke-opacity:1;stroke-width:2px}.chart-container line.dashed{stroke-dasharray:5,3}.chart-container .tick.x-axis-label{display:block}.chart-container .tick .specific-value{text-anchor:start}.chart-container .tick .y-value-text{text-anchor:end}.chart-container .tick .x-value-text{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{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}
|
||||
2
dist/frappe-charts.min.esm.js
vendored
2
dist/frappe-charts.min.esm.js
vendored
File diff suppressed because one or more lines are too long
3
dist/frappe-charts.min.iife.js
vendored
3
dist/frappe-charts.min.iife.js
vendored
File diff suppressed because one or more lines are too long
1
dist/frappe-charts.min.iife.js.map
vendored
Normal file
1
dist/frappe-charts.min.iife.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/assets/img/frappe-bird.png
Normal file
BIN
docs/assets/img/frappe-bird.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
3
docs/assets/js/frappe-charts.min.js
vendored
3
docs/assets/js/frappe-charts.min.js
vendored
File diff suppressed because one or more lines are too long
1
docs/assets/js/frappe-charts.min.js.map
Normal file
1
docs/assets/js/frappe-charts.min.js.map
Normal file
File diff suppressed because one or more lines are too long
@ -1,186 +1,218 @@
|
||||
// Composite Chart
|
||||
// ================================================================================
|
||||
let report_count_list = [17, 40, 33, 44, 126, 156,
|
||||
324, 333, 478, 495, 373];
|
||||
let reportCountList = [152, 222, 199, 287, 534, 709,
|
||||
1179, 1256, 1632, 1856, 1850];
|
||||
|
||||
let bar_composite_data = {
|
||||
let lineCompositeData = {
|
||||
labels: ["2007", "2008", "2009", "2010", "2011", "2012",
|
||||
"2013", "2014", "2015", "2016", "2017"],
|
||||
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Average 100 reports/month",
|
||||
value: 1200,
|
||||
}
|
||||
],
|
||||
|
||||
datasets: [{
|
||||
"title": "Events",
|
||||
"values": report_count_list,
|
||||
// "formatted": report_count_list.map(d => d + " reports")
|
||||
"name": "Events",
|
||||
"values": reportCountList
|
||||
}]
|
||||
};
|
||||
|
||||
let line_composite_data = {
|
||||
|
||||
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: [{
|
||||
"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 = {
|
||||
// 0: {values: [4, 0, 3, 1, 1, 2, 1, 2, 1, 0, 1, 1]},
|
||||
0: {values: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]},
|
||||
1: {values: [2, 3, 3, 2, 1, 4, 0, 1, 2, 7, 11, 4]},
|
||||
2: {values: [7, 7, 2, 4, 0, 1, 5, 3, 1, 2, 0, 1]},
|
||||
3: {values: [0, 2, 6, 2, 2, 1, 2, 3, 6, 3, 7, 10]},
|
||||
4: {values: [9, 10, 8, 10, 6, 5, 8, 8, 24, 15, 10, 13]},
|
||||
5: {values: [9, 13, 16, 9, 4, 5, 7, 10, 14, 22, 23, 24]},
|
||||
6: {values: [20, 22, 28, 19, 28, 19, 14, 19, 51, 37, 29, 38]},
|
||||
7: {values: [29, 20, 22, 16, 16, 19, 24, 26, 57, 31, 46, 27]},
|
||||
8: {values: [36, 24, 38, 27, 15, 22, 24, 38, 32, 57, 139, 26]},
|
||||
9: {values: [37, 36, 32, 33, 12, 34, 52, 45, 58, 57, 64, 35]},
|
||||
10: {values: [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 0, 0]}
|
||||
// 10: {values: [36, 46, 45, 32, 27, 31, 30, 36, 39, 49, 40, 40]}
|
||||
// 10: {values: [-36, -46, -45, -32, -27, -31, -30, -36, -39, -49, -40, -40]}
|
||||
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 bar_composite_chart = new Chart ({
|
||||
parent: c1,
|
||||
title: "Fireball/Bolide Events - Yearly (more than 5 reports)",
|
||||
data: bar_composite_data,
|
||||
type: 'bar',
|
||||
height: 180,
|
||||
colors: ['orange'],
|
||||
is_navigable: 1,
|
||||
is_series: 1
|
||||
// region_fill: 1
|
||||
});
|
||||
|
||||
let line_composite_chart = new Chart ({
|
||||
parent: c2,
|
||||
data: line_composite_data,
|
||||
let lineCompositeChart = new Chart (c1, {
|
||||
title: "Fireball/Bolide Events - Yearly (reported)",
|
||||
data: lineCompositeData,
|
||||
type: 'line',
|
||||
height: 180,
|
||||
height: 190,
|
||||
colors: ['green'],
|
||||
is_series: 1
|
||||
isNavigable: 1,
|
||||
valuesOverPoints: 1,
|
||||
|
||||
lineOptions: {
|
||||
dotSize: 8
|
||||
},
|
||||
// yAxisMode: 'tick'
|
||||
// regionFill: 1
|
||||
});
|
||||
|
||||
bar_composite_chart.parent.addEventListener('data-select', (e) => {
|
||||
line_composite_chart.update_values([more_line_data[e.index]]);
|
||||
let barCompositeChart = new Chart (c2, {
|
||||
data: barCompositeData,
|
||||
type: 'bar',
|
||||
height: 190,
|
||||
colors: ['violet', 'light-blue', '#46a9f9'],
|
||||
valuesOverPoints: 1,
|
||||
axisOptions: {
|
||||
xAxisMode: 'tick'
|
||||
},
|
||||
barOptions: {
|
||||
stacked: 1
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
lineCompositeChart.parent.addEventListener('data-select', (e) => {
|
||||
let i = e.index;
|
||||
barCompositeChart.updateDatasets([
|
||||
fireballOver25[i], fireball_5_25[i], fireball_2_5[i]
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
// Demo Chart (bar, linepts, scatter(blobs), percentage)
|
||||
// ================================================================================
|
||||
let type_data = {
|
||||
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: [
|
||||
{
|
||||
title: "Some Data",
|
||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||
name: "Some Data",
|
||||
values: [18, 40, 30, 35, 8, 52, 17, -4],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
},
|
||||
{
|
||||
title: "Another Set",
|
||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||
name: "Another Set",
|
||||
values: [30, 50, -10, 15, 18, 32, 27, 14],
|
||||
axisPosition: 'right',
|
||||
chartType: 'bar'
|
||||
},
|
||||
{
|
||||
title: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37]
|
||||
name: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37],
|
||||
chartType: 'line'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let type_chart = new Chart({
|
||||
parent: "#chart-types",
|
||||
title: "My Awesome Chart",
|
||||
data: type_data,
|
||||
type: 'bar',
|
||||
height: 250,
|
||||
colors: ['light-blue', 'violet', 'blue'],
|
||||
is_series: 1,
|
||||
format_tooltip_x: d => (d + '').toUpperCase(),
|
||||
format_tooltip_y: d => d + ' pts'
|
||||
});
|
||||
// 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',
|
||||
// }
|
||||
// });
|
||||
|
||||
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.get_different_chart(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
|
||||
|
||||
// Aggregation 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 = {
|
||||
parent: "#chart-trends",
|
||||
title: "Mean Total Sunspot Count - Yearly",
|
||||
data: trends_data,
|
||||
type: 'line',
|
||||
let args = {
|
||||
data: typeData,
|
||||
type: 'axis-mixed',
|
||||
height: 250,
|
||||
colors: ['blue'],
|
||||
is_series: 1,
|
||||
show_dots: 0,
|
||||
heatline: 1,
|
||||
x_axis_mode: 'tick',
|
||||
y_axis_mode: 'span'
|
||||
};
|
||||
colors: ['purple', 'magenta', 'light-blue'],
|
||||
|
||||
new Chart(plot_chart_args);
|
||||
maxLegendPoints: 6,
|
||||
maxSlices: 10,
|
||||
|
||||
tooltipOptions: {
|
||||
formatTooltipX: d => (d + '').toUpperCase(),
|
||||
formatTooltipY: d => d + ' pts',
|
||||
}
|
||||
}
|
||||
let aggrChart = new Chart("#chart-aggr", args);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll('.chart-plot-buttons button')
|
||||
document.querySelectorAll('.aggr-type-buttons button')
|
||||
).map(el => {
|
||||
el.addEventListener('click', (e) => {
|
||||
let btn = e.target;
|
||||
let type = btn.getAttribute('data-type');
|
||||
let config = [];
|
||||
args.type = type;
|
||||
|
||||
if(type === 'line') {
|
||||
config = [0, 0, 0];
|
||||
} else if(type === 'region') {
|
||||
config = [0, 0, 1];
|
||||
} else {
|
||||
config = [0, 1, 0];
|
||||
let newChart = new Chart("#chart-aggr", args);;
|
||||
if(newChart){
|
||||
aggrChart = newChart;
|
||||
}
|
||||
|
||||
plot_chart_args.show_dots = config[0];
|
||||
plot_chart_args.heatline = config[1];
|
||||
plot_chart_args.region_fill = config[2];
|
||||
|
||||
plot_chart_args.init = false;
|
||||
|
||||
new Chart(plot_chart_args);
|
||||
|
||||
Array.prototype.slice.call(
|
||||
btn.parentNode.querySelectorAll('button')).map(el => {
|
||||
el.classList.remove('active');
|
||||
@ -194,7 +226,9 @@ Array.prototype.slice.call(
|
||||
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));
|
||||
|
||||
let getRandom = () => Math.floor(Math.random() * 75 - 15);
|
||||
let update_data_all_values = Array.from({length: 30}, getRandom);
|
||||
|
||||
// We're gonna be shuffling this
|
||||
let update_data_all_indices = update_data_all_labels.map((d,i) => i);
|
||||
@ -209,47 +243,135 @@ let update_data = {
|
||||
datasets: [{
|
||||
"values": get_update_data(update_data_all_values)
|
||||
}],
|
||||
"specific_values": [
|
||||
yMarkers: [
|
||||
{
|
||||
title: "Altitude",
|
||||
// title: "A very long text",
|
||||
line_type: "dashed",
|
||||
value: 38
|
||||
label: "Altitude",
|
||||
value: 25,
|
||||
type: 'dashed'
|
||||
}
|
||||
],
|
||||
yRegions: [
|
||||
{
|
||||
label: "Range",
|
||||
start: 10,
|
||||
end: 45
|
||||
},
|
||||
]
|
||||
],
|
||||
};
|
||||
|
||||
let update_chart = new Chart({
|
||||
parent: "#chart-update",
|
||||
let update_chart = new Chart("#chart-update", {
|
||||
data: update_data,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
colors: ['red'],
|
||||
is_series: 1,
|
||||
region_fill: 1
|
||||
colors: ['#ff6c03'],
|
||||
lineOptions: {
|
||||
// hideLine: 1,
|
||||
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);
|
||||
update_chart.update_values(
|
||||
[{values: get_update_data(update_data_all_values)}],
|
||||
update_data_all_labels.slice(0, 10)
|
||||
);
|
||||
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)}],
|
||||
yMarkers: [
|
||||
{
|
||||
label: "Altitude",
|
||||
value: value,
|
||||
type: 'dashed'
|
||||
}
|
||||
],
|
||||
yRegions: [
|
||||
{
|
||||
label: "Range",
|
||||
start: start,
|
||||
end: end
|
||||
},
|
||||
],
|
||||
}
|
||||
update_chart.update(data);
|
||||
});
|
||||
|
||||
chart_update_buttons.querySelector('[data-update="add"]').addEventListener("click", (e) => {
|
||||
// NOTE: this ought to be problem, labels stay the same after update
|
||||
let index = update_chart.x.length; // last index to add
|
||||
let index = update_chart.state.datasetLength; // last index to add
|
||||
if(index >= update_data_all_indices.length) return;
|
||||
update_chart.add_data_point(
|
||||
[update_data_all_values[index]], update_data_all_labels[index]
|
||||
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.remove_data_point();
|
||||
update_chart.removeDataPoint();
|
||||
});
|
||||
|
||||
// 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,
|
||||
type: 'line',
|
||||
height: 250,
|
||||
colors: ['#238e38'],
|
||||
lineOptions: {
|
||||
hideDots: 1,
|
||||
heatline: 1,
|
||||
},
|
||||
axisOptions: {
|
||||
xAxisMode: 'tick',
|
||||
yAxisMode: 'span',
|
||||
xIsSeries: 1
|
||||
}
|
||||
};
|
||||
|
||||
new Chart("#chart-trends", plotChartArgs);
|
||||
|
||||
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 = {};
|
||||
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(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -293,14 +415,13 @@ let events_data = {
|
||||
]
|
||||
};
|
||||
|
||||
let events_chart = new Chart({
|
||||
parent: "#chart-events",
|
||||
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'],
|
||||
is_navigable: 1,
|
||||
isNavigable: 1,
|
||||
});
|
||||
|
||||
let data_div = document.querySelector('.chart-events-data');
|
||||
@ -314,68 +435,25 @@ events_chart.parent.addEventListener('data-select', (e) => {
|
||||
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({
|
||||
parent: "#chart-aggr",
|
||||
data: aggr_data,
|
||||
type: 'bar',
|
||||
height: 250,
|
||||
colors: ['purple', 'orange'],
|
||||
});
|
||||
|
||||
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 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++) {
|
||||
heatmap_data[parseInt(timestamp)] = Math.floor(Math.random() * 5);
|
||||
heatmapData[parseInt(timestamp)] = Math.floor(Math.random() * 5);
|
||||
timestamp = Math.floor(timestamp - 86400).toFixed(1);
|
||||
}
|
||||
|
||||
new Chart({
|
||||
parent: "#chart-heatmap",
|
||||
data: heatmap_data,
|
||||
new Chart("#chart-heatmap", {
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
legendScale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: 1 // default 0
|
||||
discreteDomains: 1,
|
||||
legendColors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c']
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
@ -384,10 +462,10 @@ Array.prototype.slice.call(
|
||||
el.addEventListener('click', (e) => {
|
||||
let btn = e.target;
|
||||
let mode = btn.getAttribute('data-mode');
|
||||
let discrete_domains = 0;
|
||||
let discreteDomains = 0;
|
||||
|
||||
if(mode === 'discrete') {
|
||||
discrete_domains = 1;
|
||||
discreteDomains = 1;
|
||||
}
|
||||
|
||||
let colors = [];
|
||||
@ -398,14 +476,13 @@ Array.prototype.slice.call(
|
||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
}
|
||||
|
||||
new Chart({
|
||||
parent: "#chart-heatmap",
|
||||
data: heatmap_data,
|
||||
new Chart("#chart-heatmap", {
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
legendScale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: discrete_domains,
|
||||
legend_colors: colors
|
||||
discreteDomains: discreteDomains,
|
||||
legendColors: colors
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
@ -428,23 +505,22 @@ Array.prototype.slice.call(
|
||||
colors = ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'];
|
||||
}
|
||||
|
||||
let discrete_domains = 1;
|
||||
let discreteDomains = 1;
|
||||
|
||||
let view_mode = document
|
||||
.querySelector('.heatmap-mode-buttons .active')
|
||||
.getAttribute('data-mode');
|
||||
if(view_mode === 'continuous') {
|
||||
discrete_domains = 0;
|
||||
discreteDomains = 0;
|
||||
}
|
||||
|
||||
new Chart({
|
||||
parent: "#chart-heatmap",
|
||||
data: heatmap_data,
|
||||
new Chart("#chart-heatmap", {
|
||||
data: heatmapData,
|
||||
type: 'heatmap',
|
||||
legend_scale: [0, 1, 2, 4, 5],
|
||||
legendScale: [0, 1, 2, 4, 5],
|
||||
height: 115,
|
||||
discrete_domains: discrete_domains,
|
||||
legend_colors: colors
|
||||
discreteDomains: discreteDomains,
|
||||
legendColors: colors
|
||||
});
|
||||
|
||||
Array.prototype.slice.call(
|
||||
|
||||
559
docs/assets/js/old_index.js
Executable file
559
docs/assets/js/old_index.js
Executable file
@ -0,0 +1,559 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
268
docs/index.html
268
docs/index.html
@ -18,13 +18,15 @@
|
||||
|
||||
<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">
|
||||
|
||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row hero" style="padding-top: 30px; padding-bottom: 0px;">
|
||||
<div class="jumbotron" style="background: transparent;">
|
||||
<h1>Frappé Charts</h1>
|
||||
<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>-->
|
||||
@ -44,68 +46,64 @@
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
<!--Bars, Lines or <a href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts" target="_blank">Percentages</a>-->
|
||||
Create a chart
|
||||
</h6>
|
||||
<p class="step-explain">Install</p>
|
||||
<pre><code class="hljs console"> npm install frappe-charts</code></pre>
|
||||
<p class="step-explain">And include it in your project</p>
|
||||
<pre><code class="hljs javascript"> import Chart from "frappe-charts/dist/frappe-charts.min.esm"</code></pre>
|
||||
<p class="step-explain">... or include it directly in your HTML</p>
|
||||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@0.0.8/dist/frappe-charts.min.iife.js"></script></code></pre>
|
||||
<p class="step-explain">Make a new Chart</p>
|
||||
<h6 class="margin-vertical-rem">Create a chart</h6>
|
||||
<pre><code class="hljs html"> <!--HTML-->
|
||||
<div id="chart"></div></code></pre>
|
||||
<pre><code class="hljs javascript"> // Javascript
|
||||
let data = {
|
||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
|
||||
let chart = new Chart( "#chart", { // or DOM element
|
||||
data: {
|
||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
|
||||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],
|
||||
|
||||
datasets: [
|
||||
{
|
||||
title: "Some Data",
|
||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||
},
|
||||
{
|
||||
title: "Another Set",
|
||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||
},
|
||||
{
|
||||
title: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37]
|
||||
}
|
||||
]
|
||||
};
|
||||
datasets: [
|
||||
{
|
||||
label: "Some Data", type: 'bar',
|
||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||
},
|
||||
{
|
||||
label: "Another Set", type: 'bar',
|
||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||
},
|
||||
{
|
||||
label: "Yet Another", type: 'line',
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37]
|
||||
}
|
||||
],
|
||||
|
||||
yMarkers: [{ label: "Marker", value: 70 }],
|
||||
yRegions: [{ label: "Region", start: -10, end: 50 }]
|
||||
},
|
||||
|
||||
let chart = new Chart({
|
||||
parent: "#chart", // or a DOM element
|
||||
title: "My Awesome Chart",
|
||||
data: data,
|
||||
type: 'bar', // or 'line', 'scatter', 'pie', 'percentage'
|
||||
type: 'axis-mixed', // or 'bar', 'line', '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'
|
||||
colors: ['purple', '#ffa3ef', 'red']
|
||||
});</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='scatter'>Scatter Chart</button>
|
||||
<!-- <div id="chart-types" class="border" style="margin-bottom: 15px"></div> -->
|
||||
<!-- <div >
|
||||
<div class="btn-group x-axis-buttons margin-vertical-px" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-type='span'>X span</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='tick'>X tick</button>
|
||||
</div>
|
||||
<div class="btn-group y-axis-buttons margin-vertical-px" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-type='span'>Y span</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='tick'>Y tick</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon">.00</span>
|
||||
<input type="text" class="form-control" aria-label="Amount (rounded to the nearest dollar)">
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<div id="chart-aggr" class="border"></div>
|
||||
<div class="btn-group aggr-type-buttons margin-vertical-px mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-type='axis-mixed'>Mixed</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">
|
||||
<!-- <p class="text-muted">
|
||||
<a target="_blank" href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts">Why Percentage?</a>
|
||||
</p>
|
||||
</p> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -114,42 +112,12 @@
|
||||
<h6 class="margin-vertical-rem">
|
||||
Update Values
|
||||
</h6>
|
||||
<pre><code class="hljs javascript"> // Update entire datasets
|
||||
chart.update_values(
|
||||
[
|
||||
{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 = [
|
||||
{
|
||||
title: "Altitude",
|
||||
line_type: "dashed", // or "solid"
|
||||
value: 38
|
||||
}
|
||||
]
|
||||
...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -158,25 +126,20 @@
|
||||
<h6 class="margin-vertical-rem">
|
||||
Plot Trends
|
||||
</h6>
|
||||
<pre><code class="hljs javascript"> ...
|
||||
x_axis_mode: 'tick', // for short label ticks
|
||||
// or 'span' for long spanning vertical axis lines
|
||||
y_axis_mode: 'span', // for long horizontal lines, or 'tick'
|
||||
is_series: 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="hideDots">Line</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="hideLine">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>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="regionFill">Region</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
type: 'line', // Line Chart specific properties:
|
||||
<!-- <pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
lineOptions: 'line', // Line Chart specific properties:
|
||||
|
||||
show_dots: 0, // Show data points on the line; default 1
|
||||
hideDots: 1, // Hide data points on the line; default 0
|
||||
heatline: 1, // Show a value-wise line gradient; default 0
|
||||
region_fill: 1, // Fill the area under the graph; default 0
|
||||
...</code></pre>
|
||||
regionFill: 1, // Fill the area under the graph; default 0
|
||||
...</code></pre> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -204,8 +167,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
type: 'bar', // Bar Chart specific properties:
|
||||
is_navigable: 1, // Navigate across bars; default 0
|
||||
isNavigable: 1, // Navigate across data points; default 0
|
||||
...
|
||||
|
||||
chart.parent.addEventListener('data-select', (e) => {
|
||||
@ -214,22 +176,6 @@
|
||||
</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">
|
||||
@ -242,23 +188,23 @@
|
||||
<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>
|
||||
<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>
|
||||
</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
|
||||
data: heatmapData, // object with date/timestamp-value pairs
|
||||
|
||||
discrete_domains: 1, // default: 0
|
||||
discreteDomains: 1 // default: 0
|
||||
|
||||
start: start_date,
|
||||
start: startDate,
|
||||
// A Date object;
|
||||
// default: today's date in past year
|
||||
// for an annual heatmap
|
||||
|
||||
legend_colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'],
|
||||
legendColors: ['#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']
|
||||
@ -267,13 +213,97 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">Available options:</h6>
|
||||
<pre><code class="hljs javascript">
|
||||
...
|
||||
{
|
||||
data: {
|
||||
labels: [],
|
||||
datasets: [],
|
||||
yRegions: [],
|
||||
yMarkers: []
|
||||
}
|
||||
title: '',
|
||||
colors: [],
|
||||
height: 200,
|
||||
|
||||
tooltipOptions: {
|
||||
formatTooltipX: d => (d + '').toUpperCase(),
|
||||
formatTooltipY: d => d + ' pts',
|
||||
}
|
||||
|
||||
// Axis charts
|
||||
isNavigable: 1, // default: 0
|
||||
valuesOverPoints: 1, // default: 0
|
||||
barOptions: {
|
||||
stacked: 1 // default: 0
|
||||
}
|
||||
|
||||
lineOptions: {
|
||||
dotSize: 6, // default: 4
|
||||
hideLine: 0, // default: 0
|
||||
hideDots: 1, // default: 0
|
||||
heatline: 1, // default: 0
|
||||
regionFill: 1 // default: 0
|
||||
}
|
||||
|
||||
axisOptions: {
|
||||
yAxisMode: 'span', // Axis lines, default
|
||||
xAxisMode: 'tick', // No axis lines, only short ticks
|
||||
xIsSeries: 1 // Allow skipping x values for space
|
||||
// default: 0
|
||||
},
|
||||
|
||||
// Pie/Percentage charts
|
||||
|
||||
maxLegendPoints: 6, // default: 20
|
||||
maxSlices: 10, // default: 20
|
||||
|
||||
// Heatmap
|
||||
|
||||
discreteDomains: 1, // default: 1
|
||||
start: startDate, // Date object
|
||||
legendColors: []
|
||||
}
|
||||
...
|
||||
|
||||
// Updating values
|
||||
chart.update(data);
|
||||
|
||||
// Axis charts:
|
||||
chart.addDataPoint(label, valueFromEachDataset, index)
|
||||
chart.removeDataPoint(index)
|
||||
chart.updateDataset(datasetValues, index)
|
||||
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">Install</h6>
|
||||
<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>
|
||||
<p class="step-explain">... or include it directly in your HTML</p>
|
||||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@0.0.8/dist/frappe-charts.min.iife.js"></script></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 style="margin-top: 1rem;">
|
||||
<a class="github-button" href="https://github.com/frappe/charts" data-icon="octicon-star" data-show-count="true" aria-label="Star frappe/charts on GitHub">Star</a>
|
||||
</p>
|
||||
<p>License: MIT</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -285,7 +315,7 @@
|
||||
</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">
|
||||
<img style="padding: 5px; width: 40px; background: #fff" class="frappe-bird" src="./assets/img/frappe-bird.png">
|
||||
<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>.
|
||||
|
||||
312
docs/old_index.html
Normal file
312
docs/old_index.html
Normal file
@ -0,0 +1,312 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Frappe Charts</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="keywords" content="open source javascript js charts library svg zero-dependency interactive data visualization beautiful drag resize">
|
||||
<meta name="description" content="A simple, responsive, modern charts library for the web.">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/normalize.css" media="screen">
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto:400,700' rel='stylesheet' type='text/css'>
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/bootstrap.min.css" media="screen">
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/frappe_theme.css" media="screen">
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/index.css" media="screen">
|
||||
<link rel="stylesheet" type="text/css" href="assets/css/default.css" media="screen">
|
||||
<script src="assets/js/highlight.pack.js"></script>
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
|
||||
<link rel="shortcut icon" href="https://frappe.github.io/frappe/assets/img/favicon.png" type="image/x-icon">
|
||||
<link rel="icon" href="https://frappe.github.io/frappe/assets/img/favicon.png" type="image/x-icon">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row hero" style="padding-top: 30px; padding-bottom: 0px;">
|
||||
<div class="jumbotron" style="background: transparent;">
|
||||
<h1>Frappe Charts</h1>
|
||||
<p class="mt-2">GitHub-inspired simple and modern charts for the web</p>
|
||||
<p class="mt-2">with zero dependencies.</p>
|
||||
<!--<p class="mt-2">Because dumb charts are hard to come by.</p>-->
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1 later" style="font-size: 14px;">
|
||||
<div id="chart-composite-1" class="border"><svg height=225></svg></div>
|
||||
<p class="mt-1">Click or use arrow keys to navigate data points</p>
|
||||
</div>
|
||||
<div class="col-sm-10 push-sm-1 later" style="font-size: 14px;">
|
||||
<div id="chart-composite-2" class="border"><svg height=225></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group later">
|
||||
<div class="row section">
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
<!--Bars, Lines or <a href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts" target="_blank">Percentages</a>-->
|
||||
Create a chart
|
||||
</h6>
|
||||
<p class="step-explain">Install</p>
|
||||
<pre><code class="hljs console"> npm install frappe-charts</code></pre>
|
||||
<p class="step-explain">And include it in your project</p>
|
||||
<pre><code class="hljs javascript"> import Chart from "frappe-charts/dist/frappe-charts.min.esm"</code></pre>
|
||||
<p class="step-explain">... or include it directly in your HTML</p>
|
||||
<pre><code class="hljs html"> <script src="https://unpkg.com/frappe-charts@0.0.8/dist/frappe-charts.min.iife.js"></script></code></pre>
|
||||
<p class="step-explain">Make a new Chart</p>
|
||||
<pre><code class="hljs html"> <!--HTML-->
|
||||
<div id="chart"></div></code></pre>
|
||||
<pre><code class="hljs javascript"> // Javascript
|
||||
let data = {
|
||||
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
|
||||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],
|
||||
|
||||
datasets: [
|
||||
{
|
||||
label: "Some Data",
|
||||
values: [25, 40, 30, 35, 8, 52, 17, -4]
|
||||
},
|
||||
{
|
||||
label: "Another Set",
|
||||
values: [25, 50, -10, 15, 18, 32, 27, 14]
|
||||
},
|
||||
{
|
||||
label: "Yet Another",
|
||||
values: [15, 20, -3, -15, 58, 12, -17, 37]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
let chart = new Chart({
|
||||
parent: "#chart", // or a DOM element
|
||||
title: "My Awesome Chart",
|
||||
data: data,
|
||||
type: 'bar', // or 'line', 'scatter', 'pie', 'percentage'
|
||||
height: 250,
|
||||
|
||||
colors: ['#7cd6fd', 'violet', 'blue'],
|
||||
// hex-codes or these preset colors;
|
||||
// defaults (in order):
|
||||
// ['light-blue', 'blue', 'violet', 'red',
|
||||
// 'orange', 'yellow', 'green', 'light-green',
|
||||
// 'purple', 'magenta', 'grey', 'dark-grey']
|
||||
|
||||
format_tooltip_x: d => (d + '').toUpperCase(),
|
||||
format_tooltip_y: d => d + ' pts'
|
||||
});</code></pre>
|
||||
<div id="chart-types" class="border"></div>
|
||||
<div class="btn-group chart-type-buttons margin-vertical-px mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-type='bar'>Bar Chart</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='line'>Line Chart</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='pie'>Pie Chart</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type='percentage'>Percentage Chart</button>
|
||||
</div>
|
||||
<p class="text-muted">
|
||||
<a target="_blank" href="http://www.storytellingwithdata.com/blog/2011/07/death-to-pie-charts">Why Percentage?</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
Update Values
|
||||
</h6>
|
||||
<pre><code class="hljs javascript"> // Update entire datasets
|
||||
chart.updateData(
|
||||
[
|
||||
{values: new_dataset_1_values},
|
||||
{values: new_dataset_2_values}
|
||||
],
|
||||
new_labels
|
||||
);
|
||||
|
||||
// Add a new data point
|
||||
chart.add_data_point(
|
||||
[new_value_1, new_value_2],
|
||||
new_label,
|
||||
index // defaults to last index
|
||||
);
|
||||
|
||||
// Remove a data point
|
||||
chart.remove_data_point(index);</code></pre>
|
||||
<div id="chart-update" class="border"></div>
|
||||
<div class="chart-update-buttons mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="random">Random Data</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="add">Add Value</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-update="remove">Remove Value</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
// Include specific Y values in input data to be displayed as lines
|
||||
// (before passing data to a new chart):
|
||||
|
||||
data.specific_values = [
|
||||
{
|
||||
label: "Altitude",
|
||||
line_type: "dashed", // or "solid"
|
||||
value: 38
|
||||
}
|
||||
]
|
||||
...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
Plot Trends
|
||||
</h6>
|
||||
<pre><code class="hljs javascript"> ...
|
||||
xAxisMode: 'tick', // for short label ticks
|
||||
// or 'span' for long spanning vertical axis lines
|
||||
yAxisMode: 'span', // for long horizontal lines, or 'tick'
|
||||
isSeries: 1, // to allow for skipping of X values
|
||||
...</code></pre>
|
||||
<div id="chart-trends" class="border"></div>
|
||||
<div class="btn-group chart-plot-buttons mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="line">Line</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="dots">Dots</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-type="heatline">HeatLine</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-type="region">Region</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
type: 'line', // Line Chart specific properties:
|
||||
|
||||
hideDots: 1, // Hide data points on the line; default 0
|
||||
heatline: 1, // Show a value-wise line gradient; default 0
|
||||
regionFill: 1, // Fill the area under the graph; default 0
|
||||
...</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
Listen to state change
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<div id="chart-events" class="border"></div>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<div class="chart-events-data" class="border data-container">
|
||||
<div class="image-container border">
|
||||
<img class="moon-image" src="./assets/img/europa.jpg">
|
||||
</div>
|
||||
<div class="data margin-vertical-px">
|
||||
<h6 class="moon-name">Europa</h6>
|
||||
<p>Semi-major-axis: <span class="semi-major-axis">671034</span> km</p>
|
||||
<p>Mass: <span class="mass">4800000</span> x 10^16 kg</p>
|
||||
<p>Diameter: <span class="diameter">3121.6</span> km</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> ...
|
||||
type: 'bar', // Bar Chart specific properties:
|
||||
isNavigable: 1, // Navigate across bars; default 0
|
||||
...
|
||||
|
||||
chart.parent.addEventListener('data-select', (e) => {
|
||||
update_moon_data(e.index); // e contains index and value of current datapoint
|
||||
});</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
Simple Aggregations
|
||||
</h6>
|
||||
<div id="chart-aggr" class="border"></div>
|
||||
<div class="chart-aggr-buttons mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-aggregation="sums">Show Sums</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-aggregation="average">Show Averages</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> chart.show_sums(); // and `hide_sums()`
|
||||
|
||||
chart.show_averages(); // and `hide_averages()`</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<h6 class="margin-vertical-rem">
|
||||
And a Month-wise Heatmap
|
||||
</h6>
|
||||
<div id="chart-heatmap" class="border"
|
||||
style="overflow: scroll; text-align: center; padding: 20px;"></div>
|
||||
<div class="heatmap-mode-buttons btn-group mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-mode="discrete">Discrete</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-mode="continuous">Continuous</button>
|
||||
</div>
|
||||
<div class="heatmap-color-buttons btn-group mt-1 mx-auto" role="group">
|
||||
<button type="button" class="btn btn-sm btn-secondary active" data-color="default">Default green</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-color="halloween">GitHub's Halloween</button>
|
||||
</div>
|
||||
<pre><code class="hljs javascript margin-vertical-px"> let heatmap = new Chart({
|
||||
parent: "#heatmap",
|
||||
type: 'heatmap',
|
||||
height: 115,
|
||||
data: heatmap_data, // object with date/timestamp-value pairs
|
||||
|
||||
discrete_domains: 1 // default: 0
|
||||
|
||||
start: start_date,
|
||||
// A Date object;
|
||||
// default: today's date in past year
|
||||
// for an annual heatmap
|
||||
|
||||
legend_colors: ['#ebedf0', '#fdf436', '#ffc700', '#ff9100', '#06001c'],
|
||||
// Set of five incremental colors,
|
||||
// beginning with a low-saturation color for zero data;
|
||||
// default: ['#ebedf0', '#c6e48b', '#7bc96f', '#239a3b', '#196127']
|
||||
|
||||
});</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 push-sm-1">
|
||||
<div class="dashboard-section">
|
||||
<!-- Closing -->
|
||||
<div class="text-center" style="margin-top: 70px">
|
||||
<a href="https://github.com/frappe/charts/archive/master.zip"><button class="large blue button">Download</button></a>
|
||||
<p style="margin-top: 3rem;margin-bottom: 1.5rem;"><a href="https://github.com/frappe/charts" target="_blank">View on GitHub</a></p>
|
||||
<p style="margin-top: 1rem;"><iframe src="https://ghbtns.com/github-btn.html?user=frappe&repo=charts&type=star&count=true" frameborder="0" scrolling="0" width="94px" height="20px"></iframe></p>
|
||||
<p>License: MIT</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="built-with-frappe text-center" style="margin-top: -20px">
|
||||
<img style="padding: 5px; width: 40px; background: #fff" class="frappe-bird" src="https://frappe.github.io/frappe/assets/img/frappe-bird-grey.svg">
|
||||
<p style="margin: 24px 0 0px 0; font-size: 15px">
|
||||
Project maintained by <a href="https://frappe.io" target="_blank">Frappe</a>.
|
||||
Used in <a href="https://erpnext.com" target="_blank">ERPNext</a>.
|
||||
Read the <a href="https://medium.com/@pratu16x7/so-we-decided-to-create-our-own-charts-a95cb5032c97" target="_blank">blog post</a>.
|
||||
</p>
|
||||
<p style="margin: 24px 0 80px 0; font-size: 12px">
|
||||
Data from the <a href="https://www.amsmeteors.org" target="_blank">American Meteor Society</a>,
|
||||
<a href="http://www.sidc.be/silso" target="_blank">SILSO</a> and
|
||||
<a href="https://api.nasa.gov/index.html" target="_blank">NASA Open APIs</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a href="https://github.com/frappe/charts" target="_blank" class="github-corner" aria-label="View source on Github">
|
||||
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#9a9a9a; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
|
||||
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
||||
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
|
||||
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
|
||||
</svg>
|
||||
</a>
|
||||
|
||||
<script src="assets/js/frappe-charts.min.js"></script>
|
||||
<script src="assets/js/old_index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -52,5 +52,8 @@
|
||||
"rollup-plugin-uglify": "^2.0.1",
|
||||
"rollup-plugin-uglify-es": "0.0.1",
|
||||
"rollup-watch": "^4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint": "^4.18.2"
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,17 +15,19 @@ import pkg from './package.json';
|
||||
|
||||
export default [
|
||||
{
|
||||
input: 'src/js/charts.js',
|
||||
input: 'src/js/chart.js',
|
||||
sourcemap: true,
|
||||
output: [
|
||||
{
|
||||
file: pkg.main,
|
||||
format: 'cjs',
|
||||
file: 'docs/assets/js/frappe-charts.min.js',
|
||||
format: 'iife',
|
||||
},
|
||||
{
|
||||
file: pkg.module,
|
||||
format: 'es',
|
||||
file: pkg.browser,
|
||||
format: 'iife',
|
||||
}
|
||||
],
|
||||
name: 'Chart',
|
||||
plugins: [
|
||||
postcss({
|
||||
preprocessor: (content, id) => new Promise((resolve, reject) => {
|
||||
@ -33,7 +35,6 @@ export default [
|
||||
resolve({ code: result.css.toString() })
|
||||
}),
|
||||
extensions: [ '.scss' ],
|
||||
// extract: 'dist/frappe-charts.min.css',
|
||||
plugins: [
|
||||
nested(),
|
||||
cssnext({ warnForDuplicates: false }),
|
||||
@ -56,7 +57,47 @@ export default [
|
||||
]
|
||||
},
|
||||
{
|
||||
input: 'src/js/charts.js',
|
||||
input: 'src/js/chart.js',
|
||||
output: [
|
||||
{
|
||||
file: pkg.main,
|
||||
format: 'cjs',
|
||||
},
|
||||
{
|
||||
file: pkg.module,
|
||||
format: 'es',
|
||||
}
|
||||
],
|
||||
plugins: [
|
||||
postcss({
|
||||
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/scss/**',
|
||||
]
|
||||
}),
|
||||
babel({
|
||||
exclude: 'node_modules/**',
|
||||
}),
|
||||
replace({
|
||||
exclude: 'node_modules/**',
|
||||
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
}),
|
||||
uglify()
|
||||
],
|
||||
},
|
||||
{
|
||||
input: 'src/js/chart.js',
|
||||
output: [
|
||||
{
|
||||
file: pkg.src,
|
||||
@ -87,46 +128,5 @@ export default [
|
||||
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
})
|
||||
],
|
||||
},
|
||||
{
|
||||
input: 'src/js/charts.js',
|
||||
output: [
|
||||
{
|
||||
file: 'docs/assets/js/frappe-charts.min.js',
|
||||
format: 'iife',
|
||||
},
|
||||
{
|
||||
file: pkg.browser,
|
||||
format: 'iife',
|
||||
}
|
||||
],
|
||||
name: 'Chart',
|
||||
plugins: [
|
||||
postcss({
|
||||
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/scss/**',
|
||||
]
|
||||
}),
|
||||
babel({
|
||||
exclude: 'node_modules/**',
|
||||
}),
|
||||
replace({
|
||||
exclude: 'node_modules/**',
|
||||
ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
|
||||
}),
|
||||
uglify()
|
||||
],
|
||||
}
|
||||
];
|
||||
|
||||
40
src/js/chart.js
Normal file
40
src/js/chart.js
Normal file
@ -0,0 +1,40 @@
|
||||
import '../scss/charts.scss';
|
||||
|
||||
// import MultiAxisChart from './charts/MultiAxisChart';
|
||||
import PercentageChart from './charts/PercentageChart';
|
||||
import PieChart from './charts/PieChart';
|
||||
import Heatmap from './charts/Heatmap';
|
||||
import AxisChart from './charts/AxisChart';
|
||||
|
||||
const chartTypes = {
|
||||
// multiaxis: MultiAxisChart,
|
||||
percentage: PercentageChart,
|
||||
heatmap: Heatmap,
|
||||
pie: PieChart
|
||||
};
|
||||
|
||||
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') {
|
||||
options.type = 'line';
|
||||
return new AxisChart(parent, options);
|
||||
}
|
||||
|
||||
if (!chartTypes[chartType]) {
|
||||
console.error("Undefined chart type: " + chartType);
|
||||
return;
|
||||
}
|
||||
|
||||
return new chartTypes[chartType](parent, options);
|
||||
}
|
||||
|
||||
export default class Chart {
|
||||
constructor(parent, options) {
|
||||
return getChartByType(options.type, parent, options);
|
||||
}
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
import '../scss/charts.scss';
|
||||
|
||||
import BarChart from './charts/BarChart';
|
||||
import LineChart from './charts/LineChart';
|
||||
import ScatterChart from './charts/ScatterChart';
|
||||
import PercentageChart from './charts/PercentageChart';
|
||||
import PieChart from './charts/PieChart';
|
||||
import Heatmap from './charts/Heatmap';
|
||||
|
||||
// if (ENV !== 'production') {
|
||||
// // Enable LiveReload
|
||||
// document.write(
|
||||
// '<script src="http://' + (location.host || 'localhost').split(':')[0] +
|
||||
// ':35729/livereload.js?snipver=1"></' + 'script>'
|
||||
// );
|
||||
// }
|
||||
|
||||
const chartTypes = {
|
||||
line: LineChart,
|
||||
bar: BarChart,
|
||||
scatter: ScatterChart,
|
||||
percentage: PercentageChart,
|
||||
heatmap: Heatmap,
|
||||
pie: PieChart
|
||||
};
|
||||
|
||||
function getChartByType(chartType = 'line', options) {
|
||||
if (!chartTypes[chartType]) {
|
||||
return new LineChart(options);
|
||||
}
|
||||
|
||||
return new chartTypes[chartType](options);
|
||||
}
|
||||
|
||||
export default class Chart {
|
||||
constructor(args) {
|
||||
return getChartByType(args.type, arguments[0]);
|
||||
}
|
||||
}
|
||||
72
src/js/charts/AggregationChart.js
Normal file
72
src/js/charts/AggregationChart.js
Normal file
@ -0,0 +1,72 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { $, getOffset } from '../utils/dom';
|
||||
|
||||
export default class AggregationChart extends BaseChart {
|
||||
constructor(parent, args) {
|
||||
super(parent, args);
|
||||
}
|
||||
|
||||
configure(args) {
|
||||
super.configure(args);
|
||||
|
||||
this.config.maxSlices = args.maxSlices || 20;
|
||||
this.config.maxLegendPoints = args.maxLegendPoints || 20;
|
||||
}
|
||||
|
||||
calc() {
|
||||
let s = this.state;
|
||||
let maxSlices = this.config.maxSlices;
|
||||
s.sliceTotals = [];
|
||||
|
||||
let allTotals = this.data.labels.map((label, i) => {
|
||||
let total = 0;
|
||||
this.data.datasets.map(e => {
|
||||
total += e.values[i];
|
||||
});
|
||||
return [total, label];
|
||||
}).filter(d => { return d[0] > 0; }); // keep only positive results
|
||||
|
||||
let totals = allTotals;
|
||||
if(allTotals.length > maxSlices) {
|
||||
// Prune and keep a grey area for rest as per maxSlices
|
||||
allTotals.sort((a, b) => { return b[0] - a[0]; });
|
||||
|
||||
totals = allTotals.slice(0, maxSlices-1);
|
||||
let remaining = allTotals.slice(maxSlices-1);
|
||||
|
||||
let sumOfRemaining = 0;
|
||||
remaining.map(d => {sumOfRemaining += d[0];});
|
||||
totals.push([sumOfRemaining, 'Rest']);
|
||||
this.colors[maxSlices-1] = 'grey';
|
||||
}
|
||||
|
||||
s.labels = [];
|
||||
totals.map(d => {
|
||||
s.sliceTotals.push(d[0]);
|
||||
s.labels.push(d[1]);
|
||||
});
|
||||
}
|
||||
|
||||
renderLegend() {
|
||||
let s = this.state;
|
||||
|
||||
this.statsWrapper.textContent = '';
|
||||
|
||||
this.legendTotals = s.sliceTotals.slice(0, this.config.maxLegendPoints);
|
||||
|
||||
let xValues = s.labels;
|
||||
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>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,81 +0,0 @@
|
||||
import AxisChart from './AxisChart';
|
||||
|
||||
export default class BarChart extends AxisChart {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
|
||||
this.type = 'bar';
|
||||
this.x_axis_mode = args.x_axis_mode || 'tick';
|
||||
this.y_axis_mode = args.y_axis_mode || 'span';
|
||||
this.setup();
|
||||
}
|
||||
|
||||
setup_values() {
|
||||
super.setup_values();
|
||||
this.x_offset = this.avg_unit_width;
|
||||
this.unit_args = {
|
||||
type: 'bar',
|
||||
args: {
|
||||
spaceWidth: this.avg_unit_width/2,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
make_overlay() {
|
||||
// Just make one out of the first element
|
||||
let index = this.x.length - 1;
|
||||
let unit = this.y[0].svg_units[index];
|
||||
this.update_current_data_point(index);
|
||||
|
||||
if(this.overlay) {
|
||||
this.overlay.parentNode.removeChild(this.overlay);
|
||||
}
|
||||
this.overlay = unit.cloneNode();
|
||||
this.overlay.style.fill = '#000000';
|
||||
this.overlay.style.opacity = '0.4';
|
||||
this.draw_area.appendChild(this.overlay);
|
||||
}
|
||||
|
||||
bind_overlay() {
|
||||
// on event, update overlay
|
||||
this.parent.addEventListener('data-select', (e) => {
|
||||
this.update_overlay(e.svg_unit);
|
||||
});
|
||||
}
|
||||
|
||||
bind_units(units_array) {
|
||||
units_array.map(unit => {
|
||||
unit.addEventListener('click', () => {
|
||||
let index = unit.getAttribute('data-point-index');
|
||||
this.update_current_data_point(index);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
update_overlay(unit) {
|
||||
let attributes = [];
|
||||
Object.keys(unit.attributes).map(index => {
|
||||
attributes.push(unit.attributes[index]);
|
||||
});
|
||||
|
||||
attributes.filter(attr => attr.specified).map(attr => {
|
||||
this.overlay.setAttribute(attr.name, attr.nodeValue);
|
||||
});
|
||||
|
||||
this.overlay.style.fill = '#000000';
|
||||
this.overlay.style.opacity = '0.4';
|
||||
}
|
||||
|
||||
on_left_arrow() {
|
||||
this.update_current_data_point(this.current_index - 1);
|
||||
}
|
||||
|
||||
on_right_arrow() {
|
||||
this.update_current_data_point(this.current_index + 1);
|
||||
}
|
||||
|
||||
set_avg_unit_width_and_x_offset() {
|
||||
this.avg_unit_width = this.width/(this.x.length + 1);
|
||||
this.x_offset = this.avg_unit_width;
|
||||
}
|
||||
}
|
||||
@ -2,177 +2,106 @@ import SvgTip from '../objects/SvgTip';
|
||||
import { $, isElementInViewport, getElementContentWidth } from '../utils/dom';
|
||||
import { makeSVGContainer, makeSVGDefs, makeSVGGroup } from '../utils/draw';
|
||||
import { getStringWidth } from '../utils/helpers';
|
||||
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 Chart from '../charts';
|
||||
|
||||
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: []
|
||||
};
|
||||
|
||||
// TODO: 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: []
|
||||
};
|
||||
import { getDifferentChart } from '../config';
|
||||
import { runSMILAnimation } from '../utils/animation';
|
||||
|
||||
export default class BaseChart {
|
||||
constructor({
|
||||
height = 240,
|
||||
|
||||
title = '',
|
||||
subtitle = '',
|
||||
colors = [],
|
||||
summary = [],
|
||||
|
||||
is_navigable = 0,
|
||||
has_legend = 0,
|
||||
|
||||
type = '',
|
||||
|
||||
parent,
|
||||
data
|
||||
}) {
|
||||
this.raw_chart_args = arguments[0];
|
||||
constructor(parent, options) {
|
||||
this.rawChartArgs = options;
|
||||
|
||||
this.parent = typeof parent === 'string' ? document.querySelector(parent) : parent;
|
||||
this.title = title;
|
||||
this.subtitle = subtitle;
|
||||
|
||||
this.data = data;
|
||||
|
||||
this.specific_values = data.specific_values || [];
|
||||
this.summary = summary;
|
||||
|
||||
this.is_navigable = is_navigable;
|
||||
if(this.is_navigable) {
|
||||
this.current_index = 0;
|
||||
if (!(this.parent instanceof HTMLElement)) {
|
||||
throw new Error('No `parent` element to render on was provided.');
|
||||
}
|
||||
this.has_legend = has_legend;
|
||||
|
||||
this.setColors(colors, type);
|
||||
this.set_margins(height);
|
||||
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.config = {
|
||||
showTooltip: 1, // calculate
|
||||
showLegend: options.showLegend || 1,
|
||||
isNavigable: options.isNavigable || 0,
|
||||
animate: 1
|
||||
};
|
||||
this.state = {};
|
||||
this.options = {};
|
||||
|
||||
this.initTimeout = INIT_CHART_UPDATE_TIMEOUT;
|
||||
|
||||
if(this.config.isNavigable) {
|
||||
this.overlays = [];
|
||||
}
|
||||
|
||||
this.configure(options);
|
||||
}
|
||||
|
||||
get_different_chart(type) {
|
||||
if(type === this.type) return;
|
||||
configure(args) {
|
||||
this.setColors();
|
||||
this.setMargins();
|
||||
|
||||
if(!ALL_CHART_TYPES.includes(type)) {
|
||||
console.error(`'${type}' is not a valid chart type.`);
|
||||
}
|
||||
|
||||
if(!COMPATIBLE_CHARTS[this.type].includes(type)) {
|
||||
console.error(`'${this.type}' chart cannot be converted to a '${type}' chart.`);
|
||||
}
|
||||
|
||||
// whether the new chart can use the existing colors
|
||||
const use_color = COLOR_COMPATIBLE_CHARTS[this.type].includes(type);
|
||||
|
||||
// Okay, this is anticlimactic
|
||||
// this function will need to actually be 'change_chart_type(type)'
|
||||
// that will update only the required elements, but for now ...
|
||||
return new Chart({
|
||||
parent: this.raw_chart_args.parent,
|
||||
title: this.title,
|
||||
data: this.raw_chart_args.data,
|
||||
type: type,
|
||||
height: this.raw_chart_args.height,
|
||||
colors: use_color ? this.colors : undefined
|
||||
});
|
||||
// Bind window events
|
||||
window.addEventListener('resize', () => this.draw(true));
|
||||
window.addEventListener('orientationchange', () => this.draw(true));
|
||||
}
|
||||
|
||||
setColors(colors, type) {
|
||||
this.colors = colors;
|
||||
setColors() {
|
||||
let args = this.rawChartArgs;
|
||||
|
||||
// TODO: Needs structure as per only labels/datasets
|
||||
const list = type === 'percentage' || type === 'pie'
|
||||
? this.data.labels
|
||||
: this.data.datasets;
|
||||
// Needs structure as per only labels/datasets, from config
|
||||
const list = args.type === 'percentage' || args.type === 'pie'
|
||||
? args.data.labels
|
||||
: args.data.datasets;
|
||||
|
||||
if(!this.colors || (list && this.colors.length < list.length)) {
|
||||
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));
|
||||
}
|
||||
|
||||
set_margins(height) {
|
||||
this.base_height = height;
|
||||
this.height = height - 40;
|
||||
this.translate_x = 60;
|
||||
this.translate_y = 10;
|
||||
setMargins() {
|
||||
let height = this.argHeight;
|
||||
this.baseHeight = height;
|
||||
this.height = height - VERT_SPACE_OUTSIDE_BASE_CHART;
|
||||
this.translateY = TRANSLATE_Y_BASE_CHART;
|
||||
|
||||
// Horizontal margins
|
||||
this.leftMargin = LEFT_MARGIN_BASE_CHART;
|
||||
this.rightMargin = RIGHT_MARGIN_BASE_CHART;
|
||||
}
|
||||
|
||||
setup() {
|
||||
if(!this.parent) {
|
||||
console.error("No parent element to render on was provided.");
|
||||
return;
|
||||
}
|
||||
if(this.validate_and_prepare_data()) {
|
||||
this.bind_window_events();
|
||||
this.refresh(true);
|
||||
}
|
||||
}
|
||||
|
||||
validate_and_prepare_data() {
|
||||
validate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bind_window_events() {
|
||||
window.addEventListener('resize', () => this.refresh());
|
||||
window.addEventListener('orientationchange', () => this.refresh());
|
||||
}
|
||||
|
||||
refresh(init=false) {
|
||||
this.setup_base_values();
|
||||
this.set_width();
|
||||
|
||||
this.setup_container();
|
||||
this.setup_components();
|
||||
|
||||
this.setup_values();
|
||||
this.setup_utils();
|
||||
|
||||
this.make_graph_components(init);
|
||||
this.make_tooltip();
|
||||
|
||||
if(this.summary.length > 0) {
|
||||
this.show_custom_summary();
|
||||
} else {
|
||||
this.show_summary();
|
||||
}
|
||||
|
||||
if(this.is_navigable) {
|
||||
this.setup_navigation(init);
|
||||
setup() {
|
||||
if(this.validate()) {
|
||||
this._setup();
|
||||
}
|
||||
}
|
||||
|
||||
set_width() {
|
||||
let special_values_width = 0;
|
||||
let char_width = 8;
|
||||
this.specific_values.map(val => {
|
||||
let str_width = getStringWidth((val.title + ""), char_width);
|
||||
if(str_width > special_values_width) {
|
||||
special_values_width = str_width - 40;
|
||||
}
|
||||
});
|
||||
this.base_width = getElementContentWidth(this.parent) - special_values_width;
|
||||
this.width = this.base_width - this.translate_x * 2;
|
||||
_setup() {
|
||||
this.makeContainer();
|
||||
this.makeTooltip();
|
||||
|
||||
this.draw(false, true);
|
||||
}
|
||||
|
||||
setup_base_values() {}
|
||||
setupComponents() {
|
||||
this.components = new Map();
|
||||
}
|
||||
|
||||
setup_container() {
|
||||
makeContainer() {
|
||||
this.container = $.create('div', {
|
||||
className: 'chart-container',
|
||||
innerHTML: `<h6 class="title">${this.title}</h6>
|
||||
@ -185,120 +114,175 @@ export default class BaseChart {
|
||||
this.parent.innerHTML = '';
|
||||
this.parent.appendChild(this.container);
|
||||
|
||||
this.chart_wrapper = this.container.querySelector('.frappe-chart');
|
||||
this.stats_wrapper = this.container.querySelector('.graph-stats-container');
|
||||
|
||||
this.make_chart_area();
|
||||
this.make_draw_area();
|
||||
this.chartWrapper = this.container.querySelector('.frappe-chart');
|
||||
this.statsWrapper = this.container.querySelector('.graph-stats-container');
|
||||
}
|
||||
|
||||
make_chart_area() {
|
||||
this.svg = makeSVGContainer(
|
||||
this.chart_wrapper,
|
||||
'chart',
|
||||
this.base_width,
|
||||
this.base_height
|
||||
);
|
||||
this.svg_defs = makeSVGDefs(this.svg);
|
||||
return this.svg;
|
||||
}
|
||||
|
||||
make_draw_area() {
|
||||
this.draw_area = makeSVGGroup(
|
||||
this.svg,
|
||||
this.type + '-chart',
|
||||
`translate(${this.translate_x}, ${this.translate_y})`
|
||||
);
|
||||
}
|
||||
|
||||
setup_components() { }
|
||||
|
||||
make_tooltip() {
|
||||
makeTooltip() {
|
||||
this.tip = new SvgTip({
|
||||
parent: this.chart_wrapper,
|
||||
parent: this.chartWrapper,
|
||||
colors: this.colors
|
||||
});
|
||||
this.bind_tooltip();
|
||||
this.bindTooltip();
|
||||
}
|
||||
|
||||
bindTooltip() {}
|
||||
|
||||
show_summary() {}
|
||||
show_custom_summary() {
|
||||
this.summary.map(d => {
|
||||
let stats = $.create('div', {
|
||||
className: 'stats',
|
||||
innerHTML: `<span class="indicator">
|
||||
<i style="background:${d.color}"></i>
|
||||
${d.title}: ${d.value}
|
||||
</span>`
|
||||
});
|
||||
this.stats_wrapper.appendChild(stats);
|
||||
});
|
||||
}
|
||||
draw(onlyWidthChange=false, init=false) {
|
||||
this.calcWidth();
|
||||
this.calc(onlyWidthChange);
|
||||
this.makeChartArea();
|
||||
this.setupComponents();
|
||||
|
||||
setup_navigation(init=false) {
|
||||
this.make_overlay();
|
||||
this.components.forEach(c => c.setup(this.drawArea));
|
||||
// this.components.forEach(c => c.make());
|
||||
this.render(this.components, false);
|
||||
|
||||
if(init) {
|
||||
this.bind_overlay();
|
||||
this.data = this.realData;
|
||||
setTimeout(() => {this.update();}, this.initTimeout);
|
||||
}
|
||||
|
||||
if(!onlyWidthChange) {
|
||||
this.renderLegend();
|
||||
}
|
||||
|
||||
this.setupNavigation(init);
|
||||
}
|
||||
|
||||
calcWidth() {
|
||||
this.baseWidth = getElementContentWidth(this.parent);
|
||||
this.width = this.baseWidth - (this.leftMargin + this.rightMargin);
|
||||
}
|
||||
|
||||
update(data=this.data) {
|
||||
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
|
||||
this.overlays.map(o => o.parentNode.removeChild(o));
|
||||
// ref.parentNode.insertBefore(element, ref);
|
||||
}
|
||||
let elementsToAnimate = [];
|
||||
// Can decouple to this.refreshComponents() first to save animation timeout
|
||||
components.forEach(c => {
|
||||
elementsToAnimate = elementsToAnimate.concat(c.update(animate));
|
||||
});
|
||||
if(elementsToAnimate.length > 0) {
|
||||
runSMILAnimation(this.chartWrapper, this.svg, elementsToAnimate);
|
||||
setTimeout(() => {
|
||||
components.forEach(c => c.make());
|
||||
this.updateNav();
|
||||
}, CHART_POST_ANIMATE_TIMEOUT);
|
||||
} else {
|
||||
components.forEach(c => c.make());
|
||||
this.updateNav();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if(!this.config.isNavigable) return;
|
||||
|
||||
if(init) {
|
||||
this.bindOverlay();
|
||||
|
||||
this.keyActions = {
|
||||
'13': this.onEnterKey.bind(this),
|
||||
'37': this.onLeftArrow.bind(this),
|
||||
'38': this.onUpArrow.bind(this),
|
||||
'39': this.onRightArrow.bind(this),
|
||||
'40': this.onDownArrow.bind(this),
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if(isElementInViewport(this.chart_wrapper)) {
|
||||
if(isElementInViewport(this.chartWrapper)) {
|
||||
e = e || window.event;
|
||||
|
||||
if (e.keyCode == '37') {
|
||||
this.on_left_arrow();
|
||||
} else if (e.keyCode == '39') {
|
||||
this.on_right_arrow();
|
||||
} else if (e.keyCode == '38') {
|
||||
this.on_up_arrow();
|
||||
} else if (e.keyCode == '40') {
|
||||
this.on_down_arrow();
|
||||
} else if (e.keyCode == '13') {
|
||||
this.on_enter_key();
|
||||
if(this.keyActions[e.keyCode]) {
|
||||
this.keyActions[e.keyCode]();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
make_overlay() {}
|
||||
bind_overlay() {}
|
||||
bind_units() {}
|
||||
makeOverlay() {}
|
||||
updateOverlay() {}
|
||||
bindOverlay() {}
|
||||
bindUnits() {}
|
||||
|
||||
on_left_arrow() {}
|
||||
on_right_arrow() {}
|
||||
on_up_arrow() {}
|
||||
on_down_arrow() {}
|
||||
on_enter_key() {}
|
||||
onLeftArrow() {}
|
||||
onRightArrow() {}
|
||||
onUpArrow() {}
|
||||
onDownArrow() {}
|
||||
onEnterKey() {}
|
||||
|
||||
get_data_point(index=this.current_index) {
|
||||
// check for length
|
||||
let data_point = {
|
||||
index: index
|
||||
};
|
||||
let y = this.y[0];
|
||||
['svg_units', 'y_tops', 'values'].map(key => {
|
||||
let data_key = key.slice(0, key.length-1);
|
||||
data_point[data_key] = y[key][index];
|
||||
});
|
||||
data_point.label = this.x[index];
|
||||
return data_point;
|
||||
}
|
||||
getDataPoint(index = 0) {}
|
||||
setCurrentDataPoint(point) {}
|
||||
|
||||
update_current_data_point(index) {
|
||||
index = parseInt(index);
|
||||
if(index < 0) index = 0;
|
||||
if(index >= this.x.length) index = this.x.length - 1;
|
||||
if(index === this.current_index) return;
|
||||
this.current_index = index;
|
||||
$.fire(this.parent, "data-select", this.get_data_point());
|
||||
}
|
||||
updateDataset(dataset, index) {}
|
||||
addDataset(dataset, index) {}
|
||||
removeDataset(index = 0) {}
|
||||
|
||||
// Objects
|
||||
setup_utils() { }
|
||||
updateDatasets(datasets) {}
|
||||
|
||||
makeDrawAreaComponent(className, transform='') {
|
||||
return makeSVGGroup(this.draw_area, className, transform);
|
||||
updateDataPoint(dataPoint, index = 0) {}
|
||||
addDataPoint(dataPoint, index = 0) {}
|
||||
removeDataPoint(index = 0) {}
|
||||
|
||||
getDifferentChart(type) {
|
||||
return getDifferentChart(type, this.type, this.parent, this.rawChartArgs);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,41 +5,39 @@ import { calcDistribution, getMaxCheckpoint } from '../utils/intervals';
|
||||
import { isValidColor } from '../utils/colors';
|
||||
|
||||
export default class Heatmap extends BaseChart {
|
||||
constructor({
|
||||
start = '',
|
||||
domain = '',
|
||||
subdomain = '',
|
||||
data = {},
|
||||
discrete_domains = 0,
|
||||
count_label = '',
|
||||
legend_colors = []
|
||||
}) {
|
||||
super(arguments[0]);
|
||||
constructor(parent, options) {
|
||||
super(parent, options);
|
||||
|
||||
this.type = 'heatmap';
|
||||
|
||||
this.domain = domain;
|
||||
this.subdomain = subdomain;
|
||||
this.data = data;
|
||||
this.discrete_domains = discrete_domains;
|
||||
this.count_label = count_label;
|
||||
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 = start || addDays(today, 365);
|
||||
this.start = options.start || addDays(today, 365);
|
||||
|
||||
legend_colors = legend_colors.slice(0, 5);
|
||||
this.legend_colors = this.validate_colors(legend_colors)
|
||||
? legend_colors
|
||||
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.translate_x = 0;
|
||||
this.translateX = 0;
|
||||
this.setup();
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
this.leftMargin = 10;
|
||||
this.translateY = 10;
|
||||
}
|
||||
|
||||
validate_colors(colors) {
|
||||
if(colors.length < 5) return 0;
|
||||
|
||||
@ -54,210 +52,212 @@ export default class Heatmap extends BaseChart {
|
||||
return valid;
|
||||
}
|
||||
|
||||
setup_base_values() {
|
||||
configure() {
|
||||
super.configure();
|
||||
this.today = new Date();
|
||||
|
||||
if(!this.start) {
|
||||
this.start = new Date();
|
||||
this.start.setFullYear( this.start.getFullYear() - 1 );
|
||||
}
|
||||
this.first_week_start = new Date(this.start.toDateString());
|
||||
this.last_week_start = new Date(this.today.toDateString());
|
||||
if(this.first_week_start.getDay() !== 7) {
|
||||
addDays(this.first_week_start, (-1) * this.first_week_start.getDay());
|
||||
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(this.last_week_start.getDay() !== 7) {
|
||||
addDays(this.last_week_start, (-1) * this.last_week_start.getDay());
|
||||
if(this.lastWeekStart.getDay() !== 7) {
|
||||
addDays(this.lastWeekStart, (-1) * this.lastWeekStart.getDay());
|
||||
}
|
||||
this.no_of_cols = getWeeksBetween(this.first_week_start + '', this.last_week_start + '') + 1;
|
||||
this.no_of_cols = getWeeksBetween(this.firstWeekStart + '', this.lastWeekStart + '') + 1;
|
||||
}
|
||||
|
||||
set_width() {
|
||||
this.base_width = (this.no_of_cols + 3) * 12 ;
|
||||
calcWidth() {
|
||||
this.baseWidth = (this.no_of_cols + 3) * 12 ;
|
||||
|
||||
if(this.discrete_domains) {
|
||||
this.base_width += (12 * 12);
|
||||
if(this.discreteDomains) {
|
||||
this.baseWidth += (12 * 12);
|
||||
}
|
||||
}
|
||||
|
||||
setup_components() {
|
||||
this.domain_label_group = this.makeDrawAreaComponent(
|
||||
makeChartArea() {
|
||||
super.makeChartArea();
|
||||
this.domainLabelGroup = makeSVGGroup(this.drawArea,
|
||||
'domain-label-group chart-label');
|
||||
|
||||
this.data_groups = this.makeDrawAreaComponent(
|
||||
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';
|
||||
}
|
||||
|
||||
setup_values() {
|
||||
this.domain_label_group.textContent = '';
|
||||
this.data_groups.textContent = '';
|
||||
calc() {
|
||||
|
||||
let data_values = Object.keys(this.data).map(key => this.data[key]);
|
||||
this.distribution = calcDistribution(data_values, this.distribution_size);
|
||||
let dataValues = Object.keys(this.data).map(key => this.data[key]);
|
||||
this.distribution = calcDistribution(dataValues, this.distribution_size);
|
||||
|
||||
this.month_names = ["January", "February", "March", "April", "May", "June",
|
||||
this.monthNames = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
|
||||
this.render_all_weeks_and_store_x_values(this.no_of_cols);
|
||||
}
|
||||
|
||||
render_all_weeks_and_store_x_values(no_of_weeks) {
|
||||
let current_week_sunday = new Date(this.first_week_start);
|
||||
this.week_col = 0;
|
||||
this.current_month = current_week_sunday.getMonth();
|
||||
render() {
|
||||
this.renderAllWeeksAndStoreXValues(this.no_of_cols);
|
||||
}
|
||||
|
||||
this.months = [this.current_month + ''];
|
||||
this.month_weeks = {}, this.month_start_points = [];
|
||||
this.month_weeks[this.current_month] = 0;
|
||||
this.month_start_points.push(13);
|
||||
renderAllWeeksAndStoreXValues(no_of_weeks) {
|
||||
// renderAllWeeksAndStoreXValues
|
||||
this.domainLabelGroup.textContent = '';
|
||||
this.dataGroups.textContent = '';
|
||||
|
||||
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);
|
||||
|
||||
for(var i = 0; i < no_of_weeks; i++) {
|
||||
let data_group, month_change = 0;
|
||||
let day = new Date(current_week_sunday);
|
||||
let dataGroup, monthChange = 0;
|
||||
let day = new Date(currentWeekSunday);
|
||||
|
||||
[data_group, month_change] = this.get_week_squares_group(day, this.week_col);
|
||||
this.data_groups.appendChild(data_group);
|
||||
this.week_col += 1 + parseInt(this.discrete_domains && month_change);
|
||||
this.month_weeks[this.current_month]++;
|
||||
if(month_change) {
|
||||
this.current_month = (this.current_month + 1) % 12;
|
||||
this.months.push(this.current_month + '');
|
||||
this.month_weeks[this.current_month] = 1;
|
||||
[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;
|
||||
}
|
||||
addDays(current_week_sunday, 7);
|
||||
addDays(currentWeekSunday, 7);
|
||||
}
|
||||
this.render_month_labels();
|
||||
}
|
||||
|
||||
get_week_squares_group(current_date, index) {
|
||||
const no_of_weekdays = 7;
|
||||
const square_side = 10;
|
||||
const cell_padding = 2;
|
||||
get_week_squares_group(currentDate, index) {
|
||||
const noOfWeekdays = 7;
|
||||
const squareSide = 10;
|
||||
const cellPadding = 2;
|
||||
const step = 1;
|
||||
const today_time = this.today.getTime();
|
||||
const todayTime = this.today.getTime();
|
||||
|
||||
let month_change = 0;
|
||||
let week_col_change = 0;
|
||||
let monthChange = 0;
|
||||
let weekColChange = 0;
|
||||
|
||||
let data_group = makeSVGGroup(this.data_groups, 'data-group');
|
||||
let dataGroup = makeSVGGroup(this.dataGroups, 'data-group');
|
||||
|
||||
for(var y = 0, i = 0; i < no_of_weekdays; i += step, y += (square_side + cell_padding)) {
|
||||
let data_value = 0;
|
||||
let color_index = 0;
|
||||
for(var y = 0, i = 0; i < noOfWeekdays; i += step, y += (squareSide + cellPadding)) {
|
||||
let dataValue = 0;
|
||||
let colorIndex = 0;
|
||||
|
||||
let current_timestamp = current_date.getTime()/1000;
|
||||
let timestamp = Math.floor(current_timestamp - (current_timestamp % 86400)).toFixed(1);
|
||||
let currentTimestamp = currentDate.getTime()/1000;
|
||||
let timestamp = Math.floor(currentTimestamp - (currentTimestamp % 86400)).toFixed(1);
|
||||
|
||||
if(this.data[timestamp]) {
|
||||
data_value = this.data[timestamp];
|
||||
dataValue = this.data[timestamp];
|
||||
}
|
||||
|
||||
if(this.data[Math.round(timestamp)]) {
|
||||
data_value = this.data[Math.round(timestamp)];
|
||||
dataValue = this.data[Math.round(timestamp)];
|
||||
}
|
||||
|
||||
if(data_value) {
|
||||
color_index = getMaxCheckpoint(data_value, this.distribution);
|
||||
if(dataValue) {
|
||||
colorIndex = getMaxCheckpoint(dataValue, this.distribution);
|
||||
}
|
||||
|
||||
let x = 13 + (index + week_col_change) * 12;
|
||||
let x = 13 + (index + weekColChange) * 12;
|
||||
|
||||
let dataAttr = {
|
||||
'data-date': getDdMmYyyy(current_date),
|
||||
'data-value': data_value,
|
||||
'data-day': current_date.getDay()
|
||||
'data-date': getDdMmYyyy(currentDate),
|
||||
'data-value': dataValue,
|
||||
'data-day': currentDate.getDay()
|
||||
};
|
||||
let heatSquare = makeHeatSquare('day', x, y, square_side,
|
||||
this.legend_colors[color_index], dataAttr);
|
||||
|
||||
data_group.appendChild(heatSquare);
|
||||
let heatSquare = makeHeatSquare('day', x, y, squareSide,
|
||||
this.legendColors[colorIndex], dataAttr);
|
||||
|
||||
let next_date = new Date(current_date);
|
||||
addDays(next_date, 1);
|
||||
if(next_date.getTime() > today_time) break;
|
||||
dataGroup.appendChild(heatSquare);
|
||||
|
||||
let nextDate = new Date(currentDate);
|
||||
addDays(nextDate, 1);
|
||||
if(nextDate.getTime() > todayTime) break;
|
||||
|
||||
|
||||
if(next_date.getMonth() - current_date.getMonth()) {
|
||||
month_change = 1;
|
||||
if(this.discrete_domains) {
|
||||
week_col_change = 1;
|
||||
if(nextDate.getMonth() - currentDate.getMonth()) {
|
||||
monthChange = 1;
|
||||
if(this.discreteDomains) {
|
||||
weekColChange = 1;
|
||||
}
|
||||
|
||||
this.month_start_points.push(13 + (index + week_col_change) * 12);
|
||||
this.monthStartPoints.push(13 + (index + weekColChange) * 12);
|
||||
}
|
||||
current_date = next_date;
|
||||
currentDate = nextDate;
|
||||
}
|
||||
|
||||
return [data_group, month_change];
|
||||
return [dataGroup, monthChange];
|
||||
}
|
||||
|
||||
render_month_labels() {
|
||||
// this.first_month_label = 1;
|
||||
// if (this.first_week_start.getDate() > 8) {
|
||||
// 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.month_start_points.shift();
|
||||
// let first_month_start = this.monthStartPoints.shift();
|
||||
// render first month if
|
||||
|
||||
// let last_month = this.months.pop();
|
||||
// let last_month_start = this.month_start_points.pop();
|
||||
// let last_month_start = this.monthStartPoints.pop();
|
||||
// render last month if
|
||||
|
||||
this.months.shift();
|
||||
this.month_start_points.shift();
|
||||
this.monthStartPoints.shift();
|
||||
this.months.pop();
|
||||
this.month_start_points.pop();
|
||||
this.monthStartPoints.pop();
|
||||
|
||||
this.month_start_points.map((start, i) => {
|
||||
let month_name = this.month_names[this.months[i]].substring(0, 3);
|
||||
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.domain_label_group.appendChild(text);
|
||||
this.domainLabelGroup.appendChild(text);
|
||||
});
|
||||
}
|
||||
|
||||
make_graph_components() {
|
||||
Array.prototype.slice.call(
|
||||
this.container.querySelectorAll('.graph-stats-container, .sub-title, .title')
|
||||
).map(d => {
|
||||
d.style.display = 'None';
|
||||
});
|
||||
this.chart_wrapper.style.marginTop = '0px';
|
||||
this.chart_wrapper.style.paddingTop = '0px';
|
||||
}
|
||||
|
||||
bind_tooltip() {
|
||||
bindTooltip() {
|
||||
Array.prototype.slice.call(
|
||||
document.querySelectorAll(".data-group .day")
|
||||
).map(el => {
|
||||
el.addEventListener('mouseenter', (e) => {
|
||||
let count = e.target.getAttribute('data-value');
|
||||
let date_parts = e.target.getAttribute('data-date').split('-');
|
||||
let dateParts = e.target.getAttribute('data-date').split('-');
|
||||
|
||||
let month = this.month_names[parseInt(date_parts[1])-1].substring(0, 3);
|
||||
let month = this.monthNames[parseInt(dateParts[1])-1].substring(0, 3);
|
||||
|
||||
let g_off = this.chart_wrapper.getBoundingClientRect(), p_off = e.target.getBoundingClientRect();
|
||||
let gOff = this.chartWrapper.getBoundingClientRect(), pOff = e.target.getBoundingClientRect();
|
||||
|
||||
let width = parseInt(e.target.getAttribute('width'));
|
||||
let x = p_off.left - g_off.left + (width+2)/2;
|
||||
let y = p_off.top - g_off.top - (width+2)/2;
|
||||
let value = count + ' ' + this.count_label;
|
||||
let name = ' on ' + month + ' ' + date_parts[0] + ', ' + date_parts[2];
|
||||
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.set_values(x, y, name, value, [], 1);
|
||||
this.tip.show_tip();
|
||||
this.tip.setValues(x, y, {name: name, value: value, valueFirst: 1}, []);
|
||||
this.tip.showTip();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
update(data) {
|
||||
this.data = data;
|
||||
this.setup_values();
|
||||
this.bind_tooltip();
|
||||
super.update(data);
|
||||
this.bindTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,91 +0,0 @@
|
||||
import AxisChart from './AxisChart';
|
||||
import { makeSVGGroup, makePath, makeGradient } from '../utils/draw';
|
||||
|
||||
export default class LineChart extends AxisChart {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
|
||||
this.x_axis_mode = args.x_axis_mode || 'span';
|
||||
this.y_axis_mode = args.y_axis_mode || 'span';
|
||||
|
||||
if(args.hasOwnProperty('show_dots')) {
|
||||
this.show_dots = args.show_dots;
|
||||
} else {
|
||||
this.show_dots = 1;
|
||||
}
|
||||
this.region_fill = args.region_fill;
|
||||
|
||||
if(Object.getPrototypeOf(this) !== LineChart.prototype) {
|
||||
return;
|
||||
}
|
||||
this.dot_radius = args.dot_radius || 4;
|
||||
this.heatline = args.heatline;
|
||||
this.type = 'line';
|
||||
|
||||
this.setup();
|
||||
}
|
||||
|
||||
setup_graph_components() {
|
||||
this.setup_path_groups();
|
||||
super.setup_graph_components();
|
||||
}
|
||||
|
||||
setup_path_groups() {
|
||||
this.paths_groups = [];
|
||||
this.y.map((d, i) => {
|
||||
this.paths_groups[i] = makeSVGGroup(
|
||||
this.draw_area,
|
||||
'path-group path-group-' + i
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setup_values() {
|
||||
super.setup_values();
|
||||
this.unit_args = {
|
||||
type: 'dot',
|
||||
args: { radius: this.dot_radius }
|
||||
};
|
||||
}
|
||||
|
||||
make_new_units_for_dataset(x_values, y_values, color, dataset_index,
|
||||
no_of_datasets, units_group, units_array, unit) {
|
||||
if(this.show_dots) {
|
||||
super.make_new_units_for_dataset(x_values, y_values, color, dataset_index,
|
||||
no_of_datasets, units_group, units_array, unit);
|
||||
}
|
||||
}
|
||||
|
||||
make_paths() {
|
||||
this.y.map((d, i) => {
|
||||
this.make_path(d, i, this.x_axis_positions, d.y_tops, d.color || this.colors[i]);
|
||||
});
|
||||
}
|
||||
|
||||
make_path(d, i, x_positions, y_positions, color) {
|
||||
let points_list = y_positions.map((y, i) => (x_positions[i] + ',' + y));
|
||||
let points_str = points_list.join("L");
|
||||
|
||||
this.paths_groups[i].textContent = '';
|
||||
|
||||
d.path = makePath("M"+points_str, 'line-graph-path', color);
|
||||
this.paths_groups[i].appendChild(d.path);
|
||||
|
||||
if(this.heatline) {
|
||||
let gradient_id = makeGradient(this.svg_defs, color);
|
||||
d.path.style.stroke = `url(#${gradient_id})`;
|
||||
}
|
||||
|
||||
if(this.region_fill) {
|
||||
this.fill_region_for_dataset(d, i, color, points_str);
|
||||
}
|
||||
}
|
||||
|
||||
fill_region_for_dataset(d, i, color, points_str) {
|
||||
let gradient_id = makeGradient(this.svg_defs, color, true);
|
||||
let pathStr = "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`;
|
||||
|
||||
d.regionPath = makePath(pathStr, `region-fill`, 'none', `url(#${gradient_id})`);
|
||||
this.paths_groups[i].appendChild(d.regionPath);
|
||||
}
|
||||
}
|
||||
173
src/js/charts/MultiAxisChart.js
Normal file
173
src/js/charts/MultiAxisChart.js
Normal file
@ -0,0 +1,173 @@
|
||||
import AxisChart from './AxisChart';
|
||||
import { Y_AXIS_MARGIN } from '../utils/constants';
|
||||
// import { ChartComponent } from '../objects/ChartComponents';
|
||||
import { floatTwo } from '../utils/helpers';
|
||||
|
||||
export default class MultiAxisChart extends AxisChart {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
// this.unitType = args.unitType || 'line';
|
||||
// this.setup();
|
||||
}
|
||||
|
||||
preSetup() {
|
||||
this.type = 'multiaxis';
|
||||
}
|
||||
|
||||
setMargins() {
|
||||
super.setMargins();
|
||||
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;
|
||||
}
|
||||
|
||||
prepareYAxis() { }
|
||||
|
||||
prepareData(data) {
|
||||
super.prepareData(data);
|
||||
let sets = this.state.datasets;
|
||||
// let axesLeft = sets.filter(d => d.axisPosition === 'left');
|
||||
// let axesRight = sets.filter(d => d.axisPosition === 'right');
|
||||
// let axesNone = sets.filter(d => !d.axisPosition ||
|
||||
// !['left', 'right'].includes(d.axisPosition));
|
||||
|
||||
let leftCount = 0, rightCount = 0;
|
||||
|
||||
sets.forEach((d, i) => {
|
||||
d.yAxis = {
|
||||
position: d.axisPosition,
|
||||
index: d.axisPosition === 'left' ? leftCount++ : rightCount++
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
configure(args) {
|
||||
super.configure(args);
|
||||
this.config.xAxisMode = args.xAxisMode || 'tick';
|
||||
this.config.yAxisMode = args.yAxisMode || 'span';
|
||||
}
|
||||
|
||||
// setUnitWidthAndXOffset() {
|
||||
// this.state.unitWidth = this.width/(this.state.datasetLength);
|
||||
// this.state.xOffset = this.state.unitWidth/2;
|
||||
// }
|
||||
|
||||
configUnits() {
|
||||
this.unitArgs = {
|
||||
type: 'bar',
|
||||
args: {
|
||||
spaceWidth: this.state.unitWidth/2,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
setYAxis() {
|
||||
this.state.datasets.map(d => {
|
||||
this.calcYAxisParameters(d.yAxis, d.values, this.unitType === 'line');
|
||||
});
|
||||
}
|
||||
|
||||
calcYUnits() {
|
||||
this.state.datasets.map(d => {
|
||||
d.positions = d.values.map(val => floatTwo(d.yAxis.zeroLine - val * d.yAxis.scaleMultiplier));
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: function doesn't exist, handle with components
|
||||
renderConstants() {
|
||||
this.state.datasets.map(d => {
|
||||
let guidePos = d.yAxis.position === 'left'
|
||||
? -1 * d.yAxis.index * Y_AXIS_MARGIN
|
||||
: this.width + d.yAxis.index * Y_AXIS_MARGIN;
|
||||
this.renderer.xLine(guidePos, '', {
|
||||
pos:'top',
|
||||
mode: 'span',
|
||||
stroke: this.colors[i],
|
||||
className: 'y-axis-guide'
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
getYAxesComponents() {
|
||||
return this.data.datasets.map((e, i) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'y axis y-axis-' + i,
|
||||
make: () => {
|
||||
let yAxis = this.state.datasets[i].yAxis;
|
||||
this.renderer.setZeroline(yAxis.zeroline);
|
||||
let options = {
|
||||
pos: yAxis.position,
|
||||
mode: 'tick',
|
||||
offset: yAxis.index * Y_AXIS_MARGIN,
|
||||
stroke: this.colors[i]
|
||||
};
|
||||
|
||||
return yAxis.positions.map((position, j) =>
|
||||
this.renderer.yLine(position, yAxis.labels[j], options)
|
||||
);
|
||||
},
|
||||
animate: () => {}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// TODO remove renderer zeroline from above and below
|
||||
getChartComponents() {
|
||||
return this.data.datasets.map((d, index) => {
|
||||
return new ChartComponent({
|
||||
layerClass: 'dataset-units dataset-' + index,
|
||||
make: () => {
|
||||
let d = this.state.datasets[index];
|
||||
let unitType = this.unitArgs;
|
||||
|
||||
// the only difference, should be tied to datasets or default
|
||||
this.renderer.setZeroline(d.yAxis.zeroLine);
|
||||
|
||||
return d.positions.map((y, j) => {
|
||||
return this.renderer[unitType.type](
|
||||
this.state.xAxisPositions[j],
|
||||
y,
|
||||
unitType.args,
|
||||
this.colors[index],
|
||||
j,
|
||||
index,
|
||||
this.state.datasetLength
|
||||
);
|
||||
});
|
||||
},
|
||||
animate: (svgUnits) => {
|
||||
let d = this.state.datasets[index];
|
||||
let unitType = this.unitArgs.type;
|
||||
|
||||
// have been updated in axis render;
|
||||
let newX = this.state.xAxisPositions;
|
||||
let newY = this.state.datasets[index].positions;
|
||||
|
||||
let lastUnit = svgUnits[svgUnits.length - 1];
|
||||
let parentNode = lastUnit.parentNode;
|
||||
|
||||
if(this.oldState.xExtra > 0) {
|
||||
for(var i = 0; i<this.oldState.xExtra; i++) {
|
||||
let unit = lastUnit.cloneNode(true);
|
||||
parentNode.appendChild(unit);
|
||||
svgUnits.push(unit);
|
||||
}
|
||||
}
|
||||
|
||||
this.renderer.setZeroline(d.yAxis.zeroLine);
|
||||
|
||||
svgUnits.map((unit, i) => {
|
||||
if(newX[i] === undefined || newY[i] === undefined) return;
|
||||
this.elementsToAnimate.push(this.renderer['animate' + unitType](
|
||||
unit, // unit, with info to replace where it came from in the data
|
||||
newX[i],
|
||||
newY[i],
|
||||
index,
|
||||
this.state.noOfDatasets
|
||||
));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,127 +1,74 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { $, offset } from '../utils/dom';
|
||||
import AggregationChart from './AggregationChart';
|
||||
import { $, getOffset } from '../utils/dom';
|
||||
|
||||
export default class PercentageChart extends BaseChart {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
export default class PercentageChart extends AggregationChart {
|
||||
constructor(parent, args) {
|
||||
super(parent, args);
|
||||
this.type = 'percentage';
|
||||
|
||||
this.max_slices = 10;
|
||||
this.max_legend_points = 6;
|
||||
|
||||
this.setup();
|
||||
}
|
||||
|
||||
make_chart_area() {
|
||||
this.chart_wrapper.className += ' ' + 'graph-focus-margin';
|
||||
this.chart_wrapper.style.marginTop = '45px';
|
||||
makeChartArea() {
|
||||
this.chartWrapper.className += ' ' + 'graph-focus-margin';
|
||||
this.chartWrapper.style.marginTop = '45px';
|
||||
|
||||
this.stats_wrapper.className += ' ' + 'graph-focus-margin';
|
||||
this.stats_wrapper.style.marginBottom = '30px';
|
||||
this.stats_wrapper.style.paddingTop = '0px';
|
||||
}
|
||||
this.statsWrapper.className += ' ' + 'graph-focus-margin';
|
||||
this.statsWrapper.style.marginBottom = '30px';
|
||||
this.statsWrapper.style.paddingTop = '0px';
|
||||
|
||||
make_draw_area() {
|
||||
this.chart_div = $.create('div', {
|
||||
this.svg = $.create('div', {
|
||||
className: 'div',
|
||||
inside: this.chart_wrapper
|
||||
inside: this.chartWrapper
|
||||
});
|
||||
|
||||
this.chart = $.create('div', {
|
||||
className: 'progress-chart',
|
||||
inside: this.chart_div
|
||||
inside: this.svg
|
||||
});
|
||||
}
|
||||
|
||||
setup_components() {
|
||||
this.percentage_bar = $.create('div', {
|
||||
this.percentageBar = $.create('div', {
|
||||
className: 'progress',
|
||||
inside: this.chart
|
||||
});
|
||||
}
|
||||
|
||||
setup_values() {
|
||||
this.slice_totals = [];
|
||||
let all_totals = this.data.labels.map((d, i) => {
|
||||
let total = 0;
|
||||
this.data.datasets.map(e => {
|
||||
total += e.values[i];
|
||||
});
|
||||
return [total, d];
|
||||
}).filter(d => { return d[0] > 0; }); // keep only positive results
|
||||
|
||||
let totals = all_totals;
|
||||
|
||||
if(all_totals.length > this.max_slices) {
|
||||
all_totals.sort((a, b) => { return b[0] - a[0]; });
|
||||
|
||||
totals = all_totals.slice(0, this.max_slices-1);
|
||||
let others = all_totals.slice(this.max_slices-1);
|
||||
|
||||
let sum_of_others = 0;
|
||||
others.map(d => {sum_of_others += d[0];});
|
||||
|
||||
totals.push([sum_of_others, 'Rest']);
|
||||
|
||||
this.colors[this.max_slices-1] = 'grey';
|
||||
}
|
||||
|
||||
this.labels = [];
|
||||
totals.map(d => {
|
||||
this.slice_totals.push(d[0]);
|
||||
this.labels.push(d[1]);
|
||||
});
|
||||
|
||||
this.legend_totals = this.slice_totals.slice(0, this.max_legend_points);
|
||||
}
|
||||
|
||||
make_graph_components() {
|
||||
this.grand_total = this.slice_totals.reduce((a, b) => a + b, 0);
|
||||
this.slices = [];
|
||||
this.slice_totals.map((total, i) => {
|
||||
render() {
|
||||
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`,
|
||||
inside: this.percentage_bar,
|
||||
'data-index': i,
|
||||
inside: this.percentageBar,
|
||||
styles: {
|
||||
background: this.colors[i],
|
||||
width: total*100/this.grand_total + "%"
|
||||
width: total*100/this.grandTotal + "%"
|
||||
}
|
||||
});
|
||||
this.slices.push(slice);
|
||||
s.slices.push(slice);
|
||||
});
|
||||
}
|
||||
|
||||
bind_tooltip() {
|
||||
this.slices.map((slice, i) => {
|
||||
slice.addEventListener('mouseenter', () => {
|
||||
let g_off = offset(this.chart_wrapper), p_off = offset(slice);
|
||||
bindTooltip() {
|
||||
let s = this.state;
|
||||
|
||||
let x = p_off.left - g_off.left + slice.offsetWidth/2;
|
||||
let y = p_off.top - g_off.top - 6;
|
||||
let title = (this.formatted_labels && this.formatted_labels.length>0
|
||||
? this.formatted_labels[i] : this.labels[i]) + ': ';
|
||||
let percent = (this.slice_totals[i]*100/this.grand_total).toFixed(1);
|
||||
this.chartWrapper.addEventListener('mousemove', (e) => {
|
||||
let slice = e.target;
|
||||
if(slice.classList.contains('progress-bar')) {
|
||||
|
||||
this.tip.set_values(x, y, title, percent + "%");
|
||||
this.tip.show_tip();
|
||||
});
|
||||
});
|
||||
}
|
||||
let i = slice.getAttribute('data-index');
|
||||
let gOff = getOffset(this.chartWrapper), pOff = getOffset(slice);
|
||||
|
||||
show_summary() {
|
||||
let x_values = this.formatted_labels && this.formatted_labels.length > 0
|
||||
? this.formatted_labels : this.labels;
|
||||
this.legend_totals.map((d, i) => {
|
||||
if(d) {
|
||||
let stats = $.create('div', {
|
||||
className: 'stats',
|
||||
inside: this.stats_wrapper
|
||||
});
|
||||
stats.innerHTML = `<span class="indicator">
|
||||
<i style="background: ${this.colors[i]}"></i>
|
||||
<span class="text-muted">${x_values[i]}:</span>
|
||||
${d}
|
||||
</span>`;
|
||||
let x = pOff.left - gOff.left + slice.offsetWidth/2;
|
||||
let y = pOff.top - gOff.top - 6;
|
||||
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);
|
||||
|
||||
this.tip.setValues(x, y, {name: title, value: percent + "%"});
|
||||
this.tip.showTip();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,213 +1,168 @@
|
||||
import BaseChart from './BaseChart';
|
||||
import { $, offset } from '../utils/dom';
|
||||
import { makePath } from '../utils/draw';
|
||||
import AggregationChart from './AggregationChart';
|
||||
import { getComponent } from '../objects/ChartComponents';
|
||||
import { getOffset } from '../utils/dom';
|
||||
import { getPositionByAngle } from '../utils/helpers';
|
||||
import { makeArcPathStr } from '../utils/draw';
|
||||
import { lightenDarkenColor } from '../utils/colors';
|
||||
import { runSVGAnimation, transform } from '../utils/animation';
|
||||
const ANGLE_RATIO = Math.PI / 180;
|
||||
const FULL_ANGLE = 360;
|
||||
import { transform } from '../utils/animation';
|
||||
import { FULL_ANGLE } from '../utils/constants';
|
||||
|
||||
export default class PieChart extends BaseChart {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
export default class PieChart extends AggregationChart {
|
||||
constructor(parent, args) {
|
||||
super(parent, args);
|
||||
this.type = 'pie';
|
||||
this.elements_to_animate = null;
|
||||
this.hoverRadio = args.hoverRadio || 0.1;
|
||||
this.max_slices = 10;
|
||||
this.max_legend_points = 6;
|
||||
this.isAnimate = false;
|
||||
this.startAngle = args.startAngle || 0;
|
||||
this.clockWise = args.clockWise || false;
|
||||
this.mouseMove = this.mouseMove.bind(this);
|
||||
this.mouseLeave = this.mouseLeave.bind(this);
|
||||
this.initTimeout = 0;
|
||||
|
||||
this.setup();
|
||||
}
|
||||
setup_values() {
|
||||
this.centerX = this.width / 2;
|
||||
this.centerY = this.height / 2;
|
||||
this.radius = (this.height > this.width ? this.centerX : this.centerY);
|
||||
this.slice_totals = [];
|
||||
let all_totals = this.data.labels.map((d, i) => {
|
||||
let total = 0;
|
||||
this.data.datasets.map(e => {
|
||||
total += e.values[i];
|
||||
});
|
||||
return [total, d];
|
||||
}).filter(d => { return d[0] > 0; }); // keep only positive results
|
||||
|
||||
let totals = all_totals;
|
||||
configure(args) {
|
||||
super.configure(args);
|
||||
this.mouseMove = this.mouseMove.bind(this);
|
||||
this.mouseLeave = this.mouseLeave.bind(this);
|
||||
|
||||
if(all_totals.length > this.max_slices) {
|
||||
all_totals.sort((a, b) => { return b[0] - a[0]; });
|
||||
this.hoverRadio = args.hoverRadio || 0.1;
|
||||
this.config.startAngle = args.startAngle || 0;
|
||||
|
||||
totals = all_totals.slice(0, this.max_slices-1);
|
||||
let others = all_totals.slice(this.max_slices-1);
|
||||
|
||||
let sum_of_others = 0;
|
||||
others.map(d => {sum_of_others += d[0];});
|
||||
|
||||
totals.push([sum_of_others, 'Rest']);
|
||||
|
||||
this.colors[this.max_slices-1] = 'grey';
|
||||
}
|
||||
|
||||
this.labels = [];
|
||||
totals.map(d => {
|
||||
this.slice_totals.push(d[0]);
|
||||
this.labels.push(d[1]);
|
||||
});
|
||||
|
||||
this.legend_totals = this.slice_totals.slice(0, this.max_legend_points);
|
||||
this.clockWise = args.clockWise || false;
|
||||
}
|
||||
|
||||
static getPositionByAngle(angle,radius){
|
||||
return {
|
||||
x:Math.sin(angle * ANGLE_RATIO) * radius,
|
||||
y:Math.cos(angle * ANGLE_RATIO) * radius,
|
||||
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();
|
||||
}
|
||||
makeArcPath(startPosition,endPosition){
|
||||
const{centerX,centerY,radius,clockWise} = this;
|
||||
return `M${centerX} ${centerY} L${centerX+startPosition.x} ${centerY+startPosition.y} A ${radius} ${radius} 0 0 ${clockWise ? 1 : 0} ${centerX+endPosition.x} ${centerY+endPosition.y} z`;
|
||||
}
|
||||
make_graph_components(init){
|
||||
const{radius,clockWise} = this;
|
||||
this.grand_total = this.slice_totals.reduce((a, b) => a + b, 0);
|
||||
const prevSlicesProperties = this.slicesProperties || [];
|
||||
this.slices = [];
|
||||
this.elements_to_animate = [];
|
||||
this.slicesProperties = [];
|
||||
let curAngle = 180 - this.startAngle;
|
||||
this.slice_totals.map((total, i) => {
|
||||
|
||||
calcSlices() {
|
||||
let s = this.state;
|
||||
const { radius, clockWise } = this;
|
||||
|
||||
const prevSlicesProperties = s.slicesProperties || [];
|
||||
s.sliceStrings = [];
|
||||
s.slicesProperties = [];
|
||||
let curAngle = 180 - this.config.startAngle;
|
||||
|
||||
s.sliceTotals.map((total, i) => {
|
||||
const startAngle = curAngle;
|
||||
const originDiffAngle = (total / this.grand_total) * FULL_ANGLE;
|
||||
const originDiffAngle = (total / s.grandTotal) * FULL_ANGLE;
|
||||
const diffAngle = clockWise ? -originDiffAngle : originDiffAngle;
|
||||
const endAngle = curAngle = curAngle + diffAngle;
|
||||
const startPosition = PieChart.getPositionByAngle(startAngle,radius);
|
||||
const endPosition = PieChart.getPositionByAngle(endAngle,radius);
|
||||
const prevProperty = init && prevSlicesProperties[i];
|
||||
const startPosition = getPositionByAngle(startAngle, radius);
|
||||
const endPosition = getPositionByAngle(endAngle, radius);
|
||||
|
||||
const prevProperty = this.init && prevSlicesProperties[i];
|
||||
|
||||
let curStart,curEnd;
|
||||
if(init){
|
||||
curStart = prevProperty?prevProperty.startPosition : startPosition;
|
||||
curEnd = prevProperty? prevProperty.endPosition : startPosition;
|
||||
}else{
|
||||
if(this.init) {
|
||||
curStart = prevProperty ? prevProperty.startPosition : startPosition;
|
||||
curEnd = prevProperty ? prevProperty.endPosition : startPosition;
|
||||
} else {
|
||||
curStart = startPosition;
|
||||
curEnd = endPosition;
|
||||
}
|
||||
const curPath = this.makeArcPath(curStart,curEnd);
|
||||
let slice = makePath(curPath, 'pie-path', 'none', this.colors[i]);
|
||||
slice.style.transition = 'transform .3s;';
|
||||
this.draw_area.appendChild(slice);
|
||||
const curPath = makeArcPathStr(curStart, curEnd, this.center, this.radius, this.clockWise);
|
||||
|
||||
this.slices.push(slice);
|
||||
this.slicesProperties.push({
|
||||
s.sliceStrings.push(curPath);
|
||||
s.slicesProperties.push({
|
||||
startPosition,
|
||||
endPosition,
|
||||
value: total,
|
||||
total: this.grand_total,
|
||||
total: s.grandTotal,
|
||||
startAngle,
|
||||
endAngle,
|
||||
angle:diffAngle
|
||||
angle: diffAngle
|
||||
});
|
||||
if(init){
|
||||
this.elements_to_animate.push([{unit: slice, array: this.slices, index: this.slices.length - 1},
|
||||
{d:this.makeArcPath(startPosition,endPosition)},
|
||||
650, "easein",null,{
|
||||
d:curPath
|
||||
}]);
|
||||
}
|
||||
|
||||
});
|
||||
if(init){
|
||||
this.run_animation();
|
||||
}
|
||||
this.init = 0;
|
||||
}
|
||||
run_animation() {
|
||||
// if(this.isAnimate) return ;
|
||||
// this.isAnimate = true;
|
||||
if(!this.elements_to_animate || this.elements_to_animate.length === 0) return;
|
||||
let anim_svg = runSVGAnimation(this.svg, this.elements_to_animate);
|
||||
|
||||
if(this.svg.parentNode == this.chart_wrapper) {
|
||||
this.chart_wrapper.removeChild(this.svg);
|
||||
this.chart_wrapper.appendChild(anim_svg);
|
||||
setupComponents() {
|
||||
let s = this.state;
|
||||
|
||||
}
|
||||
let componentConfigs = [
|
||||
[
|
||||
'pieSlices',
|
||||
{ },
|
||||
function() {
|
||||
return {
|
||||
sliceStrings: s.sliceStrings,
|
||||
colors: this.colors
|
||||
};
|
||||
}.bind(this)
|
||||
]
|
||||
];
|
||||
|
||||
// Replace the new svg (data has long been replaced)
|
||||
setTimeout(() => {
|
||||
// this.isAnimate = false;
|
||||
if(anim_svg.parentNode == this.chart_wrapper) {
|
||||
this.chart_wrapper.removeChild(anim_svg);
|
||||
this.chart_wrapper.appendChild(this.svg);
|
||||
}
|
||||
}, 650);
|
||||
this.components = new Map(componentConfigs
|
||||
.map(args => {
|
||||
let component = getComponent(...args);
|
||||
return [args[0], component];
|
||||
}));
|
||||
}
|
||||
|
||||
calTranslateByAngle(property){
|
||||
const{radius,hoverRadio} = this;
|
||||
const position = PieChart.getPositionByAngle(property.startAngle+(property.angle / 2),radius);
|
||||
const position = getPositionByAngle(property.startAngle+(property.angle / 2),radius);
|
||||
return `translate3d(${(position.x) * hoverRadio}px,${(position.y) * hoverRadio}px,0)`;
|
||||
}
|
||||
|
||||
hoverSlice(path,i,flag,e){
|
||||
if(!path) return;
|
||||
const color = this.colors[i];
|
||||
if(flag){
|
||||
transform(path,this.calTranslateByAngle(this.slicesProperties[i]));
|
||||
path.style.fill = lightenDarkenColor(color,50);
|
||||
let g_off = offset(this.svg);
|
||||
if(flag) {
|
||||
transform(path, this.calTranslateByAngle(this.state.slicesProperties[i]));
|
||||
path.style.fill = lightenDarkenColor(color, 50);
|
||||
let g_off = getOffset(this.svg);
|
||||
let x = e.pageX - g_off.left + 10;
|
||||
let y = e.pageY - g_off.top - 10;
|
||||
let title = (this.formatted_labels && this.formatted_labels.length>0
|
||||
? this.formatted_labels[i] : this.labels[i]) + ': ';
|
||||
let percent = (this.slice_totals[i]*100/this.grand_total).toFixed(1);
|
||||
this.tip.set_values(x, y, title, percent + "%");
|
||||
this.tip.show_tip();
|
||||
}else{
|
||||
let title = (this.formatted_labels && this.formatted_labels.length > 0
|
||||
? this.formatted_labels[i] : this.state.labels[i]) + ': ';
|
||||
let percent = (this.state.sliceTotals[i] * 100 / this.state.grandTotal).toFixed(1);
|
||||
this.tip.setValues(x, y, {name: title, value: percent + "%"});
|
||||
this.tip.showTip();
|
||||
} else {
|
||||
transform(path,'translate3d(0,0,0)');
|
||||
this.tip.hide_tip();
|
||||
this.tip.hideTip();
|
||||
path.style.fill = color;
|
||||
}
|
||||
}
|
||||
|
||||
bindTooltip() {
|
||||
this.chartWrapper.addEventListener('mousemove', this.mouseMove);
|
||||
this.chartWrapper.addEventListener('mouseleave', this.mouseLeave);
|
||||
}
|
||||
|
||||
mouseMove(e){
|
||||
const target = e.target;
|
||||
let slices = this.components.get('pieSlices').store;
|
||||
let prevIndex = this.curActiveSliceIndex;
|
||||
let prevAcitve = this.curActiveSlice;
|
||||
for(let i = 0; i < this.slices.length; i++){
|
||||
if(target === this.slices[i]){
|
||||
this.hoverSlice(prevAcitve,prevIndex,false);
|
||||
this.curActiveSlice = target;
|
||||
this.curActiveSliceIndex = i;
|
||||
this.hoverSlice(target,i,true,e);
|
||||
break;
|
||||
}
|
||||
if(slices.includes(target)) {
|
||||
let i = slices.indexOf(target);
|
||||
this.hoverSlice(prevAcitve, prevIndex,false);
|
||||
this.curActiveSlice = target;
|
||||
this.curActiveSliceIndex = i;
|
||||
this.hoverSlice(target, i, true, e);
|
||||
} else {
|
||||
this.mouseLeave();
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeave(){
|
||||
this.hoverSlice(this.curActiveSlice,this.curActiveSliceIndex,false);
|
||||
}
|
||||
bind_tooltip() {
|
||||
this.draw_area.addEventListener('mousemove',this.mouseMove);
|
||||
this.draw_area.addEventListener('mouseleave',this.mouseLeave);
|
||||
}
|
||||
|
||||
show_summary() {
|
||||
let x_values = this.formatted_labels && this.formatted_labels.length > 0
|
||||
? this.formatted_labels : this.labels;
|
||||
this.legend_totals.map((d, i) => {
|
||||
const color = this.colors[i];
|
||||
|
||||
if(d) {
|
||||
let stats = $.create('div', {
|
||||
className: 'stats',
|
||||
inside: this.stats_wrapper
|
||||
});
|
||||
stats.innerHTML = `<span class="indicator">
|
||||
<i style="background-color:${color};"></i>
|
||||
<span class="text-muted">${x_values[i]}:</span>
|
||||
${d}
|
||||
</span>`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
import LineChart from './LineChart';
|
||||
|
||||
export default class ScatterChart extends LineChart {
|
||||
constructor(args) {
|
||||
super(args);
|
||||
|
||||
this.type = 'scatter';
|
||||
|
||||
if(!args.dot_radius) {
|
||||
this.dot_radius = 8;
|
||||
} else {
|
||||
this.dot_radius = args.dot_radius;
|
||||
}
|
||||
|
||||
this.setup();
|
||||
}
|
||||
|
||||
setup_graph_components() {
|
||||
this.setup_path_groups();
|
||||
super.setup_graph_components();
|
||||
}
|
||||
|
||||
setup_path_groups() {}
|
||||
|
||||
setup_values() {
|
||||
super.setup_values();
|
||||
this.unit_args = {
|
||||
type: 'dot',
|
||||
args: { radius: this.dot_radius }
|
||||
};
|
||||
}
|
||||
|
||||
make_paths() {}
|
||||
make_path() {}
|
||||
}
|
||||
46
src/js/config.js
Normal file
46
src/js/config.js
Normal file
@ -0,0 +1,46 @@
|
||||
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);
|
||||
}
|
||||
366
src/js/objects/ChartComponents.js
Normal file
366
src/js/objects/ChartComponents.js
Normal file
@ -0,0 +1,366 @@
|
||||
import { makeSVGGroup } from '../utils/draw';
|
||||
import { makePath, xLine, yLine, yMarker, yRegion, datasetBar, datasetDot, getPaths } from '../utils/draw';
|
||||
import { equilizeNoOfElements } from '../utils/draw-utils';
|
||||
import { translateHoriLine, translateVertLine, animateRegion, animateBar,
|
||||
animateDot, animatePath, animatePathStr } from '../utils/animate';
|
||||
|
||||
class ChartComponent {
|
||||
constructor({
|
||||
layerClass = '',
|
||||
layerTransform = '',
|
||||
constants,
|
||||
|
||||
getData,
|
||||
makeElements,
|
||||
animateElements
|
||||
}) {
|
||||
this.layerTransform = layerTransform;
|
||||
this.constants = constants;
|
||||
|
||||
this.makeElements = makeElements;
|
||||
this.getData = getData;
|
||||
|
||||
this.animateElements = animateElements;
|
||||
|
||||
this.store = [];
|
||||
|
||||
this.layerClass = layerClass;
|
||||
this.layerClass = typeof(this.layerClass) === 'function'
|
||||
? this.layerClass() : this.layerClass;
|
||||
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
refresh(data) {
|
||||
this.data = data || this.getData();
|
||||
}
|
||||
|
||||
setup(parent) {
|
||||
this.layer = makeSVGGroup(parent, this.layerClass, this.layerTransform);
|
||||
}
|
||||
|
||||
make() {
|
||||
this.render(this.data);
|
||||
this.oldData = this.data;
|
||||
}
|
||||
|
||||
render(data) {
|
||||
this.store = this.makeElements(data);
|
||||
|
||||
this.layer.textContent = '';
|
||||
this.store.forEach(element => {
|
||||
this.layer.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
update(animate = true) {
|
||||
this.refresh();
|
||||
let animateElements = [];
|
||||
if(animate) {
|
||||
animateElements = this.animateElements(this.data);
|
||||
}
|
||||
return animateElements;
|
||||
}
|
||||
}
|
||||
|
||||
let componentConfigs = {
|
||||
pieSlices: {
|
||||
layerClass: 'pie-slices',
|
||||
makeElements(data) {
|
||||
return data.sliceStrings.map((s, i) =>{
|
||||
let slice = makePath(s, 'pie-path', 'none', data.colors[i]);
|
||||
slice.style.transition = 'transform .3s;';
|
||||
return slice;
|
||||
});
|
||||
},
|
||||
|
||||
animateElements(newData) {
|
||||
return this.store.map((slice, i) =>
|
||||
animatePathStr(slice, newData.sliceStrings[i])
|
||||
);
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
layerClass: 'y axis',
|
||||
makeElements(data) {
|
||||
return data.positions.map((position, i) =>
|
||||
yLine(position, data.labels[i], this.constants.width,
|
||||
{mode: this.constants.mode, pos: this.constants.pos})
|
||||
);
|
||||
},
|
||||
|
||||
animateElements(newData) {
|
||||
let newPos = newData.positions;
|
||||
let newLabels = newData.labels;
|
||||
let oldPos = this.oldData.positions;
|
||||
let oldLabels = this.oldData.labels;
|
||||
|
||||
[oldPos, newPos] = equilizeNoOfElements(oldPos, newPos);
|
||||
[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
|
||||
|
||||
this.render({
|
||||
positions: oldPos,
|
||||
labels: newLabels
|
||||
});
|
||||
|
||||
return this.store.map((line, i) => {
|
||||
return translateHoriLine(
|
||||
line, newPos[i], oldPos[i]
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
xAxis: {
|
||||
layerClass: 'x axis',
|
||||
makeElements(data) {
|
||||
return data.positions.map((position, i) =>
|
||||
xLine(position, data.calcLabels[i], this.constants.height,
|
||||
{mode: this.constants.mode, pos: this.constants.pos})
|
||||
);
|
||||
},
|
||||
|
||||
animateElements(newData) {
|
||||
let newPos = newData.positions;
|
||||
let newLabels = newData.calcLabels;
|
||||
let oldPos = this.oldData.positions;
|
||||
let oldLabels = this.oldData.calcLabels;
|
||||
|
||||
[oldPos, newPos] = equilizeNoOfElements(oldPos, newPos);
|
||||
[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
|
||||
|
||||
this.render({
|
||||
positions: oldPos,
|
||||
calcLabels: newLabels
|
||||
});
|
||||
|
||||
return this.store.map((line, i) => {
|
||||
return translateVertLine(
|
||||
line, newPos[i], oldPos[i]
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
yMarkers: {
|
||||
layerClass: 'y-markers',
|
||||
makeElements(data) {
|
||||
return data.map(marker =>
|
||||
yMarker(marker.position, marker.label, this.constants.width,
|
||||
{pos:'right', mode: 'span', lineType: 'dashed'})
|
||||
);
|
||||
},
|
||||
animateElements(newData) {
|
||||
[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
|
||||
|
||||
let newPos = newData.map(d => d.position);
|
||||
let newLabels = newData.map(d => d.label);
|
||||
|
||||
let oldPos = this.oldData.map(d => d.position);
|
||||
|
||||
this.render(oldPos.map((pos, i) => {
|
||||
return {
|
||||
position: oldPos[i],
|
||||
label: newLabels[i]
|
||||
};
|
||||
}));
|
||||
|
||||
return this.store.map((line, i) => {
|
||||
return translateHoriLine(
|
||||
line, newPos[i], oldPos[i]
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
yRegions: {
|
||||
layerClass: 'y-regions',
|
||||
makeElements(data) {
|
||||
return data.map(region =>
|
||||
yRegion(region.startPos, region.endPos, this.constants.width,
|
||||
region.label)
|
||||
);
|
||||
},
|
||||
animateElements(newData) {
|
||||
[this.oldData, newData] = equilizeNoOfElements(this.oldData, newData);
|
||||
|
||||
let newPos = newData.map(d => d.endPos);
|
||||
let newLabels = newData.map(d => d.label);
|
||||
let newStarts = newData.map(d => d.startPos);
|
||||
|
||||
let oldPos = this.oldData.map(d => d.endPos);
|
||||
let oldStarts = this.oldData.map(d => d.startPos);
|
||||
|
||||
this.render(oldPos.map((pos, i) => {
|
||||
return {
|
||||
startPos: oldStarts[i],
|
||||
endPos: oldPos[i],
|
||||
label: newLabels[i]
|
||||
};
|
||||
}));
|
||||
|
||||
let animateElements = [];
|
||||
|
||||
this.store.map((rectGroup, i) => {
|
||||
animateElements = animateElements.concat(animateRegion(
|
||||
rectGroup, newStarts[i], newPos[i], oldPos[i]
|
||||
));
|
||||
});
|
||||
|
||||
return animateElements;
|
||||
}
|
||||
},
|
||||
|
||||
barGraph: {
|
||||
layerClass: function() { return 'dataset-units dataset-bars dataset-' + this.constants.index; },
|
||||
makeElements(data) {
|
||||
let c = this.constants;
|
||||
this.unitType = 'bar';
|
||||
this.units = data.yPositions.map((y, j) => {
|
||||
return datasetBar(
|
||||
data.xPositions[j],
|
||||
y,
|
||||
data.barWidth,
|
||||
c.color,
|
||||
data.labels[j],
|
||||
j,
|
||||
data.offsets[j],
|
||||
{
|
||||
zeroLine: data.zeroLine,
|
||||
barsWidth: data.barsWidth,
|
||||
minHeight: c.minHeight
|
||||
}
|
||||
);
|
||||
});
|
||||
return this.units;
|
||||
},
|
||||
animateElements(newData) {
|
||||
let c = this.constants;
|
||||
|
||||
let newXPos = newData.xPositions;
|
||||
let newYPos = newData.yPositions;
|
||||
let newOffsets = newData.offsets;
|
||||
let newLabels = newData.labels;
|
||||
|
||||
let oldXPos = this.oldData.xPositions;
|
||||
let oldYPos = this.oldData.yPositions;
|
||||
let oldOffsets = this.oldData.offsets;
|
||||
let oldLabels = this.oldData.labels;
|
||||
|
||||
[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
|
||||
[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
|
||||
[oldOffsets, newOffsets] = equilizeNoOfElements(oldOffsets, newOffsets);
|
||||
[oldLabels, newLabels] = equilizeNoOfElements(oldLabels, newLabels);
|
||||
|
||||
this.render({
|
||||
xPositions: oldXPos,
|
||||
yPositions: oldYPos,
|
||||
offsets: oldOffsets,
|
||||
labels: newLabels,
|
||||
|
||||
zeroLine: this.oldData.zeroLine,
|
||||
barsWidth: this.oldData.barsWidth,
|
||||
barWidth: this.oldData.barWidth,
|
||||
});
|
||||
|
||||
let animateElements = [];
|
||||
|
||||
this.store.map((bar, i) => {
|
||||
animateElements = animateElements.concat(animateBar(
|
||||
bar, newXPos[i], newYPos[i], newData.barWidth, newOffsets[i], c.index,
|
||||
{zeroLine: newData.zeroLine}
|
||||
));
|
||||
});
|
||||
|
||||
return animateElements;
|
||||
}
|
||||
},
|
||||
|
||||
lineGraph: {
|
||||
layerClass: function() { return 'dataset-units dataset-line dataset-' + this.constants.index; },
|
||||
makeElements(data) {
|
||||
let c = this.constants;
|
||||
this.unitType = 'dot';
|
||||
this.paths = {};
|
||||
if(!c.hideLine) {
|
||||
this.paths = getPaths(
|
||||
data.xPositions,
|
||||
data.yPositions,
|
||||
c.color,
|
||||
{
|
||||
heatline: c.heatline,
|
||||
regionFill: c.regionFill
|
||||
},
|
||||
{
|
||||
svgDefs: c.svgDefs,
|
||||
zeroLine: data.zeroLine
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.units = [];
|
||||
if(!c.hideDots) {
|
||||
this.units = data.yPositions.map((y, j) => {
|
||||
return datasetDot(
|
||||
data.xPositions[j],
|
||||
y,
|
||||
data.radius,
|
||||
c.color,
|
||||
(c.valuesOverPoints ? data.values[j] : ''),
|
||||
j
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
return Object.values(this.paths).concat(this.units);
|
||||
},
|
||||
animateElements(newData) {
|
||||
let newXPos = newData.xPositions;
|
||||
let newYPos = newData.yPositions;
|
||||
let newValues = newData.values;
|
||||
|
||||
let oldXPos = this.oldData.xPositions;
|
||||
let oldYPos = this.oldData.yPositions;
|
||||
let oldValues = this.oldData.values;
|
||||
|
||||
[oldXPos, newXPos] = equilizeNoOfElements(oldXPos, newXPos);
|
||||
[oldYPos, newYPos] = equilizeNoOfElements(oldYPos, newYPos);
|
||||
[oldValues, newValues] = equilizeNoOfElements(oldValues, newValues);
|
||||
|
||||
this.render({
|
||||
xPositions: oldXPos,
|
||||
yPositions: oldYPos,
|
||||
values: newValues,
|
||||
|
||||
zeroLine: this.oldData.zeroLine,
|
||||
radius: this.oldData.radius,
|
||||
});
|
||||
|
||||
let animateElements = [];
|
||||
|
||||
if(Object.keys(this.paths).length) {
|
||||
animateElements = animateElements.concat(animatePath(
|
||||
this.paths, newXPos, newYPos, newData.zeroLine));
|
||||
}
|
||||
|
||||
if(this.units.length) {
|
||||
this.units.map((dot, i) => {
|
||||
animateElements = animateElements.concat(animateDot(
|
||||
dot, newXPos[i], newYPos[i]));
|
||||
});
|
||||
}
|
||||
|
||||
return animateElements;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export function getComponent(name, constants, getData) {
|
||||
let keys = Object.keys(componentConfigs).filter(k => name.includes(k));
|
||||
let config = componentConfigs[keys[0]];
|
||||
Object.assign(config, {
|
||||
constants: constants,
|
||||
getData: getData
|
||||
});
|
||||
return new ChartComponent(config);
|
||||
}
|
||||
@ -7,10 +7,10 @@ export default class SvgTip {
|
||||
}) {
|
||||
this.parent = parent;
|
||||
this.colors = colors;
|
||||
this.title_name = '';
|
||||
this.title_value = '';
|
||||
this.list_values = [];
|
||||
this.title_value_first = 0;
|
||||
this.titleName = '';
|
||||
this.titleValue = '';
|
||||
this.listValues = [];
|
||||
this.titleValueFirst = 0;
|
||||
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
@ -22,16 +22,16 @@ export default class SvgTip {
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.make_tooltip();
|
||||
this.makeTooltip();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.fill();
|
||||
this.calc_position();
|
||||
// this.show_tip();
|
||||
this.calcPosition();
|
||||
// this.showTip();
|
||||
}
|
||||
|
||||
make_tooltip() {
|
||||
makeTooltip() {
|
||||
this.container = $.create('div', {
|
||||
inside: this.parent,
|
||||
className: 'graph-svg-tip comparison',
|
||||
@ -39,27 +39,30 @@ export default class SvgTip {
|
||||
<ul class="data-point-list"></ul>
|
||||
<div class="svg-pointer"></div>`
|
||||
});
|
||||
this.hide_tip();
|
||||
this.hideTip();
|
||||
|
||||
this.title = this.container.querySelector('.title');
|
||||
this.data_point_list = this.container.querySelector('.data-point-list');
|
||||
this.dataPointList = this.container.querySelector('.data-point-list');
|
||||
|
||||
this.parent.addEventListener('mouseleave', () => {
|
||||
this.hide_tip();
|
||||
this.hideTip();
|
||||
});
|
||||
}
|
||||
|
||||
fill() {
|
||||
let title;
|
||||
if(this.title_value_first) {
|
||||
title = `<strong>${this.title_value}</strong>${this.title_name}`;
|
||||
if(this.index) {
|
||||
this.container.setAttribute('data-point-index', this.index);
|
||||
}
|
||||
if(this.titleValueFirst) {
|
||||
title = `<strong>${this.titleValue}</strong>${this.titleName}`;
|
||||
} else {
|
||||
title = `${this.title_name}<strong>${this.title_value}</strong>`;
|
||||
title = `${this.titleName}<strong>${this.titleValue}</strong>`;
|
||||
}
|
||||
this.title.innerHTML = title;
|
||||
this.data_point_list.innerHTML = '';
|
||||
this.dataPointList.innerHTML = '';
|
||||
|
||||
this.list_values.map((set, i) => {
|
||||
this.listValues.map((set, i) => {
|
||||
const color = this.colors[i] || 'black';
|
||||
|
||||
let li = $.create('li', {
|
||||
@ -70,50 +73,51 @@ export default class SvgTip {
|
||||
${set.title ? set.title : '' }`
|
||||
});
|
||||
|
||||
this.data_point_list.appendChild(li);
|
||||
this.dataPointList.appendChild(li);
|
||||
});
|
||||
}
|
||||
|
||||
calc_position() {
|
||||
calcPosition() {
|
||||
let width = this.container.offsetWidth;
|
||||
|
||||
this.top = this.y - this.container.offsetHeight;
|
||||
this.left = this.x - width/2;
|
||||
let max_left = this.parent.offsetWidth - width;
|
||||
let maxLeft = this.parent.offsetWidth - width;
|
||||
|
||||
let pointer = this.container.querySelector('.svg-pointer');
|
||||
|
||||
if(this.left < 0) {
|
||||
pointer.style.left = `calc(50% - ${-1 * this.left}px)`;
|
||||
this.left = 0;
|
||||
} else if(this.left > max_left) {
|
||||
let delta = this.left - max_left;
|
||||
let pointer_offset = `calc(50% + ${delta}px)`;
|
||||
pointer.style.left = pointer_offset;
|
||||
} else if(this.left > maxLeft) {
|
||||
let delta = this.left - maxLeft;
|
||||
let pointerOffset = `calc(50% + ${delta}px)`;
|
||||
pointer.style.left = pointerOffset;
|
||||
|
||||
this.left = max_left;
|
||||
this.left = maxLeft;
|
||||
} else {
|
||||
pointer.style.left = `50%`;
|
||||
}
|
||||
}
|
||||
|
||||
set_values(x, y, title_name = '', title_value = '', list_values = [], title_value_first = 0) {
|
||||
this.title_name = title_name;
|
||||
this.title_value = title_value;
|
||||
this.list_values = list_values;
|
||||
setValues(x, y, title = {}, listValues = [], index = -1) {
|
||||
this.titleName = title.name;
|
||||
this.titleValue = title.value;
|
||||
this.listValues = listValues;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.title_value_first = title_value_first;
|
||||
this.titleValueFirst = title.valueFirst || 0;
|
||||
this.index = index;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
hide_tip() {
|
||||
hideTip() {
|
||||
this.container.style.top = '0px';
|
||||
this.container.style.left = '0px';
|
||||
this.container.style.opacity = '0';
|
||||
}
|
||||
|
||||
show_tip() {
|
||||
showTip() {
|
||||
this.container.style.top = this.top + 'px';
|
||||
this.container.style.left = this.left + 'px';
|
||||
this.container.style.opacity = '1';
|
||||
|
||||
@ -1,58 +1,105 @@
|
||||
import { getBarHeightAndYAttr } from './draw-utils';
|
||||
|
||||
export function getAnimXLine() {}
|
||||
export const UNIT_ANIM_DUR = 350;
|
||||
export const PATH_ANIM_DUR = 350;
|
||||
export const MARKER_LINE_ANIM_DUR = UNIT_ANIM_DUR;
|
||||
export const REPLACE_ALL_NEW_DUR = 250;
|
||||
|
||||
export function getAnimYLine() {}
|
||||
export const STD_EASING = 'easein';
|
||||
|
||||
export var Animator = (function() {
|
||||
var Animator = function(totalHeight, totalWidth, zeroLine, avgUnitWidth) {
|
||||
// constants
|
||||
this.totalHeight = totalHeight;
|
||||
this.totalWidth = totalWidth;
|
||||
export function translate(unit, oldCoord, newCoord, duration) {
|
||||
let old = typeof oldCoord === 'string' ? oldCoord : oldCoord.join(', ');
|
||||
return [
|
||||
unit,
|
||||
{transform: newCoord.join(', ')},
|
||||
duration,
|
||||
STD_EASING,
|
||||
"translate",
|
||||
{transform: old}
|
||||
];
|
||||
}
|
||||
|
||||
// changeables
|
||||
this.avgUnitWidth = avgUnitWidth;
|
||||
this.zeroLine = zeroLine;
|
||||
};
|
||||
export function translateVertLine(xLine, newX, oldX) {
|
||||
return translate(xLine, [oldX, 0], [newX, 0], MARKER_LINE_ANIM_DUR);
|
||||
}
|
||||
|
||||
Animator.prototype = {
|
||||
bar: function(barObj, x, yTop, index, noOfDatasets) {
|
||||
let start = x - this.avgUnitWidth/4;
|
||||
let width = (this.avgUnitWidth/2)/noOfDatasets;
|
||||
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);
|
||||
export function translateHoriLine(yLine, newY, oldY) {
|
||||
return translate(yLine, [0, oldY], [0, newY], MARKER_LINE_ANIM_DUR);
|
||||
}
|
||||
|
||||
x = start + (width * index);
|
||||
export function animateRegion(rectGroup, newY1, newY2, oldY2) {
|
||||
let newHeight = newY1 - newY2;
|
||||
let rect = rectGroup.childNodes[0];
|
||||
let width = rect.getAttribute("width");
|
||||
let rectAnim = [
|
||||
rect,
|
||||
{ height: newHeight, 'stroke-dasharray': `${width}, ${newHeight}` },
|
||||
MARKER_LINE_ANIM_DUR,
|
||||
STD_EASING
|
||||
]
|
||||
|
||||
return [barObj, {width: width, height: height, x: x, y: y}, 350, "easein"];
|
||||
// bar.animate({height: args.newHeight, y: yTop}, 350, mina.easein);
|
||||
},
|
||||
let groupAnim = translate(rectGroup, [0, oldY2], [0, newY2], MARKER_LINE_ANIM_DUR);
|
||||
return [rectAnim, groupAnim];
|
||||
}
|
||||
|
||||
dot: function(dotObj, x, yTop) {
|
||||
return [dotObj, {cx: x, cy: yTop}, 350, "easein"];
|
||||
// dot.animate({cy: yTop}, 350, mina.easein);
|
||||
},
|
||||
export function animateBar(bar, x, yTop, width, offset=0, index=0, meta={}) {
|
||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
|
||||
y -= offset;
|
||||
if(bar.nodeName !== 'rect') {
|
||||
let rect = bar.childNodes[0];
|
||||
let rectAnim = [
|
||||
rect,
|
||||
{width: width, height: height},
|
||||
UNIT_ANIM_DUR,
|
||||
STD_EASING
|
||||
]
|
||||
|
||||
path: function(d, pathStr) {
|
||||
let pathComponents = [];
|
||||
const animPath = [{unit: d.path, object: d, key: 'path'}, {d:"M"+pathStr}, 350, "easein"];
|
||||
pathComponents.push(animPath);
|
||||
let oldCoordStr = bar.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||
let groupAnim = translate(bar, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||
return [rectAnim, groupAnim];
|
||||
} else {
|
||||
return [[bar, {width: width, height: height, x: x, y: y}, UNIT_ANIM_DUR, STD_EASING]];
|
||||
}
|
||||
// bar.animate({height: args.newHeight, y: yTop}, UNIT_ANIM_DUR, mina.easein);
|
||||
}
|
||||
|
||||
if(d.regionPath) {
|
||||
let regStartPt = `0,${this.zeroLine}L`;
|
||||
let regEndPt = `L${this.totalWidth}, ${this.zeroLine}`;
|
||||
export function animateDot(dot, x, y) {
|
||||
if(dot.nodeName !== 'circle') {
|
||||
let oldCoordStr = dot.getAttribute("transform").split("(")[1].slice(0, -1);
|
||||
let groupAnim = translate(dot, oldCoordStr, [x, y], MARKER_LINE_ANIM_DUR);
|
||||
return [groupAnim];
|
||||
} else {
|
||||
return [[dot, {cx: x, cy: y}, UNIT_ANIM_DUR, STD_EASING]];
|
||||
}
|
||||
// dot.animate({cy: yTop}, UNIT_ANIM_DUR, mina.easein);
|
||||
}
|
||||
|
||||
const animRegion = [
|
||||
{unit: d.regionPath, object: d, key: 'regionPath'},
|
||||
{d:"M" + regStartPt + pathStr + regEndPt},
|
||||
350,
|
||||
"easein"
|
||||
];
|
||||
pathComponents.push(animRegion);
|
||||
}
|
||||
export function animatePath(paths, newXList, newYList, zeroLine) {
|
||||
let pathComponents = [];
|
||||
|
||||
return pathComponents;
|
||||
},
|
||||
};
|
||||
let pointsStr = newYList.map((y, i) => (newXList[i] + ',' + y));
|
||||
let pathStr = pointsStr.join("L");
|
||||
|
||||
const animPath = [paths.path, {d:"M"+pathStr}, PATH_ANIM_DUR, STD_EASING];
|
||||
pathComponents.push(animPath);
|
||||
|
||||
if(paths.region) {
|
||||
let regStartPt = `${newXList[0]},${zeroLine}L`;
|
||||
let regEndPt = `L${newXList.slice(-1)[0]}, ${zeroLine}`;
|
||||
|
||||
const animRegion = [
|
||||
paths.region,
|
||||
{d:"M" + regStartPt + pathStr + regEndPt},
|
||||
PATH_ANIM_DUR,
|
||||
STD_EASING
|
||||
];
|
||||
pathComponents.push(animRegion);
|
||||
}
|
||||
|
||||
return pathComponents;
|
||||
}
|
||||
|
||||
export function animatePathStr(oldPath, pathStr) {
|
||||
return [oldPath, {d: pathStr}, UNIT_ANIM_DUR, STD_EASING];
|
||||
}
|
||||
|
||||
return Animator;
|
||||
})();
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
// Leveraging SMIL Animations
|
||||
|
||||
import { REPLACE_ALL_NEW_DUR } from './animate';
|
||||
|
||||
const EASING = {
|
||||
ease: "0.25 0.1 0.25 1",
|
||||
linear: "0 0 1 1",
|
||||
@ -9,7 +11,7 @@ const EASING = {
|
||||
easeinout: "0.42 0 0.58 1"
|
||||
};
|
||||
|
||||
function animateSVG(element, props, dur, easingType="linear", type=undefined, oldValues={}) {
|
||||
function animateSVGElement(element, props, dur, easingType="linear", type=undefined, oldValues={}) {
|
||||
|
||||
let animElement = element.cloneNode(true);
|
||||
let newElement = element.cloneNode(true);
|
||||
@ -65,29 +67,23 @@ export function transform(element, style) { // eslint-disable-line no-unused-var
|
||||
element.style.oTransform = style;
|
||||
}
|
||||
|
||||
export function runSVGAnimation(svgContainer, elements) {
|
||||
function animateSVG(svgContainer, elements) {
|
||||
let newElements = [];
|
||||
let animElements = [];
|
||||
|
||||
elements.map(element => {
|
||||
let obj = element[0];
|
||||
let parent = obj.unit.parentNode;
|
||||
let unit = element[0];
|
||||
let parent = unit.parentNode;
|
||||
|
||||
let animElement, newElement;
|
||||
|
||||
element[0] = obj.unit;
|
||||
[animElement, newElement] = animateSVG(...element);
|
||||
element[0] = unit;
|
||||
[animElement, newElement] = animateSVGElement(...element);
|
||||
|
||||
newElements.push(newElement);
|
||||
animElements.push([animElement, parent]);
|
||||
|
||||
parent.replaceChild(animElement, obj.unit);
|
||||
|
||||
if(obj.array) {
|
||||
obj.array[obj.index] = newElement;
|
||||
} else {
|
||||
obj.object[obj.key] = newElement;
|
||||
}
|
||||
parent.replaceChild(animElement, unit);
|
||||
});
|
||||
|
||||
let animSvg = svgContainer.cloneNode(true);
|
||||
@ -99,3 +95,22 @@ export function runSVGAnimation(svgContainer, elements) {
|
||||
|
||||
return animSvg;
|
||||
}
|
||||
|
||||
export function runSMILAnimation(parent, svgElement, elementsToAnimate) {
|
||||
if(elementsToAnimate.length === 0) return;
|
||||
|
||||
let animSvgElement = animateSVG(svgElement, elementsToAnimate);
|
||||
if(svgElement.parentNode == parent) {
|
||||
parent.removeChild(svgElement);
|
||||
parent.appendChild(animSvgElement);
|
||||
|
||||
}
|
||||
|
||||
// Replace the new svgElement (data has already been replaced)
|
||||
setTimeout(() => {
|
||||
if(animSvgElement.parentNode == parent) {
|
||||
parent.removeChild(animSvgElement);
|
||||
parent.appendChild(svgElement);
|
||||
}
|
||||
}, REPLACE_ALL_NEW_DUR);
|
||||
}
|
||||
|
||||
124
src/js/utils/axis-chart-utils.js
Normal file
124
src/js/utils/axis-chart-utils.js
Normal file
@ -0,0 +1,124 @@
|
||||
import { floatTwo, fillArray } from '../utils/helpers';
|
||||
import { DEFAULT_AXIS_CHART_TYPE, AXIS_DATASET_CHART_TYPES, DEFAULT_CHAR_WIDTH } from '../utils/constants';
|
||||
|
||||
export function dataPrep(data, type) {
|
||||
data.labels = data.labels || [];
|
||||
|
||||
let datasetLength = data.labels.length;
|
||||
|
||||
// Datasets
|
||||
let datasets = data.datasets;
|
||||
let zeroArray = new Array(datasetLength).fill(0);
|
||||
if(!datasets) {
|
||||
// default
|
||||
datasets = [{
|
||||
values: zeroArray
|
||||
}];
|
||||
}
|
||||
|
||||
datasets.map((d, i)=> {
|
||||
// Set values
|
||||
if(!d.values) {
|
||||
d.values = zeroArray;
|
||||
} else {
|
||||
// Check for non values
|
||||
let vals = d.values;
|
||||
vals = vals.map(val => (!isNaN(val) ? val : 0));
|
||||
|
||||
// Trim or extend
|
||||
if(vals.length > datasetLength) {
|
||||
vals = vals.slice(0, datasetLength);
|
||||
} else {
|
||||
vals = fillArray(vals, datasetLength - vals.length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Set labels
|
||||
//
|
||||
|
||||
// Set type
|
||||
if(!d.chartType ) {
|
||||
if(!AXIS_DATASET_CHART_TYPES.includes(type)) type === DEFAULT_AXIS_CHART_TYPE;
|
||||
d.chartType = type;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Markers
|
||||
|
||||
// Regions
|
||||
// data.yRegions = data.yRegions || [];
|
||||
if(data.yRegions) {
|
||||
data.yRegions.map(d => {
|
||||
if(d.end < d.start) {
|
||||
[d.start, d.end] = [d.end, d.start];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function zeroDataPrep(realData) {
|
||||
let datasetLength = realData.labels.length;
|
||||
let zeroArray = new Array(datasetLength).fill(0);
|
||||
|
||||
let zeroData = {
|
||||
labels: realData.labels.slice(0, -1),
|
||||
datasets: realData.datasets.map(d => {
|
||||
return {
|
||||
name: '',
|
||||
values: zeroArray.slice(0, -1),
|
||||
chartType: d.chartType
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
||||
if(realData.yMarkers) {
|
||||
zeroData.yMarkers = [
|
||||
{
|
||||
value: 0,
|
||||
label: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
if(realData.yRegions) {
|
||||
zeroData.yRegions = [
|
||||
{
|
||||
start: 0,
|
||||
end: 0,
|
||||
label: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
return zeroData;
|
||||
}
|
||||
|
||||
export function getShortenedLabels(chartWidth, labels=[], isSeries=true) {
|
||||
let allowedSpace = chartWidth / labels.length;
|
||||
let allowedLetters = allowedSpace / DEFAULT_CHAR_WIDTH;
|
||||
|
||||
let calcLabels = labels.map((label, i) => {
|
||||
label += "";
|
||||
if(label.length > allowedLetters) {
|
||||
|
||||
if(!isSeries) {
|
||||
if(allowedLetters-3 > 0) {
|
||||
label = label.slice(0, allowedLetters-3) + " ...";
|
||||
} else {
|
||||
label = label.slice(0, allowedLetters) + '..';
|
||||
}
|
||||
} else {
|
||||
let multiple = Math.ceil(label.length/allowedLetters);
|
||||
if(i % multiple !== 0) {
|
||||
label = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
return label;
|
||||
});
|
||||
|
||||
return calcLabels;
|
||||
}
|
||||
@ -16,7 +16,7 @@ const PRESET_COLOR_MAP = {
|
||||
};
|
||||
|
||||
export const DEFAULT_COLORS = ['light-blue', 'blue', 'violet', 'red', 'orange',
|
||||
'yellow', 'green', 'light-green', 'purple', 'magenta'];
|
||||
'yellow', 'green', 'light-green', 'purple', 'magenta', 'light-grey', 'dark-grey'];
|
||||
|
||||
function limitColor(r){
|
||||
if (r > 255) return 255;
|
||||
|
||||
23
src/js/utils/constants.js
Normal file
23
src/js/utils/constants.js
Normal file
@ -0,0 +1,23 @@
|
||||
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 INIT_CHART_UPDATE_TIMEOUT = 700;
|
||||
export const CHART_POST_ANIMATE_TIMEOUT = 400;
|
||||
|
||||
export const DEFAULT_AXIS_CHART_TYPE = 'line';
|
||||
export const AXIS_DATASET_CHART_TYPES = ['line', 'bar'];
|
||||
|
||||
export const 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 DEFAULT_CHAR_WIDTH = 7;
|
||||
|
||||
// Universal constants
|
||||
export const ANGLE_RATIO = Math.PI / 180;
|
||||
export const FULL_ANGLE = 360;
|
||||
@ -31,4 +31,9 @@ export function addDays(date, numberOfDays) {
|
||||
date.setDate(date.getDate() + numberOfDays);
|
||||
}
|
||||
|
||||
// export function getMonthName() {}
|
||||
export function getMonthName(i) {
|
||||
let monthNames = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
];
|
||||
return monthNames[i];
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ $.create = (tag, o) => {
|
||||
return element;
|
||||
};
|
||||
|
||||
export function offset(element) {
|
||||
export function getOffset(element) {
|
||||
let rect = element.getBoundingClientRect();
|
||||
return {
|
||||
// https://stackoverflow.com/a/7436602/6495043
|
||||
@ -74,7 +74,7 @@ export function getElementContentWidth(element) {
|
||||
return element.clientWidth - padding;
|
||||
}
|
||||
|
||||
$.bind = (element, o) => {
|
||||
export function bind(element, o){
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
var callback = o[event];
|
||||
@ -84,9 +84,9 @@ $.bind = (element, o) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$.unbind = (element, o) => {
|
||||
export function unbind(element, o){
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
var callback = o[event];
|
||||
@ -96,9 +96,9 @@ $.unbind = (element, o) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
$.fire = (target, type, properties) => {
|
||||
export function fire(target, type, properties) {
|
||||
var evt = document.createEvent("HTMLEvents");
|
||||
|
||||
evt.initEvent(type, true, true );
|
||||
@ -108,4 +108,4 @@ $.fire = (target, type, properties) => {
|
||||
}
|
||||
|
||||
return target.dispatchEvent(evt);
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
export function getBarHeightAndYAttr(yTop, zeroLine, totalHeight) {
|
||||
import { fillArray } from './helpers';
|
||||
|
||||
export function getBarHeightAndYAttr(yTop, zeroLine) {
|
||||
let height, y;
|
||||
if (yTop <= zeroLine) {
|
||||
height = zeroLine - yTop;
|
||||
y = yTop;
|
||||
|
||||
// In case of invisible bars
|
||||
if(height === 0) {
|
||||
height = totalHeight * 0.01;
|
||||
y -= height;
|
||||
}
|
||||
} else {
|
||||
height = yTop - zeroLine;
|
||||
y = zeroLine;
|
||||
|
||||
// In case of invisible bars
|
||||
if(height === 0) {
|
||||
height = totalHeight * 0.01;
|
||||
}
|
||||
}
|
||||
|
||||
return [height, y];
|
||||
}
|
||||
|
||||
export function equilizeNoOfElements(array1, array2,
|
||||
extraCount = array2.length - array1.length) {
|
||||
|
||||
// Doesn't work if either has zero elements.
|
||||
if(extraCount > 0) {
|
||||
array1 = fillArray(array1, extraCount);
|
||||
} else {
|
||||
array2 = fillArray(array2, extraCount);
|
||||
}
|
||||
return [array1, array2];
|
||||
}
|
||||
|
||||
@ -1,12 +1,19 @@
|
||||
import { getBarHeightAndYAttr } from './draw-utils';
|
||||
import { getStringWidth } from './helpers';
|
||||
import { STD_EASING, UNIT_ANIM_DUR, MARKER_LINE_ANIM_DUR, PATH_ANIM_DUR } from './animate';
|
||||
import { DOT_OVERLAY_SIZE_INCR } from './constants';
|
||||
|
||||
// Constants used
|
||||
const AXIS_TICK_LENGTH = 6;
|
||||
const LABEL_MARGIN = 4;
|
||||
export const FONT_SIZE = 10;
|
||||
const BASE_LINE_COLOR = '#dadada';
|
||||
const BASE_BG_COLOR = '#F7FAFC';
|
||||
|
||||
function $(expr, con) {
|
||||
return typeof expr === "string"? (con || document).querySelector(expr) : expr || null;
|
||||
}
|
||||
|
||||
function createSVG(tag, o) {
|
||||
export function createSVG(tag, o) {
|
||||
var element = document.createElementNS("http://www.w3.org/2000/svg", tag);
|
||||
|
||||
for (var i in o) {
|
||||
@ -82,6 +89,14 @@ export function makeSVGGroup(parent, className, transform='') {
|
||||
});
|
||||
}
|
||||
|
||||
export function wrapInSVGGroup(elements, className='') {
|
||||
let g = createSVG('g', {
|
||||
className: className
|
||||
});
|
||||
elements.forEach(e => g.appendChild(e));
|
||||
return g;
|
||||
}
|
||||
|
||||
export function makePath(pathStr, className='', stroke='none', fill='none') {
|
||||
return createSVG('path', {
|
||||
className: className,
|
||||
@ -93,8 +108,18 @@ export function makePath(pathStr, className='', stroke='none', fill='none') {
|
||||
});
|
||||
}
|
||||
|
||||
export function makeArcPathStr(startPosition, endPosition, center, radius, clockWise=1){
|
||||
let [arcStartX, arcStartY] = [center.x + startPosition.x, center.y + startPosition.y];
|
||||
let [arcEndX, arcEndY] = [center.x + endPosition.x, center.y + endPosition.y];
|
||||
|
||||
return `M${center.x} ${center.y}
|
||||
L${arcStartX} ${arcStartY}
|
||||
A ${radius} ${radius} 0 0 ${clockWise ? 1 : 0}
|
||||
${arcEndX} ${arcEndY} z`;
|
||||
}
|
||||
|
||||
export function makeGradient(svgDefElem, color, lighter = false) {
|
||||
let gradientId ='path-fill-gradient' + '-' + color;
|
||||
let gradientId ='path-fill-gradient' + '-' + color + '-' +(lighter ? 'lighter' : 'default');
|
||||
let gradientDef = renderVerticalGradient(svgDefElem, gradientId);
|
||||
let opacities = [1, 0.6, 0.2];
|
||||
if(lighter) {
|
||||
@ -130,109 +155,385 @@ export function makeText(className, x, y, content) {
|
||||
className: className,
|
||||
x: x,
|
||||
y: y,
|
||||
dy: '.32em',
|
||||
dy: (FONT_SIZE / 2) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
innerHTML: content
|
||||
});
|
||||
}
|
||||
|
||||
export function makeXLine(height, textStartAt, point, labelClass, axisLineClass, xPos) {
|
||||
let line = createSVG('line', {
|
||||
function makeVertLine(x, label, y1, y2, options={}) {
|
||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
|
||||
let l = createSVG('line', {
|
||||
className: 'line-vertical ' + options.className,
|
||||
x1: 0,
|
||||
x2: 0,
|
||||
y1: 0,
|
||||
y2: height
|
||||
y1: y1,
|
||||
y2: y2,
|
||||
styles: {
|
||||
stroke: options.stroke
|
||||
}
|
||||
});
|
||||
|
||||
let text = createSVG('text', {
|
||||
className: labelClass,
|
||||
x: 0,
|
||||
y: textStartAt,
|
||||
dy: '.71em',
|
||||
innerHTML: point
|
||||
y: y1 > y2 ? y1 + LABEL_MARGIN : y1 - LABEL_MARGIN - FONT_SIZE,
|
||||
dy: FONT_SIZE + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
'text-anchor': 'middle',
|
||||
innerHTML: label + ""
|
||||
});
|
||||
|
||||
let xLine = createSVG('g', {
|
||||
className: `tick ${axisLineClass}`,
|
||||
transform: `translate(${ xPos }, 0)`
|
||||
let line = createSVG('g', {
|
||||
transform: `translate(${ x }, 0)`
|
||||
});
|
||||
|
||||
xLine.appendChild(line);
|
||||
xLine.appendChild(text);
|
||||
line.appendChild(l);
|
||||
line.appendChild(text);
|
||||
|
||||
return xLine;
|
||||
return line;
|
||||
}
|
||||
|
||||
export function makeYLine(startAt, width, textEndAt, point, labelClass, axisLineClass, yPos, darker=false, lineType="") {
|
||||
let line = createSVG('line', {
|
||||
className: lineType === "dashed" ? "dashed": "",
|
||||
x1: startAt,
|
||||
x2: width,
|
||||
function makeHoriLine(y, label, x1, x2, options={}) {
|
||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
|
||||
if(!options.lineType) options.lineType = '';
|
||||
let className = 'line-horizontal ' + options.className +
|
||||
(options.lineType === "dashed" ? "dashed": "");
|
||||
|
||||
let l = createSVG('line', {
|
||||
className: className,
|
||||
x1: x1,
|
||||
x2: x2,
|
||||
y1: 0,
|
||||
y2: 0
|
||||
y2: 0,
|
||||
styles: {
|
||||
stroke: options.stroke
|
||||
}
|
||||
});
|
||||
|
||||
let text = createSVG('text', {
|
||||
className: labelClass,
|
||||
x: textEndAt,
|
||||
x: x1 < x2 ? x1 - LABEL_MARGIN : x1 + LABEL_MARGIN,
|
||||
y: 0,
|
||||
dy: '.32em',
|
||||
innerHTML: point+""
|
||||
dy: (FONT_SIZE / 2 - 2) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
'text-anchor': x1 < x2 ? 'end' : 'start',
|
||||
innerHTML: label+""
|
||||
});
|
||||
|
||||
let yLine = createSVG('g', {
|
||||
className: `tick ${axisLineClass}`,
|
||||
transform: `translate(0, ${yPos})`,
|
||||
let line = createSVG('g', {
|
||||
transform: `translate(0, ${y})`,
|
||||
'stroke-opacity': 1
|
||||
});
|
||||
|
||||
if(darker) {
|
||||
if(text === 0 || text === '0') {
|
||||
line.style.stroke = "rgba(27, 31, 35, 0.6)";
|
||||
}
|
||||
|
||||
yLine.appendChild(line);
|
||||
yLine.appendChild(text);
|
||||
line.appendChild(l);
|
||||
line.appendChild(text);
|
||||
|
||||
return yLine;
|
||||
return line;
|
||||
}
|
||||
|
||||
export var UnitRenderer = (function() {
|
||||
var UnitRenderer = function(totalHeight, zeroLine, avgUnitWidth) {
|
||||
this.totalHeight = totalHeight;
|
||||
this.zeroLine = zeroLine;
|
||||
this.avgUnitWidth = avgUnitWidth;
|
||||
};
|
||||
export function yLine(y, label, width, options={}) {
|
||||
if(!options.pos) options.pos = 'left';
|
||||
if(!options.offset) options.offset = 0;
|
||||
if(!options.mode) options.mode = 'span';
|
||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
|
||||
if(!options.className) options.className = '';
|
||||
|
||||
UnitRenderer.prototype = {
|
||||
bar: function (x, yTop, args, color, index, datasetIndex, noOfDatasets) {
|
||||
let totalWidth = this.avgUnitWidth - args.spaceWidth;
|
||||
let startX = x - totalWidth/2;
|
||||
let x1 = -1 * AXIS_TICK_LENGTH;
|
||||
let x2 = options.mode === 'span' ? width + AXIS_TICK_LENGTH : 0;
|
||||
|
||||
let width = totalWidth / noOfDatasets;
|
||||
let currentX = startX + width * datasetIndex;
|
||||
if(options.mode === 'tick' && options.pos === 'right') {
|
||||
x1 = width + AXIS_TICK_LENGTH
|
||||
x2 = width;
|
||||
}
|
||||
|
||||
let [height, y] = getBarHeightAndYAttr(yTop, this.zeroLine, this.totalHeight);
|
||||
let offset = options.pos === 'left' ? -1 * options.offset : options.offset;
|
||||
|
||||
return createSVG('rect', {
|
||||
className: `bar mini`,
|
||||
style: `fill: ${color}`,
|
||||
'data-point-index': index,
|
||||
x: currentX,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
x1 += options.offset;
|
||||
x2 += options.offset;
|
||||
|
||||
return makeHoriLine(y, label, x1, x2, {
|
||||
stroke: options.stroke,
|
||||
className: options.className,
|
||||
lineType: options.lineType
|
||||
});
|
||||
}
|
||||
|
||||
export function xLine(x, label, height, options={}) {
|
||||
if(!options.pos) options.pos = 'bottom';
|
||||
if(!options.offset) options.offset = 0;
|
||||
if(!options.mode) options.mode = 'span';
|
||||
if(!options.stroke) options.stroke = BASE_LINE_COLOR;
|
||||
if(!options.className) options.className = '';
|
||||
|
||||
// Draw X axis line in span/tick mode with optional label
|
||||
// y2(span)
|
||||
// |
|
||||
// |
|
||||
// x line |
|
||||
// |
|
||||
// |
|
||||
// ---------------------+-- y2(tick)
|
||||
// |
|
||||
// y1
|
||||
|
||||
let y1 = height + AXIS_TICK_LENGTH;
|
||||
let y2 = options.mode === 'span' ? -1 * AXIS_TICK_LENGTH : height;
|
||||
|
||||
if(options.mode === 'tick' && options.pos === 'top') {
|
||||
// top axis ticks
|
||||
y1 = -1 * AXIS_TICK_LENGTH;
|
||||
y2 = 0;
|
||||
}
|
||||
|
||||
return makeVertLine(x, label, y1, y2, {
|
||||
stroke: options.stroke,
|
||||
className: options.className,
|
||||
lineType: options.lineType
|
||||
});
|
||||
}
|
||||
|
||||
export function yMarker(y, label, width, options={}) {
|
||||
let labelSvg = createSVG('text', {
|
||||
className: 'chart-label',
|
||||
x: width - getStringWidth(label, 5) - LABEL_MARGIN,
|
||||
y: 0,
|
||||
dy: (FONT_SIZE / -2) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
'text-anchor': 'start',
|
||||
innerHTML: label+""
|
||||
});
|
||||
|
||||
let line = makeHoriLine(y, '', 0, width, {
|
||||
stroke: options.stroke || BASE_LINE_COLOR,
|
||||
className: options.className || '',
|
||||
lineType: options.lineType
|
||||
});
|
||||
|
||||
line.appendChild(labelSvg);
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
export function yRegion(y1, y2, width, label) {
|
||||
// return a group
|
||||
let height = y1 - y2;
|
||||
|
||||
let rect = createSVG('rect', {
|
||||
className: `bar mini`, // remove class
|
||||
styles: {
|
||||
fill: `rgba(228, 234, 239, 0.49)`,
|
||||
stroke: BASE_LINE_COLOR,
|
||||
'stroke-dasharray': `${width}, ${height}`
|
||||
},
|
||||
// 'data-point-index': index,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
|
||||
dot: function(x, y, args, color, index) {
|
||||
return createSVG('circle', {
|
||||
style: `fill: ${color}`,
|
||||
'data-point-index': index,
|
||||
cx: x,
|
||||
cy: y,
|
||||
r: args.radius
|
||||
});
|
||||
let labelSvg = createSVG('text', {
|
||||
className: 'chart-label',
|
||||
x: width - getStringWidth(label+"", 4.5) - LABEL_MARGIN,
|
||||
y: 0,
|
||||
dy: (FONT_SIZE / -2) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
'text-anchor': 'start',
|
||||
innerHTML: label+""
|
||||
});
|
||||
|
||||
let region = createSVG('g', {
|
||||
transform: `translate(0, ${y2})`
|
||||
});
|
||||
|
||||
region.appendChild(rect);
|
||||
region.appendChild(labelSvg);
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
export function datasetBar(x, yTop, width, color, label='', index=0, offset=0, meta={}) {
|
||||
let [height, y] = getBarHeightAndYAttr(yTop, meta.zeroLine);
|
||||
y -= offset;
|
||||
|
||||
let rect = createSVG('rect', {
|
||||
className: `bar mini`,
|
||||
style: `fill: ${color}`,
|
||||
'data-point-index': index,
|
||||
x: x,
|
||||
y: y,
|
||||
width: width,
|
||||
height: height || meta.minHeight // TODO: correct y for positive min height
|
||||
});
|
||||
|
||||
label += "";
|
||||
|
||||
if(!label && !label.length) {
|
||||
return rect;
|
||||
} else {
|
||||
rect.setAttribute('y', 0);
|
||||
rect.setAttribute('x', 0);
|
||||
let text = createSVG('text', {
|
||||
className: 'data-point-value',
|
||||
x: width/2,
|
||||
y: 0,
|
||||
dy: (FONT_SIZE / 2 * -1) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
'text-anchor': 'middle',
|
||||
innerHTML: label
|
||||
});
|
||||
|
||||
let group = createSVG('g', {
|
||||
'data-point-index': index,
|
||||
transform: `translate(${x}, ${y})`
|
||||
});
|
||||
group.appendChild(rect);
|
||||
group.appendChild(text);
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
export function datasetDot(x, y, radius, color, label='', index=0, meta={}) {
|
||||
let dot = createSVG('circle', {
|
||||
style: `fill: ${color}`,
|
||||
'data-point-index': index,
|
||||
cx: x,
|
||||
cy: y,
|
||||
r: radius
|
||||
});
|
||||
|
||||
label += "";
|
||||
|
||||
if(!label && !label.length) {
|
||||
return dot;
|
||||
} else {
|
||||
dot.setAttribute('cy', 0);
|
||||
dot.setAttribute('cx', 0);
|
||||
|
||||
let text = createSVG('text', {
|
||||
className: 'data-point-value',
|
||||
x: 0,
|
||||
y: 0,
|
||||
dy: (FONT_SIZE / 2 * -1 - radius) + 'px',
|
||||
'font-size': FONT_SIZE + 'px',
|
||||
'text-anchor': 'middle',
|
||||
innerHTML: label
|
||||
});
|
||||
|
||||
let group = createSVG('g', {
|
||||
'data-point-index': index,
|
||||
transform: `translate(${x}, ${y})`
|
||||
});
|
||||
group.appendChild(dot);
|
||||
group.appendChild(text);
|
||||
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPaths(xList, yList, color, options={}, meta={}) {
|
||||
let pointsList = yList.map((y, i) => (xList[i] + ',' + y));
|
||||
let pointsStr = pointsList.join("L");
|
||||
let path = makePath("M"+pointsStr, 'line-graph-path', color);
|
||||
|
||||
// HeatLine
|
||||
if(options.heatline) {
|
||||
let gradient_id = makeGradient(meta.svgDefs, color);
|
||||
path.style.stroke = `url(#${gradient_id})`;
|
||||
}
|
||||
|
||||
let paths = {
|
||||
path: path
|
||||
}
|
||||
|
||||
// Region
|
||||
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})`);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
export let makeOverlay = {
|
||||
'bar': (unit) => {
|
||||
let transformValue;
|
||||
if(unit.nodeName !== 'rect') {
|
||||
transformValue = unit.getAttribute('transform');
|
||||
unit = unit.childNodes[0];
|
||||
}
|
||||
};
|
||||
let overlay = unit.cloneNode();
|
||||
overlay.style.fill = '#000000';
|
||||
overlay.style.opacity = '0.4';
|
||||
|
||||
if(transformValue) {
|
||||
overlay.setAttribute('transform', transformValue);
|
||||
}
|
||||
return overlay;
|
||||
},
|
||||
|
||||
'dot': (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);
|
||||
}
|
||||
return overlay;
|
||||
}
|
||||
}
|
||||
|
||||
export let updateOverlay = {
|
||||
'bar': (unit, overlay) => {
|
||||
let transformValue;
|
||||
if(unit.nodeName !== 'rect') {
|
||||
transformValue = unit.getAttribute('transform');
|
||||
unit = unit.childNodes[0];
|
||||
}
|
||||
let attributes = ['x', 'y', 'width', 'height'];
|
||||
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);
|
||||
}
|
||||
},
|
||||
|
||||
'dot': (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UnitRenderer;
|
||||
})();
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { ANGLE_RATIO } from './constants';
|
||||
|
||||
/**
|
||||
* Returns the value of a number upto 2 decimal places.
|
||||
* @param {Number} d Any number
|
||||
@ -37,6 +39,22 @@ export function shuffle(array) {
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill an array with extra points
|
||||
* @param {Array} array Array
|
||||
* @param {Number} count number of filler elements
|
||||
* @param {Object} element element to fill with
|
||||
* @param {Boolean} start fill at start?
|
||||
*/
|
||||
export function fillArray(array, count, element, start=false) {
|
||||
if(!element) {
|
||||
element = start ? array[0] : array[array.length - 1];
|
||||
}
|
||||
let fillerArray = new Array(Math.abs(count)).fill(element);
|
||||
array = start ? fillerArray.concat(array) : array.concat(fillerArray);
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns pixel width of string.
|
||||
* @param {String} string
|
||||
@ -45,3 +63,23 @@ export function shuffle(array) {
|
||||
export function getStringWidth(string, charWidth) {
|
||||
return (string+"").length * charWidth;
|
||||
}
|
||||
|
||||
export function bindChange(obj, getFn, setFn) {
|
||||
return new Proxy(obj, {
|
||||
set: function(target, prop, value) {
|
||||
setFn();
|
||||
return Reflect.set(target, prop, value);
|
||||
},
|
||||
get: function(target, prop, value) {
|
||||
getFn();
|
||||
return Reflect.get(target, prop);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getPositionByAngle(angle, radius) {
|
||||
return {
|
||||
x:Math.sin(angle * ANGLE_RATIO) * radius,
|
||||
y:Math.cos(angle * ANGLE_RATIO) * radius,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { floatTwo } from './helpers';
|
||||
|
||||
function normalize(x) {
|
||||
// Calculates mantissa and exponent of a number
|
||||
// Returns normalized number and exponent
|
||||
@ -21,7 +23,7 @@ function normalize(x) {
|
||||
return [sig * man, exp];
|
||||
}
|
||||
|
||||
function getRangeIntervals(max, min=0) {
|
||||
function getChartRangeIntervals(max, min=0) {
|
||||
let upperBound = Math.ceil(max);
|
||||
let lowerBound = Math.floor(min);
|
||||
let range = upperBound - lowerBound;
|
||||
@ -59,19 +61,19 @@ function getRangeIntervals(max, min=0) {
|
||||
return intervals;
|
||||
}
|
||||
|
||||
function getIntervals(maxValue, minValue=0) {
|
||||
function getChartIntervals(maxValue, minValue=0) {
|
||||
let [normalMaxValue, exponent] = normalize(maxValue);
|
||||
let normalMinValue = minValue ? minValue/Math.pow(10, exponent): 0;
|
||||
|
||||
// Allow only 7 significant digits
|
||||
normalMaxValue = normalMaxValue.toFixed(6);
|
||||
|
||||
let intervals = getRangeIntervals(normalMaxValue, normalMinValue);
|
||||
let intervals = getChartRangeIntervals(normalMaxValue, normalMinValue);
|
||||
intervals = intervals.map(value => value * Math.pow(10, exponent));
|
||||
return intervals;
|
||||
}
|
||||
|
||||
export function calcIntervals(values, withMinimum=false) {
|
||||
export function calcChartIntervals(values, withMinimum=false) {
|
||||
//*** Where the magic happens ***
|
||||
|
||||
// Calculates best-fit y intervals from given values
|
||||
@ -84,7 +86,7 @@ export function calcIntervals(values, withMinimum=false) {
|
||||
let exponent = 0, intervals = []; // eslint-disable-line no-unused-vars
|
||||
|
||||
function getPositiveFirstIntervals(maxValue, absMinValue) {
|
||||
let intervals = getIntervals(maxValue);
|
||||
let intervals = getChartIntervals(maxValue);
|
||||
|
||||
let intervalSize = intervals[1] - intervals[0];
|
||||
|
||||
@ -102,9 +104,9 @@ export function calcIntervals(values, withMinimum=false) {
|
||||
if(maxValue >= 0 && minValue >= 0) {
|
||||
exponent = normalize(maxValue)[1];
|
||||
if(!withMinimum) {
|
||||
intervals = getIntervals(maxValue);
|
||||
intervals = getChartIntervals(maxValue);
|
||||
} else {
|
||||
intervals = getIntervals(maxValue, minValue);
|
||||
intervals = getChartIntervals(maxValue, minValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,9 +144,9 @@ export function calcIntervals(values, withMinimum=false) {
|
||||
|
||||
exponent = normalize(pseudoMaxValue)[1];
|
||||
if(!withMinimum) {
|
||||
intervals = getIntervals(pseudoMaxValue);
|
||||
intervals = getChartIntervals(pseudoMaxValue);
|
||||
} else {
|
||||
intervals = getIntervals(pseudoMaxValue, pseudoMinValue);
|
||||
intervals = getChartIntervals(pseudoMaxValue, pseudoMinValue);
|
||||
}
|
||||
|
||||
intervals = intervals.reverse().map(d => d * (-1));
|
||||
@ -153,6 +155,51 @@ export function calcIntervals(values, withMinimum=false) {
|
||||
return intervals;
|
||||
}
|
||||
|
||||
export function getZeroIndex(yPts) {
|
||||
let zeroIndex;
|
||||
let interval = getIntervalSize(yPts);
|
||||
if(yPts.indexOf(0) >= 0) {
|
||||
// the range has a given zero
|
||||
// zero-line on the chart
|
||||
zeroIndex = yPts.indexOf(0);
|
||||
} else if(yPts[0] > 0) {
|
||||
// Minimum value is positive
|
||||
// zero-line is off the chart: below
|
||||
let min = yPts[0];
|
||||
zeroIndex = (-1) * min / interval;
|
||||
} else {
|
||||
// Maximum value is negative
|
||||
// zero-line is off the chart: above
|
||||
let max = yPts[yPts.length - 1];
|
||||
zeroIndex = (-1) * max / interval + (yPts.length - 1);
|
||||
}
|
||||
return zeroIndex;
|
||||
}
|
||||
|
||||
export function getRealIntervals(max, noOfIntervals, min = 0, asc = 1) {
|
||||
let range = max - min;
|
||||
let part = range * 1.0 / noOfIntervals;
|
||||
let intervals = [];
|
||||
|
||||
for(var i = 0; i <= noOfIntervals; i++) {
|
||||
intervals.push(min + part * i);
|
||||
}
|
||||
|
||||
return asc ? intervals : intervals.reverse();
|
||||
}
|
||||
|
||||
export function getIntervalSize(orderedArray) {
|
||||
return orderedArray[1] - orderedArray[0];
|
||||
}
|
||||
|
||||
export function getValueRange(orderedArray) {
|
||||
return orderedArray[orderedArray.length-1] - orderedArray[0];
|
||||
}
|
||||
|
||||
export function scale(val, yAxis) {
|
||||
return floatTwo(yAxis.zeroLine - val * yAxis.scaleMultiplier)
|
||||
}
|
||||
|
||||
export function calcDistribution(values, distributionSize) {
|
||||
// Assume non-negative values,
|
||||
// implying distribution minimum at zero
|
||||
|
||||
@ -28,8 +28,13 @@
|
||||
}
|
||||
.graph-stats-container {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding-top: 10px;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
.stats {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
@ -51,8 +56,8 @@
|
||||
}
|
||||
}
|
||||
.axis, .chart-label {
|
||||
font-size: 11px;
|
||||
fill: #555b51;
|
||||
// temp commented
|
||||
line {
|
||||
stroke: #dadada;
|
||||
}
|
||||
@ -62,11 +67,26 @@
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
.data-points {
|
||||
.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 {
|
||||
@ -78,17 +98,18 @@
|
||||
line.dashed {
|
||||
stroke-dasharray: 5,3;
|
||||
}
|
||||
.tick {
|
||||
&.x-axis-label {
|
||||
display: block;
|
||||
}
|
||||
.axis-line {
|
||||
// &.x-axis-label {
|
||||
// display: block;
|
||||
// }
|
||||
// TODO: hack dy attr to be settable via styles
|
||||
.specific-value {
|
||||
text-anchor: start;
|
||||
}
|
||||
.y-value-text {
|
||||
.y-line {
|
||||
text-anchor: end;
|
||||
}
|
||||
.x-value-text {
|
||||
.x-line {
|
||||
text-anchor: middle;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user