123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- const { ccclass, property, executeInEditMode, executionOrder } = cc._decorator;
- /**
- * 雷达图组件
- * @see RadarChart.ts https://gitee.com/ifaswind/eazax-ccc/blob/master/components/RadarChart.ts
- */
- @ccclass
- @executeInEditMode
- @executionOrder(-10)
- export default class RadarChart extends cc.Component {
- @property({ type: cc.Node, tooltip: CC_DEV && '绘制节点(不指定则默认为当前节点)' })
- private target: cc.Node = null;
- @property private _axisLength: number = 200;
- @property({ tooltip: CC_DEV && '轴线长度' })
- public get axisLength() { return this._axisLength; }
- public set axisLength(value: number) { this._axisLength = value; this.draw(this.curDatas); }
- @property private _axes: number = 6;
- @property({ tooltip: CC_DEV && '轴线数量(至少 3 条)' })
- public get axes() { return this._axes; }
- public set axes(value: number) { this._axes = Math.floor(value >= 3 ? value : 3); this.draw(this.curDatas); }
- @property private _axisScales: number = 3;
- @property({ tooltip: CC_DEV && '轴线上的刻度数(至少 1 个)' })
- public get axisScales() { return this._axisScales; }
- public set axisScales(value: number) { this._axisScales = Math.floor(value >= 1 ? value : 1); this.draw(this.curDatas); }
- @property private _drawAxes: boolean = true;
- @property({ tooltip: CC_DEV && '是否绘制轴线' })
- public get drawAxes() { return this._drawAxes; }
- public set drawAxes(value: boolean) { this._drawAxes = value; this.draw(this.curDatas); }
- @property private _gridLineWidth: number = 4;
- @property({ tooltip: CC_DEV && '轴线和外网格线的宽度' })
- public get gridLineWidth() { return this._gridLineWidth; }
- public set gridLineWidth(value: number) { this._gridLineWidth = value; this.draw(this.curDatas); }
- @property private _innerGridLineWidth: number = 4;
- @property({ tooltip: CC_DEV && '内网格线宽度' })
- public get innerGridLineWidth() { return this._innerGridLineWidth; }
- public set innerGridLineWidth(value: number) { this._innerGridLineWidth = value; this.draw(this.curDatas); }
- @property private _gridLineColor: cc.Color = cc.Color.GRAY;
- @property({ tooltip: CC_DEV && '轴线和网格线的颜色' })
- public get gridLineColor() { return this._gridLineColor; }
- public set gridLineColor(value: cc.Color) { this._gridLineColor = value; this.draw(this.curDatas); }
- @property private _gridFillColor: cc.Color = cc.color(100, 100, 100, 100);
- @property({ tooltip: CC_DEV && '网格内部填充的颜色' })
- public get gridFillColor() { return this._gridFillColor; }
- public set gridFillColor(value: cc.Color) { this._gridFillColor = value; this.draw(this.curDatas); }
- @property private _dataValuesStrings: string[] = ['0.8,0.5,0.6,0.5,0.8,0.6', '0.5,0.9,0.5,0.8,0.5,0.9'];
- @property({ type: [cc.String], tooltip: CC_DEV && '数据数值(字符串形式,使用英文逗号分隔)' })
- public get dataValuesStrings() { return this._dataValuesStrings; }
- public set dataValuesStrings(value: string[]) { this._dataValuesStrings = value; this.drawWithProperties(); }
- @property private _dataLineWidths: number[] = [5, 5];
- @property({ type: [cc.Integer], tooltip: CC_DEV && '数据线宽度' })
- public get dataLineWidths() { return this._dataLineWidths; }
- public set dataLineWidths(value: number[]) { this._dataLineWidths = value; this.drawWithProperties(); }
- @property private _dataLineColors: cc.Color[] = [cc.Color.BLUE, cc.Color.RED];
- @property({ type: [cc.Color], tooltip: CC_DEV && '数据线颜色' })
- public get dataLineColors() { return this._dataLineColors; }
- public set dataLineColors(value: cc.Color[]) { this._dataLineColors = value; this.drawWithProperties(); }
- @property private _dataFillColors: cc.Color[] = [cc.color(120, 120, 180, 100), cc.color(180, 120, 120, 100)];
- @property({ type: [cc.Color], tooltip: CC_DEV && '数据填充颜色' })
- public get dataFillColors() { return this._dataFillColors; }
- public set dataFillColors(value: cc.Color[]) { this._dataFillColors = value; this.drawWithProperties(); }
- @property private _dataJoinColors: cc.Color[] = [];
- @property({ type: [cc.Color], tooltip: CC_DEV && '数据节点颜色' })
- public get dataJoinColors() { return this._dataJoinColors; }
- public set dataJoinColors(value: cc.Color[]) { this._dataJoinColors = value; this.drawWithProperties(); }
- @property private _drawDataJoin: boolean = true;
- @property({ tooltip: CC_DEV && '是否绘制数据节点' })
- public get drawDataJoin() { return this._drawDataJoin; }
- public set drawDataJoin(value: boolean) { this._drawDataJoin = value; this.draw(this.curDatas); }
- private graphics: cc.Graphics = null;
- private keepUpdating: boolean = false;
- private angles: number[] = null;
- private _curDatas: RadarChartData[] = [];
- public get curDatas() { return this._curDatas; }
- private toRes: () => void = null;
- protected onLoad() {
- this.init();
- this.drawWithProperties();
- }
- protected update() {
- if (!this.keepUpdating || this._curDatas.length === 0) return;
- this.draw(this._curDatas);
- }
- /**
- * 初始化
- */
- private init() {
- // 获取组件
- if (!this.target) this.target = this.node;
- this.graphics = this.target.getComponent(cc.Graphics) || this.target.addComponent(cc.Graphics);
- // 设置端点和拐角样式
- this.graphics.lineJoin = cc.Graphics.LineJoin.ROUND;
- this.graphics.lineCap = cc.Graphics.LineCap.ROUND;
- }
- /**
- * 使用当前属性绘制
- */
- private drawWithProperties() {
- // 获取属性面板配置
- let datas: RadarChartData[] = [];
- for (let i = 0; i < this.dataValuesStrings.length; i++) {
- datas.push({
- values: this.processValuesString(this.dataValuesStrings[i]),
- lineWidth: this._dataLineWidths[i] || defaultOptions.lineWidth,
- lineColor: this._dataLineColors[i] || defaultOptions.lineColor,
- fillColor: this._dataFillColors[i] || defaultOptions.fillColor,
- joinColor: this._dataJoinColors[i] || defaultOptions.joinColor
- });
- }
- // 绘制
- this.draw(datas);
- }
- /**
- * 将数值字符串转为数值数组
- * @param valuesString 数值字符串
- */
- private processValuesString(valuesString: string): number[] {
- const strings = valuesString.split(',');
- let values: number[] = [];
- for (let j = 0; j < strings.length; j++) {
- const value = parseFloat(strings[j]);
- values.push(isNaN(value) ? 0 : value);
- }
- return values;
- }
- /**
- * 画基本线框
- */
- private drawBase() {
- // 填充染料
- this.graphics.lineWidth = this._gridLineWidth;
- this.graphics.strokeColor = this._gridLineColor;
- this.graphics.fillColor = this._gridFillColor;
- // 计算轴线角度
- this.angles = [];
- const iAngle = 360 / this.axes; // 轴间夹角
- for (let i = 0; i < this.axes; i++) this.angles.push(iAngle * i);
- // 计算刻度坐标
- let scalesSet: cc.Vec2[][] = [];
- const iLength = this._axisLength / this._axisScales;
- for (let i = 0; i < this._axisScales; i++) {
- let scales = [];
- // 计算刻度在轴上的位置
- const length = this._axisLength - (iLength * i);
- for (let j = 0; j < this.angles.length; j++) {
- // 将角度转为弧度
- const radian = (Math.PI / 180) * this.angles[j];
- // 根据三角公式计算刻度相对于中心点(0, 0)的坐标
- scales.push(cc.v2(length * Math.cos(radian), length * Math.sin(radian)));
- }
- scalesSet.push(scales);
- }
- // 创建轴线
- if (this._drawAxes) {
- for (let i = 0; i < scalesSet[0].length; i++) {
- this.graphics.moveTo(0, 0);
- this.graphics.lineTo(scalesSet[0][i].x, scalesSet[0][i].y);
- }
- }
- // 创建外网格线
- this.graphics.moveTo(scalesSet[0][0].x, scalesSet[0][0].y);
- for (let i = 1; i < scalesSet[0].length; i++) {
- this.graphics.lineTo(scalesSet[0][i].x, scalesSet[0][i].y);
- }
- this.graphics.close(); // 闭合当前线条(外网格线)
- // 填充线条包围的空白区域
- this.graphics.fill();
- // 绘制已创建的线条(轴线和外网格线)
- this.graphics.stroke();
- // 画内网格线
- if (scalesSet.length > 1) {
- this.graphics.lineWidth = this._innerGridLineWidth;
- // 创建内网格线
- for (let i = 1; i < scalesSet.length; i++) {
- this.graphics.moveTo(scalesSet[i][0].x, scalesSet[i][0].y);
- for (let j = 1; j < scalesSet[i].length; j++) {
- this.graphics.lineTo(scalesSet[i][j].x, scalesSet[i][j].y);
- }
- this.graphics.close(); // 闭合当前线条(内网格线)
- }
- // 绘制已创建的线条(内网格线)
- this.graphics.stroke();
- }
- }
- /**
- * 绘制数据
- * @param data 数据
- */
- public draw(data: RadarChartData | RadarChartData[]) {
- // 擦除旧图像
- this.graphics.clear();
- // 画轴线和网格线
- this.drawBase();
- // 包装单条数据
- const datas = Array.isArray(data) ? data : [data];
- this._curDatas = datas;
- // 数值不足需补 0
- this.resizeCurDatasValues(0);
- // 开始绘制数据
- for (let i = 0; i < datas.length; i++) {
- // 装填染料
- this.graphics.strokeColor = datas[i].lineColor || defaultOptions.lineColor;
- this.graphics.fillColor = datas[i].fillColor || defaultOptions.fillColor;
- this.graphics.lineWidth = datas[i].lineWidth || defaultOptions.lineWidth;
- // 计算节点坐标
- let coords = [];
- for (let j = 0; j < this.axes; j++) {
- const length = (datas[i].values[j] > 1 ? 1 : datas[i].values[j]) * this.axisLength;
- const radian = (Math.PI / 180) * this.angles[j];
- coords.push(cc.v2(length * Math.cos(radian), length * Math.sin(radian)));
- }
- // 创建线条
- this.graphics.moveTo(coords[0].x, coords[0].y);
- for (let j = 1; j < coords.length; j++) {
- this.graphics.lineTo(coords[j].x, coords[j].y);
- }
- this.graphics.close(); // 闭合线条
- // 填充包围区域
- this.graphics.fill();
- // 绘制线条
- this.graphics.stroke();
- // 绘制数据节点
- if (this._drawDataJoin) {
- for (let j = 0; j < coords.length; j++) {
- // 大圆
- this.graphics.strokeColor = datas[i].lineColor || defaultOptions.lineColor;
- this.graphics.circle(coords[j].x, coords[j].y, 2);
- this.graphics.stroke();
- // 小圆
- this.graphics.strokeColor = datas[i].joinColor || defaultOptions.joinColor;
- this.graphics.circle(coords[j].x, coords[j].y, .65);
- this.graphics.stroke();
- }
- }
- }
- }
- /**
- * 缓动绘制
- * @param data 目标数据
- * @param duration 动画时长
- */
- public to(data: RadarChartData | RadarChartData[], duration: number): Promise<void> {
- return new Promise(res => {
- // 处理上一个 Promise
- this.unscheduleAllCallbacks();
- this.toRes && this.toRes();
- this.toRes = res;
- // 包装单条数据
- const datas = Array.isArray(data) ? data : [data];
- // 打开每帧更新
- this.keepUpdating = true;
- // 动起来!
- for (let i = 0; i < datas.length; i++) {
- if (!this._curDatas[i]) continue;
- // 数值动起来!
- for (let j = 0; j < this._curDatas[i].values.length; j++) {
- cc.tween(this._curDatas[i].values)
- .to(duration, { [j]: datas[i].values[j] > 1 ? 1 : datas[i].values[j] })
- .start();
- }
- // 样式动起来!
- cc.tween(this._curDatas[i])
- .to(duration, {
- lineWidth: datas[i].lineWidth || this._curDatas[i].lineWidth,
- lineColor: datas[i].lineColor || this._curDatas[i].lineColor,
- fillColor: datas[i].fillColor || this._curDatas[i].fillColor,
- joinColor: datas[i].joinColor || this._curDatas[i].joinColor
- })
- .start();
- }
- this.scheduleOnce(() => {
- // 关闭每帧更新
- this.keepUpdating = false;
- // resolve Promise
- this.toRes();
- this.toRes = null;
- }, duration);
- });
- }
- /**
- * 检查并调整数据中的数值数量
- * @param fill 填充数值
- */
- private resizeCurDatasValues(fill: number = 0) {
- for (let i = 0; i < this._curDatas.length; i++) {
- // 数值数量少于轴数时才进行调整
- if (this._curDatas[i].values.length < this._axes) {
- const diff = this._axes - this._curDatas[i].values.length;
- for (let j = 0; j < diff; j++) this._curDatas[i].values.push(fill);
- }
- }
- }
- }
- /**
- * 雷达图数据
- */
- export interface RadarChartData {
- /** 数值 */
- values: number[];
- /** 线的宽度 */
- lineWidth?: number;
- /** 线的颜色 */
- lineColor?: cc.Color;
- /** 填充的颜色 */
- fillColor?: cc.Color;
- /** 节点的颜色 */
- joinColor?: cc.Color;
- }
- /**
- * 不指定时使用的样式配置
- */
- const defaultOptions = {
- lineWidth: 5,
- lineColor: cc.Color.BLUE,
- fillColor: cc.color(120, 120, 180, 100),
- joinColor: cc.Color.WHITE,
- }
|