package dev.mars3142.fhq.views.dashboard; import com.vaadin.flow.component.Component; import com.vaadin.flow.component.board.Board; import com.vaadin.flow.component.charts.Chart; import com.vaadin.flow.component.charts.model.*; import com.vaadin.flow.component.grid.ColumnTextAlign; import com.vaadin.flow.component.grid.Grid; import com.vaadin.flow.component.grid.GridVariant; import com.vaadin.flow.component.html.H2; import com.vaadin.flow.component.html.Main; import com.vaadin.flow.component.html.Span; import com.vaadin.flow.component.icon.Icon; import com.vaadin.flow.component.icon.VaadinIcon; import com.vaadin.flow.component.orderedlayout.FlexComponent; import com.vaadin.flow.component.orderedlayout.HorizontalLayout; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.select.Select; import com.vaadin.flow.data.renderer.ComponentRenderer; import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.router.Route; import com.vaadin.flow.router.RouteAlias; import com.vaadin.flow.theme.lumo.LumoUtility.BoxSizing; import com.vaadin.flow.theme.lumo.LumoUtility.FontSize; import com.vaadin.flow.theme.lumo.LumoUtility.FontWeight; import com.vaadin.flow.theme.lumo.LumoUtility.Margin; import com.vaadin.flow.theme.lumo.LumoUtility.Padding; import com.vaadin.flow.theme.lumo.LumoUtility.TextColor; import dev.mars3142.fhq.views.MainLayout; import dev.mars3142.fhq.views.dashboard.ServiceHealth.Status; @PageTitle("Dashboard") @Route(value = "", layout = MainLayout.class) @RouteAlias(value = "", layout = MainLayout.class) public class DashboardView extends Main { public DashboardView() { addClassName("dashboard-view"); Board board = new Board(); board.addRow(createHighlight("Current users", "745", 33.7), createHighlight("View events", "54.6k", -112.45), createHighlight("Conversion rate", "18%", 3.9), createHighlight("Custom metric", "-123.45", 0.0)); board.addRow(createViewEvents()); board.addRow(createServiceHealth(), createResponseTimes()); add(board); } private Component createHighlight(String title, String value, Double percentage) { VaadinIcon icon = VaadinIcon.ARROW_UP; String prefix = ""; String theme = "badge"; if (percentage == 0) { prefix = "±"; } else if (percentage > 0) { prefix = "+"; theme += " success"; } else if (percentage < 0) { icon = VaadinIcon.ARROW_DOWN; theme += " error"; } H2 h2 = new H2(title); h2.addClassNames(FontWeight.NORMAL, Margin.NONE, TextColor.SECONDARY, FontSize.XSMALL); Span span = new Span(value); span.addClassNames(FontWeight.SEMIBOLD, FontSize.XXXLARGE); Icon i = icon.create(); i.addClassNames(BoxSizing.BORDER, Padding.XSMALL); Span badge = new Span(i, new Span(prefix + percentage.toString())); badge.getElement().getThemeList().add(theme); VerticalLayout layout = new VerticalLayout(h2, span, badge); layout.addClassName(Padding.LARGE); layout.setPadding(false); layout.setSpacing(false); return layout; } private Component createViewEvents() { // Header Select year = new Select(); year.setItems("2011", "2012", "2013", "2014", "2015", "2016", "2017", "2018", "2019", "2020", "2021"); year.setValue("2021"); year.setWidth("100px"); HorizontalLayout header = createHeader("View events", "City/month"); header.add(year); // Chart Chart chart = new Chart(ChartType.AREASPLINE); Configuration conf = chart.getConfiguration(); conf.getChart().setStyledMode(true); XAxis xAxis = new XAxis(); xAxis.setCategories("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); conf.addxAxis(xAxis); conf.getyAxis().setTitle("Values"); PlotOptionsAreaspline plotOptions = new PlotOptionsAreaspline(); plotOptions.setPointPlacement(PointPlacement.ON); plotOptions.setMarker(new Marker(false)); conf.addPlotOptions(plotOptions); conf.addSeries(new ListSeries("Berlin", 189, 191, 291, 396, 501, 403, 609, 712, 729, 942, 1044, 1247)); conf.addSeries(new ListSeries("London", 138, 246, 248, 348, 352, 353, 463, 573, 778, 779, 885, 887)); conf.addSeries(new ListSeries("New York", 65, 65, 166, 171, 293, 302, 308, 317, 427, 429, 535, 636)); conf.addSeries(new ListSeries("Tokyo", 0, 11, 17, 123, 130, 142, 248, 349, 452, 454, 458, 462)); // Add it all together VerticalLayout viewEvents = new VerticalLayout(header, chart); viewEvents.addClassName(Padding.LARGE); viewEvents.setPadding(false); viewEvents.setSpacing(false); viewEvents.getElement().getThemeList().add("spacing-l"); return viewEvents; } private Component createServiceHealth() { // Header HorizontalLayout header = createHeader("Service health", "Input / output"); // Grid Grid grid = new Grid(); grid.addThemeVariants(GridVariant.LUMO_NO_BORDER); grid.setAllRowsVisible(true); grid.addColumn(new ComponentRenderer<>(serviceHealth -> { Span status = new Span(); String statusText = getStatusDisplayName(serviceHealth); status.getElement().setAttribute("aria-label", "Status: " + statusText); status.getElement().setAttribute("title", "Status: " + statusText); status.getElement().getThemeList().add(getStatusTheme(serviceHealth)); return status; })).setHeader("").setFlexGrow(0).setAutoWidth(true); grid.addColumn(ServiceHealth::getCity).setHeader("City").setFlexGrow(1); grid.addColumn(ServiceHealth::getInput).setHeader("Input").setAutoWidth(true).setTextAlign(ColumnTextAlign.END); grid.addColumn(ServiceHealth::getOutput).setHeader("Output").setAutoWidth(true) .setTextAlign(ColumnTextAlign.END); grid.setItems(new ServiceHealth(Status.EXCELLENT, "Münster", 324, 1540), new ServiceHealth(Status.OK, "Cluj-Napoca", 311, 1320), new ServiceHealth(Status.FAILING, "Ciudad Victoria", 300, 1219)); // Add it all together VerticalLayout serviceHealth = new VerticalLayout(header, grid); serviceHealth.addClassName(Padding.LARGE); serviceHealth.setPadding(false); serviceHealth.setSpacing(false); serviceHealth.getElement().getThemeList().add("spacing-l"); return serviceHealth; } private Component createResponseTimes() { HorizontalLayout header = createHeader("Response times", "Average across all systems"); // Chart Chart chart = new Chart(ChartType.PIE); Configuration conf = chart.getConfiguration(); conf.getChart().setStyledMode(true); chart.setThemeName("gradient"); DataSeries series = new DataSeries(); series.add(new DataSeriesItem("System 1", 12.5)); series.add(new DataSeriesItem("System 2", 12.5)); series.add(new DataSeriesItem("System 3", 12.5)); series.add(new DataSeriesItem("System 4", 12.5)); series.add(new DataSeriesItem("System 5", 12.5)); series.add(new DataSeriesItem("System 6", 12.5)); conf.addSeries(series); // Add it all together VerticalLayout serviceHealth = new VerticalLayout(header, chart); serviceHealth.addClassName(Padding.LARGE); serviceHealth.setPadding(false); serviceHealth.setSpacing(false); serviceHealth.getElement().getThemeList().add("spacing-l"); return serviceHealth; } private HorizontalLayout createHeader(String title, String subtitle) { H2 h2 = new H2(title); h2.addClassNames(FontSize.XLARGE, Margin.NONE); Span span = new Span(subtitle); span.addClassNames(TextColor.SECONDARY, FontSize.XSMALL); VerticalLayout column = new VerticalLayout(h2, span); column.setPadding(false); column.setSpacing(false); HorizontalLayout header = new HorizontalLayout(column); header.setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN); header.setSpacing(false); header.setWidthFull(); return header; } private String getStatusDisplayName(ServiceHealth serviceHealth) { Status status = serviceHealth.getStatus(); if (status == Status.OK) { return "Ok"; } else if (status == Status.FAILING) { return "Failing"; } else if (status == Status.EXCELLENT) { return "Excellent"; } else { return status.toString(); } } private String getStatusTheme(ServiceHealth serviceHealth) { Status status = serviceHealth.getStatus(); String theme = "badge primary small"; if (status == Status.EXCELLENT) { theme += " success"; } else if (status == Status.FAILING) { theme += " error"; } return theme; } }