eu.hansolo.medusa.tools.Helper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of Medusa Show documentation
Show all versions of Medusa Show documentation
Medusa is a JavaFX 8 library containing gauges and clocks
/*
* 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.tools;
import eu.hansolo.medusa.Alarm;
import eu.hansolo.medusa.Clock;
import eu.hansolo.medusa.Fonts;
import eu.hansolo.medusa.Gauge;
import eu.hansolo.medusa.Gauge.ScaleDirection;
import eu.hansolo.medusa.Gauge.SkinType;
import eu.hansolo.medusa.Section;
import eu.hansolo.medusa.TickLabelLocation;
import eu.hansolo.medusa.TickLabelOrientation;
import eu.hansolo.medusa.TickMarkType;
import eu.hansolo.medusa.TimeSection;
import java.math.BigDecimal;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.ImagePattern;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Circle;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
/**
* Created by hansolo on 11.12.15.
*/
public class Helper {
public static final double MAX_TICK_MARK_LENGTH = 0.125;
public static final double MAX_TICK_MARK_WIDTH = 0.02;
public static final Color INACTIVE_ALARM_COLOR = Color.rgb(90, 90, 90, 0.5);
public static final T clamp(final T MIN, final T MAX, final T VALUE) {
if (VALUE.doubleValue() < MIN.doubleValue()) return MIN;
if (VALUE.doubleValue() > MAX.doubleValue()) return MAX;
return VALUE;
}
public static final int clamp(final int MIN, final int MAX, final int VALUE) {
if (VALUE < MIN) return MIN;
if (VALUE > MAX) return MAX;
return VALUE;
}
public static final long clamp(final long MIN, final long MAX, final long VALUE) {
if (VALUE < MIN) return MIN;
if (VALUE > MAX) return MAX;
return VALUE;
}
public static final double clamp(final double MIN, final double MAX, final double VALUE) {
if (Double.compare(VALUE, MIN) < 0) return MIN;
if (Double.compare(VALUE, MAX) > 0) return MAX;
return VALUE;
}
public static final double[] calcAutoScale(final double MIN_VALUE, final double MAX_VALUE) {
double maxNoOfMajorTicks = 10;
double maxNoOfMinorTicks = 10;
double niceMinValue;
double niceMaxValue;
double niceRange;
double majorTickSpace;
double minorTickSpace;
niceRange = (calcNiceNumber((MAX_VALUE - MIN_VALUE), false));
majorTickSpace = calcNiceNumber(niceRange / (maxNoOfMajorTicks - 1), true);
niceMinValue = (Math.floor(MIN_VALUE / majorTickSpace) * majorTickSpace);
niceMaxValue = (Math.ceil(MAX_VALUE / majorTickSpace) * majorTickSpace);
minorTickSpace = calcNiceNumber(majorTickSpace / (maxNoOfMinorTicks - 1), true);
return new double[]{ niceMinValue, niceMaxValue, majorTickSpace, minorTickSpace };
}
/**
* Calculates nice minValue, maxValue and stepSize for given MIN and MAX values
* @param MIN
* @param MAX
* @return array of doubles with [niceMin, niceMax, niceRange, niceStep]
*/
public static final double[] getNiceScale(final double MIN, final double MAX) {
return getNiceScale(MIN, MAX, 20);
}
/**
* Calculates nice minValue, maxValue and stepSize for given MIN and MAX values
* @param MIN
* @param MAX
* @param MAX_NO_OF_TICKS
* @return array of doubles with [niceMin, niceMax, niceRange, niceStep]
*/
public static final double[] getNiceScale(final double MIN, final double MAX, final int MAX_NO_OF_TICKS) {
// Minimal increment to avoid round extreme values to be on the edge of the chart
double minimum = MIN;
double maximum = MAX;
double epsilon = (MAX - MIN) / 1e6;
maximum += epsilon;
minimum -= epsilon;
double range = maximum - minimum;
// Target number of values to be displayed on the Y axis (it may be less)
int stepCount = MAX_NO_OF_TICKS;
// First approximation
double roughStep = range / (stepCount - 1);
// Set best niceStep for the range
//double[] goodNormalizedSteps = { 1, 1.5, 2, 2.5, 5, 7.5, 10 }; // keep the 10 at the end
double[] goodNormalizedSteps = { 1, 2, 5, 10 };
// Normalize rough niceStep to find the normalized one that fits best
double stepPower = Math.pow(10, -Math.floor(Math.log10(Math.abs(roughStep))));
double normalizedStep = roughStep * stepPower;
double goodNormalizedStep = Arrays.stream(goodNormalizedSteps).filter(n -> Double.compare(n, normalizedStep) >= 0).findFirst().getAsDouble();
double niceStep = goodNormalizedStep / stepPower;
// Determine the scale limits based on the chosen niceStep.
double niceMin = minimum < 0 ? Math.floor(minimum / niceStep) * niceStep : Math.ceil(minimum / niceStep) * niceStep;
double niceMax = maximum < 0 ? Math.floor(maximum / niceStep) * niceStep : Math.ceil(maximum / niceStep) * niceStep;
if (MIN % niceStep == 0) { niceMin = MIN; }
if (MAX % niceStep == 0) { niceMax = MAX; }
double niceRange = niceMax - niceMin;
return new double[] { niceMin, niceMax, niceRange, niceStep };
}
/**
* Returns a "niceScaling" number approximately equal to the range.
* Rounds the number if ROUND == true.
* Takes the ceiling if ROUND = false.
*
* @param RANGE the value range (maxValue - minValue)
* @param ROUND whether to round the result or ceil
* @return a "niceScaling" number to be used for the value range
*/
public static final double calcNiceNumber(final double RANGE, final boolean ROUND) {
double niceFraction;
double exponent = Math.floor(Math.log10(RANGE)); // exponent of range
double fraction = RANGE / Math.pow(10, exponent); // fractional part of range
if (ROUND) {
if (Double.compare(fraction, 1.5) < 0) {
niceFraction = 1;
} else if (Double.compare(fraction, 3) < 0) {
niceFraction = 2;
} else if (Double.compare(fraction, 7) < 0) {
niceFraction = 5;
} else {
niceFraction = 10;
}
} else {
if (Double.compare(fraction, 1) <= 0) {
niceFraction = 1;
} else if (Double.compare(fraction, 2) <= 0) {
niceFraction = 2;
} else if (Double.compare(fraction, 5) <= 0) {
niceFraction = 5;
} else {
niceFraction = 10;
}
}
return niceFraction * Math.pow(10, exponent);
}
public static final Color getColorOfSection(final List SECTIONS, final double VALUE, final Color DEFAULT_COLOR) {
for (Section section : SECTIONS) {
if (section.contains(VALUE)) return section.getColor();
}
return DEFAULT_COLOR;
}
public static final void rotateContextForText(final GraphicsContext CTX, final double START_ANGLE, final double ANGLE, final TickLabelOrientation ORIENTATION) {
switch (ORIENTATION) {
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;
}
}
public static final void adjustTextSize(final Text TEXT, final double MAX_WIDTH, final double FONT_SIZE) {
final String FONT_NAME = TEXT.getFont().getName();
double adjustableFontSize = FONT_SIZE;
while (TEXT.getBoundsInLocal().getWidth() > MAX_WIDTH && adjustableFontSize > 0) {
adjustableFontSize -= 0.05;
TEXT.setFont(new Font(FONT_NAME, adjustableFontSize));
}
}
public static final void adjustTextSize(final Label TEXT, final double MAX_WIDTH, final double FONT_SIZE) {
final String FONT_NAME = TEXT.getFont().getName();
double adjustableFontSize = FONT_SIZE;
while (TEXT.getBoundsInLocal().getWidth() > MAX_WIDTH && adjustableFontSize > 0) {
adjustableFontSize -= 0.05;
TEXT.setFont(new Font(FONT_NAME, adjustableFontSize));
}
}
public static DateTimeFormatter getDateFormat(final Locale LOCALE) {
if (Locale.US == LOCALE) {
return DateTimeFormatter.ofPattern("MM/dd/YYYY");
} else if (Locale.CHINA == LOCALE) {
return DateTimeFormatter.ofPattern("YYYY.MM.dd");
} else {
return DateTimeFormatter.ofPattern("dd.MM.YYYY");
}
}
public static DateTimeFormatter getLocalizedDateFormat(final Locale LOCALE) {
return DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(LOCALE);
}
public static void enableNode(final Node NODE, final boolean ENABLE) {
NODE.setManaged(ENABLE);
NODE.setVisible(ENABLE);
}
public static final String colorToCss(final Color COLOR) {
return COLOR.toString().replace("0x", "#");
}
public static final ThreadFactory getThreadFactory(final String THREAD_NAME, final boolean IS_DAEMON) {
return runnable -> {
Thread thread = new Thread(runnable, THREAD_NAME);
thread.setDaemon(IS_DAEMON);
return thread;
};
}
public static final void stopTask(ScheduledFuture> task) {
if (null == task) return;
task.cancel(true);
task = null;
}
public static final ImagePattern createCarbonPattern() {
final double SIZE = 12;
final Canvas CANVAS = new Canvas(SIZE, SIZE);
final GraphicsContext CTX = CANVAS.getGraphicsContext2D();
CTX.setFill(new LinearGradient(0, 0, 0, 0.5 * SIZE,
false, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(35, 35, 35)),
new Stop(1, Color.rgb(23, 23, 23))));
CTX.fillRect(0, 0, SIZE * 0.5, SIZE * 0.5);
CTX.setFill(new LinearGradient(0, 0, 0, 0.416666 * SIZE,
false, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(38, 38, 38)),
new Stop(1, Color.rgb(30, 30, 30))));
CTX.fillRect(SIZE * 0.083333, 0, SIZE * 0.333333, SIZE * 0.416666);
CTX.setFill(new LinearGradient(0, 0.5 * SIZE, 0, SIZE,
false, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(35, 35, 35)),
new Stop(1, Color.rgb(23, 23, 23))));
CTX.fillRect(SIZE * 0.5, SIZE * 0.5, SIZE * 0.5, SIZE * 0.5);
CTX.setFill(new LinearGradient(0, 0.5 * SIZE, 0, 0.916666 * SIZE,
false, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(38, 38, 38)),
new Stop(1, Color.rgb(30, 30, 30))));
CTX.fillRect(SIZE * 0.583333, SIZE * 0.5, SIZE * 0.333333, SIZE * 0.416666);
CTX.setFill(new LinearGradient(0, 0, 0, 0.5 * SIZE,
false, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(48, 48, 48)),
new Stop(1, Color.rgb(40, 40, 40))));
CTX.fillRect(SIZE * 0.5, 0, SIZE * 0.5, SIZE * 0.5);
CTX.setFill(new LinearGradient(0, 0.083333 * SIZE, 0, 0.5 * SIZE,
false, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(53, 53, 53)),
new Stop(1, Color.rgb(45, 45, 45))));
CTX.fillRect(SIZE * 0.583333, SIZE * 0.083333, SIZE * 0.333333, SIZE * 0.416666);
CTX.setFill(new LinearGradient(0, 0.5 * SIZE, 0, SIZE,
false, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(48, 48, 48)),
new Stop(1, Color.rgb(40, 40, 40))));
CTX.fillRect(0, SIZE * 0.5, SIZE * 0.5, SIZE * 0.5);
CTX.setFill(new LinearGradient(0, 0.583333 * SIZE, 0, SIZE,
false, CycleMethod.NO_CYCLE,
new Stop(0, Color.rgb(53, 53, 53)),
new Stop(1, Color.rgb(45, 45, 45))));
CTX.fillRect(SIZE * 0.083333, SIZE * 0.583333, SIZE * 0.333333, SIZE * 0.416666);
final Image PATTERN_IMAGE = CANVAS.snapshot(new SnapshotParameters(), null);
final ImagePattern PATTERN = new ImagePattern(PATTERN_IMAGE, 0, 0, SIZE, SIZE, false);
return PATTERN;
}
public static void drawTrapezoid(final GraphicsContext CTX,
final double PI1X, final double PI1Y, final double PI2X, final double PI2Y,
final double PO1X, final double PO1Y, final double PO2X, final double PO2Y) {
CTX.beginPath();
CTX.moveTo(PI2X, PI2Y);
CTX.lineTo(PI1X, PI1Y);
CTX.lineTo(PO1X, PO1Y);
CTX.lineTo(PO2X, PO2Y);
CTX.closePath();
CTX.fill();
}
public static void drawTriangle(final GraphicsContext CTX,
final double PIX, final double PIY, final double PO1X, final double PO1Y, final double PO2X, final double PO2Y) {
CTX.beginPath();
CTX.moveTo(PIX, PIY);
CTX.lineTo(PO1X, PO1Y);
CTX.lineTo(PO2X, PO2Y);
CTX.closePath();
CTX.fill();
}
public static void drawDot(final GraphicsContext CTX, final double CENTER_X, final double CENTER_Y, final double SIZE) {
CTX.fillOval(CENTER_X, CENTER_Y, SIZE, SIZE);
}
public static void drawLine(final GraphicsContext CTX, final double P1X, final double P1Y, final double P2X, final double P2Y) {
CTX.strokeLine(P1X, P1Y, P2X, P2Y);
}
public static boolean isMonochrome(final Color COLOR) {
return Double.compare(COLOR.getRed(), COLOR.getGreen()) == 0 && Double.compare(COLOR.getGreen(), COLOR.getBlue()) == 0;
}
public static double colorDistance(final Color COLOR_1, final Color COLOR_2) {
final double DELTA_R = (COLOR_2.getRed() - COLOR_1.getRed());
final double DELTA_G = (COLOR_2.getGreen() - COLOR_1.getGreen());
final double DELTA_B = (COLOR_2.getBlue() - COLOR_1.getBlue());
return Math.sqrt(DELTA_R * DELTA_R + DELTA_G * DELTA_G + DELTA_B * DELTA_B);
}
public static boolean isBright(final Color COLOR) { return !isDark(COLOR); }
public static boolean isDark(final Color COLOR) {
final double DISTANCE_TO_WHITE = colorDistance(COLOR, Color.WHITE);
final double DISTANCE_TO_BLACK = colorDistance(COLOR, Color.BLACK);
return DISTANCE_TO_BLACK < DISTANCE_TO_WHITE;
}
public static Color getTranslucentColorFrom(final Color COLOR, final double FACTOR) {
return Color.color(COLOR.getRed(), COLOR.getGreen(), COLOR.getBlue(), Helper.clamp(0.0, 1.0, FACTOR));
}
public static void drawRadialTickMarks(final Gauge GAUGE, final GraphicsContext CTX,
final double MIN_VALUE, final double MAX_VALUE,
final double START_ANGLE, final double ANGLE_RANGE, final double ANGLE_STEP,
final double CENTER_X, final double CENTER_Y, final double SIZE) {
double sinValue;
double cosValue;
double centerX = CENTER_X;
double centerY = CENTER_Y;
int tickLabelDecimals = GAUGE.getTickLabelDecimals();
String tickLabelFormatString = "%." + tickLabelDecimals + "f";
double minorTickSpace = GAUGE.getMinorTickSpace();
double tmpAngleStep = ANGLE_STEP * minorTickSpace;
TickLabelOrientation tickLabelOrientation = GAUGE.getTickLabelOrientation();
TickLabelLocation tickLabelLocation = GAUGE.getTickLabelLocation();
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(MIN_VALUE);
double counter = MIN_VALUE;
List tickMarkSections = GAUGE.getTickMarkSections();
List tickLabelSections = GAUGE.getTickLabelSections();
Color tickMarkColor = GAUGE.getTickMarkColor();
Color majorTickMarkColor = GAUGE.getMajorTickMarkColor().equals(tickMarkColor) ? tickMarkColor : GAUGE.getMajorTickMarkColor();
Color mediumTickMarkColor = GAUGE.getMediumTickMarkColor().equals(tickMarkColor) ? tickMarkColor : GAUGE.getMediumTickMarkColor();
Color minorTickMarkColor = GAUGE.getMinorTickMarkColor().equals(tickMarkColor) ? tickMarkColor : GAUGE.getMinorTickMarkColor();
double majorTickMarkLengthFactor = GAUGE.getMajorTickMarkLengthFactor();
double majorTickMarkWidthFactor = GAUGE.getMajorTickMarkWidthFactor();
double mediumTickMarkLengthFactor = GAUGE.getMediumTickMarkLengthFactor();
double mediumTickMarkWidthFactor = GAUGE.getMediumTickMarkWidthFactor();
double minorTickMarkLengthFactor = GAUGE.getMinorTickMarkLengthFactor();
double minorTickMarkWidthFactor = GAUGE.getMinorTickMarkWidthFactor();
Color tickLabelColor = GAUGE.getTickLabelColor();
Color zeroColor = GAUGE.getZeroColor();
boolean isNotZero = true;
TickMarkType majorTickMarkType = GAUGE.getMajorTickMarkType();
TickMarkType mediumTickMarkType = GAUGE.getMediumTickMarkType();
TickMarkType minorTickMarkType = GAUGE.getMinorTickMarkType();
boolean tickMarkSectionsVisible = GAUGE.getTickMarkSectionsVisible();
boolean tickLabelSectionsVisible = GAUGE.getTickLabelSectionsVisible();
boolean majorTickMarksVisible = GAUGE.getMajorTickMarksVisible();
boolean mediumTickMarksVisible = GAUGE.getMediumTickMarksVisible();
boolean minorTickMarksVisible = GAUGE.getMinorTickMarksVisible();
boolean tickLabelsVisible = GAUGE.getTickLabelsVisible();
boolean onlyFirstAndLastLabelVisible = GAUGE.isOnlyFirstAndLastTickLabelVisible();
boolean customTickLabelsEnabled = GAUGE.getCustomTickLabelsEnabled();
Locale locale = GAUGE.getLocale();
List customTickLabels = customTickLabelsEnabled ? GAUGE.getCustomTickLabels() : null;
double textDisplacementFactor = majorTickMarkType == TickMarkType.DOT ? (TickLabelLocation.OUTSIDE == tickLabelLocation ? 0.95 : 1.05) : 1.0;
double majorDotSize;
double majorHalfDotSize;
double mediumDotSize;
double mediumHalfDotSize;
double minorDotSize;
double minorHalfDotSize;
double orthTextFactor;
if (TickLabelLocation.OUTSIDE == tickLabelLocation) {
orthTextFactor = 0.45 * textDisplacementFactor;//TickLabelOrientation.ORTHOGONAL == tickLabelOrientation ? 0.45 * textDisplacementFactor : 0.45 * textDisplacementFactor;
majorDotSize = 0.02 * SIZE;
majorHalfDotSize = majorDotSize * 0.5;
mediumDotSize = 0.01375 * SIZE;
mediumHalfDotSize = mediumDotSize * 0.5;
minorDotSize = 0.0075 * SIZE;
minorHalfDotSize = minorDotSize * 0.5;
} else {
orthTextFactor = TickLabelOrientation.ORTHOGONAL == tickLabelOrientation ? 0.38 * textDisplacementFactor : 0.37 * textDisplacementFactor;
majorDotSize = 0.025 * SIZE;
majorHalfDotSize = majorDotSize * 0.5;
mediumDotSize = 0.01875 * SIZE;
mediumHalfDotSize = mediumDotSize * 0.5;
minorDotSize = 0.0125 * SIZE;
minorHalfDotSize = minorDotSize * 0.5;
};
double customFontSizeFactor = GAUGE.getCustomTickLabelFontSize() / 400;
boolean fullRange = (MIN_VALUE < 0 && MAX_VALUE > 0);
double tickLabelFontSize = tickLabelDecimals == 0 ? 0.054 * SIZE : 0.051 * SIZE;
tickLabelFontSize = GAUGE.getCustomTickLabelsEnabled() ? customFontSizeFactor * SIZE : tickLabelFontSize;
double tickMarkFontSize = tickLabelDecimals == 0 ? 0.047 * SIZE: 0.044 * SIZE;
double tickLabelOrientationFactor = TickLabelOrientation.HORIZONTAL == tickLabelOrientation ? 0.9 : 1.0;
Font tickLabelFont = Fonts.robotoCondensedRegular(tickLabelFontSize * tickLabelOrientationFactor);
Font tickMarkFont = Fonts.robotoCondensedRegular(tickMarkFontSize * tickLabelOrientationFactor);
Font tickLabelZeroFont = fullRange ? Fonts.robotoCondensedBold(tickLabelFontSize * tickLabelOrientationFactor) : tickLabelFont;
Font tickMarkZeroFont = fullRange ? Fonts.robotoCondensedBold(tickMarkFontSize * tickLabelOrientationFactor) : tickMarkFont;
// Variables needed for tickmarks
double innerPointX;
double innerPointY;
double innerMediumPointX;
double innerMediumPointY;
double innerMinorPointX;
double innerMinorPointY;
double outerPointX;
double outerPointY;
double outerMediumPointX;
double outerMediumPointY;
double outerMinorPointX;
double outerMinorPointY;
double textPointX;
double textPointY;
double dotCenterX;
double dotCenterY;
double dotMediumCenterX;
double dotMediumCenterY;
double dotMinorCenterX;
double dotMinorCenterY;
double tickLabelTickMarkX;
double tickLabelTickMarkY;
double trapezoidMajorInnerAngle1;
double trapezoidMajorInnerAngle2;
double trapezoidMajorOuterAngle1;
double trapezoidMajorOuterAngle2;
double trapezoidMajorInnerPoint1X;
double trapezoidMajorInnerPoint1Y;
double trapezoidMajorInnerPoint2X;
double trapezoidMajorInnerPoint2Y;
double trapezoidMajorOuterPoint1X;
double trapezoidMajorOuterPoint1Y;
double trapezoidMajorOuterPoint2X;
double trapezoidMajorOuterPoint2Y;
double trapezoidMediumInnerAngle1;
double trapezoidMediumInnerAngle2;
double trapezoidMediumOuterAngle1;
double trapezoidMediumOuterAngle2;
double trapezoidMediumInnerPoint1X;
double trapezoidMediumInnerPoint1Y;
double trapezoidMediumInnerPoint2X;
double trapezoidMediumInnerPoint2Y;
double trapezoidMediumOuterPoint1X;
double trapezoidMediumOuterPoint1Y;
double trapezoidMediumOuterPoint2X;
double trapezoidMediumOuterPoint2Y;
double trapezoidMinorInnerAngle1;
double trapezoidMinorInnerAngle2;
double trapezoidMinorOuterAngle1;
double trapezoidMinorOuterAngle2;
double trapezoidMinorInnerPoint1X;
double trapezoidMinorInnerPoint1Y;
double trapezoidMinorInnerPoint2X;
double trapezoidMinorInnerPoint2Y;
double trapezoidMinorOuterPoint1X;
double trapezoidMinorOuterPoint1Y;
double trapezoidMinorOuterPoint2X;
double trapezoidMinorOuterPoint2Y;
double triangleMajorInnerPointX;
double triangleMajorInnerPointY;
double triangleMajorOuterPointX;
double triangleMajorOuterPointY;
double triangleMediumInnerPointX;
double triangleMediumInnerPointY;
double triangleMediumOuterPointX;
double triangleMediumOuterPointY;
double triangleMinorInnerPointX;
double triangleMinorInnerPointY;
double triangleMinorOuterPointX;
double triangleMinorOuterPointY;
ScaleDirection scaleDirection = GAUGE.getScaleDirection();
// Draw tickmark ring
if (GAUGE.isTickMarkRingVisible()) {
SkinType skinType = GAUGE.getSkinType();
double xy = TickLabelLocation.INSIDE == tickLabelLocation ? SIZE * 0.026 : SIZE * 0.14;
double wh = TickLabelLocation.INSIDE == tickLabelLocation ? SIZE * 0.948 : SIZE * 0.72;
double offset = -90 + START_ANGLE;
double horVerOffset = SIZE * 0.055555555;
CTX.setLineWidth(SIZE * 0.004);
CTX.setLineCap(StrokeLineCap.SQUARE);
CTX.save();
CTX.setStroke(tickMarkColor);
switch(skinType) {
case HORIZONTAL: CTX.strokeArc(xy + horVerOffset, xy, wh, wh, offset, -ANGLE_RANGE, ArcType.OPEN); break;
case VERTICAL : CTX.strokeArc(xy, xy + horVerOffset, wh, wh, offset, -ANGLE_RANGE, ArcType.OPEN); break;
default : CTX.strokeArc(xy, xy, wh, wh, offset, -ANGLE_RANGE, ArcType.OPEN); break;
}
CTX.restore();
if (tickMarkSections.size() > 0) {
int listSize = tickMarkSections.size();
double sectionStartAngle;
for (int i = 0; i < listSize; i++) {
Section section = tickMarkSections.get(i);
if (Double.compare(section.getStart(), MIN_VALUE) < 0 && Double.compare(section.getStop(), MAX_VALUE) < 0) {
sectionStartAngle = 0;
} else {
sectionStartAngle = ScaleDirection.CLOCKWISE == scaleDirection ? (section.getStart() - MIN_VALUE) * ANGLE_STEP : -(section.getStart() - MIN_VALUE) * ANGLE_STEP;
}
double sectionAngleExtend;
if (Double.compare(section.getStop(), MAX_VALUE) > 0) {
sectionAngleExtend = ScaleDirection.CLOCKWISE == scaleDirection ? (MAX_VALUE - section.getStart()) * ANGLE_STEP : -(MAX_VALUE - section.getStart()) * ANGLE_STEP;
} else {
sectionAngleExtend = ScaleDirection.CLOCKWISE == scaleDirection ? (section.getStop() - section.getStart()) * ANGLE_STEP : -(section.getStop() - section.getStart()) * ANGLE_STEP;
}
CTX.save();
CTX.setStroke(section.getColor());
switch(skinType) {
case HORIZONTAL: CTX.strokeArc(xy + horVerOffset, xy, wh, wh, offset - sectionStartAngle, -sectionAngleExtend, ArcType.OPEN); break;
case VERTICAL : CTX.strokeArc(xy, xy + horVerOffset, wh, wh, offset - sectionStartAngle, -sectionAngleExtend, ArcType.OPEN); break;
default : CTX.strokeArc(xy, xy, wh, wh, offset - sectionStartAngle, -sectionAngleExtend, ArcType.OPEN); break;
}
CTX.restore();
}
}
}
// Main loop
BigDecimal tmpStepBD = new BigDecimal(tmpAngleStep);
tmpStepBD = tmpStepBD.setScale(3, BigDecimal.ROUND_HALF_UP);
double tmpStep = tmpStepBD.doubleValue();
double angle = 0;
int customTickLabelCounter = 0;
for (double i = 0 ; Double.compare(-ANGLE_RANGE - tmpStep, i) <= 0 ; i -= tmpStep) {
sinValue = Math.sin(Math.toRadians(angle + START_ANGLE));
cosValue = Math.cos(Math.toRadians(angle + START_ANGLE));
switch(tickLabelLocation) {
case OUTSIDE:
innerPointX = centerX + SIZE * 0.3585 * sinValue;
innerPointY = centerY + SIZE * 0.3585 * cosValue;
innerMediumPointX = innerPointX;
innerMediumPointY = innerPointY;
innerMinorPointX = innerPointX;
innerMinorPointY = innerPointY;
outerPointX = centerX + SIZE * (0.3585 + MAX_TICK_MARK_LENGTH * majorTickMarkLengthFactor) * sinValue;
outerPointY = centerY + SIZE * (0.3585 + MAX_TICK_MARK_LENGTH * majorTickMarkLengthFactor) * cosValue;
outerMediumPointX = centerX + SIZE * (0.3585 + MAX_TICK_MARK_LENGTH * mediumTickMarkLengthFactor) * sinValue;
outerMediumPointY = centerY + SIZE * (0.3585 + MAX_TICK_MARK_LENGTH * mediumTickMarkLengthFactor) * cosValue;
outerMinorPointX = centerX + SIZE * (0.3585 + MAX_TICK_MARK_LENGTH * minorTickMarkLengthFactor) * sinValue;
outerMinorPointY = centerY + SIZE * (0.3585 + MAX_TICK_MARK_LENGTH * minorTickMarkLengthFactor) * cosValue;
textPointX = centerX + SIZE * orthTextFactor * sinValue;
textPointY = centerY + SIZE * orthTextFactor * cosValue;
dotCenterX = centerX + SIZE * 0.3685 * sinValue;
dotCenterY = centerY + SIZE * 0.3685 * cosValue;
dotMediumCenterX = centerX + SIZE * 0.365375 * sinValue;
dotMediumCenterY = centerY + SIZE * 0.365375 * cosValue;
dotMinorCenterX = centerX + SIZE * 0.36225 * sinValue;
dotMinorCenterY = centerY + SIZE * 0.36225 * cosValue;
tickLabelTickMarkX = centerX + SIZE * 0.3805 * sinValue;
tickLabelTickMarkY = centerY + SIZE * 0.3805 * cosValue;
trapezoidMajorInnerAngle1 = Math.toRadians(angle - 1.2 + START_ANGLE);
trapezoidMajorInnerAngle2 = Math.toRadians(angle + 1.2 + START_ANGLE);
trapezoidMajorOuterAngle1 = Math.toRadians(angle - 0.8 + START_ANGLE);
trapezoidMajorOuterAngle2 = Math.toRadians(angle + 0.8 + START_ANGLE);
trapezoidMajorInnerPoint1X = centerX + SIZE * 0.3585 * Math.sin(trapezoidMajorInnerAngle1);
trapezoidMajorInnerPoint1Y = centerY + SIZE * 0.3585 * Math.cos(trapezoidMajorInnerAngle1);
trapezoidMajorInnerPoint2X = centerX + SIZE * 0.3585 * Math.sin(trapezoidMajorInnerAngle2);
trapezoidMajorInnerPoint2Y = centerY + SIZE * 0.3585 * Math.cos(trapezoidMajorInnerAngle2);
trapezoidMajorOuterPoint1X = centerX + SIZE * 0.4105 * Math.sin(trapezoidMajorOuterAngle1);
trapezoidMajorOuterPoint1Y = centerY + SIZE * 0.4105 * Math.cos(trapezoidMajorOuterAngle1);
trapezoidMajorOuterPoint2X = centerX + SIZE * 0.4105 * Math.sin(trapezoidMajorOuterAngle2);
trapezoidMajorOuterPoint2Y = centerY + SIZE * 0.4105 * Math.cos(trapezoidMajorOuterAngle2);
trapezoidMediumInnerAngle1 = Math.toRadians(angle - 1.0 + START_ANGLE);
trapezoidMediumInnerAngle2 = Math.toRadians(angle + 1.0 + START_ANGLE);
trapezoidMediumOuterAngle1 = Math.toRadians(angle - 0.7 + START_ANGLE);
trapezoidMediumOuterAngle2 = Math.toRadians(angle + 0.7 + START_ANGLE);
trapezoidMediumInnerPoint1X = centerX + SIZE * 0.3585 * Math.sin(trapezoidMediumInnerAngle1);
trapezoidMediumInnerPoint1Y = centerY + SIZE * 0.3585 * Math.cos(trapezoidMediumInnerAngle1);
trapezoidMediumInnerPoint2X = centerX + SIZE * 0.3585 * Math.sin(trapezoidMediumInnerAngle2);
trapezoidMediumInnerPoint2Y = centerY + SIZE * 0.3585 * Math.cos(trapezoidMediumInnerAngle2);
trapezoidMediumOuterPoint1X = centerX + SIZE * 0.3985 * Math.sin(trapezoidMajorOuterAngle1);
trapezoidMediumOuterPoint1Y = centerY + SIZE * 0.3985 * Math.cos(trapezoidMediumOuterAngle1);
trapezoidMediumOuterPoint2X = centerX + SIZE * 0.3985 * Math.sin(trapezoidMediumOuterAngle2);
trapezoidMediumOuterPoint2Y = centerY + SIZE * 0.3985 * Math.cos(trapezoidMediumOuterAngle2);
trapezoidMinorInnerAngle1 = Math.toRadians(angle - 0.8 + START_ANGLE);
trapezoidMinorInnerAngle2 = Math.toRadians(angle + 0.8 + START_ANGLE);
trapezoidMinorOuterAngle1 = Math.toRadians(angle - 0.6 + START_ANGLE);
trapezoidMinorOuterAngle2 = Math.toRadians(angle + 0.6 + START_ANGLE);
trapezoidMinorInnerPoint1X = centerX + SIZE * 0.3585 * Math.sin(trapezoidMinorInnerAngle1);
trapezoidMinorInnerPoint1Y = centerY + SIZE * 0.3585 * Math.cos(trapezoidMinorInnerAngle1);
trapezoidMinorInnerPoint2X = centerX + SIZE * 0.3585 * Math.sin(trapezoidMinorInnerAngle2);
trapezoidMinorInnerPoint2Y = centerY + SIZE * 0.3585 * Math.cos(trapezoidMinorInnerAngle2);
trapezoidMinorOuterPoint1X = centerX + SIZE * 0.3975 * Math.sin(trapezoidMinorOuterAngle1);
trapezoidMinorOuterPoint1Y = centerY + SIZE * 0.3975 * Math.cos(trapezoidMinorOuterAngle1);
trapezoidMinorOuterPoint2X = centerX + SIZE * 0.3975 * Math.sin(trapezoidMinorOuterAngle2);
trapezoidMinorOuterPoint2Y = centerY + SIZE * 0.3975 * Math.cos(trapezoidMinorOuterAngle2);
triangleMajorInnerPointX = centerX + SIZE * 0.3585 * sinValue;
triangleMajorInnerPointY = centerY + SIZE * 0.3585 * cosValue;
triangleMajorOuterPointX = centerX + SIZE * 0.4105 * sinValue;
triangleMajorOuterPointY = centerY + SIZE * 0.4105 * cosValue;
triangleMediumInnerPointX = triangleMajorInnerPointX;
triangleMediumInnerPointY = triangleMajorInnerPointY;
triangleMediumOuterPointX = centerX + SIZE * 0.4045 * sinValue;
triangleMediumOuterPointY = centerY + SIZE * 0.4045 * cosValue;
triangleMinorInnerPointX = triangleMajorInnerPointX;
triangleMinorInnerPointY = triangleMajorInnerPointY;
triangleMinorOuterPointX = centerX + SIZE * 0.3975 * sinValue;
triangleMinorOuterPointY = centerY + SIZE * 0.3975 * cosValue;
break;
case INSIDE:
default:
innerPointX = centerX + SIZE * (0.475 - MAX_TICK_MARK_LENGTH * majorTickMarkLengthFactor) * sinValue;
innerPointY = centerY + SIZE * (0.475 - MAX_TICK_MARK_LENGTH * majorTickMarkLengthFactor) * cosValue;
innerMediumPointX = centerX + SIZE * (0.475 - MAX_TICK_MARK_LENGTH * mediumTickMarkLengthFactor) * sinValue;
innerMediumPointY = centerY + SIZE * (0.475 - MAX_TICK_MARK_LENGTH * mediumTickMarkLengthFactor) * cosValue;
innerMinorPointX = centerX + SIZE * (0.475 - MAX_TICK_MARK_LENGTH * minorTickMarkLengthFactor) * sinValue;
innerMinorPointY = centerY + SIZE * (0.475 - MAX_TICK_MARK_LENGTH * minorTickMarkLengthFactor) * cosValue;
outerPointX = centerX + SIZE * 0.475 * sinValue;
outerPointY = centerY + SIZE * 0.475 * cosValue;
outerMediumPointX = outerPointX;
outerMediumPointY = outerPointY;
outerMinorPointX = outerPointX;
outerMinorPointY = outerPointY;
textPointX = centerX + SIZE * orthTextFactor * sinValue;
textPointY = centerY + SIZE * orthTextFactor * cosValue;
dotCenterX = centerX + SIZE * 0.4625 * sinValue;
dotCenterY = centerY + SIZE * 0.4625 * cosValue;
dotMediumCenterX = centerX + SIZE * 0.465625 * sinValue;
dotMediumCenterY = centerY + SIZE * 0.465625 * cosValue;
dotMinorCenterX = centerX + SIZE * 0.46875 * sinValue;
dotMinorCenterY = centerY + SIZE * 0.46875 * cosValue;
tickLabelTickMarkX = centerX + SIZE * 0.445 * sinValue;
tickLabelTickMarkY = centerY + SIZE * 0.445 * cosValue;
trapezoidMajorInnerAngle1 = Math.toRadians(angle - 0.8 + START_ANGLE);
trapezoidMajorInnerAngle2 = Math.toRadians(angle + 0.8 + START_ANGLE);
trapezoidMajorOuterAngle1 = Math.toRadians(angle - 1.2 + START_ANGLE);
trapezoidMajorOuterAngle2 = Math.toRadians(angle + 1.2 + START_ANGLE);
trapezoidMajorInnerPoint1X = centerX + SIZE * 0.423 * Math.sin(trapezoidMajorInnerAngle1);
trapezoidMajorInnerPoint1Y = centerY + SIZE * 0.423 * Math.cos(trapezoidMajorInnerAngle1);
trapezoidMajorInnerPoint2X = centerX + SIZE * 0.423 * Math.sin(trapezoidMajorInnerAngle2);
trapezoidMajorInnerPoint2Y = centerY + SIZE * 0.423 * Math.cos(trapezoidMajorInnerAngle2);
trapezoidMajorOuterPoint1X = centerX + SIZE * 0.475 * Math.sin(trapezoidMajorOuterAngle1);
trapezoidMajorOuterPoint1Y = centerY + SIZE * 0.475 * Math.cos(trapezoidMajorOuterAngle1);
trapezoidMajorOuterPoint2X = centerX + SIZE * 0.475 * Math.sin(trapezoidMajorOuterAngle2);
trapezoidMajorOuterPoint2Y = centerY + SIZE * 0.475 * Math.cos(trapezoidMajorOuterAngle2);
trapezoidMediumInnerAngle1 = Math.toRadians(angle - 0.7 + START_ANGLE);
trapezoidMediumInnerAngle2 = Math.toRadians(angle + 0.7 + START_ANGLE);
trapezoidMediumOuterAngle1 = Math.toRadians(angle - 1.0 + START_ANGLE);
trapezoidMediumOuterAngle2 = Math.toRadians(angle + 1.0 + START_ANGLE);
trapezoidMediumInnerPoint1X = centerX + SIZE * 0.435 * Math.sin(trapezoidMediumInnerAngle1);
trapezoidMediumInnerPoint1Y = centerY + SIZE * 0.435 * Math.cos(trapezoidMediumInnerAngle1);
trapezoidMediumInnerPoint2X = centerX + SIZE * 0.435 * Math.sin(trapezoidMediumInnerAngle2);
trapezoidMediumInnerPoint2Y = centerY + SIZE * 0.435 * Math.cos(trapezoidMediumInnerAngle2);
trapezoidMediumOuterPoint1X = centerX + SIZE * 0.475 * Math.sin(trapezoidMajorOuterAngle1);
trapezoidMediumOuterPoint1Y = centerY + SIZE * 0.475 * Math.cos(trapezoidMediumOuterAngle1);
trapezoidMediumOuterPoint2X = centerX + SIZE * 0.475 * Math.sin(trapezoidMediumOuterAngle2);
trapezoidMediumOuterPoint2Y = centerY + SIZE * 0.475 * Math.cos(trapezoidMediumOuterAngle2);
trapezoidMinorInnerAngle1 = Math.toRadians(angle - 0.6 + START_ANGLE);
trapezoidMinorInnerAngle2 = Math.toRadians(angle + 0.6 + START_ANGLE);
trapezoidMinorOuterAngle1 = Math.toRadians(angle - 0.8 + START_ANGLE);
trapezoidMinorOuterAngle2 = Math.toRadians(angle + 0.8 + START_ANGLE);
trapezoidMinorInnerPoint1X = centerX + SIZE * 0.440 * Math.sin(trapezoidMinorInnerAngle1);
trapezoidMinorInnerPoint1Y = centerY + SIZE * 0.440 * Math.cos(trapezoidMinorInnerAngle1);
trapezoidMinorInnerPoint2X = centerX + SIZE * 0.440 * Math.sin(trapezoidMinorInnerAngle2);
trapezoidMinorInnerPoint2Y = centerY + SIZE * 0.440 * Math.cos(trapezoidMinorInnerAngle2);
trapezoidMinorOuterPoint1X = centerX + SIZE * 0.475 * Math.sin(trapezoidMinorOuterAngle1);
trapezoidMinorOuterPoint1Y = centerY + SIZE * 0.475 * Math.cos(trapezoidMinorOuterAngle1);
trapezoidMinorOuterPoint2X = centerX + SIZE * 0.475 * Math.sin(trapezoidMinorOuterAngle2);
trapezoidMinorOuterPoint2Y = centerY + SIZE * 0.475 * Math.cos(trapezoidMinorOuterAngle2);
triangleMajorInnerPointX = centerX + SIZE * 0.423 * sinValue;
triangleMajorInnerPointY = centerY + SIZE * 0.423 * cosValue;
triangleMajorOuterPointX = centerX + SIZE * 0.475 * sinValue;
triangleMajorOuterPointY = centerY + SIZE * 0.475 * cosValue;
triangleMediumInnerPointX = centerX + SIZE * 0.43 * sinValue;
triangleMediumInnerPointY = centerY + SIZE * 0.43 * cosValue;
triangleMediumOuterPointX = triangleMajorOuterPointX;
triangleMediumOuterPointY = triangleMajorOuterPointY;
triangleMinorInnerPointX = centerX + SIZE * 0.436 * sinValue;
triangleMinorInnerPointY = centerY + SIZE * 0.436 * cosValue;
triangleMinorOuterPointX = triangleMajorOuterPointX;
triangleMinorOuterPointY = triangleMajorOuterPointY;
break;
}
// Set the general tickmark color
CTX.setStroke(tickMarkColor);
CTX.setFill(tickMarkColor);
CTX.setLineCap(StrokeLineCap.BUTT);
if (Double.compare(counterBD.remainder(majorTickSpaceBD).doubleValue(), 0.0) == 0) {
// Draw major tick mark
isNotZero = Double.compare(0.0, counter) != 0;
TickMarkType tickMarkType = null;
if (majorTickMarksVisible) {
tickMarkType = majorTickMarkType;
CTX.setFill(tickMarkSectionsVisible ? Helper.getColorOfSection(tickMarkSections, counter, majorTickMarkColor) : majorTickMarkColor);
CTX.setStroke(tickMarkSectionsVisible ? Helper.getColorOfSection(tickMarkSections, counter, majorTickMarkColor) : majorTickMarkColor);
CTX.setLineWidth(SIZE * (TickMarkType.BOX == tickMarkType || TickMarkType.PILL == tickMarkType ? 0.016 : MAX_TICK_MARK_WIDTH * majorTickMarkWidthFactor));
CTX.setLineCap(TickMarkType.PILL == tickMarkType ? StrokeLineCap.ROUND : StrokeLineCap.BUTT);
} else if (minorTickMarksVisible) {
tickMarkType = minorTickMarkType;
CTX.setFill(tickMarkSectionsVisible ? Helper.getColorOfSection(tickMarkSections, counter, minorTickMarkColor) : minorTickMarkColor);
CTX.setStroke(tickMarkSectionsVisible ? Helper.getColorOfSection(tickMarkSections, counter, minorTickMarkColor) : minorTickMarkColor);
CTX.setLineWidth(SIZE * (TickMarkType.BOX == tickMarkType || TickMarkType.PILL == tickMarkType ? 0.007 : MAX_TICK_MARK_WIDTH * minorTickMarkWidthFactor));
CTX.setLineCap(TickMarkType.PILL == tickMarkType ? StrokeLineCap.ROUND : StrokeLineCap.BUTT);
}
if (fullRange && !isNotZero) {
CTX.setFill(zeroColor);
CTX.setStroke(zeroColor);
}
if (null != tickMarkType) {
switch (tickMarkType) {
case TRAPEZOID:
if (majorTickMarksVisible) {
Helper.drawTrapezoid(CTX, trapezoidMajorInnerPoint1X, trapezoidMajorInnerPoint1Y, trapezoidMajorInnerPoint2X, trapezoidMajorInnerPoint2Y,
trapezoidMajorOuterPoint1X, trapezoidMajorOuterPoint1Y, trapezoidMajorOuterPoint2X, trapezoidMajorOuterPoint2Y);
} else if (minorTickMarksVisible) {
Helper.drawTrapezoid(CTX, trapezoidMinorInnerPoint1X, trapezoidMinorInnerPoint1Y, trapezoidMinorInnerPoint2X, trapezoidMinorInnerPoint2Y,
trapezoidMinorOuterPoint1X, trapezoidMinorOuterPoint1Y, trapezoidMinorOuterPoint2X, trapezoidMinorOuterPoint2Y);
}
break;
case TRIANGLE:
if (majorTickMarksVisible) {
if (TickLabelLocation.INSIDE == tickLabelLocation) {
Helper.drawTriangle(CTX, triangleMajorInnerPointX, triangleMajorInnerPointY, trapezoidMajorOuterPoint1X, trapezoidMajorOuterPoint1Y, trapezoidMajorOuterPoint2X, trapezoidMajorOuterPoint2Y);
} else {
Helper.drawTriangle(CTX, triangleMajorOuterPointX, triangleMajorOuterPointY, trapezoidMajorInnerPoint1X, trapezoidMajorInnerPoint1Y, trapezoidMajorInnerPoint2X, trapezoidMajorInnerPoint2Y);
}
} else if (minorTickMarksVisible) {
if (TickLabelLocation.INSIDE == tickLabelLocation) {
Helper.drawTriangle(CTX, triangleMinorInnerPointX, triangleMinorInnerPointY, trapezoidMinorOuterPoint1X, trapezoidMinorOuterPoint1Y, trapezoidMinorOuterPoint2X, trapezoidMinorOuterPoint2Y);
} else {
Helper.drawTriangle(CTX, triangleMinorOuterPointX, triangleMinorOuterPointY, trapezoidMinorInnerPoint1X, trapezoidMinorInnerPoint1Y, trapezoidMinorInnerPoint2X, trapezoidMinorInnerPoint2Y);
}
}
break;
case DOT:
if (majorTickMarksVisible) {
Helper.drawDot(CTX, dotCenterX - majorHalfDotSize, dotCenterY - majorHalfDotSize, majorDotSize);
} else if (minorTickMarksVisible) {
Helper.drawDot(CTX, dotMinorCenterX - minorHalfDotSize, dotMinorCenterY - minorHalfDotSize, minorDotSize);
}
break;
case TICK_LABEL:
if (majorTickMarksVisible) {
CTX.save();
CTX.translate(tickLabelTickMarkX, tickLabelTickMarkY);
Helper.rotateContextForText(CTX, START_ANGLE, angle, tickLabelOrientation);
CTX.setFont(isNotZero ? tickMarkFont : tickMarkZeroFont);
CTX.setTextAlign(TextAlignment.CENTER);
CTX.setTextBaseline(VPos.CENTER);
CTX.fillText(String.format(locale, tickLabelFormatString, counter), 0, 0);
CTX.restore();
}
break;
case LINE:
default:
if (majorTickMarksVisible) {
Helper.drawLine(CTX, innerPointX, innerPointY, outerPointX, outerPointY);
} else if (minorTickMarksVisible) {
if (TickLabelLocation.OUTSIDE == tickLabelLocation) {
Helper.drawLine(CTX, innerPointX, innerPointY, outerMinorPointX, outerMinorPointY);
} else {
Helper.drawLine(CTX, innerMinorPointX, innerMinorPointY, outerPointX, outerPointY);
}
}
break;
}
}
// Draw tick label text
if (tickLabelsVisible) {
CTX.save();
CTX.translate(textPointX, textPointY);
Helper.rotateContextForText(CTX, START_ANGLE, angle, tickLabelOrientation);
CTX.setFont(isNotZero ? tickLabelFont : tickLabelZeroFont);
CTX.setTextAlign(TextAlignment.CENTER);
CTX.setTextBaseline(VPos.CENTER);
if (!onlyFirstAndLastLabelVisible) {
if (isNotZero) {
CTX.setFill(tickLabelSectionsVisible ? Helper.getColorOfSection(tickLabelSections, counter, tickLabelColor) : tickLabelColor);
} else {
CTX.setFill(tickLabelSectionsVisible ? Helper.getColorOfSection(tickLabelSections, counter, tickLabelColor) : fullRange ? zeroColor : tickLabelColor);
}
} else {
if ((Double.compare(counter, MIN_VALUE) == 0 || Double.compare(counter, MAX_VALUE) == 0)) {
if (isNotZero) {
CTX.setFill(tickLabelSectionsVisible ? Helper.getColorOfSection(tickLabelSections, counter, tickLabelColor) : tickLabelColor);
} else {
CTX.setFill(tickLabelSectionsVisible ? Helper.getColorOfSection(tickLabelSections, counter, tickLabelColor) : fullRange ? zeroColor : tickLabelColor);
}
} else {
CTX.setFill(Color.TRANSPARENT);
}
}
if (customTickLabelsEnabled) {
if (customTickLabelCounter >= 0) {
CTX.fillText(customTickLabels.get(customTickLabelCounter), 0, 0);
customTickLabelCounter++;
}
if (customTickLabelCounter > customTickLabels.size() - 1) customTickLabelCounter = -1;
} else {
CTX.fillText(String.format(locale, tickLabelFormatString, counter), 0, 0);
}
CTX.restore();
}
} else if (mediumTickMarksVisible &&
Double.compare(minorTickSpaceBD.remainder(mediumCheck2).doubleValue(), 0.0) != 0.0 &&
Double.compare(counterBD.remainder(mediumCheck5).doubleValue(), 0.0) == 0.0) {
// Draw medium tick mark
CTX.setFill(tickMarkSectionsVisible ? Helper.getColorOfSection(tickMarkSections, counter, mediumTickMarkColor) : mediumTickMarkColor);
CTX.setStroke(tickMarkSectionsVisible ? Helper.getColorOfSection(tickMarkSections, counter, mediumTickMarkColor) : mediumTickMarkColor);
switch(mediumTickMarkType) {
case TRAPEZOID:
Helper.drawTrapezoid(CTX, trapezoidMediumInnerPoint1X, trapezoidMediumInnerPoint1Y, trapezoidMediumInnerPoint2X, trapezoidMediumInnerPoint2Y,
trapezoidMediumOuterPoint1X, trapezoidMediumOuterPoint1Y, trapezoidMediumOuterPoint2X, trapezoidMediumOuterPoint2Y);
break;
case TRIANGLE:
if (TickLabelLocation.INSIDE == tickLabelLocation) {
Helper.drawTriangle(CTX, triangleMediumInnerPointX, triangleMediumInnerPointY, trapezoidMediumOuterPoint1X, trapezoidMediumOuterPoint1Y, trapezoidMediumOuterPoint2X, trapezoidMediumOuterPoint2Y);
} else {
Helper.drawTriangle(CTX, triangleMediumOuterPointX, triangleMediumOuterPointY, trapezoidMediumInnerPoint1X, trapezoidMediumInnerPoint1Y, trapezoidMediumInnerPoint2X, trapezoidMediumInnerPoint2Y);
}
break;
case DOT:
Helper.drawDot(CTX, dotMediumCenterX - mediumHalfDotSize, dotMediumCenterY - mediumHalfDotSize, mediumDotSize);
break;
case BOX:
CTX.setLineWidth(SIZE * 0.009);
if (TickLabelLocation.OUTSIDE == tickLabelLocation) {
Helper.drawLine(CTX, innerPointX, innerPointY, outerMediumPointX, outerMediumPointY);
} else {
Helper.drawLine(CTX, innerMediumPointX, innerMediumPointY, outerPointX, outerPointY);
}
break;
case PILL:
CTX.setLineCap(StrokeLineCap.ROUND);
CTX.setLineWidth(SIZE * 0.009);
if (TickLabelLocation.OUTSIDE == tickLabelLocation) {
Helper.drawLine(CTX, innerPointX, innerPointY, outerMediumPointX, outerMediumPointY);
} else {
Helper.drawLine(CTX, innerMediumPointX, innerMediumPointY, outerPointX, outerPointY);
}
break;
case LINE:
default:
CTX.setLineWidth(SIZE * MAX_TICK_MARK_WIDTH * mediumTickMarkWidthFactor);
if (TickLabelLocation.OUTSIDE == tickLabelLocation) {
Helper.drawLine(CTX, innerPointX, innerPointY, outerMediumPointX, outerMediumPointY);
} else {
Helper.drawLine(CTX, innerMediumPointX, innerMediumPointY, outerPointX, outerPointY);
}
break;
}
} else if (minorTickMarksVisible && Double.compare(counterBD.remainder(minorTickSpaceBD).doubleValue(), 0.0) == 0) {
// Draw minor tick mark
if (TickMarkType.TICK_LABEL != majorTickMarkType) {
CTX.setFill(tickMarkSectionsVisible ? Helper.getColorOfSection(tickMarkSections, counter, minorTickMarkColor) : minorTickMarkColor);
CTX.setStroke(tickMarkSectionsVisible ? Helper.getColorOfSection(tickMarkSections, counter, minorTickMarkColor) : minorTickMarkColor);
switch (minorTickMarkType) {
case TRAPEZOID:
Helper.drawTrapezoid(CTX, trapezoidMinorInnerPoint1X, trapezoidMinorInnerPoint1Y, trapezoidMinorInnerPoint2X, trapezoidMinorInnerPoint2Y,
trapezoidMinorOuterPoint1X, trapezoidMinorOuterPoint1Y, trapezoidMinorOuterPoint2X, trapezoidMinorOuterPoint2Y);
break;
case TRIANGLE:
if (TickLabelLocation.INSIDE == tickLabelLocation) {
Helper.drawTriangle(CTX, triangleMinorInnerPointX, triangleMinorInnerPointY, trapezoidMinorOuterPoint1X, trapezoidMinorOuterPoint1Y, trapezoidMinorOuterPoint2X, trapezoidMinorOuterPoint2Y);
} else {
Helper.drawTriangle(CTX, triangleMinorOuterPointX, triangleMinorOuterPointY, trapezoidMinorInnerPoint1X, trapezoidMinorInnerPoint1Y, trapezoidMinorInnerPoint2X, trapezoidMinorInnerPoint2Y);
}
break;
case DOT:
Helper.drawDot(CTX, dotMinorCenterX - minorHalfDotSize, dotMinorCenterY - minorHalfDotSize, minorDotSize);
break;
case BOX:
CTX.setLineWidth(SIZE * 0.007);
if (TickLabelLocation.OUTSIDE == tickLabelLocation) {
Helper.drawLine(CTX, innerPointX, innerPointY, outerMinorPointX, outerMinorPointY);
} else {
Helper.drawLine(CTX, innerMinorPointX, innerMinorPointY, outerPointX, outerPointY);
}
break;
case PILL:
CTX.setLineCap(StrokeLineCap.ROUND);
CTX.setLineWidth(SIZE * 0.007);
if (TickLabelLocation.OUTSIDE == tickLabelLocation) {
Helper.drawLine(CTX, innerPointX, innerPointY, outerMinorPointX, outerMinorPointY);
} else {
Helper.drawLine(CTX, innerMinorPointX, innerMinorPointY, outerPointX, outerPointY);
}
break;
case LINE:
default:
CTX.setLineWidth(SIZE * MAX_TICK_MARK_WIDTH * minorTickMarkWidthFactor);
if (TickLabelLocation.OUTSIDE == tickLabelLocation) {
Helper.drawLine(CTX, innerPointX, innerPointY, outerMinorPointX, outerMinorPointY);
} else {
Helper.drawLine(CTX, innerMinorPointX, innerMinorPointY, outerPointX, outerPointY);
}
break;
}
}
}
counterBD = counterBD.add(minorTickSpaceBD);
counter = counterBD.doubleValue();
if (counter > MAX_VALUE) break;
angle = ScaleDirection.CLOCKWISE == scaleDirection ? (angle - tmpAngleStep) : (angle + tmpAngleStep);
}
}
public static Image createNoiseImage(final double WIDTH, final double HEIGHT, final Color DARK_COLOR, final Color BRIGHT_COLOR, final double ALPHA_VARIATION_IN_PERCENT) {
if (Double.compare(WIDTH, 0) <= 0 || Double.compare(HEIGHT, 0) <= 0) return null;
int width = (int) WIDTH;
int height = (int) HEIGHT;
double alphaVariationInPercent = Helper.clamp(0.0, 100.0, ALPHA_VARIATION_IN_PERCENT);
final WritableImage IMAGE = new WritableImage(width, height);
final PixelWriter PIXEL_WRITER = IMAGE.getPixelWriter();
final Random BW_RND = new Random();
final Random ALPHA_RND = new Random();
final double ALPHA_START = alphaVariationInPercent / 100 / 2;
final double ALPHA_VARIATION = alphaVariationInPercent / 100;
for (int y = 0 ; y < height ; y++) {
for (int x = 0 ; x < width ; x++) {
final Color NOISE_COLOR = BW_RND.nextBoolean() ? BRIGHT_COLOR : DARK_COLOR;
final double NOISE_ALPHA = Helper.clamp(0.0, 1.0, ALPHA_START + ALPHA_RND.nextDouble() * ALPHA_VARIATION);
PIXEL_WRITER.setColor(x, y, Color.color(NOISE_COLOR.getRed(), NOISE_COLOR.getGreen(), NOISE_COLOR.getBlue(), NOISE_ALPHA));
}
}
return IMAGE;
}
public static void drawTimeSections(final Clock CLOCK, final GraphicsContext CTX, final List SECTIONS, final double SIZE,
final double XY_INSIDE, final double XY_OUTSIDE, final double WH_INSIDE, final double WH_OUTSIDE,
final double LINE_WIDTH) {
if (SECTIONS.isEmpty()) return;
TickLabelLocation tickLabelLocation = CLOCK.getTickLabelLocation();
ZonedDateTime time = CLOCK.getTime();
boolean isAM = time.get(ChronoField.AMPM_OF_DAY) == 0;
double xy = TickLabelLocation.INSIDE == tickLabelLocation ? XY_INSIDE * SIZE : XY_OUTSIDE * SIZE;
double wh = TickLabelLocation.INSIDE == tickLabelLocation ? WH_INSIDE * SIZE : WH_OUTSIDE * SIZE;
double offset = 90;
int listSize = SECTIONS.size();
double angleStep = 360.0 / 60.0;
boolean highlightSections = CLOCK.isHighlightSections();
for (int i = 0 ; i < listSize ; i++) {
TimeSection section = SECTIONS.get(i);
LocalTime start = section.getStart();
LocalTime stop = section.getStop();
boolean isStartAM = start.get(ChronoField.AMPM_OF_DAY) == 0;
boolean isStopAM = stop.get(ChronoField.AMPM_OF_DAY) == 0;
boolean draw = isAM ? (isStartAM || isStopAM) :(!isStartAM || !isStopAM);
if (draw) {
double sectionStartAngle = (start.getHour() % 12 * 5.0 + start.getMinute() / 12.0 + start.getSecond() / 300.0) * angleStep + 180;
double sectionAngleExtend = ((stop.getHour() - start.getHour()) % 12 * 5.0 + (stop.getMinute() - start.getMinute()) / 12.0 + (stop.getSecond() - start.getSecond()) / 300.0) * angleStep;
//TODO: Add an indicator to the section like -1 or similar
// check if start was already yesterday
if (start.getHour() > stop.getHour()) { sectionAngleExtend = (360.0 - Math.abs(sectionAngleExtend)); }
CTX.save();
if (highlightSections) {
CTX.setStroke(section.contains(time.toLocalTime()) ? section.getHighlightColor() : section.getColor());
} else {
CTX.setStroke(section.getColor());
}
CTX.setLineWidth(SIZE * LINE_WIDTH);
CTX.setLineCap(StrokeLineCap.BUTT);
CTX.strokeArc(xy, xy, wh, wh, -(offset + sectionStartAngle), -sectionAngleExtend, ArcType.OPEN);
CTX.restore();
}
}
}
public static void drawTimeAreas(final Clock CLOCK, final GraphicsContext CTX, final List AREAS, final double SIZE,
final double XY_INSIDE, final double XY_OUTSIDE, final double WH_INSIDE, final double WH_OUTSIDE) {
if (AREAS.isEmpty()) return;
TickLabelLocation tickLabelLocation = CLOCK.getTickLabelLocation();
ZonedDateTime time = CLOCK.getTime();
boolean isAM = time.get(ChronoField.AMPM_OF_DAY) == 0;
double xy = TickLabelLocation.OUTSIDE == tickLabelLocation ? XY_OUTSIDE * SIZE : XY_INSIDE * SIZE;
double wh = TickLabelLocation.OUTSIDE == tickLabelLocation ? WH_OUTSIDE * SIZE : WH_INSIDE * SIZE;
double offset = 90;
double angleStep = 360.0 / 60.0;
int listSize = AREAS.size();
boolean highlightAreas = CLOCK.isHighlightAreas();
for (int i = 0; i < listSize ; i++) {
TimeSection area = AREAS.get(i);
LocalTime start = area.getStart();
LocalTime stop = area.getStop();
boolean isStartAM = start.get(ChronoField.AMPM_OF_DAY) == 0;
boolean isStopAM = stop.get(ChronoField.AMPM_OF_DAY) == 0;
boolean draw = isAM ? (isStartAM || isStopAM) :(!isStartAM || !isStopAM);
if (draw) {
double areaStartAngle = (start.getHour() % 12 * 5.0 + start.getMinute() / 12.0 + start.getSecond() / 300.0) * angleStep + 180;;
double areaAngleExtend = ((stop.getHour() - start.getHour()) % 12 * 5.0 + (stop.getMinute() - start.getMinute()) / 12.0 + (stop.getSecond() - start.getSecond()) / 300.0) * angleStep;
//TODO: Add an indicator to the area like -1 or similar
// check if start was already yesterday
if (start.getHour() > stop.getHour()) { areaAngleExtend = (360.0 - Math.abs(areaAngleExtend)); }
CTX.save();
if (highlightAreas) {
CTX.setFill(area.contains(time.toLocalTime()) ? area.getHighlightColor() : area.getColor());
} else {
CTX.setFill(area.getColor());
}
CTX.fillArc(xy, xy, wh, wh, -(offset + areaStartAngle), -areaAngleExtend, ArcType.ROUND);
CTX.restore();
}
}
}
public static void drawAlarms(final Clock CLOCK, final double SIZE, final double ALARM_MARKER_SIZE, final double ALARM_MARKER_RADIUS, final Map ALARM_MAP, final DateTimeFormatter DATE_TIME_FORMATTER, final ZonedDateTime TIME) {
if (CLOCK.isAlarmsVisible()) {
double alarmSize = ALARM_MARKER_SIZE * SIZE;
double center = SIZE * 0.5;
double angleStep = 360.0 / 60.0;
for (Map.Entry entry : ALARM_MAP.entrySet()) {
Alarm alarm = entry.getKey();
ZonedDateTime alarmTime = alarm.getTime();
double alarmAngle = (alarmTime.getMinute() + alarmTime.getSecond() / 60.0) * angleStep + 180;
double sinValue = Math.sin(Math.toRadians((-alarmAngle)));
double cosValue = Math.cos(Math.toRadians((-alarmAngle)));
Color alarmColor = alarm.isArmed() ? alarm.getColor() : INACTIVE_ALARM_COLOR;
Circle dot = entry.getValue();
dot.setRadius(alarmSize);
dot.setCenterX(center + SIZE * ALARM_MARKER_RADIUS * sinValue);
dot.setCenterY(center + SIZE * ALARM_MARKER_RADIUS * cosValue);
dot.setFill(alarmColor);
dot.setStroke(alarmColor.darker());
dot.setPickOnBounds(false);
dot.setOnMousePressed(e -> alarm.fireAlarmMarkerEvent(alarm.ALARM_MARKER_PRESSED_EVENT));
dot.setOnMouseReleased(e -> alarm.fireAlarmMarkerEvent(alarm.ALARM_MARKER_RELEASED_EVENT));
if (alarmTime.getDayOfMonth() == TIME.getDayOfMonth() &&
alarmTime.getMonthValue() == TIME.getMonthValue() &&
alarmTime.getYear() == TIME.getYear() &&
alarmTime.getHour() == TIME.getHour() &&
alarmTime.getMinute() >= TIME.getMinute()) {
dot.setManaged(true);
dot.setVisible(true);
} else {
dot.setManaged(false);
dot.setVisible(false);
}
Tooltip alarmTooltip;
if (alarm.getText().isEmpty()) {
alarmTooltip = new Tooltip(DATE_TIME_FORMATTER.format(alarm.getTime()));
} else {
alarmTooltip = new Tooltip(new StringBuilder(alarm.getText()).append("\n").append(DATE_TIME_FORMATTER.format(alarm.getTime())).toString());
}
alarmTooltip.setTextAlignment(TextAlignment.CENTER);
Tooltip.install(dot, alarmTooltip);
}
}
}
public static String formatNumber(final Gauge GAUGE, final double VALUE) {
return formatNumber(GAUGE.getLocale(), GAUGE.getFormatString(), GAUGE.getDecimals(), VALUE);
}
public static String formatNumber(final Locale LOCALE, final String FORMAT_STRING, final int DECIMALS, final double VALUE) {
double value = VALUE;
if (value > 0) {
value = Math.floor(value * Math.pow(10, DECIMALS)) / Math.pow(10, DECIMALS);
} else if (value < 0) {
value = Math.ceil(value * Math.pow(10, DECIMALS)) / Math.pow(10, DECIMALS);
}
return String.format(LOCALE, FORMAT_STRING, value);
}
public static String formatNumber(final Locale LOCALE, final double MIN_VALUE, final double MAX_VALUE, final int DECIMALS, final double VALUE) {
StringBuilder sb = new StringBuilder("%.").append(DECIMALS).append("f");
String f = sb.toString();
int minLength = String.format(Locale.US, f, MIN_VALUE).length();
int maxLength = String.format(Locale.US, f, MAX_VALUE).length();
int length = Math.max(minLength, maxLength);
StringBuilder formatStringBuilder = new StringBuilder("%").append(length).append(".").append(DECIMALS).append("f");
String formatString = formatStringBuilder.toString();
double value = VALUE;
if (value > 0) {
value = Math.floor(VALUE * Math.pow(10, DECIMALS)) / Math.pow(10, DECIMALS);
} else if (value < 0) {
value = Math.ceil(VALUE * Math.pow(10, DECIMALS)) / Math.pow(10, DECIMALS);
}
return String.format(LOCALE, formatString, value);
}
}