package ecoparasite.svg; import ecoparasite.representation.ValeursXY; import ecoparasite.svg.elements.*; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; public class SVGBuilder { final int SIZE_TICK_TEXT = ElementsFactory.AXES_TEXT_SIZE - 3; private ArrayList pointsX; private ArrayList pointsY; private Double offsetX; private Double offsetY; private SVGResizing resizer; private Double minPointsX; private Double minPointsY; private Double maxPointsX; private Double maxPointsY; public SVGBuilder(HashMap> axesPoints ) throws IncorrectAxesPointsException { if( axesPoints.get("AxeX") == null || axesPoints.get("AxeY") == null || axesPoints.get("OffsetX") == null || axesPoints.get("OffsetY") == null ){ throw new IncorrectAxesPointsException(); } this.pointsX = axesPoints.get("AxeX"); this.pointsY = axesPoints.get("AxeY"); this.offsetX = axesPoints.get("OffsetX").getFirst(); this.offsetY = axesPoints.get("OffsetY").getFirst(); this.minPointsX = this.pointsX.getFirst(); this.minPointsY = this.pointsY.getFirst(); this.maxPointsX = this.pointsX.getLast(); this.maxPointsY = this.pointsY.getLast(); } public ArrayList getPointsX() { return pointsX; } public ArrayList getPointsY() { return pointsY; } public Double getOffsetX() { return offsetX; } public Double getOffsetY() { return offsetY; } public SVGResizing getResizer() { if( this.resizer == null ){ this.resizer = new SVGResizing( this.minPointsX, this.minPointsY, this.maxPointsX, this.maxPointsY ); } return resizer; } public void setResizer(SVGResizing resizer) { this.resizer = resizer; } public ArrayList buildAll(String XLabel, String YLabel, HashSet points, double A, double B){ ArrayList elements = new ArrayList<>(); elements.addAll(buildAxes(XLabel, YLabel)); elements.addAll(buildXTicks()); elements.addAll(buildYTicks()); elements.addAll(buildPoints(points)); elements.addAll(buildRegression(A,B)); return elements; } public ArrayList buildAxes(String XLabel, String YLabel){ final int OFFSET_TEXT_AXISX_X = -20; final int OFFSET_TEXT_AXISX_Y = -10; final int OFFSET_TEXT_AXISY_X = +5; final int OFFSET_TEXT_AXISY_Y = +10; ArrayList elements = new ArrayList<>(); double beginAxeX = getBeginAxeX(); double beginAxeY = getBeginAxeY(); Coordonnees bottom = getResizer().resize( beginAxeX, minPointsY ); Coordonnees top = getResizer().resize( beginAxeX, maxPointsY ); Coordonnees left = getResizer().resize( minPointsX, beginAxeY ); Coordonnees right = getResizer().resize( maxPointsX, beginAxeY ); // Axes elements.add( new Line( bottom, top, ElementsFactory.COLOR_BLACK, 2 ) ); elements.add( new Line( left, right, ElementsFactory.COLOR_BLACK, 2 ) ); // Labels. elements.add( new Text( new Coordonnees( right.getX() + OFFSET_TEXT_AXISX_X, right.getY() + OFFSET_TEXT_AXISX_Y ), XLabel, ElementsFactory.COLOR_BLACK, ElementsFactory.AXES_TEXT_SIZE ) ); elements.add( new Text( new Coordonnees( top.getX() + OFFSET_TEXT_AXISY_X, top.getY() + OFFSET_TEXT_AXISY_Y ), YLabel, ElementsFactory.COLOR_BLACK, ElementsFactory.AXES_TEXT_SIZE )); return elements; } public ArrayList buildXTicks(){ final int OFFSET_TICK = -5; final int OFFSET_TEXT_X = -10; final int OFFSET_TEXT_Y = +15; ArrayList elements = new ArrayList<>(); double beginAxeY = getBeginAxeY(); for( Double X : this.pointsX ){ Coordonnees coords = getResizer().resize( X, beginAxeY ); elements.add(new Line( coords, new Coordonnees( coords.getX(), coords.getY() + OFFSET_TICK ), ElementsFactory.COLOR_BLACK, 1 )); elements.add(new Text( new Coordonnees( coords.getX() + OFFSET_TEXT_X, coords.getY() + OFFSET_TEXT_Y ), X.toString(), ElementsFactory.COLOR_BLACK, SIZE_TICK_TEXT )); } return elements; } public ArrayList buildYTicks(){ final int OFFSET_TICK = +5; final int OFFSET_TEXT_X = -35; final int OFFSET_TEXT_Y = +5; ArrayList elements = new ArrayList<>(); double beginAxeX = getBeginAxeX(); for( Double Y : this.pointsY ){ Coordonnees coords = getResizer().resize( beginAxeX, Y ); elements.add(new Line( new Coordonnees(coords.getX() + OFFSET_TICK, coords.getY() ), coords, ElementsFactory.COLOR_BLACK, 1 )); elements.add(new Text( new Coordonnees( coords.getX() + OFFSET_TEXT_X, coords.getY() + OFFSET_TEXT_Y ), Y.toString(), ElementsFactory.COLOR_BLACK, SIZE_TICK_TEXT )); } return elements; } public ArrayList buildPoints( HashSet points ){ ArrayList elements = new ArrayList<>(); for( ValeursXY point : points ){ Coordonnees coords = getResizer().resize( point.getX(), point.getY() ); elements.add(new Circle(coords,3,ElementsFactory.COLOR_BLUE) ); } return elements; } public ArrayList buildRegression( double A, double B ){ ArrayList elements = new ArrayList<>(); double y1 = A * minPointsX + B; double y2 = A * maxPointsX + B; Coordonnees coords1 = getResizer().resize(minPointsX, y1); Coordonnees coords2 = getResizer().resize(maxPointsX, y2); elements.add( new Line( coords1, coords2, ElementsFactory.COLOR_RED, 2 ) ); return elements; } private double getBeginAxeX(){ return ( minPointsX > 0 ) ? minPointsX : ( maxPointsX < 0 ? maxPointsX : 0 ); } private double getBeginAxeY(){ return ( minPointsY > 0 ) ? minPointsY : ( maxPointsY < 0 ? maxPointsY : 0 ); } /** * Permet de renvoyer des valeurs "clean" pour l'affichage des axes * @param h Contient les Coordonnées de chacun des points de nos données * @return une HashMap de String et de Hashset de Double. * Avec la String "AxeX", un Hashset de Double contenant les valeurs des gradations de l'axe X * Avec la String "AxeY", un Hashset de Double contenant les valeurs des gragations de l'axe Y * Avec la String "OffsetX", un Hashset de Double contenant uniquement la valeur de l'offset des points par rapport à l'axe X * Avec la String "OffsetY", un Hashset de Double contenant uniquement la valeur de l'offset des points par rapport à l'axe Y */ public static HashMap< String ,ArrayList> calcPointAxes(HashSet h){ HashMap< String, ArrayList > map = new HashMap<>(); //Définition des min et max double max_x = Double.MIN_VALUE; double min_x = Double.MAX_VALUE; double max_y = Double.MIN_VALUE; double min_y = Double.MAX_VALUE; //Trouvé les min et max for (ValeursXY var : h) { if (max_x < var.getX()){ max_x = var.getX(); } if (min_x > var.getX()){ min_x = var.getX(); } if (max_y < var.getY()){ max_y = var.getY(); } if (min_y > var.getY()){ min_y = var.getY(); } } double range_x = max_x-min_x; double range_y = max_y-min_y; int target = 10; // Ideal Number of Gradation double step_x = niceStep(range_x,target); double step_y = niceStep(range_y,target); double nicemin_x = roundMin(min_x,step_x); double nicemax_x = roundMax(max_x,step_x); double nicemin_y = roundMin(min_y,step_y); double nicemax_y = roundMax(max_y,step_y); // Compléter un Hashset de Double pour X et pour Y et Offset X et Y. TODO ArrayList axeX = new ArrayList<>(); ArrayList axeY = new ArrayList<>(); ArrayList OffsetX = new ArrayList<>(); ArrayList OffsetY = new ArrayList<>(); Double ix = nicemin_x; while ( ix <= nicemax_x ) { axeX.add(ix); ix+=step_x; }; map.put("AxeX", axeX); Double iy = nicemin_y; while ( iy <= nicemax_y ) { axeY.add(iy); iy+=step_y; } map.put("AxeY",axeY); double offsetX = min_x - nicemin_x; double offsetY = min_y - nicemin_y; ArrayList offsetXHash = new ArrayList<>(); offsetXHash.add(offsetX); ArrayList offsetYHash = new ArrayList<>(); offsetYHash.add(offsetY); map.put("OffsetX", offsetXHash); map.put("OffsetY", offsetYHash); return map; } /** * Fonction de calcul d'un step rond * Cette fonction est basé sur une idée demandée à ChatGPT * @param range écart entre la plus petite et la plus grande valeur * @param targetTicks nombre de gradation ideal * @return */ public static double niceStep(double range, int targetTicks) { double rawStep = range / targetTicks; double exponent = Math.floor(Math.log10(rawStep)); double fraction = rawStep / Math.pow(10, exponent); double niceFraction; if (fraction < 1.5) niceFraction = 1; else if (fraction < 3) niceFraction = 2; else if (fraction < 7) niceFraction = 5; else niceFraction = 10; return niceFraction * Math.pow(10, exponent); } /** * retourne une valeur arrondi "joli" adapter à un graphique * @param value * @param step * @return */ public static double roundMin(double value, double step) { return Math.floor(value / step) * step; } public static double roundMax(double value, double step) { return Math.ceil(value / step) * step; } }