ro.nextreports.engine.chart.JsonExporter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nextreports-engine Show documentation
Show all versions of nextreports-engine Show documentation
NextReports Engine is a lightweight Java platform development library which
can be used to run NextReports inside your applications.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 ro.nextreports.engine.chart;
import java.awt.Color;
import java.awt.Font;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ro.nextreports.engine.exporter.exception.NoDataFoundException;
import ro.nextreports.engine.exporter.util.function.AbstractGFunction;
import ro.nextreports.engine.exporter.util.function.FunctionFactory;
import ro.nextreports.engine.exporter.util.function.FunctionUtil;
import ro.nextreports.engine.exporter.util.function.GFunction;
import ro.nextreports.engine.i18n.I18nUtil;
import ro.nextreports.engine.queryexec.QueryException;
import ro.nextreports.engine.queryexec.QueryResult;
import ro.nextreports.engine.util.StringUtil;
import ro.nextreports.jofc2.model.Text;
import ro.nextreports.jofc2.model.axis.Axis;
import ro.nextreports.jofc2.model.axis.Label;
import ro.nextreports.jofc2.model.axis.Label.Rotation;
import ro.nextreports.jofc2.model.axis.XAxis;
import ro.nextreports.jofc2.model.axis.YAxis;
import ro.nextreports.jofc2.model.axis.YLabel;
import ro.nextreports.jofc2.model.elements.AnimatedElement;
import ro.nextreports.jofc2.model.elements.AreaLineChart;
import ro.nextreports.jofc2.model.elements.BarChart;
import ro.nextreports.jofc2.model.elements.Element;
import ro.nextreports.jofc2.model.elements.HorizontalBarChart;
import ro.nextreports.jofc2.model.elements.LineChart;
import ro.nextreports.jofc2.model.elements.PieChart;
import ro.nextreports.jofc2.model.elements.PieChart.AnimationPie;
import ro.nextreports.jofc2.model.elements.StackedBarChart;
import ro.nextreports.jofc2.model.elements.Tooltip;
/**
* @author Decebal Suiu
*/
public class JsonExporter implements ChartExporter {
private OutputStream out;
private QueryResult result;
private Chart chart;
private PrintStream stream;
// default background color for a flash chart (if none is set)
private final Color DEFAULT_BACKGROUND = new Color(248, 248, 216);
private Map parameterValues;
private String drillFunction;
private static final String X_KEY = "X_VALUE";
public static final String X_VALUE = "${" + X_KEY + "}";
// animation strings
// http://teethgrinder.co.uk/open-flash-chart-2/bar-chart-on-show.php
private String POP = "pop";
private String POP_UP = "pop-up";
private String DROP = "drop";
private String FADE_IN = "fade-in";
private String GROW_UP = "grow-up"; // does not work
private String GROW_DOWN = "grow-down"; // does not work -> use grow
// for line
private String EXPLODE = "explode";
private String MID_SLIDE= "mid-slide";
private String SHRINK_IN = "shrink-in";
private String language;
public JsonExporter(Map parameterValues, QueryResult result, OutputStream out,
Chart chart, String drillFunction, String language) {
this.parameterValues = parameterValues;
this.result = result;
this.out = out;
this.chart = chart;
this.drillFunction = drillFunction;
this.language = language;
}
public boolean export() throws QueryException, NoDataFoundException {
testForData();
initExport();
String json = createFlashChart().toString();
stream.print(json);
return true;
}
private void testForData() throws QueryException, NoDataFoundException {
// for procedure call we do not know the row count (is -1)
if (this.out == null || result == null
|| result.getColumnCount() <= 0
|| result.getRowCount() == 0) {
throw new NoDataFoundException();
}
}
protected void initExport() throws QueryException {
stream = createPrintStream();
}
protected PrintStream createPrintStream() throws QueryException {
try {
return new PrintStream(out, false, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new QueryException(e);
}
}
private String getStyle(ChartTitle chartTitle) {
StringBuffer titleStyle = new StringBuffer();
Font font = chartTitle.getFont();
titleStyle.append('{');
titleStyle.append("font-size: ").append(font.getSize()).append("px;");
titleStyle.append("color: ").append(getHexColor(chartTitle.getColor())).append(";");
titleStyle.append("font-family: ").append(font.getFamily()).append(";");
if (font.isBold()) {
// it's omitted by ofc
titleStyle.append("font-weight: bold;");
}
if (font.isItalic()) {
// it's omitted by ofc
titleStyle.append("font-style: italic;");
}
byte align = chartTitle.getAlignment();
if (align == ChartTitle.LEFT_ALIGNMENT) {
titleStyle.append("text-align: left;");
} else if (align == ChartTitle.CENTRAL_ALIGNMENT) {
titleStyle.append("text-align: center;");
} else if (align == ChartTitle.RIGHT_ALIGNMENT) {
titleStyle.append("text-align: right;");
}
titleStyle.append('}');
return titleStyle.toString();
}
private ro.nextreports.jofc2.model.Chart createFlashChart() throws QueryException {
ChartTitle chartTitle = chart.getTitle();
String title = replaceParameters(chartTitle.getTitle());
title = StringUtil.getI18nString(title, I18nUtil.getLanguageByName(chart, language));
String titleStyle = getStyle(chartTitle);
ro.nextreports.jofc2.model.Chart flashChart = new ro.nextreports.jofc2.model.Chart(title, titleStyle);
XAxis xAxis = new XAxis();
xAxis.setColour(getHexColor(chart.getxAxisColor()));
flashChart.setXAxis(xAxis);
YAxis yAxis = new YAxis();
yAxis.setColour(getHexColor(chart.getyAxisColor()));
flashChart.setYAxis(yAxis);
setBackground(flashChart);
setGridAxisColors(flashChart, xAxis, yAxis);
setLegends(flashChart);
setTooltip(flashChart);
boolean showXLabel = chart.getXShowLabel() == null ? false : chart.getXShowLabel();
boolean showYLabel = chart.getYShowLabel() == null ? false : chart.getYShowLabel();
setTicks(xAxis, yAxis, showXLabel, showYLabel);
setElements(flashChart, xAxis, yAxis, showXLabel, showYLabel);
String yTooltipPattern = chart.getYTooltipPattern();
if (yTooltipPattern != null) {
DecimalFormat df = new DecimalFormat(yTooltipPattern);
int numDecimals = df.getMinimumFractionDigits();
char decimalSEparator = df.getDecimalFormatSymbols().getDecimalSeparator();
flashChart.setNumDecimals(numDecimals);
flashChart.setDecimalSeparatorIsComma(',' == decimalSEparator);
flashChart.setThousandSeparatorDisabled(false);
}
return flashChart;
}
private Element[] createBarChart(XAxis xAxis, YAxis yAxis, boolean showXLabel, boolean showYLabel) throws QueryException {
Element[] charts = new Element[chart.getYColumns().size()];
for (int i = 0; i < charts.length; i++) {
BarChart barChart;
byte style = chart.getType().getStyle();
switch (style) {
case ChartType.STYLE_BAR_GLASS:
barChart = new BarChart(BarChart.Style.GLASS);
break;
case ChartType.STYLE_BAR_CYLINDER:
barChart = new BarChart(BarChart.Style.CYLINDER);
break;
case ChartType.STYLE_BAR_PARALLELIPIPED:
barChart = new BarChart(BarChart.Style.THREED);
break;
case ChartType.STYLE_BAR_DOME:
barChart = new BarChart(BarChart.Style.DOME);
break;
default:
barChart = new BarChart();
break;
}
barChart.setColour(getHexColor(chart.getForegrounds().get(i)));
barChart.setAlpha(getAlpha(chart.getTransparency()));
barChart.setTooltip("#val#");
barChart.setFontSize(chart.getFont().getSize());
barChart.setOnShow(new AnimatedElement.OnShow(DROP,0,1));
charts[i] = barChart;
}
createChart(xAxis, yAxis, showXLabel, showYLabel, charts);
return charts;
}
private Element[] createHorizontalBarChart(XAxis xAxis, YAxis yAxis, boolean showXLabel, boolean showYLabel) throws QueryException {
Element[] charts = new Element[chart.getYColumns().size()];
for (int i = 0; i < charts.length; i++) {
HorizontalBarChart barChart = new HorizontalBarChart();
barChart.setColour(getHexColor(chart.getForegrounds().get(i)));
barChart.setAlpha(getAlpha(chart.getTransparency()));
barChart.setTooltip("#val#");
// setting font size on horizontal bar chart will make a json which cannot be rendered by JOFC api!
//barChart.setFontSize(chart.getFont().getSize());
charts[i] = barChart;
}
createChart(xAxis, yAxis, showXLabel, showYLabel, charts);
return charts;
}
private Element[] createPieChart(boolean showXLabel, boolean showYLabel) throws QueryException {
Element[] charts = new Element[1];
PieChart pieChart = new PieChart();
pieChart.setAnimate(true);
pieChart.setStartAngle(35);
pieChart.setBorder(2);
List animations = new ArrayList();
animations.add(new AnimationPie.Fade());
animations.add(new AnimationPie.Bounce(10));
pieChart.addAnimations(animations);
pieChart.setGradientFill(true);
pieChart.setAlpha(getAlpha(chart.getTransparency()));
List colors = new ArrayList();
for (Color color : chart.getForegrounds()) {
colors.add(getHexColor(color));
}
pieChart.setColours(colors.toArray(new String[10]));
pieChart.setTooltip("#val# of #total#
#percent# of 100%");
pieChart.setFontSize(chart.getFont().getSize());
charts[0] = pieChart;
createChart(null, null, showXLabel, showYLabel, new Element[]{pieChart});
return charts;
}
private Element[] createStackedBarChart(XAxis xAxis, YAxis yAxis, boolean showXLabel, boolean showYLabel) throws QueryException {
Element[] charts = new Element[chart.getYColumns().size()];
StackedBarChart barChart = new StackedBarChart();
barChart.setAlpha(getAlpha(chart.getTransparency()));
barChart.setTooltip("#val#");
barChart.setFontSize(chart.getFont().getSize());
for (int i = 0; i < charts.length; i++) {
charts[i] = barChart;
}
createChart(xAxis, yAxis, showXLabel, showYLabel, charts);
return charts;
}
private Element[] createLineChart(XAxis xAxis, YAxis yAxis, boolean showXLabel, boolean showYLabel) throws QueryException {
Element[] charts = new Element[chart.getYColumns().size()];
for (int i = 0; i < charts.length; i++) {
LineChart lineChart = new LineChart();
lineChart.setWidth(4);
lineChart.setColour(getHexColor(chart.getForegrounds().get(i)));
lineChart.setAlpha(getAlpha(chart.getTransparency()));
lineChart.setDotSize(5);
LineChart.Style.Type type;
byte style = chart.getType().getStyle();
switch (style) {
case ChartType.STYLE_LINE_DOT_SOLID:
type = LineChart.Style.Type.SOLID_DOT;
break;
case ChartType.STYLE_LINE_DOT_HOLLOW:
type = LineChart.Style.Type.HALLOW_DOT;
break;
case ChartType.STYLE_LINE_DOT_ANCHOR:
type = LineChart.Style.Type.ANCHOR;
break;
case ChartType.STYLE_LINE_DOT_BOW:
type = LineChart.Style.Type.BOW;
break;
case ChartType.STYLE_LINE_DOT_STAR:
type = LineChart.Style.Type.STAR;
break;
default:
type = LineChart.Style.Type.DOT;
break;
}
lineChart.setDotStyle(new LineChart.Style(type, "#111111", 4, 4).setRotation(90));
lineChart.setFontSize(chart.getFont().getSize());
lineChart.setOnShow(new AnimatedElement.OnShow(EXPLODE));
charts[i] = lineChart;
}
createChart(xAxis, yAxis, showXLabel, showYLabel, charts);
return charts;
}
private Element[] createAreaChart(XAxis xAxis, YAxis yAxis, boolean showXLabel, boolean showYLabel) throws QueryException {
Element[] charts = new Element[chart.getYColumns().size()];
for (int i = 0; i < charts.length; i++) {
AreaLineChart areaChart = new AreaLineChart();
areaChart.setWidth(4);
areaChart.setColour(getHexColor(chart.getForegrounds().get(i)));
areaChart.setAlpha(getAlpha(chart.getTransparency()));
areaChart.setDotSize(5);
areaChart.setFontSize(chart.getFont().getSize());
areaChart.setOnShow(new AnimatedElement.OnShow(MID_SLIDE));
charts[i] = areaChart;
}
createChart(xAxis, yAxis, showXLabel, showYLabel, charts);
return charts;
}
private void createChart(XAxis xAxis, YAxis yAxis, boolean showXLabel, boolean showYLabel, Element[] elementChart) throws QueryException {
Number min = Double.MAX_VALUE;
Number max = Double.MIN_VALUE;
int row = 0;
Object previous = null;
String xColumn = chart.getXColumn();
String xPattern = chart.getXPattern();
String lastXValue = "";
boolean isPie = elementChart[0] instanceof PieChart;
boolean isHorizontal = elementChart[0] instanceof HorizontalBarChart;
boolean isStacked = elementChart[0] instanceof StackedBarChart;
int chartsNo = elementChart.length;
GFunction[] functions = new GFunction[chartsNo];
List keys = new ArrayList();
for (int i = 0; i < chartsNo; i++) {
functions[i] = FunctionFactory.getFunction(chart.getYFunction());
if ((chart.getYColumnsLegends() != null) && (i < chart.getYColumnsLegends().size())) {
if (chart.getYColumnsLegends().get(i) != null) {
if (isStacked) {
StackedBarChart.Key key = new StackedBarChart.Key(getHexColor(chart.getForegrounds().get(i)),
StringUtil.getI18nString(replaceParameters(chart.getYColumnsLegends().get(i)),I18nUtil.getLanguageByName(chart, language)), 12);
keys.add(key);
} else {
elementChart[i].setText(StringUtil.getI18nString(replaceParameters(chart.getYColumnsLegends().get(i)),I18nUtil.getLanguageByName(chart, language)));
}
}
}
}
// legends for stacked bar chart
if (isStacked) {
((StackedBarChart)elementChart[0]).addKeys(keys);
}
int groups = 1;
HashMap infoLabels = new HashMap();
while (result.hasNext()) {
Object[] objects = new Object[chartsNo];
Number[] computedValues = new Number[chartsNo];
for (int i = 0; i < chartsNo; i++) {
if (chart.getYColumns().get(i) != null) {
objects[i] = result.nextValue(chart.getYColumns().get(i));
Number value = null;
if (objects[i] instanceof Number) {
value = (Number) objects[i];
} else if (objects[i] != null) {
value = 1;
}
if (value == null) {
value = 0;
}
// open flash chart bug:
// if value is of type BigDecimal and chart is StackedBarChart : json has values between ""
// and bars are drawn over the title (a good stackedbarchart has not the values between "")
// curiously all other chart types have values between ""
// so here we assure that values are not BigDecimals
computedValues[i] = value.doubleValue();
}
}
Object xValue;
if (row == 0) {
xValue = result.nextValue(xColumn);
lastXValue = getStringValue(xColumn, xPattern);
} else {
xValue = previous;
}
Object newXValue = result.nextValue(xColumn);
boolean add = false;
// no function : add the value
if (AbstractGFunction.NOOP.equals(functions[0].getName())) {
lastXValue = getStringValue(xColumn, xPattern);
add = true;
// compute function
} else {
boolean equals = FunctionUtil.parameterEquals(xValue, newXValue);
if (equals) {
for (int i = 0; i < chartsNo; i++) {
functions[i].compute(objects[i]);
}
} else {
for (int i = 0; i < chartsNo; i++) {
add = true;
computedValues[i] = (Number) functions[i].getComputedValue();
functions[i].reset();
functions[i].compute(objects[i]);
}
}
}
if (add) {
//System.out.println("compValue="+computedValue);
Number sum = 0;
for (int i = 0; i < chartsNo; i++) {
addValue(computedValues[i], lastXValue, elementChart[i], i);
if (!isStacked) {
min = Math.min(min.doubleValue(), computedValues[i].doubleValue());
max = Math.max(max.doubleValue(), computedValues[i].doubleValue());
} else {
sum = sum.doubleValue() + computedValues[i].doubleValue();
}
}
if ((chart.getStartingFromZero() != null) && chart.getStartingFromZero() && (min.intValue() > 0)) {
min = 0;
}
if (isStacked) {
min = 0;
max = Math.max(max.doubleValue(), sum.doubleValue());
}
infoLabels.put(groups, lastXValue);
groups++;
//System.out.println("add label : " + lastXValue + " g="+ groups);
lastXValue = getStringValue(xColumn, xPattern);
}
row++;
previous = newXValue;
}
// last group
if (!AbstractGFunction.NOOP.equals(functions[0].getName())) {
Number sum = 0;
for (int i = 0; i < chartsNo; i++) {
Number value = (Number) functions[i].getComputedValue();
addValue(value, lastXValue, elementChart[i], i);
if (!isStacked) {
min = Math.min(min.doubleValue(), value.doubleValue());
max = Math.max(max.doubleValue(), value.doubleValue());
} else {
sum = sum.doubleValue() + value.doubleValue();
}
}
if ((chart.getStartingFromZero() != null) && chart.getStartingFromZero() && (min.intValue() > 0)) {
min = 0;
}
if (isStacked) {
min = 0;
max = Math.max(max.doubleValue(), sum.doubleValue());
}
infoLabels.put(groups, lastXValue);
} else {
groups--;
}
// info labels
if (!isPie) {
// for horizontal charts (horizontal bars)
if (isHorizontal) {
int factor = groups < 2 ? 0 : (groups - 2) / 10;
YRange range = setAxisRange(yAxis, 1, groups - 0.5);
for (int i = groups; i > 0; i--) {
//System.out.println("add label : " + infoLabels.get(i) + " pos="+ (groups - i));
String text = infoLabels.get(i);
if (!showXLabel) {
text = "";
}
addYLabel(yAxis, text, groups - i + factor + 1);
for (int j=1; j