package kg.apc.charting.plotters; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.Stroke; import java.io.Serializable; import java.util.Iterator; import java.util.Map.Entry; import kg.apc.charting.AbstractGraphPanelChartElement; import kg.apc.charting.AbstractGraphRow; import kg.apc.charting.ChartSettings; import org.apache.jorphan.gui.NumberRenderer; public abstract class AbstractRowPlotter implements Serializable { private final static int labelSpacing = 5; private Stroke lineStroke = null; protected ChartSettings chartSettings = null; protected NumberRenderer labelRenderer = null; protected Rectangle chartRect = null; protected long minXVal; protected long maxXVal; protected double minYVal; protected double maxYVal; //live values to paint rows protected double dxForDVal; protected double dyForDVal; protected double calcPointX = 0; protected double calcPointY = 0; protected int x, y; protected int prevX; protected int prevY; protected boolean allowMarkers = false; protected boolean mustDrawFirstZeroingLine; public AbstractRowPlotter(ChartSettings chartSettings, NumberRenderer labelRenderer) { this.chartSettings = chartSettings; this.labelRenderer = labelRenderer; } public void setBoundsValues(Rectangle chartRect, long minXVal, long maxXVal, double minYVal, double maxYVal) { this.chartRect = chartRect; this.minXVal = minXVal; this.maxXVal = maxXVal; this.minYVal = minYVal; this.maxYVal = maxYVal; dxForDVal = (maxXVal <= minXVal) ? 0 : (double) chartRect.width / (maxXVal - minXVal); dyForDVal = (maxYVal <= minYVal) ? 0 : (double) chartRect.height / (maxYVal - minYVal); } /* * Check if the point (x,y) is contained in the chart area * We check only minX, maxX, and maxY to avoid flickering. * We take max(chartRect.y, y) as redering value * This is done to prevent line out of range if new point is added * during chart paint. */ protected boolean isChartPointValid(int xx, int yy) { boolean ret = true; //check x if (xx < chartRect.x || xx > chartRect.x + chartRect.width) { ret = false; } else //check y bellow x axis if (yy > chartRect.y + chartRect.height) { ret = false; } return ret; } //this method is responsible to maintain x, y, prevX, prevY public synchronized void paintRow(Graphics2D g2d, AbstractGraphRow row, Color color, double zoomFactor, int limitPointFactor) { Iterator<Entry<Long, AbstractGraphPanelChartElement>> it = row.iterator(); Entry<Long, AbstractGraphPanelChartElement> element; Stroke olStroke; prevX = -1; prevY = chartRect.y + chartRect.height; mustDrawFirstZeroingLine = chartSettings.isDrawFinalZeroingLines(); olStroke = g2d.getStroke(); if (row.isDrawThickLines()) { g2d.setStroke(chartSettings.getThickStroke()); } else { g2d.setStroke(getLineStroke()); } g2d.setColor(color); while (it.hasNext()) { if (!row.isDrawOnChart()) { continue; } if (limitPointFactor == 1) { element = it.next(); AbstractGraphPanelChartElement elt = (AbstractGraphPanelChartElement) element.getValue(); //not compatible with factor != 1, ie cannot be used if limit nb of point is selected. if (chartSettings.getHideNonRepValLimit() > 0) { while (!elt.isPointRepresentative(chartSettings.getHideNonRepValLimit()) && it.hasNext()) { element = it.next(); elt = (AbstractGraphPanelChartElement) element.getValue(); } if (!elt.isPointRepresentative(chartSettings.getHideNonRepValLimit())) { break; } } calcPointX = element.getKey().doubleValue(); calcPointY = elt.getValue(); } else { calcPointX = 0; calcPointY = 0; int nbPointProcessed = 0; for (int i = 0; i < limitPointFactor; i++) { if (it.hasNext()) { element = it.next(); calcPointX = calcPointX + element.getKey().doubleValue(); calcPointY = calcPointY + ((AbstractGraphPanelChartElement) element.getValue()).getValue(); nbPointProcessed++; } } calcPointX = calcPointX / (double) nbPointProcessed; calcPointY = calcPointY / (double) nbPointProcessed; } calcPointY = calcPointY * zoomFactor; x = chartRect.x + (int) ((calcPointX - minXVal) * dxForDVal); int yHeight = (int) ((calcPointY - minYVal) * dyForDVal); y = chartRect.y + chartRect.height - yHeight; //now x and y are set, we can call plotter processPoint(g2d, row.getGranulationValue()); //set prevX, prevY if (isChartPointValid(x, y)) { if (allowMarkers) { processMarker(g2d, row); } prevX = x; prevY = y; } if (row.isDrawValueLabel() && isChartPointValid(x, y) && y >= chartRect.y) { drawLabels(g2d, row, calcPointY); } } processFinalLines(row, g2d); x = 0; y = 0; prevX = -1; prevY = chartRect.y + chartRect.height; postPaintRow(row, g2d); g2d.setStroke(olStroke); } protected abstract void processPoint(Graphics2D g2d, int granulation); protected void postPaintRow(AbstractGraphRow row, Graphics2D g2d) { } private void processMarker(Graphics2D g2d, AbstractGraphRow row) { int radius = row.getMarkerSize(); //check if forced via settings if (chartSettings.getChartMarkers() == ChartSettings.CHART_MARKERS_YES) { radius = Math.max(AbstractGraphRow.MARKER_SIZE_SMALL, row.getMarkerSize()); } if (chartSettings.getChartMarkers() == ChartSettings.CHART_MARKERS_NO) { radius = AbstractGraphRow.MARKER_SIZE_NONE; } // draw markers if (radius != AbstractGraphRow.MARKER_SIZE_NONE && (y >= chartRect.y)) { if (isChartPointValid(x, y)) { g2d.fillOval(x - radius, y - radius, (radius) * 2, (radius) * 2); } } } private void drawLabels(Graphics2D g2d, AbstractGraphRow row, double yValue) { Color oldColor = g2d.getColor(); g2d.setColor(Color.DARK_GRAY); Font oldFont = g2d.getFont(); g2d.setFont(g2d.getFont().deriveFont(Font.BOLD)); FontMetrics fm = g2d.getFontMetrics(g2d.getFont()); labelRenderer.setValue(yValue); int labelSize = g2d.getFontMetrics(g2d.getFont()).stringWidth(labelRenderer.getText()); //if close to end if (x + row.getMarkerSize() + labelSpacing + labelSize > chartRect.x + chartRect.width) { g2d.drawString(labelRenderer.getText(), x - row.getMarkerSize() - labelSpacing - labelSize, y + fm.getAscent() / 2); } else { g2d.drawString(labelRenderer.getText(), x + row.getMarkerSize() + labelSpacing, y + fm.getAscent() / 2); } g2d.setFont(oldFont); g2d.setColor(oldColor); } private void processFinalLines(AbstractGraphRow row, Graphics2D g2d) { if (chartSettings.getLineWidth() == 0) { return; } Stroke oldStroke = null; if (row.isDrawLine() && chartSettings.isDrawFinalZeroingLines()) { if (row.isDrawThickLines()) { oldStroke = g2d.getStroke(); g2d.setStroke(chartSettings.getThickStroke()); } g2d.drawLine(prevX, Math.max(prevY, chartRect.y), (int) (prevX + dxForDVal), chartRect.y + chartRect.height); if (row.isDrawThickLines()) { g2d.setStroke(oldStroke); } } } private Stroke getLineStroke() { if (chartSettings.getLineWidth() > 1) { lineStroke = new BasicStroke(chartSettings.getLineWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); } else { lineStroke = new BasicStroke(chartSettings.getLineWidth()); } return lineStroke; } }