Java tutorial
/** * Copyright (C) 2008-2010, Squale Project - http://www.squale.org * * This file is part of Squale. * * Squale is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or any later version. * * Squale is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Squale. If not, see <http://www.gnu.org/licenses/>. */ package org.squale.squaleweb.util.graph; import java.text.NumberFormat; import java.util.Locale; import org.jfree.chart.ChartFactory; import org.jfree.chart.JFreeChart; import org.jfree.chart.annotations.XYLineAnnotation; import org.jfree.chart.axis.LogarithmicAxis; import org.jfree.chart.axis.ValueAxis; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.title.TextTitle; import org.jfree.data.xy.DefaultXYZDataset; import org.jfree.ui.RectangleEdge; import org.squale.squaleweb.resources.WebMessages; /** * @author 6370258 Gnration du graphe de type Bubble, stock dans un form et appel depuis une page Jsp */ public class BubbleMaker extends AbstractGraphMaker { /** * Hauteur du diagramme par dfaut */ public static final int DEFAULT_HEIGHT = 500; /** * Hauteur du diagramme par dfaut */ public static final int DEFAULT_WIDTH = 550; /** * Position par dfaut de l'axe horizontal */ private static final int DEFAULT_HORIZONTAL_AXIS_POS = 7; /** * Position par dfaut de l'axe vertical */ private static final int DEFAULT_VERTICAL_AXIS_POS = 10; /** * Marge par dfaut avec les axes. */ private static final double DEFAULT_AXIS_MARGIN = 2; /** * Permet de paramtrer la valeur de l'axe horizontal */ private int mHorizontalAxisPos; /** * Permet de paramtrer la valeur de l'axe horizontal */ private int mVerticalAxisPos; /** * Contient <code>true</code> si des donnes peuvent tre ajoutes */ private boolean mCouldAddDatas = true; /** * Borne infrieure en de de laquelle la taille du point est la taille minimale */ private static final int DEFAULT_LOWER_LIMIT = 1; /** * Borne suprieure au del de laquelle la taille du point est la taille maximale */ private static final int DEFAULT_UPPER_LIMIT = 10; /** * Taille minimale du point */ private static final double DEFAULT_LOWER_SIZE = 0.05; /** * Taille maximale du point */ private static final double DEFAULT_UPPER_SIZE = 0.3; /** * Poucentage des mthodes difficilement maintenables mais structures */ private double totalTopLeft; /** * Pourcentage des mthodes difficilement maintenables et mal structures */ private double totalTopRight; /** * Pourcentage des mthodes maintenables et bien structures */ private double totalBottomLeft; /** * Pourcentage des mthodes maintenables mais mal structures */ private double totalBottomRight; /** * La locale */ private Locale locale; /** * Dataset contenant les valeurs mettre dans le diagramme */ private DefaultXYZDataset mDataSet; /** * Constructeur par dfaut */ public BubbleMaker() { mDataSet = new DefaultXYZDataset(); // Initialisation des pourcentages totalTopLeft = 0; totalTopRight = 0; totalBottomLeft = 0; totalBottomRight = 0; // Etiquete figurant sur l'axe horizontal (peut etre null) Par dfaut : "v(g)" mXLabel = WebMessages.getString("bubble.default.axis.domain"); // Etiquete figurant sur l'axe des valeurs (peut etre null) Par dfaut : "ev(g)" mYLabel = WebMessages.getString("bubble.default.axis.value"); // Initialisation de la valeur des axes aux valeurs par dfaut mHorizontalAxisPos = DEFAULT_HORIZONTAL_AXIS_POS; mVerticalAxisPos = DEFAULT_VERTICAL_AXIS_POS; } /** * Constructeur avec la locale fourni * * @param pLocale la locale */ public BubbleMaker(Locale pLocale) { this(); locale = pLocale; } /** * Constructeur avec la locale fourni et la position des axes * * @param pLocale la locale * @param pHorizontalAxisPos la position de l'axe horizontal * @param pVerticalAxisPos la position de l'axe vertical */ public BubbleMaker(Locale pLocale, Long pHorizontalAxisPos, Long pVerticalAxisPos) { this(pHorizontalAxisPos, pVerticalAxisPos); locale = pLocale; } /** * Constructeur avec juste la position des axes Permet de factoriser le traitement du cas null * * @param pHorizontalAxisPos la position de l'axe horizontal * @param pVerticalAxisPos la position de l'axe vertical */ public BubbleMaker(Long pHorizontalAxisPos, Long pVerticalAxisPos) { this(); // Si c'est null on garde la valeur par dfaut if (pHorizontalAxisPos != null & pHorizontalAxisPos.intValue() != -1) { mHorizontalAxisPos = pHorizontalAxisPos.intValue(); } // Si c'est null on garde la valeur par dfaut if (pVerticalAxisPos != null && pVerticalAxisPos.intValue() != -1) { mVerticalAxisPos = pVerticalAxisPos.intValue(); } } /** * Constructeur avec le titre du diagramme * * @param pTitle titre du diagramme (peut etre <code>null</code>) */ public BubbleMaker(String pTitle) { this(); mTitle = pTitle; } /** * Constructeur avec le titre du diagramme et les titres des axes * * @param pTitle titre du diagramme (peut etre <code>null</code>) * @param pDomainAxisLabel titre de l'axe horizontal (peut etre <code>null</code>) * @param pValueAxisLabel titre de l'axe des valeurs (peut etre <code>null</code>) */ public BubbleMaker(String pTitle, String pDomainAxisLabel, String pValueAxisLabel) { this(pTitle); mXLabel = pDomainAxisLabel; mYLabel = pValueAxisLabel; } /** * Constructeur avec le titre du diagramme et les titres des axes * * @param pTitle titre du diagramme (peut etre <code>null</code>) * @param pDomainAxisLabel titre de l'axe horizontal (peut etre <code>null</code>) * @param pValueAxisLabel titre de l'axe des valeurs (peut etre <code>null</code>) * @param pHorizontalAxisPos la position de l'axe des abssices * @param pVerticalAxisPos la position de l'axe des ordones */ public BubbleMaker(String pTitle, String pDomainAxisLabel, String pValueAxisLabel, Long pHorizontalAxisPos, Long pVerticalAxisPos) { this(pHorizontalAxisPos, pVerticalAxisPos); mTitle = pTitle; mXLabel = pDomainAxisLabel; mYLabel = pValueAxisLabel; } /** * Constructeur complet * * @param pTitle titre du diagramme (peut etre <code>null</code>) * @param pLocale la locale * @param pDomainAxisLabel titre de l'axe horizontal (peut etre <code>null</code>) * @param pValueAxisLabel titre de l'axe des valeurs (peut etre <code>null</code>) * @param pHorizontalAxisPos la position de l'axe des abssices * @param pVerticalAxisPos la position de l'axe des ordones */ public BubbleMaker(Locale pLocale, String pTitle, String pDomainAxisLabel, String pValueAxisLabel, Long pHorizontalAxisPos, Long pVerticalAxisPos) { this(pLocale, pHorizontalAxisPos, pVerticalAxisPos); mTitle = pTitle; mXLabel = pDomainAxisLabel; mYLabel = pValueAxisLabel; } /** * @see org.squale.squalecommon.util.graph.AbstractGraphMaker#getDefaultHeight() * @return la hauteur par dfaut */ protected int getDefaultHeight() { return DEFAULT_HEIGHT; } /** * @see org.squale.squalecommon.util.graph.AbstractGraphMaker#getDefaultWidth() * @return la largeur par dfaut */ protected int getDefaultWidth() { return DEFAULT_WIDTH; } /** * Ajoute une series de valeurs (x, y) au graphe de type Bubble <br /> * <b>Attention : </b>les doublons doivent tre vits pour des raisons de performances On doit toujours avoir * l'galit suivante : pHorizontalValues.length == pVerticalValues.length * * @param pName le nom de la srie (peut etre <code>null</code> si c'est la premire srie, dans ce cas, seule * cette srie sera ajoutee) * @param pHorizontalValues les valeurs de l'axe horizontal * @param pVerticalValues les valeurs de l'axe vertical * @param pTotal nombre de mthodes ayant une mme valeur (vg, evg) * @return <code>true</code> si tout s'est bien pass, <code>false</code> sinon */ public boolean addSerie(String pName, double[] pHorizontalValues, double[] pVerticalValues, double[] pTotal) { boolean ret = false; // si les nombres de valeurs horizontales et verticales sont gaux // et que l'on peut encore ajouter des valeurs if ((pHorizontalValues.length == pVerticalValues.length) && (mCouldAddDatas)) { ret = true; // partir d'ici on considre que tout va bien se passer // si tout s'est bien pass, on ajoute les valeurs // La taille du point est proportionnelle aux mthodes ayant les mmes valeurs (vg,evg) double[] pSize = new double[pTotal.length]; // La taille du point est proportionnelle aux nombres de mthodes ayant les mmes valeurs (vg, evg) for (int i = 0; i < pTotal.length; i++) { double radius = Math.sqrt(pTotal[i]); // En de de la borne infrieure, la taille du point a la taille minimale if (radius <= DEFAULT_LOWER_LIMIT) { pSize[i] = DEFAULT_LOWER_SIZE; } else { // Au del de la borne suprieure, la taille du point a la taille maximale if (radius >= DEFAULT_UPPER_LIMIT) { pSize[i] = DEFAULT_UPPER_SIZE; } else { // Entre les bornes infrieure et suprieure, La taille du point grossit // proportionnellement au nombre de mthodes ayant les mmes vg et evg, // la taille variant entre la taille minimale et la taille maximale pSize[i] = ((double) (radius - DEFAULT_LOWER_LIMIT) * (double) (DEFAULT_UPPER_SIZE - DEFAULT_LOWER_SIZE) / (double) (DEFAULT_UPPER_LIMIT - DEFAULT_LOWER_LIMIT)) + DEFAULT_LOWER_SIZE; } } } addXYSeries(pName, pHorizontalValues, pVerticalValues, pSize); addDistribution(pHorizontalValues, pVerticalValues, pTotal); } return ret; } /** * Cre une XYSeries et l'ajoute. * * @param pName le nom de la srie (peut etre <code>null</code> si c'est la premire srie, dans ce cas, seule * cette srie sera ajoutee). * @param pHorizontalValues les valeurs de l'axe horizontal. * @param pVerticalValues les valeurs de l'axe vertical. * @param pSize nombre de mthodes ayant une mme valeur (vg, evg) */ private void addXYSeries(String pName, double[] pHorizontalValues, double[] pVerticalValues, double[] pSize) { String name = pName; if (null == name) { // si aucun nom n'a t spcifi on affecte le nom par dfaut pour la courbe name = WebMessages.getString("bubble.undefined.name"); } double[][] series = new double[][] { pHorizontalValues, pVerticalValues, pSize }; mDataSet.addSeries(name, series); } /** * Selon la valeur du couple (vg, evg) calcule la distribution des mthodes dans les quatre coins du graphes en * fonction des critres - Maintenable et structur - Maintenable mais mal structur - Difficilement maintenable * mais structur - Difficilement maintenable et mal structur * * @param pHorizontalValues les valeurs vg * @param pVerticalValues les valeurs evg * @param pTotal le nombre de mthodes ayant les mmes valeurs (vg, evg) */ private void addDistribution(double[] pHorizontalValues, double[] pVerticalValues, double[] pTotal) { for (int i = 0; i < pHorizontalValues.length; i++) { // Mthode structure ? if (pHorizontalValues[i] <= mVerticalAxisPos) { // Mthode structure et maintenable ? if (pVerticalValues[i] <= mHorizontalAxisPos) { totalBottomLeft += pTotal[i]; // Mthode structure mais difficilement maintenable ? } else { totalTopLeft += pTotal[i]; } // Mthode non structure ? } else { // Mthode non structure mais maintenable ? if (pVerticalValues[i] <= mHorizontalAxisPos) { totalBottomRight += pTotal[i]; // Mthode non structure et difficilement maintenable ? } else { totalTopRight += pTotal[i]; } } } } /** * Construit (ou reconstruit) le diagramme puis le retourne * * @return le diagramme JFreeChart. */ protected JFreeChart getChart() { JFreeChart retChart = super.getChart(); if (null == retChart) { retChart = getCommonChart(); super.setChart(retChart); } return retChart; } /** * Construit (ou reconstruit) le diagramme puis le retourne * * @param pProjectId l'Id du projet * @param pCurrentAuditId l'Id de l'audit courant * @param pPreviousAuditId l'id de l'audit prcdent * @param pVgs tableau des valeurs vg * @param pEvgs tableau des valeurs evg * @param pTotal nombre de mthodes ayant la mme valeur (vg, evg) * @param pTres metrics tres * @return le diagramme JFreeChart. */ public JFreeChart getChart(String pProjectId, String pCurrentAuditId, String pPreviousAuditId, double[] pVgs, double[] pEvgs, double[] pTotal, String[] pTres) { JFreeChart retChart = super.getChart(); if (null == retChart) { // Gnration du graphe de type Bubble, directement depuis la page Jsp retChart = getCommonChart(); // initialise les valeurs mProjectId = pProjectId; mPreviousAuditId = pPreviousAuditId; mCurrentAuditId = pCurrentAuditId; BubbleUrlGenerator generator = null; if (pTres.length < 2) { generator = new BubbleUrlGenerator(pProjectId, pCurrentAuditId, pPreviousAuditId, pVgs, pEvgs, "", ""); } else { generator = new BubbleUrlGenerator(pProjectId, pCurrentAuditId, pPreviousAuditId, pVgs, pEvgs, pTres[0], pTres[1]); } retChart.getXYPlot().getRenderer().setURLGenerator(generator); BubbleToolTipGenerator toolTipGenerator = new BubbleToolTipGenerator(pVgs, pEvgs, pTotal); retChart.getXYPlot().getRenderer().setToolTipGenerator(toolTipGenerator); super.setChart(retChart); } return retChart; } /** * Factorisation du code commun la gnration du graphe * * @return graphe de type Bubble */ private JFreeChart getCommonChart() { // Cration du graphe de type Bubble JFreeChart chart = ChartFactory.createBubbleChart(mTitle, mXLabel, mYLabel, mDataSet, PlotOrientation.VERTICAL, mShowLegend, true, false); ValueAxis domainAxis = chart.getXYPlot().getDomainAxis(); ValueAxis rangeAxis = chart.getXYPlot().getRangeAxis(); // Dtermination des bornes en abscisse et en ordonne double maxDomain = Math.max(domainAxis.getUpperBound(), DEFAULT_VERTICAL_AXIS_POS + DEFAULT_AXIS_MARGIN) + 1; double minDomain = 0; double maxRange = Math.max(rangeAxis.getUpperBound(), DEFAULT_HORIZONTAL_AXIS_POS + DEFAULT_AXIS_MARGIN) + 1; double minRange = 0; // Mise l'chelle logarithmique des axes LogarithmicAxis newDomainAxis = new LogarithmicAxis(domainAxis.getLabel()); LogarithmicAxis newRangeAxis = new LogarithmicAxis(rangeAxis.getLabel()); // Affectation des bornes en abscisse et en ordonne newDomainAxis.setLowerBound(minDomain); newDomainAxis.setUpperBound(maxDomain); newRangeAxis.setLowerBound(minRange); newRangeAxis.setUpperBound(maxRange); chart.getXYPlot().setDomainAxis(newDomainAxis); chart.getXYPlot().setRangeAxis(newRangeAxis); // Affichage de la rpartition des mthodes selon les critres displayRepartitionSubtitles(chart); // Annotations XYLineAnnotation horizontalAxis = new XYLineAnnotation(minDomain, DEFAULT_HORIZONTAL_AXIS_POS, maxDomain, DEFAULT_HORIZONTAL_AXIS_POS); XYLineAnnotation verticalAxis = new XYLineAnnotation(DEFAULT_VERTICAL_AXIS_POS, minRange, DEFAULT_VERTICAL_AXIS_POS, maxRange); chart.getXYPlot().addAnnotation(horizontalAxis); chart.getXYPlot().addAnnotation(verticalAxis); return chart; } /** * Affichage sous la forme de sous-titres de la rpartition des mthodes - Maintenable et structur, - Maintenable * mais mal structur - Difficilement maintenable mais structur - Difficilement maintenable et mal structur * * @param pChart graphe de type Bubble */ private void displayRepartitionSubtitles(JFreeChart pChart) { double percentTopLeft = 0; double percentTopRight = 0; double percentBottomLeft = 0; double percentBottomRight = 0; int totalMethods = (int) (totalTopLeft + totalTopRight + totalBottomLeft + totalBottomRight); if (totalMethods > 0) { final int oneHundred = 100; // haut gauche : difficilement maintenable mais structur percentTopLeft = totalTopLeft * oneHundred / totalMethods; // haut droit : difficilement maintenable et mal structur percentTopRight = totalTopRight * oneHundred / totalMethods; // Bas gauche : maintenable et structur percentBottomLeft = totalBottomLeft * oneHundred / totalMethods; // Bas droit : maintenable mais mal structur percentBottomRight = totalBottomRight * oneHundred / totalMethods; } // Formatage des zones d'affichage NumberFormat numberFormat = NumberFormat.getInstance(); numberFormat.setMaximumFractionDigits(1); numberFormat.setMinimumFractionDigits(1); // Ajout du sous-titre de rpartition des mthodes avec le pourcentage correspondant : partie suprieure, coin // gauche et droit StringBuffer stringBufferTop = new StringBuffer(); stringBufferTop.append(WebMessages.getString(locale, "bubble.project.subtitle.topLeft") + " " + numberFormat.format(percentTopLeft) + "% "); stringBufferTop.append(WebMessages.getString(locale, "bubble.project.subtitle.topRight") + " " + numberFormat.format(percentTopRight) + "%"); TextTitle subtitleTop = new TextTitle(stringBufferTop.toString()); // Ajout du sous-titre de rpartition des mthodes avec le pourcentage correspondant : partie infrieure, coin // gauche et droit StringBuffer stringBufferBottom = new StringBuffer(); stringBufferBottom.append(WebMessages.getString(locale, "bubble.project.subtitle.bottomLeft") + " " + numberFormat.format(percentBottomLeft) + "% "); stringBufferBottom.append(WebMessages.getString(locale, "bubble.project.subtitle.bottomRight") + " " + numberFormat.format(percentBottomRight) + "%"); TextTitle subtitleBottom = new TextTitle(stringBufferBottom.toString()); // Les rpartitions sont ajoutes sous la forme de sous-menus subtitleBottom.setPosition(RectangleEdge.BOTTOM); pChart.addSubtitle(subtitleBottom); subtitleTop.setPosition(RectangleEdge.BOTTOM); pChart.addSubtitle(subtitleTop); } }