296 lines
9.2 KiB
JavaScript
296 lines
9.2 KiB
JavaScript
/* Copyright (c) 2015 Jean-Marc VIGLINO,
|
|
released under the CeCILL-B license (French BSD license)
|
|
(http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt).
|
|
*
|
|
* Add a chart style to display charts (pies or bars) on a map
|
|
*/
|
|
|
|
import ol_style_RegularShape from 'ol/style/RegularShape.js'
|
|
import ol_style_Fill from 'ol/style/Fill.js'
|
|
import {asString as ol_color_asString} from 'ol/color.js'
|
|
|
|
/**
|
|
* @classdesc
|
|
* Set chart style for vector features.
|
|
*
|
|
* @constructor
|
|
* @param {} options
|
|
* @param {String} options.type Chart type: pie,pie3D, donut or bar
|
|
* @param {number} options.radius Chart radius/size, default 20
|
|
* @param {number} options.rotation Rotation in radians (positive rotation clockwise). Default is 0.
|
|
* @param {string} [options.declutterMode] Declutter mode "declutter" | "obstacle" | "none" | undefined
|
|
* @param {bool} options.snapToPixel use integral numbers of pixels, default true
|
|
* @param {_ol_style_Stroke_} options.stroke stroke style
|
|
* @param {String|Array<ol_color>} options.colors predefined color set "classic","dark","pale","pastel","neon" / array of color string, default classic
|
|
* @param {Array<number>} options.displacement
|
|
* @param {number} options.offsetX X offset in px (deprecated, use displacement)
|
|
* @param {number} options.offsetY Y offset in px (deprecated, use displacement)
|
|
* @param {number} options.animation step in an animation sequence [0,1]
|
|
* @param {number} options.max maximum value for bar chart
|
|
* @see [Statistic charts example](../../examples/style/map.style.chart.html)
|
|
* @extends {ol_style_RegularShape}
|
|
* @implements {ol.structs.IHasChecksum}
|
|
* @api
|
|
*/
|
|
var ol_style_Chart = class olstyleChart extends ol_style_RegularShape {
|
|
constructor(opt_options) {
|
|
var options = opt_options || {};
|
|
var strokeWidth = 0;
|
|
if (opt_options.stroke)
|
|
strokeWidth = opt_options.stroke.getWidth();
|
|
super({
|
|
radius: options.radius + strokeWidth,
|
|
fill: new ol_style_Fill({ color: [0, 0, 0] }),
|
|
rotation: options.rotation,
|
|
displacement: options.displacement,
|
|
snapToPixel: options.snapToPixel,
|
|
declutterMode: options.declutterMode,
|
|
});
|
|
this.setScale(options.scale || 1);
|
|
|
|
this._stroke = options.stroke;
|
|
this._radius = options.radius || 20;
|
|
this._donutratio = options.donutRatio || 0.5;
|
|
this._type = options.type;
|
|
this._offset = [options.offsetX ? options.offsetX : 0, options.offsetY ? options.offsetY : 0];
|
|
this._animation = (typeof (options.animation) == 'number') ? { animate: true, step: options.animation } : this._animation = { animate: false, step: 1 };
|
|
this._max = options.max;
|
|
// Not yet drawn
|
|
this._done = false
|
|
|
|
this._data = options.data;
|
|
if (options.colors instanceof Array) {
|
|
this._colors = options.colors;
|
|
} else {
|
|
this._colors = ol_style_Chart.colors[options.colors];
|
|
if (!this._colors)
|
|
this._colors = ol_style_Chart.colors.classic;
|
|
}
|
|
this.render()
|
|
}
|
|
/**
|
|
* Clones the style.
|
|
* @return {ol_style_Chart}
|
|
*/
|
|
clone() {
|
|
var s = new ol_style_Chart({
|
|
type: this._type,
|
|
radius: this._radius,
|
|
rotation: this.getRotation(),
|
|
scale: this.getScale(),
|
|
data: this.getData(),
|
|
snapToPixel: this.getSnapToPixel ? this.getSnapToPixel() : false,
|
|
stroke: this._stroke,
|
|
colors: this._colors,
|
|
offsetX: this._offset[0],
|
|
offsetY: this._offset[1],
|
|
animation: this._animation,
|
|
declutterMode: this.getDeclutterMode ? this.getDeclutterMode() : null,
|
|
});
|
|
s.setScale(this.getScale());
|
|
s.setOpacity(this.getOpacity());
|
|
return s;
|
|
}
|
|
/** Get data associatied with the chart
|
|
*/
|
|
getData() {
|
|
return this._data;
|
|
}
|
|
/** Set data associatied with the chart
|
|
* @param {Array<number>}
|
|
*/
|
|
setData(data) {
|
|
this._data = data;
|
|
this._done = false
|
|
}
|
|
/** Get symbol radius
|
|
*/
|
|
getRadius() {
|
|
return this._radius;
|
|
}
|
|
/** Set symbol radius
|
|
* @param {number} symbol radius
|
|
* @param {number} donut ratio
|
|
*/
|
|
setRadius(radius, ratio) {
|
|
this._radius = radius;
|
|
this.donuratio_ = ratio || this.donuratio_;
|
|
this._done = false
|
|
}
|
|
/** Set animation step
|
|
* @param {false|number} false to stop animation or the step of the animation [0,1]
|
|
*/
|
|
setAnimation(step) {
|
|
if (step === false) {
|
|
if (this._animation.animate == false)
|
|
return;
|
|
this._animation.animate = false;
|
|
} else {
|
|
if (this._animation.step == step)
|
|
return;
|
|
this._animation.animate = true;
|
|
this._animation.step = step;
|
|
}
|
|
this._done = false
|
|
}
|
|
/**
|
|
* @return {RenderOptions} The render options
|
|
*/
|
|
createRenderOptions() {
|
|
var opt = super.createRenderOptions();
|
|
opt.chartOptions = [
|
|
'chart',
|
|
Object.values(this._animation||{}).join(','),
|
|
this._type,
|
|
(this._data || []).join(','),
|
|
(this._colors || []).join(','),
|
|
this._donutratio,
|
|
this._max,
|
|
].join('-')
|
|
return opt;
|
|
}
|
|
/** @private
|
|
*/
|
|
getImage(pixelratio) {
|
|
pixelratio = pixelratio || 1;
|
|
|
|
// Get canvas
|
|
var canvas = super.getImage(pixelratio);
|
|
|
|
if (this._done === pixelratio) return canvas;
|
|
this._done = pixelratio
|
|
|
|
var strokeStyle;
|
|
var strokeWidth = 0;
|
|
|
|
if (this._stroke) {
|
|
strokeStyle = ol_color_asString(this._stroke.getColor());
|
|
strokeWidth = this._stroke.getWidth();
|
|
}
|
|
|
|
// draw the circle on the canvas
|
|
var context = (canvas.getContext('2d'));
|
|
context.save();
|
|
// reset transform
|
|
context.setTransform(pixelratio, 0, 0, pixelratio, 0, 0);
|
|
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
context.lineJoin = 'round';
|
|
|
|
var sum = 0;
|
|
var i, c;
|
|
for (i = 0; i < this._data.length; i++) {
|
|
sum += this._data[i];
|
|
}
|
|
|
|
// then move to (x, y)
|
|
context.translate(0, 0);
|
|
|
|
var step = this._animation.animate ? this._animation.step : 1;
|
|
//console.log(this._animation.step)
|
|
// Draw pie
|
|
switch (this._type) {
|
|
case "donut":
|
|
case "pie3D":
|
|
case "pie": {
|
|
var a, a0 = Math.PI * (step - 1.5);
|
|
c = canvas.width / 2 / pixelratio;
|
|
context.strokeStyle = strokeStyle;
|
|
context.lineWidth = strokeWidth;
|
|
context.save();
|
|
if (this._type == "pie3D") {
|
|
context.translate(0, c * 0.3);
|
|
context.scale(1, 0.7);
|
|
context.beginPath();
|
|
context.fillStyle = "#369";
|
|
context.arc(c, c * 1.4, this._radius * step, 0, 2 * Math.PI);
|
|
context.fill();
|
|
context.stroke();
|
|
}
|
|
if (this._type == "donut") {
|
|
context.save();
|
|
context.beginPath();
|
|
context.rect(0, 0, 2 * c, 2 * c);
|
|
context.arc(c, c, this._radius * step * this._donutratio, 0, 2 * Math.PI);
|
|
context.clip("evenodd");
|
|
}
|
|
for (i = 0; i < this._data.length; i++) {
|
|
context.beginPath();
|
|
context.moveTo(c, c);
|
|
context.fillStyle = this._colors[i % this._colors.length];
|
|
a = a0 + 2 * Math.PI * this._data[i] / sum * step;
|
|
context.arc(c, c, this._radius * step, a0, a);
|
|
context.closePath();
|
|
context.fill();
|
|
context.stroke();
|
|
a0 = a;
|
|
}
|
|
if (this._type == "donut") {
|
|
context.restore();
|
|
context.beginPath();
|
|
context.strokeStyle = strokeStyle;
|
|
context.lineWidth = strokeWidth;
|
|
context.arc(c, c, this._radius * step * this._donutratio, Math.PI * (step - 1.5), a0);
|
|
context.stroke();
|
|
}
|
|
context.restore();
|
|
break;
|
|
}
|
|
case "bar":
|
|
default: {
|
|
var max = 0;
|
|
if (this._max) {
|
|
max = this._max;
|
|
} else {
|
|
for (i = 0; i < this._data.length; i++) {
|
|
if (max < this._data[i])
|
|
max = this._data[i];
|
|
}
|
|
}
|
|
var s = Math.min(5, 2 * this._radius / this._data.length);
|
|
c = canvas.width / 2 / pixelratio;
|
|
var b = canvas.width / pixelratio - strokeWidth;
|
|
var x, x0 = c - this._data.length * s / 2;
|
|
context.strokeStyle = strokeStyle;
|
|
context.lineWidth = strokeWidth;
|
|
for (i = 0; i < this._data.length; i++) {
|
|
context.beginPath();
|
|
context.fillStyle = this._colors[i % this._colors.length];
|
|
x = x0 + s;
|
|
var h = this._data[i] / max * 2 * this._radius * step;
|
|
context.rect(x0, b - h, s, h);
|
|
//console.log ( x0+", "+(b-this._data[i]/max*2*this._radius)+", "+x+", "+b);
|
|
context.closePath();
|
|
context.fill();
|
|
context.stroke();
|
|
x0 = x;
|
|
}
|
|
}
|
|
}
|
|
|
|
context.restore();
|
|
|
|
// Set Anchor
|
|
if (!this.setDisplacement) {
|
|
var anchor = this.getAnchor();
|
|
anchor[0] = c - this._offset[0];
|
|
anchor[1] = c - this._offset[1];
|
|
}
|
|
|
|
// return image
|
|
return canvas;
|
|
}
|
|
};
|
|
|
|
/** Default color set: classic, dark, pale, pastel, neon
|
|
*/
|
|
ol_style_Chart.colors = {
|
|
"classic": ["#ffa500","blue","red","green","cyan","magenta","yellow","#0f0"],
|
|
"dark": ["#960","#003","#900","#060","#099","#909","#990","#090"],
|
|
"pale": ["#fd0","#369","#f64","#3b7","#880","#b5d","#666"],
|
|
"pastel": ["#fb4","#79c","#f66","#7d7","#acc","#fdd","#ff9","#b9b"],
|
|
"neon": ["#ff0","#0ff","#0f0","#f0f","#f00","#00f"]
|
|
};
|
|
|
|
export default ol_style_Chart
|