/** * 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 G�n�ration du graphe de type Bubble, stock� dans un form et appel� depuis une page Jsp */ public class BubbleMaker extends AbstractGraphMaker { /** * Hauteur du diagramme par d�faut */ public static final int DEFAULT_HEIGHT = 500; /** * Hauteur du diagramme par d�faut */ public static final int DEFAULT_WIDTH = 550; /** * Position par d�faut de l'axe horizontal */ private static final int DEFAULT_HORIZONTAL_AXIS_POS = 7; /** * Position par d�faut de l'axe vertical */ private static final int DEFAULT_VERTICAL_AXIS_POS = 10; /** * Marge par d�faut avec les axes. */ private static final double DEFAULT_AXIS_MARGIN = 2; /** * Permet de param�trer la valeur de l'axe horizontal */ private int mHorizontalAxisPos; /** * Permet de param�trer la valeur de l'axe horizontal */ private int mVerticalAxisPos; /** * Contient <code>true</code> si des donn�es peuvent �tre ajout�es */ private boolean mCouldAddDatas = true; /** * Borne inf�rieure en de�� de laquelle la taille du point est � la taille minimale */ private static final int DEFAULT_LOWER_LIMIT = 1; /** * Borne sup�rieure 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 m�thodes difficilement maintenables mais structur�es */ private double totalTopLeft; /** * Pourcentage des m�thodes difficilement maintenables et mal structur�es */ private double totalTopRight; /** * Pourcentage des m�thodes maintenables et bien structur�es */ private double totalBottomLeft; /** * Pourcentage des m�thodes maintenables mais mal structur�es */ private double totalBottomRight; /** * La locale */ private Locale locale; /** * Dataset contenant les valeurs � mettre dans le diagramme */ private DefaultXYZDataset mDataSet; /** * Constructeur par d�faut */ 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 d�faut : "v(g)" mXLabel = WebMessages.getString( "bubble.default.axis.domain" ); // Etiquete figurant sur l'axe des valeurs (peut etre null) Par d�faut : "ev(g)" mYLabel = WebMessages.getString( "bubble.default.axis.value" ); // Initialisation de la valeur des axes aux valeurs par d�faut 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 d�faut if ( pHorizontalAxisPos != null & pHorizontalAxisPos.intValue() != -1 ) { mHorizontalAxisPos = pHorizontalAxisPos.intValue(); } // Si c'est null on garde la valeur par d�faut 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 ordon�es */ 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 ordon�es */ 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 d�faut */ protected int getDefaultHeight() { return DEFAULT_HEIGHT; } /** * @see org.squale.squalecommon.util.graph.AbstractGraphMaker#getDefaultWidth() * @return la largeur par d�faut */ 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 �vit�s pour des raisons de performances On doit toujours avoir * l'�galit� suivante : pHorizontalValues.length == pVerticalValues.length * * @param pName le nom de la s�rie (peut etre <code>null</code> si c'est la premi�re s�rie, dans ce cas, seule * cette s�rie sera ajout�ee) * @param pHorizontalValues les valeurs de l'axe horizontal * @param pVerticalValues les valeurs de l'axe vertical * @param pTotal nombre de m�thodes ayant une m�me 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 consid�re que tout va bien se passer // si tout s'est bien pass�, on ajoute les valeurs // La taille du point est proportionnelle aux m�thodes ayant les m�mes valeurs (vg,evg) double[] pSize = new double[pTotal.length]; // La taille du point est proportionnelle aux nombres de m�thodes ayant les m�mes valeurs (vg, evg) for ( int i = 0; i < pTotal.length; i++ ) { double radius = Math.sqrt( pTotal[i] ); // En de�� de la borne inf�rieure, la taille du point a la taille minimale if ( radius <= DEFAULT_LOWER_LIMIT ) { pSize[i] = DEFAULT_LOWER_SIZE; } else { // Au del� de la borne sup�rieure, la taille du point a la taille maximale if ( radius >= DEFAULT_UPPER_LIMIT ) { pSize[i] = DEFAULT_UPPER_SIZE; } else { // Entre les bornes inf�rieure et sup�rieure, La taille du point grossit // proportionnellement au nombre de m�thodes ayant les m�mes 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; } /** * Cr�e une XYSeries et l'ajoute. * * @param pName le nom de la s�rie (peut etre <code>null</code> si c'est la premi�re s�rie, dans ce cas, seule * cette s�rie sera ajout�ee). * @param pHorizontalValues les valeurs de l'axe horizontal. * @param pVerticalValues les valeurs de l'axe vertical. * @param pSize nombre de m�thodes ayant une m�me 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� sp�cifi� on affecte le nom par d�faut 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 m�thodes dans les quatre coins du graphes en * fonction des crit�res - 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 m�thodes ayant les m�mes valeurs (vg, evg) */ private void addDistribution( double[] pHorizontalValues, double[] pVerticalValues, double[] pTotal ) { for ( int i = 0; i < pHorizontalValues.length; i++ ) { // M�thode structur�e ? if ( pHorizontalValues[i] <= mVerticalAxisPos ) { // M�thode structur�e et maintenable ? if ( pVerticalValues[i] <= mHorizontalAxisPos ) { totalBottomLeft += pTotal[i]; // M�thode structur�e mais difficilement maintenable ? } else { totalTopLeft += pTotal[i]; } // M�thode non structur�e ? } else { // M�thode non structur�e mais maintenable ? if ( pVerticalValues[i] <= mHorizontalAxisPos ) { totalBottomRight += pTotal[i]; // M�thode non structur�e 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 pr�c�dent * @param pVgs tableau des valeurs vg * @param pEvgs tableau des valeurs evg * @param pTotal nombre de m�thodes ayant la m�me 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 ) { // G�n�ration 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 g�n�ration du graphe * * @return graphe de type Bubble */ private JFreeChart getCommonChart() { // Cr�ation 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(); // D�termination des bornes en abscisse et en ordonn�e 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 ordonn�e newDomainAxis.setLowerBound( minDomain ); newDomainAxis.setUpperBound( maxDomain ); newRangeAxis.setLowerBound( minRange ); newRangeAxis.setUpperBound( maxRange ); chart.getXYPlot().setDomainAxis( newDomainAxis ); chart.getXYPlot().setRangeAxis( newRangeAxis ); // Affichage de la r�partition des m�thodes selon les crit�res 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 r�partition des m�thodes - 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 r�partition des m�thodes avec le pourcentage correspondant : partie sup�rieure, 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 r�partition des m�thodes avec le pourcentage correspondant : partie inf�rieure, 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 r�partitions sont ajout�es sous la forme de sous-menus subtitleBottom.setPosition( RectangleEdge.BOTTOM ); pChart.addSubtitle( subtitleBottom ); subtitleTop.setPosition( RectangleEdge.BOTTOM ); pChart.addSubtitle( subtitleTop ); } }