/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.services.stats;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import nl.strohalm.cyclos.entities.accounts.SystemAccountType;
import nl.strohalm.cyclos.entities.accounts.transactions.PaymentFilter;
import nl.strohalm.cyclos.entities.groups.Group;
import nl.strohalm.cyclos.entities.reports.StatisticalNumber;
import nl.strohalm.cyclos.services.stats.exceptions.InconsistentDataDimensionsException;
import nl.strohalm.cyclos.services.stats.general.FilterUsed;
import nl.strohalm.cyclos.utils.DataObject;
import nl.strohalm.cyclos.utils.NamedPeriod;
import nl.strohalm.cyclos.utils.StringValuedEnum;
import nl.strohalm.cyclos.utils.statistics.ListOperations;
import org.jfree.chart.plot.Marker;
/**
* this class is the base value object containing the statistical data to be shown in the jsp. It is used for rendering the data tables. For each
* statistic table, the following <b>must</b> be done:
* <ul>
* <li>Assign the data array via the constructor. This is a two dimensional array of Numbers
* <li>set the baseKey. This is the basekey for looking up strings in the language resource bundles <br>
* The baseKey is used for the following items, so these keys must be provided in the language resource bundle:
* <ul>
* <li><b>plain basekey</b> is used for the graph title
* <li><b><code>basekey</code>.title</b> is used as the title of the surrounding window
* <li><b><code>basekey</code>.yAxis</b> is used as the title along the y-axis of the graph
* <li><b><code>basekey</code>.xAxis</b> is used as the title along the x-axis of the graph
* <li><b><code>basekey</code>.row<code>N</code></b> is used as the name of the Nth data row in the table
* <li><b><code>basekey</code>.row<code>N</code>.short</b> is used as a shorter version of this name, to appear at the x-Axis below the Nth category
* in the graph. Note that this string must be as short as possible to fit in the graph.
* <li><b>Helpfile</b>: all the dots (.) are deleted from the basekey, the elements between the dots are capitalized for their first letter, and a
* <code>.jsp</code> is appended. This needs to be provided as a help file.
* </ul>
* <li>set the row keys, or the row Headers. See setRowKeys method for details.
* <li>set the column keys or the column headers.
* <li>If any filters were used, you <b>must</b> pass the used filters via the <code>setFilter</code> methods. Best use the corresponding methods of
* StatisticalService: passPaymentFilter and passGroupFilter.
* </ul>
*
* Optionally, the following may be set:
* <ul>
* <li>set graphType
* <li>in addition to row keys, set one or more row headers for specific strings which need not to be taken from the language resource bundle. You can
* set one at a time, or an array with all of the headers.
* <li>same for column headers, you can also set one or more.
* <li>you can set column subheaders; usually this is used to indicate the units in the column. This will only show up in the table, not in the graph
* <li>set a subtitle for the graph and table
* <li>set the multiGraph field in case you want to display a separate graph for each table row or column
* <li>set showTable to false - this makes that the table is not shown. The default is showTable = true. Don't forget to set a graphtype if showTable
* is false, otherwise nothing is shown.
* <li>set a scale Factor for the x-axis. (like "x 1000")
* <li>set the graph dimensions. See {@link #setGraphDimensions(TableToGraph, Integer, Integer)}
* <li>if any unit along the x or y-axis is to be displayed, you can pass this via the <code>setXAxisUnits</code> method or <code>setYAxisUnits</code>
* method.
* <li>You can set markers for the graph. A marker is a horizontal or vertical line which is drawn through the whole graph in order to indicate a
* special result value or domain value. For example, a vertical line to indicate "today" in a graph with dates along the x-axis.
* </ul>
*
* @author rinke
*/
public class StatisticalResultDTO extends DataObject {
/**
* enum indicating the graph type.
*
* @author Rinke
*
*/
public enum GraphType implements StringValuedEnum {
/**
* no graph
*/
NONE("none"),
/**
* a vertical bar graph
*/
BAR("verticalbar"),
/**
* a line graph
*/
LINE("line"),
/**
* a pie chart
*/
PIE("pie"),
/**
* a chart where each serie is an area, and areas are stacked upon each other.
*/
STACKED_AREA("stackedarea");
/**
* a String describing the value. This String value is used in the jsp to set the graphtype attribute.
*/
private final String value;
private GraphType(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
/**
* an enum indicating how one table should be split up into multiple graphs.
*
* @author Rinke
*
*/
public enum MultiGraph {
/**
* no splitting up
*/
NONE,
/**
* each column in the table should get its own graph
*/
BY_COLUMN,
/**
* each row in the table should get its own graph.
*/
BY_ROW;
}
/**
* static innner class defining the Key Object for a language resouce bundle. A simple String for ResourceKey will not do the job, because
* sometimes a placeholder argument is needed for the key (like "{0}"). This Object allows for placeholder arguments to be passed. Presently only
* used for row keys, not for column keys. See setRowKeys method for usage.
*
* @author rinke
*
*/
public static class ResourceKey {
private String key;
private Object[] args;
public ResourceKey(final String key, final Object... args) {
this.key = key;
this.args = args;
}
public Object[] getArgs() {
return args;
}
public String getKey() {
return key;
}
}
/**
* This enum determines which orientation is used when the table is converted to a graph.
*
* @author Rinke
*/
public enum TableToGraph {
/**
* the table columns become the categories along the x-axis of the graph. This means that the table rows become the series in the legend of
* the graph.
*/
COLUMN_IS_CATEGORY,
/**
* the table columns become the series in the legend of the graph. This means that the table rows become the categories along the x-axis of
* the graph.
*/
COLUMN_IS_SERIES;
}
private static final long serialVersionUID = 7243923567624382393L;
/**
* public pseudo-constructor; calls the private constructor.
* @return a StatisticalResultDTO instance, containing no data, and display graph nor table.
*/
static StatisticalResultDTO noDataAvailable(final String key) {
final StatisticalResultDTO result = new StatisticalResultDTO();
result.setBaseKey(key);
return result;
}
/**
* the number of rows
*/
private int rows;
/**
* the number of columns
*/
private int columns;
/**
* the keys for the row headers of the table. The real headers must be fetched from the language resource bundle with these keys.
*/
private ResourceKey[] rowKeys;
/**
* the real strings for the row headers. In some cases, these strings are NOT fetched from the language resource bundle, for example in case of a
* payment filter name. In such a case, the actual name is stored in this field.
*/
private String[] rowHeaders;
/**
* the keys for the column headers of the table. The real headers are fetched from the language resource bundle with these keys.
*/
private String[] columnKeys;
/**
* the headers above the table columns; these are also the seriesNames of the graph. This is used in case the strings are not fetched from the
* resource bundles, for example with strings like "2006" or "%".
*/
private String[] columnHeaders;
/**
* the subheaders above the table columns; usually these are used to indicate the units in the column.
*/
private String[] columnSubHeaders;
/**
* The data for the table; can only be set via the constructor
*/
private Number[][] tableCells;
/**
* The number of categories in the graph. Categories are set on the x-axis; usually, this correspondents to the rows in the table, or in some
* exceptional cases (when <code>
* dontSwitchXY == true</code) in columns in the table. <br>
* It can have the following values:
* <ul>
* <li><code>null</code>: all categories in the table will be rendered in the graph. <li><i>value > number of table categories</i>: ignored,
* treated as if it were <code>null</code>. <li><i>value < number of table categories</i>: only this amount of categories is shown in the table,
* starting from the first category. So the last or the last few categories which are in the table will not be shown in the graph. </ul> <br>
* So set this field only if there are categories you want to show in the table but exclude from the graph.
*/
private Integer categoryCount;
/**
* The number of series in the graph. Series are set in the legend of the graph; usually, this correspondents to the columns in the table, or in
* some exceptional cases (when <code>
* dontSwitchXY == true</code) in rows in the table. <br>
* It can have the following values:
* <ul>
* <li><code>null</code>: all series in the table will be rendered in the graph. <li><i>value > number of table series</i>: ignored, treated as if
* it were <code>null</code>. <li><i>value < number of table series</i>: only this amount of series is shown in the table, starting from the first
* serie. So the last or the last few series which are in the table will not be shown in the graph. </ul> <br>
* So set this field only if there are series you want to show in the table but exclude from the graph.
*/
private Integer seriesCount;
/**
* this is the base key for the language resource files. <br>
* Language Strings are being taken from this resource file by this base key.<br>
* The basekey is used for the following, so all of these must be provided in the language resource bundle:
* <ul>
* <li><b>plain basekey</b> is used for the graph title
* <li><b><code>basekey</code>.title</b> is used as the title of the surrounding window
* <li>
* <b><code>basekey</code>.yAxis</b> is used as the title along the y-axis of the graph
* <li><b><code>basekey</code>.xAxis</b> is used as the title along the x-axis of the graph
* <li><b><code>basekey</code>.row<code>N</code></b> is used as the name of the Nth data row in the table
* <li><b> <code>basekey</code>.row<code>N</code>.short</b> is used as a shorter version of this name, to appear at the x-Axis below the Nth
* category in the graph. Note that this string must be as short as possible to fit in the graph.
* <li><b>Helpfile</b>: all the dots (.) are deleted from the basekey, the elements between the dots are capitalized for their first letter, and a
* <code>.jsp</code> is appended. This needs to be provided as a help file.
* </ul>
*/
private String baseKey;
/**
* subtitle above the graph or above the leftmost column in the table
*/
private String subTitle;
/**
* the scalefactor for the x-axis. This is usually not used, but it may contains Strings like "(x 1000)".
*/
private String scaleFactorX;
/**
* the type of the graph, can be one of BAR, LINE, PIE or NONE
*/
private GraphType graphType;
/**
* indicates if the chart is a combined chart. If so, for each series (column) OR for each category (table row) a separate graph is created. The
* graphs are tiled in vertical direction, while there is one combined x-axis. If none (the default), all series and categories are shown in one
* single graph, having both x-axis and y-axis in common. The table just shows all the series and categories, independant of the value of this
* field. So this field only effects the presentation of the graph, not of the table.
*/
private MultiGraph multiGraph;
/**
* true if the table must NOT be shown. This is true in case of histograms.
*/
private boolean showTable;
/**
* For most graphs, x and y of the table need to be switched in order to display the graph correctly.<br>
* In special cases, this does <b>NOT</b> need to be switched. In such a case, set <code>dontSwitchXY</code> to true.
*/
private boolean dontSwitchXY;
/**
* the list with used filters, to be displayed in the "Filters used" box below each graph or table.
*/
private final List<FilterUsed> filtersUsed = new ArrayList<FilterUsed>();
/**
* a String representing the units to be displayed along the y axis of a graph.
*/
private String yAxisUnits = "";
/**
* a String representing the units to be displayed along the x axis of a graph.
*/
private String xAxisUnits = "";
/**
* a String used to set the help file. The default is "statistics". Only use in case this is another file than statistics.
*/
private String helpFile = "statistics";
/**
* an array of Markers. Markers are horizontal or vertical lines to draw through the graph to mark a special result value or domain value. In this
* case, these are the domain markers (= vertical lines), but a future version may also include a special field for range markers (= horizontal
* lines). Markers should be set to the jsp with the following construct:
*
* <pre>
* <cewolf:chartpostprocessor id="chartPostProcessorImpl" >
* <cewolf:param name="domainMarkers" value="${dataTable.domainMarkers}" />
* </cewolf:chartpostprocessor>
* </pre>
*
* For each Marker in the array, you MUST set the place where the marker is to be drawn in the graph. In case of Category plots, this is the label
* which is placed on the x-axis for which you want to put the vertical line in the graph.<br>
* Optionally, you can set a color (setPaint) and a label (setLabel) to the Marker. The label is a string literal; if you want to put a language
* string in this label, you must set the keys for the marker on the service, and read these and replace them on the action. See for example
* paymentService.getSimulateConversionGraph and SimulateConversionAction.
*/
private Marker[] domainMarkers;
/**
* This constructor only sets the data without column and row names.
* @param tableCells a Number[][], where: Series = j = columnHeaders; Categories = i = rowKeys
*/
public StatisticalResultDTO(final Number[][] tableCells) {
this.tableCells = tableCells;
rows = tableCells.length;
rowKeys = new ResourceKey[rows];
rowHeaders = new String[rows];
columns = tableCells[0].length;
columnKeys = new String[columns];
columnHeaders = new String[columns];
columnSubHeaders = new String[columns];
graphType = GraphType.NONE;
multiGraph = MultiGraph.NONE;
showTable = true;
}
/**
* private constructor, displays no graphs nor tables, and contains no data
*
*/
private StatisticalResultDTO() {
graphType = GraphType.NONE;
tableCells = new Number[0][0];
}
// ########################### Column Getters And Setters ###############################
// #######################################################################################
public String getBaseKey() {
return baseKey;
}
/**
*
* @return the number of categories for the graph.
*/
public int getCategoriesCount() {
if (categoryCount != null) {
return categoryCount;
}
return (dontSwitchXY) ? columns : rows;
}
public String getColumnHeader(final int index) {
return columnHeaders[index];
}
public String[] getColumnHeaders() {
return columnHeaders;
}
public String getColumnKey(final int index) {
return columnKeys[index];
}
public String[] getColumnKeys() {
return columnKeys;
}
public String[] getColumnSubHeaders() {
return columnSubHeaders;
}
/**
* gets domain markers for the graph. See the markers field for further explanation.
*/
public Marker[] getDomainMarkers() {
return domainMarkers;
}
/**
* gets the List with the used filter specs.
*
* @return a List with FilterUsed Objects, one object for every relevant filter.
*/
public List<FilterUsed> getFiltersUsed() {
return filtersUsed;
}
public GraphType getGraphType() {
return graphType;
}
public String getHelpFile() {
return helpFile;
}
public MultiGraph getMultiGraph() {
return multiGraph;
}
public String getRowHeader(final int index) {
return rowHeaders[index];
}
public String[] getRowHeaders() {
return rowHeaders;
}
/**
* returns the rowKey with the given index as a String. Any placeholder arguments are ignored.
*
* @param index
* @return a String containing the row key, without any placeholder arguments (like "{0}")
*/
public String getRowKey(final int index) {
return rowKeys[index].getKey();
}
/**
* returns the arguments for the row key with the given index, for use in keys having {n} placeholder arguments.
* @param index the index of the row key
* @return an array of Objects holding the placeholder arguments for the key. If no arguments are available, the length of the array is 0.
*/
public Object[] getRowKeyArgs(final int index) {
return rowKeys[index].getArgs();
}
public ResourceKey[] getRowKeys() {
return rowKeys;
}
public String getScaleFactorX() {
return scaleFactorX;
}
/**
*
* @return the number of series for the graph
*/
public int getSeriesCount() {
if (seriesCount != null) {
return seriesCount;
}
return (dontSwitchXY) ? rows : columns;
}
public String getSubTitle() {
return subTitle;
}
public Number[][] getTableCells() {
return tableCells;
}
/**
* gets the units for the x-axis. Use <code>getParenthesizedYAxisUnits()</code> if you need the units between ().
*/
public String getXAxisUnits() {
return xAxisUnits;
}
/**
* gets the units for the y-axis. Use <code>getParenthesizedYAxisUnits()</code> if you need the units between ().
*/
public String getYAxisUnits() {
return yAxisUnits;
}
/**
* checks if the numbers in this object do have errors defined. It checks if the complete range has any error bar.
* @return true if error bars are defined.
*/
public boolean hasErrorBars() {
for (final Number[] element : tableCells) {
for (final Number element2 : element) {
if (element2 instanceof StatisticalNumber) {
if (((StatisticalNumber) element2).hasErrorBar()) {
return true;
}
}
}
}
return false;
}
public boolean isDontSwitchXY() {
return dontSwitchXY;
}
public boolean isShowTable() {
return showTable;
}
/**
* Allows you to order the series according to an array of bytes with order numbers. Of course, this can only be done after the tableCells and
* seriesNames have been set to the graph/table. All corresponding headers and keys, as well as the data is reordered.<br>
* Example:<br>
* Suppose the tableCells contain the following points:
*
* <pre>
* { { 5, 7, 2 }, { 5, 7, 2 }, { 5, 7, 2 }, { 5, 7, 2 }, { 5, 7, 2 } }
* </pre>
*
* Suppose the seriesOrder param is {2, 1, 3};<br>
* After running this method, the tableCells will look like this:
*
* <pre>
* { { 7, 5, 2 }, { 7, 5, 2 }, { 7, 5, 2 }, { 7, 5, 2 }, { 7, 5, 2 } }
* </pre>
*
* @param seriesOrder an array of bytes containing the index numbers of the series. The values do not need to be subsequent numbers; also {16, 8,
* 10} would be possible. If a value is encountered more than once, corresponding elements are considered equal at ordering.
*/
public void orderSeries(final byte[] seriesOrder) {
// helper class for bundling all relevant elements in order to index them in one go, with one rule
class IndexedSerie {
private Number[] numbers;
private String seriesHeader;
private String seriesSubHeader;
private String seriesKey;
private byte index;
IndexedSerie(final Number[] numbers, final int originalIndex, final byte index) {
this.numbers = numbers;
if (columnHeaders != null && columnHeaders.length > 0) {
seriesHeader = columnHeaders[originalIndex];
}
// the key by def exists, else IllegalArgumentException
seriesKey = columnKeys[originalIndex];
if (columnSubHeaders != null && columnSubHeaders.length > 0) {
seriesSubHeader = columnSubHeaders[originalIndex];
}
this.index = index;
}
}
final Comparator<IndexedSerie> comparator = new Comparator<IndexedSerie>() {
public int compare(final IndexedSerie o1, final IndexedSerie o2) {
if (o1.index == o2.index) {
// first the smallest
final double result = o1.numbers[0].doubleValue() - o2.numbers[0].doubleValue();
return (int) Math.signum(result);
}
// first the one with the lowest index
return o1.index - o2.index;
}
};
// first some checks
if (tableCells == null || columnKeys == null) {
throw new IllegalArgumentException("Method orderSeries may only be called after setting columnKeys.");
}
if (tableCells.length == 0 || tableCells[0] == null || tableCells[0].length == 0) {
// nothing to order, so return
return;
}
if (seriesOrder == null || seriesOrder.length != columnKeys.length || seriesOrder.length != tableCells[0].length) {
throw new InconsistentDataDimensionsException("SeriesNames / dataset length does not match order length.");
}
// transpose so that we get an array of different series, in stead of an array of points
final Number[][] transposedMatrix = ListOperations.transposeMatrix(tableCells);
// rewrite transposed Matrix as an array of series and add the indexes
final IndexedSerie[] arrayOfSeries = new IndexedSerie[seriesOrder.length];
for (int i = 0; i < seriesOrder.length; i++) {
arrayOfSeries[i] = new IndexedSerie(transposedMatrix[i], i, seriesOrder[i]);
}
// sort array of series on their added index
Arrays.sort(arrayOfSeries, comparator);
// write back the array of series to a matrix
final Number[][] transposedOrderedMatrix = new Number[seriesOrder.length][transposedMatrix[0].length];
for (int i = 0; i < seriesOrder.length; i++) {
transposedOrderedMatrix[i] = arrayOfSeries[i].numbers;
// keys and headers can already be written back
columnKeys[i] = arrayOfSeries[i].seriesKey;
if (columnHeaders != null && columnHeaders.length > 0) {
columnHeaders[i] = arrayOfSeries[i].seriesHeader;
}
if (columnSubHeaders != null && columnSubHeaders.length > 0) {
columnSubHeaders[i] = arrayOfSeries[i].seriesSubHeader;
}
}
// transpose back
final Number[][] orderedMatrix = ListOperations.transposeMatrix(transposedOrderedMatrix);
// write back
tableCells = orderedMatrix;
}
public void setBaseKey(final String baseKey) {
this.baseKey = baseKey;
}
/**
* a setter to set one specific indexed columnheader.
* @param header
* @param index
*/
public void setColumnHeader(final String header, final int index) {
columnHeaders[index] = header;
}
/**
* Simple setter for the columnKeys (seriesnames)
* @param lColumnKeys a String[], the header keys above the printed table (and series names keys of the graph)
* @throws InconsistentDataDimensionsException. Raised if length of the String array param is inconsistent with data (and the number of columns >
* 0).
*/
public void setColumnKeys(final String[] lColumnKeys) throws InconsistentDataDimensionsException {
if (columns > 0 && lColumnKeys.length != columns) {
throw new InconsistentDataDimensionsException("SeriesNames length and dataset length do not match.");
}
columnKeys = lColumnKeys;
}
/**
* Sets domain markers for the graph. See the domainMarkers field for further explanation
* @param markers
*/
public void setDomainMarkers(final Marker[] markers) {
domainMarkers = markers;
}
/**
* This setter sets three properties which can only be set at once (because the second and third may only be set AFTER the first is set, I created
* a 3-in-1-setter).<br>
* It takes care for the number of categories and series in the graph. There is no obligation to set this; if you do not set it, it follows the
* default behaviour. All params accept <code>null</code> values, which also corresponds to the default behaviour. <br>
* The default behaviour is:
* <ul>
* <li>a table column in the graph will be shown as a serie in the graph, meaning that it shows up in the legend of the graph.
* <li>the number of table columns equals the number of series in the graph.
* <li>the number of table rows equals the number of categories along the x-axis of the graph.
* </ul>
* @param tableToGraph a <code>TableToGraph</code> enum which determines how the orientation of the graph is. If this is set to <code>null</code>
* the default behaviour will be kept, meaning that table columns are converted to graph series.<br>
* Note that this param only needs to be set in exceptional cases. Normally you can just pass <code>null</code>.
* @param seriesCount an <code>Integer</code> which indicates the number of series in the graph. If (and only if) this is smaller than the number
* of columns in the table (or the number of rows, depending on the <code>columnIsCategory</code> param), then only part of the available series
* is shown in the graph, starting the count at the first series.<br>
* A <code>null</code> parameter enforces the default behaviour.<br>
* Example: if <code>columnIsCategory</code> is false, the table has 5 columns, and seriesCount = 4, then only the first 4 columns of the table
* are shown in the graph; the last column of the table is not shown.
* @param categoriesCount as with seriesCount, only now for categories along the x-axis of the graph.
*/
public void setGraphDimensions(final TableToGraph tableToGraph, final Integer seriesCount, final Integer categoriesCount) {
if (tableToGraph != null && tableToGraph == TableToGraph.COLUMN_IS_CATEGORY) {
dontSwitchXY = true;
} else {
dontSwitchXY = false;
}
final int tableSeriesCount = (dontSwitchXY) ? rows : columns;
if (seriesCount != null && seriesCount < tableSeriesCount) {
this.seriesCount = seriesCount;
} else {
this.seriesCount = null;
}
final int tableCategoriesCount = (dontSwitchXY) ? columns : rows;
if (categoriesCount != null && categoriesCount < tableCategoriesCount) {
categoryCount = categoriesCount;
} else {
categoryCount = null;
}
}
public void setGraphType(final GraphType graphType) {
this.graphType = graphType;
}
/**
* sets the help file. The default is "statistics". Use this only in case the help file is another than statistics, so in case this
* class is used outside the context of the statistics module.
* @param fileName
*/
public void setHelpFile(final String fileName) {
helpFile = fileName;
}
/**
* a setter to set one specific indexed rowheader.
* @param header
* @param index
*/
public void setRowHeader(final String header, final int index) {
rowHeaders[index] = header;
}
/**
* A setter for the categories or table row headers. Use this version if you need a param inside one or more of the keys. Usage:
* <code>final StatisticalResultDTO.ResourceKey[] rowKeys = new StatisticalResultDTO.ResourceKey[n];
* rowKeys[k] = new StatisticalResultDTO.ResourceKey(keyStringForKeyWithoutArgument);
* rowKeys[m] = new StatisticalResultDTO.ResourceKey(keyStringForKeyWithArgument, new Object[] { arg0, arg1 });
* statisticalResultDTO.setRowKeys(rowKeys);</code>
*
* @param rowKeys = categories. An Array containing ResourceKey Objects. The headers for each row, and the categories along the x-axis of the
* graph. Note that these are the KEYS for the row header strings; these corresponding strings are found in the language resource bundle.
* @throws InconsistentDataDimensionsException. Raised if the length of the String array parameter does not match the length of the already set 2
* dimensional array with the data (and if the number of rows > 0).
*/
public void setRowKeys(final ResourceKey[] rowKeys) throws InconsistentDataDimensionsException {
if (rows > 0 && rowKeys.length != rows) {
throw new InconsistentDataDimensionsException("Number of rowKeys and dataset length do not match.");
}
this.rowKeys = rowKeys;
}
/**
* a Simple setter for the categories or table row headers. Use this version simply when only needing Strings for keys, without any parameters.
* @param rowKeys = categories. The headers for each row, and the categories along the x-axis of the graph. Note that these are the KEYS for the
* row header strings; these corresponding strings are found in the language resource bundle.
* @throws InconsistentDataDimensionsException. Raised if the length of the String array parameter does not match the length of the already set 2
* dimensional array with the data (and if the number of rows > 0).
*/
public void setRowKeys(final String[] rowKeys) throws InconsistentDataDimensionsException {
if (rows > 0 && rowKeys.length != rows) {
throw new InconsistentDataDimensionsException("Number of rowKeys and dataset length do not match.");
}
final ResourceKey[] newRowKeys = new ResourceKey[rowKeys.length];
for (int i = 0; i < rowKeys.length; i++) {
newRowKeys[i] = new ResourceKey(rowKeys[i]);
}
this.rowKeys = newRowKeys;
}
/**
* makes that the table is not shown. You MUST set a graphtype then.
* @param showTable
*/
public void setShowTable(final boolean showTable) {
this.showTable = showTable;
}
public void setSubTitle(final String subTitle) {
this.subTitle = subTitle;
}
/**
* if any x-axis unit is to be displayed with the x-axis label, you can pass it via this method. If you need a key in stead of a string, we
* suggest you don't use this method, but just place the unit between parenthesis behind the <baseKey>.xAxis string in the resouce bundle.
* @param axisUnits the String to be displayed along the x-axis. It will be placed between () after the normal x-axis label.
*/
public void setXAxisUnits(final String axisUnits) {
xAxisUnits = axisUnits;
}
/**
* if any y-axis unit is to be displayed with the y-axis label, you can pass it via this method.
* @param axisUnits the String to be displayed along the y-axis. It will be placed between () after the normal y-axis label.
*/
public void setYAxisUnits(final String axisUnits) {
yAxisUnits = axisUnits;
}
/**
* A simple setter for the columnHeaders (= seriesNames).
* @param columnHeaders. The headers above the printed table (and the series names of the graph)
* @throws InconsistenDataDimensionsException. Raised if the length of the String array parameter does not match the length of the allready set 2
* dimensional array with the data (and the number of columns > 0).
*/
void setColumnHeaders(final String[] lColumnHeaders) throws InconsistentDataDimensionsException {
if (columns > 0 && lColumnHeaders.length != columns) {
throw new InconsistentDataDimensionsException("SeriesNames length and dataset length do not match.");
}
columnHeaders = lColumnHeaders;
}
/**
* see setColumnHeaders. This works the same, but for columnSubHeaders (which are usually used for unit indication).
* @param lColumnSubHeaders
* @throws InconsistentDataDimensionsException
*/
void setColumnSubHeaders(final String[] lColumnSubHeaders) throws InconsistentDataDimensionsException {
if (columns > 0 && lColumnSubHeaders.length != columns) {
throw new InconsistentDataDimensionsException("SeriesNames length and dataset length do not match.");
}
columnSubHeaders = lColumnSubHeaders;
}
/**
* sets the filter for the Member Groups. If you set this, it is shown below the graph or table as "used filters".
*
* @param groupFilter a <code>Collection</code> of <code>Group</code>s.
*/
void setFilter(final Collection<Group> groupFilter) {
FilterUsed filterUsed;
if (groupFilter == null) {
setFilterAsNotUsed(FilterUsed.FilterType.GROUP);
return;
} else if (groupFilter.size() == 0) {
filterUsed = FilterUsed.nothingSelected(FilterUsed.FilterType.GROUP, "member.search.allGroups");
} else {
final List<String> names = new ArrayList<String>(groupFilter.size());
for (final Group g : groupFilter) {
names.add(g.toString());
}
filterUsed = new FilterUsed(FilterUsed.FilterType.GROUP, names);
}
filtersUsed.add(filterUsed);
}
/**
* Displays a <code>NamedPeriod</code> in the "filters used" box below every graph or table.
*
* @param period a <code>NamedPeriod</code> of which the name will be displayed
*/
void setFilter(final NamedPeriod period) {
final FilterUsed filterUsed = new FilterUsed(FilterUsed.FilterType.PERIOD, period.getName());
filtersUsed.add(filterUsed);
}
/**
* Mark a <code>PaymentFilter</code> so that it is displayed in the "filters used" box below each graph or table.
*
* @param paymentFilter the <code>PaymentFilter</code>. If <code>null</code> then it is shown as "all payments". Otherwise, the name is used. Use
* <code>setFilter(filterType)</code> if you want to display "not used" for the payment filter.
*/
void setFilter(final PaymentFilter paymentFilter) {
final FilterUsed filterUsed;
if (paymentFilter == null) {
filterUsed = FilterUsed.nothingSelected(FilterUsed.FilterType.PAYMENT, "reports.stats.general.allPaymentTypes");
} else {
filterUsed = new FilterUsed(FilterUsed.FilterType.PAYMENT, paymentFilter.toString());
}
filtersUsed.add(filterUsed);
}
/**
* Mark a <code>SystemAccountType</code> so that it is displayed in the "filters used" box below each graph or table.
*
* @param systemAccountFilter the <code>SystemAccountType</code> which was used. Note that this may not be null.
*/
void setFilter(final SystemAccountType systemAccountFilter) {
final FilterUsed filterUsed = new FilterUsed(FilterUsed.FilterType.SYSTEM_ACCOUNT, systemAccountFilter.toString());
filtersUsed.add(filterUsed);
}
/**
* Mark a Filter so that it is displayed as "not used" in the results.
*/
void setFilterAsNotUsed(final FilterUsed.FilterType filterType) {
final FilterUsed filterUsed = FilterUsed.noFilterUsed(filterType);
filtersUsed.add(filterUsed);
}
void setMultiGraph(final MultiGraph multiGraph) {
this.multiGraph = multiGraph;
}
/**
* A simple setter for the table row strings. See also setRowKeys. Generally, when a client uses this class, first the rowHeaders are checked. For
* each missing rowHeaders, the rowKey is used to fetch the string from the language resource bundle.
* @param rowHeaders
* @throws InconsistentDataDimensionsException if the number of headers does not match the number of rows, and the number of rows > 0.
*
*/
void setRowHeaders(final String[] rowHeaders) throws InconsistentDataDimensionsException {
if (rows > 0 && rowHeaders.length != rows) {
throw new InconsistentDataDimensionsException("Number of rowKeys and dataset length do not match.");
}
this.rowHeaders = rowHeaders;
}
void setScaleFactorX(final String scaleFactorX) {
this.scaleFactorX = scaleFactorX;
}
}