@@ -39,6 +39,10 @@ hr { | |||
margin-bottom: 2rem; | |||
} | |||
.step-explain { | |||
margin-top: 30px; | |||
} | |||
pre.highlight { | |||
background: #f7f7f7; | |||
border-radius: 3px; | |||
@@ -8,7 +8,7 @@ let bar_composite_data = { | |||
"2013", "2014", "2015", "2016", "2017"], | |||
"datasets": [{ | |||
"title": "Reports", | |||
"title": "Events", | |||
"color": "orange", | |||
"values": report_count_list, | |||
// "formatted": report_count_list.map(d => d + " reports") | |||
@@ -40,7 +40,7 @@ let more_line_data = { | |||
let bar_composite_chart = new Chart ({ | |||
parent: "#chart-composite-1", | |||
title: "Reposrts", | |||
title: "Fireball/Bolide Events - Yearly (more than 5 reports)", | |||
data: bar_composite_data, | |||
type: 'bar', | |||
height: 180, | |||
@@ -63,23 +63,79 @@ bar_composite_chart.parent.addEventListener('data-select', (e) => { | |||
// Demo Chart (bar, linepts, scatter(blobs), percentage) | |||
// ================================================================================ | |||
let type_data = { | |||
"labels": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], | |||
"datasets": [{ | |||
"color": "light-blue", | |||
"values": [25, 40, 30, 35, 8, 52, 17] | |||
"labels": ["12am-3am", "3am-6am", "6am-9am", "9am-12pm", | |||
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"], | |||
"datasets": [ | |||
{ | |||
title: "Some Data", color: "light-blue", | |||
values: [25, 40, 30, 35, 8, 52, 17, -4] | |||
}, | |||
{ | |||
"color": "violet", | |||
"values": [25, 50, -10, 15, 18, 32, 27] | |||
title: "Another Set", color: "violet", | |||
values: [25, 50, -10, 15, 18, 32, 27, 14] | |||
}, | |||
{ | |||
title: "Yet Another", color: "blue", | |||
values: [15, 20, -3, -15, 58, 12, -17, 37] | |||
} | |||
] | |||
}; | |||
let type_chart = new Chart({ | |||
parent: "#chart-types", | |||
title: "My Awesome Chart", | |||
data: type_data, | |||
type: 'bar', | |||
height: 250 | |||
}); | |||
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'); | |||
type_chart = type_chart.get_different_chart(type); | |||
Array.prototype.slice.call( | |||
btn.parentNode.querySelectorAll('button')).map(el => { | |||
el.classList.remove('active'); | |||
}); | |||
btn.classList.add('active'); | |||
}); | |||
}); | |||
// Trends Chart | |||
// ================================================================================ | |||
let trends_data = { | |||
"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"], | |||
"datasets": [ | |||
{ | |||
"color": "blue", | |||
"values": [15, 20, -3, -15, 58, 12, -17] | |||
"values": [25, 40, 30, 35, 48, 52, 17, 15, 20, -3, -15, 58, | |||
12, -17, 35, 48, 40, 30, 52, 17, 25, 5, 48, 52, 17] | |||
} | |||
] | |||
}; | |||
let plot_chart = new Chart({ | |||
parent: "#chart-trends", | |||
data: trends_data, | |||
type: 'line', | |||
height: 250, | |||
show_dots: 0, | |||
// region_fill: 1, | |||
heatline: 1, | |||
x_axis_mode: 'tick', | |||
y_axis_mode: 'tick' | |||
}); | |||
// 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"]; | |||
@@ -103,7 +159,7 @@ let update_data = { | |||
"specific_values": [ | |||
{ | |||
title: "Altitude", | |||
// title: "Altiteragrwst ude", | |||
// title: "A very long text", | |||
line_type: "dashed", | |||
value: 38 | |||
}, | |||
@@ -152,14 +208,7 @@ let heatmap_data = { | |||
// ================================================================================ | |||
let type_chart = new Chart({ | |||
parent: "#chart-types", | |||
data: type_data, | |||
type: 'bar', | |||
height: 250, | |||
// region_fill: 1, | |||
// y_axis_mode: 'tick' | |||
}); | |||
let update_chart = new Chart({ | |||
parent: "#chart-update", | |||
@@ -195,24 +244,6 @@ let heatmap = new Chart({ | |||
// Events | |||
// ================================================================================ | |||
Array.prototype.slice.call( | |||
document.querySelectorAll('.chart-type-buttons button') | |||
).map(el => { | |||
el.addEventListener('click', (e) => { | |||
btn = e.target; | |||
let type = btn.getAttribute('data-type'); | |||
type_chart = type_chart.get_different_chart(type); | |||
Array.prototype.slice.call( | |||
btn.parentNode.querySelectorAll('button')).map(el => { | |||
el.classList.remove('active'); | |||
}); | |||
btn.classList.add('active'); | |||
}); | |||
}); | |||
let chart_update_buttons = document.querySelector('.chart-update-buttons'); | |||
@@ -32,7 +32,7 @@ | |||
<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">Use arrow keys to navigate data points</p> | |||
<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> | |||
@@ -46,15 +46,51 @@ | |||
<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 new chart | |||
Create a chart | |||
</h6> | |||
<pre><code class="hljs html"><div id="chart"></div></code></pre> | |||
<p class="step-explain">Include it in your HTML</p> | |||
<pre><code class="hljs html"> <script src="frappe-charts.min.js" /></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: [ | |||
{ | |||
title: "Some Data", color: "light-blue", | |||
values: [25, 40, 30, 35, 8, 52, 17, -4] | |||
}, | |||
{ | |||
title: "Another Set", color: "violet", | |||
values: [25, 50, -10, 15, 18, 32, 27, 14] | |||
}, | |||
{ | |||
title: "Yet Another", color: "blue", | |||
values: [15, 20, -3, -15, 58, 12, -17, 37] | |||
} | |||
] | |||
}; | |||
let chart = new Chart({ | |||
parent: "#chart", | |||
title: "My Awesome Chart", | |||
data: data, | |||
type: 'bar', // or 'line', 'scatter', 'percentage' | |||
height: 250 | |||
});</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> | |||
<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> | |||
@@ -74,6 +110,32 @@ | |||
</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"> ... | |||
x_axis_mode: 'tick', // for short label ticks | |||
// or 'span' for long spanning vertical axis lines | |||
y_axis_mode: 'tick', | |||
...</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-update="line">Line</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-update="heatline">HeatLine</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-update="region">Region</button> | |||
</div> | |||
<pre><code class="hljs javascript margin-vertical-px"> ... | |||
type: 'line', // Line chart specific properties: | |||
show_dots: 0, // Show data points on the line; default 1 | |||
heatline: 1, // Show a value-wise line gradient; default 0 | |||
region_fill: 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"> | |||
@@ -102,7 +164,7 @@ | |||
<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 Average</button> | |||
<button type="button" class="btn btn-sm btn-secondary" data-aggregation="average">Show Averages</button> | |||
</div> | |||
<pre><code class="hljs html margin-vertical-px"><div id="chart"></div></code></pre> | |||
</div> | |||
@@ -2,6 +2,7 @@ import '../styles/charts.less'; | |||
import BarChart from './charts/BarChart'; | |||
import LineChart from './charts/LineChart'; | |||
import ScatterChart from './charts/ScatterChart'; | |||
import PercentageChart from './charts/PercentageChart'; | |||
import Heatmap from './charts/Heatmap'; | |||
@@ -19,6 +20,8 @@ export default class Chart { | |||
return new LineChart(arguments[0]); | |||
} else if(args.type === 'bar') { | |||
return new BarChart(arguments[0]); | |||
} else if(args.type === 'scatter') { | |||
return new ScatterChart(arguments[0]); | |||
} else if(args.type === 'percentage') { | |||
return new PercentageChart(arguments[0]); | |||
} else if(args.type === 'heatmap') { | |||
@@ -234,13 +234,15 @@ export default class AxisChart extends BaseChart { | |||
); | |||
} | |||
make_new_units_for_dataset(x_values, y_values, color, dataset_index, no_of_datasets, group, array, unit) { | |||
if(!group) group = this.svg_units_groups[dataset_index]; | |||
if(!array) array = this.y[dataset_index].svg_units; | |||
make_new_units_for_dataset(x_values, y_values, color, dataset_index, | |||
no_of_datasets, units_group, units_array, unit) { | |||
if(!units_group) units_group = this.svg_units_groups[dataset_index]; | |||
if(!units_array) units_array = this.y[dataset_index].svg_units; | |||
if(!unit) unit = this.unit_args; | |||
group.textContent = ''; | |||
array.length = 0; | |||
units_group.textContent = ''; | |||
units_array.length = 0; | |||
y_values.map((y, i) => { | |||
let data_unit = this.draw[unit.type]( | |||
@@ -248,12 +250,17 @@ export default class AxisChart extends BaseChart { | |||
y, | |||
unit.args, | |||
color, | |||
i, | |||
dataset_index, | |||
no_of_datasets | |||
); | |||
group.appendChild(data_unit); | |||
array.push(data_unit); | |||
units_group.appendChild(data_unit); | |||
units_array.push(data_unit); | |||
}); | |||
if(this.is_navigable) { | |||
this.bind_units(units_array); | |||
} | |||
} | |||
make_y_specifics() { | |||
@@ -952,17 +959,18 @@ export default class AxisChart extends BaseChart { | |||
setup_utils() { | |||
this.draw = { | |||
'bar': (x, y_top, args, color, index, no_of_datasets) => { | |||
'bar': (x, y_top, args, color, index, dataset_index, no_of_datasets) => { | |||
let total_width = this.avg_unit_width - args.space_width; | |||
let start_x = x - total_width/2; | |||
let width = total_width / no_of_datasets; | |||
let current_x = start_x + width * index; | |||
let current_x = start_x + width * dataset_index; | |||
let [height, y] = this.get_bar_height_and_y_attr(y_top); | |||
return $.createSVG('rect', { | |||
className: `bar mini fill ${color}`, | |||
'data-point-index': index, | |||
x: current_x, | |||
y: y, | |||
width: width, | |||
@@ -970,9 +978,10 @@ export default class AxisChart extends BaseChart { | |||
}); | |||
}, | |||
'dot': (x, y, args, color) => { | |||
'dot': (x, y, args, color, index) => { | |||
return $.createSVG('circle', { | |||
className: `fill ${color}`, | |||
'data-point-index': index, | |||
cx: x, | |||
cy: y, | |||
r: args.radius | |||
@@ -44,6 +44,15 @@ export default class BarChart extends AxisChart { | |||
}); | |||
} | |||
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 => { | |||
@@ -1,5 +1,6 @@ | |||
import SvgTip from '../objects/SvgTip'; | |||
import $ from '../helpers/dom'; | |||
import Chart from '../charts'; | |||
export default class BaseChart { | |||
constructor({ | |||
@@ -36,7 +37,7 @@ export default class BaseChart { | |||
} | |||
this.has_legend = has_legend; | |||
this.chart_types = ['line', 'bar', 'percentage', 'heatmap']; | |||
this.chart_types = ['line', 'scatter', 'bar', 'percentage', 'heatmap']; | |||
this.set_margins(height); | |||
} | |||
@@ -49,9 +50,10 @@ export default class BaseChart { | |||
// Only across compatible types | |||
let compatible_types = { | |||
bar: ['line', 'percentage'], | |||
line: ['bar', 'percentage'], | |||
percentage: ['bar', 'line'], | |||
bar: ['line', 'scatter', 'percentage'], | |||
line: ['scatter', 'bar', 'percentage'], | |||
scatter: ['line', 'bar', 'percentage'], | |||
percentage: ['bar', 'line', 'scatter'], | |||
heatmap: [] | |||
}; | |||
@@ -62,8 +64,9 @@ export default class BaseChart { | |||
// 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 BaseChart({ | |||
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 | |||
@@ -127,7 +130,7 @@ export default class BaseChart { | |||
setup_container() { | |||
this.container = $.create('div', { | |||
className: 'chart-container', | |||
innerHTML: `<h6 class="title" style="margin-top: 15px;">${this.title}</h6> | |||
innerHTML: `<h6 class="title">${this.title}</h6> | |||
<h6 class="sub-title uppercase">${this.subtitle}</h6> | |||
<div class="frappe-chart graphics"></div> | |||
<div class="graph-stats-container"></div>` | |||
@@ -216,6 +219,7 @@ export default class BaseChart { | |||
make_overlay() {} | |||
bind_overlay() {} | |||
bind_units() {} | |||
on_left_arrow() {} | |||
on_right_arrow() {} | |||
@@ -238,6 +242,7 @@ export default class BaseChart { | |||
} | |||
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; | |||
@@ -4,14 +4,23 @@ import $ from '../helpers/dom'; | |||
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.region_fill = args.region_fill; | |||
this.x_axis_mode = args.x_axis_mode || 'span'; | |||
this.y_axis_mode = args.y_axis_mode || 'span'; | |||
this.setup(); | |||
} | |||
@@ -35,10 +44,18 @@ export default class LineChart extends AxisChart { | |||
super.setup_values(); | |||
this.unit_args = { | |||
type: 'dot', | |||
args: { radius: 8 } | |||
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]); | |||
@@ -57,39 +74,60 @@ export default class LineChart extends AxisChart { | |||
d: "M"+points_str | |||
}); | |||
if(this.heatline) { | |||
let gradient_id = this.make_gradient(color); | |||
d.path.style.stroke = `url(#${gradient_id})`; | |||
} | |||
if(this.region_fill) { | |||
let gradient_id ='path-fill-gradient' + '-' + color; | |||
this.gradient_def = $.createSVG('linearGradient', { | |||
inside: this.svg_defs, | |||
id: gradient_id, | |||
x1: 0, | |||
x2: 0, | |||
y1: 0, | |||
y2: 1 | |||
}); | |||
this.fill_region_for_dataset(d, i, color, points_str); | |||
} | |||
} | |||
fill_region_for_dataset(d, i, color, points_str) { | |||
let gradient_id = this.make_gradient(color, true); | |||
d.region_path = $.createSVG('path', { | |||
inside: this.paths_groups[i], | |||
className: `region-fill`, | |||
d: "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`, | |||
}); | |||
d.region_path.style.stroke = "none"; | |||
d.region_path.style.fill = `url(#${gradient_id})`; | |||
} | |||
make_gradient(color, lighter = false) { | |||
let gradient_id ='path-fill-gradient' + '-' + color; | |||
let gradient_def = $.createSVG('linearGradient', { | |||
inside: this.svg_defs, | |||
id: gradient_id, | |||
x1: 0, | |||
x2: 0, | |||
y1: 0, | |||
y2: 1 | |||
}); | |||
let set_gradient_stop = (grad_elem, offset, color, opacity) => { | |||
$.createSVG('stop', { | |||
'className': 'stop-color ' + color, | |||
'inside': grad_elem, | |||
'offset': offset, | |||
'stop-opacity': opacity | |||
}); | |||
}; | |||
set_gradient_stop(this.gradient_def, "0%", color, 0.4); | |||
set_gradient_stop(this.gradient_def, "50%", color, 0.2); | |||
set_gradient_stop(this.gradient_def, "100%", color, 0); | |||
d.region_path = $.createSVG('path', { | |||
inside: this.paths_groups[i], | |||
className: `region-fill`, | |||
d: "M" + `0,${this.zero_line}L` + points_str + `L${this.width},${this.zero_line}`, | |||
let set_gradient_stop = (grad_elem, offset, color, opacity) => { | |||
$.createSVG('stop', { | |||
'className': 'stop-color ' + color, | |||
'inside': grad_elem, | |||
'offset': offset, | |||
'stop-opacity': opacity | |||
}); | |||
}; | |||
let opacities = [1, 0.6, 0.2]; | |||
d.region_path.style.stroke = "none"; | |||
d.region_path.style.fill = `url(#${gradient_id})`; | |||
if(lighter) { | |||
opacities = [0.4, 0.2, 0]; | |||
} | |||
set_gradient_stop(gradient_def, "0%", color, opacities[0]); | |||
set_gradient_stop(gradient_def, "50%", color, opacities[1]); | |||
set_gradient_stop(gradient_def, "100%", color, opacities[2]); | |||
return gradient_id; | |||
} | |||
} |
@@ -0,0 +1,35 @@ | |||
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() {} | |||
} |
@@ -3,6 +3,15 @@ | |||
.graph-focus-margin { | |||
margin: 0px 5%; | |||
} | |||
&>.title { | |||
margin-top: 25px; | |||
margin-left: 25px; | |||
text-align: left; | |||
text-transform: uppercase; | |||
font-weight: normal; | |||
font-size: 12px; | |||
color: #6c7680; | |||
} | |||
.graphics { | |||
margin-top: 10px; | |||
padding-top: 10px; | |||
@@ -39,7 +48,7 @@ | |||
} | |||
} | |||
.axis, .chart-label { | |||
font-size: 10px; | |||
font-size: 11px; | |||
fill: #555b51; | |||
line { | |||
stroke: #dadada; | |||
@@ -127,10 +136,12 @@ | |||
li { | |||
min-width: 90px; | |||
flex: 1; | |||
font-weight: 600; | |||
} | |||
} | |||
strong { | |||
color: #dfe2e5; | |||
font-weight: 600; | |||
} | |||
.svg-pointer { | |||
position: absolute; | |||