add ScatterChart, heatline, onclick; update first graph on page

This commit is contained in:
pratu16x7 2017-10-30 02:17:49 +05:30
parent 1ec700c6af
commit 371c227029
12 changed files with 306 additions and 99 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -39,6 +39,10 @@ hr {
margin-bottom: 2rem;
}
.step-explain {
margin-top: 30px;
}
pre.highlight {
background: #f7f7f7;
border-radius: 3px;

View File

@ -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]
},
{
"color": "violet",
"values": [25, 50, -10, 15, 18, 32, 27]
"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": "blue",
"values": [15, 20, -3, -15, 58, 12, -17]
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": [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');

View File

@ -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">&lt;div id="chart"&gt;&lt;/div&gt;</code></pre>
<p class="step-explain">Include it in your HTML</p>
<pre><code class="hljs html"> &lt;script src="frappe-charts.min.js" /&gt;</code></pre>
<p class="step-explain">Make a new Chart</p>
<pre><code class="hljs html"> &lt!--HTML--&gt;
&lt;div id="chart"&gt;&lt;/div&gt;</code></pre>
<pre><code class="hljs javascript"> // Javascript
let data = {
labels: ["12am-3am", "3am-6am", "6am-9am", "9am-12pm",
"12pm-3pm", "3pm-6pm", "6pm-9pm", "9pm-12am"],
datasets: [
{
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">&lt;div id="chart"&gt;&lt;/div&gt;</code></pre>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -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
});
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}`,
});
d.region_path.style.stroke = "none";
d.region_path.style.fill = `url(#${gradient_id})`;
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
});
};
let opacities = [1, 0.6, 0.2];
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;
}
}

View File

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

View File

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