Skip to content

Commit

Permalink
Merge branch 'master' into production
Browse files Browse the repository at this point in the history
  • Loading branch information
Joao Paulo Oliveira Fernandes committed Jun 19, 2020
2 parents 09cac3e + ced0451 commit f75c8fd
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 29 deletions.
124 changes: 96 additions & 28 deletions ChartJSEnhancements.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as Chart from 'chart.js';

export default class ChartJSEnhancements {
constructor(chartjs_object) {
constructor(chartjs_object, change_point_radius = true) {
this.chartjs_object = chartjs_object;
this.canvas = chartjs_object.canvas;
this.ctx = this.chartjs_object.ctx;
this.datasets = this.chartjs_object.data.datasets;
this.data_length = this.datasets.reduce(function(accumulator, dataset) {
Expand All @@ -10,9 +13,12 @@ export default class ChartJSEnhancements {

this.chartjs_object.options.hoverMode = this.quick_mode ? null : Chart.defaults.global.hover.mode;
this.chartjs_object.options.hover.animationDuration = this.quick_mode ? 0 : 200;
for (let i = 0; i < this.datasets.length; ++i) {
this.datasets[i].pointRadius = Array(this.datasets[i].data.length);
this.datasets[i].pointBorderWidth = Array(this.datasets[i].data.length);
this.change_point_radius = change_point_radius;
if (this.change_point_radius) {
for (let i = 0; i < this.datasets.length; ++i) {
this.datasets[i].pointRadius = Array(this.datasets[i].data.length);
this.datasets[i].pointBorderWidth = Array(this.datasets[i].data.length);
}
}
this.chartjs_object.update({duration: 0});
}
Expand Down Expand Up @@ -43,10 +49,12 @@ export default class ChartJSEnhancements {

unselectPoints(update = true) {
this.selected_points = [];
this.datasets.forEach(dataset => {
dataset.pointRadius = Array(dataset.data.length);
dataset.pointBorderWidth = Array(dataset.data.length);
});
if (this.change_point_radius) {
this.datasets.forEach(dataset => {
dataset.pointRadius = Array(dataset.data.length);
dataset.pointBorderWidth = Array(dataset.data.length);
});
}
if (update) this.chartjs_object.update({duration: this.quick_mode ? 0 : 300});
if (this.after_unselect_handler !== undefined) this.after_unselect_handler();
}
Expand All @@ -73,6 +81,10 @@ export default class ChartJSEnhancements {
break;
default:
this.ignore_mouse_button = true;
if (event.shiftKey) {
this.action = "pan";
this.using_shift = true;
}
}
this.chartjs_object.unbindEvents();
this.rect_selector.startX = event.offsetX;
Expand Down Expand Up @@ -104,13 +116,19 @@ export default class ChartJSEnhancements {
resizeHeight: this.chartjs_object.canvas.offsetHeight,
resizeQuality: "high"
}).then(image => {
this.temp_ctx.fillStyle = this.temp_background_color;
this.temp_ctx.fillRect(0, 0, this.temp_canvas.width, this.temp_canvas.height);
this.temp_ctx.drawImage(image, 0, 0);
}).catch(err => {
this.temp_ctx.fillStyle = this.temp_background_color;
this.temp_ctx.fillRect(0, 0, this.temp_canvas.width, this.temp_canvas.height);
this.temp_ctx.drawImage(this.chartjs_object.canvas, 0, 0, this.chartjs_object.canvas.width,
this.chartjs_object.canvas.height, 0, 0, this.chartjs_object.canvas.offsetWidth,
this.chartjs_object.canvas.offsetHeight);
});
} else {
this.temp_ctx.fillStyle = this.temp_background_color;
this.temp_ctx.fillRect(0, 0, this.temp_canvas.width, this.temp_canvas.height);
this.temp_ctx.drawImage(this.chartjs_object.canvas, 0, 0);
}
}
Expand All @@ -128,7 +146,12 @@ export default class ChartJSEnhancements {
const ratio_y = point_px_y/height;
for (let i in this.chartjs_object.options.scales.yAxes) {
const y_axis_id = this.chartjs_object.options.scales.yAxes[i].id;
const point_y = this.chartjs_object.scales[y_axis_id].max - (this.chartjs_object.scales[y_axis_id].max - this.chartjs_object.scales[y_axis_id].min)*ratio_y;
let point_y;
if (this.chartjs_object.options.scales.yAxes[i].ticks.reverse) {
point_y = this.chartjs_object.scales[y_axis_id].min - (this.chartjs_object.scales[y_axis_id].min - this.chartjs_object.scales[y_axis_id].max)*ratio_y;
} else {
point_y = this.chartjs_object.scales[y_axis_id].max - (this.chartjs_object.scales[y_axis_id].max - this.chartjs_object.scales[y_axis_id].min)*ratio_y;
}
this.selector_points.y[y_axis_id].push(point_y);
}

Expand All @@ -152,32 +175,40 @@ export default class ChartJSEnhancements {
this.chartjs_object.options.scales.xAxes[0].ticks.min = x_min_max_data.min;
this.chartjs_object.options.scales.xAxes[0].ticks.max = x_min_max_data.max;
for (let i in this.chartjs_object.options.scales.yAxes) {
this.chartjs_object.options.scales.yAxes[i].ticks.max = y_min_max_data[this.chartjs_object.options.scales.yAxes[i].id].max;
this.chartjs_object.options.scales.yAxes[i].ticks.min = y_min_max_data[this.chartjs_object.options.scales.yAxes[i].id].min;
if (this.chartjs_object.options.scales.yAxes[i].ticks.reverse) {
this.chartjs_object.options.scales.yAxes[i].ticks.max = y_min_max_data[this.chartjs_object.options.scales.yAxes[i].id].min;
this.chartjs_object.options.scales.yAxes[i].ticks.min = y_min_max_data[this.chartjs_object.options.scales.yAxes[i].id].max;
} else {
this.chartjs_object.options.scales.yAxes[i].ticks.max = y_min_max_data[this.chartjs_object.options.scales.yAxes[i].id].max;
this.chartjs_object.options.scales.yAxes[i].ticks.min = y_min_max_data[this.chartjs_object.options.scales.yAxes[i].id].min;
}
}
} else if (this.action === "select") {
let filtered = Array(this.data_length);
let j = 0;
for (let i = 0; i < this.datasets.length; ++i) {
let dataset = this.datasets[i];
dataset.data.forEach((datum, index) => {
for (let index = 0; index < this.datasets.length; ++index) {
let datum = this.datasets[index];
const is_inside = datum.x <= x_min_max_data.max && datum.x >= x_min_max_data.min &&
datum.y <= y_min_max_data[dataset.yAxisID].max && datum.y >= y_min_max_data[dataset.yAxisID].min;
datum.index = index;
if (is_inside) {
dataset.pointRadius[index] = 5;
dataset.pointBorderWidth[index] = 2;
if (this.change_point_radius) {
dataset.pointRadius[index] = 5;
dataset.pointBorderWidth[index] = 2;
}
filtered[j++] = {
x: datum.x,
y: datum.y,
dataset: dataset.label,
index: datum.index,
}
} else {
} else if (this.change_point_radius) {
dataset.pointRadius[index] = 3;
dataset.pointBorderWidth[index] = 1;
}
});
}
}
filtered = filtered.slice(0, j);
this.selected_points = filtered;
Expand All @@ -198,7 +229,10 @@ export default class ChartJSEnhancements {
this.panning = false;
this.mouse_button_value = 0;
this.selecting_points = false;
if (!this.ignore_mouse_button) this.action = this.previous_action;
if (!this.ignore_mouse_button || this.using_shift) {
this.action = this.previous_action;
this.using_shift = false;
};
}
}

Expand All @@ -207,7 +241,7 @@ export default class ChartJSEnhancements {
if (!this.panning) {
this.rect_selector.w = event.offsetX - this.rect_selector.startX;
this.rect_selector.h = event.offsetY - this.rect_selector.startY;
this.ctx.clearRect(0, 0, this.temp_canvas.offsetWidth, this.temp_canvas.offsetHeight);
this.ctx.clearRect(0, 0, this.chartjs_object.canvas.offsetWidth, this.chartjs_object.canvas.offsetHeight);
this.ctx.drawImage(this.temp_canvas, 0, 0);
const backup = this.ctx.strokeStyle;
this.ctx.strokeStyle = "#444";
Expand All @@ -231,7 +265,10 @@ export default class ChartJSEnhancements {
for (let i in this.chartjs_object.options.scales.yAxes) {
const y_axis_id = this.chartjs_object.options.scales.yAxes[i].id;
const height_grid = this.chartjs_object.scales[y_axis_id].max - this.chartjs_object.scales[y_axis_id].min;
const height_transformed = height_grid*height_ratio;
let height_transformed = height_grid*height_ratio;
if (this.chartjs_object.options.scales.yAxes[i].ticks.reverse) {
height_transformed = -height_transformed;
}
this.chartjs_object.options.scales.yAxes[i].ticks.min += height_transformed;
this.chartjs_object.options.scales.yAxes[i].ticks.max += height_transformed;
}
Expand Down Expand Up @@ -266,6 +303,10 @@ export default class ChartJSEnhancements {
this.zoom_y_factor = factor;
}

setBackgroundColor(color) {
this.temp_background_color = color;
}

zoom(event) {
event.preventDefault();
if (this.chartjs_object.options.scales.xAxes[0].ticks.min === undefined || this.chartjs_object.options.scales.xAxes[0].ticks.max === undefined) {
Expand All @@ -284,16 +325,37 @@ export default class ChartJSEnhancements {
const ratio_y = point_px_y/height;
for (let i in this.chartjs_object.options.scales.yAxes) {
const width_view = this.chartjs_object.options.scales.yAxes[i].ticks.max - this.chartjs_object.options.scales.yAxes[i].ticks.min;
this.chartjs_object.options.scales.yAxes[i].ticks.min += this.zoom_y_factor * factor * (1 - ratio_y) * width_view;
this.chartjs_object.options.scales.yAxes[i].ticks.max -= this.zoom_y_factor * factor * ratio_y * width_view;
if (this.chartjs_object.options.scales.yAxes[i].ticks.reverse) {
this.chartjs_object.options.scales.yAxes[i].ticks.min += this.zoom_y_factor * factor * ratio_y * width_view;
this.chartjs_object.options.scales.yAxes[i].ticks.max -= this.zoom_y_factor * factor * (1 - ratio_y) * width_view;
} else {
this.chartjs_object.options.scales.yAxes[i].ticks.min += this.zoom_y_factor * factor * (1 - ratio_y) * width_view;
this.chartjs_object.options.scales.yAxes[i].ticks.max -= this.zoom_y_factor * factor * ratio_y * width_view;
}
}

this.chartjs_object.update({duration: this.quick_mode ? 0 : 50});
}

initialize(action_buttons) {

destroy() {
this.canvas = null;
this.chartjs_object = null;
this.ctx = null;
this.datasets = null;
this.temp_canvas = null;
this.temp_ctx = null;
}

removeEventListeners() {
for (let event_name in this.event_handlers) {
this.canvas.removeEventListener(event_name, this.event_handlers[event_name]);
}
}

initialize(action_buttons, background_color = 'white') {
this.temp_canvas = document.createElement('canvas');
this.temp_ctx = this.temp_canvas.getContext("2d", { alpha: false });
this.temp_background_color = background_color;
this.rect_selector = {};
this.selector_points = {x: [], y: {}};
this.resetPoints();
Expand Down Expand Up @@ -321,10 +383,16 @@ export default class ChartJSEnhancements {
this.action_buttons.pan = action_buttons.pan !== undefined ? action_buttons.pan : 0;
}

this.chartjs_object.canvas.addEventListener('mousedown', this.clickHandler.bind(this));
this.chartjs_object.canvas.addEventListener('mouseup', this.clickHandler.bind(this));
this.chartjs_object.canvas.addEventListener('mousemove', this.mousemoveHandler.bind(this));
this.chartjs_object.canvas.addEventListener('dblclick', () => { this.resetZoom() });
this.chartjs_object.canvas.addEventListener('wheel', this.zoom.bind(this));
this.event_handlers = {
'mousedown': this.clickHandler.bind(this),
'mouseup': this.clickHandler.bind(this),
'mousemove': this.mousemoveHandler.bind(this),
'dblclick': () => { this.resetZoom() },
'wheel': this.zoom.bind(this)
};

for (let event_name in this.event_handlers) {
this.chartjs_object.canvas.addEventListener(event_name, this.event_handlers[event_name]);
}
}
}
42 changes: 41 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,48 @@
# Chart control functions enhancement for ChartJS
A chartjs plugin for zooming, panning and select data in a chartjs instance.
A [chartjs](https://www.chartjs.org/) plugin for **zooming**, **panning** and **selecting** data in a chartjs instance.

See the [example](https://jjppof.github.io/chartjs-plugin-zoom-pan-select/example/index).

### Install:

```
npm install @jjppof/chartjs-plugin-zoom-pan-select
```

### Usage:

```javascript
import ChartJSEnhancements from '@jjppof/chartjs-plugin-zoom-pan-select';

let enhancer = new ChartJSEnhancements(chartjs_object);
enhancer.initialize(mouse_buttons);
/*
mouse_buttons is an optional object with the mouse button number for each chart action (pan, zoom or select).
More info about the numbers: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons#Return_value
Example:
mouse_buttons = {
zoom: zoom_button_value, //optional
select: select_button_value, //optional
pan: pan_button_value //optional
};
*/
```

### Methods:

```javascript
ChartJSEnhancements(chartjs_object, change_point_radius); //change_point_radius: default true, when selecting points, if this is set to true, the radius of the bullets changes
initialize(mouse_buttons, background_color); // mouse_buttons explained on Usage section. background_color if the background color of your chart. Default: white
setAction(action); //sets the mode of the chart if no mouse button is defined. The arg is a string that can be "zoom", "pan" or "select"
setAfterSelectHandler(callable); //callable that receives the selected points array as arg
setAfterUnselectHandler(callable);
setZoomXFactor(factor); //factor is a number
setZoomYFactor(factor);
setBackgroundColor(color); //updates the background color of the chart
setMinMax(); //updates the ticks.max and ticks.min chart properties without updating the chart
unselectPoints(update); //boolean that says if the chart is going to update or not
resetZoom(update, update_options); //boolean that says if the chart is going to update or not. update_options is the update options from chartjs update() function
removeEventListeners(); //removes all event listeners added by this plugin
destroy(); //remove all external references
```
Loading

0 comments on commit f75c8fd

Please sign in to comment.