import _ from 'lodash';
import Graphics from '../graphics';
import Utility from '../utility';
import { EasingFunctions } from '../variable';
import Text from '../text';
/**
* [Effect]{@link Effect} 에서 생성, 관리하는 Pie 차트의 이펙트 클래스
* @alias PieEffect
*/
export default class PieEffect {
/**
* @description Pie차트 이펙트에서 필요한 값들을 설정
* @param { object } _object 이펙트 생성에 필요한 각종 오브젝트들
* @param { object } _option 이펙트 생성에 필요한 옵션
*/
constructor(_object, _option) {
/**
* @description ChartJS 오브젝트
* @type { ChartJS }
*/
this.chart = _object.chart;
/**
* @description 이펙트를 생성할 PIXI 오브젝트
* @type { PIXI }
*/
this.pixi = _object.pixi;
/**
* @description Effect 클래스
* @type { Effect }
*/
this.parent = _object.effect;
/**
* @description 생성할 이펙트 옵션
* @type { object }
*/
this.option = _option;
/**
* @description 이펙트를 표현할 Graphics
* @type { Graphics }
*/
this.graphics = new Graphics(this.pixi);
/**
* @description 이펙트를 표현할 Graphics (2개의 Graphics가 필요함)
* @type { Graphics }
*/
this.topGraphics = new Graphics(this.pixi);
/**
* @description label을 표현할 Text
* @type { Text }
*/
this.labelText = new Text(this.pixi, 'red', {});
/**
* @description value를 표현할 Text
* @type { Text }
*/
this.valueText = new Text(this.pixi, '10', {});
/**
* @description percent를 표현할 Text
* @type { Text }
*/
this.percentText = new Text(this.pixi, '57%', {});
/**
* @description label, value, percent를 가지고 있는 배열
* @type { Array }
*/
this.text = [this.labelText, this.valueText, this.percentText];
this.text.map(object => object.setAlpha(0));
/**
* @description extrude 효과 시간
* @type { number }
*/
this.extrudeDuration = 1;
/**
* @description extrude 효과 길이
* @type { number }
*/
this.extrudeDistance = 35;
Math.radians = degrees => degrees * Math.PI / 180;
Math.degrees = radians => radians * 180 / Math.PI;
}
/**
* 마우스를 Pie 차트에 올리면 돌출되는 효과와 함께 텍스트를 보여줌
* @memberof PieEffect
* @instance
*/
extrude() {
this.excuetedEffect = 'extrude';
this.chart.options.tooltips.enabled = false;
this.chart.options.hover.mode = null;
this.extrudePrevIndex = -1;
const {
radiusLength, chartArea,
} = this.chart.controller;
this.radiusLength = radiusLength;
this.center = {
x: (chartArea.right - chartArea.left) / 2 + chartArea.left,
y: (chartArea.bottom - chartArea.top) / 2 + chartArea.top,
};
const { data } = this.chart.config.data.datasets[0];
this.chartValueSum = 0;
for (let i = 0; i < data.length; i++) {
this.chartValueSum += data[i];
}
this.extrudeUpdateInterval = setInterval(() => {
this.extrudeUpdate();
}, 16);
this.chart.canvas.onmousemove = (event) => {
const activePoints = this.chart.getElementsAtEvent(event);
if (activePoints[0]) {
const chartData = activePoints[0]._chart.config.data;
const index = activePoints[0]._index;
if (this.extrudePrevIndex !== index) {
this.extrudePrevIndex = index;
const model = activePoints[0]._model;
const { startAngle, endAngle } = model;
const colorString = chartData.datasets[0].backgroundColor[index];
const colorArray = colorString.substring(5, colorString.length - 3).split(',');
const color = Utility.rgb2hex(colorArray);
const label = chartData.labels[index];
const value = chartData.datasets[0].data[index];
const textAngle = Math.min(startAngle, endAngle) + Math.abs(endAngle - startAngle) / 2;
const labelTextPosition = Utility.findNewPointByAngleWithDistance(this.center, Math.degrees(textAngle), this.radiusLength * 9 / 10);
const valueTextPosition = Utility.findNewPointByAngleWithDistance(this.center, Math.degrees(textAngle), this.radiusLength * 8 / 10);
const percentTextPosition = Utility.findNewPointByAngleWithDistance(this.center, Math.degrees(textAngle), this.radiusLength * 5 / 10);
let textRoatation = Math.atan2(labelTextPosition.y - this.center.y, labelTextPosition.x - this.center.x) + Math.radians(90);
textRoatation = Math.degrees(textRoatation) >= 135 && Math.degrees(textRoatation) <= 225 ? textRoatation + Math.radians(180) : textRoatation;
for (let i = 0; i < this.text.length; i++) {
this.text[i].setAlpha(0);
this.text[i].object.rotation = textRoatation;
this.text[i].object.style.fill = '0xffffff';
this.text[i].setColorBasedOnBackground(colorArray);
this.text[i].object.style.stroke = Utility.hex2string(color);
this.text[i].object.style.strokeThickness = 6;
this.text[i].object.style.dropShadow = true;
this.text[i].object.style.dropShadowColor = '#000000';
this.text[i].object.style.dropShadowBlur = 4;
this.text[i].object.style.dropShadowAngle = Math.PI / 3;
this.text[i].object.style.dropShadowDistance = 6;
this.text[i].setScale(this.parent.ratio.mid * 0.9);
}
this.labelText.object.text = label;
this.valueText.object.text = value;
this.percentText.object.text = `${Math.floor((value / this.chartValueSum) * 100)}%`;
this.extrudeInformation = {
first: Utility.findNewPointByAngleWithDistance(this.center, Math.degrees(startAngle), radiusLength),
second: Utility.findNewPointByAngleWithDistance(this.center, Math.degrees(endAngle), radiusLength),
startAngle,
endAngle,
color,
labelTextPosition,
valueTextPosition,
percentTextPosition,
};
}
}
};
}
/**
* Extrude 효과의 상태 업데이트
* @memberof PieEffect
* @instance
*/
extrudeUpdate() {
if (_.isUndefined(this.extrudeInformation)) {
return;
}
this.extrudeStore = this.extrudeStore || {};
if (_.isUndefined(this.extrudeStore.information) || !_.isEqual(this.extrudeStore.information, this.extrudeInformation)) {
this.graphics.object.clear();
this.extrudeStore.information = this.extrudeInformation;
this.extrudeStore.startTime = new Date();
this.extrudeStore.prevMinusY = 0;
}
const {
first, second, startAngle, endAngle, color, labelTextPosition, valueTextPosition, percentTextPosition,
} = this.extrudeStore.information;
const elapsedTime = new Date() - this.extrudeStore.startTime;
const easingTime = elapsedTime / (this.extrudeDuration * 1000);
const easing = EasingFunctions.easeOutQuart;
const progress = easingTime < 1 ? easing(easingTime) : 1;
const minusY = this.extrudeDistance * progress;
if (progress >= 1) { return; }
this.graphics.object.beginFill(color, 1);
this.graphics.object.moveTo(this.center.x, this.center.y - minusY);
this.graphics.object.lineTo(first.x, first.y - minusY);
this.graphics.object.arc(this.center.x, this.center.y - minusY, this.radiusLength, startAngle, endAngle, false);
this.graphics.object.lineTo(second.x, second.y - minusY);
this.graphics.object.lineTo(this.center.x, this.center.y - minusY);
this.graphics.object.endFill();
this.topGraphics.object.clear();
this.topGraphics.object.beginFill(0xFFFFFF, 0.3);
this.topGraphics.object.lineStyle(2, color, 1);
this.topGraphics.object.moveTo(this.center.x, this.center.y - minusY);
this.topGraphics.object.lineTo(first.x, first.y - minusY);
this.topGraphics.object.arc(this.center.x, this.center.y - minusY, this.radiusLength, startAngle, endAngle, false);
this.topGraphics.object.lineTo(second.x, second.y - minusY);
this.topGraphics.object.lineTo(this.center.x, this.center.y - minusY);
this.topGraphics.object.endFill();
this.labelText.setPosition(labelTextPosition.x, labelTextPosition.y - minusY);
this.labelText.setAlpha(progress);
this.valueText.setPosition(valueTextPosition.x, valueTextPosition.y - minusY);
this.valueText.setAlpha(progress);
this.percentText.setPosition(percentTextPosition.x, percentTextPosition.y - minusY);
this.percentText.setAlpha(progress / 2);
this.extrudeStore.prevMinusY = minusY;
}
/**
* 현재 생성되어 있는 Pie Effect 제거
* <p> [Effect]{@link Effect} 클래스에 의해 호출된다.
* @memberof PieEffect
* @instance
*/
destroy() {
clearInterval(this.extrudeUpdateInterval);
delete this.extrudeUpdateInterval;
this.topGraphics.object.clear();
this.graphics.object.clear();
for (let i = 0; i < 3; i++) {
this.text[i].setAlpha(0);
}
this.chart.options.tooltips.enabled = true;
this.chart.options.hover.mode = 'single';
}
}