/* * Copyright (c) 2015 by Gerrit Grunwald * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package eu.hansolo.medusa.skins; import eu.hansolo.medusa.Fonts; import eu.hansolo.medusa.Gauge; import eu.hansolo.medusa.LcdDesign; import eu.hansolo.medusa.Marker; import eu.hansolo.medusa.Section; import eu.hansolo.medusa.TickLabelOrientation; import eu.hansolo.medusa.tools.Helper; import java.math.BigDecimal; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javafx.beans.InvalidationListener; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.geometry.Pos; import javafx.geometry.VPos; import javafx.scene.CacheHint; import javafx.scene.Group; import javafx.scene.canvas.Canvas; import javafx.scene.canvas.GraphicsContext; import javafx.scene.control.Label; import javafx.scene.control.Tooltip; import javafx.scene.effect.BlurType; import javafx.scene.effect.DropShadow; import javafx.scene.effect.InnerShadow; import javafx.scene.layout.Pane; import javafx.scene.paint.Color; import javafx.scene.paint.CycleMethod; import javafx.scene.paint.LinearGradient; import javafx.scene.paint.Paint; import javafx.scene.paint.RadialGradient; import javafx.scene.paint.Stop; import javafx.scene.shape.ArcType; import javafx.scene.shape.Circle; import javafx.scene.shape.ClosePath; import javafx.scene.shape.CubicCurveTo; import javafx.scene.shape.FillRule; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; import javafx.scene.shape.Rectangle; import javafx.scene.shape.SVGPath; import javafx.scene.shape.Shape; import javafx.scene.shape.StrokeLineCap; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.scene.text.TextAlignment; import javafx.scene.transform.Rotate; import static eu.hansolo.medusa.tools.Helper.enableNode; /** * Created by hansolo on 30.12.15. */ public class AmpSkin extends GaugeSkinBase { protected static final double PREFERRED_WIDTH = 310; protected static final double PREFERRED_HEIGHT = 260; protected static final double MINIMUM_WIDTH = 31; protected static final double MINIMUM_HEIGHT = 26; protected static final double MAXIMUM_WIDTH = 1024; protected static final double MAXIMUM_HEIGHT = 858; private static final double ASPECT_RATIO = 0.83870968; private static final double START_ANGLE = 225; private static final double ANGLE_RANGE = 90; private Map<Marker, Shape> markerMap = new ConcurrentHashMap<>(); private double oldValue; private double width; private double height; private Pane pane; private SVGPath foreground; private Canvas ticksAndSectionsCanvas; private GraphicsContext ticksAndSections; private Pane markerPane; private Path threshold; private Path average; private double ledSize; private InnerShadow ledOnShadow; private InnerShadow ledOffShadow; private LinearGradient frameGradient; private LinearGradient ledOnGradient; private LinearGradient ledOffGradient; private RadialGradient highlightGradient; private Canvas ledCanvas; private GraphicsContext led; private Path needle; private MoveTo needleMoveTo1; private CubicCurveTo needleCubicCurveTo2; private CubicCurveTo needleCubicCurveTo3; private CubicCurveTo needleCubicCurveTo4; private LineTo needleLineTo5; private CubicCurveTo needleCubicCurveTo6; private ClosePath needleClosePath7; private Rotate needleRotate; private Group shadowGroup; private InnerShadow lightEffect; private DropShadow dropShadow; private Text titleText; private Text unitText; private Rectangle lcd; private Label lcdText; private double angleStep; private Tooltip thresholdTooltip; private String formatString; private Locale locale; private ListChangeListener<Section> sectionListener; private InvalidationListener currentValueListener; private InvalidationListener needleRotateListener; private ListChangeListener<Marker> markerListener; // ******************** Constructors ************************************** public AmpSkin(Gauge gauge) { super(gauge); if (gauge.isAutoScale()) gauge.calcAutoScale(); angleStep = gauge.getAngleRange() / gauge.getRange(); oldValue = gauge.getValue(); formatString = new StringBuilder("%.").append(Integer.toString(gauge.getDecimals())).append("f").toString(); locale = gauge.getLocale(); sectionListener = c -> redraw(); currentValueListener = o -> rotateNeedle(); needleRotateListener = o -> handleEvents("ANGLE"); markerListener = c -> { updateMarkers(); redraw(); }; updateMarkers(); initGraphics(); registerListeners(); } // ******************** Initialization ************************************ private void initGraphics() { // Set initial size if (Double.compare(gauge.getPrefWidth(), 0.0) <= 0 || Double.compare(gauge.getPrefHeight(), 0.0) <= 0 || Double.compare(gauge.getWidth(), 0.0) <= 0 || Double.compare(gauge.getHeight(), 0.0) <= 0) { if (gauge.getPrefWidth() > 0 && gauge.getPrefHeight() > 0) { gauge.setPrefSize(gauge.getPrefWidth(), gauge.getPrefHeight()); } else { gauge.setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT); } } ticksAndSectionsCanvas = new Canvas(PREFERRED_WIDTH, PREFERRED_HEIGHT); ticksAndSections = ticksAndSectionsCanvas.getGraphicsContext2D(); ledCanvas = new Canvas(); led = ledCanvas.getGraphicsContext2D(); thresholdTooltip = new Tooltip("Threshold\n(" + String.format(locale, formatString, gauge.getThreshold()) + ")"); thresholdTooltip.setTextAlignment(TextAlignment.CENTER); threshold = new Path(); Helper.enableNode(threshold, gauge.isThresholdVisible()); Tooltip.install(threshold, thresholdTooltip); average = new Path(); Helper.enableNode(average, gauge.isAverageVisible()); markerPane = new Pane(); needleRotate = new Rotate(180 - START_ANGLE); needleRotate.setAngle(needleRotate.getAngle() + (gauge.getValue() - oldValue - gauge.getMinValue()) * angleStep); needleMoveTo1 = new MoveTo(); needleCubicCurveTo2 = new CubicCurveTo(); needleCubicCurveTo3 = new CubicCurveTo(); needleCubicCurveTo4 = new CubicCurveTo(); needleLineTo5 = new LineTo(); needleCubicCurveTo6 = new CubicCurveTo(); needleClosePath7 = new ClosePath(); needle = new Path(needleMoveTo1, needleCubicCurveTo2, needleCubicCurveTo3, needleCubicCurveTo4, needleLineTo5, needleCubicCurveTo6, needleClosePath7); needle.setFillRule(FillRule.EVEN_ODD); needle.getTransforms().setAll(needleRotate); dropShadow = new DropShadow(); dropShadow.setColor(Color.rgb(0, 0, 0, 0.25)); dropShadow.setBlurType(BlurType.TWO_PASS_BOX); dropShadow.setRadius(0.015 * PREFERRED_WIDTH); dropShadow.setOffsetY(0.015 * PREFERRED_WIDTH); shadowGroup = new Group(needle); shadowGroup.setEffect(gauge.isShadowsEnabled() ? dropShadow : null); titleText = new Text(gauge.getTitle()); titleText.setTextOrigin(VPos.CENTER); titleText.setFill(gauge.getTitleColor()); Helper.enableNode(titleText, !gauge.getTitle().isEmpty()); unitText = new Text(gauge.getUnit()); unitText.setMouseTransparent(true); unitText.setTextOrigin(VPos.CENTER); lcd = new Rectangle(0.3 * PREFERRED_WIDTH, 0.1 * PREFERRED_HEIGHT); lcd.setArcWidth(0.0125 * PREFERRED_HEIGHT); lcd.setArcHeight(0.0125 * PREFERRED_HEIGHT); lcd.relocate((PREFERRED_WIDTH - lcd.getWidth()) * 0.5, 0.44 * PREFERRED_HEIGHT); Helper.enableNode(lcd, gauge.isLcdVisible() && gauge.isValueVisible()); lcdText = new Label(String.format(locale, "%." + gauge.getDecimals() + "f", gauge.getValue())); lcdText.setAlignment(Pos.CENTER_RIGHT); lcdText.setVisible(gauge.isValueVisible()); // Set initial value angleStep = ANGLE_RANGE / gauge.getRange(); double targetAngle = 180 - START_ANGLE + (gauge.getValue() - gauge.getMinValue()) * angleStep; targetAngle = clamp(180 - START_ANGLE, 180 - START_ANGLE + ANGLE_RANGE, targetAngle); needleRotate.setAngle(targetAngle); lightEffect = new InnerShadow(BlurType.TWO_PASS_BOX, Color.rgb(255, 255, 255, 0.65), 2, 0.0, 0.0, 2.0); foreground = new SVGPath(); foreground.setContent("M 26 26.5 C 26 20.2432 26.2432 20 32.5 20 L 277.5 20 C 283.7568 20 284 20.2432 284 26.5 L 284 143.5 C 284 149.7568 283.7568 150 277.5 150 L 32.5 150 C 26.2432 150 26 149.7568 26 143.5 L 26 26.5 ZM 0 6.7241 L 0 253.2758 C 0 260 0 260 6.75 260 L 303.25 260 C 310 260 310 260 310 253.2758 L 310 6.7241 C 310 0 310 0 303.25 0 L 6.75 0 C 0 0 0 0 0 6.7241 Z"); foreground.setEffect(lightEffect); // Add all nodes pane = new Pane(); pane.getChildren().setAll(ticksAndSectionsCanvas, markerPane, ledCanvas, unitText, lcd, lcdText, shadowGroup, foreground, titleText); getChildren().setAll(pane); } @Override protected void registerListeners() { super.registerListeners(); gauge.getMarkers().addListener(markerListener); gauge.getSections().addListener(sectionListener); gauge.currentValueProperty().addListener(currentValueListener); needleRotate.angleProperty().addListener(needleRotateListener); } // ******************** Methods ******************************************* protected void handleEvents(final String EVENT_TYPE) { super.handleEvents(EVENT_TYPE); if ("FINISHED".equals(EVENT_TYPE)) { if ( gauge.isHighlightSections() ) { redraw(); } } else if ("ANGLE".equals(EVENT_TYPE)) { double currentValue = (needleRotate.getAngle() + START_ANGLE - 180) / angleStep + gauge.getMinValue(); lcdText.setText((String.format(locale, formatString, currentValue))); if (gauge.isLcdVisible()) { lcdText.setAlignment(Pos.CENTER_RIGHT); lcdText.setTranslateX((width - lcdText.getPrefWidth()) * 0.5); } else { lcdText.setAlignment(Pos.CENTER); lcdText.setTranslateX((width - lcdText.getLayoutBounds().getWidth()) * 0.5); } } else if ("VISIBILITY".equals(EVENT_TYPE)) { enableNode(ledCanvas, gauge.isLedVisible()); enableNode(titleText, !gauge.getTitle().isEmpty()); enableNode(unitText, !gauge.getUnit().isEmpty()); enableNode(lcd,gauge.isLcdVisible()); enableNode(lcdText,gauge.isValueVisible()); enableNode(threshold, gauge.isThresholdVisible()); enableNode(average, gauge.isAverageVisible()); boolean markersVisible = gauge.getMarkersVisible(); for (Shape shape : markerMap.values()) { Helper.enableNode(shape, markersVisible); } resize(); redraw(); } else if ("LED".equals(EVENT_TYPE)) { if (gauge.isLedVisible()) { drawLed(led); } } else if ("LCD".equals(EVENT_TYPE)) { if (gauge.isLcdVisible()) redraw(); } else if ("RECALC".equals(EVENT_TYPE)) { angleStep = gauge.getAngleStep(); if (gauge.getValue() < gauge.getMinValue()) { oldValue = gauge.getMinValue(); } if (gauge.getValue() > gauge.getMaxValue()) { oldValue = gauge.getMaxValue(); } redraw(); rotateNeedle(); } } // ******************** Private Methods *********************************** private void rotateNeedle() { angleStep = ANGLE_RANGE / (gauge.getRange()); double targetAngle = 180 - START_ANGLE + (gauge.getCurrentValue() - gauge.getMinValue()) * angleStep; targetAngle = clamp(180 - START_ANGLE, 180 - START_ANGLE + ANGLE_RANGE, targetAngle); needleRotate.setAngle(targetAngle); if (gauge.isAverageVisible()) drawAverage(); } private void drawMarkers() { markerPane.getChildren().setAll(markerMap.values()); markerPane.getChildren().addAll(average, threshold); double minValue = gauge.getMinValue(); double markerSize = 0.0125 * width; double pathHalf = markerSize * 0.3; double scaledWidth = width * 1.106; double centerX = width * 0.5; double centerY = height * 0.77; if (gauge.getMarkersVisible()) { for (Map.Entry<Marker, Shape> entry : markerMap.entrySet()) { Marker marker = entry.getKey(); Shape shape = entry.getValue(); double valueAngle = START_ANGLE - (marker.getValue() - minValue) * angleStep; double sinValue = Math.sin(Math.toRadians(valueAngle)); double cosValue = Math.cos(Math.toRadians(valueAngle)); switch (marker.getMarkerType()) { case TRIANGLE: Path triangle = (Path) shape; triangle.getElements().clear(); triangle.getElements().add(new MoveTo(centerX + scaledWidth * 0.38 * sinValue, centerY + scaledWidth * 0.38 * cosValue)); sinValue = Math.sin(Math.toRadians(valueAngle - pathHalf)); cosValue = Math.cos(Math.toRadians(valueAngle - pathHalf)); triangle.getElements().add(new LineTo(centerX + scaledWidth * 0.4075 * sinValue, centerY + scaledWidth * 0.4075 * cosValue)); sinValue = Math.sin(Math.toRadians(valueAngle + pathHalf)); cosValue = Math.cos(Math.toRadians(valueAngle + pathHalf)); triangle.getElements().add(new LineTo(centerX + scaledWidth * 0.4075 * sinValue, centerY + scaledWidth * 0.4075 * cosValue)); triangle.getElements().add(new ClosePath()); break; case DOT: Circle dot = (Circle) shape; dot.setRadius(markerSize); dot.setCenterX(centerX + scaledWidth * 0.3945 * sinValue); dot.setCenterY(centerY + scaledWidth * 0.3945 * cosValue); break; case STANDARD: default: Path standard = (Path) shape; standard.getElements().clear(); standard.getElements().add(new MoveTo(centerX + scaledWidth * 0.38 * sinValue, centerY + scaledWidth * 0.38 * cosValue)); sinValue = Math.sin(Math.toRadians(valueAngle - pathHalf)); cosValue = Math.cos(Math.toRadians(valueAngle - pathHalf)); standard.getElements().add(new LineTo(centerX + scaledWidth * 0.4075 * sinValue, centerY + scaledWidth * 0.4075 * cosValue)); standard.getElements().add(new LineTo(centerX + scaledWidth * 0.43 * sinValue, centerY + scaledWidth * 0.43 * cosValue)); sinValue = Math.sin(Math.toRadians(valueAngle + pathHalf)); cosValue = Math.cos(Math.toRadians(valueAngle + pathHalf)); standard.getElements().add(new LineTo(centerX + scaledWidth * 0.43 * sinValue, centerY + scaledWidth * 0.43 * cosValue)); standard.getElements().add(new LineTo(centerX + scaledWidth * 0.4075 * sinValue, centerY + scaledWidth * 0.4075 * cosValue)); standard.getElements().add(new ClosePath()); break; } Color markerColor = marker.getColor(); shape.setFill(markerColor); shape.setStroke(markerColor.darker()); shape.setPickOnBounds(false); Tooltip markerTooltip; if (marker.getText().isEmpty()) { markerTooltip = new Tooltip(Double.toString(marker.getValue())); } else { markerTooltip = new Tooltip(new StringBuilder(marker.getText()).append("\n(").append(Double.toString(marker.getValue())).append(")").toString()); } markerTooltip.setTextAlignment(TextAlignment.CENTER); Tooltip.install(shape, markerTooltip); shape.setOnMousePressed(e -> marker.fireMarkerEvent(marker.MARKER_PRESSED_EVENT)); shape.setOnMouseReleased(e -> marker.fireMarkerEvent(marker.MARKER_RELEASED_EVENT)); } } if (gauge.isThresholdVisible()) { // Draw threshold threshold.getElements().clear(); double thresholdAngle = START_ANGLE - (gauge.getThreshold() - minValue) * angleStep; double thresholdSize = Helper.clamp(2.0, 2.5, 0.01 * scaledWidth); double sinValue = Math.sin(Math.toRadians(thresholdAngle)); double cosValue = Math.cos(Math.toRadians(thresholdAngle)); threshold.getElements().add(new MoveTo(centerX + scaledWidth * 0.38 * sinValue, centerY + scaledWidth * 0.38 * cosValue)); sinValue = Math.sin(Math.toRadians(thresholdAngle - thresholdSize)); cosValue = Math.cos(Math.toRadians(thresholdAngle - thresholdSize)); threshold.getElements().add(new LineTo(centerX + scaledWidth * 0.35 * sinValue, centerY + scaledWidth * 0.35 * cosValue)); sinValue = Math.sin(Math.toRadians(thresholdAngle + thresholdSize)); cosValue = Math.cos(Math.toRadians(thresholdAngle + thresholdSize)); threshold.getElements().add(new LineTo(centerX + scaledWidth * 0.35 * sinValue, centerY + scaledWidth * 0.35 * cosValue)); threshold.getElements().add(new ClosePath()); threshold.setFill(gauge.getThresholdColor()); threshold.setStroke(gauge.getTickMarkColor()); } } private void drawAverage() { double scaledWidth = width * 1.106; double centerX = width * 0.5; double centerY = height * 0.77; double minValue = gauge.getMinValue(); // Draw average average.getElements().clear(); double averageAngle = START_ANGLE - (gauge.getAverage() - minValue) * angleStep; double averageSize = Helper.clamp(2.0, 2.5, 0.01 * scaledWidth); double sinValue = Math.sin(Math.toRadians(averageAngle)); double cosValue = Math.cos(Math.toRadians(averageAngle)); average.getElements().add(new MoveTo(centerX + scaledWidth * 0.38 * sinValue, centerY + scaledWidth * 0.38 * cosValue)); sinValue = Math.sin(Math.toRadians(averageAngle - averageSize)); cosValue = Math.cos(Math.toRadians(averageAngle - averageSize)); average.getElements().add(new LineTo(centerX + scaledWidth * 0.35 * sinValue, centerY + scaledWidth * 0.35 * cosValue)); sinValue = Math.sin(Math.toRadians(averageAngle + averageSize)); cosValue = Math.cos(Math.toRadians(averageAngle + averageSize)); average.getElements().add(new LineTo(centerX + scaledWidth * 0.35 * sinValue, centerY + scaledWidth * 0.35 * cosValue)); average.getElements().add(new ClosePath()); average.setFill(gauge.getAverageColor()); average.setStroke(gauge.getTickMarkColor()); } private void updateMarkers() { markerMap.clear(); for (Marker marker : gauge.getMarkers()) { switch(marker.getMarkerType()) { case TRIANGLE: markerMap.put(marker, new Path()); break; case DOT : markerMap.put(marker, new Circle()); break; case STANDARD: default: markerMap.put(marker, new Path()); break; } } } private void drawTickMarks(final GraphicsContext CTX) { double sinValue; double cosValue; double orthText = TickLabelOrientation.ORTHOGONAL == gauge.getTickLabelOrientation() ? 0.51 : 0.52; Point2D center = new Point2D(width * 0.5, height * 0.77); double minorTickSpace = gauge.getMinorTickSpace(); double minValue = gauge.getMinValue(); //double maxValue = gauge.getMaxValue(); double tmpAngleStep = angleStep * minorTickSpace; int decimals = gauge.getTickLabelDecimals(); BigDecimal minorTickSpaceBD = BigDecimal.valueOf(minorTickSpace); BigDecimal majorTickSpaceBD = BigDecimal.valueOf(gauge.getMajorTickSpace()); BigDecimal mediumCheck2 = BigDecimal.valueOf(2 * minorTickSpace); BigDecimal mediumCheck5 = BigDecimal.valueOf(5 * minorTickSpace); BigDecimal counterBD = BigDecimal.valueOf(minValue); double counter = minValue; Font tickLabelFont = Fonts.robotoCondensedRegular((decimals == 0 ? 0.045 : 0.038) * height); CTX.setFont(tickLabelFont); for (double angle = 0 ; Double.compare(-ANGLE_RANGE - tmpAngleStep, angle) < 0 ; angle -= tmpAngleStep) { sinValue = Math.sin(Math.toRadians(angle + START_ANGLE)); cosValue = Math.cos(Math.toRadians(angle + START_ANGLE)); Point2D innerPoint = new Point2D(center.getX() + width * 0.41987097 * sinValue, center.getY() + width * 0.41987097 * cosValue); Point2D outerMinorPoint = new Point2D(center.getX() + width * 0.45387097 * sinValue, center.getY() + width * 0.45387097 * cosValue); Point2D outerMediumPoint = new Point2D(center.getX() + width * 0.46387097 * sinValue, center.getY() + width * 0.46387097 * cosValue); Point2D outerPoint = new Point2D(center.getX() + width * 0.48387097 * sinValue, center.getY() + width * 0.48387097 * cosValue); Point2D textPoint = new Point2D(center.getX() + width * orthText * sinValue, center.getY() + width * orthText * cosValue); CTX.setStroke(gauge.getTickMarkColor()); if (Double.compare(counterBD.remainder(majorTickSpaceBD).doubleValue(), 0.0) == 0) { // Draw major tickmark CTX.setLineWidth(height * 0.0055); CTX.strokeLine(innerPoint.getX(), innerPoint.getY(), outerPoint.getX(), outerPoint.getY()); // Draw text CTX.save(); CTX.translate(textPoint.getX(), textPoint.getY()); switch(gauge.getTickLabelOrientation()) { case ORTHOGONAL: if ((360 - START_ANGLE - angle) % 360 > 90 && (360 - START_ANGLE - angle) % 360 < 270) { CTX.rotate((180 - START_ANGLE - angle) % 360); } else { CTX.rotate((360 - START_ANGLE - angle) % 360); } break; case TANGENT: if ((360 - START_ANGLE - angle - 90) % 360 > 90 && (360 - START_ANGLE - angle - 90) % 360 < 270) { CTX.rotate((90 - START_ANGLE - angle) % 360); } else { CTX.rotate((270 - START_ANGLE - angle) % 360); } break; case HORIZONTAL: default: break; } CTX.setTextAlign(TextAlignment.CENTER); CTX.setTextBaseline(VPos.CENTER); CTX.setFill(gauge.getTickLabelColor()); CTX.fillText(String.format(locale, "%." + decimals + "f", counter), 0, 0); CTX.restore(); } else if (gauge.getMediumTickMarksVisible() && Double.compare(minorTickSpaceBD.remainder(mediumCheck2).doubleValue(), 0.0) != 0.0 && Double.compare(counterBD.remainder(mediumCheck5).doubleValue(), 0.0) == 0.0) { CTX.setLineWidth(height * 0.0035); CTX.strokeLine(innerPoint.getX(), innerPoint.getY(), outerMediumPoint.getX(), outerMediumPoint.getY()); } else if (gauge.getMinorTickMarksVisible() && Double.compare(counterBD.remainder(minorTickSpaceBD).doubleValue(), 0.0) == 0) { CTX.setLineWidth(height * 0.00225); CTX.strokeLine(innerPoint.getX(), innerPoint.getY(), outerMinorPoint.getX(), outerMinorPoint.getY()); } counterBD = counterBD.add(minorTickSpaceBD); counter = counterBD.doubleValue(); } } private void drawSections(final GraphicsContext CTX) { final double x = width * 0.06; final double y = width * 0.21; final double w = width * 0.88; final double h = height * 1.05; final double MIN_VALUE = gauge.getMinValue(); final double MAX_VALUE = gauge.getMaxValue(); final double OFFSET = 90 - START_ANGLE; final ObservableList<Section> sections = gauge.getSections(); final boolean highlightSections = gauge.isHighlightSections(); double value = gauge.getCurrentValue(); int listSize = sections.size(); for (int i = 0 ; i < listSize ; i++) { final Section SECTION = sections.get(i); final double SECTION_START_ANGLE; if (Double.compare(SECTION.getStart(), MAX_VALUE) <= 0 && Double.compare(SECTION.getStop(), MIN_VALUE) >= 0) { if (SECTION.getStart() < MIN_VALUE && SECTION.getStop() < MAX_VALUE) { SECTION_START_ANGLE = 0; } else { SECTION_START_ANGLE = (SECTION.getStart() - MIN_VALUE) * angleStep; } final double SECTION_ANGLE_EXTEND; if (SECTION.getStop() > MAX_VALUE) { SECTION_ANGLE_EXTEND = (MAX_VALUE - SECTION.getStart()) * angleStep; } else { SECTION_ANGLE_EXTEND = (SECTION.getStop() - SECTION.getStart()) * angleStep; } CTX.save(); if (highlightSections) { CTX.setStroke(SECTION.contains(value) ? SECTION.getHighlightColor() : SECTION.getColor()); } else { CTX.setStroke(SECTION.getColor()); } CTX.setLineWidth(height * 0.0415); CTX.setLineCap(StrokeLineCap.BUTT); CTX.strokeArc(x, y, w, h, -(OFFSET + SECTION_START_ANGLE), -SECTION_ANGLE_EXTEND, ArcType.OPEN); CTX.restore(); } } } private void drawLed(final GraphicsContext CTX) { CTX.clearRect(0, 0, ledSize, ledSize); CTX.setFill(frameGradient); CTX.fillOval(0, 0, ledSize, ledSize); CTX.save(); if (gauge.isLedOn()) { CTX.setEffect(ledOnShadow); CTX.setFill(ledOnGradient); } else { CTX.setEffect(ledOffShadow); CTX.setFill(ledOffGradient); } CTX.fillOval(0.14 * ledSize, 0.14 * ledSize, 0.72 * ledSize, 0.72 * ledSize); CTX.restore(); CTX.setFill(highlightGradient); CTX.fillOval(0.21 * ledSize, 0.21 * ledSize, 0.58 * ledSize, 0.58 * ledSize); } private double clamp(final double MIN_VALUE, final double MAX_VALUE, final double VALUE) { if (VALUE < MIN_VALUE) return MIN_VALUE; if (VALUE > MAX_VALUE) return MAX_VALUE; return VALUE; } private void resizeStaticText() { double maxWidth = width * 0.9; double fontSize = height * 0.11; titleText.setFont(Fonts.robotoMedium(fontSize)); titleText.setText(gauge.getTitle()); if ( titleText.getLayoutBounds().getWidth() > maxWidth ) { Helper.adjustTextSize(titleText, maxWidth, fontSize); } titleText.setTranslateX((width - titleText.getLayoutBounds().getWidth()) * 0.5); titleText.setTranslateY(height * 0.76); maxWidth = width * 0.3; fontSize = height * 0.1; unitText.setFont(Fonts.robotoMedium(fontSize)); unitText.setText(gauge.getUnit()); if ( unitText.getLayoutBounds().getWidth() > maxWidth ) { Helper.adjustTextSize(unitText, maxWidth, fontSize); } unitText.setTranslateX((width - unitText.getLayoutBounds().getWidth()) * 0.5); unitText.setTranslateY(height * 0.37); } private void resizeText() { resizeStaticText(); if (gauge.isLcdVisible()) { lcdText.setPadding(new Insets(0, 0.005 * width, 0, 0.005 * width)); switch(gauge.getLcdFont()) { case LCD: lcdText.setFont(Fonts.digital(0.108 * height)); lcdText.setTranslateY(0.45 * height); break; case DIGITAL: lcdText.setFont(Fonts.digitalReadout(0.105 * height)); lcdText.setTranslateY(0.44 * height); break; case DIGITAL_BOLD: lcdText.setFont(Fonts.digitalReadoutBold(0.105 * height)); lcdText.setTranslateY(0.44 * height); break; case ELEKTRA: lcdText.setFont(Fonts.elektra(0.1116 * height)); lcdText.setTranslateY(0.435 * height); break; case STANDARD: default: lcdText.setFont(Fonts.robotoMedium(0.09 * height)); lcdText.setTranslateY(0.43 * height); break; } lcdText.setAlignment(Pos.CENTER_RIGHT); lcdText.setPrefSize(0.3 * width, 0.014 * height); lcdText.setTranslateX((width - lcdText.getPrefWidth()) * 0.5); } else { lcdText.setAlignment(Pos.CENTER); lcdText.setFont(Fonts.robotoMedium(height * 0.1)); lcdText.setPrefSize(0.3 * width, 0.014 * height); lcdText.setTranslateY(0.43 * height); lcdText.setTranslateX((width - lcdText.getLayoutBounds().getWidth()) * 0.5); } } @Override public void dispose() { gauge.getMarkers().removeListener(markerListener); gauge.getSections().removeListener(sectionListener); gauge.currentValueProperty().removeListener(currentValueListener); needleRotate.angleProperty().removeListener(needleRotateListener); super.dispose(); } @Override protected void resize() { width = gauge.getWidth() - gauge.getInsets().getLeft() - gauge.getInsets().getRight(); height = gauge.getHeight() - gauge.getInsets().getTop() - gauge.getInsets().getBottom(); if (ASPECT_RATIO * width > height) { width = 1 / (ASPECT_RATIO / height); } else if (1 / (ASPECT_RATIO / height) > width) { height = ASPECT_RATIO * width; } if (width > 0 && height > 0) { pane.setMaxSize(width, height); pane.relocate((gauge.getWidth() - width) * 0.5, (gauge.getHeight() - height) * 0.5); dropShadow.setRadius(0.01 * height); dropShadow.setOffsetY(0.01 * height); ticksAndSectionsCanvas.setWidth(width); ticksAndSectionsCanvas.setHeight(height); markerPane.setPrefSize(width, width); lcd.setWidth(0.3 * width); lcd.setHeight(0.1 * height); lcd.setArcWidth(0.0125 * height); lcd.setArcHeight(0.0125 * height); lcd.relocate((width - lcd.getWidth()) * 0.5, 0.44 * height); ledSize = 0.06 * height; final Color LED_COLOR = gauge.getLedColor(); frameGradient = new LinearGradient(0.14 * ledSize, 0.14 * ledSize, 0.84 * ledSize, 0.84 * ledSize, false, CycleMethod.NO_CYCLE, new Stop(0.0, Color.rgb(20, 20, 20, 0.65)), new Stop(0.15, Color.rgb(20, 20, 20, 0.65)), new Stop(0.26, Color.rgb(41, 41, 41, 0.65)), new Stop(0.26, Color.rgb(41, 41, 41, 0.64)), new Stop(0.85, Color.rgb(200, 200, 200, 0.41)), new Stop(1.0, Color.rgb(200, 200, 200, 0.35))); ledOnGradient = new LinearGradient(0.25 * ledSize, 0.25 * ledSize, 0.74 * ledSize, 0.74 * ledSize, false, CycleMethod.NO_CYCLE, new Stop(0.0, LED_COLOR.deriveColor(0.0, 1.0, 0.77, 1.0)), new Stop(0.49, LED_COLOR.deriveColor(0.0, 1.0, 0.5, 1.0)), new Stop(1.0, LED_COLOR)); ledOffGradient = new LinearGradient(0.25 * ledSize, 0.25 * ledSize, 0.74 * ledSize, 0.74 * ledSize, false, CycleMethod.NO_CYCLE, new Stop(0.0, LED_COLOR.deriveColor(0.0, 1.0, 0.20, 1.0)), new Stop(0.49, LED_COLOR.deriveColor(0.0, 1.0, 0.13, 1.0)), new Stop(1.0, LED_COLOR.deriveColor(0.0, 1.0, 0.2, 1.0))); highlightGradient = new RadialGradient(0, 0, 0.3 * ledSize, 0.3 * ledSize, 0.29 * ledSize, false, CycleMethod.NO_CYCLE, new Stop(0.0, Color.WHITE), new Stop(1.0, Color.TRANSPARENT)); ledCanvas.setWidth(ledSize); ledCanvas.setHeight(ledSize); ledCanvas.relocate(0.11 * width, 0.10 * height); ledOffShadow = new InnerShadow(BlurType.TWO_PASS_BOX, Color.rgb(0, 0, 0, 0.65), 0.07 * ledSize, 0, 0, 0); ledOnShadow = new InnerShadow(BlurType.TWO_PASS_BOX, Color.rgb(0, 0, 0, 0.65), 0.07 * ledSize, 0, 0, 0); ledOnShadow.setInput(new DropShadow(BlurType.TWO_PASS_BOX, gauge.getLedColor(), 0.36 * ledSize, 0, 0, 0)); double needleWidth = height * 0.015; double needleHeight = height * 0.58; needle.setCache(false); needleMoveTo1.setX(0.25 * needleWidth); needleMoveTo1.setY(0.025423728813559324 * needleHeight); needleCubicCurveTo2.setControlX1(0.25 * needleWidth); needleCubicCurveTo2.setControlY1(0.00847457627118644 * needleHeight); needleCubicCurveTo2.setControlX2(0.375 * needleWidth); needleCubicCurveTo2.setControlY2(0); needleCubicCurveTo2.setX(0.5 * needleWidth); needleCubicCurveTo2.setY(0); needleCubicCurveTo3.setControlX1(0.625 * needleWidth); needleCubicCurveTo3.setControlY1(0); needleCubicCurveTo3.setControlX2(0.75 * needleWidth); needleCubicCurveTo3.setControlY2(0.00847457627118644 * needleHeight); needleCubicCurveTo3.setX(0.75 * needleWidth); needleCubicCurveTo3.setY(0.025423728813559324 * needleHeight); needleCubicCurveTo4.setControlX1(0.75 * needleWidth); needleCubicCurveTo4.setControlY1(0.025423728813559324 * needleHeight); needleCubicCurveTo4.setControlX2(needleWidth); needleCubicCurveTo4.setControlY2(needleHeight); needleCubicCurveTo4.setX(needleWidth); needleCubicCurveTo4.setY(needleHeight); needleLineTo5.setX(0); needleLineTo5.setY(needleHeight); needleCubicCurveTo6.setControlX1(0); needleCubicCurveTo6.setControlY1(needleHeight); needleCubicCurveTo6.setControlX2(0.25 * needleWidth); needleCubicCurveTo6.setControlY2(0.025423728813559324 * needleHeight); needleCubicCurveTo6.setX(0.25 * needleWidth); needleCubicCurveTo6.setY(0.025423728813559324 * needleHeight); needle.setCache(true); needle.setCacheHint(CacheHint.ROTATE); LinearGradient needleGradient = new LinearGradient(needle.getLayoutBounds().getMinX(), 0, needle.getLayoutBounds().getMaxX(), 0, false, CycleMethod.NO_CYCLE, new Stop(0.0, gauge.getNeedleColor()), new Stop(0.5, gauge.getNeedleColor()), new Stop(0.5, gauge.getNeedleColor().brighter().brighter()), new Stop(1.0, gauge.getNeedleColor().brighter().brighter())); needle.setFill(needleGradient); needle.setStrokeWidth(0); needle.setStroke(Color.TRANSPARENT); needle.relocate((width - needle.getLayoutBounds().getWidth()) * 0.5, height * 0.77 - needle.getLayoutBounds().getHeight()); needleRotate.setPivotX(needle.getLayoutBounds().getWidth() * 0.5); needleRotate.setPivotY(needle.getLayoutBounds().getHeight()); lightEffect.setRadius(Helper.clamp(0.5, 1.5, 0.00769231 * height)); lightEffect.setOffsetY(Helper.clamp(0.5, 1.5, 0.00769231 * height)); foreground.setScaleX(width / PREFERRED_WIDTH); foreground.setScaleY(height / PREFERRED_HEIGHT); foreground.setTranslateX((width - PREFERRED_WIDTH) * 0.5); foreground.setTranslateY((height - PREFERRED_HEIGHT) * 0.5); resizeText(); } } @Override protected void redraw() { locale = gauge.getLocale(); formatString = new StringBuilder("%.").append(Integer.toString(gauge.getDecimals())).append("f").toString(); Color backgroundColor = gauge.getBackgroundPaint() instanceof Color ? (Color) gauge.getBackgroundPaint() : Color.WHITE; ticksAndSectionsCanvas.setCache(false); ticksAndSections.clearRect(0, 0, width, height); ticksAndSections.setFill(new LinearGradient(0, 0, 0, height, false, CycleMethod.NO_CYCLE, new Stop(0.0, Color.TRANSPARENT), new Stop(0.07692308, backgroundColor.deriveColor(0.0, 1.0, 0.706, 1.0)), new Stop(0.08461538, backgroundColor.deriveColor(0.0, 1.0, 0.921, 1.0)), new Stop(0.56923077, backgroundColor), new Stop(0.579, backgroundColor.deriveColor(0.0, 1.0, 0.706, 1.0)), new Stop(1.0, Color.TRANSPARENT))); ticksAndSections.fillRect(0, 0, width, height); if (gauge.getSectionsVisible()) drawSections(ticksAndSections); drawTickMarks(ticksAndSections); ticksAndSectionsCanvas.setCache(true); ticksAndSectionsCanvas.setCacheHint(CacheHint.QUALITY); titleText.setFill(gauge.getTitleColor()); titleText.setText(gauge.getTitle()); unitText.setFill(gauge.getUnitColor()); unitText.setText(gauge.getUnit()); if (gauge.isLcdVisible()) { LcdDesign lcdDesign = gauge.getLcdDesign(); Color[] lcdColors = lcdDesign.getColors(); LinearGradient lcdGradient = new LinearGradient(0, 1, 0, lcd.getHeight() - 1, false, CycleMethod.NO_CYCLE, new Stop(0, lcdColors[0]), new Stop(0.03, lcdColors[1]), new Stop(0.5, lcdColors[2]), new Stop(0.5, lcdColors[3]), new Stop(1.0, lcdColors[4])); Paint lcdFramePaint; if (lcdDesign.name().startsWith("FLAT")) { lcdFramePaint = Color.WHITE; } else { lcdFramePaint = new LinearGradient(0, 0, 0, lcd.getHeight(), false, CycleMethod.NO_CYCLE, new Stop(0.0, Color.rgb(26, 26, 26)), new Stop(0.01, Color.rgb(77, 77, 77)), new Stop(0.99, Color.rgb(77, 77, 77)), new Stop(1.0, Color.rgb(221, 221, 221))); } lcd.setFill(lcdGradient); lcd.setStroke(lcdFramePaint); lcdText.setTextFill(lcdColors[5]); } if (gauge.isLedVisible()) drawLed(led); shadowGroup.setEffect(gauge.isShadowsEnabled() ? dropShadow : null); foreground.setFill(gauge.getForegroundPaint()); // Markers drawMarkers(); thresholdTooltip.setText("Threshold\n(" + String.format(locale, formatString, gauge.getThreshold()) + ")"); } }