package fr.openwide.core.wicket.more.jqplot.data.adapter; import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; import javax.annotation.Nonnull; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import fr.openwide.core.commons.util.functional.SerializableFunction; import fr.openwide.core.wicket.more.jqplot.util.LabelledSeries; import fr.openwide.core.wicket.more.jqplot.util.LabelledSeriesEntry; import fr.openwide.core.wicket.more.rendering.Renderer; import nl.topicus.wqplot.data.AbstractSeries; import nl.topicus.wqplot.data.Series; import nl.topicus.wqplot.data.SeriesEntry; import nl.topicus.wqplot.options.PlotOptions; import nl.topicus.wqplot.options.PlotSeries; import nl.topicus.wqplot.options.PlotTick; import nl.topicus.wqplot.options.PlotTooltipAxes; public final class JQPlotDataAdapters { private JQPlotDataAdapters() { } public static <S, K, V extends Number> IJQPlotDataAdapter<S, K, V> addPercentLabels(final IJQPlotDataAdapter<S, K, V> adapter, final Renderer<? super Double> percentRenderer) { return new ForwardingJQPlotDataAdapter<S, K, V>(adapter) { private static final long serialVersionUID = 1L; @Override protected Collection<? extends AbstractSeries<?, V, ?>> transformOnGet(Collection<? extends AbstractSeries<?, V, ?>> delegateObject, Locale locale) { return addPercentLabels(delegateObject, percentRenderer, locale); } @Override public void configure(PlotOptions options, Map<? extends S, PlotSeries> seriesMap, Map<? extends K, PlotTick> keysMap, Locale locale) { options.getHighlighter() .setTooltipAxes(PlotTooltipAxes.y) .setYvalues(2) .setFormatString("%s - %s (%s)") .setUseAxesFormatters(true); } }; } private static <K, V extends Number, E extends SeriesEntry<? extends K, V>> Collection<LabelledSeries<K, V>> addPercentLabels( Collection<? extends AbstractSeries<? extends K, V, ? extends E>> input, Renderer<? super Double> percentRenderer, Locale locale) { final Map<K, Double> sums = Maps.newHashMap(); for (Series<?, ?, ? extends E> series : input) { for (E entry : series.getData()) { K key = entry.getKey(); Double currentSum = sums.get(key); if (currentSum == null) { currentSum = 0.0; } V currentValue = entry.getValue(); if (currentValue != null) { currentSum += currentValue.doubleValue(); } sums.put(key, currentSum); } } Renderer<E> entryRenderer = percentRenderer.onResultOf(new SerializableFunction<E, Double>() { private static final long serialVersionUID = 595417526944754574L; @Override public Double apply(@Nonnull E entry) { V absoluteValue = entry.getValue(); if (absoluteValue == null) { return null; } else { return absoluteValue.doubleValue() / sums.get(entry.getKey()); } } }); return addLabels(input, entryRenderer, locale); } private static <K, V extends Number, E extends SeriesEntry<? extends K, V>> Collection<LabelledSeries<K, V>> addLabels( Collection<? extends AbstractSeries<? extends K, V, ? extends E>> input, Renderer<? super E> renderer, Locale locale) { List<LabelledSeries<K, V>> seriesList = Lists.newArrayListWithCapacity(input.size()); for (Series<?, ?, ? extends E> series : input) { LabelledSeries<K, V> copiedSeries = new LabelledSeries<>(); seriesList.add(copiedSeries); for (E entry : series.getData()) { copiedSeries.addEntry(new LabelledSeriesEntry<K, V>(entry.getKey(), entry.getValue(), renderer.render(entry, locale))); } } return seriesList; } public static <S, K, V extends Number & Comparable<V>> IJQPlotDataAdapter<S, K, V> fix(final IJQPlotDataAdapter<S, K, V> adapter) { return new ForwardingJQPlotDataAdapter<S, K, V>(adapter) { private static final long serialVersionUID = 1L; @Override public void afterConfigure(PlotOptions options, Map<? extends S, PlotSeries> seriesMap, Map<? extends K, PlotTick> keysMap, Locale locale) { // jqPlot bug workaround // To avoid the problems related to data too low: jqPlot duplicates the values on the Y axis when // we use an integer format and that the differences are decimal. String formatString = options.getAxes().getYaxis().getTickOptions().getFormatString(); if ("%d".equals(formatString) || "%'d".equals(formatString)) { Collection<? extends V> values = getValues(); double min = values.isEmpty() ? 0.0 : Ordering.natural().min(values).doubleValue(); double max = values.isEmpty() ? 1.0 : Ordering.natural().max(values).doubleValue(); Boolean fillToZero = options.getSeriesDefaults().getFillToZero(); if ((max - min) <= 5.0) { options.getAxes().getYaxis() .setMin(0.0 <= min && max <= 5.0 || fillToZero != null && fillToZero ? 0 : Double.valueOf(min).intValue() - 2) .setMax(Double.valueOf(max).intValue() + 2) .setNumberTicks(5); } else { options.getAxes().getYaxis() .setMin(null) .setMax(null) .setNumberTicks(null); } } super.configure(options, seriesMap, keysMap, locale); } @Override protected Collection<? extends AbstractSeries<?, V, ?>> transformOnGet( Collection<? extends AbstractSeries<?, V, ?>> delegateObject, Locale locale) { // jqPlot bug workaround // jqPlot doesn't work if we give it an empty series or null ("No data specified") // The only way to make it accept the fact that we want to display a series (for instance in the legend) even // if there is no data is to add a null entry in this series. for (AbstractSeries<?, ?, ?> series : delegateObject) { if (series.getData().isEmpty()) { series.addEntry(null); } } return delegateObject; } }; } }