/**
* Copyright 2010-2012 Jingjing Li.
*
* This file is part of jplot2d.
*
* jplot2d is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* jplot2d 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 Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with jplot2d. If not, see .
*/
package org.jplot2d.element.impl;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.jplot2d.data.XYGraphData;
import org.jplot2d.transform.Transform1D;
/**
* Supply graph data points in iterable chunks.
*
* @author Jingjing Li
*/
public class XYGraphDataChunker implements Iterable {
/**
* For re-using the instance of this class to avoid re-allocate buffers.
*/
private static final ThreadLocal _threadLocalBuilder = new ThreadLocal();
private static final int CHUNK_SIZE = 1000;
public static class ChunkData {
public int size;
public float[] xBuf;
public float[] yBuf;
public Color[] markColorBuf;
public int xErrorSize;
public float[] xLowBuf;
public float[] xHighBuf;
public int yErrorSize;
public float[] yLowBuf;
public float[] yHighBuf;
private ChunkData(int bufferSize) {
xBuf = new float[bufferSize];
yBuf = new float[bufferSize];
markColorBuf = new Color[bufferSize];
xLowBuf = new float[bufferSize];
xHighBuf = new float[bufferSize];
yLowBuf = new float[bufferSize];
yHighBuf = new float[bufferSize];
size = 0;
xErrorSize = 0;
yErrorSize = 0;
}
/**
* reset this ChunkData for next use.
*/
private void reset() {
size = 0;
xErrorSize = 0;
yErrorSize = 0;
}
private void addPoint(float x, float y) {
xBuf[size] = x;
yBuf[size] = y;
markColorBuf[size] = null;
xLowBuf[size] = xHighBuf[size] = 0;
yLowBuf[size] = yHighBuf[size] = 0;
/*
* error size need not change because it always equals the last valid data.size + 1
*/
size++;
}
}
private class DataChunkIterator implements Iterator {
/**
* The chunk to be returned by next()
*/
private ChunkData _chunk = new ChunkData(CHUNK_SIZE);
/**
* The chunk next to _chunk
*/
private ChunkData _nextChunk = new ChunkData(CHUNK_SIZE);
/**
* The current data point index
*/
private int i;
/**
* Reset this iterator states.
*/
private void reset() {
i = 0;
prepareNextChunk(_chunk);
}
public boolean hasNext() {
return _chunk != null && _chunk.size > 0;
}
public ChunkData next() {
if (_chunk.size == 0) {
throw new NoSuchElementException();
}
prepareNextChunk(_nextChunk);
ChunkData result = _chunk;
_chunk = _nextChunk;
_nextChunk = result;
return result;
}
public void remove() {
throw new UnsupportedOperationException();
}
/**
* After forwarding the current to next, calculating the next chunk. After this method, the
* ChunkData.size() may be 0 if there is no more chunk.
*/
private void prepareNextChunk(ChunkData data) {
// fill the next chunk, non-empty
data.reset();
while (i < graphData.size()) {
fillChunk(data);
if (data.size > 0) {
break;
}
}
}
/**
* calculating the next chunk from the current position till the NaN data or chunk full.
* After this method, the ChunkData.size() may be 0 if there is no valid data in this chunk.
*/
private void fillChunk(ChunkData data) {
/* eat off the beginning NaNs */
while (i < graphData.size()) {
if (Double.isNaN(graphData.getX(i)) || Double.isNaN(graphData.getY(i))) {
i++;
} else {
break;
}
}
if (i >= graphData.size()) {
return;
}
boolean drawMark = graph.isSymbolVisible();
boolean haspre = false;
double prex = Double.NaN;
double prey = Double.NaN;
boolean isPreBigNumber = false;
while (i < graphData.size()) {
double x = xW2D.convert(graphData.getX(i));
double y = yW2D.convert(graphData.getY(i));
/* break at NaN value */
if (Double.isNaN(graphData.getX(i)) || Double.isNaN(graphData.getY(i))) {
i++;
break;
}
boolean isThisBigNumber = isBigNumber(x) || (isBigNumber(y));
if (haspre) {
// if previous is a big number bring to the clip border.
if (isPreBigNumber) {
Point2D a = new Point2D.Double(x, y);
Point2D b = new Point2D.Double(prex, prey);
Point2D newb = intersecate(clip, b, a);
if (newb != null) {
data.addPoint((float) newb.getX(), (float) newb.getY());
}
}
/* bring this to the clip border */
if (isThisBigNumber) {
Point2D a = new Point2D.Double(x, y);
Point2D b = new Point2D.Double(prex, prey);
Point2D newa = intersecate(clip, a, b);
if (newa != null) {
data.addPoint((float) newa.getX(), (float) newa.getY());
}
}
} else {
haspre = true;
}
prex = x;
prey = y;
isPreBigNumber = isThisBigNumber;
if (!isThisBigNumber) {
/* data buffer */
data.xBuf[data.size] = (float) x;
data.yBuf[data.size] = (float) y;
/* markColorBuf */
if (drawMark) {
data.markColorBuf[data.size] = graph.getEffectiveSymbolColor(i);
}
/* error buffer */
if (graphData.getXError() != null && i < graphData.getXError().size()) {
boolean vxlow = !Double.isNaN(graphData.getXErrorLow(i));
boolean vxhigh = !Double.isNaN(graphData.getXErrorHigh(i));
if (vxlow || vxhigh) {
data.xErrorSize = data.size + 1;
if (vxlow) {
data.xLowBuf[data.size] = (float) xW2D.convert(graphData.getX(i)
- graphData.getXErrorLow(i));
} else {
data.xLowBuf[data.size] = data.xBuf[data.size];
}
if (vxhigh) {
data.xHighBuf[data.size] = (float) xW2D.convert(graphData.getX(i)
+ graphData.getXErrorHigh(i));
} else {
data.xHighBuf[data.size] = data.xBuf[data.size];
}
}
}
if (graphData.getYError() != null && i < graphData.getYError().size()) {
boolean vylow = !Double.isNaN(graphData.getYErrorLow(i));
boolean vyhigh = !Double.isNaN(graphData.getYErrorHigh(i));
if (vylow || vyhigh) {
data.yErrorSize = data.size + 1;
if (vylow) {
data.yLowBuf[data.size] = (float) yW2D.convert(graphData.getY(i)
- graphData.getYErrorLow(i));
} else {
data.yLowBuf[data.size] = data.yBuf[data.size];
}
if (vyhigh) {
data.yHighBuf[data.size] = (float) yW2D.convert(graphData.getY(i)
+ graphData.getYErrorHigh(i));
} else {
data.yHighBuf[data.size] = data.yBuf[data.size];
}
}
}
data.size++;
}
/*
* the next data point may generate 2 points by bring the big number to clip border
* if both this and next point are big number. So an extra array space must be
* reserved
*/
if (data.size >= CHUNK_SIZE - 1) {
/*
* Do not forward the i to make next chunk starts from the line data in last
* chunk (need overlap).
*/
break;
} else {
i++;
}
}
}
}
private LayerEx layer;
private XYGraphEx graph;
private XYGraphData graphData;
private Rectangle2D clip;
private Transform1D xW2D, yW2D;
private final DataChunkIterator ite = new DataChunkIterator();
private XYGraphDataChunker() {
}
private void setLineData(XYGraphEx dp) {
this.graphData = dp.getData();
this.layer = dp.getParent();
this.graph = dp;
calcTransformXY();
}
private void setClip(Rectangle2D clip) {
this.clip = clip;
}
/**
* Calculate the transform for world value to device value
*/
private void calcTransformXY() {
xW2D = layer
.getXAxisTransform()
.getNormalTransform()
.createTransform(layer.getPaperTransform().getXPtoD(0),
layer.getPaperTransform().getXPtoD(layer.getSize().getWidth()));
yW2D = layer
.getYAxisTransform()
.getNormalTransform()
.createTransform(layer.getPaperTransform().getYPtoD(0),
layer.getPaperTransform().getYPtoD(layer.getSize().getHeight()));
}
/*
* Warning: Only one iterator allowed!
*/
public DataChunkIterator iterator() {
ite.reset();
return ite;
}
/**
* On some system, the underlayer graphic only process short int.
*
* @param utoD2
* @return boolean
*/
private static final boolean isBigNumber(double utoD2) {
return (utoD2 <= Short.MIN_VALUE || utoD2 >= Short.MAX_VALUE);
}
/**
* Calculate the intersection between the the line AB and the clip border. Return null if there
* is not intersection.
*
* @param clip
* the clip
* @param a
* the last point
* @param b
* the previous point
* @return the point on the intersection between the the line AB and the clip border and closer
* to A.
*/
private static Point2D intersecate(Rectangle2D clip, Point2D a, Point2D b) {
Line2D line = new Line2D.Double(a, b);
Point2D result = null;
double offset = 10;
// Rectangle2D clip = g.getClip().getBounds2D();
double slope = (b.getY() - a.getY()) / (b.getX() - a.getX());
if (clip.intersectsLine(line)) {
if (a.getX() < clip.getX() - offset) {
result = new Point2D.Double(clip.getX() - offset, slope
* (clip.getX() - offset - b.getX()) + b.getY());
}
if (a.getX() > clip.getMaxX() + offset) {
result = new Point2D.Double(clip.getMaxX() + offset, slope
* (clip.getMaxX() + offset - b.getX()) + b.getY());
}
if (a.getY() < clip.getY() - offset) {
result = new Point2D.Double((clip.getY() - offset - b.getY()) / slope + b.getX(),
clip.getY() - offset);
}
if (a.getY() > clip.getMaxY() + offset) {
result = new Point2D.Double(
(clip.getMaxY() + offset - b.getY()) / slope + b.getX(), clip.getMaxY()
+ offset);
}
}
return result;
}
public static XYGraphDataChunker getInstance(XYGraphEx dp, Rectangle clip) {
XYGraphDataChunker builder = _threadLocalBuilder.get();
if (builder == null) {
builder = new XYGraphDataChunker();
_threadLocalBuilder.set(builder);
}
builder.setLineData(dp);
builder.setClip(clip);
return builder;
}
}
Ads help maintain this website.