Model based implementation
I've put up a newer, Wicket 1.4 based solution on a gist at github. Provides the same functionality - a component that displays a JFreeChart with tooltips and clickable entities. This solution uses models and thus allows the graph to be redrawn when the model data changes.
Original Code
This page details a component that integrates a JFreeChart chart and wicket producing a clickable imagemap integrated with a wicket AjaxLink, and tooltips specified via the tooltip generator of the JFreeChart.
The major component used by simply constructing and adding to your parent panel is MappedChart:
Markup:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmnls="http://www.w3.org/1999/xhtml" xmnls:wicket="http://wicket.apache.org"> <wicket:panel> <img wicket:id="image" /> <map wicket:id="imageMap" > <area wicket:id = "areas" /> </map> </wicket:panel> </html>
Java Code:
import org.apache.wicket.AttributeModifier; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.markup.html.panel.Panel; import org.apache.wicket.model.Model; import org.jfree.chart.JFreeChart; import org.jfree.chart.entity.ChartEntity; import org.jfree.chart.entity.EntityCollection; /** * Component that produces an image and associated image map from * the given JFreeChart chart. Uses the JFreeChart tooltip generator * to provide tooltips for the chart entities but does not use * the JFreeChart URL generator, but instead calls an Ajax callback * function/ * * @author Jonny Wray * */ public abstract class MappedChart extends Panel{ private static final long serialVersionUID = 4137002187344769160L; public MappedChart(String panelId, JFreeChart chart, int width, int height){ super(panelId); ChartImage image = new ChartImage("image", chart, width, height); String mapName = getPath(); image.add(new AttributeModifier("usemap", true, new Model("#"+mapName))); add(image); DynamicImageMap imageMap = constructImageMap(image, mapName); add(imageMap); } /** * The callback method that is called when a specific image map entity is * clicked on. * * @param target * @param entity */ protected abstract void onClickCallback(AjaxRequestTarget target, ChartEntity entity); private DynamicImageMap constructImageMap(ChartImage image, String mapName){ DynamicImageMap imageMap = new DynamicImageMap("imageMap", mapName); EntityCollection entities = image.getRenderingInfo().getEntityCollection(); if (entities != null) { int count = entities.getEntityCount(); for (int i = count - 1; i >= 0; i--) { final ChartEntity entity = entities.getEntity(i); imageMap.addArea(entity.getShapeType(), entity.getShapeCoords(), entity.getToolTipText(), new AjaxLink("link"){ private static final long serialVersionUID = -7982198051678987986L; @Override public void onClick(AjaxRequestTarget target) { onClickCallback(target, entity); } }); } } return imageMap; } }
Supporting code:
import org.apache.wicket.ajax.AjaxEventBehavior; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.IAjaxCallDecorator; import org.apache.wicket.ajax.calldecorator.CancelEventIfNoAjaxDecorator; import org.apache.wicket.ajax.markup.html.IAjaxLink; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.model.IModel; /** * A mapped area segment of an image map that adds an Ajax link * to the area as well as a regular tooltip. * * @author Jonny Wray * */ public class MapArea extends WebMarkupContainer{ private static final long serialVersionUID = -135521429660733572L; private String shape; private String coords; private String tooltipText; /** * Construct the map area * * @param id Component identifier * @param model Model * @param shape The specific area shape * @param coords The coordinates of the area as a comma separated list * @param tooltipText The tooltip text, or null to not include it * @param linkCallback The link callback function called when the area is click, or null to have no link functionality */ public MapArea(String id, IModel model, String shape, String coords, String tooltipText, final IAjaxLink linkCallback) { super(id, model); this.shape = shape; this.coords = coords; this.tooltipText = tooltipText; if(linkCallback != null){ add(new AjaxEventBehavior("onclick"){ private static final long serialVersionUID = 2615093257359874075L; @Override protected void onEvent(AjaxRequestTarget target) { linkCallback.onClick(target); } protected IAjaxCallDecorator getAjaxCallDecorator(){ return new CancelEventIfNoAjaxDecorator(); } }); } setOutputMarkupId(true); } /** * Construct the map area * * @param id Component identifier * @param shape The specific area shape * @param coords The coordinates of the area as a comma separated list * @param tooltipText The tooltip text, or null to not include it * @param linkCallback The link callback function called when the area is click, or null to have no link functionality */ public MapArea(String id, String shape, String coords, String tooltipText, final IAjaxLink linkCallback) { this(id, null, shape, coords, tooltipText, linkCallback); } @Override protected void onComponentTag(final ComponentTag tag){ super.onComponentTag(tag); assert tag.getName().equals("area"); tag.put("shape", shape); tag.put("coords", coords); tag.put("href", "#"); if(tooltipText != null && !tooltipText.isEmpty()){ tag.put("title", tooltipText); } } }
import org.apache.wicket.ajax.markup.html.IAjaxLink; import org.apache.wicket.markup.ComponentTag; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.repeater.RepeatingView; /** * Produces markup for an image map HTML element with * repeating mapped areas. * * @author Jonny Wray * */ public class DynamicImageMap extends WebMarkupContainer{ private static final long serialVersionUID = 8859550289557897390L; private String mapName; private RepeatingView areas; private int areaCounter = 0; public DynamicImageMap(final String id, String mapName){ super(id); this.mapName = mapName; areas = new RepeatingView("areas"); add(areas); } public void addArea(String shape, String coords, String tooltipText, IAjaxLink linkCallback){ MapArea area = new MapArea(Integer.toString(areaCounter++), shape, coords, tooltipText, linkCallback); areas.add(area); } @Override protected void onComponentTag(final ComponentTag tag){ super.onComponentTag(tag); assert tag.getName().equals("map"); tag.put("name", mapName); } }
import java.awt.image.BufferedImage; import org.apache.wicket.Resource; import org.apache.wicket.markup.html.image.Image; import org.apache.wicket.markup.html.image.resource.DynamicImageResource; import org.apache.wicket.protocol.http.WebResponse; import org.jfree.chart.ChartRenderingInfo; import org.jfree.chart.JFreeChart; /** * Wicket Image constructed from a JFreeChart and exposing the * rendering information to allow image map creation * * @author Jonny Wray * */ public class ChartImage extends Image { private static final long serialVersionUID = -7165602010769784429L; private int width; private int height; private JFreeChart chart; private transient BufferedImage image; private transient ChartRenderingInfo renderingInfo; public ChartImage(String id, JFreeChart chart, int width, int height){ super(id); this.width = width; this.height = height; this.chart = chart; } private BufferedImage createBufferedImage(){ if(image == null){ renderingInfo = new ChartRenderingInfo(); image = chart.createBufferedImage(width, height, renderingInfo); } return image; } public ChartRenderingInfo getRenderingInfo(){ if(renderingInfo == null){ createBufferedImage(); } return renderingInfo; } @Override protected Resource getImageResource() { return new DynamicImageResource(){ private static final long serialVersionUID = -4386816651419227671L; @Override protected byte[] getImageData() { return toImageData(createBufferedImage()); } @Override protected void setHeaders(WebResponse response) { if (isCacheable()) { super.setHeaders(response); } else { response.setHeader("Pragma", "no-cache"); response.setHeader("Cache-Control", "no-cache"); response.setDateHeader("Expires", 0); } } }; } }