Semi donut chart in jfreechart - java

How to draw a semi do nut chart in jfreechart , For example use this below link, https://www.highcharts.com/demo/pie-semi-circle
This is my code
DefaultPieDataset dataset = new DefaultPieDataset( );
dataset.setValue("Safari-32", new Long( 32) ); dataset.setValue("Chrome-44", new Long( 44) );
dataset.setValue("Apple-24", new Long( 24) );
dataset.setValue("Google-75", new Long( 75) );
dataset.setValue("Michele", new Long( 97) ); dataset.setValue("Jony", new Long( 41) );
JFreeChart chart = ChartFactory.createRingChart("Chart title", dataset, true, false, false);
chart.setBackgroundPaint(Color.WHITE);
chart.setBorderVisible(false);
RingPlot plots = (RingPlot) chart.getPlot();
Font font3 = new Font("Book Antiqua", Font.BOLD, 17);
plots.setShadowPaint(null);
plots.setBackgroundPaint(null);
plots.setOutlineVisible(false);
plots.setLabelOutlinePaint(null);
plots.setLabelBackgroundPaint(Color.WHITE);
plots.setCenterTextMode(CenterTextMode.FIXED);
String te = "334";
plots.setCenterText((String)te); plots.setCenterTextFont(font3);
plots.setLabelGenerator(null); // Remove the labels from chart area
font3 = new Font("Book Antiqua", Font.PLAIN, 10);
LegendTitle legend = chart.getLegend();
legend.setPosition(RectangleEdge.RIGHT); legend.setItemFont(font3);
legend.setBorder(0, 0, 0, 0);
String filename = "D:\\ad\\do nut.jpg";
ChartUtilities.saveChartAsJPEG(new File(filename), chart, 250, 155);
This is my code and this produces a full donut chart. I need a donut of starting angle from 180 degree to 0 degree

Initial
Refresh
It's quite simple to create a semi/half donut in java using jfreechart. The most important thing is the invisible dataset. My favourite aircraft is the F16 Falcon, but my code below doesn't have any military associations. Feel free to reuse and/or adapt the source code.
Description: Example class to demonstrate semi/half-donut (TAGS: java, osgi, jfreechart, version 1.0.19, SWT, #PostConstruct, ChartComposite, Eclipse, E4, PiePlot, RingPlot, Semi, Half, Example, BackgroundImage, Resizing, Layout)
package de.enjo.jfreechart.semi.donut.example.parts;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Objects;
import javax.annotation.PostConstruct;
import javax.imageio.ImageIO;
import javax.inject.Inject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ColorRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.RingPlot;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.experimental.chart.swt.ChartComposite;
/*
* Example class to demonstrate semi/half-donut with jfreechart version 1.0.19
* (SWT, ChartComposite, Eclipse, E4, jfreechart, PiePlot, RingPlot, Semi, Half, Example)
*
* #note: This example doesn't use the ChartPanel class to avoid an additional Swing-UI Thread
* */
public class SamplePart {
// synchronize ui, i.e. dataset has been changed
#Inject
private UISynchronize uiSync;
// refresh ui job
private final Job refreshJob = new Job("Refresh Job") {
#Override
protected IStatus run(IProgressMonitor monitor) {
uiSync.asyncExec(SamplePart.this::updateUI);
return Status.OK_STATUS;
}
};
// invisible dataset, the most important thing
private final String INVISIBLE = "have_a_look_on_me_if_you_can_xD";
// an awt image
private BufferedImage backgroundImage;
// color white
private org.eclipse.swt.graphics.Color backgroundColor = new org.eclipse.swt.graphics.Color(255, 255, 255);
private java.awt.Color whiteColorAlphaChannel = new java.awt.Color(255, 255, 255, 0);
// swt widget
private ChartComposite chartComposite;
// the colors we need/support
private ColorRegistry colors;
public SamplePart() {
colors = new ColorRegistry();
colors.put("COLOR" + 0, new RGB(0, 0, 255));
colors.put("COLOR" + 1, new RGB(0, 255, 0));
colors.put("COLOR" + 2, new RGB(255, 0, 0));
colors.put("COLOR" + 3, new RGB(0, 255, 255));
colors.put("COLOR" + 4, new RGB(255, 255, 0));
colors.put("COLOR" + 5, new RGB(128, 128, 128));
}
/**
* initializing ui & simulate data change via refresh job (delay 3 seconds)
*
* #note: wait 3 seconds after starting to refresh
*/
#PostConstruct
public void createComposite(Composite parent) {
initializeExample(parent);
refreshJob.schedule(3000);// milliseconds
}
// construct and set all defaults
private void initializeExample(Composite parent) {
parent.setLayout(GridLayoutFactory.fillDefaults().numColumns(1).create());
parent.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
parent.setBackground(backgroundColor); // white
/**
* 1st load background image
*/
try {
// no really military associations
backgroundImage = loadImage();
} catch (Exception e) {
/* ignore , it has to be present */}
/**
* 2nd create composite (swt)
*/
chartComposite = new ChartComposite(parent, SWT.NONE);
chartComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
chartComposite.setDomainZoomable(false);// no zoom needed
chartComposite.setRangeZoomable(false);// no zoom needed
/**
* 3rd create dataset (important thing (...most important thing...*fg))
*
* - don't forget to decrease by the previous value (degree of previous value =>
* array[index-1])
*
* - you need an additional, invisible dataset to complete the pie or ring plot
* up to 100% (360° or 1.0f)
*
* FYI: 0.0f-1.0f <> 0%-100% <> 0°-360°
*/
DefaultPieDataset dataset = new DefaultPieDataset();
// in this example we use a range of 0°-180°
int[] degreeValues = new int[] { 45, 90, 135, 180 };// 45°, 90° etc.
for (int index = 0; index < degreeValues.length; index++) {
if (index == 0) {
dataset.setValue(String.valueOf(index), Math.toRadians(degreeValues[index]));
} else {
dataset.setValue(String.valueOf(index), Math.toRadians(degreeValues[index] - degreeValues[index - 1]));
}
}
// MOST IMPORTANT
// invisible dataset to complete pie chart up to 100%
// you can use any other numbered key here, in my case the key is named:
// have_a_look_on_me_if_you_can_xD (should be unique)
dataset.setValue(INVISIBLE, Math.toRadians(180));// semi => 180°, we have 360° now
/**
* 4th create plot & chart
*/
final RingPlot plot = new RingPlot(dataset);
plot.setOutlineVisible(false);
plot.setLabelGenerator(null);
for (int index = 0; index < degreeValues.length; index++) {
plot.setSectionPaint(String.valueOf((index)), getAWTColor(index));
// section stroke line for better visibility
plot.setSectionOutlinePaint(String.valueOf((index)), new java.awt.Color(213, 54, 0));
}
// MOST IMPORTANT
// invisible section to complete pie chart up to 100%
// you can use any other numbered key here, in my case the key is named:
// have_a_look_on_me_if_you_can_xD (should be unique)
plot.setSectionPaint(INVISIBLE, whiteColorAlphaChannel); // 180° alpha invisible
plot.setSectionOutlinePaint(INVISIBLE, whiteColorAlphaChannel); // 180° alpha invisible
//
plot.setBackgroundImage(backgroundImage);// you can also use a picture of your grandma ^^
plot.setBackgroundImageAlpha(1.0f); // background image without transparent channel
plot.setForegroundAlpha(0.5f); // plots drawing with transparent channel (semi)
plot.setSectionDepth(0.9D);// ring depth/width in double
plot.setCircular(true); // no ellipse
plot.setInnerSeparatorExtension(0.2f);// percent of inner separator strokes
plot.setOuterSeparatorExtension(0.2f);// percent of outer separator strokes
plot.setSectionOutlinesVisible(true);// strokes between datasets
plot.setSeparatorPaint(new java.awt.Color(213, 54, 0)); // stroke paint
plot.setShadowPaint(null);// no shadow drawing needed (i'm always on the bright side of life... :P)
/**
* #note: edit image to correct size and position of AOI (area-of-interest)
* first before use these settings
*
* #note: setBackgroundImageAlignment(0) = resize of image is disabled. Comment
* it out to enable auto resizing (not recommended in this case)
*/
plot.setInteriorGap(0.0D);
plot.setBackgroundImageAlignment(0);
//
final JFreeChart chart = new JFreeChart(null, null, plot, false);
chart.setBackgroundPaint(new java.awt.Color(parent.getBackground().getRed(), parent.getBackground().getGreen(),
parent.getBackground().getBlue()));
/*
* 5th complete initialization
*/
chartComposite.setChart(chart);
}
private java.awt.Color getAWTColor(int index) {
/*
* ensure that dataset size/length does not exceed the color size/length
*/
org.eclipse.swt.graphics.Color color = colors.get("COLOR" + index);
return new java.awt.Color(color.getRed(), color.getGreen(), color.getBlue());
}
// is called by the refresh job
public void updateUI() {
// simulate date change
if (Objects.nonNull(chartComposite) && !chartComposite.isDisposed()) {
chartComposite.setRedraw(false);
DefaultPieDataset dataset = new DefaultPieDataset();
// in this example we use a range 0°-180°, 6 colors are supported
int[] degreeValues = new int[] { 30, 60, 90, 120, 150, 180 };// 30°, 60° etc.
for (int index = 0; index < degreeValues.length; index++) {
if (index == 0) {
dataset.setValue(String.valueOf(index), Math.toRadians(degreeValues[index]));
} else {
dataset.setValue(String.valueOf(index),
Math.toRadians(degreeValues[index] - degreeValues[index - 1]));
}
}
// MOST IMPORTANT
// invisible dataset to complete pie chart up to 100%
// you can use any other numbered key here, in my case the key is named:
// have_a_look_on_me_if_you_can_xD (should be unique)
dataset.setValue(INVISIBLE, Math.toRadians(180));// semi => 180°, we have 360° now
((RingPlot) chartComposite.getChart().getPlot()).setDataset(dataset);
chartComposite.setRedraw(true);
}
}
/*
* ############################# helper area ############################
*/
private BufferedImage loadImage() throws Exception {
/**
* #note: image source:
* https://www.pngarea.com/view/e6f205a3_jet-png-f-16-plane-png-transparent-png/
*
* #note: put this image into the icons directory, named "f16.png" and replace
* transparent background with white background color first
*
* #note: this example has no military associations. I like this aircraft, not
* more :). You can use every other image with white background in this
* example
*/
return getBufferedImage(SamplePart.class.getResourceAsStream("/icons/f16.png"));
}
private BufferedImage getBufferedImage(InputStream inputStream) throws Exception {
byte[] bytes = read(inputStream);
return getBufferedImage(bytes);
}
/*
* load resources without additional plugins, like emf etc.
*/
private byte[] read(InputStream is) throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead = 0;
byte[] data = new byte[is.available()];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
buffer.flush();
byte[] bytes = buffer.toByteArray();
buffer.close();
is.close();
return bytes;
}
private BufferedImage getBufferedImage(byte[] bytes) throws Exception {
InputStream is = new ByteArrayInputStream(bytes);
BufferedImage bufferedImage = ImageIO.read(is);
is.close();
return bufferedImage;
}
}

I need a donut of starting angle from 180 degree to 0 degree.
You can start at 180° using the parent PiePlot method setStartAngle(). You can hide the lower half using a transparent color, as shown here, or cover it using one of the approaches shown here. OverlayLayout and transparent white is illustrated below.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.OverlayLayout;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.RingPlot;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
public class RingChartTest {
private PieDataset createDataset() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("Safari", 75);
dataset.setValue("Chrome", 60);
dataset.setValue("FireFox", 45);
dataset.setValue("", 180);
return dataset;
}
private JFreeChart createChart(PieDataset dataset) {
JFreeChart chart = ChartFactory.createRingChart(
"Browser Share", dataset, true, false, false);
RingPlot plot = (RingPlot) chart.getPlot();
plot.setStartAngle(180);
plot.setCircular(true);
plot.setSimpleLabels(true);
plot.setSectionDepth(0.5);
plot.setBackgroundPaint(Color.WHITE);
Color invisible = new Color(0xffffffff, true);
plot.setSectionPaint("", invisible);
plot.setSectionOutlinePaint("", invisible);
plot.setShadowPaint(null);
//plot.setLabelGenerator(null);
return chart;
}
public JPanel createDemoPanel() {
JFreeChart jfreechart = createChart(createDataset());
ChartPanel chartPanel = new ChartPanel(jfreechart) {
#Override
public Dimension getPreferredSize() {
return new Dimension(500, 400);
}
};
chartPanel.setLayout(new OverlayLayout(chartPanel));
JLabel label = new JLabel("BrowserShare");
label.setFont(label.getFont().deriveFont(48.0f));
label.setHorizontalAlignment(JLabel.CENTER);
label.setVerticalAlignment(JLabel.CENTER);
label.setAlignmentX(0.5f);
label.setAlignmentY(0.75f);
label.setOpaque(true);
label.setBackground(Color.LIGHT_GRAY);
chartPanel.add(label);
return chartPanel;
}
public static void main(String args[]) {
EventQueue.invokeLater(() -> {
JFrame f = new JFrame("Ring Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new RingChartTest().createDemoPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
});
}
}

Related

How to get low level access to JavaFX chart so an image can be drawn?

I want to draw an image behind the LineChart so that any grid and lines are drawn over the image. It seems LineChart would need to expose some lower level accessor to the Canvas so this could be done.
public class ChartBgImageTest {
public ChartBgImageTest() {
final CategoryAxis xAxis = new CategoryAxis();
final NumberAxis yAxis = new NumberAxis(1, 21, 0.1);
final LineChart<String, Number> lineChart = new LineChart<String, Number>(xAxis, yAxis);
BufferedImage bufferedImage = GraphicsEnvironment
.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration()
.createCompatibleImage(600, 400);
BufferedImage img = new BufferedImage(600, 400, BufferedImage.TYPE_INT_RGB);
img.createGraphics().fillRect(0, 0, 50, 50);
// Fill img code here
Graphics g2d = bufferedImage.createGraphics();
g2d.drawImage(img, 0, 0, null);
// TODO How to draw the img to lineChart background.
// So that any grids or lines on the chart are drawn above the img.
}
}
The charts in the javafx.scene.chart package don't use a Canvas to draw the charts. They instead use the scene graph. In your question you're creating an image on the fly, but assuming you have an already-created image, possibly as an embedded resource, you could use CSS to add the image to the chart's background. Looking at the XYChart section of JavaFX CSS Reference Guide, you'll see one of the substructures is labeled chart-plot-background and is a Region, which means a background image can be applied to it via CSS. For example:
App.java:
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
var chart = new LineChart<>(new NumberAxis(), new NumberAxis(), createChartData());
chart.getXAxis().setLabel("X");
chart.getYAxis().setLabel("Y");
var scene = new Scene(chart, 1000, 650);
scene.getStylesheets().add(getClass().getResource("/style.css").toString());
primaryStage.setScene(scene);
primaryStage.show();
}
private static ObservableList<Series<Number, Number>> createChartData() {
var random = new Random();
var chartData = FXCollections.<Series<Number, Number>>observableArrayList();
for (int i = 1; i <= 3; i++) {
var data = FXCollections.<Data<Number, Number>>observableArrayList();
for (int j = 0; j <= 15; j++) {
data.add(new Data<>(j, random.nextInt(250)));
}
chartData.add(new Series<>("Series #" + i, data));
}
return chartData;
}
}
style.css:
.chart-plot-background {
-fx-background-image: url(/* your URL */);
-fx-background-size: cover;
}
That will only draw the image behind the actual chart content (i.e. the data). The axis, legend, and surrounding space won't have the image behind it. If you want the entire chart to have the background image then you can utilize the fact that Chart, which all chart implementations inherit from, extends from Region. Change the CSS to:
.chart {
-fx-background-image: url(/* your URL */);
-fx-background-size: cover;
}
.chart-plot-background,
.chart-legend {
-fx-background-color: null;
}
If you want something between the plot background and the entire chart you can add the image to .chart-content (documented here).
If you have to stay within code (e.g. because you're creating the image on the fly) then you'll have to get a reference to the necessary Region. To do that you can use Node#lookup(String). Be aware, however, that you may need to wait until the chart has been rendered on screen before calling lookup, as it's possible the needed descendant node has not been created and added to the scene graph beforehand (this is especially true of controls).
import java.util.Random;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.image.Image;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundImage;
import javafx.scene.layout.BackgroundPosition;
import javafx.scene.layout.BackgroundSize;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
var chart = new LineChart<>(new NumberAxis(), new NumberAxis(), createChartData());
chart.setTitle("Example Chart");
chart.getXAxis().setLabel("X");
chart.getYAxis().setLabel("Y");
var scene = new Scene(chart, 1000, 650);
primaryStage.setScene(scene);
primaryStage.show();
var plotBackground = (Region) chart.lookup(".chart-plot-background");
plotBackground.setBackground(
new Background(
new BackgroundImage(
new Image(/* your URL */),
null,
null,
BackgroundPosition.CENTER,
new BackgroundSize(0, 0, false, false, true, false))));
}
private static ObservableList<Series<Number, Number>> createChartData() {
var random = new Random();
var chartData = FXCollections.<Series<Number, Number>>observableArrayList();
for (int i = 1; i <= 3; i++) {
var data = FXCollections.<Data<Number, Number>>observableArrayList();
for (int j = 0; j <= 15; j++) {
data.add(new Data<>(j, random.nextInt(250)));
}
chartData.add(new Series<>("Series #" + i, data));
}
return chartData;
}
}
Notice that you can't use BufferedImage directly with the JavaFX API. If you need to actually draw an Image in code you have a few options:
Use a WritableImage directly (via its PixelWriter). This does not provide a nice API, such as fillRect(...).
Draw to a Canvas then create a snapshot of the result.
Use SwingFXUtils#toFXImage(BufferedImage,WritableImage) to convert a BufferedImage into a JavaFX Image

How do I properly add a MouseHandler to my JFreeChart-FX to drag the chart from left to right

I managed to create a Candlestick Chart using JFreeChart-FX and display it using the fxgraphics2d API. But I am pretty much confused on how to enable any interaction with my chart and need some help to this.
I'd be very grateful for any help in the right direction.
I started with this example to get up my initial Chart and altered it so that it uses my data. Then I use a custom Canvas, which utilizes fxgraphics2d to make the JPanel component accessable as a node for my JavaFX applicaiton.
So I know there is for example a specific PanHandlerFX class, but I am lost to utilize this. As far as I was able to research (e.g. here), I need to add the PanHandlerFX class to the list of availableMouseHandlers of my ChartCanvas. But my canvas does not offer anything like availableMouseHandlers. I feel lost now, since there is so few tutorials and information to find regarding the JFree-FX charts and the documentation does not help me either.
Here is my custom canvas class:
import javafx.scene.canvas.Canvas;
import org.jfree.chart.JFreeChart;
import org.jfree.fx.FXGraphics2D;
import java.awt.geom.Rectangle2D;
public class ChartCanvas extends Canvas {
JFreeChart chart;
private FXGraphics2D graphics2D;
public ChartCanvas(JFreeChart chart) {
this.chart = chart;
this.graphics2D = new FXGraphics2D(getGraphicsContext2D());
// Redraw canvas when size changes.
widthProperty().addListener(e -> draw());
heightProperty().addListener(e -> draw());
}
private void draw() {
double width = getWidth();
double height = getHeight();
getGraphicsContext2D().clearRect(0, 0, width, height);
this.chart.draw(this.graphics2D, new Rectangle2D.Double(0, 0, width, height));
//(this.graphics2D,, new Rectangle2D.Double(0, 0, width, height));
}
}
Here is my custom JFreeChart:
import javafx.collections.ObservableList;
import org.ezstrats.model.chartData.Candlestick;
import org.ezstrats.model.chartData.Exchange;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.DateAxis;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.labels.HighLowItemLabelGenerator;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.CombinedDomainXYPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.CandlestickRenderer;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.data.time.FixedMillisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.jfree.data.time.ohlc.OHLCSeries;
import org.jfree.data.time.ohlc.OHLCSeriesCollection;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
public class JFreeCandlestickChart extends JPanel {
private static final DateFormat READABLE_TIME_FORMAT = new SimpleDateFormat("kk:mm:ss");
private OHLCSeries ohlcSeries;
private TimeSeries volumeSeries;
private JFreeChart candlestickChart;
public JFreeCandlestickChart(String title) {
ObservableList<Candlestick> candlesticks = Exchange.getCandlesticks();
// Create new chart
candlestickChart = createChart(title, candlesticks);
// Create new chart panel
final ChartPanel chartPanel = new ChartPanel(candlestickChart);
chartPanel.setPreferredSize(new Dimension(832, 468));
chartPanel.getChart().getXYPlot().getDomainAxis().setAutoRange(false);
chartPanel.getChart().getXYPlot().getDomainAxis().setLowerBound(candlesticks.get(candlesticks.size() - 300).getTimestampOpen());
chartPanel.getChart().getXYPlot().getDomainAxis().setUpperBound(candlesticks.get(candlesticks.size() - 1).getTimestampOpen());
// Enable zooming - not workign?! ...
chartPanel.setMouseZoomable(true);
chartPanel.setMouseWheelEnabled(true);
chartPanel.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
// process before
super.mouseDragged(e);
chartPanel.getChart().getXYPlot().getDomainAxis().configure();
// process after
}
});
add(chartPanel, BorderLayout.CENTER);
}
public JFreeChart createChart(String title, ObservableList<Candlestick> candlesticks){
/**
* 1st:
* Creating candlestick subplot
*/
// Create OHLCSeriesCollection as a price dataset for candlestick chart
OHLCSeriesCollection candlestickDataset = new OHLCSeriesCollection();
ohlcSeries = new OHLCSeries("Price");
candlestickDataset.addSeries(ohlcSeries);
// Create candlestick chart priceAxis
NumberAxis priceAxis = new NumberAxis("Price");
priceAxis.setAutoRangeIncludesZero(false);
// Create candlestick chart renderer
CandlestickRenderer candlestickRenderer = new CandlestickRenderer(CandlestickRenderer.WIDTHMETHOD_AVERAGE,
false,
new HighLowItemLabelGenerator(new SimpleDateFormat("kk:mm"), new DecimalFormat("0.00000000")));
// Create candlestickSubplot
XYPlot candlestickSubplot = new XYPlot(candlestickDataset, null, priceAxis, candlestickRenderer);
candlestickSubplot.setBackgroundPaint(Color.white);
/**
* 2nd:
* Creating volume subplot
*/
// creates TimeSeriesCollection as a volume dataset for volume chart
TimeSeriesCollection volumeDataset = new TimeSeriesCollection();
volumeSeries = new TimeSeries("Volume");
volumeDataset.addSeries(volumeSeries);
// Create volume chart volumeAxis
NumberAxis volumeAxis = new NumberAxis("Volume");
volumeAxis.setAutoRangeIncludesZero(true);
// Set to no decimal
volumeAxis.setNumberFormatOverride(new DecimalFormat("0"));
// Create volume chart renderer
XYBarRenderer timeRenderer = new XYBarRenderer();
timeRenderer.setShadowVisible(false);
timeRenderer.setDefaultToolTipGenerator(new StandardXYToolTipGenerator("Volume--> Time={1} Size={2}",
new SimpleDateFormat("kk:mm"), new DecimalFormat("0")));
// Create volumeSubplot
XYPlot volumeSubplot = new XYPlot(volumeDataset, null, volumeAxis, timeRenderer);
volumeSubplot.setBackgroundPaint(Color.white);
/**
* 3rd:
* Adding Candles to this chart
**/
for (Candlestick candle: candlesticks){
addCandleToChart(candle.getTimestampOpen(),
candle.getPriceOpen(),
candle.getPriceHigh(),
candle.getPriceLow(),
candle.getPriceClose(),
candle.getVolumeQuote());
}
/**
* 4th:
* Create chart main plot with two subplots (candlestickSubplot,
* volumeSubplot) and one common dateAxis
*/
// Creating charts common dateAxis
DateAxis dateAxis = new DateAxis("Time");
dateAxis.setDateFormatOverride(new SimpleDateFormat("dd.mm.yy kk:mm"));
//dateAxis.setRange();
// reduce the default left/right margin from 0.05 to 0.02
dateAxis.setLowerMargin(0.02);
dateAxis.setUpperMargin(0.02);
dateAxis.setLabelAngle(0);
// Create mainPlot
CombinedDomainXYPlot mainPlot = new CombinedDomainXYPlot(dateAxis);
mainPlot.setGap(10.0);
mainPlot.add(candlestickSubplot, 4);
mainPlot.add(volumeSubplot, 1);
mainPlot.setOrientation(PlotOrientation.VERTICAL);
mainPlot.setDomainPannable(true);
JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, mainPlot, false);
//chart.removeLegend();
// Einbetten in JScrollPaenl??? um Scrollen zu ermöglichen...
// ChartPanel chartPanel = new ChartPanel(chart);
return chart;
}
/**
* Fill series with data.
*
* #param c opentime
* #param o openprice
* #param h highprice
* #param l lowprice
* #param c closeprice
* #param v volume
*/
private void addCandleToChart(long time, double o, double h, double l, double c, double v) {
// Add bar to the data. Let's repeat the same bar
FixedMillisecond t = new FixedMillisecond(time);
//READABLE_TIME_FORMAT.parse(String.valueOf(time)));
ohlcSeries.add(t, o, h, l, c);
volumeSeries.add(t, v);
}
public void setOhlcSeries(OHLCSeries ohlcSeries) {
this.ohlcSeries = ohlcSeries;
}
public void setVolumeSeries(TimeSeries volumeSeries) {
this.volumeSeries = volumeSeries;
}
public OHLCSeries getOhlcSeries() {
return ohlcSeries;
}
public TimeSeries getVolumeSeries() {
return volumeSeries;
}
public JFreeChart getCandlestickChart() {
return candlestickChart;
}
}
And this is how I print the chart (main.class):
// Switching Views
public void drawNewChart(JFreeChart newChart){
centerChart.getChildren().removeAll();
ChartCanvas chartCanvas = new ChartCanvas(newChart);
centerChart.getChildren().add(chartCanvas);
chartCanvas.widthProperty().bind(centerChart.widthProperty());
chartCanvas.heightProperty().bind(centerChart.heightProperty());
}
As shown here, construct a ChartViewer with your JFreeChart to create an interactive chart. The viewer's enclosed ChartCanvas will manage the PanHandlerFX for you. As a concrete example, add the following line to the example, and drag as described here:
plot.setDomainPannable(true);
Original:
After drag right:
As an aside, you may find the JavaFX Demos helpful in this context.

Export composite to image independent of the screen resolution

Is there a way in SWT to export a composite to an image which always has the same size/resolution? The problem is that we have a Dashboard which looks always different when opening on screens with different display size/resolution. The question is now can I export the Dashboard to an image wich has a fixed size and always looks the same no matter on which screen size/resolution it has been created?
For the time being we do it like that but as said it depends on the display it has been created on:
Image image = new Image(Display.getCurrent(), this.content.getBounds().width, this.content.getBounds().height);
ImageLoader loader = new ImageLoader();
FileOutputStream fileOutputStream = new FileOutputStream(file);
GC gc = new GC(image);
this.content.print(gc);
gc.dispose();
loader.data = new ImageData[] { image.getImageData() };
loader.save(fileOutputStream, imageFormat);
fileOutputStream.close();
Is there for example some way to create a virtual screen with a certain resolution, which isn't actually displayed and only used for exporting the Dashboard?
Any help or pointers would be appreciated.
In one of my applications I was creating graphs and charts in SWT. The user was able to export these to an image of a size and format that they specified. My solution was to take the GC of the chart composite and redraw it to a new composite off screen, then export the newly drawn composite.
Here's my class which accomplished this:
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Composite;
/**
* The class used for writing an {#link Composite} to an image file
*
* #author David L. Moffett
*
*/
public class CompositeImageWriter
{
/**
* Redraws the composite to the desired size off-screen and then writes that
* composite as an image to the desired location and in the desired format
*
* #param absolutePath
* the absolute path to the desired output file
* #param compositeToDraw
* the composite to be written to file as an image
* #param width
* the desired width in pixels that the composite should be redrawn
* to
* #param height
* the desired height in pixels that the composite should be redrawn
* to
* #param imageType
* an int representing the type of image that should be written
*/
public static void drawComposite(String absolutePath, Composite compositeToDraw, int width,
int height, int imageType)
{
Image image = new Image(compositeToDraw.getDisplay(), width, height);
GC gc = new GC(image);
int originalWidth = compositeToDraw.getBounds().width;
int originalHeight = compositeToDraw.getBounds().height;
compositeToDraw.setSize(width, height);
compositeToDraw.print(gc);
compositeToDraw.setSize(originalWidth, originalHeight);
ImageLoader loader = new ImageLoader();
loader.data = new ImageData[] { image.getImageData() };
loader.save(absolutePath, imageType);
image.dispose();
gc.dispose();
}
}
For your case of wanting to always export at a specific size, just replace the width and height arguments with appropriate constants.
Edit 1
I should add that the int imageType argument is the corresponding SWT modifier (for example: SWT.IMAGE_PNG, SWT.IMAGE_JPEG, SWT.IMAGE_BMP, etc...).
Edit 2
I updated the static reference to dynamically get the display from the compositeToDraw. Further, here's an example I put together which uses the CompositeImageWriter which you may be able to use to debug your issue:
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
public class CompositeToWrite extends Composite
{
private int width, height;
private Label l;
public CompositeToWrite(Composite parent, int style)
{
super(parent, style);
this.setLayout(new GridLayout(1, true));
this.addListener(SWT.Resize, new Listener()
{
#Override
public void handleEvent(Event event)
{
updateText();
}
});
Button b = new Button(this, SWT.NONE);
b.setText("Export as image (500, 500)");
b.addListener(SWT.Selection, new Listener()
{
#Override
public void handleEvent(Event event)
{
CompositeImageWriter.drawComposite("./img/output.png", CompositeToWrite.this, 500, 500,
SWT.IMAGE_PNG);
}
});
l = new Label(this, SWT.CENTER);
GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
gd.verticalAlignment = SWT.CENTER;
l.setLayoutData(gd);
updateText();
}
protected void updateText()
{
width = this.getBounds().width;
height = this.getBounds().height;
l.setText("My label is centered in composite (" + width + ", " + height + ")");
}
}
In the case of this example I create a simple composite that once added to a shell will look something like this:
When I click the button it resizes the composite to 500 x 500 and writes the resized composite to a file. The result is this picture:
I should note that I did notice the composite flicker when the button is clicked, so this may not be happening completely in the background or "off screen" as I initially suggested.

drawing a diagram with JFreeChart

I'm working with jFreeChart on Eclipse (in Windows) and I want to draw gantt diagram for processors.
I'm doing a XY Chart
But my programme draw me a line with only P0. I want something like
draw from 0 to 10 --> P0
then from 10 to 20 --> draw H
then from 20 to 30 ---->draw wait
from 30 to 40---> draw P0
from 35 to 40 -->draw H
Code:
import javax.swing.JFrame;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
public class JFreeChartLineChartExample extends JFrame {
private static final long serialVersionUID = 1L;
public JFreeChartLineChartExample(String applicationTitle, String chartTitle) {
super(applicationTitle);
// based on the dataset we create the chart
JFreeChart pieChart = ChartFactory.createXYLineChart(chartTitle, "Time", "Processors",
createDataset(),PlotOrientation.VERTICAL, true, true, false);
// Adding chart into a chart panel
ChartPanel chartPanel = new ChartPanel(pieChart);
// settind default size
chartPanel.setPreferredSize(new java.awt.Dimension(500, 270));
// add to contentPane
setContentPane(chartPanel);
}
private XYDataset createDataset() {
final XYSeries P0 = new XYSeries("P0");
P0.add(0, 1);
P0.add(10, 1);
P0.add(35, 1);
P0.add(50, 1);
P0.add(85, 1);
P0.add(110, 1);
final XYSeries P2 = new XYSeries("P2");
final XYSeries P1 = new XYSeries("P1");
final XYSeries H = new XYSeries("H");
H.add(10, 1);
H.add(20, 1);
H.add(45, 1);
H.add(100, 1);
final XYSeries wait = new XYSeries("wait");
wait.add(80, 1);
wait.add(90, 1);
wait.add(105, 1);
final XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(P0);
dataset.addSeries(P2);
dataset.addSeries(P1);
dataset.addSeries(H);
dataset.addSeries(wait);
return dataset;
}
public static void main(String[] args) {
JFreeChartLineChartExample chart = new JFreeChartLineChartExample(" GANTT ", " GANTT");
chart.pack();
chart.setVisible(true);
}
}
As far as I have understood you want to have gaps in your datasets. Your code paints a straight line from time 0 to 110 for dataset P0 because JFreeChart does not know that you want to have gaps.
To add gaps you need to add null values to your datasets.
For example P0 should be created as follows:
final XYSeries P0 = new XYSeries("P0");
P0.add(0, 1);
P0.add(10, 1);
P0.add(11, null);
P0.add(35, 1);
P0.add(50, 1);
P0.add(51, null);
P0.add(85, 1);
P0.add(110, 1);
You might also have a look at the special Gantt classes provided by JFreeChart. There are some examples in the JFreeChart demo.

draw a multiple plot with JFreechart (bar, XY)

Hello i have to make a program to display power curves, and therefore i need to display three different plots on one window.
The different kind of plots are XY (just points), bar, and XY with lines.
My problem(s) : somehow i can get only two of the charts to get drawn AND i can't change the colors of the single chart correctly.
EDIT : When i put as comment the declaration of the third chart, the second one finally gets drawn. Is it impossible to draw three charts ?
Any help will be greatly appreciated, thanks ;)
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.DatasetRenderingOrder;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYBarRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
import org.jfree.ui.ApplicationFrame;
public class OverlaidPlot extends ApplicationFrame
{
final XYSeries series0 = new XYSeries("Graph0");
final XYSeries series1 = new XYSeries("Graph1");
final XYSeries series2 = new XYSeries("Graph2");
public OverlaidXYPlotDemo(final String title)
{
super(title);
final JFreeChart chart = createOverlaidChart();
final ChartPanel panel = new ChartPanel(chart, true, true, true, true, true);
panel.setPreferredSize(new java.awt.Dimension(800, 600));
setContentPane(panel);
}
public void addElem0(double x, double y)
{
this.series0.add(x, y);
}
public void addElem1(double x, double y)
{
this.series1.add(x, y);
}
public void addElem2(double x, double y)
{
this.series2.add(x, y);
}
private JFreeChart createOverlaidChart()
{
final NumberAxis domainAxis = new NumberAxis("Speed (m/s)");
final ValueAxis rangeAxis = new NumberAxis("Power (kw)");
// create plot ...
final IntervalXYDataset data0 = createDataset0();
final XYItemRenderer renderer0 = new XYBarRenderer(0.20);
// change "new XYBarRenderer(0.20)" to "StandardXYItemRenderer()" if you want to change type of graph
final XYPlot plot = new XYPlot(data0, domainAxis, rangeAxis, renderer0);
// add a second dataset and renderer...
final IntervalXYDataset data1 = createDataset1();
final XYLineAndShapeRenderer renderer1 = new XYLineAndShapeRenderer(false, true);
// arguments of new XYLineAndShapeRenderer are to activate or deactivate the display of points or line. Set first argument to true if you want to draw lines between the points for e.g.
plot.setDataset(1, data1);
plot.setRenderer(1, renderer1);
// add a third dataset and renderer...
final IntervalXYDataset data2 = createDataset2();
final XYLineAndShapeRenderer renderer2 = new XYLineAndShapeRenderer(true, true);
// arguments of new XYLineAndShapeRenderer are to activate or deactivate the display of points or line. Set first argument to true if you want to draw lines between the points for e.g.
plot.setDataset(1, data2);
plot.setRenderer(1, renderer2);
plot.setDatasetRenderingOrder(DatasetRenderingOrder.FORWARD);
NumberAxis domain = (NumberAxis) plot.getDomainAxis();/*
domain.setRange(0.00, 30);*/
domain.setTickUnit(new NumberTickUnit(0.5));
domain.setVerticalTickLabels(true);
// return a new chart containing the overlaid plot...
return new JFreeChart("Test", JFreeChart.DEFAULT_TITLE_FONT, plot, true);
}
private IntervalXYDataset createDataset0()
{
// create dataset 0...
final XYSeriesCollection coll0 = new XYSeriesCollection(series0);
return coll0;
}
private IntervalXYDataset createDataset1()
{
// create dataset 1...
final XYSeriesCollection coll1 = new XYSeriesCollection(series1);
return coll1;
}
private IntervalXYDataset createDataset2()
{
// create dataset 2...
final XYSeriesCollection coll2 = new XYSeriesCollection(series2);
return coll2;
}
}
You have two datasets at the same index within the plot - make sure you're setting each dataset to a unique index:
plot.setDataset(2, data2);
plot.setRenderer(2, renderer2);
After changing this, I ran your example with some test data and was able to see all three data sets plotted.

Categories