/** * Copyright (C) 2010-14 diirt developers. See COPYRIGHT.TXT * All rights reserved. Use is subject to license terms. See LICENSE.TXT */ package org.diirt.graphene; import org.diirt.util.stats.Range; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import static org.diirt.graphene.InterpolationScheme.CUBIC; import static org.diirt.graphene.InterpolationScheme.LINEAR; import static org.diirt.graphene.InterpolationScheme.NEAREST_NEIGHBOR; import static org.diirt.graphene.ReductionScheme.FIRST_MAX_MIN_LAST; import static org.diirt.graphene.ReductionScheme.NONE; import org.diirt.util.array.ArrayDouble; import org.diirt.util.array.ListDouble; import org.diirt.util.array.ListMath; import org.diirt.util.array.ListNumber; import org.diirt.util.array.SortedListView; import org.diirt.util.stats.Ranges; /** * Renderer for a line graph with multiple y axes. * * @author carcassi, sjdallst */ public class MultiAxisLineGraph2DRenderer extends Graph2DRenderer<MultiAxisLineGraph2DRendererUpdate> { /** * List of supported interpolation schemes for this renderer. */ public static java.util.List<InterpolationScheme> supportedInterpolationScheme = Arrays.asList(InterpolationScheme.NEAREST_NEIGHBOR, InterpolationScheme.LINEAR, InterpolationScheme.CUBIC); /** * List of supported data reduction schemes for this renderer. */ public static java.util.List<ReductionScheme> supportedReductionScheme = Arrays.asList(ReductionScheme.FIRST_MAX_MIN_LAST, ReductionScheme.NONE); /** * Default interpolation scheme: nearest neighbor. */ public static final InterpolationScheme DEFAULT_INTERPOLATION_SCHEME = InterpolationScheme.NEAREST_NEIGHBOR; /** * Default interpolation scheme: nearest neighbor. */ public static final ReductionScheme DEFAULT_REDUCTION_SCHEME = ReductionScheme.FIRST_MAX_MIN_LAST; /** * Default separate area flag: false (all lines overlap in one area). */ public static final boolean DEFAULT_SEPARATE_AREAS = false; @Override public MultiAxisLineGraph2DRendererUpdate newUpdate() { return new MultiAxisLineGraph2DRendererUpdate(); } private InterpolationScheme interpolation = DEFAULT_INTERPOLATION_SCHEME; private ReductionScheme reduction = DEFAULT_REDUCTION_SCHEME; private List<ListDouble> yReferenceCoords; private List<ListDouble> yReferenceValues; private List<List<String>> yReferenceLabels; private Range emptyRange; private AxisRangeInstance xAxisRange = AxisRanges.auto().createInstance(); private AxisRangeInstance yAxisRange = AxisRanges.auto().createInstance(); private List<AxisRangeInstance> yAxisRanges; private ValueScale xValueScale = ValueScales.linearScale(); private ValueScale yValueScale = ValueScales.linearScale(); private Range xAggregatedRange; private List<Range> yAggregatedRange; private Range xPlotRange; private List<Range> yPlotRange; private HashMap<Integer, Range> indexToRangeMap = new HashMap<Integer,Range>(); private int numGraphs = 0; private int spaceForYAxes; private int minimumGraphWidth = 200; private int yLabelMaxWidth = 0; private int xLabelMaxHeight; private NumberColorMap valueColorScheme = NumberColorMaps.JET; private NumberColorMapInstance valueColorSchemeInstance; private double xPlotValueStart; private List<Double> yPlotValueStart; private double xPlotValueEnd; private List<Double> yPlotValueEnd; private ArrayList<Double> graphBoundaries; private ArrayList<Double> graphBoundaryRatios; private int marginBetweenGraphs = 0; private int totalYMargins = 0; private int minimumGraphHeight = 100; protected List<String> xReferenceLabels; // The pixel coordinates for the area private int xAreaCoordStart; private List<Integer> yAreaCoordStart; private List<Integer> yAreaCoordEnd; private int xAreaCoordEnd; // The pixel coordinates for the ranges // These match the xPlotValueXxx private double xPlotCoordStart; private List<Double> yPlotCoordStart; private List<Double> yPlotCoordEnd; private double xPlotCoordEnd; // The pixel size of the range (not of the plot area) private List<Double> yPlotCoordHeight; private double xPlotCoordWidth; private boolean stretch = false; private boolean separateAreas = DEFAULT_SEPARATE_AREAS; /** * Creates a new line graph renderer. * * @param imageWidth the graph width * @param imageHeight the graph height */ public MultiAxisLineGraph2DRenderer(int imageWidth, int imageHeight) { super(imageWidth, imageHeight); } /** * The current interpolation used for the line. * * @return the current interpolation */ public InterpolationScheme getInterpolation() { return interpolation; } @Override public void update(MultiAxisLineGraph2DRendererUpdate update) { super.update(update); if (update.getInterpolation() != null) { interpolation = update.getInterpolation(); } if (update.getDataReduction() != null) { reduction = update.getDataReduction(); } if (update.getMinimumGraphWidth() != null){ minimumGraphWidth = update.getMinimumGraphWidth(); } if(update.getImageHeight() != null){ if(stretch){ for(int i = 0; i < graphBoundaries.size(); i++){ graphBoundaries.set(i, getImageHeight() * graphBoundaryRatios.get(i)); } } else if((double)getImageHeight()/numGraphs - totalYMargins >= (minimumGraphHeight*2)){ numGraphs+=1; } if((double)getImageHeight()/numGraphs - totalYMargins <= minimumGraphHeight){ numGraphs = 0; } } if(update.getIndexToRange() != null){ indexToRangeMap = update.getIndexToRange(); } if(update.getMarginBetweenGraphs() != null){ marginBetweenGraphs = update.getMarginBetweenGraphs(); } if(update.getMinimumGraphHeight() != null){ minimumGraphHeight = update.getMinimumGraphHeight(); } if(update.isSeparateAreas() != null){ separateAreas = update.isSeparateAreas(); } } /** * Draws the graph on the given graphics context. * * @param g the graphics on which to display the data * @param data the data to display */ public void draw(Graphics2D g, List<Point2DDataset> data) { this.g = g; //Make a list containing the x range of each data set (each one should be the same). List<Range> dataRangesX = new ArrayList<Range>(); for(int i = 0; i < data.size(); i++){ dataRangesX.add(data.get(i).getXStatistics().getRange()); } //Make a list containing the y range of each data set (each one should be different). List<Range> dataRangesY = new ArrayList<Range>(); for(int i = 0; i < data.size(); i++){ dataRangesY.add(data.get(i).getYStatistics().getRange()); } labelFontMetrics = g.getFontMetrics(labelFont); if(separateAreas){ xLabelMaxHeight = labelFontMetrics.getHeight() - labelFontMetrics.getLeading(); totalYMargins = xLabelMaxHeight + marginBetweenGraphs + topMargin + bottomMargin + topAreaMargin + bottomAreaMargin + xLabelMargin + 1; getNumGraphsSplit(data); Range datasetRange = Ranges.range(0,numGraphs-1); valueColorSchemeInstance = valueColorScheme.createInstance(datasetRange); calculateRanges(dataRangesX, dataRangesY, numGraphs); setGraphBoundaries(data); calculateLabels(); calculateGraphAreaSplit(); drawBackground(); drawGraphArea(); }else{ getNumGraphs(data); Range datasetRange = Ranges.range(0,numGraphs-1); valueColorSchemeInstance = valueColorScheme.createInstance(datasetRange); calculateRanges(dataRangesX, dataRangesY, numGraphs); calculateLabels(); calculateGraphArea(); drawBackground(); drawGraphArea(); } List<SortedListView> xValues = new ArrayList<SortedListView>(); for(int i = 0; i < numGraphs; i++){ xValues.add(org.diirt.util.array.ListNumbers.sortedView(data.get(i).getXValues())); } List<ListNumber> yValues = new ArrayList<ListNumber>(); for(int i = 0; i < numGraphs; i++){ yValues.add(org.diirt.util.array.ListNumbers.sortedView(data.get(i).getYValues(), xValues.get(i).getIndexes())); } if(separateAreas){ g.setColor(Color.BLACK); for(int i = 0; i < numGraphs; i++){ drawValueExplicitLine(xValues.get(i), yValues.get(i), interpolation, reduction,i); } } else{ for(int i = 0; i < numGraphs; i++){ g.setColor(new Color(valueColorSchemeInstance.colorFor(i))); drawValueExplicitLine(xValues.get(i), yValues.get(i), interpolation, reduction,i); } } } //method to get the number of graphs to draw when simply drawing mutiple y axes. private void getNumGraphs(List<Point2DDataset> data){ numGraphs = data.size(); //if yLabelMaxWidth has not been calculated, guess that it will be 15 if(yLabelMaxWidth == 0){ yLabelMaxWidth = 15; } spaceForYAxes = leftMargin + (yLabelMaxWidth + yLabelMargin*2 + 1)*(numGraphs-(numGraphs/2)) - 1; if(numGraphs > 1){ spaceForYAxes += rightMargin + (yLabelMaxWidth + yLabelMargin*2 + 1)*(numGraphs/2) + 1; } else{ spaceForYAxes += rightMargin; } while((double)getImageWidth() - spaceForYAxes < minimumGraphWidth){ numGraphs-=1; spaceForYAxes = leftMargin + (yLabelMaxWidth + yLabelMargin*2 + 1)*(numGraphs-(numGraphs/2)) - 1; if(numGraphs > 1){ spaceForYAxes += rightMargin + (yLabelMaxWidth + yLabelMargin*2 + 1)*(numGraphs/2) + 1; } else{ spaceForYAxes += rightMargin; } } } //method to get number of graphs when the graphs are separateAreas private void getNumGraphsSplit(List<Point2DDataset> data){ if(this.graphBoundaries == null || this.graphBoundaries.size() != numGraphs+1){ numGraphs = data.size(); while((double)getImageHeight()/numGraphs - totalYMargins < minimumGraphHeight){ numGraphs-=1; } } //if all the graphs are being drawn, they can be stretched along with the image. stretch = numGraphs == data.size(); } private void setGraphBoundaries(List<Point2DDataset> data){ if(this.graphBoundaries == null || this.graphBoundaries.size() != numGraphs+1){ graphBoundaries = new ArrayList<Double>(); for(double i = 0; i <= numGraphs; i++){ if(stretch){ if(i > 0){ graphBoundaries.add(i/numGraphs*(getImageHeight()) + marginBetweenGraphs); } else{ graphBoundaries.add(i/numGraphs*(getImageHeight())); } } else{ if(i > 0){ graphBoundaries.add(i* (minimumGraphHeight+totalYMargins)); } else{ graphBoundaries.add(i*minimumGraphHeight); } } } graphBoundaryRatios = new ArrayList<Double>(); for(double i = 0; i <= numGraphs; i++){ if(stretch){ graphBoundaryRatios.add(i/numGraphs); } else{ graphBoundaryRatios.add((i*minimumGraphHeight)/getImageHeight()); } } } } protected void calculateRanges(List<Range> xDataRange, List<Range> yDataRange, int length) { for(int i = 0; i < length; i++){ xAggregatedRange = aggregateRange(xDataRange.get(i), xAggregatedRange); // TODO: should be update to use display range xPlotRange = xAxisRange.axisRange(xDataRange.get(i), xDataRange.get(i)); } if(yAggregatedRange == null || yDataRange.size() != yAggregatedRange.size() || yDataRange.size() != length){ yAggregatedRange = new ArrayList<Range>(); yPlotRange = new ArrayList<Range>(); yAxisRanges = new ArrayList<>(); for(int i = 0; i < length; i++){ if(indexToRangeMap.isEmpty() || !indexToRangeMap.containsKey(i)){ yAggregatedRange.add(aggregateRange(yDataRange.get(i), emptyRange)); AxisRangeInstance instance = yAxisRange.getAxisRange().createInstance(); yAxisRanges.add(instance); // TODO: should be update to use display range yPlotRange.add(instance.axisRange(yDataRange.get(i), yDataRange.get(i))); } else{ if(indexToRangeMap.containsKey(i)){ yAggregatedRange.add(aggregateRange(yDataRange.get(i), emptyRange)); yPlotRange.add(indexToRangeMap.get(i)); } } } } else{ for(int i = 0; i < length; i++){ if(indexToRangeMap.isEmpty() || !indexToRangeMap.containsKey(i)){ yAggregatedRange.set(i,aggregateRange(yDataRange.get(i), yAggregatedRange.get(i))); // TODO: should be update to use display range yPlotRange.set(i,yAxisRanges.get(i).axisRange(yDataRange.get(i), yDataRange.get(i))); } else{ if(indexToRangeMap.containsKey(i)){ yPlotRange.set(i,indexToRangeMap.get(i)); } } } } } @Override protected void calculateLabels() { // Calculate horizontal axis references. If range is zero, use special logic if (!(xPlotRange.getMinimum() == xPlotRange.getMaximum())) { ValueAxis xAxis = xValueScale.references(xPlotRange, 2, Math.max(2, getImageWidth() / 60)); xReferenceLabels = Arrays.asList(xAxis.getTickLabels()); xReferenceValues = new ArrayDouble(xAxis.getTickValues()); } else { // TODO: use something better to format the number xReferenceLabels = Collections.singletonList(Double.toString(xPlotRange.getMinimum())); xReferenceValues = new ArrayDouble(xPlotRange.getMinimum()); } // Calculate vertical axis references. If range is zero, use special logic if(yReferenceLabels == null || yReferenceLabels.size() != numGraphs){ yReferenceLabels = new ArrayList<List<String>>(); yReferenceValues = new ArrayList<ListDouble>(); for(int i = 0; i < yPlotRange.size(); i++){ if (!(yPlotRange.get(i).getMinimum() == yPlotRange.get(i).getMaximum())) { ValueAxis yAxis; if(separateAreas){ yAxis = yValueScale.references(yPlotRange.get(i), 2, Math.max(2, (graphBoundaries.get(i+1).intValue() - graphBoundaries.get(i).intValue()) / 60)); }else{ yAxis = yValueScale.references(yPlotRange.get(i), 2, Math.max(2, getImageHeight() / 60)); } yReferenceLabels.add(Arrays.asList(yAxis.getTickLabels())); yReferenceValues.add(new ArrayDouble(yAxis.getTickValues())); } else { // TODO: use something better to format the number yReferenceLabels.add(Collections.singletonList(Double.toString(yPlotRange.get(i).getMinimum()))); yReferenceValues.add(new ArrayDouble(yPlotRange.get(i).getMinimum())); } } } else{ for(int i = 0; i < yPlotRange.size(); i++){ if (!(yPlotRange.get(i).getMinimum() == yPlotRange.get(i).getMaximum())) { ValueAxis yAxis; if(separateAreas){ yAxis = yValueScale.references(yPlotRange.get(i), 2, Math.max(2, (graphBoundaries.get(i+1).intValue() - graphBoundaries.get(i).intValue()) / 60)); }else{ yAxis = yValueScale.references(yPlotRange.get(i), 2, Math.max(2, getImageHeight() / 60)); } yReferenceLabels.set(i,Arrays.asList(yAxis.getTickLabels())); yReferenceValues.set(i,new ArrayDouble(yAxis.getTickValues())); } else { // TODO: use something better to format the number yReferenceLabels.set(i,Collections.singletonList(Double.toString(yPlotRange.get(i).getMinimum()))); yReferenceValues.set(i,new ArrayDouble(yPlotRange.get(i).getMinimum())); } } } labelFontMetrics = g.getFontMetrics(labelFont); xLabelMaxHeight = labelFontMetrics.getHeight() - labelFontMetrics.getLeading(); // Compute y axis spacing int yLabelWidth = 0; yLabelMaxWidth = 0; for (int a = 0; a < yReferenceLabels.size(); a++) { for(int b = 0; b < yReferenceLabels.get(a).size(); b++){ yLabelWidth = labelFontMetrics.stringWidth(yReferenceLabels.get(a).get(b)); yLabelMaxWidth = Math.max(yLabelMaxWidth, yLabelWidth); } } } @Override protected void calculateGraphArea() { int areaFromBottom = bottomMargin + xLabelMaxHeight + xLabelMargin; int areaFromLeft = leftMargin + (yLabelMaxWidth + yLabelMargin*2 + 1)*(numGraphs-(numGraphs/2)) - 1; int areaFromRight; if(numGraphs > 1){ areaFromRight = rightMargin + (yLabelMaxWidth + yLabelMargin*2 + 1)*(numGraphs/2) + 1; } else{ areaFromRight = rightMargin; } xPlotValueStart = xPlotRange.getMinimum(); xPlotValueEnd = xPlotRange.getMaximum(); if (xPlotValueStart == xPlotValueEnd) { // If range is zero, fake a range xPlotValueStart -= 1.0; xPlotValueEnd += 1.0; } xAreaCoordStart = areaFromLeft; xAreaCoordEnd = getImageWidth() - areaFromRight; xPlotCoordStart = xAreaCoordStart + leftAreaMargin + xPointMargin; xPlotCoordEnd = xAreaCoordEnd - rightAreaMargin - xPointMargin; xPlotCoordWidth = xPlotCoordEnd - xPlotCoordStart; //set the start and end of each plot in terms of values. if(yPlotValueStart == null || yPlotValueStart.size() != yPlotRange.size()){ yPlotValueStart = new ArrayList<Double>(); yPlotValueEnd = new ArrayList<Double>(); for(int i = 0; i < yPlotRange.size(); i++){ yPlotValueStart.add(yPlotRange.get(i).getMinimum()); yPlotValueEnd.add(yPlotRange.get(i).getMaximum()); } } else{ for(int i = 0; i < yPlotRange.size(); i++){ yPlotValueStart.set(i, yPlotRange.get(i).getMinimum()); yPlotValueEnd.set(i, yPlotRange.get(i).getMaximum()); } } for(int i = 0; i < yPlotRange.size(); i++){ if (yPlotValueStart.get(i).doubleValue() == yPlotValueEnd.get(i).doubleValue()) { // If range is zero, fake a range yPlotValueStart.set(i, yPlotValueStart.get(i)-1.0); yPlotValueEnd.set(i, yPlotValueEnd.get(i)+1.0); } } super.yAreaCoordStart = topMargin; super.yAreaCoordEnd = getImageHeight() - areaFromBottom; super.yPlotCoordStart = super.yAreaCoordStart + topAreaMargin + yPointMargin; super.yPlotCoordEnd = super.yAreaCoordEnd - bottomAreaMargin - yPointMargin; super.yPlotCoordHeight = super.yPlotCoordEnd - super.yPlotCoordStart; //Only calculates reference coordinates if calculateLabels() was called if (xReferenceValues != null) { double[] xRefCoords = new double[xReferenceValues.size()]; for (int i = 0; i < xRefCoords.length; i++) { xRefCoords[i] = scaledX1(xReferenceValues.getDouble(i)); } xReferenceCoords = new ArrayDouble(xRefCoords); } yReferenceCoords = new ArrayList<ListDouble>(); if (yReferenceValues != null) { for(int a = 0; a < yReferenceValues.size(); a++){ double[] yRefCoords = new double[yReferenceValues.get(a).size()]; for (int b = 0; b < yRefCoords.length; b++) { if(separateAreas){ yRefCoords[b] = scaledYSplit(yReferenceValues.get(a).getDouble(b),a); }else{ yRefCoords[b] = scaledY(yReferenceValues.get(a).getDouble(b),a); } } yReferenceCoords.add(new ArrayDouble(yRefCoords)); } } } protected void calculateGraphAreaSplit() { int areaFromBottom = bottomMargin + xLabelMaxHeight + xLabelMargin; int areaFromLeft = leftMargin + yLabelMaxWidth + yLabelMargin; xPlotValueStart = xPlotRange.getMinimum(); xPlotValueEnd = xPlotRange.getMaximum(); if (xPlotValueStart == xPlotValueEnd) { // If range is zero, fake a range xPlotValueStart -= 1.0; xPlotValueEnd += 1.0; } xAreaCoordStart = areaFromLeft; xAreaCoordEnd = getImageWidth() - rightMargin; xPlotCoordStart = xAreaCoordStart + leftAreaMargin + xPointMargin; xPlotCoordEnd = xAreaCoordEnd - rightAreaMargin - xPointMargin; xPlotCoordWidth = xPlotCoordEnd - xPlotCoordStart; //set the start and end of each plot in terms of values. if(yPlotValueStart == null || yPlotValueStart.size() != yPlotRange.size()){ yPlotValueStart = new ArrayList<Double>(); yPlotValueEnd = new ArrayList<Double>(); for(int i = 0; i < yPlotRange.size(); i++){ yPlotValueStart.add(yPlotRange.get(i).getMinimum()); yPlotValueEnd.add(yPlotRange.get(i).getMaximum()); } } else{ for(int i = 0; i < yPlotRange.size(); i++){ yPlotValueStart.set(i, yPlotRange.get(i).getMinimum()); yPlotValueEnd.set(i, yPlotRange.get(i).getMaximum()); } } //range faking for(int i = 0; i < yPlotRange.size(); i++){ if (yPlotValueStart.get(i).doubleValue() == yPlotValueEnd.get(i).doubleValue()) { // If range is zero, fake a range yPlotValueStart.set(i, yPlotValueStart.get(i)-1.0); yPlotValueEnd.set(i, yPlotValueEnd.get(i)+1.0); } } if(yAreaCoordStart == null || yAreaCoordStart.size() != numGraphs){ yAreaCoordStart = new ArrayList<Integer>(); yAreaCoordEnd = new ArrayList<Integer>(); yPlotCoordStart = new ArrayList<Double>(); yPlotCoordEnd = new ArrayList<Double>(); yPlotCoordHeight = new ArrayList<Double>(); yAreaCoordStart.add(topMargin + graphBoundaries.get(0).intValue()); yAreaCoordEnd.add(graphBoundaries.get(1).intValue()-areaFromBottom - marginBetweenGraphs); yPlotCoordStart.add(yAreaCoordStart.get(0) + topAreaMargin + yPointMargin); yPlotCoordEnd.add(yAreaCoordEnd.get(0) - bottomAreaMargin - yPointMargin); yPlotCoordHeight.add(yPlotCoordEnd.get(0)-yPlotCoordStart.get(0)); for(int i = 1; i < numGraphs-1; i++){ yAreaCoordStart.add(topMargin + graphBoundaries.get(i).intValue()); yAreaCoordEnd.add(graphBoundaries.get(i+1).intValue()-areaFromBottom - marginBetweenGraphs); yPlotCoordStart.add(yAreaCoordStart.get(i) + topAreaMargin + yPointMargin); yPlotCoordEnd.add(yAreaCoordEnd.get(i) - bottomAreaMargin - yPointMargin); yPlotCoordHeight.add(yPlotCoordEnd.get(i)-yPlotCoordStart.get(i)); } yAreaCoordStart.add(topMargin + graphBoundaries.get(numGraphs-1).intValue()); yAreaCoordEnd.add(graphBoundaries.get(numGraphs).intValue()-areaFromBottom - marginBetweenGraphs); yPlotCoordStart.add(yAreaCoordStart.get(numGraphs-1) + topAreaMargin + yPointMargin); yPlotCoordEnd.add(yAreaCoordEnd.get(numGraphs-1) - bottomAreaMargin - yPointMargin); yPlotCoordHeight.add(yPlotCoordEnd.get(numGraphs-1)-yPlotCoordStart.get(numGraphs-1)); } else{ yAreaCoordStart.set(0,topMargin + graphBoundaries.get(0).intValue()); yAreaCoordEnd.set(0,graphBoundaries.get(1).intValue()-areaFromBottom - marginBetweenGraphs); yPlotCoordStart.set(0,yAreaCoordStart.get(0) + topAreaMargin + yPointMargin); yPlotCoordEnd.set(0,yAreaCoordEnd.get(0) - bottomAreaMargin - yPointMargin); yPlotCoordHeight.set(0,yPlotCoordEnd.get(0)-yPlotCoordStart.get(0)); for(int i = 1; i < numGraphs-1; i++){ yAreaCoordStart.set(i,topMargin + graphBoundaries.get(i).intValue() + marginBetweenGraphs); yAreaCoordEnd.set(i,graphBoundaries.get(i+1).intValue()-areaFromBottom - marginBetweenGraphs); yPlotCoordStart.set(i,yAreaCoordStart.get(i) + topAreaMargin + yPointMargin); yPlotCoordEnd.set(i,yAreaCoordEnd.get(i) - bottomAreaMargin - yPointMargin); yPlotCoordHeight.set(i,yPlotCoordEnd.get(i)-yPlotCoordStart.get(i)); } yAreaCoordStart.set(numGraphs-1,topMargin + graphBoundaries.get(numGraphs-1).intValue()); yAreaCoordEnd.set(numGraphs-1,graphBoundaries.get(numGraphs).intValue()-areaFromBottom - marginBetweenGraphs); yPlotCoordStart.set(numGraphs-1,yAreaCoordStart.get(numGraphs-1) + topAreaMargin + yPointMargin); yPlotCoordEnd.set(numGraphs-1,yAreaCoordEnd.get(numGraphs-1) - bottomAreaMargin - yPointMargin); yPlotCoordHeight.set(numGraphs-1,yPlotCoordEnd.get(numGraphs-1)-yPlotCoordStart.get(numGraphs-1)); } //Only calculates reference coordinates if calculateLabels() was called if (xReferenceValues != null) { double[] xRefCoords = new double[xReferenceValues.size()]; for (int i = 0; i < xRefCoords.length; i++) { xRefCoords[i] = scaledX1(xReferenceValues.getDouble(i)); } xReferenceCoords = new ArrayDouble(xRefCoords); } yReferenceCoords = new ArrayList<ListDouble>(); if (yReferenceValues != null) { for(int a = 0; a < yReferenceValues.size(); a++){ double[] yRefCoords = new double[yReferenceValues.get(a).size()]; for (int b = 0; b < yRefCoords.length; b++) { if(separateAreas){ yRefCoords[b] = scaledYSplit(yReferenceValues.get(a).getDouble(b),a); }else{ yRefCoords[b] = scaledY(yReferenceValues.get(a).getDouble(b),a); } } yReferenceCoords.add(new ArrayDouble(yRefCoords)); } } } private final double scaledY(double value, int index) { return yValueScale.scaleValue(value, yPlotValueStart.get(index), yPlotValueEnd.get(index), super.yPlotCoordEnd, super.yPlotCoordStart); } private final double scaledYSplit(double value, int index) { return yValueScale.scaleValue(value, yPlotValueStart.get(index), yPlotValueEnd.get(index), yPlotCoordEnd.get(index), yPlotCoordStart.get(index)); } @Override protected void drawGraphArea() { g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // When drawing the reference line, align them to the pixel if(separateAreas){ drawVerticalReferenceLinesSplit(); drawHorizontalReferenceLinesSplit(); drawYLabelsSplit(); drawXLabelsSplit(); }else{ drawVerticalReferenceLines(); drawHorizontalReferenceLines(); drawYLabels(); drawXLabels(); } } @Override protected void drawVerticalReferenceLines() { g.setColor(referenceLineColor); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); ListNumber xTicks = xReferenceCoords; for (int i = 0; i < xTicks.size(); i++) { Shape line = new Line2D.Double(xTicks.getDouble(i), super.yAreaCoordStart, xTicks.getDouble(i), super.yAreaCoordEnd - 1); g.draw(line); } int count = 0; for(int i = 0; i < numGraphs; i+=2){ g.setColor(new Color(valueColorSchemeInstance.colorFor(i))); Shape line = new Line2D.Double((xAreaCoordStart - (count+1)*(yLabelMargin + 1) - count*(yLabelMaxWidth + yLabelMargin)), super.yAreaCoordStart, (xAreaCoordStart - (count+1)*(yLabelMargin + 1) - count*(yLabelMaxWidth + yLabelMargin)), super.yAreaCoordEnd - 1); g.draw(line); count++; } count = 0; for(int i = 1; i < numGraphs; i+=2){ g.setColor(new Color(valueColorSchemeInstance.colorFor(i))); Shape line = new Line2D.Double((xAreaCoordEnd + (count+1)*(yLabelMargin + 1) + count*(yLabelMaxWidth + yLabelMargin)), super.yAreaCoordStart, (xAreaCoordEnd + (count+1)*(yLabelMargin + 1) + count*(yLabelMaxWidth + yLabelMargin)), super.yAreaCoordEnd - 1); g.draw(line); count++; } } @Override protected void drawHorizontalReferenceLines() { g.setColor(referenceLineColor); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); ListNumber yTicks = yReferenceCoords.get(0); for (int b = 0; b < yTicks.size(); b++) { Shape line = new Line2D.Double(xAreaCoordStart, yTicks.getDouble(b), xAreaCoordEnd - 1, yTicks.getDouble(b)); g.draw(line); } } protected void drawVerticalReferenceLinesSplit() { g.setColor(referenceLineColor); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); ListNumber xTicks = xReferenceCoords; for(int a = 0; a < numGraphs; a++){ for (int b = 0; b < xTicks.size(); b++) { Shape line = new Line2D.Double(xTicks.getDouble(b), yAreaCoordStart.get(a), xTicks.getDouble(b), yAreaCoordEnd.get(a) - 1); g.draw(line); } } } protected void drawHorizontalReferenceLinesSplit() { g.setColor(referenceLineColor); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE); for(int a = 0; a < numGraphs; a++){ ListNumber yTicks = yReferenceCoords.get(a); for (int b = 0; b < yTicks.size(); b++) { Shape line = new Line2D.Double(xAreaCoordStart, yTicks.getDouble(b), xAreaCoordEnd - 1, yTicks.getDouble(b)); g.draw(line); } } } @Override protected void drawYLabels() { // Draw Y labels int evenCount = 0; int oddCount = 0; for(int a = 0; a < numGraphs; a++){ ListNumber yTicks = yReferenceCoords.get(a); if (yReferenceLabels.get(a) != null && !yReferenceLabels.get(a).isEmpty()) { //g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g.setColor(labelColor); g.setFont(labelFont); FontMetrics metrics = g.getFontMetrics(); // Draw first and last label int verticalLinePos; int[] drawRange = new int[] {super.yAreaCoordStart, super.yAreaCoordEnd - 1}; int xRightLabel; if(a % 2 == 0){ xRightLabel = (int) (xAreaCoordStart - (evenCount+1)*(yLabelMargin + 1)*2 - evenCount*(yLabelMaxWidth - 1)); verticalLinePos = (xAreaCoordStart - (evenCount+1)*(yLabelMargin + 1) - evenCount*(yLabelMaxWidth + yLabelMargin)); evenCount++; } else{ xRightLabel = (int) (xAreaCoordEnd + (oddCount+1)*(yLabelMargin + 1)*2 + (oddCount+1)*(yLabelMaxWidth) - oddCount - 1); verticalLinePos = (xAreaCoordEnd + (oddCount+1)*(yLabelMargin + 1) + oddCount*(yLabelMaxWidth + yLabelMargin)); oddCount++; } drawHorizontalReferencesLabel(g, metrics, yReferenceLabels.get(a).get(0), (int) Math.floor(yTicks.getDouble(0)), drawRange, xRightLabel, true, false, verticalLinePos); drawHorizontalReferencesLabel(g, metrics, yReferenceLabels.get(a).get(yReferenceLabels.get(a).size() - 1), (int) Math.floor(yTicks.getDouble(yReferenceLabels.get(a).size() - 1)), drawRange, xRightLabel, false, false, verticalLinePos); for (int b = 1; b < yReferenceLabels.get(a).size() - 1; b++) { drawHorizontalReferencesLabel(g, metrics, yReferenceLabels.get(a).get(b), (int) Math.floor(yTicks.getDouble(b)), drawRange, xRightLabel, true, false, verticalLinePos); } } } } protected void drawYLabelsSplit() { // Draw Y labels for(int a = 0; a < numGraphs; a++){ ListNumber yTicks = yReferenceCoords.get(a); if (yReferenceLabels.get(a) != null && !yReferenceLabels.get(a).isEmpty()) { //g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g.setColor(labelColor); g.setFont(labelFont); FontMetrics metrics = g.getFontMetrics(); // Draw first and last label int[] drawRange = new int[] {yAreaCoordStart.get(a), yAreaCoordEnd.get(a) - 1}; int xRightLabel = (int) (xAreaCoordStart - yLabelMargin - 1); drawHorizontalReferencesLabelSplit(g, metrics, yReferenceLabels.get(a).get(0), (int) Math.floor(yTicks.getDouble(0)), drawRange, xRightLabel, true, false); drawHorizontalReferencesLabelSplit(g, metrics, yReferenceLabels.get(a).get(yReferenceLabels.get(a).size() - 1), (int) Math.floor(yTicks.getDouble(yReferenceLabels.get(a).size() - 1)), drawRange, xRightLabel, false, false); for (int b = 1; b < yReferenceLabels.get(a).size() - 1; b++) { drawHorizontalReferencesLabelSplit(g, metrics, yReferenceLabels.get(a).get(b), (int) Math.floor(yTicks.getDouble(b)), drawRange, xRightLabel, true, false); } } } } @Override protected void drawXLabels() { // Draw X labels ListNumber xTicks = xReferenceCoords; if (xReferenceLabels != null && !xReferenceLabels.isEmpty()) { //g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g.setColor(labelColor); g.setFont(labelFont); FontMetrics metrics = g.getFontMetrics(); // Draw first and last label int[] drawRange = new int[] {xAreaCoordStart, xAreaCoordEnd - 1}; int yTop = (int) (super.yAreaCoordEnd + xLabelMargin); drawVerticalReferenceLabel(g, metrics, xReferenceLabels.get(0), (int) Math.floor(xTicks.getDouble(0)), drawRange, yTop, true, false); drawVerticalReferenceLabel(g, metrics, xReferenceLabels.get(xReferenceLabels.size() - 1), (int) Math.floor(xTicks.getDouble(xReferenceLabels.size() - 1)), drawRange, yTop, false, false); for (int i = 1; i < xReferenceLabels.size() - 1; i++) { drawVerticalReferenceLabel(g, metrics, xReferenceLabels.get(i), (int) Math.floor(xTicks.getDouble(i)), drawRange, yTop, true, false); } } } private static void drawVerticalReferenceLabel(Graphics2D graphics, FontMetrics metrics, String text, int xCenter, int[] drawRange, int yTop, boolean updateMin, boolean centeredOnly) { // If the center is not in the range, don't draw anything if (drawRange[MAX] < xCenter || drawRange[MIN] > xCenter) return; // If there is no space, don't draw anything if (drawRange[MAX] - drawRange[MIN] < metrics.getHeight()) return; Java2DStringUtilities.Alignment alignment = Java2DStringUtilities.Alignment.TOP; int targetX = xCenter; int halfWidth = metrics.stringWidth(text) / 2; if (xCenter < drawRange[MIN] + halfWidth) { // Can't be drawn in the center if (centeredOnly) return; alignment = Java2DStringUtilities.Alignment.TOP_LEFT; targetX = drawRange[MIN]; } else if (xCenter > drawRange[MAX] - halfWidth) { // Can't be drawn in the center if (centeredOnly) return; alignment = Java2DStringUtilities.Alignment.TOP_RIGHT; targetX = drawRange[MAX]; } Java2DStringUtilities.drawString(graphics, alignment, targetX, yTop, text); if (updateMin) { drawRange[MIN] = targetX + metrics.getHeight(); } else { drawRange[MAX] = targetX - metrics.getHeight(); } } protected void drawXLabelsSplit() { // Draw X labels ListNumber xTicks = xReferenceCoords; if (xReferenceLabels != null && !xReferenceLabels.isEmpty()) { //g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g.setColor(labelColor); g.setFont(labelFont); FontMetrics metrics = g.getFontMetrics(); // Draw first and last label int[] drawRange = new int[] {xAreaCoordStart, xAreaCoordEnd - 1}; int yTop = (int) (yAreaCoordEnd.get(numGraphs-1) + xLabelMargin); drawVerticalReferenceLabelSplit(g, metrics, xReferenceLabels.get(0), (int) Math.floor(xTicks.getDouble(0)), drawRange, yTop, true, false); drawVerticalReferenceLabelSplit(g, metrics, xReferenceLabels.get(xReferenceLabels.size() - 1), (int) Math.floor(xTicks.getDouble(xReferenceLabels.size() - 1)), drawRange, yTop, false, false); for (int i = 1; i < xReferenceLabels.size() - 1; i++) { drawVerticalReferenceLabelSplit(g, metrics, xReferenceLabels.get(i), (int) Math.floor(xTicks.getDouble(i)), drawRange, yTop, true, false); } } } private static final int MIN = 0; private static final int MAX = 1; private static void drawHorizontalReferencesLabel(Graphics2D graphics, FontMetrics metrics, String text, int yCenter, int[] drawRange, int xRight, boolean updateMin, boolean centeredOnly, int verticalLinePos) { // If the center is not in the range, don't draw anything if (drawRange[MAX] < yCenter || drawRange[MIN] > yCenter) return; // If there is no space, don't draw anything if (drawRange[MAX] - drawRange[MIN] < metrics.getHeight()) return; Java2DStringUtilities.Alignment alignment = Java2DStringUtilities.Alignment.RIGHT; int targetY = yCenter; int halfHeight = metrics.getAscent() / 2; if (yCenter < drawRange[MIN] + halfHeight) { // Can't be drawn in the center if (centeredOnly) return; alignment = Java2DStringUtilities.Alignment.TOP_RIGHT; targetY = drawRange[MIN]; } else if (yCenter > drawRange[MAX] - halfHeight) { // Can't be drawn in the center if (centeredOnly) return; alignment = Java2DStringUtilities.Alignment.BOTTOM_RIGHT; targetY = drawRange[MAX]; } Java2DStringUtilities.drawString(graphics, alignment, xRight, targetY, text); graphics.drawLine(verticalLinePos - 1 , targetY, verticalLinePos + 1, targetY); if (updateMin) { drawRange[MAX] = targetY - metrics.getHeight(); } else { drawRange[MIN] = targetY + metrics.getHeight(); } } private static void drawHorizontalReferencesLabelSplit(Graphics2D graphics, FontMetrics metrics, String text, int yCenter, int[] drawRange, int xRight, boolean updateMin, boolean centeredOnly) { // If the center is not in the range, don't draw anything if (drawRange[MAX] < yCenter || drawRange[MIN] > yCenter) return; // If there is no space, don't draw anything if (drawRange[MAX] - drawRange[MIN] < metrics.getHeight()) return; Java2DStringUtilities.Alignment alignment = Java2DStringUtilities.Alignment.RIGHT; int targetY = yCenter; int halfHeight = metrics.getAscent() / 2; if (yCenter < drawRange[MIN] + halfHeight) { // Can't be drawn in the center if (centeredOnly) return; alignment = Java2DStringUtilities.Alignment.TOP_RIGHT; targetY = drawRange[MIN]; } else if (yCenter > drawRange[MAX] - halfHeight) { // Can't be drawn in the center if (centeredOnly) return; alignment = Java2DStringUtilities.Alignment.BOTTOM_RIGHT; targetY = drawRange[MAX]; } Java2DStringUtilities.drawString(graphics, alignment, xRight, targetY, text); if (updateMin) { drawRange[MAX] = targetY - metrics.getHeight(); } else { drawRange[MIN] = targetY + metrics.getHeight(); } } private static void drawVerticalReferenceLabelSplit(Graphics2D graphics, FontMetrics metrics, String text, int xCenter, int[] drawRange, int yTop, boolean updateMin, boolean centeredOnly) { // If the center is not in the range, don't draw anything if (drawRange[MAX] < xCenter || drawRange[MIN] > xCenter) return; // If there is no space, don't draw anything if (drawRange[MAX] - drawRange[MIN] < metrics.getHeight()) return; Java2DStringUtilities.Alignment alignment = Java2DStringUtilities.Alignment.TOP; int targetX = xCenter; int halfWidth = metrics.stringWidth(text) / 2; if (xCenter < drawRange[MIN] + halfWidth) { // Can't be drawn in the center if (centeredOnly) return; alignment = Java2DStringUtilities.Alignment.TOP_LEFT; targetX = drawRange[MIN]; } else if (xCenter > drawRange[MAX] - halfWidth) { // Can't be drawn in the center if (centeredOnly) return; alignment = Java2DStringUtilities.Alignment.TOP_RIGHT; targetX = drawRange[MAX]; } Java2DStringUtilities.drawString(graphics, alignment, targetX, yTop, text); if (updateMin) { drawRange[MIN] = targetX + metrics.getHeight(); } else { drawRange[MAX] = targetX - metrics.getHeight(); } } private final double scaledX1(double value) { return xValueScale.scaleValue(value, xPlotValueStart, xPlotValueEnd, xPlotCoordStart, xPlotCoordEnd); } protected void drawValueExplicitLine(ListNumber xValues, ListNumber yValues, InterpolationScheme interpolation, ReductionScheme reduction, int index) { MultiAxisLineGraph2DRenderer.ScaledData scaledData; g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); // Narrow the data int start = org.diirt.util.array.ListNumbers.binarySearchValueOrLower(xValues, xPlotValueStart); int end = org.diirt.util.array.ListNumbers.binarySearchValueOrHigher(xValues, xPlotValueEnd); xValues = ListMath.limit(xValues, start, end + 1); yValues = ListMath.limit(yValues, start, end + 1); switch (reduction) { default: throw new IllegalArgumentException("Reduction scheme " + reduction + " not supported"); case NONE: scaledData = scaleNoReduction(xValues, yValues, start,index); break; case FIRST_MAX_MIN_LAST: scaledData = scaleFirstMaxMinLastReduction(xValues, yValues, start, index); break; } // create path Path2D path; switch (interpolation) { default: case NEAREST_NEIGHBOR: path = nearestNeighbour(scaledData); break; case LINEAR: path = linearInterpolation(scaledData); break; case CUBIC: path = cubicInterpolation(scaledData); break; } // Draw the line g.draw(path); } private static class ScaledData { private double[] scaledX; private double[] scaledY; private int start; private int end; } private MultiAxisLineGraph2DRenderer.ScaledData scaleNoReduction(ListNumber xValues, ListNumber yValues, int dataStart, int index) { MultiAxisLineGraph2DRenderer.ScaledData scaledData = new MultiAxisLineGraph2DRenderer.ScaledData(); int dataCount = xValues.size(); scaledData.scaledX = new double[dataCount]; scaledData.scaledY = new double[dataCount]; for (int i = 0; i < scaledData.scaledY.length; i++) { scaledData.scaledX[i] = scaledX1(xValues.getDouble(i)); if(separateAreas){ scaledData.scaledY[i] = scaledYSplit(yValues.getDouble(i), index); }else{ scaledData.scaledY[i] = scaledY(yValues.getDouble(i), index); } processScaledValue(dataStart + i, xValues.getDouble(i), yValues.getDouble(i), scaledData.scaledX[i], scaledData.scaledY[i]); } scaledData.end = dataCount; return scaledData; } private MultiAxisLineGraph2DRenderer.ScaledData scaleFirstMaxMinLastReduction(ListNumber xValues, ListNumber yValues, int dataStart, int index) { // The number of points generated by this is about 4 times the // number of points on the x axis. If the number of points is less // than that, it's not worth it. Don't do the data reduction. if (xValues.size() < xPlotCoordWidth * 4) { return scaleNoReduction(xValues, yValues, dataStart, index); } MultiAxisLineGraph2DRenderer.ScaledData scaledData = new MultiAxisLineGraph2DRenderer.ScaledData(); scaledData.scaledX = new double[((int) xPlotCoordWidth + 1)*4 ]; scaledData.scaledY = new double[((int) xPlotCoordWidth + 1)*4]; int cursor = 0; int previousPixel = (int) scaledX1(xValues.getDouble(0)); double last; if(separateAreas){ last = scaledYSplit(yValues.getDouble(0),index); }else{ last = scaledY(yValues.getDouble(0),index); } double min = last; double max = last; scaledData.scaledX[0] = previousPixel; scaledData.scaledY[0] = min; processScaledValue(dataStart, xValues.getDouble(0), yValues.getDouble(0), scaledX1(xValues.getDouble(0)), last); cursor++; for (int i = 1; i < xValues.size(); i++) { double currentScaledX = scaledX1(xValues.getDouble(i)); int currentPixel = (int) currentScaledX; if (currentPixel == previousPixel) { if(separateAreas){ last = scaledYSplit(yValues.getDouble(i),index); }else{ last = scaledY(yValues.getDouble(i),index); } min = MathIgnoreNaN.min(min, last); max = MathIgnoreNaN.max(max, last); processScaledValue(dataStart + i, xValues.getDouble(i), yValues.getDouble(i), currentScaledX, last); } else { scaledData.scaledX[cursor] = previousPixel; scaledData.scaledY[cursor] = max; cursor++; scaledData.scaledX[cursor] = previousPixel; scaledData.scaledY[cursor] = min; cursor++; scaledData.scaledX[cursor] = previousPixel; scaledData.scaledY[cursor] = last; cursor++; previousPixel = currentPixel; if(separateAreas){ last = scaledYSplit(yValues.getDouble(i),index); }else{ last = scaledY(yValues.getDouble(i),index); } min = last; max = last; scaledData.scaledX[cursor] = currentPixel; scaledData.scaledY[cursor] = last; cursor++; } } scaledData.scaledX[cursor] = previousPixel; scaledData.scaledY[cursor] = max; cursor++; scaledData.scaledX[cursor] = previousPixel; scaledData.scaledY[cursor] = min; cursor++; scaledData.end = cursor; return scaledData; } private static Path2D.Double nearestNeighbour(MultiAxisLineGraph2DRenderer.ScaledData scaledData) { double[] scaledX = scaledData.scaledX; double[] scaledY = scaledData.scaledY; int start = scaledData.start; int end = scaledData.end; Path2D.Double line = new Path2D.Double(); line.moveTo(scaledX[start], scaledY[start]); for (int i = 1; i < end; i++) { double halfX = scaledX[i - 1] + (scaledX[i] - scaledX[i - 1]) / 2; if (!java.lang.Double.isNaN(scaledY[i-1])) { line.lineTo(halfX, scaledY[i - 1]); if (!java.lang.Double.isNaN(scaledY[i])) line.lineTo(halfX, scaledY[i]); } else { line.moveTo(halfX, scaledY[i]); } } line.lineTo(scaledX[end - 1], scaledY[end - 1]); return line; } private static Path2D.Double linearInterpolation(MultiAxisLineGraph2DRenderer.ScaledData scaledData){ double[] scaledX = scaledData.scaledX; double[] scaledY = scaledData.scaledY; int start = scaledData.start; int end = scaledData.end; Path2D.Double line = new Path2D.Double(); for (int i = start; i < end; i++) { // Do I have a current value? if (!java.lang.Double.isNaN(scaledY[i])) { // Do I have a previous value? if (i != start && !java.lang.Double.isNaN(scaledY[i - 1])) { // Here I have both the previous value and the current value line.lineTo(scaledX[i], scaledY[i]); } else { // Don't have a previous value // Do I have a next value? if (i != end - 1 && !java.lang.Double.isNaN(scaledY[i + 1])) { // There is no value before, but there is a value after line.moveTo(scaledX[i], scaledY[i]); } else { // There is no value either before or after line.moveTo(scaledX[i] - 1, scaledY[i]); line.lineTo(scaledX[i] + 1, scaledY[i]); } } } } return line; } private static Path2D.Double cubicInterpolation(MultiAxisLineGraph2DRenderer.ScaledData scaledData){ double[] scaledX = scaledData.scaledX; double[] scaledY = scaledData.scaledY; int start = scaledData.start; int end = scaledData.end; Path2D.Double path = new Path2D.Double(); for (int i = start; i < end; i++) { double y1; double y2; double x1; double x2; double y0; double x0; double y3; double x3; double bx0; double by0; double bx3; double by3; double bdy0; double bdy3; double bx1; double by1; double bx2; double by2; //1. start at i = start //2. convert to bezier in the same place as you assign in the three normal cases //3. if statements - start with most general to most specific in the middle //4. can make function that converts to bezier and then you call it //5 check location and if you have nan in most if's //Do I have current value? if (!java.lang.Double.isNaN(scaledY[i])){ //Do I have previous value? if (i > start && !java.lang.Double.isNaN(scaledY[i - 1])) { //Do I have value two before? if (i > start + 1 && !java.lang.Double.isNaN(scaledY[i - 2])) { //Do I have next value? if (i != end - 1 && !java.lang.Double.isNaN(scaledY[i + 1])) { y2 = scaledY[i]; x2 = scaledX[i]; y0 = scaledY[i - 2]; x0 = scaledX[i - 2]; y3 = scaledY[i + 1]; x3 = scaledX[i + 1]; y1 = scaledY[i - 1]; x1 = scaledX[i - 1]; bx0 = x1; by0 = y1; bx3 = x2; by3 = y2; bdy0 = (y2 - y0) / (x2 - x0); bdy3 = (y3 - y1) / (x3 - x1); bx1 = bx0 + (x2 - x0) / 6.0; by1 = (bx1 - bx0) * bdy0 + by0; bx2 = bx3 - (x3 - x1) / 6.0; by2 = (bx2 - bx3) * bdy3 + by3; path.curveTo(bx1, by1, bx2, by2, bx3, by3); } else{//Have current, previous, two before, but not value after y2 = scaledY[i]; x2 = scaledX[i]; y1 = scaledY[i - 1]; x1 = scaledX[i - 1]; y0 = scaledY[i - 2]; x0 = scaledX[i - 2]; y3 = y2 + (y2 - y1) / 2; x3 = x2 + (x2 - x1) / 2; bx0 = x1; by0 = y1; bx3 = x2; by3 = y2; bdy0 = (y2 - y0) / (x2 - x0); bdy3 = (y3 - y1) / (x3 - x1); bx1 = bx0 + (x2 - x0) / 6.0; by1 = (bx1 - bx0) * bdy0 + by0; bx2 = bx3 - (x3 - x1) / 6.0; by2 = (bx2 - bx3) * bdy3 + by3; path.curveTo(bx1, by1, bx2, by2, bx3, by3); } } else if (i != end - 1 && !java.lang.Double.isNaN(scaledY[i + 1])) { //Have current , previous, and next, but not two before path.moveTo(scaledX[i - 1], scaledY[i - 1]); y2 = scaledY[i]; x2 = scaledX[i]; y1 = scaledY[i - 1]; x1 = scaledX[i - 1]; y0 = y1 - (y2 - y1) / 2; x0 = x1 - (x2 - x1) / 2; y3 = scaledY[i + 1]; x3 = scaledX[i + 1]; bx0 = x1; by0 = y1; bx3 = x2; by3 = y2; bdy0 = (y2 - y0) / (x2 - x0); bdy3 = (y3 - y1) / (x3 - x1); bx1 = bx0 + (x2 - x0) / 6.0; by1 = (bx1 - bx0) * bdy0 + by0; bx2 = bx3 - (x3 - x1) / 6.0; by2 = (bx2 - bx3) * bdy3 + by3; path.curveTo(bx1, by1, bx2, by2, bx3, by3); }else{//have current, previous, but not two before or next path.lineTo(scaledX[i], scaledY[i]); } //have current, but not previous }else{ // No previous value if (i != end - 1 && !java.lang.Double.isNaN(scaledY[i + 1])) { // If we have the next value, just move, we'll draw later path.moveTo(scaledX[i], scaledY[i]); } else { // If not, write a small horizontal line path.moveTo(scaledX[i] - 1, scaledY[i]); path.lineTo(scaledX[i] + 1, scaledY[i]); } } }else{ //do not have current // Do nothing } } return path; } }