package com.baselet.element.elementnew.plot.drawer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import com.baselet.control.SharedUtils;
import com.baselet.control.basics.geom.Dimension;
import com.baselet.control.basics.geom.Point;
import com.baselet.control.enums.AlignHorizontal;
import com.baselet.diagram.draw.DrawHandler;
import com.baselet.diagram.draw.helper.ColorOwn;
import com.baselet.diagram.draw.helper.ColorOwn.Transparency;
public class PlotDrawHandler {
// Enumerations
public enum Position {
LEFT, UP, DOWN, RIGHT
}
// Plot specific settings
private String[] title;
private String[] desc;
private Double[][] values;
private TreeSet<Double> valuesSorted;
private TreeSet<Double> valuesShownOnAxisSorted;
// private Double[][] valuesMinMaxCorrected; // if all values are >0 or all values are <0 the distance from 0 to the first real value will be subtracted
protected DrawHandler base;
private Double minVal = null;
private Double maxVal = null;
private List<String> colors;
private final Canvas canvas;
private final AxisConfig axisConfig;
public PlotDrawHandler(DrawHandler baseDrawHandler, Dimension size) {
base = baseDrawHandler;
// drawLegend = false;
axisConfig = new AxisConfig();
canvas = new Canvas(size);
}
// Legend Settings
// private boolean drawLegend;
// private Rectangle legendPos;
// /**
// * Abstracts the axis drawing from the type of variables on the axis (description or values)
// * Methods called from this method don't know if they handle a description or value axis
// * @param xAxis if true this is the method call for the x-axis
// * @param valuesSorted the sorted list of values
// */
// private void abstractValueDescFromAxisAndDraw(boolean xAxis) {
// int segmentDisp, lastDrawnSegmentDisp;
//
// if /* thisIsDescAxis */ ((xAxis && axisConfig.isxDescription()) || (!xAxis && !axisConfig.isxDescription())) {
// // if (axisConfig.drawDescriptionAxis()) drawAxisLine(xAxis);
// if (true) {
// lastDrawnSegmentDisp = -axisConfig.getDescSegment()/2;
// for (int i = 0; i < desc.length; i++) {
// segmentDisp = (i * axisConfig.getDescSegment()) + (axisConfig.getDescSegment()/2);
// String value;
// if (xAxis) value = desc[i];
// else value = desc[desc.length-i-1]; // yAxis is drawn from bottom to top, therefore invert computation direction
// // axisConfig.activateDescriptionAxis();
// lastDrawnSegmentDisp = drawMarkerTextIfThereIsEnoughSpace(xAxis, segmentDisp, lastDrawnSegmentDisp, value);
// }
// }
// }
// else /* thisIsValueAxis */ {
// // if (axisConfig.drawValueAxis()) drawAxisLine(xAxis);
// if (true) {
// Double[] valuesToDisplay;
// if (axisConfig.drawValueAxisMarkersAll()) {
// if (axisConfig.getValueSegment() < 1) valuesToDisplay = Utils.createDoubleArrayFromTo(minVal, maxVal, Math.ceil(1/axisConfig.getValueSegment()));
// else valuesToDisplay = Utils.createDoubleArrayFromTo(minVal, maxVal);
// }
// else valuesToDisplay = valuesSorted;
//
// lastDrawnSegmentDisp = (int) (valuesToDisplay[0] * axisConfig.getValueSegment() - 100); // Start at the lowest possible number
// for (Double v : valuesToDisplay) {
// segmentDisp = (int) calculateValuePos(v, axisConfig.getValueSegment());
// // valueStringToDisplay is the String representation of the value (".0" should not be displayed)
// String valueStringToDisplay = (Math.round(v) == v) ? String.valueOf(Math.round(v)) : String.valueOf(v);
// if (axisConfig.drawValueAxisMarkersAll()) {
// int oldLength = valueStringToDisplay.length();
// if (oldLength > 2) {
// valueStringToDisplay = valueStringToDisplay.substring(0, 2);
// for (int i = 0; i < oldLength-2; i++) valueStringToDisplay += "0";
// }
// }
// // if (value == 0) continue; // 0 is not displayed because it would overlap with the arrow end
// // axisConfig.activateValueAxis();
// lastDrawnSegmentDisp = drawMarkerTextIfThereIsEnoughSpace(xAxis, segmentDisp, lastDrawnSegmentDisp, valueStringToDisplay);
// }
// }
// }
// }
//
// /**
// * Draws text descriptions of axes only if there is enough space for it.
// */
// private int drawMarkerTextIfThereIsEnoughSpace(boolean xAxis, int segmentDisp, int lastDrawnSegmentDisp, String valueAsString) {
// boolean drawMarker = false;
// // If text should be displayed markers where there would be no space for the text are not drawn
// // if (axisConfig.drawActiveAxisMarkerText()) {
// int textSpaceNeeded;
// if (xAxis) textSpaceNeeded = base.textWidth(valueAsString);
// else textSpaceNeeded = base.textHeight();
// if ((segmentDisp - lastDrawnSegmentDisp) >= textSpaceNeeded) {
// drawMarker = true;
// lastDrawnSegmentDisp = segmentDisp;
// }
// // }
// else drawMarker = true;
//
// if (drawMarker) drawAxisMarker(xAxis, segmentDisp, valueAsString);
// return lastDrawnSegmentDisp;
// }
// //TODO isnt working properly now
// public void enableLegend(Position position) {
// this.drawLegend = true;
// this.legendPos = getLegendPosition(position);
// }
//
// public void disableLegend() {
// this.drawLegend = false;
// }
//
// private void drawLegend() {
// base.drawRectangle(legendPos.x, legendPos.y, legendPos.width, legendPos.height);
// }
//
// private Rectangle getLegendPosition(Position position) {
// // Calculate size of the legend
// final Rectangle innerLegendBorder = new Rectangle(10, 10, 10, 10);
// int legendWidth = innerLegendBorder.x + innerLegendBorder.width;
// int legendHeight = innerLegendBorder.y + innerLegendBorder.height;
// final int legendSpace = 10;
//
// int textWidth;
// for (String v : desc) {
// legendHeight += base.textHeight();
// textWidth = base.textWidth(v) + innerLegendBorder.x + innerLegendBorder.width;
// if (textWidth > legendWidth) legendWidth = textWidth;
// }
//
// // The outerBorder of the plot must be adjusted to free space for the legend
// int borderX = canvas.getOuterLeftPos();
// int borderY = canvas.getOuterUpPos();
// int borderW = canvas.getOuterRightBorderWidth();
// int borderH = canvas.getOuterDownBorderHeight();
//
// if (position == Position.LEFT) borderX += legendWidth + legendSpace;
// else if (position == Position.RIGHT) borderW += legendWidth + legendSpace;
// else if (position == Position.UP) borderY += legendHeight + legendSpace;
// else if (position == Position.DOWN) borderH += legendHeight + legendSpace;
//
// canvas.setBorder(borderX, borderY, borderW, borderH, AxisConfig.ARROW_DISTANCE);
//
// // Calculate and return the position of the legend rectangle
// final int x, y;
// if (position == Position.LEFT || position == Position.RIGHT) {
// y = (canvas.getInnerDownPos() - legendHeight) / 2;
// if (position == Position.LEFT) {
// x = 1;
// } else {
// x = canvas.getInnerRightPos() - legendWidth - legendSpace/2;
// }
// } else {
// x = (canvas.getInnerRightPos() - legendWidth) / 2;
// if (position == Position.UP) {
// y = 1;
// } else {
// y = canvas.getInnerDownPos() - legendHeight - legendSpace/2;
// }
// }
// return new Rectangle(x, y, legendWidth, legendHeight);
// }
public final void drawPlotAndDescValueAxis(boolean xIsDescription, boolean drawBars, boolean drawLines, boolean drawPoints) {
axisConfig.setxIsDescription(xIsDescription);
setupAxis();
calculateAdditionalSpaceForYAxisTextWidth();
// log.debug("yIsDescription: " + yIsDescription + ", descSegment: " + axisConfig.getDescSegment() + ", valueSegment: " + axisConfig.getValueSegment());
// log.debug("valueRange: " + valueRange + ", barsCount: " + elementCount + ", SourceAxisPos/DescAxisPos: " + axisConfig.getDescAxisPos() + ", BarStart/ValueAxisPos: " + axisConfig.getValueAxisPos());
if (drawBars) {
drawBars(xIsDescription, values, axisConfig.getDescAxisPos(), axisConfig.getValueAxisPos(), axisConfig.getValueSegment(), axisConfig.getDescSegment(), colors);
}
if (drawLines) {
drawLineOrPoints(xIsDescription, values, axisConfig.getDescAxisPos(), axisConfig.getValueAxisPos(), axisConfig.getValueSegment(), axisConfig.getDescSegment(), colors, true);
}
if (drawPoints) {
drawLineOrPoints(xIsDescription, values, axisConfig.getDescAxisPos(), axisConfig.getValueAxisPos(), axisConfig.getValueSegment(), axisConfig.getDescSegment(), colors, false);
}
if (axisConfig.showAxis()) {
drawAxis(xIsDescription, axisConfig.getDescAxisPos(), axisConfig.getValueAxisPos(), axisConfig.getValueSegment(), axisConfig.getDescSegment());
}
}
private void setupAxis() {
final Double valueRange = Math.max(1.0, maxVal - minVal); // The range is >=1 (otherwise nothing will be drawn)
Double negativeRange = 0.0;
if (minVal > 0) {
negativeRange = 0.0;
}
if (minVal < 0) {
if (maxVal < 0) {
negativeRange = valueRange;
}
else {
negativeRange = -minVal;
}
}
int elementCount = desc.length; // Amount of bars/lines/...
for (Double[] vArray : values) {
if (vArray.length > elementCount) {
elementCount = vArray.length;
}
}
// Calculate some necessary variables to draw the bars (these variables abstract from horizontal/vertical to a relative point of view)
if (axisConfig.isxDescription()) {
axisConfig.setDescSegment(canvas.getInnerHorizontalDrawspace() / elementCount);
axisConfig.setValueSegment(canvas.getInnerVerticalDrawspace() / valueRange);
axisConfig.setDescAxisPos((int) (canvas.getInnerDownPos() - axisConfig.getValueSegment() * negativeRange));
axisConfig.setValueAxisPos(canvas.getInnerLeftPos());
}
else {
axisConfig.setDescSegment(canvas.getInnerVerticalDrawspace() / elementCount);
axisConfig.setValueSegment(canvas.getInnerHorizontalDrawspace() / valueRange);
axisConfig.setDescAxisPos((int) (canvas.getInnerLeftPos() + axisConfig.getValueSegment() * negativeRange));
axisConfig.setValueAxisPos(canvas.getInnerUpPos());
}
}
private final void drawAxis(boolean xIsDescription, int sourceAxisPos, int valueAxisPos, Double valueSegment, int descSegment) {
List<Integer> xpoints = new ArrayList<Integer>();
List<String> xtext = new ArrayList<String>();
List<Integer> ypoints = new ArrayList<Integer>();
List<String> ytext = new ArrayList<String>();
int lineIterator = valueAxisPos + descSegment / 2;
for (String d : desc) {
if (xIsDescription) {
xpoints.add(lineIterator);
xtext.add(d);
}
else {
ypoints.add(lineIterator);
ytext.add(d);
}
lineIterator += descSegment;
}
for (Double v : valuesShownOnAxisSorted) {
int linePos = (int) calculateValuePos(v, valueSegment);
if (xIsDescription) {
ypoints.add(sourceAxisPos - linePos);
ytext.add(String.valueOf(v));
}
else {
xpoints.add(sourceAxisPos + linePos);
xtext.add(String.valueOf(v));
}
}
drawGraylines(xpoints, ypoints);
base.setForegroundColor(ColorOwn.BLACK.transparency(Transparency.FOREGROUND));
drawAxisLine();
drawMarkers(xpoints, ypoints);
drawMarkerTexts(xpoints, xtext, ypoints, ytext);
}
/**
* Method to draw one line (which one is specified by the boolean xAxis variable)
* @param xAxis
* @param drawArrows
*/
private void drawAxisLine() {
if (axisConfig.drawXAxis()) {
final int x1 = canvas.getInnerLeftPos();
final int x2 = canvas.getInnerRightPos();
final int y = axisConfig.getxAxisPos();
base.drawLine(x1, y, x2, y);
}
if (axisConfig.drawYAxis()) {
final int x = axisConfig.getyAxisPos();
final int y1 = canvas.getInnerUpPos();
final int y2 = canvas.getInnerDownPos();
base.drawLine(x, y1, x, y2);
}
}
private void drawGraylines(List<Integer> xpoints, List<Integer> ypoints) {
base.setForegroundColor(ColorOwn.BLACK.transparency(Transparency.SELECTION_BACKGROUND));
boolean drawVerticalGraylines = axisConfig.isxDescription() && axisConfig.drawDescriptionAxisMarkerGrayline() || !axisConfig.isxDescription() && axisConfig.drawValueAxisMarkerGrayline();
boolean drawHorizontalGraylines = !axisConfig.isxDescription() && axisConfig.drawDescriptionAxisMarkerGrayline() || axisConfig.isxDescription() && axisConfig.drawValueAxisMarkerGrayline();
if (drawVerticalGraylines) {
for (Integer x : xpoints) {
base.drawLine(x, canvas.getInnerUpPos(), x, canvas.getInnerDownPos());
}
}
if (drawHorizontalGraylines) {
for (Integer y : ypoints) {
base.drawLine(canvas.getInnerLeftPos(), y, canvas.getInnerRightPos(), y);
}
}
}
private void drawMarkers(List<Integer> xpoints, List<Integer> ypoints) {
boolean drawVerticalMarkers = axisConfig.isxDescription() && axisConfig.drawDescriptionAxisMarkers() || !axisConfig.isxDescription() && axisConfig.drawValueAxisMarkers();
boolean drawHorizontalMarkers = !axisConfig.isxDescription() && axisConfig.drawDescriptionAxisMarkers() || axisConfig.isxDescription() && axisConfig.drawValueAxisMarkers();
if (drawVerticalMarkers) {
for (Integer x : xpoints) {
base.drawLine(x, axisConfig.getxAxisPos(), x, axisConfig.getxAxisPos() + AxisConfig.ARROW_SIZE);
}
}
if (drawHorizontalMarkers) {
for (Integer y : ypoints) {
base.drawLine(axisConfig.getyAxisPos() - AxisConfig.ARROW_SIZE, y, axisConfig.getyAxisPos(), y);
}
}
}
private void drawMarkerTexts(List<Integer> xpoints, List<String> xtext, List<Integer> ypoints, List<String> ytext) {
boolean drawVerticalMarkerTexts = axisConfig.isxDescription() && axisConfig.drawDescriptionAxisMarkerText() || !axisConfig.isxDescription() && axisConfig.drawValueAxisMarkerText();
boolean drawHorizontalMarkerTexts = !axisConfig.isxDescription() && axisConfig.drawDescriptionAxisMarkerText() || axisConfig.isxDescription() && axisConfig.drawValueAxisMarkerText();
if (drawVerticalMarkerTexts) {
for (int i = 0; i < xpoints.size(); i++) {
base.print(xtext.get(i), xpoints.get(i), axisConfig.getxAxisPos() + AxisConfig.ARROW_DISTANCE, AlignHorizontal.CENTER);
}
}
if (drawHorizontalMarkerTexts) {
for (int i = 0; i < ypoints.size(); i++) {
base.print(ytext.get(i), axisConfig.getyAxisPos() - 8, (int) (ypoints.get(i) + base.textHeightMax() / 2), AlignHorizontal.RIGHT);
}
}
}
private final void drawLineOrPoints(boolean xIsDescription, Double[][] values, int sourceAxisPos, int valueAxisPos, Double valueSegment, int descSegment, List<String> colors, boolean line) {
int cIndex = 0;
for (int valueIndex = 0; valueIndex < values.length; valueIndex++) {
Double[] vArray = values[valueIndex];
int actualValPos;
int lineIterator = valueAxisPos + descSegment / 2;
List<Point> points = new ArrayList<Point>();
for (Double v : vArray) {
actualValPos = (int) calculateValuePos(v, valueSegment);
if (xIsDescription) {
points.add(new Point(lineIterator, sourceAxisPos - actualValPos));
}
else {
points.add(new Point(sourceAxisPos + actualValPos, lineIterator));
}
lineIterator += descSegment;
}
if (cIndex >= colors.size()) {
cIndex = 0; // Restart with first color if all colors in the array has been used
}
base.setForegroundColor(ColorOwn.forStringOrNull(colors.get(cIndex), Transparency.FOREGROUND));
base.setBackgroundColor(ColorOwn.forStringOrNull(colors.get(cIndex), Transparency.FOREGROUND));
if (line) {
for (int i = 0; i < points.size() - 1; i++) {
Point point1 = points.get(i);
Point point2 = points.get(i + 1);
base.drawLine(point1.x, point1.y, point2.x, point2.y);
}
}
else {
for (int i = 0; i < points.size(); i++) {
Point point = points.get(i);
base.drawCircle(point.x, point.y, 2);
}
}
// print titleCol
base.setForegroundColor(ColorOwn.forStringOrNull(colors.get(cIndex), Transparency.FOREGROUND).darken(75));
base.print(title[valueIndex], points.get(points.size() - 1).x, points.get(points.size() - 1).y, AlignHorizontal.CENTER);
cIndex++;
}
base.resetColorSettings();
}
private final void drawBars(boolean xIsDescription, Double[][] values, int sourceAxisPos, int valueAxisPos, Double valueSegment, int descSegment, List<String> colors) {
int barLength;
int valueRowAmount = values.length;
for (int vIndex = 0; vIndex < valueRowAmount; vIndex++) {
int cIndex = 0;
int subBarIterator = valueAxisPos;
for (Double v : values[vIndex]) {
if (cIndex >= colors.size()) {
cIndex = 0; // Restart with first color if all colors in the array has been used
}
base.setForegroundColor(ColorOwn.TRANSPARENT);
base.setBackgroundColorAndKeepTransparency(colors.get(cIndex));
barLength = (int) calculateValuePos(v, valueSegment);
int barWidth = 0;
int ownvar = vIndex * (int) Math.round((double) descSegment / valueRowAmount);
// calculate last bar width, fixing rounding errors
if (vIndex == valueRowAmount - 1) {
barWidth = subBarIterator + descSegment - (subBarIterator + ownvar);
}
else {
barWidth = (int) Math.round((double) descSegment / valueRowAmount);
}
if (xIsDescription) {
if (barLength > 0) {
base.drawRectangle(subBarIterator + ownvar, sourceAxisPos - barLength, barWidth, barLength);
}
else {
base.drawRectangle(subBarIterator + ownvar, sourceAxisPos, barWidth, -barLength);
}
}
else {
if (barLength > 0) {
base.drawRectangle(sourceAxisPos, subBarIterator + ownvar, barLength, barWidth);
}
else {
base.drawRectangle(sourceAxisPos + barLength, subBarIterator + ownvar, -barLength, barWidth);
}
}
subBarIterator += descSegment;
cIndex++;
}
}
base.resetColorSettings();
}
public final void drawPiePlot() {
Double valueSum = 0.0;
for (Double v : values[0]) {
valueSum += Math.abs(v);
}
final Point ulCorner;
final int diameter;
int height = canvas.getInnerVerticalDrawspace();
int width = canvas.getInnerHorizontalDrawspace();
diameter = height > width ? width : height;
ulCorner = new Point(canvas.getInnerLeftPos(), canvas.getInnerUpPos());
drawPieArcs(values[0], desc, ulCorner, diameter, valueSum, colors);
}
private final void drawPieArcs(Double[] values, String[] desc, Point ulCorner, int diameter, Double valueSum, List<String> colors) {
int cIndex = 0;
Double arcAngle = 0D;
Double startAngle = 0D;
for (int i = 0; i < values.length; i++) {
if (cIndex >= colors.size()) {
cIndex = 0; // Restart with first color if all colors in the array has been used
}
ColorOwn currentFg = base.getForegroundColor();
base.setForegroundColor(ColorOwn.TRANSPARENT);
base.setBackgroundColorAndKeepTransparency(colors.get(cIndex));
arcAngle = i < values.length - 1 ? Math.round(360.0 / valueSum * Math.abs(values[i])) : 360 - startAngle;
// System.out.println("val: "+values[i]+" winkel: "+arcAngle);
int height = canvas.getInnerVerticalDrawspace();
int width = canvas.getInnerHorizontalDrawspace();
base.drawArc(ulCorner.x + width / 2.0 - diameter / 2.0, ulCorner.y + height / 2.0 - diameter / 2.0, diameter, diameter, startAngle.floatValue(), arcAngle.floatValue(), false);
base.setForegroundColor(currentFg);
double radians = (360 - startAngle + (360 - arcAngle / 2)) * Math.PI / 180.0;
int value_x = (int) (diameter / 2.0 * Math.cos(radians) + ulCorner.x + diameter / 2.0 + width / 2.0 - diameter / 2.0);
int value_y = (int) (diameter / 2.0 * Math.sin(radians) + ulCorner.y + diameter / 2.0 + height / 2.0 - diameter / 2.0);
base.setForegroundColor(ColorOwn.forStringOrNull(colors.get(cIndex), Transparency.FOREGROUND).darken(75));
base.print(desc[i], value_x, value_y, AlignHorizontal.CENTER);
// System.out.println("value_x: "+value_x+" / value_y:"+value_y);
startAngle += arcAngle;
cIndex++;
}
base.resetColorSettings();
}
private void calculateAdditionalSpaceForYAxisTextWidth() {
double maxWidth = 0;
double valueWidth;
if (axisConfig.isxDescription()) { // y-axis contains values
if (axisConfig.drawValueAxisMarkerText()) {
for (Double v : valuesShownOnAxisSorted) {
valueWidth = base.textWidth(String.valueOf(v));
if (valueWidth > maxWidth) {
maxWidth = valueWidth;
}
}
}
}
else { // y-axis contains description
if (axisConfig.drawDescriptionAxisMarkerText()) {
for (String d : desc) {
valueWidth = base.textWidth(d);
if (valueWidth > maxWidth) {
maxWidth = valueWidth;
}
}
}
}
double adjustValue = maxWidth + canvas.getOuterLeftPos() - (axisConfig.getyAxisPos() - canvas.getInnerLeftPos()) - 5;
if (adjustValue > canvas.getOuterLeftPos()) {
canvas.setBorderX((int) adjustValue);
setupAxis();
// If the y-axis is not exactly over the innerleft-border, it will be displaced by the last setupAxis() call and therefore the additional space for it must be recalculated again
if (axisConfig.getyAxisPos() - canvas.getInnerLeftPos() != 0) {
adjustValue = maxWidth + canvas.getOuterLeftPos() - (axisConfig.getyAxisPos() - canvas.getInnerLeftPos()) - 5;
if (adjustValue > canvas.getOuterLeftPos()) {
canvas.setBorderX((int) adjustValue);
setupAxis();
}
}
}
}
/**
* Calculated value * valueSegment but account for displacements of values if all values are positive or negativ (= positive minVal or negative maxVal)
*/
public double calculateValuePos(double value, double valueSegment) {
if (value > 0 && minVal > 0) {
value -= minVal;
}
else if (value < 0 && maxVal < 0) {
value -= maxVal;
}
return value * valueSegment;
}
public void setValues(String[] desc, String[] title, Double[][] values, List<String> colors) {
this.desc = SharedUtils.cloneArray(desc);
this.title = SharedUtils.cloneArray(title);
this.colors = new ArrayList<String>(colors);
this.values = SharedUtils.cloneArray(values);
valuesSorted = new TreeSet<Double>();
for (Double[] vArray : values) {
for (Double v : vArray) {
valuesSorted.add(v);
}
}
valuesShownOnAxisSorted = axisConfig.setValueAxisList(valuesSorted);
minVal = minRealOrShownValue();
maxVal = maxRealOrShownValue();
}
public void setMinValue(Double minVal) throws IOException {
Double limit = Math.min(minRealOrShownValue(), maxVal);
if (minVal > limit) {
throw new IOException("minValue must be <= " + limit);
}
else {
this.minVal = minVal;
}
}
public void setMaxValue(Double maxVal) throws IOException {
Double limit = Math.max(maxRealOrShownValue(), minVal);
if (maxVal < limit) {
throw new IOException("maxValue must be >= " + limit);
}
else {
this.maxVal = maxVal;
}
}
private double minRealOrShownValue() {
if (valuesShownOnAxisSorted.isEmpty()) {
return valuesSorted.first();
}
else {
return Math.min(valuesSorted.first(), valuesShownOnAxisSorted.first());
}
}
private double maxRealOrShownValue() {
if (valuesShownOnAxisSorted.isEmpty()) {
return valuesSorted.last();
}
else {
return Math.max(valuesSorted.last(), valuesShownOnAxisSorted.last());
}
}
public Canvas getCanvas() {
return canvas;
}
public AxisConfig getAxisConfig() {
return axisConfig;
}
}