diff --git a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/RESTConstants.java b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/RESTConstants.java
index 4ff2d1bd3a5..c83dc4eb13b 100644
--- a/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/RESTConstants.java
+++ b/bundles/org.openhab.core.io.rest/src/main/java/org/openhab/core/io/rest/RESTConstants.java
@@ -24,5 +24,5 @@ public class RESTConstants {
public static final String JAX_RS_NAME = "openhab";
- public static final String API_VERSION = "4";
+ public static final String API_VERSION = "5";
}
diff --git a/bundles/org.openhab.core.ui/pom.xml b/bundles/org.openhab.core.ui/pom.xml
index ad61893bb30..ec985177b00 100644
--- a/bundles/org.openhab.core.ui/pom.xml
+++ b/bundles/org.openhab.core.ui/pom.xml
@@ -18,7 +18,8 @@
org.knowm.xchart
xchart
- 2.6.1
+
+ 3.1.0
org.openhab.core.bundles
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java
index 8d76b45da5b..c7b7a70ebcf 100644
--- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/chart/ChartProvider.java
@@ -13,8 +13,10 @@
package org.openhab.core.ui.chart;
import java.awt.image.BufferedImage;
-import java.util.Date;
+import java.time.ZonedDateTime;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.items.ItemNotFoundException;
/**
@@ -25,6 +27,7 @@
* @author Chris Jackson - Initial contribution
* @author Holger Reichert - Support for themes, DPI, legend hiding
*/
+@NonNullByDefault
public interface ChartProvider {
/**
* Gets the name of this chart provider.
@@ -56,8 +59,9 @@ public interface ChartProvider {
* @throws ItemNotFoundException if an item or group is not found
* @throws IllegalArgumentException if an invalid argument is passed
*/
- BufferedImage createChart(String service, String theme, Date startTime, Date endTime, int height, int width,
- String items, String groups, Integer dpi, Boolean legend) throws ItemNotFoundException;
+ BufferedImage createChart(@Nullable String service, @Nullable String theme, ZonedDateTime startTime,
+ ZonedDateTime endTime, int height, int width, @Nullable String items, @Nullable String groups,
+ @Nullable Integer dpi, @Nullable Boolean legend) throws ItemNotFoundException;
/**
* Gets the type of data that will be written by the chart.
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/ChartServlet.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/ChartServlet.java
index 00e12d149e6..f7088bdd977 100644
--- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/ChartServlet.java
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/ChartServlet.java
@@ -15,16 +15,16 @@
import static java.util.Map.entry;
import java.awt.image.BufferedImage;
-import java.io.EOFException;
import java.io.IOException;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
-import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageOutputStream;
import javax.servlet.ServletConfig;
@@ -32,7 +32,10 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigurableService;
+import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.io.http.servlet.OpenHABServlet;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.ui.chart.ChartProvider;
@@ -65,6 +68,7 @@
* @author Chris Jackson - Initial contribution
* @author Holger Reichert - Support for themes, DPI, legend hiding
*/
+@NonNullByDefault
@Component(immediate = true, service = ChartServlet.class, configurationPid = "org.openhab.chart", //
property = Constants.SERVICE_PID + "=org.openhab.chart")
@ConfigurableService(category = "system", label = "Charts", description_uri = ChartServlet.CONFIG_URI)
@@ -76,6 +80,9 @@ public class ChartServlet extends OpenHABServlet {
private static final int CHART_HEIGHT = 240;
private static final int CHART_WIDTH = 480;
private static final String DATE_FORMAT = "yyyyMMddHHmm";
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMAT);
+
+ private final TimeZoneProvider timeZoneProvider;
private String providerName = "default";
private int defaultHeight = CHART_HEIGHT;
@@ -86,21 +93,25 @@ public class ChartServlet extends OpenHABServlet {
// The URI of this servlet
public static final String SERVLET_NAME = "/chart";
- protected static final Map PERIODS = Map.ofEntries( //
- entry("h", 3600000L), entry("4h", 14400000L), //
- entry("8h", 28800000L), entry("12h", 43200000L), //
- entry("D", 86400000L), entry("2D", 172800000L), //
- entry("3D", 259200000L), entry("W", 604800000L), //
- entry("2W", 1209600000L), entry("M", 2592000000L), //
- entry("2M", 5184000000L), entry("4M", 10368000000L), //
- entry("Y", 31536000000L)//
+ private static final Duration DEFAULT_PERIOD = Duration.ofDays(1);
+
+ private static final Map PERIODS = Map.ofEntries( //
+ entry("h", Duration.ofHours(1)), entry("4h", Duration.ofHours(4)), //
+ entry("8h", Duration.ofHours(8)), entry("12h", Duration.ofHours(12)), //
+ entry("D", Duration.ofDays(1)), entry("2D", Duration.ofDays(2)), //
+ entry("3D", Duration.ofDays(3)), entry("W", Duration.ofDays(7)), //
+ entry("2W", Duration.ofDays(14)), entry("M", Duration.ofDays(30)), //
+ entry("2M", Duration.ofDays(60)), entry("4M", Duration.ofDays(120)), //
+ entry("Y", Duration.ofDays(365))//
);
protected static final Map CHART_PROVIDERS = new ConcurrentHashMap<>();
@Activate
- public ChartServlet(final @Reference HttpService httpService, final @Reference HttpContext httpContext) {
+ public ChartServlet(final @Reference HttpService httpService, final @Reference HttpContext httpContext,
+ final @Reference TimeZoneProvider timeZoneProvider) {
super(httpService, httpContext);
+ this.timeZoneProvider = timeZoneProvider;
}
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
@@ -128,7 +139,7 @@ protected void deactivate() {
}
@Modified
- protected void modified(Map config) {
+ protected void modified(@Nullable Map config) {
applyConfig(config);
}
@@ -137,7 +148,7 @@ protected void modified(Map config) {
*
* @param config the configuration
*/
- private void applyConfig(Map config) {
+ private void applyConfig(@Nullable Map config) {
if (config == null) {
return;
}
@@ -224,22 +235,14 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser
}
// Read out the parameter period, begin and end and save them.
- Long period = null;
- Date timeBegin = null;
- Date timeEnd = null;
-
- if (periodParam != null) {
- period = PERIODS.get(periodParam);
- }
- if (period == null) {
- // use a day as the default period
- period = PERIODS.get("D");
- }
+ Duration period = periodParam == null ? DEFAULT_PERIOD : PERIODS.getOrDefault(periodParam, DEFAULT_PERIOD);
+ ZonedDateTime timeBegin = null;
+ ZonedDateTime timeEnd = null;
if (timeBeginParam != null) {
try {
- timeBegin = new SimpleDateFormat(DATE_FORMAT).parse(timeBeginParam);
- } catch (ParseException e) {
+ timeBegin = LocalDateTime.parse(timeBeginParam, FORMATTER).atZone(timeZoneProvider.getTimeZone());
+ } catch (DateTimeParseException e) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Begin and end must have this format: " + DATE_FORMAT + ".");
return;
@@ -248,8 +251,8 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser
if (timeEndParam != null) {
try {
- timeEnd = new SimpleDateFormat(DATE_FORMAT).parse(timeEndParam);
- } catch (ParseException e) {
+ timeEnd = LocalDateTime.parse(timeEndParam, FORMATTER).atZone(timeZoneProvider.getTimeZone());
+ } catch (DateTimeParseException e) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Begin and end must have this format: " + DATE_FORMAT + ".");
return;
@@ -258,16 +261,16 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser
// Set begin and end time and check legality.
if (timeBegin == null && timeEnd == null) {
- timeEnd = new Date();
- timeBegin = new Date(timeEnd.getTime() - period);
+ timeEnd = ZonedDateTime.now(timeZoneProvider.getTimeZone());
+ timeBegin = timeEnd.minus(period);
logger.debug("No begin or end is specified, use now as end and now-period as begin.");
} else if (timeEnd == null) {
- timeEnd = new Date(timeBegin.getTime() + period);
+ timeEnd = timeBegin.plus(period);
logger.debug("No end is specified, use begin + period as end.");
} else if (timeBegin == null) {
- timeBegin = new Date(timeEnd.getTime() - period);
+ timeBegin = timeEnd.minus(period);
logger.debug("No begin is specified, use end-period as begin");
- } else if (timeEnd.before(timeBegin)) {
+ } else if (timeEnd.isBefore(timeBegin)) {
throw new ServletException("The end is before the begin.");
}
@@ -311,46 +314,43 @@ protected void doGet(HttpServletRequest req, HttpServletResponse res) throws Ser
width = maxWidth;
}
- // Set the content type to that provided by the chart provider
- res.setContentType("image/" + provider.getChartType());
logger.debug("chart building with width {} height {} dpi {}", width, height, dpi);
- try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(res.getOutputStream())) {
+ try {
BufferedImage chart = provider.createChart(serviceName, req.getParameter("theme"), timeBegin, timeEnd,
height, width, req.getParameter("items"), req.getParameter("groups"), dpi, legend);
- ImageIO.write(chart, provider.getChartType().toString(), imageOutputStream);
- logger.debug("Chart successfully generated and written to the response.");
+ // Set the content type to that provided by the chart provider
+ res.setContentType("image/" + provider.getChartType());
+ try (ImageOutputStream imageOutputStream = ImageIO.createImageOutputStream(res.getOutputStream())) {
+ ImageIO.write(chart, provider.getChartType().toString(), imageOutputStream);
+ logger.debug("Chart successfully generated and written to the response.");
+ }
} catch (ItemNotFoundException e) {
logger.debug("{}", e.getMessage());
res.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
} catch (IllegalArgumentException e) {
logger.warn("Illegal argument in chart: {}", e.getMessage());
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Illegal argument in chart: " + e.getMessage());
- } catch (IIOException | EOFException e) {
+ } catch (IOException e) {
// this can happen if the request is terminated while the image is streamed, see
// https://github.com/openhab/openhab-distro/issues/684
logger.debug("Failed writing image to response stream", e);
} catch (RuntimeException e) {
- if (logger.isDebugEnabled()) {
- // we also attach the stack trace
- logger.warn("Chart generation failed: {}", e.getMessage(), e);
- } else {
- logger.warn("Chart generation failed: {}", e.getMessage());
- }
+ logger.warn("Chart generation failed: {}", e.getMessage(), logger.isDebugEnabled() ? e : null);
res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
@Override
- public void init(ServletConfig config) throws ServletException {
+ public void init(@Nullable ServletConfig config) throws ServletException {
}
@Override
- public ServletConfig getServletConfig() {
+ public @Nullable ServletConfig getServletConfig() {
return null;
}
@Override
- public String getServletInfo() {
+ public @Nullable String getServletInfo() {
return null;
}
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartTheme.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartTheme.java
index f8b29333cbb..9ed04a4008d 100644
--- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartTheme.java
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartTheme.java
@@ -15,11 +15,14 @@
import java.awt.Color;
import java.awt.Font;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Chart styling theme for the {@link DefaultChartProvider}.
*
* @author Holger Reichert - Initial contribution
*/
+@NonNullByDefault
public interface ChartTheme {
/**
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBlack.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBlack.java
index 2f77327156c..63483240cb1 100644
--- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBlack.java
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBlack.java
@@ -15,11 +15,14 @@
import java.awt.Color;
import java.awt.Font;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Implementation of the black {@link ChartTheme chart theme}.
*
* @author Holger Reichert - Initial contribution
*/
+@NonNullByDefault
public class ChartThemeBlack implements ChartTheme {
private static final String THEME_NAME = "black";
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBlackTransparent.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBlackTransparent.java
new file mode 100644
index 00000000000..afe25633eea
--- /dev/null
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBlackTransparent.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.ui.internal.chart.defaultchartprovider;
+
+import java.awt.Color;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Implementation of the black {@link ChartTheme chart theme} with transparent background.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+@NonNullByDefault
+public class ChartThemeBlackTransparent extends ChartThemeBlack {
+
+ private static final String THEME_NAME = "black_transparent";
+
+ @Override
+ public String getThemeName() {
+ return THEME_NAME;
+ }
+
+ @Override
+ public Color getPlotBackgroundColor() {
+ return new Color(0, 0, 0, 0);
+ }
+
+ @Override
+ public Color getChartBackgroundColor() {
+ return new Color(0, 0, 0, 0);
+ }
+}
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBright.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBright.java
index 8d37f403cfa..960d495d259 100644
--- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBright.java
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBright.java
@@ -15,11 +15,14 @@
import java.awt.Color;
import java.awt.Font;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Implementation of the default bright {@link ChartTheme chart theme}.
*
* @author Holger Reichert - Initial contribution
*/
+@NonNullByDefault
public class ChartThemeBright implements ChartTheme {
private static final String THEME_NAME = "bright";
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBrightTransparent.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBrightTransparent.java
new file mode 100644
index 00000000000..2d48f06cf92
--- /dev/null
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeBrightTransparent.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.ui.internal.chart.defaultchartprovider;
+
+import java.awt.Color;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Implementation of the bright {@link ChartTheme chart theme} with transparent background.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+@NonNullByDefault
+public class ChartThemeBrightTransparent extends ChartThemeBright {
+
+ private static final String THEME_NAME = "bright_transparent";
+
+ @Override
+ public String getThemeName() {
+ return THEME_NAME;
+ }
+
+ @Override
+ public Color getPlotBackgroundColor() {
+ return new Color(0, 0, 0, 0);
+ }
+
+ @Override
+ public Color getChartBackgroundColor() {
+ return new Color(0, 0, 0, 0);
+ }
+}
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeDark.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeDark.java
index 6ca36f004ca..39620f2b4b0 100644
--- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeDark.java
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeDark.java
@@ -15,11 +15,14 @@
import java.awt.Color;
import java.awt.Font;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Implementation of the dark {@link ChartTheme chart theme}.
*
* @author Holger Reichert - Initial contribution
*/
+@NonNullByDefault
public class ChartThemeDark implements ChartTheme {
private static final String THEME_NAME = "dark";
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeDarkTransparent.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeDarkTransparent.java
new file mode 100644
index 00000000000..42b365d7d6d
--- /dev/null
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeDarkTransparent.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.ui.internal.chart.defaultchartprovider;
+
+import java.awt.Color;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Implementation of the dark {@link ChartTheme chart theme} with transparent background.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+@NonNullByDefault
+public class ChartThemeDarkTransparent extends ChartThemeDark {
+
+ private static final String THEME_NAME = "dark_transparent";
+
+ @Override
+ public String getThemeName() {
+ return THEME_NAME;
+ }
+
+ @Override
+ public Color getPlotBackgroundColor() {
+ return new Color(0, 0, 0, 0);
+ }
+
+ @Override
+ public Color getChartBackgroundColor() {
+ return new Color(0, 0, 0, 0);
+ }
+}
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeWhite.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeWhite.java
index 20194b0c684..b18adf879db 100644
--- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeWhite.java
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeWhite.java
@@ -15,11 +15,14 @@
import java.awt.Color;
import java.awt.Font;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Implementation of the white {@link ChartTheme chart theme}.
*
* @author Holger Reichert - Initial contribution
*/
+@NonNullByDefault
public class ChartThemeWhite implements ChartTheme {
private static final String THEME_NAME = "white";
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeWhiteTransparent.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeWhiteTransparent.java
new file mode 100644
index 00000000000..6d6559932b8
--- /dev/null
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/ChartThemeWhiteTransparent.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.core.ui.internal.chart.defaultchartprovider;
+
+import java.awt.Color;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * Implementation of the white {@link ChartTheme chart theme} with transparent background.
+ *
+ * @author Wouter Born - Initial contribution
+ */
+@NonNullByDefault
+public class ChartThemeWhiteTransparent extends ChartThemeWhite {
+
+ private static final String THEME_NAME = "white_transparent";
+
+ @Override
+ public String getThemeName() {
+ return THEME_NAME;
+ }
+
+ @Override
+ public Color getPlotBackgroundColor() {
+ return new Color(0, 0, 0, 0);
+ }
+
+ @Override
+ public Color getChartBackgroundColor() {
+ return new Color(0, 0, 0, 0);
+ }
+}
diff --git a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java
index 53154213513..7a644da2311 100644
--- a/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java
+++ b/bundles/org.openhab.core.ui/src/main/java/org/openhab/core/ui/internal/chart/defaultchartprovider/DefaultChartProvider.java
@@ -16,23 +16,26 @@
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
+import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
import java.util.stream.Collectors;
-
-import org.knowm.xchart.Chart;
-import org.knowm.xchart.ChartBuilder;
-import org.knowm.xchart.Series;
-import org.knowm.xchart.SeriesMarker;
-import org.knowm.xchart.StyleManager.LegendPosition;
-import org.openhab.core.i18n.TimeZoneProvider;
+import java.util.stream.Stream;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.knowm.xchart.XYChart;
+import org.knowm.xchart.XYChartBuilder;
+import org.knowm.xchart.XYSeries;
+import org.knowm.xchart.style.Styler.LegendPosition;
+import org.knowm.xchart.style.XYStyler;
+import org.knowm.xchart.style.markers.None;
import org.openhab.core.items.GroupItem;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
@@ -65,40 +68,54 @@
* @author Holger Reichert - Support for themes, DPI, legend hiding
* @author Christoph Weitkamp - Consider default persistence service
*/
+@NonNullByDefault
@Component(immediate = true)
public class DefaultChartProvider implements ChartProvider {
- private final Logger logger = LoggerFactory.getLogger(DefaultChartProvider.class);
+ private static class LegendPositionDecider {
+ private int counter = 0;
- private final TimeZoneProvider timeZoneProvider;
- protected final ItemUIRegistry itemUIRegistry;
- private final PersistenceServiceRegistry persistenceServiceRegistry;
+ private void addData(XYSeries series, List yData) {
+ // If the start value is below the median, then count legend position down
+ // Otherwise count up.
+ // We use this to decide whether to put the legend in the top or bottom corner.
+ if (yData.iterator().next().floatValue() > ((series.getYMax() - series.getYMin()) / 2 + series.getYMin())) {
+ counter++;
+ } else {
+ counter--;
+ }
+ }
- private int legendPosition = 0;
+ private LegendPosition getLegendPosition() {
+ return counter < 0 ? LegendPosition.InsideNW : LegendPosition.InsideSW;
+ }
+ }
- private static final ChartTheme[] CHART_THEMES_AVAILABLE = { new ChartThemeWhite(), new ChartThemeBright(),
- new ChartThemeDark(), new ChartThemeBlack() };
- public static final String CHART_THEME_DEFAULT_NAME = "bright";
- private Map chartThemes = null;
+ private static final ChartTheme CHART_THEME_DEFAULT = new ChartThemeBright();
+ private static final Map CHART_THEMES = Stream
+ .of(CHART_THEME_DEFAULT, new ChartThemeBrightTransparent(), //
+ new ChartThemeWhite(), new ChartThemeWhiteTransparent(), //
+ new ChartThemeDark(), new ChartThemeDarkTransparent(), //
+ new ChartThemeBlack(), new ChartThemeBlackTransparent()) //
+ .collect(Collectors.toMap(ChartTheme::getThemeName, Function.identity()));
- public static final int DPI_DEFAULT = 96;
+ private static final int DPI_DEFAULT = 96;
+
+ private final Logger logger = LoggerFactory.getLogger(DefaultChartProvider.class);
+
+ private final ItemUIRegistry itemUIRegistry;
+ private final PersistenceServiceRegistry persistenceServiceRegistry;
@Activate
- public DefaultChartProvider(final @Reference TimeZoneProvider timeZoneProvider,
- final @Reference ItemUIRegistry itemUIRegistry,
+ public DefaultChartProvider(final @Reference ItemUIRegistry itemUIRegistry,
final @Reference PersistenceServiceRegistry persistenceServiceRegistry) {
- this.timeZoneProvider = timeZoneProvider;
this.itemUIRegistry = itemUIRegistry;
this.persistenceServiceRegistry = persistenceServiceRegistry;
- }
- @Activate
- protected void activate() {
- logger.debug("Starting up default chart provider.");
- String themeNames = Arrays.stream(CHART_THEMES_AVAILABLE) //
- .map(t -> t.getThemeName()) //
- .collect(Collectors.joining(", "));
- logger.debug("Available themes for default chart provider: {}", themeNames);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Available themes for default chart provider: {}",
+ CHART_THEMES.keySet().stream().collect(Collectors.joining(", ")));
+ }
}
@Override
@@ -107,8 +124,9 @@ public String getName() {
}
@Override
- public BufferedImage createChart(String serviceId, String theme, Date startTime, Date endTime, int height,
- int width, String items, String groups, Integer dpiValue, Boolean legend)
+ public BufferedImage createChart(@Nullable String serviceId, @Nullable String theme, ZonedDateTime startTime,
+ ZonedDateTime endTime, int height, int width, @Nullable String items, @Nullable String groups,
+ @Nullable Integer dpiValue, @Nullable Boolean legend)
throws ItemNotFoundException, IllegalArgumentException {
logger.debug(
"Rendering chart: service: '{}', theme: '{}', startTime: '{}', endTime: '{}', width: '{}', height: '{}', items: '{}', groups: '{}', dpi: '{}', legend: '{}'",
@@ -130,61 +148,62 @@ public BufferedImage createChart(String serviceId, String theme, Date startTime,
int seriesCounter = 0;
// get theme
- ChartTheme chartTheme = getChartTheme(theme);
+ ChartTheme chartTheme = theme == null || theme.isBlank() ? CHART_THEME_DEFAULT
+ : CHART_THEMES.getOrDefault(theme, CHART_THEME_DEFAULT);
// get DPI
- int dpi;
- if (dpiValue != null && dpiValue > 0) {
- dpi = dpiValue;
- } else {
- dpi = DPI_DEFAULT;
- }
+ int dpi = dpiValue != null && dpiValue > 0 ? dpiValue : DPI_DEFAULT;
// Create Chart
- Chart chart = new ChartBuilder().width(width).height(height).build();
+ XYChart chart = new XYChartBuilder().width(width).height(height).build();
// Define the time axis - the defaults are not very nice
- long period = (endTime.getTime() - startTime.getTime()) / 1000;
+ Duration period = Duration.between(startTime, endTime);
String pattern = "HH:mm";
- if (period <= 600) { // 10 minutes
+
+ if (period.compareTo(Duration.ofMinutes(10)) <= 0) {
pattern = "mm:ss";
- } else if (period <= 86400) { // 1 day
+ } else if (period.compareTo(Duration.ofDays(1)) <= 0) {
pattern = "HH:mm";
- } else if (period <= 604800) { // 1 week
+ } else if (period.compareTo(Duration.ofDays(7)) <= 0) {
pattern = "EEE d";
} else {
pattern = "d MMM";
}
- chart.getStyleManager().setDatePattern(pattern);
+ XYStyler styler = chart.getStyler();
+ styler.setDatePattern(pattern);
// axis
- chart.getStyleManager().setAxisTickLabelsFont(chartTheme.getAxisTickLabelsFont(dpi));
- chart.getStyleManager().setAxisTickLabelsColor(chartTheme.getAxisTickLabelsColor());
- chart.getStyleManager().setXAxisMin(startTime.getTime());
- chart.getStyleManager().setXAxisMax(endTime.getTime());
+ styler.setAxisTickLabelsFont(chartTheme.getAxisTickLabelsFont(dpi));
+ styler.setAxisTickLabelsColor(chartTheme.getAxisTickLabelsColor());
+ styler.setXAxisMin(Double.valueOf(startTime.toInstant().toEpochMilli()));
+ styler.setXAxisMax(Double.valueOf(endTime.toInstant().toEpochMilli()));
int yAxisSpacing = Math.max(height / 10, chartTheme.getAxisTickLabelsFont(dpi).getSize());
- chart.getStyleManager().setYAxisTickMarkSpacingHint(yAxisSpacing);
+ styler.setYAxisTickMarkSpacingHint(yAxisSpacing);
// chart
- chart.getStyleManager().setChartBackgroundColor(chartTheme.getChartBackgroundColor());
- chart.getStyleManager().setChartFontColor(chartTheme.getChartFontColor());
- chart.getStyleManager().setChartPadding(chartTheme.getChartPadding(dpi));
- chart.getStyleManager().setPlotBackgroundColor(chartTheme.getPlotBackgroundColor());
+ styler.setChartBackgroundColor(chartTheme.getChartBackgroundColor());
+ styler.setChartFontColor(chartTheme.getChartFontColor());
+ styler.setChartPadding(chartTheme.getChartPadding(dpi));
+ styler.setPlotBackgroundColor(chartTheme.getPlotBackgroundColor());
float plotGridLinesDash = (float) chartTheme.getPlotGridLinesDash(dpi);
float[] plotGridLinesDashArray = { plotGridLinesDash, plotGridLinesDash };
- chart.getStyleManager().setPlotGridLinesStroke(
- new BasicStroke((float) chartTheme.getPlotGridLinesWidth(dpi), 0, 2, 10, plotGridLinesDashArray, 0));
- chart.getStyleManager().setPlotGridLinesColor(chartTheme.getPlotGridLinesColor());
+ styler.setPlotGridLinesStroke(new BasicStroke((float) chartTheme.getPlotGridLinesWidth(dpi),
+ BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10, plotGridLinesDashArray, 0));
+ styler.setPlotGridLinesColor(chartTheme.getPlotGridLinesColor());
// legend
- chart.getStyleManager().setLegendBackgroundColor(chartTheme.getLegendBackgroundColor());
- chart.getStyleManager().setLegendFont(chartTheme.getLegendFont(dpi));
- chart.getStyleManager().setLegendSeriesLineLength(chartTheme.getLegendSeriesLineLength(dpi));
+ styler.setLegendBackgroundColor(chartTheme.getLegendBackgroundColor());
+ styler.setLegendFont(chartTheme.getLegendFont(dpi));
+ styler.setLegendSeriesLineLength(chartTheme.getLegendSeriesLineLength(dpi));
+
+ LegendPositionDecider legendPositionDecider = new LegendPositionDecider();
// Loop through all the items
if (items != null) {
String[] itemNames = items.split(",");
for (String itemName : itemNames) {
Item item = itemUIRegistry.getItem(itemName);
- if (addItem(chart, persistenceService, startTime, endTime, item, seriesCounter, chartTheme, dpi)) {
+ if (addItem(chart, persistenceService, startTime, endTime, item, seriesCounter, chartTheme, dpi,
+ legendPositionDecider)) {
seriesCounter++;
}
}
@@ -199,7 +218,7 @@ public BufferedImage createChart(String serviceId, String theme, Date startTime,
GroupItem groupItem = (GroupItem) item;
for (Item member : groupItem.getMembers()) {
if (addItem(chart, persistenceService, startTime, endTime, member, seriesCounter, chartTheme,
- dpi)) {
+ dpi, legendPositionDecider)) {
seriesCounter++;
}
}
@@ -219,13 +238,13 @@ public BufferedImage createChart(String serviceId, String theme, Date startTime,
List xData = new ArrayList<>();
List yData = new ArrayList<>();
- xData.add(startTime);
+ xData.add(Date.from(startTime.toInstant()));
yData.add(0);
- xData.add(endTime);
+ xData.add(Date.from(endTime.toInstant()));
yData.add(0);
- Series series = chart.addSeries("NONE", xData, yData);
- series.setMarker(SeriesMarker.NONE);
+ XYSeries series = chart.addSeries("NONE", xData, yData);
+ series.setMarker(new None());
series.setLineStyle(new BasicStroke(0f));
}
@@ -243,40 +262,37 @@ public BufferedImage createChart(String serviceId, String theme, Date startTime,
// Legend position (top-left or bottom-left) is dynamically selected based on the data
// This won't be perfect, but it's a good compromise
if (showLegend) {
- if (legendPosition < 0) {
- chart.getStyleManager().setLegendPosition(LegendPosition.InsideNW);
- } else {
- chart.getStyleManager().setLegendPosition(LegendPosition.InsideSW);
- }
+ styler.setLegendPosition(legendPositionDecider.getLegendPosition());
} else { // hide the whole legend
- chart.getStyleManager().setLegendVisible(false);
+ styler.setLegendVisible(false);
}
// Write the chart as a PNG image
BufferedImage lBufferedImage = new BufferedImage(chart.getWidth(), chart.getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D lGraphics2D = lBufferedImage.createGraphics();
- chart.paint(lGraphics2D);
+ chart.paint(lGraphics2D, chart.getWidth(), chart.getHeight());
return lBufferedImage;
}
- double convertData(State state) {
+ private double convertData(State state) {
if (state instanceof DecimalType) {
return ((DecimalType) state).doubleValue();
} else if (state instanceof QuantityType) {
return ((QuantityType>) state).doubleValue();
} else if (state instanceof OnOffType) {
- return (state == OnOffType.OFF) ? 0 : 1;
+ return state == OnOffType.OFF ? 0 : 1;
} else if (state instanceof OpenClosedType) {
- return (state == OpenClosedType.CLOSED) ? 0 : 1;
+ return state == OpenClosedType.CLOSED ? 0 : 1;
} else {
logger.debug("Unsupported item type in chart: {}", state.getClass().toString());
return 0;
}
}
- boolean addItem(Chart chart, QueryablePersistenceService service, Date timeBegin, Date timeEnd, Item item,
- int seriesCounter, ChartTheme chartTheme, int dpi) {
+ private boolean addItem(XYChart chart, QueryablePersistenceService service, ZonedDateTime timeBegin,
+ ZonedDateTime timeEnd, Item item, int seriesCounter, ChartTheme chartTheme, int dpi,
+ LegendPositionDecider legendPositionDecider) {
Color color = chartTheme.getLineColor(seriesCounter);
// Get the item label
@@ -301,7 +317,7 @@ boolean addItem(Chart chart, QueryablePersistenceService service, Date timeBegin
// This is necessary for values that don't change often otherwise data will start
// after the start of the graph (or not at all if there's no change during the graph period)
filter = new FilterCriteria();
- filter.setEndDate(ZonedDateTime.ofInstant(timeBegin.toInstant(), timeZoneProvider.getTimeZone()));
+ filter.setEndDate(timeBegin);
filter.setItemName(item.getName());
filter.setPageSize(1);
filter.setOrdering(Ordering.DESCENDING);
@@ -310,13 +326,13 @@ boolean addItem(Chart chart, QueryablePersistenceService service, Date timeBegin
HistoricItem historicItem = result.iterator().next();
state = historicItem.getState();
- xData.add(timeBegin);
+ xData.add(Date.from(timeBegin.toInstant()));
yData.add(convertData(state));
}
// Now, get all the data between the start and end time
- filter.setBeginDate(ZonedDateTime.ofInstant(timeBegin.toInstant(), timeZoneProvider.getTimeZone()));
- filter.setEndDate(ZonedDateTime.ofInstant(timeEnd.toInstant(), timeZoneProvider.getTimeZone()));
+ filter.setBeginDate(timeBegin);
+ filter.setEndDate(timeEnd);
filter.setPageSize(Integer.MAX_VALUE);
filter.setOrdering(Ordering.ASCENDING);
@@ -342,7 +358,7 @@ boolean addItem(Chart chart, QueryablePersistenceService service, Date timeBegin
// Lastly, add the final state at the endtime
if (state != null) {
- xData.add(timeEnd);
+ xData.add(Date.from(timeEnd.toInstant()));
yData.add(convertData(state));
}
@@ -358,55 +374,18 @@ boolean addItem(Chart chart, QueryablePersistenceService service, Date timeBegin
yData.add(yData.iterator().next());
}
- Series series = chart.addSeries(label, xData, yData);
+ XYSeries series = chart.addSeries(label, xData, yData);
float lineWidth = (float) chartTheme.getLineWidth(dpi);
series.setLineStyle(new BasicStroke(lineWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
- series.setMarker(SeriesMarker.NONE);
+ series.setMarker(new None());
series.setLineColor(color);
- // If the start value is below the median, then count legend position down
- // Otherwise count up.
- // We use this to decide whether to put the legend in the top or bottom corner.
- if (yData.iterator().next().floatValue() > ((series.getYMax() - series.getYMin()) / 2 + series.getYMin())) {
- legendPosition++;
- } else {
- legendPosition--;
- }
-
+ legendPositionDecider.addData(series, yData);
return true;
}
@Override
public ImageType getChartType() {
- return (ImageType.png);
- }
-
- /**
- * Retrieve a chart theme by it's name. If no name is given or no theme with the given name exists, the
- * {@link DefaultChartProvider#CHART_THEME_DEFAULT_NAME default theme} gets returned.
- *
- * @param name the {@link ChartTheme#getThemeName() theme name}
- * @return {@link ChartTheme}
- */
- private ChartTheme getChartTheme(String name) {
- // if the static chartThemes hashmap is nul, we have to fill it first with all available themes,
- // based on the theme name
- if (chartThemes == null) {
- chartThemes = new HashMap<>();
- for (ChartTheme theme : CHART_THEMES_AVAILABLE) {
- chartThemes.put(theme.getThemeName(), theme);
- }
- }
- String chartThemeName = name;
- // no theme name -> default theme
- if (name == null || name.isBlank()) {
- chartThemeName = CHART_THEME_DEFAULT_NAME;
- }
- ChartTheme chartTheme = chartThemes.get(chartThemeName);
- if (chartTheme == null) {
- // no theme with the given name found -> default theme
- chartTheme = chartThemes.get(CHART_THEME_DEFAULT_NAME);
- }
- return chartTheme;
+ return ImageType.png;
}
}