/*
 * Copyright (c) 2006-2009 OrangeSignal.com All rights reserved.
 * 
 * これは Apache ライセンス Version 2.0 (以下、このライセンスと記述) に
 * 従っています。このライセンスに準拠する場合以外、このファイルを使用
 * してはなりません。このライセンスのコピーは以下から入手できます。
 * 
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 * 
 * 適用可能な法律がある、あるいは文書によって明記されている場合を除き、
 * このライセンスの下で配布されているソフトウェアは、明示的であるか暗黙の
 * うちであるかを問わず、「保証やあらゆる種類の条件を含んでおらず」、
 * 「あるがまま」の状態で提供されるものとします。
 * このライセンスが適用される特定の許諾と制限については、このライセンス
 * を参照してください。
 */

package jp.sf.orangesignal.chart.axis;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

import jp.sf.orangesignal.chart.ChartColor;
import jp.sf.orangesignal.chart.ui.PriceScaleType;
import jp.sf.orangesignal.chart.util.DrawUtils;

/**
 * 数値の軸情報を提供します。
 * 
 * @author 杉澤 浩二
 */
public class NumberAxis extends Axis {

	// ---------------------------------------- 範囲

	/**
	 * 範囲の種類を表す列挙型です。
	 */
	public enum RangeType { FULL, NEGATIVE, POSITIVE }

	/**
	 * 範囲の種類を保持します。
	 */
	private RangeType rangeType = RangeType.FULL;

	/**
	 * 範囲の種類を設定します。
	 * 
	 * @param rangeType 範囲の種類
	 */
	public void setRangeType(final RangeType rangeType) {
		this.rangeType = rangeType;
	}

	/**
	 * 固定下限値を保持します。
	 */
	private Double fixedLower = null;

	/**
	 * 固定下限値を設定します。
	 * 既に固定上限値も設定されている場合、範囲が更新されます。
	 * 
	 * @param fixedLower 固定下限値
	 */
	public void setFixedLower(final Double fixedLower) {
		this.fixedLower = fixedLower;
		if (this.fixedLower != null && this.fixedUpper != null)
			setRange(fixedLower.doubleValue(), fixedUpper.doubleValue());
	}

	/**
	 * 固定上限値を保持します。
	 */
	private Double fixedUpper = null;

	/**
	 * 固定上限値を設定します。
	 * 既に固定上限値も設定されている場合、範囲が更新されます。
	 * 
	 * @param fixedUpper 固定上限値
	 */
	public void setFixedUpper(final Double fixedUpper) {
		this.fixedUpper = fixedUpper;
		if (this.fixedLower != null && this.fixedUpper != null)
			setRange(fixedLower.doubleValue(), fixedUpper.doubleValue());
	}

	/**
	 * 下限値からのマージン比率を保持します。
	 */
	private double lowerPadding = 0;

	/**
	 * 下限値からのマージン比率を設定します。
	 * 
	 * @param lowerPadding 下限値からのマージン比率
	 */
	public void setLowerPadding(final double lowerPadding) { this.lowerPadding = lowerPadding; }

	/**
	 * 上限値からのマージン比率を保持します。
	 */
	private double upperPadding = 0;

	/**
	 * 上限値からのマージン比率を設定します。
	 * 
	 * @param upperPadding 上限値からのマージン比率
	 */
	public void setUpperPadding(final double upperPadding) { this.upperPadding = upperPadding; }

	private double lowerMargin = 0;

	public void setLowerMargin(final double lowerMargin) { this.lowerMargin = lowerMargin; }

	private double upperMargin = 0;

	public void setUpperMargin(final double upperMargin) { this.upperMargin = upperMargin; }

	/**
	 * 既定の下限値です。
	 */
	private static final double DEFAULT_MIN_RANGE = 0;

	/**
	 * 既定の上限値です。
	 */
	private static final double DEFAULT_MAX_RANGE = 1;

	/**
	 * 範囲を保持します。
	 */
	private Range range = new Range(DEFAULT_MIN_RANGE, DEFAULT_MAX_RANGE);

	/**
	 * 範囲を返します。
	 * 
	 * @return 範囲
	 */
	public Range getRange() { return range; }

	/**
	 * 指定された範囲にマージン比率を加えた範囲を設定します。
	 * 但し、固定上限値が設定されている場合、上限値は固定上限値の値となります。
	 * また、固定下限値が設定されている場合、下限値は固定下限値の値となります。
	 * 
	 * @param lower 下限値
	 * @param upper 上限値
	 */
	private void setRange(final double lower, final double upper) {
		final double range = upper - lower;
		double l = this.fixedLower != null ? this.fixedLower.doubleValue() : lower - (range * this.lowerPadding);
		double u = 	this.fixedUpper != null ? this.fixedUpper.doubleValue() : upper + (range * this.upperPadding);
		//double l = this.fixedLower != null ? this.fixedLower.doubleValue() : lower;
		//double u = 	this.fixedUpper != null ? this.fixedUpper.doubleValue() : upper;

		switch (this.rangeType) {
			case POSITIVE:
				u = Math.max(u, 0);
				l = Math.max(l, 0);
				break;
			case NEGATIVE:
				u = Math.min(u, 0);
				l = Math.min(l, 0);
				break;
			case FULL:
			default:
				break;
		}

		this.range = new Range(l, u);
	}

	private Double[] uppers = null;
	private Double[] lowers = null;

	public void prepare(final Number[][] dataset) {
		prepare(dataset, 0);
	}

	public void prepare(final Number[][] dataset, final double margin) {
		if (dataset == null || dataset.length <= 0) {
			this.uppers = null;
			this.lowers = null;
			return;
		}

		int length = 0;
		for (int i = 0; i < dataset.length; i++) {
			if (dataset[i] != null) {
				length = dataset[i].length;
				break;
			}
		}

		this.uppers = new Double[length];
		this.lowers = new Double[length];

		for (int i = 0; i < dataset.length; i++) {
			final Number[] data = dataset[i];
			if (data != null) {
				for (int j = 0; j < Math.min(data.length, length); j++) {
					if (data[j] != null) {
						final double value = data[j].doubleValue();
						if (this.uppers[j] == null || this.uppers[j].doubleValue() < (value + margin)) {
							this.uppers[j] = value + margin;
						}
						if (this.lowers[j] == null || this.lowers[j].doubleValue() > (value - margin)) {
							this.lowers[j] = value - margin;
						}
					}
				}
			}
		}
	}

	public void autoAdjustRange(final int start, final int period) {
		Double upper = null;
		Double lower = null;

		if (this.uppers != null && this.lowers != null) {
			for (int i = start; i < (start + period); i++) {
				if (i < 0 || i >= uppers.length /* || i >= lowers.length */) {
					continue;
				}
				if (uppers[i] != null) {
					final double value = uppers[i].doubleValue();
					if (upper == null || upper.doubleValue() < value) {
						upper = value;
					}
				}
				if (lowers[i] != null) {
					final double value = lowers[i].doubleValue();
					if (lower == null || lower.doubleValue() > value) {
						lower = value;
					}
				}
			}
		}

		if (lower == null) {
			lower = DEFAULT_MIN_RANGE;
		}
		if (upper == null) {
			upper = DEFAULT_MAX_RANGE;
		}
		if (lower.compareTo(upper) == 0) {
			upper = upper.doubleValue() + 0.1;
			lower = lower.doubleValue() - 0.1;
		}

		setRange(lower, upper);
	}

	// ---------------------------------------- 目盛り

	/**
	 * 目盛りを描画するかどうかを保持します。
	 */
	protected boolean visibleTickmark = true;

//	/**
//	 * 目盛りを描画するかどうかを設定します。
//	 * 
//	 * @param visibleTickmark 目盛りを描画するかどうか
//	 */
//	public void setVisibleTickmark(final boolean visibleTickmark) {
//		this.visibleTickmark = visibleTickmark;
//	}

	/**
	 * 目盛りの種類を保持します。
	 */
	private PriceScaleType scale = PriceScaleType.NORMAL;

	/**
	 * 目盛りの種類を設定します。
	 * 
	 * @param scale 目盛りの種類
	 */
	public void setScale(final PriceScaleType scale) { this.scale = scale; }

	/**
	 * 目盛り単位群を保持します。
	 */
	private final NumberTickUnits units = new NumberTickUnits();

	/**
	 * 目盛りリストを保持します。
	 */
	private List<Double> ticks;

	// ---------------------------------------- 目盛り計算

	private static final int MAX_TICK_COUNT = 500;

	/**
	 * 目盛りリストを更新します。
	 */
	@Override
	public void refreshTicks() {
		// １目盛りの試算単位を算出
		final double size = this.range.getLength() / getTickCount();
		// １目盛りの単位を設定
		final NumberTickUnit unit = this.units.getCeilingTickUnit(size);

		// 目盛りの最低値を算出
		final double lowestTickValue = Math.ceil(this.range.getLower() / unit.getSize()) * unit.getSize();

		final int max = Math.min(getTickCount(), MAX_TICK_COUNT);
		this.ticks = new ArrayList<Double>(max);

		for (int i = 0; i < max; i++) {
			final double currentTickValue = lowestTickValue + (i * unit.getSize());
			if (currentTickValue > this.range.getUpper()) {
				break;
			}
			this.ticks.add(currentTickValue);
		}
	}

	/**
	 * 描画幅の下限値です。
	 */
	private static int MINIMUM_DRAW_WIDTH = 100;

	/**
	 * 描画高さの下限値です。
	 */
	private static int MINIMUM_DRAW_HEIGHT = 100;

	@Override
	public void refreshTicks(final Graphics2D g2, final Rectangle2D area) {
		final FontMetrics fm = g2.getFontMetrics(FONT);

		final int tickCount;
		if (getOrientation() == Orientation.VERTICAL) {
			tickCount = (int) Math.ceil(Math.max(area.getHeight(), MINIMUM_DRAW_HEIGHT) / (fm.getAscent() * getGap()));
		} else if (getOrientation() == Orientation.HORIZONTAL) {
			tickCount = (int) Math.ceil(Math.max(area.getWidth(), MINIMUM_DRAW_WIDTH) / (fm.stringWidth(getSampleText()) * getGap()));
		} else {
			throw new RuntimeException();
		}

		setTickCount(tickCount);
		refreshTicks();
	}

	private String getSampleText() {
		switch (this.rangeType) {
			case POSITIVE:
				return valueToString(this.range.getUpper());
			case NEGATIVE:
				return valueToString(this.range.getLower());
		}

		if (this.range.getUpper() > Math.abs(this.range.getLower())) {
			return valueToString(this.range.getUpper());
		}
		return valueToString(this.range.getUpper());
	}

	// ---------------------------------------- マーカー

	/**
	 * マーカーを描画するかどうかを保持します。
	 */
	private boolean visibleMarker = true;

//	/**
//	 * マーカーを描画するかどうかを設定します。
//	 * 
//	 * @param visibleMarker マーカーを描画するかどうか
//	 */
//	public void setVisibleMarker(boolean visibleMarker) {
//		this.visibleMarker = visibleMarker;
//	}

	/**
	 * マーカーのリストを保持します。
	 */
	private final List<Marker> markers = new ArrayList<Marker>(0);

	/**
	 * 指定されたマーカーをマーカーのリストへ追加します。
	 * 
	 * @param marker マーカー
	 */
	public void addMarker(final Marker marker) {
		this.markers.add(marker);
	}

	/**
	 * マーカリストからインターバルマーカーを除去します。
	 */
	public void removeIntervalMarkers() {
		for (int i = this.markers.size() - 1; i >= 0; i--) {
			if (this.markers.get(i) instanceof IntervalMarker) {
				this.markers.remove(i);
			}
		}
	}

	/**
	 * {@link ValueMarker} 用の {@link Stroke} を保持します。
	 */
	private Stroke lineMarkerStroke = new BasicStroke(1.0F, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER);

	/**
	 * {@link ValueMarker} 用の {@link Stroke} を返します。
	 * 
	 * @return {@link ValueMarker} 用の {@link Stroke}
	 */
	public Stroke getLineMarkerStroke() { return lineMarkerStroke; }

	/**
	 * {@link ValueMarker} 用の {@link Stroke} を設定します。
	 * 
	 * @param stroke {@link ValueMarker} 用の {@link Stroke}
	 */
	public void setLineMarkerStroke(Stroke stroke) { this.lineMarkerStroke = stroke; }

	/**
	 * {@link IntervalMarker} 用の {@link Composite} を保持します。
	 */
	private Composite markerComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5F);

	/**
	 * {@link IntervalMarker} 用の {@link Composite} を返します。
	 * 
	 * @return {@link IntervalMarker} 用の {@link Composite}
	 */
	public Composite getMarkerComposite() { return markerComposite; }

	/**
	 * {@link IntervalMarker} 用の {@link Composite} を設定します。
	 * 
	 * @param composite {@link IntervalMarker} 用の {@link Composite}
	 */
	public void setMarkerComposite(final Composite composite) { this.markerComposite = composite; }

	/**
	 * マーカーを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 */
	private void drawMarkers(final Graphics2D g2, final Rectangle2D area) {
		final Shape saved = g2.getClip();
		g2.setClip(area);

		for (final Marker marker : this.markers) {
			// マーカー線
			if (marker instanceof ValueMarker) {
				final ValueMarker m = (ValueMarker) marker;
				final double y = valueToJava2D(m.getValue(), area);
				g2.setColor(m.getColor());
				g2.setStroke(lineMarkerStroke);
				g2.draw(new Line2D.Double(area.getMinX(), y, area.getMaxX(), y));
			// マーカー領域
			} else if (marker instanceof IntervalMarker) {
				final IntervalMarker m = (IntervalMarker) marker;

				final double y1 = valueToJava2D(m.getUpper() == null ? this.range.getUpper() : m.getUpper().doubleValue(), area);
				final double y2 = valueToJava2D(m.getLower() == null ? this.range.getLower() : m.getLower().doubleValue(), area);

				final Composite originalComposite = g2.getComposite();
				g2.setComposite(markerComposite);
				g2.setColor(m.getColor());
				g2.fill(new Rectangle2D.Double(area.getMinX(), y1, area.getWidth(), y2 - y1));
				g2.setComposite(originalComposite);
			} else {
				throw new RuntimeException();
			}
		}

		g2.setClip(saved);
	}

	// ---------------------------------------- 描画

	/**
	 * 目盛りやグリッド線を描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 */
	public void draw(final Graphics2D g2, final Rectangle2D area) {
		if (getOrientation() == Orientation.VERTICAL) {
			drawVertical(g2, area);
		} else if (getOrientation() == Orientation.HORIZONTAL) {
			drawHorizontal(g2, area);
		} else {
			throw new RuntimeException();
		}
	}

	/**
	 * グリッド線を描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 */
	public void drawGlidline(final Graphics2D g2, final Rectangle2D area) {
		for (final double tick : this.ticks) {
			final double y = valueToJava2D(tick, area);
			// グリッド
			drawGlidline(g2, area.getMinX(), y, area.getMaxX(), y);
		}
	}

	/**
	 * 縦軸向けにグリッド線、目盛り、目盛り文字列、マーカーを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 */
	private void drawVertical(final Graphics2D g2, final Rectangle2D area) {
		g2.setFont(FONT);
		final double right = area.getMinX() - getTickMarkOutsideLength();

		if (this.visibleTickmark) {
			final FontMetrics fm = g2.getFontMetrics();
			final int descent = fm.getDescent();

			for (final double tick : this.ticks) {
				final double y = valueToJava2D(tick, area);

				// グリッド線
				drawGlidline(g2, area.getMinX(), y, area.getMaxX(), y);

				// 目盛り
				drawTickMark(g2, right - getTickMarkInsideLength(), y, right, y);

				// 目盛りラベル
				final String label = valueToString(tick);
				final double x = right - getTickMarkInsideLength() - getTickWidthMargin() - fm.stringWidth(label);
				DrawUtils.drawText(g2, label, (float) x, (float) (y + descent));
			}
		}

		if (this.visibleMarker) {
			drawMarkers(g2, area);
		}

		// 線
		drawAxisLine(g2, right, area.getMinY(), right, area.getMaxY());
	}

	/**
	 * 横軸向けにグリッド線、目盛り、目盛り文字列、マーカーを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 */
	private void drawHorizontal(final Graphics2D g2, final Rectangle2D area) {
		g2.setFont(FONT);
		final double top = area.getMaxY() + getTickMarkOutsideLength();

		if (this.visibleTickmark) {
			final FontMetrics fm = g2.getFontMetrics();
			final int ascent = fm.getAscent();

			for (final double tick : this.ticks) {
				final double x = valueToJava2D(tick, area);

				// グリッド線
				drawGlidline(g2, x, area.getMinY(), x, area.getMaxY());

				// 目盛り
				drawTickMark(g2, x, top, x, top + getTickMarkInsideLength());

				// 目盛りラベル
				final String label = valueToString(tick);
				DrawUtils.drawText(g2, label, (float) (x - fm.stringWidth(label) / 2), (float) top + getTickMarkInsideLength() + ascent + 1);
			}
		}

		// 線
		drawAxisLine(g2, area.getMinX(), top, area.getMaxX(), top);
	}

	/**
	 * トレースを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param xy 座標(X座標またはY座標)
	 * @param margin 余白
	 */
	public void drawAxisTrace(final Graphics2D g2, final Rectangle2D area, final double xy, final int margin) {
		if (getOrientation() == Orientation.VERTICAL) {
			drawVerticalAxisTrace(g2, area, xy, margin);
		} else if (getOrientation() == Orientation.HORIZONTAL) {
			drawHorizontalAxisTrace(g2, area, xy, margin);
		} else {
			throw new RuntimeException();
		}
	}

	/**
	 * 縦軸用のトレースを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param y Y座標
	 * @param margin 余白
	 */
	private void drawVerticalAxisTrace(final Graphics2D g2, final Rectangle2D area, final double y, final int margin) {
		// トレース線を描画します。
		drawTraceLine(g2, area.getMinX(), y, area.getMaxX(), y);

		// 各座標を計算します。
		g2.setFont(FONT);
		final FontMetrics fm = g2.getFontMetrics();
		final String text = valueToString(java2DToValue(y, area));

		final double right = area.getMinX() - getTickMarkOutsideLength();
		final double textWidth = fm.stringWidth(text);
		final double x1 = right - getTickMarkInsideLength() - getTickWidthMargin() - textWidth;
		final double x2 = x1 - margin + textWidth + margin;
		final double y1 = y + fm.getDescent() - fm.getAscent();
		final double y2 = y1 + fm.getHeight();

		// マーカー領域を構築します。
		final GeneralPath path = new GeneralPath();
		path.moveTo((float) (x1 - margin), (float) y1);
		path.lineTo((float) x2, (float) y1);
		path.lineTo((float) (right + margin), (float) (y1 + (y2 - y1) * 0.5));
		path.lineTo((float) x2, (float) y2);
		path.lineTo((float) (x1 - margin), (float) y2);
		path.closePath();

		// マーカーを描画します。
		g2.setPaint(new GradientPaint((float) x1, (float) y1, Color.WHITE, (float) x1, (float) y2, ChartColor.VERY_LIGHT_GRAY));
		g2.fill(path);
		g2.setColor(Color.BLUE);
		g2.draw(path);

		// マーカーラベルを描画します。
		DrawUtils.drawText(g2, text, (float) x1, (float) y + fm.getDescent());
	}

	/**
	 * 横軸用のトレースを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param x X座標
	 * @param margin 余白
	 */
	private void drawHorizontalAxisTrace(final Graphics2D g2, final Rectangle2D area, final double x, final int margin) {
		// トレース線を描画します。
		drawTraceLine(g2, x, area.getMinY(), x, area.getMaxY());

		// 各座標を計算します。
		g2.setFont(FONT);
		final FontMetrics fm = g2.getFontMetrics();
		final String text = valueToString(java2DToValue(x, area));

		final double top = area.getMaxY() + getTickMarkOutsideLength();
		final int width = fm.stringWidth(text);
		final int height = fm.getHeight();

		final double x1 = x - width * 0.5;
		final double x2 = x + width * 0.5;
		final double y1 = top + getTickMarkInsideLength() + 1;
		final double y2 = top + getTickMarkInsideLength() + height + 1;

		// マーカー領域を構築します。
		final GeneralPath path = new GeneralPath();
		path.moveTo((float) (x1 - margin), (float) y2);	// 左下
		path.lineTo((float) (x1 - margin), (float) y1);	// 左上
		path.lineTo((float) (x1 + (x2 - x1) * 0.5 - height * 0.5), (float) y1);	// 左付根
		path.lineTo((float) (x1 + (x2 - x1) * 0.5), (float) (top - margin));	// 頂点
		path.lineTo((float) (x1 + (x2 - x1) * 0.5 + height * 0.5), (float) y1);	// 右付根
		path.lineTo((float) (x2 + margin), (float) y1);
		path.lineTo((float) (x2 + margin), (float) y2);
		path.closePath();

		// マーカーを描画します。
		g2.setPaint(new GradientPaint((float) x1, (float) top, Color.WHITE, (float) x1, (float) y2, ChartColor.VERY_LIGHT_GRAY));
		g2.fill(path);
		g2.setColor(Color.BLUE);
		g2.draw(path);

		// マーカーラベルを描画します。
		final double y = top + getTickMarkInsideLength() + fm.getAscent() + 1;
		DrawUtils.drawText(g2, text, (float) x1, (float) y);
	}

	// ---------------------------------------- 修飾

	/**
	 * 指定された値に対する目盛り文字列を返します。
	 * 
	 * @param value 値
	 * @return 目盛り文字列
	 */
	private String valueToString(final double value) {
		return this.units.getCeilingTickUnit(value).valueToString(value);
	}

	// ---------------------------------------- 座標計算

	/**
	 * 指定された値から指定された描画領域内の座標へ変換して返します。
	 * 
	 * @param value 値
	 * @param area 描画領域
	 * @return 座標
	 */
	public double valueToJava2D(final double value, final Rectangle2D area) {
		if (getOrientation() == Orientation.VERTICAL) {
			// 標準
			if (this.scale == PriceScaleType.NORMAL) {
				return area.getMaxY() - ((value - this.range.getLower()) / this.range.getLength()) * (area.getMaxY() - area.getMinY() - this.lowerMargin - this.upperMargin) - this.lowerMargin;
			}
			// 反転
			if (this.scale == PriceScaleType.REVERSE) {
				return area.getMaxY() - ((this.range.getUpper() - value) / this.range.getLength()) * (area.getMaxY() - area.getMinY());
			}
			// 対数
			if (this.scale == PriceScaleType.LOG) {
				return area.getMaxY() - ((Math.log(value) - Math.log(this.range.getLower())) * (area.getMaxY() - area.getMinY()) / (Math.log(this.range.getUpper()) - Math.log(this.range.getLower())));
			}
		} else if (getOrientation() == Orientation.HORIZONTAL) {
			// 標準
			if (this.scale == PriceScaleType.NORMAL) {
				return area.getMaxX() - ((this.range.getUpper() - value) / this.range.getLength()) * (area.getMaxX() - area.getMinX());
			}
			// 反転
			if (this.scale == PriceScaleType.REVERSE) {
				return area.getMaxX() - ((value - this.range.getLower()) / this.range.getLength()) * (area.getMaxX() - area.getMinX());
			}
			// 対数
			if (this.scale == PriceScaleType.LOG) {
				return area.getMaxX() - ((Math.log(this.range.getUpper() - Math.log(value))) * (area.getMaxX() - area.getMinX()) / (Math.log(this.range.getUpper()) - Math.log(this.range.getLower())));
			}
		}

		throw new RuntimeException();
	}

	/**
	 * 指定された座標から指定された描画領域内の値へ変換して返します。
	 * 
	 * @param java2DValue 座標
	 * @param area 描画領域
	 * @return 値
	 */
	public double java2DToValue(final double java2DValue, final Rectangle2D area) {
		if (getOrientation() == Orientation.VERTICAL) {
			// 標準
			if (this.scale == PriceScaleType.NORMAL) {
				return range.getUpper() - (java2DValue - area.getMinY() + this.upperMargin) / (area.getMaxY() - area.getMinY() - this.lowerMargin - this.upperMargin) * this.range.getLength();
			}
			// 反転
			if (this.scale == PriceScaleType.REVERSE) {
				return range.getUpper() - (area.getMaxY() - java2DValue + this.upperMargin) / (area.getMaxY() - area.getMinY() - this.lowerMargin - this.upperMargin) * this.range.getLength();
			}
			// 対数
			if (this.scale == PriceScaleType.LOG) {
				return Math.exp(Math.log(range.getUpper()) - (java2DValue - area.getMinY() + this.upperMargin) / (area.getMaxY() - area.getMinY() - this.lowerMargin - this.upperMargin) * (Math.log(this.range.getUpper()) - Math.log(this.range.getLower())));
			}
		} else if (getOrientation() == Orientation.HORIZONTAL) {
			// 標準
			if (this.scale == PriceScaleType.NORMAL) {
				return this.range.getUpper() - (area.getMaxX() - java2DValue) / (area.getMaxX() - area.getMinX()) * this.range.getLength();
			}
			// 反転
			if (this.scale == PriceScaleType.REVERSE) {
				return this.range.getUpper() - (java2DValue - area.getMinX()) / (area.getMaxX() - area.getMinX()) * this.range.getLength();
			}
			// 対数
			if (this.scale == PriceScaleType.LOG) {
				return Math.exp(Math.log(this.range.getUpper()) - (area.getMaxX() - java2DValue) / (area.getMaxX() - area.getMinX()) * (Math.log(this.range.getUpper()) - Math.log(this.range.getLower())));
			}
		}

		throw new RuntimeException();
	}

}
