//============================================================================= // Sphinx-Chart2.js //============================================================================= /*: * @plugindesc Graphique sous forme de sprite * @author Sphinx * @help * //========================================================================== * // Plugin : Sphinx-Chart2 * // Date : 29 décembre 2019 * // Auteur : Sphinx * //========================================================================== * Ce plugin ne contient aucune commande de plugin. Il met à disposition des * scripteurs une nouvelle classe de sprite représentant des graphiques. * Créer un graphique : * variable = new SphinxChart2( * type: TYPE_GRAPHIQUE, * title: TITRE_GRAPHIQUE, * categoriesLabels: LABELS_DE_CATEGORIES, * xAxisLabels: LABELS_DES_ABSCISSES, * datas: DONNEES, * colors: COULEURS, * options: OPTIONS * ); * où : * TYPE_GRAPHIQUE est l'un des types suivants : * - line * - bar * - radar * - pie * - doughnut * TITRE_GRAPHIQUE est une chaine de caractères qui sera affichée au * dessus du graphique * LABELS_DE_CATEGORIES est un tableau contenant la liste des étiquettes * des légendes à afficher * LABELS_DES_ABSCISSES est un tableau contenant la liste des étiquettes * de l'axe des abscisses à afficher * DONNEES est un tableau à 2 dimensions contenant les données à afficher * Pour les graphiques pie et doughnut, utiliser un tableau à 1 * dimension * COULEURS Liste des couleurs des données * OPTIONS est un objet de configuration du graphique contenant les clés * suivantes : * - easing : méthode d'animation de la classe Math.easing * - duration : durée de l'animation à l'ouverture du graphique * - forceMinToZero: true - le graphique commencera forcément à 0 * false - configuration par défaut * Inutilisé par les graphiques pie et doughnut * - background : couleur(s) d'arrière plan. Chaine de caractères pour * une couleur unie, tableau de chaines de caractères pour un * dégradé, un objet Bitmap pour une image de fond * - position : objet placant le graphique contenant les clés * suivantes : * - x : coordonnée X * - y : coordonnée Y * - width : largeur * - height : hauteur * - padding : objet définissant les marges de chaque côté contenant * les clés suivantes : * - top : marge en haut du graphique * - left : marge à gauche du graphique * - right : marge à droite du graphique * - bottom : marge en bas du graphique * * Ensuite, il suffit de l'ajouter à la scène ou à une window comme un sprite * ordinaire. * * Exemple de graphique : * chart = new SphinxChart2({ * type: "line", * title: "Test", * categoriesLabels: [ * "Category 1", * "Category 2", * "Category 3" * ], * xAxisLabels: [ * "1", "2", "3", "4", "5" * ], * datas: [ * [ 5, 2, 1, 4, 3 ], * [ 50, 21, 18, 34, 16 ], * [ 8, 5, 3, 7, 9 ], * ], * colors: [ * "#0099FF", * "#FF3300", * "#00CC00", * "#CC3399", * "#FF9933", * "#99CC00", * "#009999", * "#CC99FF" * ], * options: { * easing: "easeOutQuart", * duration: 1, * background: "transparent", * forceMinToZero: true, * position: { * x: 50, * y: 50, * width: 640, * height: 480, * }, * padding: { * top: 0, * left: 25, * right: 25, * bottom: 25, * } * } * }); * * - Dépendances : Sphinx-Polyfill * Sphinx-MathEasing */ DEFAULT_COLORS = [ "DeepPink", "DodgerBlue", "Gold", "OrangeRed", "WhiteSmoke", "DarkViolet" ]; function SphinxChart2() { this.initialize.apply(this, arguments); }; SphinxChart2.prototype = Object.create(Sprite_Button.prototype); SphinxChart2.prototype.constructor = SphinxChart2; SphinxChart2.prototype.initialize = function(config) { Sprite_Button.prototype.initialize.call(this); // Configuration par défaut this.config = config || {}; this.config.type = config.type || "line"; this.config.title = config.title || ""; this.config.categoriesLabels = config.categoriesLabels || []; this.config.xAxisLabels = config.xAxisLabels || []; this.config.datas = config.datas || []; this.config.colors = config.colors || DEFAULT_COLORS; this.config.options = config.options || {}; this.config.options.easing = config.options.easing || "linearTween"; this.config.options.duration = config.options.duration || 1; this.config.options.background = config.options.background || "transparent"; this.config.options.forceMinToZero = config.options.forceMinToZero || false; this.config.options.position = config.options.position || {}; this.config.options.position.x = config.options.position.x || 0; this.config.options.position.y = config.options.position.y || 0; this.config.options.position.width = config.options.position.width || 0; this.config.options.position.height = config.options.position.height || 0; this.config.options.padding = config.options.padding || {}; this.config.options.padding.top = config.options.padding.top || 0; this.config.options.padding.left = config.options.padding.left || 0; this.config.options.padding.right = config.options.padding.right || 0; this.config.options.padding.bottom = config.options.padding.bottom || 0; // Taille et position du sprite this.x = this.config.options.position.x; this.y = this.config.options.position.y; this.width = this.config.options.position.width; this.height = this.config.options.position.height; // Récupération du contexte de dessin et initialisation du compteur de frames this.frame = 0; this.bitmap = new Bitmap(this.width, this.height); this.context = this.bitmap._context; // Gestion du clic this.setClickHandler(SphinxChart2.prototype.onTouch.bind(this)); }; SphinxChart2.prototype.update = function() { Sprite_Button.prototype.update.call(this); // Mise à jour du graphisme this.drawFrame(); // Mise à jour du compteur de frames ++this.frame; }; SphinxChart2.prototype.onTouch = function() { // Mise à jour du compteur de frames this.frame = 0; }; SphinxChart2.prototype.drawFrame = function() { // Si l'animation est finie, on sort if(this.frame > this.config.options.duration * 60) return; // Nettoyage du cache this.context.clearRect(0, 0, this.width, this.height); // Dessin de l'arrière plan if(this.config.options.background instanceof Bitmap) { backgroundResize = {}; if(this.config.options.background.width / this.config.options.background.height > this.width / this.height) { backgroundResize.width = this.width; backgroundResize.height = this.width / (this.config.options.background.width / this.config.options.background.height); backgroundResize.top = (this.height - backgroundResize.height) / 2; backgroundResize.left = 0; } else { backgroundResize.width = this.height * this.config.options.background.width / this.config.options.background.height; backgroundResize.height = this.height; backgroundResize.top = 0; backgroundResize.left = (this.width - backgroundResize.width) / 2; } this.bitmap.blt(this.config.options.background, 0, 0, this.config.options.background.width, this.config.options.background.height, backgroundResize.left, backgroundResize.top, backgroundResize.width, backgroundResize.height); } else if(this.config.options.background instanceof Array && this.config.options.background.length > 1) { gradient = this.context.createLinearGradient(0, 0, 0, this.height); gradient.addColorStop(0, this.config.options.background[0]); for(i = 1; i < this.config.options.background.length; ++i) { gradient.addColorStop(i / (this.config.options.background.length - 1), this.config.options.background[i]); } this.context.fillStyle = gradient; this.context.fillRect(0, 0, this.width, this.height); } else if(this.config.options.background instanceof Array) { this.context.fillStyle = this.config.options.background[0]; this.context.fillRect(0, 0, this.width, this.height); } else { this.context.fillStyle = this.config.options.background; this.context.fillRect(0, 0, this.width, this.height); } // Calcul de la hauteur et écriture du titre if(this.config.title.length > 0) { titleHeight = this.bitmap.measureTextHeight(this.config.title); this.bitmap.fontSize = 28; this.context.font = this.bitmap._makeFontNameText(); this.context.textAlign = "center"; this.context.fillStyle = "black"; this.context.fillText(this.config.title, this.width / 2, titleHeight + this.config.options.padding.top, this.width); } // Math.easing[this.config.options.easing](this.frame, 50, 285, this.config.options.duration * 60); legendPosition = "bottom"; switch(this.config.type) { case "line": this.drawLineFrame(); break; case "bar": this.drawBarFrame(); break; case "radar": this.drawRadarFrame(); break; case "pie": legendPosition = "right"; this.drawPieFrame(); break; case "doughnut": legendPosition = "right"; this.drawDoughnutFrame(); break; } // Calcul de la largeur des légendes legendsWidth = 0; for(category of this.config.categoriesLabels) { legendsWidth += 60 + this.bitmap.measureTextWidth(category); } // Ecriture des légendes legendsLeft = (this.width - legendsWidth) / 2 for(i = 0; i < this.config.categoriesLabels.length; ++i) { // Nom de la catégorie category = this.config.categoriesLabels[i]; // Ecriture de la couleur (rectangle) this.context.save(); this.context.globalAlpha = 0.59765625; this.context.fillStyle = this.config.colors[i % this.config.colors.length]; this.context.fillRect(legendsLeft, this.height - categoriesHeight, 30, categoriesHeight - 5); this.context.restore(); this.context.lineWidth = 2; this.context.strokeStyle = this.config.colors[i % this.config.colors.length]; this.context.strokeRect(legendsLeft, this.height - categoriesHeight, 30, categoriesHeight - 5); legendsLeft += 40; // Ecriture du nom de la catégorie this.context.textAlign = "left"; this.context.fillStyle = "black"; this.context.fillText(category, legendsLeft, this.height - 8); legendsLeft += this.bitmap.measureTextWidth(category) + 20; } // Réécriture du dessin this.bitmap._setDirty(); }; //----------------------------------------------------------------------------- // Line chart SphinxChart2.prototype.drawLineFrame = function() { // Récupération de la hauteur du titre titleHeight = 0; if(this.config.title.length > 0) { titleHeight = this.bitmap.measureTextHeight(this.config.title); } // Taille de la police des axes this.bitmap.fontSize = 18; // Récupération de la police this.context.font = this.bitmap._makeFontNameText(); // Récupération des bornes des données datasMin = null; if(this.config.options.forceMinToZero) datasMin = 0; datasMax = null; datasCountMax = 0; for(datasCategory of this.config.datas) { if(datasMin == null) datasMin = Math.min(...datasCategory); else datasMin = Math.min(datasMin, ...datasCategory); if(datasMax == null) datasMax = Math.max(...datasCategory); else datasMax = Math.max(datasMax, ...datasCategory); datasCountMax = Math.max(datasCountMax, datasCategory.length); } yAxisLimitMin = Math.floor(datasMin / 5) * 5; yAxisLimitMax = Math.ceil(datasMax / 5) * 5; // Calcul de la hauteur des étiquettes de l'axe des abscisses xAxisHeight = 0; for(label of this.config.xAxisLabels) { height = this.bitmap.measureTextHeight(label); xAxisHeight = Math.max(xAxisHeight, height); } // Calcul de la hauteur des catégories categoriesHeight = 0; for(category of this.config.categoriesLabels) { height = this.bitmap.measureTextHeight(category); categoriesHeight = Math.max(categoriesHeight, height); } // Coordonnées de l'axe des abscisses xAxis = {}; xAxis.width = this.width - Math.max(this.bitmap.measureTextWidth(yAxisLimitMin), this.bitmap.measureTextWidth(yAxisLimitMax)) - this.config.options.padding.left - this.config.options.padding.right; // Coordonnées de l'axe des ordonnées yAxis = {}; yAxis.left = this.config.options.padding.left + Math.max(this.bitmap.measureTextWidth(yAxisLimitMin), this.bitmap.measureTextWidth(yAxisLimitMax)); yAxis.top = titleHeight + this.config.options.padding.top + 10; yAxis.height = this.height - titleHeight - xAxisHeight - categoriesHeight - this.config.options.padding.top - this.config.options.padding.bottom; // Dessin des axes des abscisses et des ordonnées this.context.beginPath(); this.context.moveTo(yAxis.left, yAxis.top); this.context.lineTo(yAxis.left, yAxis.top + yAxis.height); this.context.lineTo(yAxis.left + xAxis.width, yAxis.top + yAxis.height); this.context.lineWidth = 2; this.context.strokeStyle = "#999999"; this.context.stroke(); // Ajout de labels manquants sur l'axe des abscisses if(this.config.xAxisLabels.length < datasCountMax) { for(i = this.config.xAxisLabels.length + 1; i <= datasCountMax; ++i) { this.config.xAxisLabels.push(i); } } // Ajout de labels manquants sur l'axe des ordonnées if(this.config.categoriesLabels.length < this.config.datas.length) { for(i = this.config.categoriesLabels.length + 1; i <= this.config.datas.length; ++i) { this.config.categoriesLabels.push("Category " + i); } } else if(this.config.categoriesLabels.length > this.config.datas.length) { this.config.categoriesLabels = this.config.categoriesLabels.slice(0, this.config.datas.length); } // Ecriture des étiquettes des abscisses xAxisGapWidth = xAxis.width / (datasCountMax - 1); for(i = 0; i < datasCountMax; ++i) { // Repère abscisse this.context.beginPath(); this.context.moveTo(yAxis.left + xAxisGapWidth * i, yAxis.top + yAxis.height - 10); this.context.lineTo(yAxis.left + xAxisGapWidth * i, yAxis.top + yAxis.height + 10); this.context.lineWidth = 2; this.context.strokeStyle = "#999999"; this.context.stroke(); // Prolongement repère abscisse if(i > 0) { this.context.beginPath(); this.context.moveTo(yAxis.left + xAxisGapWidth * i, yAxis.top + yAxis.height - 10); this.context.lineTo(yAxis.left + xAxisGapWidth * i, yAxis.top); this.context.lineWidth = 2; this.context.strokeStyle = "#CCCCCC"; this.context.stroke(); } // Etiquette abscisse this.context.textAlign = "center"; this.context.fillStyle = "black"; labelHeight = this.bitmap.measureTextHeight(this.config.xAxisLabels[i]); this.context.fillText(this.config.xAxisLabels[i], yAxis.left + xAxisGapWidth * i, yAxis.top + yAxis.height + labelHeight + 5, xAxisGapWidth); } // Ecriture des étiquettes des ordonnées i = 5; yAxisTotalGap = Math.ceil((yAxisLimitMax - yAxisLimitMin) / 5) * 5; yAxisGap = Math.ceil(yAxisTotalGap / i / 5) * 5; yAxisLimitMax = yAxisLimitMin + (yAxisGap * 5); yAxisGapHeight = yAxis.height / (yAxisGap * 5 / yAxisGap); for(i = 5; i >= 0; --i) { // Label abscisse yAxisLabel = yAxisLimitMax - yAxisGap * i; // Repère ordonnée this.context.beginPath(); this.context.moveTo(yAxis.left - 10, yAxis.top + yAxisGapHeight * i); this.context.lineTo(yAxis.left + 10, yAxis.top + yAxisGapHeight * i); this.context.lineWidth = 2; this.context.strokeStyle = "#999999"; this.context.stroke(); // Prolongement repère ordonnée if(i < 5) { this.context.beginPath(); this.context.moveTo(yAxis.left + 10, yAxis.top + yAxisGapHeight * i); this.context.lineTo(yAxis.left + xAxis.width, yAxis.top + yAxisGapHeight * i); this.context.lineWidth = 2; this.context.strokeStyle = "#CCCCCC"; this.context.stroke(); } // Etiquette ordonnée this.context.textAlign = "right"; this.context.fillStyle = "black"; labelHeight = this.bitmap.measureTextHeight(yAxisLabel); this.context.fillText(yAxisLabel, yAxis.left - 15, yAxis.top + yAxisGapHeight * i + labelHeight / 4); } // Tracé des données du graphique easing = Math.easing[this.config.options.easing]; for(i = 0; i < this.config.datas.length; ++i) { // Données de la catégorie datasCategory = this.config.datas[i]; // Graphique this.context.beginPath(); for(j = 0; j < datasCategory.length; ++j) { data = datasCategory[j]; y = easing(this.frame, yAxis.top + yAxis.height, -yAxisGapHeight * (data - yAxisLimitMin) / yAxisGap, this.config.options.duration * 60); if(j == 0) { this.context.moveTo(yAxis.left, y); } else { this.context.lineTo(yAxis.left + xAxisGapWidth * j, y); } } this.context.lineWidth = 2; this.context.strokeStyle = this.config.colors[i % this.config.colors.length]; this.context.stroke(); } }; //----------------------------------------------------------------------------- // Bar chart SphinxChart2.prototype.drawBarFrame = function() { // Récupération de la hauteur du titre titleHeight = 0; if(this.config.title.length > 0) { titleHeight = this.bitmap.measureTextHeight(this.config.title); } // Taille de la police des axes this.bitmap.fontSize = 18; // Récupération de la police this.context.font = this.bitmap._makeFontNameText(); // Récupération des bornes des données datasMin = null; if(this.config.options.forceMinToZero) datasMin = 0; datasMax = null; datasCountMax = 0; for(datasCategory of this.config.datas) { if(datasMin == null) datasMin = Math.min(...datasCategory); else datasMin = Math.min(datasMin, ...datasCategory); if(datasMax == null) datasMax = Math.max(...datasCategory); else datasMax = Math.max(datasMax, ...datasCategory); datasCountMax = Math.max(datasCountMax, datasCategory.length); } yAxisLimitMin = Math.floor(datasMin / 5) * 5; yAxisLimitMax = Math.ceil(datasMax / 5) * 5; // Calcul de la hauteur des étiquettes de l'axe des abscisses xAxisHeight = 0; for(label of this.config.xAxisLabels) { height = this.bitmap.measureTextHeight(label); xAxisHeight = Math.max(xAxisHeight, height); } // Calcul de la hauteur des catégories categoriesHeight = 0; for(category of this.config.categoriesLabels) { height = this.bitmap.measureTextHeight(category); categoriesHeight = Math.max(categoriesHeight, height); } // Ajout de labels manquants sur l'axe des abscisses if(this.config.xAxisLabels.length < datasCountMax) { for(i = this.config.xAxisLabels.length + 1; i <= datasCountMax; ++i) { this.config.xAxisLabels.push(i); } } // Ajout de labels manquants sur l'axe des ordonnées if(this.config.categoriesLabels.length < this.config.datas.length) { for(i = this.config.categoriesLabels.length + 1; i <= this.config.datas.length; ++i) { this.config.categoriesLabels.push("Category " + i); } } else if(this.config.categoriesLabels.length > this.config.datas.length) { this.config.categoriesLabels = this.config.categoriesLabels.slice(0, this.config.datas.length); } // Coordonnées de l'axe des abscisses xAxis = {}; xAxis.width = this.width - Math.max(this.bitmap.measureTextWidth(yAxisLimitMin), this.bitmap.measureTextWidth(yAxisLimitMax)) - this.config.options.padding.left - this.config.options.padding.right; // Coordonnées de l'axe des ordonnées yAxis = {}; yAxis.left = this.config.options.padding.left + Math.max(this.bitmap.measureTextWidth(yAxisLimitMin), this.bitmap.measureTextWidth(yAxisLimitMax)); yAxis.top = titleHeight + this.config.options.padding.top + 10; yAxis.height = this.height - titleHeight - xAxisHeight - categoriesHeight - this.config.options.padding.top - this.config.options.padding.bottom; // Dessin des axes des abscisses et des ordonnées this.context.beginPath(); this.context.moveTo(yAxis.left, yAxis.top); this.context.lineTo(yAxis.left, yAxis.top + yAxis.height); this.context.lineTo(yAxis.left + xAxis.width, yAxis.top + yAxis.height); this.context.lineWidth = 2; this.context.strokeStyle = "#999999"; this.context.stroke(); // Ecriture des étiquettes des abscisses xAxisGapWidth = xAxis.width / datasCountMax; this.context.beginPath(); this.context.moveTo(yAxis.left, yAxis.top + yAxis.height - 10); this.context.lineTo(yAxis.left, yAxis.top + yAxis.height + 10); this.context.lineWidth = 2; this.context.strokeStyle = "#999999"; this.context.stroke(); for(i = 0; i < datasCountMax; ++i) { // Repère abscisse this.context.beginPath(); this.context.moveTo(yAxis.left + xAxisGapWidth * (i + 1), yAxis.top + yAxis.height - 10); this.context.lineTo(yAxis.left + xAxisGapWidth * (i + 1), yAxis.top + yAxis.height + 10); this.context.lineWidth = 2; this.context.strokeStyle = "#999999"; this.context.stroke(); // Prolongement repère abscisse this.context.beginPath(); this.context.moveTo(yAxis.left + xAxisGapWidth * (i + 1), yAxis.top + yAxis.height - 10); this.context.lineTo(yAxis.left + xAxisGapWidth * (i + 1), yAxis.top); this.context.lineWidth = 2; this.context.strokeStyle = "#CCCCCC"; this.context.stroke(); // Etiquette abscisse this.context.textAlign = "center"; this.context.fillStyle = "black"; labelHeight = this.bitmap.measureTextHeight(this.config.xAxisLabels[i]); this.context.fillText(this.config.xAxisLabels[i], yAxis.left + xAxisGapWidth * i + xAxisGapWidth / 2, yAxis.top + yAxis.height + labelHeight + 5, xAxisGapWidth); } // Ecriture des étiquettes des ordonnées i = 5; yAxisTotalGap = Math.ceil((yAxisLimitMax - yAxisLimitMin) / 5) * 5; yAxisGap = Math.ceil(yAxisTotalGap / i / 5) * 5; yAxisLimitMax = yAxisLimitMin + (yAxisGap * 5); yAxisGapHeight = yAxis.height / (yAxisGap * 5 / yAxisGap); for(i = 5; i >= 0; --i) { // Label abscisse yAxisLabel = yAxisLimitMax - yAxisGap * i; // Repère ordonnée this.context.beginPath(); this.context.moveTo(yAxis.left - 10, yAxis.top + yAxisGapHeight * i); this.context.lineTo(yAxis.left + 10, yAxis.top + yAxisGapHeight * i); this.context.lineWidth = 2; this.context.strokeStyle = "#999999"; this.context.stroke(); // Prolongement repère ordonnée if(i < 5) { this.context.beginPath(); this.context.moveTo(yAxis.left + 10, yAxis.top + yAxisGapHeight * i); this.context.lineTo(yAxis.left + xAxis.width, yAxis.top + yAxisGapHeight * i); this.context.lineWidth = 2; this.context.strokeStyle = "#CCCCCC"; this.context.stroke(); } // Etiquette ordonnée this.context.textAlign = "right"; this.context.fillStyle = "black"; labelHeight = this.bitmap.measureTextHeight(yAxisLabel); this.context.fillText(yAxisLabel, yAxis.left - 15, yAxis.top + yAxisGapHeight * i + labelHeight / 4); } // Tracé des données du graphique easing = Math.easing[this.config.options.easing]; for(i = 0; i < this.config.datas.length; ++i) { // Données de la catégorie datasCategory = this.config.datas[i]; // Graphique widthWithSpace = xAxisGapWidth / this.config.datas.length; widthWithoutSpace = xAxisGapWidth / (this.config.datas.length + 1); widthSpace = widthWithSpace - widthWithoutSpace; for(j = 0; j < datasCategory.length; ++j) { data = datasCategory[j]; width = widthWithoutSpace; height = easing(this.frame, 0, (data - yAxisLimitMin) * yAxisGapHeight / yAxisGap, this.config.options.duration * 60); x = yAxis.left + xAxisGapWidth * j + widthWithSpace * i + widthSpace / 2; y = yAxis.top + yAxis.height - height; this.context.lineWidth = 2; this.context.strokeStyle = this.config.colors[i % this.config.colors.length]; this.context.strokeRect(x, y, width, height); this.context.save(); this.context.globalAlpha = 0.59765625; this.context.fillStyle = this.config.colors[i % this.config.colors.length]; this.context.fillRect(x, y, width, height); this.context.restore(); } } }; //----------------------------------------------------------------------------- // Radar chart SphinxChart2.prototype.drawRadarFrame = function() { // Récupération de la hauteur du titre titleHeight = 0; if(this.config.title.length > 0) { titleHeight = this.bitmap.measureTextHeight(this.config.title); } // Taille de la police des axes this.bitmap.fontSize = 18; // Récupération de la police this.context.font = this.bitmap._makeFontNameText(); // Récupération des bornes des données datasMin = null; if(this.config.options.forceMinToZero) datasMin = 0; datasMax = null; datasCountMax = 0; for(datasCategory of this.config.datas) { if(datasMin == null) datasMin = Math.min(...datasCategory); else datasMin = Math.min(datasMin, ...datasCategory); if(datasMax == null) datasMax = Math.max(...datasCategory); else datasMax = Math.max(datasMax, ...datasCategory); datasCountMax = Math.max(datasCountMax, datasCategory.length); } yAxisLimitMin = Math.floor(datasMin / 5) * 5; yAxisLimitMax = Math.ceil(datasMax / 5) * 5; // Calcul de la hauteur des étiquettes de l'axe des abscisses xAxisHeight = 0; for(label of this.config.xAxisLabels) { height = this.bitmap.measureTextHeight(label); xAxisHeight = Math.max(xAxisHeight, height); } // Calcul de la hauteur des catégories categoriesHeight = 0; for(category of this.config.categoriesLabels) { height = this.bitmap.measureTextHeight(category); categoriesHeight = Math.max(categoriesHeight, height); } // Ajout de labels manquants sur l'axe des abscisses if(this.config.xAxisLabels.length < datasCountMax) { for(i = this.config.xAxisLabels.length + 1; i <= datasCountMax; ++i) { this.config.xAxisLabels.push(i); } } // Ajout de labels manquants sur l'axe des ordonnées if(this.config.categoriesLabels.length < this.config.datas.length) { for(i = this.config.categoriesLabels.length + 1; i <= this.config.datas.length; ++i) { this.config.categoriesLabels.push("Category " + i); } } else if(this.config.categoriesLabels.length > this.config.datas.length) { this.config.categoriesLabels = this.config.categoriesLabels.slice(0, this.config.datas.length); } // Angle, rayon et centre du radar degreesAngle = 360 / datasCountMax; ray = Math.min(this.width - 40, this.height - titleHeight - categoriesHeight - 40) / 2; centerX = this.width / 2; centerY = titleHeight + ray + 20; // Dessin des rayons for(i = 0; i < datasCountMax; ++i) { this.context.beginPath(); this.context.moveTo(centerX, centerY); this.context.lineTo(centerX + ray * Math.cos(((270 + degreesAngle * i) % 360) * Math.PI / 180), centerY + ray * Math.sin(((270 + degreesAngle * i) % 360) * Math.PI / 180)); this.context.lineWidth = 2; this.context.strokeStyle = "#999999"; this.context.stroke(); } // Ecriture des étiquettes des abscisses for(i = 0; i < this.config.xAxisLabels.length; ++i) { label = this.config.xAxisLabels[i]; this.context.textAlign = "center"; this.context.fillStyle = "black"; this.context.fillText(label, centerX + (ray + 10) * Math.cos(((270 + degreesAngle * i) % 360) * Math.PI / 180), centerY + (ray + 10) * Math.sin(((270 + degreesAngle * i) % 360) * Math.PI / 180)); } // Ecriture des étiquettes des ordonnées i = 5; yAxisTotalGap = Math.ceil((yAxisLimitMax - yAxisLimitMin) / 5) * 5; yAxisGap = Math.ceil(yAxisTotalGap / i / 5) * 5; yAxisLimitMax = yAxisLimitMin + (yAxisGap * 5); yAxisGapHeight = ray / (yAxisGap * 5 / yAxisGap); for(i = 0; i <= 5; ++i) { // Label abscisse yAxisLabel = yAxisLimitMin + yAxisGap * i; y = centerY - (ray / 5 * i); // Repère ordonnée this.context.beginPath(); this.context.moveTo(centerX + ray / 5 * i * Math.cos(270 * Math.PI / 180), centerY + ray / 5 * i * Math.sin(270 * Math.PI / 180)); for(j = 0; j <= datasCountMax; ++j) { this.context.lineTo(centerX + ray / 5 * i * Math.cos(((270 + degreesAngle * j) % 360) * Math.PI / 180), centerY + ray / 5 * i * Math.sin(((270 + degreesAngle * j) % 360) * Math.PI / 180)); } this.context.lineWidth = 2; this.context.strokeStyle = "#CCCCCC"; this.context.stroke(); // Etiquette ordonnée this.context.textAlign = "right"; this.context.fillStyle = "black"; labelHeight = this.bitmap.measureTextHeight(yAxisLabel); this.context.fillText(yAxisLabel, centerX - 10, centerY - ray / 5 * i + 10); } // Tracé des données du graphique easing = Math.easing[this.config.options.easing]; for(i = 0; i < this.config.datas.length; ++i) { // Données de la catégorie datasCategory = this.config.datas[i]; // Graphique dataRay = easing(this.frame, 0, ray * (datasCategory[0] - yAxisLimitMin) / (yAxisLimitMax - yAxisLimitMin), this.config.options.duration * 60); this.context.beginPath(); this.context.moveTo(centerX + dataRay * Math.cos(270 * Math.PI / 180), centerY + dataRay * Math.sin(270 * Math.PI / 180)); for(j = 0; j < datasCategory.length; ++j) { data = datasCategory[j]; dataRay = easing(this.frame, 0, ray * (data - yAxisLimitMin) / (yAxisLimitMax - yAxisLimitMin), this.config.options.duration * 60); this.context.lineTo(centerX + dataRay * Math.cos(((270 + degreesAngle * j) % 360) * Math.PI / 180), centerY + dataRay * Math.sin(((270 + degreesAngle * j) % 360) * Math.PI / 180)); } this.context.lineWidth = 2; this.context.strokeStyle = this.config.colors[i % this.config.colors.length]; this.context.stroke(); this.context.save(); this.context.globalAlpha = 0.59765625; this.context.fillStyle = this.config.colors[i % this.config.colors.length]; this.context.fill(); this.context.restore(); } }; //----------------------------------------------------------------------------- // Pie chart SphinxChart2.prototype.drawPieFrame = function() { // Récupération de la hauteur du titre titleHeight = 0; if(this.config.title.length > 0) { titleHeight = this.bitmap.measureTextHeight(this.config.title); } // Taille de la police des axes this.bitmap.fontSize = 18; // Récupération de la police this.context.font = this.bitmap._makeFontNameText(); // Calcul de la hauteur des catégories categoriesHeight = 0; for(category of this.config.categoriesLabels) { height = this.bitmap.measureTextHeight(category); categoriesHeight = Math.max(categoriesHeight, height); } // Ajout de labels manquants sur l'axe des ordonnées if(this.config.categoriesLabels.length < this.config.datas.length) { for(i = this.config.categoriesLabels.length + 1; i <= this.config.datas.length; ++i) { this.config.categoriesLabels.push("Category " + i); } } else if(this.config.categoriesLabels.length > this.config.datas.length) { this.config.categoriesLabels = this.config.categoriesLabels.slice(0, this.config.datas.length); } // Rayon et centre du camembert ray = Math.min(this.width - 40, this.height - titleHeight - categoriesHeight - 40) / 2; centerX = this.width / 2; centerY = titleHeight + ray + 20; // Tracé des données du graphique easing = Math.easing[this.config.options.easing]; // Calcul du total des données sumDatas = 0; for(data of this.config.datas) { sumDatas += data; } // Dessin du camembert lastDegreesAngle = 270; for(i = 0; i < this.config.datas.length; ++i) { // Données de la catégorie data = this.config.datas[i]; nextDegreesAngle = (lastDegreesAngle + easing(this.frame, 0, 360 * data / sumDatas, this.config.options.duration * 60)) % 360; // Graphique this.context.beginPath(); this.context.moveTo(centerX, centerY); this.context.lineTo(centerX + ray * Math.cos(lastDegreesAngle * Math.PI / 180), centerY + ray * Math.sin(lastDegreesAngle * Math.PI / 180)); this.context.arc(centerX, centerY, ray, lastDegreesAngle * Math.PI / 180, nextDegreesAngle * Math.PI / 180); this.context.lineWidth = 2; this.context.strokeStyle = this.config.colors[i % this.config.colors.length]; this.context.stroke(); this.context.save(); this.context.globalAlpha = 0.59765625; this.context.fillStyle = this.config.colors[i % this.config.colors.length]; this.context.fill(); this.context.restore(); // Décallage lastDegreesAngle = nextDegreesAngle; } }; //----------------------------------------------------------------------------- // Doughnut chart SphinxChart2.prototype.drawDoughnutFrame = function() { // Récupération de la hauteur du titre titleHeight = 0; if(this.config.title.length > 0) { titleHeight = this.bitmap.measureTextHeight(this.config.title); } // Taille de la police des axes this.bitmap.fontSize = 18; // Récupération de la police this.context.font = this.bitmap._makeFontNameText(); // Calcul de la hauteur des catégories categoriesHeight = 0; for(category of this.config.categoriesLabels) { height = this.bitmap.measureTextHeight(category); categoriesHeight = Math.max(categoriesHeight, height); } // Ajout de labels manquants sur l'axe des ordonnées if(this.config.categoriesLabels.length < this.config.datas.length) { for(i = this.config.categoriesLabels.length + 1; i <= this.config.datas.length; ++i) { this.config.categoriesLabels.push("Category " + i); } } else if(this.config.categoriesLabels.length > this.config.datas.length) { this.config.categoriesLabels = this.config.categoriesLabels.slice(0, this.config.datas.length); } // Rayon et centre du camembert ray = Math.min(this.width - 40, this.height - titleHeight - categoriesHeight - 40) / 2; centerX = this.width / 2; centerY = titleHeight + ray + 20; // Tracé des données du graphique easing = Math.easing[this.config.options.easing]; // Calcul du total des données sumDatas = 0; for(data of this.config.datas) { sumDatas += data; } // Dessin du camembert lastDegreesAngle = 270; for(i = 0; i < this.config.datas.length; ++i) { // Données de la catégorie data = this.config.datas[i]; nextDegreesAngle = (lastDegreesAngle + easing(this.frame, 0, 360 * data / sumDatas, this.config.options.duration * 60)) % 360; // Graphique this.context.beginPath(); this.context.moveTo(centerX + ray / 2 * Math.cos(lastDegreesAngle * Math.PI / 180), centerY + ray / 2 * Math.sin(lastDegreesAngle * Math.PI / 180)); this.context.lineTo(centerX + ray * Math.cos(lastDegreesAngle * Math.PI / 180), centerY + ray * Math.sin(lastDegreesAngle * Math.PI / 180)); this.context.arc(centerX, centerY, ray, lastDegreesAngle * Math.PI / 180, nextDegreesAngle * Math.PI / 180); this.context.lineTo(centerX + ray / 2 * Math.cos(nextDegreesAngle * Math.PI / 180), centerY + ray / 2 * Math.sin(nextDegreesAngle * Math.PI / 180)); this.context.arc(centerX, centerY, ray / 2, nextDegreesAngle * Math.PI / 180, lastDegreesAngle * Math.PI / 180, true); this.context.lineWidth = 2; this.context.strokeStyle = this.config.colors[i % this.config.colors.length]; this.context.stroke(); this.context.save(); this.context.globalAlpha = 0.59765625; this.context.fillStyle = this.config.colors[i % this.config.colors.length]; this.context.fill(); this.context.restore(); // Décallage lastDegreesAngle = nextDegreesAngle; } };