Есть статичная круговая диаграмма.
Задача при наведении на сектор,
вызвать callback функцию
поменять размер сектора
цвет
а еще если можно выполнить дополнительную обводку по периметру окружности пример
на картинке
но последний пункт, если это слишком трудно, и костылем решить смогу (если получится
справиться с первыми двумя пунктами), наложив одну диаграмму на другую.
Html структуру менять можно, главное получить результат.
var canvas = document.getElementById("can");
var ctx = canvas.getContext("2d");
var lastend = 0;
var data = [200, 60, 15]; //мои сектора
var myTotal = 0;
var myColor = ['red', 'green', 'blue']; //цвета
for (var e = 0; e < data.length; e++) {
myTotal += data[e];
}
for (var i = 0; i < data.length; i++) {
ctx.fillStyle = myColor[i];
ctx.beginPath();
ctx.moveTo(canvas.width / 2, canvas.height / 2);
// параметры: x, y, радиус, начальный угол, конечный угол, antiClockwise (boolean)
ctx.arc(canvas.width / 2, canvas.height / 2, canvas.height / 2, lastend, lastend
+ (Math.PI * 2 * (data[i] / myTotal)), false);
ctx.lineTo(canvas.width / 2, canvas.height / 2);
ctx.fill();
lastend += Math.PI * 2 * (data[i] / myTotal);
}
Ответы
Ответ 1
Предлагаю такой способ, считаем дистанцию от мышки до центра круга.
Если дистанция меньше радиуса - мы навели на какой-то сектор, а на какой конкретно
определяем по углу направления в сторону курсора мышки относительно центра круга.
let data = [200, 60, 15];
let colors = ['red', 'green', 'blue'];
let radius = canvas.height / 2 - 10;
let ctx = canvas.getContext("2d");
let total = data.reduce((a, b) => a + b);
let radiuses = [radius, radius, radius];
let activeAngle, activeIndex;
function callback(active) {
stat.textContent = colors[active] || 'none';
canvas.style.cursor = active > -1 ? 'pointer' : 'default';
}
function render() {
requestAnimationFrame(render);
let currentActiveIndex;
ctx.clearRect(0, 0, canvas.width, canvas.height);
var lastend = 0;
for (var i = 0; i < data.length; i++) {
ctx.fillStyle = colors[i];
ctx.beginPath();
ctx.moveTo(canvas.width / 2, canvas.height / 2);
let endAngle = lastend + (Math.PI * 2 * (data[i] / total));
let isActive = activeAngle && activeAngle > lastend && activeAngle < endAngle;
currentActiveIndex = isActive ? i : currentActiveIndex;
radiuses[i] += isActive ? 1 : -1;
radiuses[i] = Math.min(Math.max(radius, radiuses[i]), radius+10);
ctx.arc(canvas.width / 2, canvas.height / 2, radiuses[i], lastend, endAngle);
ctx.lineTo(canvas.width / 2, canvas.height / 2);
ctx.fill();
lastend += Math.PI * 2 * (data[i] / total);
}
activeIndex != currentActiveIndex && callback(activeIndex = currentActiveIndex);
}
requestAnimationFrame(render)
canvas.addEventListener('mousemove', e => {
let dy = e.layerY - canvas.height / 2;
let dx = e.layerX - canvas.width / 2;
let d = Math.sqrt(dx*dx + dy*dy) < radius;
let a = Math.atan2(dy, dx);
activeAngle = d ? a < 0 ? a + 2*Math.PI : a : null;
})
none
Ответ 2
Рекомендую использовать для таких целей Pixi или любую другую библиотеку для работы
с canvas. Ибо canvas api не имеет поддержки событий наведения или впринципе хранить
какие-либо структурированные данные об отрисованных объектах. Для добавления подобных
функций нужно писать обертки, которые будут хранить контекст и кастомные характеристики,
а судя по твоему коду - тебе не до этого. А вот в Pixi все уже есть, fabric.js тоже
отлично подойдет для этой цели.
PS
на будущее имей в виду, что canvas рендерит только один кадр, если ты видишь взаимодействие
с ним и какие-либо анимации (например анимация увеличения размера участка твоего графика),
то они реализованы через нативные события и таймеры. на каждую 1/60 (60 кадров в сек.)
canvas рендерится заново с новыми входными параметрами для абстрактных объектов.
Ответ 3
class Pipe {
constructor(_parent) {
this.canvas = Pipe.genCanvasElement();
this.ctx = this.canvas.getContext("2d");
this.curent = null;
this.mousemove = Pipe.debounce(this, function (_e) {
// console.dir(_e)
let xM, yM;
xM = _e.offsetX;
yM = _e.offsetY;
let x, y;
x = this.canvas.width / 2;
y = this.canvas.height / 2;
let radians = Math.atan2(yM - y, xM - x);
let degrees = radians * 180 / Math.PI;
if ((360 + degrees) < 360) {
degrees = 360 + degrees;
radians = degrees * Math.PI / 180;
}
// console.log(`x:${xM} y:${yM} radians:${radians} degrees:${degrees}`)
// --
this.curent = radians;
// console.log('mousemove')
this.render();
}, 500);
this.mouseleave = Pipe.debounce(this, function (_e) {
this.curent = null;
// console.log('mouseleave')
if (Pipe.t)
clearTimeout(Pipe.t);
this.render();
}, 500);
this.canvas.addEventListener('mousemove', this.mousemove);
this.canvas.addEventListener('mouseleave', this.mouseleave);
_parent.appendChild(this.canvas);
this.render();
}
static debounce(self, f, ms) {
return function (...args) {
const onComplete = () => {
f.apply(self, args);
Pipe.t = null;
};
if (Pipe.t)
clearTimeout(Pipe.t);
Pipe.t = setTimeout(onComplete, ms);
};
}
static genCanvasElement() {
let c = document.createElement('canvas');
c.width = 200;
c.height = 200;
c.style.border = 'solid grey 1px';
return c;
}
static foo(ctx, fillStyle, x, y, r, startAngle, endAngle) {
ctx.beginPath();
ctx.fillStyle = fillStyle;
ctx.moveTo(x, y);
ctx.arc(x, y, r, startAngle, endAngle, false);
ctx.lineTo(x, y);
ctx.fill();
ctx.closePath();
}
static foo2(startAngle, curent, endAngle, fillStyle, x, y, r, j, ctx) {
// console.log(`
// startAngle:${startAngle}
// endAngle:${endAngle}
// this.curent:${this.curent}
// `)
if ((startAngle < curent) && (curent < endAngle)) {
fillStyle = 'aqua';
Pipe.foo(ctx, fillStyle, x, y, r + j, startAngle, endAngle);
}
else {
Pipe.foo(ctx, fillStyle, x, y, r, startAngle, endAngle);
}
}
render() {
let ctx = this.ctx;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
let lastend = 0;
let data = [200, 60, 15]; //мои сектора
let myColor = ['red', 'green', 'blue']; //цвета
let j = this.canvas.width / 8;
let x, y, r;
x = this.canvas.width / 2;
y = this.canvas.height / 2;
r = y - j;
for (var i = 0; i < data.length; i++) {
let startAngle = lastend;
let endAngle = lastend + (Math.PI * 2 * (data[i] / 360));
let fillStyle = myColor[i];
Pipe.foo2(startAngle, this.curent, endAngle, fillStyle, x, y, r, j, ctx);
// --
lastend += Math.PI * 2 * (data[i] / 360);
}
if (lastend < Math.PI * 2) {
let startAngle = lastend;
let endAngle = Math.PI * 2;
let fillStyle = 'black';
Pipe.foo2(startAngle, this.curent, endAngle, fillStyle, x, y, r, j, ctx);
}
}
}
Pipe.t = null;
// ---
let pipe = new Pipe(document.body);