package fr.openwide.core.showcase.web.application.widgets.page; import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.time.DateUtils; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.model.IModel; import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.util.ListModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; import com.google.common.collect.Lists; import com.google.common.collect.Range; import com.google.common.collect.Table; import fr.openwide.core.commons.util.collections.DateDiscreteDomain; import fr.openwide.core.showcase.core.business.statistic.service.IStatisticService; import fr.openwide.core.showcase.core.business.user.model.UserGender; import fr.openwide.core.wicket.more.jqplot.behavior.JQPlotReplotBehavior; import fr.openwide.core.wicket.more.jqplot.component.JQPlotBarsPanel; import fr.openwide.core.wicket.more.jqplot.component.JQPlotLinesPanel; import fr.openwide.core.wicket.more.jqplot.component.JQPlotPiePanel; import fr.openwide.core.wicket.more.jqplot.component.JQPlotStackedBarsPanel; import fr.openwide.core.wicket.more.jqplot.component.JQPlotStackedLinesPanel; import fr.openwide.core.wicket.more.jqplot.config.JQPlotConfigurers; import fr.openwide.core.wicket.more.jqplot.data.adapter.IJQPlotDataAdapter; import fr.openwide.core.wicket.more.jqplot.data.adapter.JQPlotContinuousDateKeysDataAdapter; import fr.openwide.core.wicket.more.jqplot.data.adapter.JQPlotDiscreteKeysDataAdapter; import fr.openwide.core.wicket.more.link.descriptor.IPageLinkDescriptor; import fr.openwide.core.wicket.more.link.descriptor.builder.LinkDescriptorBuilder; import fr.openwide.core.wicket.more.markup.html.template.js.jquery.plugins.bootstrap.tab.BootstrapTabBehavior; import fr.openwide.core.wicket.more.model.ContiguousSetModel; import fr.openwide.core.wicket.more.model.RangeModel; import fr.openwide.core.wicket.more.rendering.EnumRenderer; import fr.openwide.core.wicket.more.rendering.Renderer; import fr.openwide.core.wicket.more.util.DatePattern; import nl.topicus.wqplot.components.JQPlot; import nl.topicus.wqplot.data.BaseSeries; import nl.topicus.wqplot.data.SimpleNumberSeries; import nl.topicus.wqplot.options.PlotBarRendererOptions; import nl.topicus.wqplot.options.PlotCanvasAxisTickRendererOptions; import nl.topicus.wqplot.options.PlotLegendLocation; import nl.topicus.wqplot.options.PlotMarkerStyle; import nl.topicus.wqplot.options.PlotOptions; import nl.topicus.wqplot.options.PlotPieRendererOptions; import nl.topicus.wqplot.options.PlotTooltipAxes; public class StatisticsPage extends WidgetsTemplate { private static final long serialVersionUID = -2974578921366640131L; private static final Date START_DATE; private static final Date END_DATE; static { try { START_DATE = DateUtils.parseDate("2020-01-01", "yyyy-MM-dd"); END_DATE = DateUtils.parseDate("2020-03-01", "yyyy-MM-dd"); } catch (ParseException e) { throw new IllegalStateException(e); } } private static final List<Integer> ACTIVE_USERS_STATS = Lists.newArrayList(40,61,108,125,134,159); private static final List<Integer> SALES_P1_STATS = Lists.newArrayList(14,8,35,40); private static final List<Integer> SALES_P2_STATS = Lists.newArrayList(5,11,22,3); private static final List<Integer> SALES_P3_STATS = Lists.newArrayList(23,38,55,24); private static final String P1_LEGEND = "Product 1"; private static final String P2_LEGEND = "Product 2"; private static final String P3_LEGEND = "Product 3"; private static final String Q1_TICK = "Q1"; private static final String Q2_TICK = "Q2"; private static final String Q3_TICK = "Q3"; private static final String Q4_TICK = "Q4"; @SpringBean private IStatisticService statisticService; public static final IPageLinkDescriptor linkDescriptor() { return LinkDescriptorBuilder.start() .page(StatisticsPage.class); } public StatisticsPage(PageParameters parameters) { super(parameters); WebMarkupContainer tabContainer = new WebMarkupContainer("tabContainer"); add(tabContainer); tabContainer.add(new BootstrapTabBehavior()); /* Makes sure the plots that are not on the initially active tab (and thus will not be * plotted immediately) will at least be plotted when switching tabs. */ tabContainer.add(new JQPlotReplotBehavior("shown.bs.tab")); WebMarkupContainer highLevelPlotsTab = new WebMarkupContainer("highLevelPlotsTab"); tabContainer.add(highLevelPlotsTab); IModel<Map<UserGender, Integer>> userCountByGenderStatisticsModel = new LoadableDetachableModel<Map<UserGender, Integer>>() { private static final long serialVersionUID = 1L; @Override protected Map<UserGender, Integer> load() { return statisticService.getUserCountByGenderStatistics(); } }; highLevelPlotsTab.add(new JQPlotPiePanel<UserGender, Integer>( "piePanel", userCountByGenderStatisticsModel, EnumRenderer.get() )); IModel<Date> startDateModel = new Model<>(START_DATE); IModel<Date> endDateModel = new Model<>(END_DATE); final IModel<Range<Date>> timeboundsModel = RangeModel.closed(startDateModel, endDateModel); IModel<Table<UserGender, Date, Integer>> userCreationCountByGenderByWeekModel = new LoadableDetachableModel<Table<UserGender, Date, Integer>>() { private static final long serialVersionUID = 1L; @Override protected Table<UserGender, Date, Integer> load() { return statisticService.getUserCreationCountByGenderByWeekStatistics(timeboundsModel.getObject()); } @Override protected void onDetach() { super.onDetach(); timeboundsModel.detach(); } }; IJQPlotDataAdapter<UserGender, Date, Integer> userCreationCountByGenderByWeekContinuousDataAdapter = new JQPlotContinuousDateKeysDataAdapter<>(userCreationCountByGenderByWeekModel, "%d/%m/%y"); highLevelPlotsTab.add( new JQPlotLinesPanel<>( "linesPanel", userCreationCountByGenderByWeekContinuousDataAdapter ) .add( JQPlotConfigurers.title("widgets.statistics.panel.lines.title"), JQPlotConfigurers.xAxisLabel("widgets.statistics.panel.lines.axis.x"), JQPlotConfigurers.yAxisLabel("widgets.statistics.panel.lines.axis.y"), JQPlotConfigurers.yAxisWindow(0, null), JQPlotConfigurers.seriesLabels(EnumRenderer.get()) ) ); IModel<? extends Set<Date>> timelineModel = ContiguousSetModel.create(timeboundsModel, DateDiscreteDomain.weeks()); IJQPlotDataAdapter<UserGender, Date, Integer> userCreationCountByGenderByWeekDiscreteDataAdapter = new JQPlotDiscreteKeysDataAdapter<UserGender, Date, Integer>( userCreationCountByGenderByWeekModel, null, timelineModel, // For unrepresented dates Model.of(0), // For missing values, Renderer.fromDatePattern(DatePattern.REALLY_SHORT_DATE) ); highLevelPlotsTab.add( new JQPlotLinesPanel<>( "linesDiscreteAxisPanel", userCreationCountByGenderByWeekDiscreteDataAdapter ) .add( JQPlotConfigurers.title("widgets.statistics.panel.linesDiscreteAdapter.title"), JQPlotConfigurers.xAxisLabel("widgets.statistics.panel.linesDiscreteAdapter.axis.x"), JQPlotConfigurers.yAxisLabel("widgets.statistics.panel.linesDiscreteAdapter.axis.y"), JQPlotConfigurers.yAxisWindow(0, null), JQPlotConfigurers.seriesLabels(EnumRenderer.get()) ), new JQPlotStackedLinesPanel<>( "stackedLinesPanel", userCreationCountByGenderByWeekDiscreteDataAdapter ) .add( JQPlotConfigurers.title("widgets.statistics.panel.stackedLines.title"), JQPlotConfigurers.xAxisLabel("widgets.statistics.panel.stackedLines.axis.x"), JQPlotConfigurers.yAxisLabel("widgets.statistics.panel.stackedLines.axis.y"), JQPlotConfigurers.seriesLabels(EnumRenderer.get()) ), new JQPlotBarsPanel<>( "barsPanel", userCreationCountByGenderByWeekDiscreteDataAdapter ) .add( JQPlotConfigurers.title("widgets.statistics.panel.bars.title"), JQPlotConfigurers.xAxisLabel("widgets.statistics.panel.bars.axis.x"), JQPlotConfigurers.yAxisLabel("widgets.statistics.panel.bars.axis.y"), JQPlotConfigurers.seriesLabels(EnumRenderer.get()) ), new JQPlotStackedBarsPanel<>( "stackedBarsPanel", userCreationCountByGenderByWeekDiscreteDataAdapter ) .add( JQPlotConfigurers.title("widgets.statistics.panel.stackedBars.title"), JQPlotConfigurers.xAxisLabel("widgets.statistics.panel.stackedBars.axis.x"), JQPlotConfigurers.yAxisLabel("widgets.statistics.panel.stackedBars.axis.y"), JQPlotConfigurers.seriesLabels(EnumRenderer.get()) ) ); WebMarkupContainer lowLevelPlotsTab = new WebMarkupContainer("lowLevelPlotsTab"); tabContainer.add(lowLevelPlotsTab); lowLevelPlotsTab.add( createDefaultChartWithLowLevelAPI("defaultChart"), createSalesChartWithLowLevelAPI("salesChart"), createPieChartWithLowLevelAPI("pieChart"), createStackedLinesChartWithLowLevelAPI("stackedLinesChart") ); } @Override protected Class<? extends WebPage> getSecondMenuPage() { return StatisticsPage.class; } private static JQPlot createDefaultChartWithLowLevelAPI(String wicketId) { // Create series SimpleNumberSeries<Integer> accountsCreated = new SimpleNumberSeries<Integer>(); for (Integer account : ACTIVE_USERS_STATS) { accountsCreated.addEntry(account); } // Build JQPlot object with given series @SuppressWarnings("unchecked") JQPlot defaultChart = new JQPlot(wicketId, new ListModel<SimpleNumberSeries<Integer>>(Lists.newArrayList(accountsCreated))); // Select few options PlotOptions defaultChartOptions = defaultChart.getOptions(); defaultChartOptions.setTitle("Active users"); defaultChartOptions.getAxes().getXaxis().setRenderer("$.jqplot.CategoryAxisRenderer"); defaultChartOptions.getAxes().getXaxis().setTicks("2007", "2008", "2009", "2010", "2011", "2012"); return defaultChart; } private static JQPlot createSalesChartWithLowLevelAPI(String wicketId) { // Create series SimpleNumberSeries<Integer> product1series = new SimpleNumberSeries<Integer>(); product1series.addEntry(SALES_P1_STATS.get(0)); product1series.addEntry(SALES_P1_STATS.get(1)); product1series.addEntry(SALES_P1_STATS.get(2)); product1series.addEntry(SALES_P1_STATS.get(3)); SimpleNumberSeries<Integer> product2series = new SimpleNumberSeries<Integer>(); product2series.addEntry(SALES_P2_STATS.get(0)); product2series.addEntry(SALES_P2_STATS.get(1)); product2series.addEntry(SALES_P2_STATS.get(2)); product2series.addEntry(SALES_P2_STATS.get(3)); SimpleNumberSeries<Integer> product3series = new SimpleNumberSeries<Integer>(); product3series.addEntry(SALES_P3_STATS.get(0)); product3series.addEntry(SALES_P3_STATS.get(1)); product3series.addEntry(SALES_P3_STATS.get(2)); product3series.addEntry(SALES_P3_STATS.get(3)); // Build JQPlot object with given series @SuppressWarnings("unchecked") JQPlot salesChart = new JQPlot(wicketId, new ListModel<SimpleNumberSeries<Integer>>( Lists.newArrayList(product1series, product2series, product3series))); // Chart options PlotOptions salesChartOptions = salesChart.getOptions(); salesChartOptions.setTitle("Sales in 2011"); salesChartOptions.getTitle().setFontSize("16pt").setTextAlign("right"); salesChartOptions.getGrid().setBackground("#D9EBFC").setBorderWidth(2.5); // Chart type salesChartOptions.getSeriesDefaults().setRenderer("$.jqplot.BarRenderer"); PlotBarRendererOptions renderOptions = new PlotBarRendererOptions(); renderOptions.setBarMargin(50.0); salesChartOptions.getSeriesDefaults().setRendererOptions(renderOptions); salesChartOptions.setStackSeries(true); // X axis salesChartOptions.getAxes().getXaxis().setRenderer("$.jqplot.CategoryAxisRenderer"); salesChartOptions.getAxes().getXaxis().setTicks(Q1_TICK, Q2_TICK, Q3_TICK, Q4_TICK); PlotCanvasAxisTickRendererOptions xTickOptions = new PlotCanvasAxisTickRendererOptions() .setFontSize("10pt") .setFontWeight("normal") .setFontStretch(1.0); salesChartOptions.getAxes().getXaxis().setTickOptions(xTickOptions); // Y axis salesChartOptions.getAxes().getYaxis().setMin(0).setAutoscale(true); salesChartOptions.getAxes().getYaxis().getTickOptions().setFontSize("10pt"); // Legend salesChartOptions.getLegend().setShow(true).setLocation(PlotLegendLocation.nw); salesChartOptions.addNewSeries().setLabel(P1_LEGEND); salesChartOptions.addNewSeries().setLabel(P2_LEGEND); salesChartOptions.addNewSeries().setLabel(P3_LEGEND); return salesChart; } private static JQPlot createPieChartWithLowLevelAPI(String wicketId) { // Create series BaseSeries<String, Double> line = new BaseSeries<String, Double>(); line.addEntry("frogs", 3.0); line.addEntry("buzzards", 7.0); line.addEntry("deer", 2.5); line.addEntry("turkeys", 6.0); line.addEntry("moles", 5.0); line.addEntry("ground hogs", 4.0); // Build JQPlot object with given series @SuppressWarnings("unchecked") JQPlot pieChart = new JQPlot(wicketId, new ListModel<BaseSeries<String, Double>>(Lists.newArrayList(line))); // Chart options PlotOptions pieChartOptions = pieChart.getOptions(); pieChartOptions.setTitle("Pie Chart with Legend and sliceMargin"); PlotPieRendererOptions renderOptions = new PlotPieRendererOptions(); renderOptions.setSliceMargin(8.0); pieChartOptions.getSeriesDefaults().setRenderer("$.jqplot.PieRenderer").setRendererOptions(renderOptions); pieChartOptions.getLegend().setShow(true).setLocation(PlotLegendLocation.nw); return pieChart; } private static JQPlot createStackedLinesChartWithLowLevelAPI(String wicketId) { // Create series BaseSeries<Date, Double> product1series = new BaseSeries<Date, Double>(); Calendar currentMonth = GregorianCalendar.getInstance(); currentMonth.set(2011, 8, 1); currentMonth = DateUtils.truncate(currentMonth, Calendar.MONTH); Calendar dateMax = GregorianCalendar.getInstance(); dateMax.set(2012, 8, 1); dateMax = DateUtils.truncate(dateMax, Calendar.MONTH); while (currentMonth.compareTo(dateMax) <= 0) { product1series.addEntry(currentMonth.getTime(), Math.random() * 8000 + 1000); currentMonth.add(Calendar.MONTH, 1); } BaseSeries<Date, Double> product2series = new BaseSeries<Date, Double>(); currentMonth.set(2011, 8, 1); currentMonth = DateUtils.truncate(currentMonth, Calendar.MONTH); while (currentMonth.compareTo(dateMax) <= 0) { product2series.addEntry(currentMonth.getTime(), Math.random() * 18000); currentMonth.add(Calendar.MONTH, 1); } BaseSeries<Date, Double> product3series = new BaseSeries<Date, Double>(); currentMonth.set(2011, 8, 1); currentMonth = DateUtils.truncate(currentMonth, Calendar.MONTH); while (currentMonth.compareTo(dateMax) <= 0) { product3series.addEntry(currentMonth.getTime(), Math.random() * 3000); currentMonth.add(Calendar.MONTH, 1); } // Build JQPlot object with given series @SuppressWarnings("unchecked") JQPlot stackedLinesChart = new JQPlot(wicketId, new ListModel<BaseSeries<Date, Double>>( Lists.newArrayList(product1series, product2series, product3series))); // Chart options PlotOptions stackedLinesOptions = stackedLinesChart.getOptions(); stackedLinesOptions.setTitle("Results by month, 2011 - 2012"); stackedLinesOptions.getTitle().setFontSize("16pt").setTextAlign("right"); stackedLinesOptions.setStackSeries(true); stackedLinesOptions.getSeriesDefaults() .setShowMarker(false) .setShadow(false) .setFill(true) .setFillAndStroke(true); // Markers stackedLinesOptions.getSeriesDefaults().getMarkerOptions() .setShow(true) .setStyle(PlotMarkerStyle.filledSquare) .setSize(6.0) .setLineWidth(1.0); // X axis Calendar calendarMin = GregorianCalendar.getInstance(); calendarMin.set(2011, 8, 1); calendarMin = DateUtils.truncate(calendarMin, Calendar.MONTH); Calendar calendarMax = GregorianCalendar.getInstance(); calendarMax.set(2012, 8, 1); calendarMax = DateUtils.truncate(calendarMax, Calendar.MONTH); stackedLinesOptions.getAxes().getXaxis() .setRenderer("$.jqplot.DateAxisRenderer") .setTickRenderer("$.jqplot.CanvasAxisTickRenderer") .setTickInterval("1 month") .setMin(calendarMin.getTime()) .setMax(calendarMax.getTime()); stackedLinesOptions.getHighlighter() .setShow(true) .setShowTooltip(true) .setTooltipAxes(PlotTooltipAxes.y) .setFormatString("%d"); PlotCanvasAxisTickRendererOptions xTickOptions = new PlotCanvasAxisTickRendererOptions() .setFormatString("%B, %Y") .setFontSize("10pt") .setAngle(-30.0) .setFontWeight("normal"); stackedLinesOptions.getAxes().getXaxis().setTickOptions(xTickOptions); // Y axis stackedLinesOptions.getAxes().getYaxis().setMin(0.0).setAutoscale(true); stackedLinesOptions.getAxes().getYaxis().getTickOptions().setFontSize("10pt"); // Legend stackedLinesOptions.getLegend().setShow(true).setLocation(PlotLegendLocation.nw); stackedLinesOptions.addNewSeries().setLabel(P1_LEGEND); stackedLinesOptions.addNewSeries().setLabel(P2_LEGEND); stackedLinesOptions.addNewSeries().setLabel(P3_LEGEND); return stackedLinesChart; } }