/******************************************************************************* | |
* Copyright (c) 2012, 2015 INRIA, and Mia-Software. | |
* All rights reserved. This program and the | |
* accompanying materials are made available under the terms of the Eclipse | |
* Public License v1.0 which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v20.html | |
* | |
* Contributors: | |
* Guillaume Doux (INRIA) - Initial API and implementation | |
* Grégoire Dupé (Mia-Software) - Bug 488952 - [Benchmark] The x axis isn't linear | |
******************************************************************************/ | |
package org.eclipse.modisco.utils.chart.birt.core.internal.exported; | |
import java.io.File; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.List; | |
import org.eclipse.birt.chart.computation.Point; | |
import org.eclipse.birt.chart.device.IDeviceRenderer; | |
import org.eclipse.birt.chart.exception.ChartException; | |
import org.eclipse.birt.chart.factory.GeneratedChartState; | |
import org.eclipse.birt.chart.factory.Generator; | |
import org.eclipse.birt.chart.model.ChartWithAxes; | |
import org.eclipse.birt.chart.model.attribute.Anchor; | |
import org.eclipse.birt.chart.model.attribute.AxisType; | |
import org.eclipse.birt.chart.model.attribute.Bounds; | |
import org.eclipse.birt.chart.model.attribute.ChartDimension; | |
import org.eclipse.birt.chart.model.attribute.ColorDefinition; | |
import org.eclipse.birt.chart.model.attribute.LegendItemType; | |
import org.eclipse.birt.chart.model.attribute.LineAttributes; | |
import org.eclipse.birt.chart.model.attribute.LineStyle; | |
import org.eclipse.birt.chart.model.attribute.impl.BoundsImpl; | |
import org.eclipse.birt.chart.model.attribute.impl.ColorDefinitionImpl; | |
import org.eclipse.birt.chart.model.attribute.impl.LineAttributesImpl; | |
import org.eclipse.birt.chart.model.attribute.impl.NumberFormatSpecifierImpl; | |
import org.eclipse.birt.chart.model.component.Axis; | |
import org.eclipse.birt.chart.model.component.Scale; | |
import org.eclipse.birt.chart.model.component.Series; | |
import org.eclipse.birt.chart.model.component.impl.SeriesImpl; | |
import org.eclipse.birt.chart.model.data.NumberDataSet; | |
import org.eclipse.birt.chart.model.data.SeriesDefinition; | |
import org.eclipse.birt.chart.model.data.impl.NumberDataSetImpl; | |
import org.eclipse.birt.chart.model.data.impl.SeriesDefinitionImpl; | |
import org.eclipse.birt.chart.model.impl.ChartWithAxesImpl; | |
import org.eclipse.birt.chart.model.type.LineSeries; | |
import org.eclipse.birt.chart.model.type.impl.LineSeriesImpl; | |
import org.eclipse.birt.chart.util.PluginSettings; | |
import org.eclipse.modisco.utils.chart.birt.core.internal.GraphHelperException; | |
import org.eclipse.modisco.utils.chart.metamodel.internal.chart.Axe; | |
import org.eclipse.modisco.utils.chart.metamodel.internal.chart.Chart; | |
import org.eclipse.modisco.utils.chart.metamodel.internal.chart.Coordinate; | |
import org.eclipse.modisco.utils.chart.metamodel.internal.chart.Serie; | |
/** | |
* Class providing support for generating a chart with Birt in png | |
* The chart is generated from a birtchart model @see org.eclipse.modisco.utils.chart.metamodel | |
* @author Guillaume Doux | |
* | |
*/ | |
public final class BirtGraphHelper { | |
private static final double UNIT_SPACING = 0.1; | |
private static BirtGraphHelper instance; | |
private List<ColorDefinition> colors; | |
private static final int CHART_WIDTH = 800; | |
private static final int CHART_HEIGHT = 400; | |
public List<ColorDefinition> getColors() { | |
return this.colors; | |
} | |
public void setColors(final List<ColorDefinition> colors) { | |
this.colors = colors; | |
} | |
private BirtGraphHelper() { | |
this.colors = new ArrayList<ColorDefinition>(); | |
this.colors.add(ColorDefinitionImpl.BLUE()); | |
this.colors.add(ColorDefinitionImpl.ORANGE()); | |
this.colors.add(ColorDefinitionImpl.GREEN()); | |
this.colors.add(ColorDefinitionImpl.RED()); | |
this.colors.add(ColorDefinitionImpl.CYAN()); | |
this.colors.add(ColorDefinitionImpl.PINK()); | |
this.colors.add(ColorDefinitionImpl.GREY()); | |
this.colors.add(ColorDefinitionImpl.YELLOW()); | |
} | |
/** | |
* Give an instance of the chart generator | |
* @return the BirtGraphHelper | |
*/ | |
public static synchronized BirtGraphHelper getInstance() { | |
if (BirtGraphHelper.instance == null) { | |
BirtGraphHelper.instance = new BirtGraphHelper(); | |
} | |
return BirtGraphHelper.instance; | |
} | |
/** | |
* Create a Birt chart using the chart passed with the birtchart parameter | |
* Works only with graph having 2 dimensions | |
* @param birtChart | |
* @param targetFolder | |
* @param fileName+".png" name of the png file (without extension) | |
* @throws ChartException, Exception | |
*/ | |
public void createBirtGraph(final Chart birtChart, final File targetFolder, | |
final String fileName) throws Exception { | |
final ChartWithAxes chart = ChartWithAxesImpl.create(); | |
if (birtChart.getAxes().size() != 2) { | |
throw new GraphHelperException("The chart should have only two dimensions."); //$NON-NLS-1$ | |
} | |
chart.setDimension(ChartDimension.TWO_DIMENSIONAL_LITERAL); | |
chart.setUnitSpacing(BirtGraphHelper.UNIT_SPACING); | |
chart.getPlot().setBackground(ColorDefinitionImpl.WHITE()); | |
chart.getPlot().getClientArea().setBackground(ColorDefinitionImpl.WHITE()); | |
chart.getLegend().setItemType(LegendItemType.SERIES_LITERAL); | |
chart.getLegend().setVisible(true); | |
chart.getLegend().setAnchor(Anchor.NORTH_EAST_LITERAL); | |
chart.getTitle().getLabel().getCaption().setValue(birtChart.getTitle()); | |
final Axe ord = birtChart.getAxes().get(1); | |
final Axis xAxis = chart.getPrimaryBaseAxes()[0]; | |
xAxis.setType(AxisType.LINEAR_LITERAL); | |
xAxis.setFormatSpecifier(NumberFormatSpecifierImpl.create()); | |
final Axe abs = birtChart.getAxes().get(0); | |
final String xCaption = String.format("%s (%s)", //$NON-NLS-1$ | |
abs.getLegend(), abs.getUnit()); | |
xAxis.getTitle().getCaption().setValue(xCaption); | |
xAxis.getTitle().setVisible(true); | |
xAxis.setCategoryAxis(false); | |
final Scale scale = xAxis.getScale(); | |
xAxis.setScale(scale); | |
final List<Point> dataPoints = new ArrayList<Point>(); | |
final SeriesDefinition ySeriesDefinition = SeriesDefinitionImpl.create(); | |
final Comparator<Point> comparator = new Comparator<Point>() { | |
public int compare(final Point point1, final Point point2) { | |
if (point1.x == point2.x) { | |
return 0; | |
} else if (point1.x < point2.x) { | |
return -1; | |
} else { | |
return 1; | |
} | |
} | |
}; | |
for (Serie serie : birtChart.getSeries()) { | |
dataPoints.clear(); | |
for (org.eclipse.modisco.utils.chart.metamodel.internal.chart.Point point : serie.getPoints()) { | |
double xCoord = 0; | |
double yCoord = 0; | |
for (Coordinate coord : point.getCoordinates()) { | |
if (coord.getAxe().equals(abs)) { | |
xCoord = coord.getValue(); | |
} | |
if (coord.getAxe().equals(ord)) { | |
yCoord = coord.getValue(); | |
} | |
} | |
final Point birtPoint = new Point(xCoord, yCoord); | |
dataPoints.add(birtPoint); | |
} | |
// sort on X axis | |
Collections.sort(dataPoints, comparator); | |
final ColorDefinition serieColor = getColorForSerie(birtChart.getSeries().indexOf(serie)); | |
final LineSeries lineSeries = createLineSeries( | |
dataPoints.toArray(new Point[0]), serieColor, | |
LineStyle.SOLID_LITERAL, serie.getName()); | |
ySeriesDefinition.getSeries().add(lineSeries); | |
final Point[] linearRegression = computeLinearRegression(dataPoints); | |
final LineSeries linearRegSeries = createLineSeries( | |
linearRegression, serieColor, LineStyle.DASHED_LITERAL, ""); //$NON-NLS-1$ | |
ySeriesDefinition.getSeries().add(linearRegSeries); | |
} | |
//Creating x axis labels | |
final ArrayList<Double> xAxisValues = new ArrayList<Double>(); | |
for (Point dataPoint : dataPoints) { | |
xAxisValues.add(Double.valueOf(dataPoint.x)); | |
} | |
final NumberDataSet xAxisDataSet = NumberDataSetImpl.create(xAxisValues); | |
final Series xAxisSeries = SeriesImpl.create(); | |
xAxisSeries.setDataSet(xAxisDataSet); | |
final SeriesDefinition xSeriesDefinition = SeriesDefinitionImpl.create(); | |
xSeriesDefinition.getSeries().add(xAxisSeries); | |
xAxis.getSeriesDefinitions().add(xSeriesDefinition); | |
createYAxis(chart, ord, xAxis, ySeriesDefinition); | |
chartRendering(targetFolder, fileName, chart); | |
} | |
private static LineSeries createLineSeries(final Point[] dataPoints, | |
final ColorDefinition serieColor, final LineStyle lineStyle, final String name) { | |
final LineSeries lineSeries = (LineSeries) LineSeriesImpl.create(); | |
lineSeries.setSeriesIdentifier(name); | |
lineSeries.setCurve(false); | |
final ArrayList<Double> yAxisValues = new ArrayList<Double>(); | |
for (Point point : dataPoints) { | |
yAxisValues.add(Double.valueOf(point.y)); | |
} | |
final NumberDataSet dataSet = NumberDataSetImpl.create(yAxisValues); | |
lineSeries.setDataSet(dataSet); | |
lineSeries.getMarkers().clear(); // Remove this to show markers for each points | |
// line attributes | |
final LineAttributes lineAttr = LineAttributesImpl | |
.create(ColorDefinitionImpl.BLACK(), lineStyle, 1); | |
lineAttr.setVisible(true); | |
lineAttr.setColor(serieColor); | |
lineSeries.setLineAttributes(lineAttr); | |
return lineSeries; | |
} | |
private static void createYAxis(final ChartWithAxes chart, final Axe ord, | |
final Axis xAxis, final SeriesDefinition ySeriesDefinition) { | |
final Axis yAxis = chart.getPrimaryOrthogonalAxis(xAxis); | |
yAxis.setType(AxisType.LINEAR_LITERAL); | |
yAxis.setFormatSpecifier(NumberFormatSpecifierImpl.create()); | |
yAxis.getTitle().setVisible(true); | |
final String yCaption = String.format("%s (%s)", //$NON-NLS-1$ | |
ord.getLegend(), ord.getUnit()); | |
yAxis.getTitle().getCaption().setValue(yCaption); | |
yAxis.getSeriesDefinitions().add(ySeriesDefinition); | |
} | |
private static void chartRendering(final File targetFolder, final String fileName, | |
final ChartWithAxes chart) throws ChartException { | |
final PluginSettings pSettings = PluginSettings.instance(); | |
final IDeviceRenderer render = pSettings.getDevice("dv.PNG"); //$NON-NLS-1$ | |
render.setProperty(IDeviceRenderer.FILE_IDENTIFIER, new File(targetFolder, | |
fileName + ".png")); //$NON-NLS-1$ | |
final Bounds bounds = BoundsImpl.create(0, 0, CHART_WIDTH, CHART_HEIGHT); | |
final Generator generator = Generator.instance(); | |
final GeneratedChartState state = generator.build(render.getDisplayServer(), chart, bounds, null, | |
null, null); | |
generator.render(render, state); | |
} | |
private ColorDefinition getColorForSerie(final int indexOf) { | |
return this.colors.get(indexOf % this.colors.size()); | |
} | |
/** | |
* Computes the linear regression of the given list of points, and returns | |
* points on the computed line. | |
* | |
* @param dataPoints | |
* the points to include in the computation | |
* @return points on the computed approximation line, with the same abscissa | |
* as the input points | |
*/ | |
private static Point[] computeLinearRegression(final List<Point> dataPoints) { | |
// see: http://en.wikipedia.org/wiki/Simple_linear_regression | |
final double size = dataPoints.size(); | |
if ((int) size == 0) { | |
return new Point[0]; | |
} | |
if ((int) size == 1) { | |
final Point point = dataPoints.get(0); | |
return new Point[] { new Point(point.x, point.y) }; | |
} | |
double sumXY = 0.0; | |
double sumX = 0.0; | |
double sumY = 0.0; | |
double sumXX = 0.0; | |
for (Point point : dataPoints) { | |
sumX += point.x; | |
sumXX += point.x * point.x; | |
sumY += point.y; | |
sumXY += point.x * point.y; | |
} | |
// a = (NΣXY - (ΣX)(ΣY)) / (NΣX² - (ΣX)²) | |
final double a = (size * sumXY - sumX * sumY) / (size * sumXX - sumX * sumX); | |
// b = (ΣY - b(ΣX)) / N | |
final double b = (sumY - a * sumX) / size; | |
List<Point> resultPoints = new ArrayList<Point>(); | |
for (Point point : dataPoints) { | |
// y = ax + b | |
resultPoints.add(new Point(point.x, a * point.x + b)); | |
} | |
return resultPoints.toArray(new Point[resultPoints.size()]); | |
} | |
} |