package fr.openwide.core.wicket.more.jqplot.component;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import nl.topicus.wqplot.components.JQPlot;
import nl.topicus.wqplot.options.PlotOptions;
import nl.topicus.wqplot.options.PlotSeries;
import nl.topicus.wqplot.options.PlotTick;
import org.apache.wicket.Component;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import fr.openwide.core.commons.util.functional.Predicates2;
import fr.openwide.core.wicket.markup.html.panel.InvisiblePanel;
import fr.openwide.core.wicket.more.condition.Condition;
import fr.openwide.core.wicket.more.jqplot.config.DefaultJQPlotRendererOptionsFactory;
import fr.openwide.core.wicket.more.jqplot.config.IJQPlotConfigurer;
import fr.openwide.core.wicket.more.jqplot.data.adapter.IJQPlotDataAdapter;
import fr.openwide.core.wicket.more.jqplot.plugin.autoresize.JQPlotAutoresizeJavascriptReference;
import fr.openwide.core.wicket.more.markup.html.basic.PlaceholderContainer;
/**
* A wrapper component around {@link JQPlot} that makes it dynamic:
* <ul>
* <li>It uses a {@link IJQPlotDataAdapter} to generate JQPlot's data on each rendering
* <li>It uses {@link IJQPlotConfigurer}s to refresh JQPlot's options on each rendering
* <li>It includes a built-in placeholder for cases when there's no data to be shown (see {@link #setNonEmptyDataPredicate(Predicate)})
* </ul>
*
* <strong>Note:</strong> don't use this class directly; use one of its subclasses instead.
*
* @see JQPlotPiePanel
* @see JQPlotLinesPanel
* @see JQPlotStackedLinesPanel
* @see JQPlotBarsPanel
* @see JQPlotStackedBarsPanel
*/
public abstract class JQPlotPanel<S, K, V extends Number & Comparable<V>> extends Panel {
private static final long serialVersionUID = -138468277129057498L;
private final IJQPlotDataAdapter<S, K, V> dataAdapter;
@SpringBean
private DefaultJQPlotRendererOptionsFactory optionsFactory;
@SpringBean
private IJQPlotConfigurer<Object, Object> defaultjqPlotConfigurer;
/*
* Also contains the default configurer (in first position) and the dataAdapter (in second position).
*/
private final Collection<IJQPlotConfigurer<? super S, ? super K>> jqPlotConfigurers = Lists.newLinkedList();
private JQPlot jqPlot;
private Predicate<Collection<V>> nonEmptyDataPredicate = Predicates2.notEmpty();
protected JQPlotPanel(String id, IJQPlotDataAdapter<S, K, V> dataAdapter) {
super(id, dataAdapter);
add(defaultjqPlotConfigurer);
this.dataAdapter = dataAdapter;
add(dataAdapter);
setOutputMarkupId(true);
}
@Override
protected void onDetach() {
super.onDetach();
for (IJQPlotConfigurer<? super S, ? super K> configurer : jqPlotConfigurers) {
configurer.detach();
}
}
protected Component createTitleActionsPanel(String wicketId) {
return new InvisiblePanel(wicketId);
}
@SafeVarargs
public final JQPlotPanel<S, K, V> add(IJQPlotConfigurer<? super S, ? super K> configurer,
IJQPlotConfigurer<? super S, ? super K> ... otherConfigurers) {
jqPlotConfigurers.addAll(Lists.asList(configurer, otherConfigurers));
return this;
}
@Override
protected void onInitialize() {
super.onInitialize();
this.jqPlot = new JQPlot("jqPlot", dataAdapter);
add(jqPlot);
add(new PlaceholderContainer("jqPlotPlaceholder").condition(Condition.componentVisible(jqPlot)));
jqPlot.add(new Behavior() {
private static final long serialVersionUID = 1L;
@Override
public void renderHead(Component component, IHeaderResponse response) {
response.render(JavaScriptHeaderItem.forReference(JQPlotAutoresizeJavascriptReference.get()));
}
});
jqPlot.add(
new Condition() {
private static final long serialVersionUID = 1L;
@Override
public boolean applies() {
/*
* We must implement this with a custom condition because the nonEmptyDataPredicate
* might change later (and thus a call to EnclosureBehavior.model(nonEmptyDataPredicate, model)
* would be incorrect).
*/
return nonEmptyDataPredicate.apply(Collections.unmodifiableCollection(dataAdapter.getValues()));
}
}
.thenShow()
);
}
@Override
protected void onConfigure() {
super.onConfigure();
PlotOptions options = jqPlot.getOptions();
Map<S, PlotSeries> seriesMap = Maps.newLinkedHashMap();
for (S series : dataAdapter.getSeriesTicks()) {
seriesMap.put(series, new PlotSeries());
}
Map<K, PlotTick> keysMap = Maps.newLinkedHashMap();
for (K key : dataAdapter.getKeysTicks()) {
keysMap.put(key, new PlotTick(key));
}
Locale locale = getLocale();
for (IJQPlotConfigurer<? super S, ? super K> configurer : jqPlotConfigurers) {
configurer.configure(options, seriesMap, keysMap, locale);
}
dataAdapter.afterConfigure(options, seriesMap, keysMap, locale);
if (!seriesMap.isEmpty()) {
options.setSeries(Lists.newArrayList(seriesMap.values()));
}
if (!keysMap.isEmpty()) {
options.getAxes().getXaxis().setTicks(Lists.newArrayList(keysMap.values()));
}
}
public JQPlotPanel<S, K, V> setNonEmptyDataPredicate(Predicate<Collection<V>> nonEmptyDataPredicate) {
this.nonEmptyDataPredicate = checkNotNull(nonEmptyDataPredicate);
return this;
}
public DefaultJQPlotRendererOptionsFactory getOptionsFactory() {
return optionsFactory;
}
public void setOptionsFactory(DefaultJQPlotRendererOptionsFactory optionsFactory) {
this.optionsFactory = optionsFactory;
}
}