com.d3x.morpheus.viz.jfree.JFChartBase Maven / Gradle / Ivy
/*
* Copyright (C) 2014-2018 D3X Systems - All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.d3x.morpheus.viz.jfree;
import java.awt.*;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Base64;
import java.util.Optional;
import javax.swing.JFrame;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.title.LegendTitle;
import org.jfree.chart.title.TextTitle;
import org.jfree.chart.title.Title;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.RectangleInsets;
import com.d3x.morpheus.viz.chart.Chart;
import com.d3x.morpheus.viz.chart.ChartException;
import com.d3x.morpheus.viz.chart.ChartLabel;
import com.d3x.morpheus.viz.chart.ChartLegend;
import com.d3x.morpheus.viz.chart.ChartOptions;
import com.d3x.morpheus.viz.chart.ChartTheme;
import com.d3x.morpheus.viz.js.JsCode;
/**
* A convenience base class for building various types of chart types
*
* @param the plot type
*
* @author Xavier Witdouck
*
*
This is open source software released under the Apache 2.0 License
*/
abstract class JFChartBase implements Chart
, ChartMouseListener {
private P plot;
private JFreeChart freeChart;
private ChartPanel chartPanel;
private ChartOptions options;
/**
* Constructor
* @param plot the plot for this chart
* @param legend true to enable legend
*/
JFChartBase(P plot, boolean legend) {
this.plot = plot;
this.freeChart = new JFreeChart(null, new Font("Arial", Font.PLAIN, 4), underlying(plot), legend);
this.freeChart.setBackgroundPaint(Color.WHITE);
this.chartPanel = new ChartPanel(freeChart);
this.chartPanel.setMouseZoomable(true);
this.chartPanel.setRefreshBuffer(true);
this.chartPanel.addChartMouseListener(this);
this.options = new ChartOptions.Default();
}
/**
* Returns the JFreeChart plot object from the Morpheus Plot adapter
* @param plot the Morpheus plot adapter
* @return the JFreeChart plot
*/
private Plot underlying(P plot) {
if (plot instanceof JFXyPlot) {
return ((JFXyPlot)plot).underlying();
} else if (plot instanceof JFCatPlot) {
return ((JFCatPlot) plot).underlying();
} else if (plot instanceof JFPiePlot) {
return ((JFPiePlot) plot).underlying();
} else {
throw new IllegalArgumentException("Unsupported plot type: " + plot);
}
}
/**
* Returns a reference to the chart panel
* @return the JFreeChart chart panel
*/
ChartPanel chartPanel() {
return chartPanel;
}
/**
* Returns the JFreeChart reference for this chart
* @return the JFreeChart reference
*/
JFreeChart freeChart() {
return freeChart;
}
@Override
public P plot() {
return plot;
}
@Override
public ChartLabel title() {
return new TitleAdapter(freeChart, false);
}
@Override
public ChartLabel subtitle() {
return new TitleAdapter(freeChart, true);
}
@Override
public ChartLegend legend() {
return new LegendAdapter(freeChart);
}
@Override
public ChartOptions options() {
return options;
}
@Override()
public ChartTheme theme() {
return new ThemeAdapter();
}
@Override
public Chart show() {
return show(1024, 600);
}
@Override
public Chart show(int width, int height) {
final JFrame frame = new JFrame();
frame.getContentPane().setLayout(new BorderLayout(0, 0));
frame.getContentPane().add(chartPanel, BorderLayout.CENTER);
frame.getContentPane().setBackground(Color.WHITE);
frame.pack();
frame.setSize(width, height);
frame.setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
return this;
}
@Override
public Chart writerPng(File file, int width, int height, boolean transparent) {
BufferedOutputStream os = null;
try {
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
if (!parent.mkdirs()) {
System.err.printf("Unable to create directory for %s", file.getAbsolutePath());
}
}
os = new BufferedOutputStream(new FileOutputStream(file));
writerPng(os, width, height, transparent);
return this;
} catch (Exception ex) {
throw new ChartException(ex.getMessage(), ex);
} finally {
try {
if (os != null) {
os.flush();
os.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
@Override
public Chart writerPng(OutputStream os, int width, int height, boolean transparent) {
try {
if (transparent) {
freeChart().setBackgroundPaint(new Color(255, 255, 255, 0));
freeChart().setBackgroundImageAlpha(0.0f);
freeChart().getPlot().setBackgroundPaint( new Color(255, 255, 255, 0) );
freeChart().getPlot().setBackgroundImageAlpha(0f);
}
ChartUtilities.writeChartAsPNG(os, freeChart, width, height, transparent, 0);
return this;
} catch (Exception ex) {
throw new ChartException(ex.getMessage(), ex);
} finally {
try {
if (os != null) {
os.flush();
os.close();
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
@Override
public void accept(JsCode jsCode, String functionName, String divId) {
try {
final String base64Image = toBase64Image();
jsCode.newFunction(functionName, func -> {
func.write("var divElement = document.getElementById('%s');", divId);
func.newLine().write("var imageElement = document.createElement('img');");
func.newLine().write("imageElement.setAttribute('src', 'data:image/png;base64, %s');", base64Image);
func.newLine().write("imageElement.setAttribute('alt', 'Embedded Chart');");
func.newLine().write("imageElement.setAttribute('class', 'chart');");
func.newLine().write("divElement.appendChild(imageElement);");
});
} catch (IOException ex) {
throw new ChartException("Failed to generate base64 image of chart", ex);
}
}
/**
* Returns a base64 encoded string of a PNG image generated from this chart
* @return the base64 encoded image
* @throws IOException if there is an I/O exception
*/
String toBase64Image() throws IOException {
final double width = this.options().getPreferredSize().map(Dimension::getWidth).orElse(700d);
final double height = this.options().getPreferredSize().map(Dimension::getHeight).orElse(400d);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
this.writerPng(baos, (int)width, (int)height, true);
final byte[] imageBytes = baos.toByteArray();
final Base64.Encoder encoder = Base64.getEncoder();
final byte[] base64 = encoder.encode(imageBytes);
return new String(base64);
}
/**
* A JFreeChart ChartLabel adapter for the chart title
*/
private class TitleAdapter implements ChartLabel {
private JFreeChart chart;
private boolean subtitle;
/**
* Constructor
* @param chart the chart reference
* @param subtitle true if this represents a subtitle
*/
private TitleAdapter(JFreeChart chart, boolean subtitle) {
this.chart = chart;
this.subtitle = subtitle;
}
@Override
public ChartLabel withText(String text) {
this.getTitle().setText(text);
return this;
}
@Override
public ChartLabel withColor(Color color) {
this.getTitle().setPaint(color);
return this;
}
@Override
public ChartLabel withFont(Font font) {
this.getTitle().setFont(font);
return this;
}
/**
* Returns the title for the chart
* @return the chart title
*/
private TextTitle getTitle() {
if (subtitle) {
if (chart.getSubtitleCount() > 0) {
for (int i=0; i {
x.setPaint(darkColor);
x.setBackgroundPaint(lightColor);
});
Optional.ofNullable(freeChart().getSubtitle(0)).ifPresent(x -> {
if (x instanceof TextTitle) {
((TextTitle)x).setBackgroundPaint(lightColor);
((TextTitle)x).setPaint(darkColor);
}
});
Optional.ofNullable(freeChart().getLegend()).ifPresent(x -> {
x.setBackgroundPaint(lightColor);
x.setItemPaint(darkColor);
});
final Plot plot = freeChart().getPlot();
if (plot instanceof XYPlot) {
final XYPlot xyPlot = (XYPlot)plot;
xyPlot.setBackgroundPaint(lightColor);
xyPlot.setDomainGridlinesVisible(true);
xyPlot.setRangeGridlinesVisible(true);
xyPlot.setDomainGridlinePaint(Color.DARK_GRAY);
xyPlot.setRangeGridlinePaint(Color.DARK_GRAY);
for (int i=0; i {
x.setPaint(lightColor);
x.setBackgroundPaint(darkColor);
});
Optional.ofNullable(freeChart().getSubtitle(0)).ifPresent(x -> {
if (x instanceof TextTitle) {
((TextTitle)x).setBackgroundPaint(darkColor);
((TextTitle)x).setPaint(lightColor);
}
});
Optional.ofNullable(freeChart().getLegend()).ifPresent(x -> {
x.setBackgroundPaint(darkColor);
x.setItemPaint(lightColor);
});
final Plot plot = freeChart().getPlot();
if (plot instanceof XYPlot) {
final XYPlot xyPlot = (XYPlot)plot;
xyPlot.setBackgroundPaint(darkColor);
xyPlot.setDomainGridlinesVisible(true);
xyPlot.setRangeGridlinesVisible(true);
xyPlot.setDomainGridlinePaint(Color.LIGHT_GRAY);
xyPlot.setRangeGridlinePaint(Color.LIGHT_GRAY);
for (int i=0; i