Child pages
  • JFreeChart with clickable imagemap
Skip to end of metadata
Go to start of metadata

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.

http://gist.github.com/647285

Example
IModel<JFreeChartRenderingBean> chartModel = new AbstractReadOnlyModel<JFreeChartRenderingBean>(){
            @Override
            public JFreeChartRenderingBean getObject() {
                return new JFreeChartRenderingBean(constructChart(), CHART_WIDTH, CHART_HEIGHT);
            }
        };
MappedJFreeChartPanel graphPanel = new MappedJFreeChartPanel("graphPanel", chartModel);
add(graphPanel);

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:

MappedChart.html
<!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:

MappedChart.java

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:

MapArea.java

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);
	}
    }
}

DynamicImageMap.java

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);
    }	
}
ChartImage.java

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);
		}
            }
        };
    }
}
  • No labels