org.squale.squaleweb.util.graph.BubbleMaker.java Source code

Java tutorial

Introduction

Here is the source code for org.squale.squaleweb.util.graph.BubbleMaker.java

Source

/**
 * 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);
    }
}