Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
eu.hansolo.medusa.skins.TileSparklineSkin Maven / Gradle / Ivy
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2016-2021 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
*
* https://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.tools.Helper;
import eu.hansolo.medusa.tools.Statistics;
import javafx.beans.InvalidationListener;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.Line;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.scene.text.Text;
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import static eu.hansolo.medusa.tools.Helper.clamp;
import static eu.hansolo.medusa.tools.Helper.formatNumber;
/**
* Created by hansolo on 05.12.16.
*/
public class TileSparklineSkin extends GaugeSkinBase {
private double size;
private Text titleText;
private Text valueText;
private Text unitText;
private Text averageText;
private Text highText;
private Text lowText;
private Text subTitleText;
private Rectangle graphBounds;
private List pathElements;
private Path sparkLine;
private Circle dot;
private Rectangle stdDeviationArea;
private Line averageLine;
private Pane pane;
private double low;
private double high;
private double minValue;
private double maxValue;
private double range;
private double stdDeviation;
private String formatString;
private Locale locale;
private int noOfDatapoints;
private List dataList;
private InvalidationListener currentValueListener;
private InvalidationListener averagingListener;
// ******************** Constructors **************************************
public TileSparklineSkin(Gauge gauge) {
super(gauge);
if (gauge.isAutoScale()) gauge.calcAutoScale();
low = gauge.getMaxValue();
high = gauge.getMinValue();
minValue = gauge.getMinValue();
maxValue = gauge.getMaxValue();
range = gauge.getRange();
stdDeviation = 0;
formatString = new StringBuilder("%.").append(Integer.toString(gauge.getDecimals())).append("f").toString();
locale = gauge.getLocale();
noOfDatapoints = gauge.getAveragingPeriod();
dataList = new LinkedList<>();
currentValueListener = o -> handleEvents("CURRENT_VALUE");
averagingListener = o -> handleEvents("AVERAGING_PERIOD");
for (int i = 0; i < noOfDatapoints; i++) { dataList.add(minValue); }
// To get smooth lines in the chart we need at least 4 values
if (noOfDatapoints < 4) throw new IllegalArgumentException("Please increase the averaging period to a value larger than 3.");
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);
}
}
graphBounds = new Rectangle(PREFERRED_WIDTH * 0.05, PREFERRED_HEIGHT * 0.5, PREFERRED_WIDTH * 0.9, PREFERRED_HEIGHT * 0.45);
titleText = new Text(gauge.getTitle());
titleText.setFill(gauge.getTitleColor());
Helper.enableNode(titleText, !gauge.getTitle().isEmpty());
valueText = new Text(String.format(locale, formatString, gauge.getValue()));
valueText.setFill(gauge.getValueColor());
Helper.enableNode(valueText, gauge.isValueVisible());
unitText = new Text(gauge.getUnit());
unitText.setFill(gauge.getUnitColor());
Helper.enableNode(unitText, !gauge.getUnit().isEmpty());
averageText = new Text(String.format(locale, formatString, gauge.getAverage()));
averageText.setFill(gauge.getAverageColor());
Helper.enableNode(averageText, gauge.isAverageVisible());
highText = new Text();
highText.setTextOrigin(VPos.BOTTOM);
highText.setFill(gauge.getValueColor());
lowText = new Text();
lowText.setTextOrigin(VPos.TOP);
lowText.setFill(gauge.getValueColor());
subTitleText = new Text(gauge.getSubTitle());
subTitleText.setTextOrigin(VPos.TOP);
subTitleText.setFill(gauge.getSubTitleColor());
stdDeviationArea = new Rectangle();
Helper.enableNode(stdDeviationArea, gauge.isAverageVisible());
averageLine = new Line();
averageLine.setStroke(gauge.getAverageColor());
averageLine.getStrokeDashArray().addAll(PREFERRED_WIDTH * 0.005, PREFERRED_WIDTH * 0.005);
Helper.enableNode(averageLine, gauge.isAverageVisible());
pathElements = new ArrayList<>(noOfDatapoints);
pathElements.add(0, new MoveTo());
for (int i = 1 ; i < noOfDatapoints ; i++) { pathElements.add(i, new LineTo()); }
sparkLine = new Path();
sparkLine.getElements().addAll(pathElements);
sparkLine.setFill(null);
sparkLine.setStroke(gauge.getBarColor());
sparkLine.setStrokeWidth(PREFERRED_WIDTH * 0.0075);
sparkLine.setStrokeLineCap(StrokeLineCap.ROUND);
sparkLine.setStrokeLineJoin(StrokeLineJoin.ROUND);
dot = new Circle();
dot.setFill(gauge.getBarColor());
pane = new Pane(titleText, valueText, unitText, stdDeviationArea, averageLine, sparkLine, dot, averageText, highText, lowText, subTitleText);
pane.setBorder(new Border(new BorderStroke(gauge.getBorderPaint(), BorderStrokeStyle.SOLID, new CornerRadii(PREFERRED_WIDTH * 0.025), new BorderWidths(gauge.getBorderWidth()))));
pane.setBackground(new Background(new BackgroundFill(gauge.getBackgroundPaint(), new CornerRadii(PREFERRED_WIDTH * 0.025), Insets.EMPTY)));
getChildren().setAll(pane);
}
@Override protected void registerListeners() {
super.registerListeners();
gauge.currentValueProperty().addListener(currentValueListener);
gauge.averagingPeriodProperty().addListener(averagingListener);
}
// ******************** Methods *******************************************
@Override protected void handleEvents(final String EVENT_TYPE) {
super.handleEvents(EVENT_TYPE);
if ("RECALC".equals(EVENT_TYPE)) {
minValue = gauge.getMinValue();
maxValue = gauge.getMaxValue();
range = gauge.getRange();
redraw();
} else if ("VISIBILITY".equals(EVENT_TYPE)) {
Helper.enableNode(titleText, !gauge.getTitle().isEmpty());
Helper.enableNode(valueText, gauge.isValueVisible());
Helper.enableNode(unitText, !gauge.getUnit().isEmpty());
Helper.enableNode(subTitleText, !gauge.getSubTitle().isEmpty());
Helper.enableNode(averageLine, gauge.isAverageVisible());
Helper.enableNode(averageText, gauge.isAverageVisible());
Helper.enableNode(stdDeviationArea, gauge.isAverageVisible());
redraw();
} else if ("SECTION".equals(EVENT_TYPE)) {
} else if ("ALERT".equals(EVENT_TYPE)) {
} else if ("VALUE".equals(EVENT_TYPE)) {
} else if ("CURRENT_VALUE".equals(EVENT_TYPE)) {
if(gauge.isAnimated()) { gauge.setAnimated(false); }
if (!gauge.isAveragingEnabled()) { gauge.setAveragingEnabled(true); }
double value = clamp(minValue, maxValue, gauge.getValue());
addData(value);
drawChart(value);
} else if ("AVERAGING_PERIOD".equals(EVENT_TYPE)) {
noOfDatapoints = gauge.getAveragingPeriod();
dataList.clear();
// To get smooth lines in the chart we need at least 4 values
if (noOfDatapoints < 4) throw new IllegalArgumentException("Please increase the averaging period to a value larger than 3.");
for (int i = 0; i < noOfDatapoints; i++) { dataList.add(minValue); }
pathElements.clear();
pathElements.add(0, new MoveTo());
for (int i = 1 ; i < noOfDatapoints ; i++) { pathElements.add(i, new LineTo()); }
sparkLine.getElements().setAll(pathElements);
redraw();
}
}
private void addData(final double VALUE) {
if (dataList.size() <= noOfDatapoints) {
Collections.rotate(dataList, -1);
dataList.set((noOfDatapoints - 1), VALUE);
} else {
dataList.add(VALUE);
}
stdDeviation = Statistics.getStdDev(dataList);
}
private void drawChart(final double VALUE) {
low = Statistics.getMin(dataList);
high = Statistics.getMax(dataList);
if (Double.compare(low, high) == 0) {
low = minValue;
high = maxValue;
}
range = high - low;
double minX = graphBounds.getX();
double maxX = minX + graphBounds.getWidth();
double minY = graphBounds.getY();
double maxY = minY + graphBounds.getHeight();
double stepX = graphBounds.getWidth() / (noOfDatapoints - 1);
double stepY = graphBounds.getHeight() / range;
if (gauge.isSmoothing()) {
smooth(dataList);
} else {
MoveTo begin = (MoveTo) pathElements.get(0);
begin.setX(minX);
begin.setY(maxY - Math.abs(low - dataList.get(0)) * stepY);
for (int i = 1; i < (noOfDatapoints - 1); i++) {
LineTo lineTo = (LineTo) pathElements.get(i);
lineTo.setX(minX + i * stepX);
lineTo.setY(maxY - Math.abs(low - dataList.get(i)) * stepY);
}
LineTo end = (LineTo) pathElements.get(noOfDatapoints - 1);
end.setX(maxX);
end.setY(maxY - Math.abs(low - dataList.get(noOfDatapoints - 1)) * stepY);
dot.setCenterX(maxX);
dot.setCenterY(end.getY());
}
double average = gauge.getAverage();
double averageY = clamp(minY, maxY, maxY - Math.abs(low - average) * stepY);
averageLine.setStartX(minX);
averageLine.setEndX(maxX);
averageLine.setStartY(averageY);
averageLine.setEndY(averageY);
stdDeviationArea.setY(averageLine.getStartY() - (stdDeviation * 0.5 * stepY));
stdDeviationArea.setHeight(stdDeviation * stepY);
valueText.setText(formatNumber(gauge.getLocale(), gauge.getFormatString(), gauge.getDecimals(), VALUE));
averageText.setText(String.format(locale, formatString, average));
highText.setText(String.format(locale, formatString, high));
lowText.setText(String.format(locale, formatString, low));
resizeDynamicText();
}
@Override public void dispose() {
gauge.currentValueProperty().removeListener(currentValueListener);
gauge.averagingPeriodProperty().removeListener(averagingListener);
super.dispose();
}
// ******************** Smoothing *****************************************
public void smooth(final List DATA_LIST) {
int size = DATA_LIST.size();
double[] x = new double[size];
double[] y = new double[size];
low = Statistics.getMin(DATA_LIST);
high = Statistics.getMax(DATA_LIST);
if (Double.compare(low, high) == 0) {
low = minValue;
high = maxValue;
}
range = high - low;
double minX = graphBounds.getX();
double maxX = minX + graphBounds.getWidth();
double minY = graphBounds.getY();
double maxY = minY + graphBounds.getHeight();
double stepX = graphBounds.getWidth() / (noOfDatapoints - 1);
double stepY = graphBounds.getHeight() / range;
for (int i = 0 ; i < size ; i++) {
x[i] = minX + i * stepX;
y[i] = maxY - Math.abs(low - DATA_LIST.get(i)) * stepY;
}
Pair px = computeControlPoints(x);
Pair py = computeControlPoints(y);
sparkLine.getElements().clear();
for (int i = 0 ; i < size - 1 ; i++) {
sparkLine.getElements().add(new MoveTo(x[i], y[i]));
sparkLine.getElements().add(new CubicCurveTo(px.getKey()[i], py.getKey()[i], px.getValue()[i], py.getValue()[i], x[i + 1], y[i + 1]));
}
dot.setCenterX(maxX);
dot.setCenterY(y[size - 1]);
}
private Pair computeControlPoints(final double[] K) {
int n = K.length - 1;
Double[] p1 = new Double[n];
Double[] p2 = new Double[n];
/*rhs vector*/
double[] a = new double[n];
double[] b = new double[n];
double[] c = new double[n];
double[] r = new double[n];
/*left most segment*/
a[0] = 0;
b[0] = 2;
c[0] = 1;
r[0] = K[0]+2*K[1];
/*internal segments*/
for (int i = 1; i < n - 1; i++) {
a[i] = 1;
b[i] = 4;
c[i] = 1;
r[i] = 4 * K[i] + 2 * K[i + 1];
}
/*right segment*/
a[n-1] = 2;
b[n-1] = 7;
c[n-1] = 0;
r[n-1] = 8 * K[n - 1] + K[n];
/*solves Ax = b with the Thomas algorithm*/
for (int i = 1; i < n; i++) {
double m = a[i] / b[i - 1];
b[i] = b[i] - m * c[i - 1];
r[i] = r[i] - m * r[i - 1];
}
p1[n-1] = r[n-1] / b[n-1];
for (int i = n - 2; i >= 0; --i) { p1[i] = (r[i] - c[i] * p1[i + 1]) / b[i]; }
for (int i = 0 ; i < n - 1 ; i++) { p2[i] = 2 * K[i + 1] - p1[i + 1]; }
p2[n - 1] = 0.5 * (K[n] + p1[n - 1]);
return new Pair<>(p1, p2);
}
// ******************** Resizing ******************************************
private void resizeDynamicText() {
double maxWidth = unitText.isManaged() ? size * 0.725 : size * 0.9;
double fontSize = size * 0.24;
valueText.setFont(Fonts.latoRegular(fontSize));
if (valueText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(valueText, maxWidth, fontSize); }
if (unitText.isVisible()) {
valueText.relocate(size * 0.925 - valueText.getLayoutBounds().getWidth() - unitText.getLayoutBounds().getWidth(), size * 0.15);
} else {
valueText.relocate(size * 0.95 - valueText.getLayoutBounds().getWidth(), size * 0.15);
}
maxWidth = size * 0.3;
fontSize = size * 0.05;
averageText.setFont(Fonts.latoRegular(fontSize));
if (averageText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(averageText, maxWidth, fontSize); }
if (averageLine.getStartY() < graphBounds.getY() + graphBounds.getHeight() * 0.5) {
averageText.setY(averageLine.getStartY() + (size * 0.0425));
} else {
averageText.setY(averageLine.getStartY() - (size * 0.0075));
}
highText.setFont(Fonts.latoRegular(fontSize));
if (highText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(highText, maxWidth, fontSize); }
highText.setY(graphBounds.getY() - size * 0.0125);
lowText.setFont(Fonts.latoRegular(fontSize));
if (lowText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(lowText, maxWidth, fontSize); }
lowText.setY(size * 0.9);
}
private void resizeStaticText() {
double maxWidth = size * 0.9;
double fontSize = size * 0.06;
titleText.setFont(Fonts.latoRegular(fontSize));
if (titleText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(titleText, maxWidth, fontSize); }
titleText.relocate(size * 0.05, size * 0.05);
maxWidth = size * 0.15;
unitText.setFont(Fonts.latoRegular(fontSize));
if (unitText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(unitText, maxWidth, fontSize); }
unitText.relocate(size * 0.95 - unitText.getLayoutBounds().getWidth(), size * 0.3275);
averageText.setX(size * 0.05);
highText.setX(size * 0.05);
lowText.setX(size * 0.05);
maxWidth = size * 0.75;
fontSize = size * 0.05;
subTitleText.setFont(Fonts.latoRegular(fontSize));
if (subTitleText.getLayoutBounds().getWidth() > maxWidth) { Helper.adjustTextSize(subTitleText, maxWidth, fontSize); }
subTitleText.relocate(size * 0.95 - subTitleText.getLayoutBounds().getWidth(), size * 0.9);
}
@Override protected void resize() {
double width = gauge.getWidth() - gauge.getInsets().getLeft() - gauge.getInsets().getRight();
double height = gauge.getHeight() - gauge.getInsets().getTop() - gauge.getInsets().getBottom();
size = width < height ? width : height;
if (width > 0 && height > 0) {
pane.setMaxSize(size, size);
pane.relocate((width - size) * 0.5, (height - size) * 0.5);
graphBounds = new Rectangle(size * 0.05, size * 0.5, size * 0.9, size * 0.39);
stdDeviationArea.setX(graphBounds.getX());
stdDeviationArea.setWidth(graphBounds.getWidth());
averageLine.getStrokeDashArray().setAll(graphBounds.getWidth() * 0.01, graphBounds.getWidth() * 0.01);
drawChart(gauge.getValue());
sparkLine.setStrokeWidth(size * 0.01);
dot.setRadius(size * 0.014);
resizeStaticText();
resizeDynamicText();
}
}
@Override protected void redraw() {
pane.setBorder(new Border(new BorderStroke(gauge.getBorderPaint(), BorderStrokeStyle.SOLID, new CornerRadii(size * 0.025), new BorderWidths(gauge.getBorderWidth() / PREFERRED_WIDTH * size))));
pane.setBackground(new Background(new BackgroundFill(gauge.getBackgroundPaint(), new CornerRadii(size * 0.025), Insets.EMPTY)));
locale = gauge.getLocale();
formatString = new StringBuilder("%.").append(Integer.toString(gauge.getDecimals())).append("f").toString();
titleText.setText(gauge.getTitle());
subTitleText.setText(gauge.getSubTitle());
resizeStaticText();
titleText.setFill(gauge.getTitleColor());
valueText.setFill(gauge.getValueColor());
averageText.setFill(gauge.getAverageColor());
highText.setFill(gauge.getValueColor());
lowText.setFill(gauge.getValueColor());
subTitleText.setFill(gauge.getSubTitleColor());
sparkLine.setStroke(gauge.getBarColor());
stdDeviationArea.setFill(Helper.getTranslucentColorFrom(gauge.getAverageColor(), 0.1));
averageLine.setStroke(gauge.getAverageColor());
dot.setFill(gauge.getBarColor());
}
}