/* * This file is part of LaTeXDraw. * Copyright (c) 2005-2017 Arnaud BLOUIN * LaTeXDraw is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later version. * LaTeXDraw is distributed 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. */ package net.sf.latexdraw.view.jfx; import com.sun.javafx.tk.FontLoader; import com.sun.javafx.tk.FontMetrics; import com.sun.javafx.tk.Toolkit; import javafx.beans.value.ChangeListener; import javafx.scene.paint.Color; import javafx.scene.shape.Path; import javafx.scene.text.Font; import javafx.scene.text.FontPosture; import javafx.scene.text.FontWeight; import net.sf.latexdraw.models.MathUtils; import net.sf.latexdraw.models.interfaces.shape.ArrowStyle; import net.sf.latexdraw.models.interfaces.shape.AxesStyle; import net.sf.latexdraw.models.interfaces.shape.IArrow; import net.sf.latexdraw.models.interfaces.shape.IAxes; import net.sf.latexdraw.models.interfaces.shape.IShape; import net.sf.latexdraw.models.interfaces.shape.PlottingStyle; import net.sf.latexdraw.models.interfaces.shape.TicksStyle; /** * The JFX view of a axes. * @author Arnaud Blouin */ public class ViewAxes extends ViewStdGrid<IAxes> { /** The interval between the labels and the axes. */ private static final double GAP_LABEL = 5d; private final Path mainAxes; private final Path pathTicks; private final ViewArrowableTrait viewArrows; private final ChangeListener<Object> labelUpdate; private final ChangeListener<Object> labelTicksUpdate; private final ChangeListener<Object> fullAxesUpdate; private final ChangeListener<Object> ticksUpdate; private final ChangeListener<Object> axesUpdate; /** * Creates the view. * @param sh The model. */ ViewAxes(final IAxes sh) { super(sh); labelUpdate = (o, formerv, newv) -> updatePath(false, false, true, false); labelTicksUpdate = (o, formerv, newv) -> updatePath(false, true, true, false); fullAxesUpdate = (o, formerv, newv) -> updatePath(true, true, true, false); ticksUpdate = (o, formerv, newv) -> updatePath(false, true, false, false); axesUpdate = (o, formerv, newv) -> updatePath(true, false, false, false); mainAxes = new Path(); pathTicks = new Path(); viewArrows = new ViewArrowableTrait(model); getChildren().add(mainAxes); getChildren().add(pathTicks); getChildren().add(viewArrows); model.labelsSizeProperty().addListener(labelUpdate); model.gridEndXProperty().addListener(fullAxesUpdate); model.gridEndYProperty().addListener(fullAxesUpdate); model.gridStartXProperty().addListener(fullAxesUpdate); model.gridStartYProperty().addListener(fullAxesUpdate); model.originXProperty().addListener(labelUpdate); model.originYProperty().addListener(labelUpdate); model.incrementXProperty().addListener(labelUpdate); model.incrementYProperty().addListener(labelUpdate); model.distLabelsXProperty().addListener(labelTicksUpdate); model.distLabelsYProperty().addListener(labelTicksUpdate); model.labelsDisplayedProperty().addListener(labelUpdate); model.showOriginProperty().addListener(labelUpdate); model.ticksDisplayedProperty().addListener(ticksUpdate); model.ticksStyleProperty().addListener(ticksUpdate); model.ticksSizeProperty().addListener(ticksUpdate); model.axesStyleProperty().addListener(axesUpdate); updatePath(true, true, true, true); } private void updatePathTicksX(final double gapx, final TicksStyle ticksStyle, final double tickLgth) { final boolean noArrowLeftX = model.getArrowStyle(1) == ArrowStyle.NONE || model.getGridMinX() == model.getOriginX(); final boolean noArrowRightX = model.getArrowStyle(3) == ArrowStyle.NONE || model.getGridMaxX() == model.getOriginX(); final double distX = model.getDistLabelsX(); double x; final double y; int inti; switch(ticksStyle) { case FULL: y = tickLgth / 2d; break; case TOP: y = 0d; break; default: y = tickLgth; } for(double incrx = model.getIncrementX(), maxx = model.getGridMaxX() / distX, minx = model.getGridMinX() / distX, i = minx * incrx; i <= maxx * incrx; i += incrx * distX) { inti = (int) i; if(isElementPaintable(noArrowLeftX, noArrowRightX, minx, maxx, inti)) { x = inti * gapx; pathTicks.getElements().add(ViewFactory.INSTANCE.createMoveTo(x, y)); pathTicks.getElements().add(ViewFactory.INSTANCE.createLineTo(x, y - tickLgth)); } } } /** * @return True if a ticks or a label corresponding to the given parameter can be painted. */ private boolean isElementPaintable(final boolean noArrow1, final boolean noArrow2, final double min, final double max, final double i) { return (noArrow2 || !MathUtils.INST.equalsDouble(max, i)) && (noArrow1 || !MathUtils.INST.equalsDouble(min, i)); } private void updatePathTicksY(final double gapy, final TicksStyle ticksStyle, final double tickLgth) { final boolean noArrowTopY = model.getArrowStyle(2) == ArrowStyle.NONE || model.getGridMaxY() == model.getOriginY(); final boolean noArrowBotY = model.getArrowStyle(0) == ArrowStyle.NONE || model.getGridMinY() == model.getOriginY(); final double distY = model.getDistLabelsY(); final double x; double y; int inti; switch(ticksStyle) { case FULL: x = -tickLgth / 2d; break; case TOP: x = 0d; break; default: x = -tickLgth; } for(double incry = model.getIncrementY(), maxy = model.getGridMaxY() / distY, miny = model.getGridMinY() / distY, i = miny * incry; i <= maxy * incry; i += incry * distY) { inti = (int) i; if(isElementPaintable(noArrowBotY, noArrowTopY, miny, maxy, inti)) { y = -inti * gapy; pathTicks.getElements().add(ViewFactory.INSTANCE.createMoveTo(x, y)); pathTicks.getElements().add(ViewFactory.INSTANCE.createLineTo(x + tickLgth, y)); } } } private void updatePathTicks(final double gapx, final double gapy) { final PlottingStyle ticksDisplay = model.getTicksDisplayed(); final TicksStyle ticksStyle = model.getTicksStyle(); final double tickLgth = ticksStyle == TicksStyle.FULL ? model.getTicksSize() * 2d : model.getTicksSize(); if(ticksDisplay.isX()) { updatePathTicksX(gapx, ticksStyle, tickLgth); } if(ticksDisplay.isY()) { updatePathTicksY(gapy, ticksStyle, tickLgth); } pathTicks.setDisable(!ticksDisplay.isX() && !ticksDisplay.isY()); pathTicks.setFill(Color.BLACK); } private void updatePathLabelsY(final PlottingStyle ticksDisplay, final TicksStyle ticksStyle, final double gapy, final Font font, final FontMetrics fontMetrics) { final int origy = (int) model.getOriginY(); final double gap; final float height = fontMetrics.getAscent(); final boolean noArrowTopY = model.getArrowStyle(2) == ArrowStyle.NONE || model.getGridMaxY() == model.getOriginY(); final boolean noArrowBotY = model.getArrowStyle(0) == ArrowStyle.NONE || model.getGridMinY() == model.getOriginY(); final boolean showOrig = model.isShowOrigin(); final double distY = model.getDistLabelsY(); final boolean xGE0 = model.getGridMinX() >= 0; String str; if(ticksStyle.isBottom() && ticksDisplay.isY()) { gap = -(model.getTicksSize() + model.getThickness() / 2d + GAP_LABEL); }else { gap = -(model.getThickness() / 2d + GAP_LABEL); } for(double incry = model.getIncrementY(), maxy = model.getGridMaxY() / distY, miny = model.getGridMinY() / distY, i = miny * incry; i <= maxy * incry; i += incry * distY) { int inti = (int) i; if((inti != 0 || showOrig && xGE0) && isElementPaintable(noArrowBotY, noArrowTopY, miny, maxy, inti)) { str = String.valueOf(inti + origy); addTextLabel(str, gap - fontMetrics.computeStringWidth(str), height / 2d - inti * gapy, font); } } } /** * Updates the labels path by drawing the labels of the X-axis. */ private void updatePathLabelsX(final PlottingStyle ticksDisplay, final TicksStyle ticksStyle, final double gapx, final Font font, final FontMetrics fontMetrics) { // Painting the labels on the X-axis. final int origx = (int) model.getOriginX(); final double gap = (ticksDisplay.isX() && ticksStyle.isBottom() ? model.getTicksSize() : 0d) + model.getThickness() / 2d + GAP_LABEL; final double sep = model.getGridMaxY() <= -model.getOriginY() ? -gap - GAP_LABEL : gap + fontMetrics.getAscent(); final boolean noArrowLeftX = model.getArrowStyle(1) == ArrowStyle.NONE || model.getGridMinX() == model.getOriginX(); final boolean noArrowRightX = model.getArrowStyle(3) == ArrowStyle.NONE || model.getGridMaxX() == model.getOriginX(); final boolean showOrig = model.isShowOrigin(); final double distX = model.getDistLabelsX(); final boolean yGE0 = model.getGridMinY() >= 0; String str; for(double incrx = model.getIncrementX(), maxx = model.getGridMaxX() / distX, minx = model.getGridMinX() / distX, i = minx * incrx; i <= maxx * incrx; i += incrx * distX) { int inti = (int) i; if((inti != 0 || showOrig && yGE0) && isElementPaintable(noArrowLeftX, noArrowRightX, minx, maxx, inti)) { str = String.valueOf(inti + origx); addTextLabel(str, inti * gapx - fontMetrics.computeStringWidth(str) / 2d, sep, font); } } } private void updatePathLabels(final double gapx, final double gapy) { final FontLoader fontLoader = Toolkit.getToolkit().getFontLoader(); final Font font = fontLoader.font("cmr10", FontWeight.NORMAL, FontPosture.REGULAR, model.getLabelsSize()); final FontMetrics fontMetrics = fontLoader.getFontMetrics(font); final PlottingStyle labelsDisplay = model.getLabelsDisplayed(); final PlottingStyle ticksDisplay = model.getTicksDisplayed(); final TicksStyle ticksStyle = model.getTicksStyle(); if(labelsDisplay.isX()) { updatePathLabelsX(ticksDisplay, ticksStyle, gapx, font, fontMetrics); } if(labelsDisplay.isY()) { updatePathLabelsY(ticksDisplay, ticksStyle, gapy, font, fontMetrics); } } private void updatePathFrame() { final double endx = model.getGridEndX(); final double endy = model.getGridEndY(); if(endx > 0d || endy > 0d) { final double y1 = endy > 0d ? - endy * IShape.PPC : 0d; final double x2 = endx > 0d ? + endx * IShape.PPC : 0d; mainAxes.getElements().add(ViewFactory.INSTANCE.createMoveTo(0d, y1)); mainAxes.getElements().add(ViewFactory.INSTANCE.createLineTo(x2, y1)); mainAxes.getElements().add(ViewFactory.INSTANCE.createLineTo(x2, 0d)); mainAxes.getElements().add(ViewFactory.INSTANCE.createLineTo(0d, 0d)); mainAxes.getElements().add(ViewFactory.INSTANCE.createClosePath()); } } private void updatePathAxes() { final IArrow arr0 = model.getArrowAt(1); final IArrow arr1 = model.getArrowAt(3); final double arr0Reduction = arr0.getArrowStyle().needsLineReduction() ? arr0.getArrowShapedWidth() : 0d; final double arr1Reduction = arr1.getArrowStyle().needsLineReduction() ? arr1.getArrowShapedWidth() : 0d; mainAxes.getElements().add(ViewFactory.INSTANCE.createMoveTo(model.getGridStartX() * IShape.PPC + arr0Reduction, 0d)); mainAxes.getElements().add(ViewFactory.INSTANCE.createLineTo(model.getGridEndX() * IShape.PPC - arr1Reduction, 0d)); mainAxes.getElements().add(ViewFactory.INSTANCE.createMoveTo(0d, -model.getGridStartY() * IShape.PPC - arr0Reduction)); mainAxes.getElements().add(ViewFactory.INSTANCE.createLineTo(0d, -model.getGridEndY() * IShape.PPC + arr1Reduction)); } public void updatePath(final boolean axes, final boolean ticks, final boolean texts, final boolean arrows) { final double incrx = model.getIncrementX(); final double incry = model.getIncrementY(); final double distX = model.getDistLabelsX(); final double distY = model.getDistLabelsY(); final AxesStyle axesStyle = model.getAxesStyle(); final double gapX = MathUtils.INST.equalsDouble(distX, 0d) ? IShape.PPC : distX / incrx * IShape.PPC; final double gapY = MathUtils.INST.equalsDouble(distY, 0d) ? IShape.PPC : distY / incry * IShape.PPC; if(arrows || axes) { viewArrows.update(model.getAxesStyle().supportsArrows()); } if(axes) { mainAxes.getElements().clear(); switch(axesStyle) { case AXES: updatePathAxes(); break; case FRAME: updatePathFrame(); break; case NONE: break; } } if(ticks) { pathTicks.getElements().clear(); updatePathTicks(gapX, gapY); } if(texts) { cleanLabels(); if(model.getLabelsDisplayed() != PlottingStyle.NONE) { labels.setDisable(false); updatePathLabels(gapX, gapY); }else { labels.setDisable(true); } } } // @Override // public void paint(final Graphics2D g, final Rectangle clip) { // final IPoint vectorTrans = beginRotation(g); // // g.setStroke(getStroke()); // g.setColor(model.getLineColour()); // g.draw(path); // g.setStroke(new BasicStroke(1f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER)); // g.draw(pathTicks); // // if(model.getAxesStyle().supportsArrows()) // paintArrows(g, false); // // if(model.getLabelsDisplayed()!=PlottingStyle.NONE) { // g.setColor(Color.BLACK); // g.fill(pathLabels); // } // // if(vectorTrans!=null) // endRotation(g, vectorTrans); // } // // // @Override // protected void paintArrows(final Graphics2D g, final boolean asShadow) { // if(arrows.size()==4) { // final Color colour = asShadow ? model.getShadowCol() : model.getFillingCol(); // arrows.forEach(arr -> arr.paint(g, colour, asShadow)); // } // } @Override public void flush() { mainAxes.getElements().clear(); pathTicks.getElements().clear(); model.labelsSizeProperty().removeListener(labelUpdate); model.gridEndXProperty().removeListener(fullAxesUpdate); model.gridEndYProperty().removeListener(fullAxesUpdate); model.gridStartXProperty().removeListener(fullAxesUpdate); model.gridStartYProperty().removeListener(fullAxesUpdate); model.originXProperty().removeListener(labelUpdate); model.originYProperty().removeListener(labelUpdate); model.incrementXProperty().removeListener(labelUpdate); model.incrementYProperty().removeListener(labelUpdate); model.distLabelsXProperty().removeListener(labelTicksUpdate); model.distLabelsYProperty().removeListener(labelTicksUpdate); model.labelsDisplayedProperty().removeListener(labelUpdate); model.showOriginProperty().removeListener(labelUpdate); model.ticksDisplayedProperty().removeListener(ticksUpdate); model.ticksStyleProperty().removeListener(ticksUpdate); model.ticksSizeProperty().removeListener(ticksUpdate); model.axesStyleProperty().removeListener(axesUpdate); super.flush(); } /** * @return The main axes path. */ public Path getMainAxes() { return mainAxes; } /** * The ticks path. * @return */ public Path getPathTicks() { return pathTicks; } }