Статьи

Интеграция гобеленов JFreeChart

Я только что закончил интеграцию JFreeChart с Tapestry . Каждый раз, когда вы интегрируете библиотеку с Tapestry, вы полны похвалы за каркас. Это то, что вы редко можете сказать о других веб-фреймворках.

Я имел в виду два использования.

  1. В качестве возвращаемого значения из обработчика событий.
  2. В качестве компонента, который может быть использован для отображения JFreeChart и соответствующей карты изображений


Для отображения диаграммы недостаточно только экземпляра JFreeChart, вам также необходимо знать ширину и высоту изображения. Также в зависимости от типа формата изображения, который вы собираетесь использовать, требуется дополнительная информация, например, в случае качества формата JPEG.
Поэтому мы создаем абстрактную модель данных для хранения информации в одном месте.

public abstract class ChartModel
{
   private JFreeChart chart;

   private int width;

   private int height;

   private ChartRenderingInfo info;

   public ChartModel(JFreeChart chart, int width, int height)
   {
      this.chart = chart;
      this.width = width;
      this.height = height;
      info = new ChartRenderingInfo(new StandardEntityCollection());
   }

   public JFreeChart getChart()
   {
      return chart;
   }

   public int getWidth()
   {
      return width;
   }

   public int getHeight()
   {
      return height;
   }

   public ChartRenderingInfo getInfo()
   {
      return info;
   }

   public abstract String getFormat();

   public abstract String getContentType();

}

Затем мы создаем конкретный класс для каждого конкретного формата изображения.

public class JPEGChartModel extends ChartModel
{

   private float quality;

   public JPEGChartModel(JFreeChart chart, int width, int height, float quality)
   {
      super(chart, width, height);
      this.setQuality(quality);
   }

   @Override
   public String getFormat()
   {
      return "jpeg";
   }

   @Override
   public String getContentType()
   {
      return "image/jpeg";
   }

   public void setQuality(float quality)
   {
      this.quality = quality;
   }

   public float getQuality()
   {
      return quality;
   }

}

public class PNGChartModel extends ChartModel
{
   private boolean encodeAlpha;

   private int compression;

   public PNGChartModel(JFreeChart chart, int width, int height,
      boolean encodeAlpha, int compression)
   {
      super(chart, width, height);
      this.setEncodeAlpha(encodeAlpha);
      this.setCompression(compression);
   }

   @Override
   public String getFormat()
   {
      return "png";
   }

   @Override
   public String getContentType()
   {
      return "image/png";
   }

   public void setEncodeAlpha(boolean encodeAlpha)
   {
      this.encodeAlpha = encodeAlpha;
   }

   public boolean isEncodeAlpha()
   {
      return encodeAlpha;
   }

   public void setCompression(int compression)
   {
      this.compression = compression;
   }

   public int getCompression()
   {
      return compression;
   }

}

Изображение записывается в выходной поток с помощью службы рендеринга ChartWriter.

public interface ChartWriter {

   public void writeChart(OutputStream out, ChartModel chartModel) throws IOException;

}

public class ChartWriterImpl implements ChartWriter
{

   private ChartRenderer renderer;

   public ChartWriterImpl(ChartRenderer renderer)
   {
      this.renderer = renderer;
   }

   public void writeChart(OutputStream out, ChartModel chartModel) throws IOException
   {
      renderer.render(chartModel, chartModel.getChart(), out);
   }

}

Реализация делегирует рендеринг сервису ChartRenderer, который является сервисом стратегии, основанным на типе класса

public interface ChartRenderer
{
   void render(ChartModel chartModel, JFreeChart jfreeChart, OutputStream out) throws IOException;
}

//JPEG Chart Renderer
public class JPEGChartRenderer implements ChartRenderer
{

   public void render(ChartModel chartModel, JFreeChart jfreeChart, OutputStream out) throws IOException
   {
      JPEGChartModel jpegChart = (JPEGChartModel) chartModel;

      ChartUtilities.writeChartAsJPEG(out,
            jpegChart.getQuality(),
            jfreeChart,
            chartModel.getWidth(),
            chartModel.getHeight(),
            jpegChart.getInfo());

   }

}

//PNG Chart Renderer
public class PNGChartRenderer implements ChartRenderer
{

   public void render(ChartModel chartModel,
      JFreeChart jfreeChart, OutputStream out) throws IOException
   {
      PNGChartModel pngChart = (PNGChartModel) chartModel;

      ChartUtilities.writeChartAsPNG(out,
            jfreeChart,
            chartModel.getWidth(),
            chartModel.getHeight(),
            pngChart.getInfo(),
            pngChart.isEncodeAlpha(),
            pngChart.getCompression()
            );

   }

}

Теперь, когда базы покрыты, давайте перейдем к двум обычаям, которые мы запланировали.

В качестве возвращаемого значения из обработчика событий.

Это можно сделать, добавив ComponentEventResultProcessor для обработки ChartModel в качестве возвращаемого значения.

public class ChartResultProcessor implements ComponentEventResultProcessor<ChartModel>
{
   public Response response;
   private ChartWriter chartWriter;

   public ChartResultProcessor(Response response, ChartWriter chartWriter)
   {
      this.response = response;
      this.chartWriter = chartWriter;
   }

   public void processResultValue(ChartModel chartModel) throws IOException
   {
      response.disableCompression();

      OutputStream out = response.getOutputStream(chartModel.getContentType());
      chartWriter.writeChart(out, chartModel);

      response.setHeader("Pragma", "no-cache");
      response.setHeader("Cache-Control", "no-cache");
      response.setDateHeader("Expires", 0);
   }

}

ChartResultProcessor делегирует отрисовку диаграммы ChartWriter.

Компонент для отображения JFreeChart и соответствующей карты изображений

Поскольку для отображения диаграммы в разных графических форматах требуются разные параметры, мы создаем абстрактный класс. Любой абстрактный класс компонента muct должен быть помещен в $ {application-package} .base. AbstractChart реализован в виде

@Import(library = "chart.js")
public abstract class AbstractChart implements ClientElement
{
   @Parameter(value = "prop:componentResources.id", defaultPrefix = BindingConstants.LITERAL, allowNull = false)
   private String clientId;

   @Parameter(required = true, allowNull = false)
   private JFreeChart chart;

   @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL, allowNull = false)
   private int width;

   @Parameter(required = true, defaultPrefix = BindingConstants.LITERAL, allowNull = false)
   private int height;

   @Parameter
   private Object[] context;

   @Parameter(defaultPrefix = BindingConstants.LITERAL)
   private String zone;

   @Parameter
   private ToolTipTagFragmentGenerator toolTipTagGenerator;

   @Parameter(value = "false", defaultPrefix = BindingConstants.LITERAL)
   private boolean useMap;

   ToolTipTagFragmentGenerator defaultToolTipTagGenerator()
   {
      return new StandardToolTipTagFragmentGenerator();
   }

   @Inject
   private ComponentResources resources;

   @Inject
   private JavaScriptSupport javaScriptSupport;

   @Inject
   private ChartWriter chartWriter;

   private String assignedClientId;

   private ChartModel internalChart;

   void setupRender()
   {
      assignedClientId = javaScriptSupport.allocateClientId(clientId);
   }

   boolean beginRender(MarkupWriter writer)
   {
      // Outer Div
      writer.element("div", "id", getClientId());

      // Write image tag
      writer.element("img", "src", getImageURL());

      // Add map if required
      if(useMap)
      {
         writer.attributes("useMap", "#" + getMapName());
      }
      writer.end(); // Close img tag

      String selectMapURL = getSelectMapURL();

      if(useMap)
      {
         createChart();
         initializeChart();

         writer.writeRaw(ChartUtilities.getImageMap(getMapName(),
               internalChart.getInfo(),
               toolTipTagGenerator,
               getURLTagGenerator(selectMapURL)));
      }

      writer.end();// Close Outer Div

      if(zone != null)
      {
         addJavaScript(selectMapURL);
      }

      return false;
   }

   private String getSelectMapURL()
   {
      return resources.createEventLink(ChartConstants.SELECT_MAP, context).toAbsoluteURI();
   }

   private URLTagFragmentGenerator getURLTagGenerator(final String url)
   {

      return new URLTagFragmentGenerator()
      {

         public String generateURLFragment(String text)
         {
            String[] parts = text.split("\\?");
            return String.format("href='%s?%s'", url, parts[1]);
         }

      };
   }

   private String getImageURL()
   {
      return resources.createEventLink(ChartConstants.SHOW_CHART, context).toAbsoluteURI();
   }

   private String getMapName()
   {
      return getClientId() + "_map";
   }

   public String getClientId()
   {
      return assignedClientId;
   }

   private void addJavaScript(String url)
   {

      JSONObject params = new JSONObject();
      params.put("zone", zone);
      params.put("id", getMapName());
      params.put("url", url);
      javaScriptSupport.addInitializerCall("mapToZone", params);
   }

   @OnEvent(ChartConstants.SHOW_CHART)
   Object showChart()
   {
      createChart();
      return internalChart;
   }

   private void createChart()
   {
      internalChart = createChart(chart, width, height);
   }

   private void initializeChart()
   {
      OutputStream out = new DummyOutputStream();
      try
      {
         chartWriter.writeChart(out, internalChart);
      }
      catch(IOException e)
      {
         throw new RuntimeException("Could not write chart : ", e);
      }
   }

   protected abstract ChartModel createChart(JFreeChart chart, int width, int height);

}

Есть один взлом. Карта изображения генерируется ChartRenderingInfo, которую можно получить только после создания диаграммы. Таким образом, мы должны создать диаграмму дважды, один раз на этапе рендеринга, чтобы получить карту, и другой на этапе действия для рендеринга диаграммы. Поскольку вывод не требуется на этапе рендеринга, мы используем DummyOutputStream.

public class DummyOutputStream extends OutputStream
{
   @Override
   public void write(int b) throws IOException
   {
   }
}

Следует также отметить, что области в imagemap должны быть связаны с зоной (если она есть). Это делается по сценарию

Tapestry.Initializer.mapToZone = function(spec)
{
   $A($(spec.id).childNodes).each(function(e)
   {
       if(e.tagName == "AREA")
       {
          Event.observe($(e), "click", function(event){
             event.preventDefault();
             var zoneManager = Tapestry.findZoneManagerForZone(zone);
             if(zoneManager != null)
             {
                zoneManager.updateFromURL(e.href);
             }
          });
       }
   });

};

JPEGChart & PNGChart наследует большую часть функциональности от AbstractChart.

public class JPEGChart extends AbstractChart
{
   @Parameter(value = "0.9", defaultPrefix = BindingConstants.LITERAL, allowNull = false)
   private float quality;

   @Override
   protected ChartModel createChart(JFreeChart chart, int width, int height)
   {
      return new JPEGChartModel(chart, width, height, quality);
   }

}

public class PNGChart extends AbstractChart
{
   @Parameter(value = "false", defaultPrefix = BindingConstants.LITERAL, allowNull = false)
   private boolean encodeAlpha;

   @Parameter(value = "0", defaultPrefix = BindingConstants.LITERAL, allowNull = false)
   private int compression;

   @Override
   protected ChartModel createChart(JFreeChart chart, int width, int height)
   {
      return new PNGChartModel(chart, width, height, encodeAlpha, compression);
   }
}

И, наконец, класс модуля

public class ChartModule
{
   public static void bind(ServiceBinder binder)
   {
      binder.bind(ChartWriter.class, ChartWriterImpl.class);
   }

   public ChartRenderer buildChartRender(StrategyBuilder builder,
         @SuppressWarnings("rawtypes") Map<Class, ChartRenderer> chartRenderers)
   {
      return builder.build(ChartRenderer.class, chartRenderers);
   }

   @Contribute(ComponentEventResultProcessor.class)
   public void provideResultProcessors(
         @SuppressWarnings("rawtypes")
         MappedConfiguration<Class,ComponentEventResultProcessor> configuration)
   {
      configuration.addInstance(ChartModel.class, ChartResultProcessor.class);
   }

   @Contribute(ChartRenderer.class)
   public void provideChartRenderers(
         @SuppressWarnings("rawtypes")
         MappedConfiguration<Class, ChartRenderer> configuration)
   {
      configuration.addInstance(JPEGChartModel.class, JPEGChartRenderer.class);
      configuration.addInstance(PNGChartModel.class, PNGChartRenderer.class);
   }
}

ChartConstants реализован как

public class JPEGChart extends AbstractChart
{
   @Parameter(value = "0.9", defaultPrefix = BindingConstants.LITERAL, allowNull = false)
   private float quality;

   @Override
   protected ChartModel createChart(JFreeChart chart, int width, int height)
   {
      return new JPEGChartModel(chart, width, height, quality);
   }

}

Полный исходный код здесь

От http://tawus.wordpress.com/2011/07/30/tapestry-jfreechart-integration/