/*
 * 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.ui.canvas;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Paint;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import jp.sf.orangesignal.chart.ChartColor;
import jp.sf.orangesignal.chart.ChartSettings;
import jp.sf.orangesignal.chart.ChartSettings.TimeSeriesSettings;
import jp.sf.orangesignal.chart.axis.DateAxis;
import jp.sf.orangesignal.chart.axis.IntervalMarker;
import jp.sf.orangesignal.chart.axis.NumberAxis;
import jp.sf.orangesignal.chart.axis.Orientation;
import jp.sf.orangesignal.chart.axis.Range;
import jp.sf.orangesignal.chart.axis.ValueMarker;
import jp.sf.orangesignal.chart.axis.NumberAxis.RangeType;
import jp.sf.orangesignal.chart.data.TimeSeriesChartDataset;
import jp.sf.orangesignal.chart.data.VolumePriceHistogram;
import jp.sf.orangesignal.chart.event.ChartEvent;
import jp.sf.orangesignal.chart.event.ChartScreenEvent;
import jp.sf.orangesignal.chart.ui.BandType;
import jp.sf.orangesignal.chart.ui.PriceChartType;
import jp.sf.orangesignal.chart.ui.Icons;
import jp.sf.orangesignal.chart.ui.UpDownColorType;
import jp.sf.orangesignal.chart.ui.VolumeType;
import jp.sf.orangesignal.chart.ui.screen.ChartScreen;
import jp.sf.orangesignal.chart.util.DrawUtils;
import jp.sf.orangesignal.chart.util.StringManager;
import jp.sf.orangesignal.ta.Step;
import jp.sf.orangesignal.ta.util.DateArrayUtils;

/**
 * 価格チャートのキャンバスを提供します。
 * 
 * @author 杉澤 浩二
 */
public class TimeSeriesChartCanvas extends AbstractChartCanvas {

	private static final long serialVersionUID = 2873840209510087160L;

	private enum CandlePattern { BEARISH, BULLISH }

	// ---------------------------------------- 軸と領域

	/**
	 * 日付軸を保持します。
	 */
	private final DateAxis dateAxis = new DateAxis();

	/**
	 * チャート描画領域を保持します。
	 */
	private Rectangle2D chartArea;

	private static final int PRICE					= 0;
	private static final int VOLUME					= 1;
	private static final int AROON_INDICATOR		= 2;
	private static final int AROON_OSCILLATOR		= 3;
	private static final int ATR					= 4;
	private static final int NATR					= 5;
	private static final int BOP					= 6;
	private static final int CHV					= 7;
	private static final int CCI					= 8;
	private static final int CMF					= 9;
	private static final int CMO					= 10;
	private static final int COPPOCK				= 11;
	private static final int DPO					= 12;
	private static final int DMI					= 13;
	private static final int EMV					= 14;
	private static final int HV						= 15;
	private static final int KAIRI					= 16;
	private static final int MI						= 17;
	private static final int MOM					= 18;
	private static final int MFI					= 19;
	private static final int MACD					= 20;
	private static final int PAIN					= 21;
	private static final int PCR					= 22;
	private static final int APO					= 23;
	private static final int PPO					= 24;
	private static final int PSY					= 25;
	private static final int QSTICK					= 26;
	private static final int RCI					= 27;
	private static final int ROC					= 28;
	private static final int RSI					= 29;
	private static final int RVI					= 30;
	private static final int SHINOHARA				= 31;
	private static final int SRV_FAST				= 32;
	private static final int TII					= 33;
	private static final int TRIX					= 34;
	private static final int TSI					= 35;
	private static final int ULTIMATE				= 36;
	private static final int WAD					= 37;
	private static final int WR						= 38;

	private static final int AD						= 39;
	private static final int CHO					= 40;
	private static final int AVO					= 41;
	private static final int PVO					= 42;
	private static final int VKAIRI					= 43;
	private static final int VR1					= 44;
	private static final int VR2					= 45;
	private static final int VROC					= 46;
	private static final int VRSI					= 47;
	private static final int WVR					= 48;
	private static final int PVI					= 49;
	private static final int NVI					= 50;
	private static final int OBV					= 51;
	private static final int PVT					= 52;

	private static final int RATIO					= 53;
	private static final int PRICE_PERFORMANCE		= 54;
	private static final int PERCENT_PERFORMANCE	= 55;

	/**
	 * 軸の配列を保持します。
	 */
	private final NumberAxis[] axes = new NumberAxis[56];

	/**
	 * チャート描画領域の配列を保持します。
	 */
	private final Rectangle2D[] areas = new Rectangle2D[56];

	/**
	 * 価格チャートの説明を保持します。
	 */
	private String priceDescription;

	/**
	 * 出来高チャートの説明を保持します。
	 */
	private String volumeDescription;

	// ---------------------------------------- 価格帯別出来高

	/**
	 * 価格帯別出来高の価格軸を保持します。
	 * この軸情報は描画ではなく価格帯別出来高データを生成する為の計算に使用されます。
	 */
	private final NumberAxis vpPriceAxis = new NumberAxis();

	/**
	 * 価格帯別出来高の出来高軸を保持します。
	 */
	private final NumberAxis vpVolumeAxis = new NumberAxis();

	/**
	 * 価格帯別出来高描画領域を保持します。
	 */
	private Rectangle2D vpArea;

	/**
	 * 価格帯別出来高を保持します。
	 */
	private Number[] vp;

	// ---------------------------------------- トレース

	/**
	 * チャート上のマウス座標を保持します。
	 */
	private Point mousePosition;

	/**
	 * チャート上の期間座標を保持します。
	 */
	private int datePosition = -1;

	/**
	 * マウス座標のチャート描画領域を保持します。
	 */
	private Rectangle2D traceArea;

	/**
	 * マウス座標の数値軸情報を保持します。
	 */
	private NumberAxis traceAxis;

	/**
	 * チャート画面への参照を保持します。
	 */
	private final ChartScreen parent;

	// ---------------------------------------- アイコン

	/**
	 * 株式分割アイコンを保持します。
	 */
	private final Image splitIcon;

	// ---------------------------------------- データ

	/**
	 * データセットを保持します。
	 */
	private TimeSeriesChartDataset dataset;

	/**
	 * 時系列データの描画開始位置を保持します。
	 */
	private int start;

	/**
	 * 描画範囲を保持します。
	 */
	private int period;

	/**
	 * 設定情報を保持します。
	 */
	private ChartSettings settings;

	// ----------------------------------------

	/**
	 * コンストラクタです。
	 * 
	 * @param icons アイコン情報
	 * @param parent チャート画面への参照
	 */
	public TimeSeriesChartCanvas(final Icons icons, final ChartScreen parent) {
		this.parent = parent;
		this.splitIcon = icons.getSplit().getImage();

		for (int i = 0; i < this.axes.length; i++)
			this.axes[i] = new NumberAxis();

//		NumberAxis axis;

		// 価格軸を設定します。
		this.axes[PRICE].setRangeType(RangeType.POSITIVE);
		this.axes[PRICE].setUpperPadding(0.05);
		this.axes[PRICE].setLowerPadding(0.05);

		// 価格帯別出来高軸を設定します。
		this.vpPriceAxis.setRangeType(RangeType.POSITIVE);
		this.vpVolumeAxis.setOrientation(Orientation.HORIZONTAL);
		this.vpVolumeAxis.setRangeType(RangeType.POSITIVE);
		this.vpVolumeAxis.setUpperPadding(0.05);
		this.vpVolumeAxis.setFixedLower(0.0);
		this.vpVolumeAxis.setGap(1.5);

		// 出来高軸を設定します。
		this.axes[VOLUME].setRangeType(RangeType.POSITIVE);
		this.axes[VOLUME].setFixedLower(0.0);
		this.axes[VOLUME].setUpperPadding(0.05);

		// アルーン軸を設定します。
		this.axes[AROON_INDICATOR].setRangeType(RangeType.POSITIVE);
		this.axes[AROON_INDICATOR].setFixedUpper(100.0);
		this.axes[AROON_INDICATOR].setFixedLower(0.0);
		this.axes[AROON_INDICATOR].addMarker(new ValueMarker(30, Color.BLACK));
		this.axes[AROON_INDICATOR].addMarker(new ValueMarker(70, Color.BLACK));

		// アルーンオシレータ軸を設定します。
		//this.axes[AROON_OSCILLATOR].setRangeType(RangeType.FULL);
		this.axes[AROON_OSCILLATOR].setFixedUpper(100.0);
		this.axes[AROON_OSCILLATOR].setFixedLower(-100.0);
		this.axes[AROON_OSCILLATOR].addMarker(new ValueMarker(0, Color.BLACK));

		// ATR
		axes[ATR].setRangeType(RangeType.POSITIVE);
		axes[ATR].setUpperPadding(0.05);
		axes[ATR].setLowerPadding(0.05);

		// NATR
		axes[NATR].setRangeType(RangeType.POSITIVE);
		axes[NATR].setUpperPadding(0.05);
		axes[NATR].setLowerPadding(0.05);

		// BMP
		axes[BOP].setFixedUpper(+100.0);
		axes[BOP].setFixedLower(-100.0);
		axes[BOP].addMarker(new ValueMarker(0, Color.BLACK));

		// チャイキンズ・ボラティリティ軸を設定します。
		this.axes[CHV].setUpperPadding(0.05);
		this.axes[CHV].setLowerPadding(0.05);
		this.axes[CHV].addMarker(new ValueMarker(0, Color.BLACK));

		// CCI軸を設定します。
		//this.axes[CCI].setRangeType(RangeType.FULL);
		this.axes[CCI].setUpperPadding(0.05);
		this.axes[CCI].setLowerPadding(0.05);
		this.axes[CCI].addMarker(new ValueMarker(0, Color.BLACK));

		// CMF
		this.axes[CMF].setUpperPadding(0.05);
		this.axes[CMF].setLowerPadding(0.05);
		this.axes[CMF].addMarker(new ValueMarker(0, Color.BLACK));

		// CMO
		this.axes[CMO].setUpperPadding(0.05);
		this.axes[CMO].setLowerPadding(0.05);
		this.axes[CMO].addMarker(new ValueMarker(0, Color.BLACK));

		// コポック買い指標軸を設定します。
		//this.axes[COPPOCK].setRangeType(RangeType.FULL);
		this.axes[COPPOCK].setUpperPadding(0.05);
		this.axes[COPPOCK].setLowerPadding(0.05);
		this.axes[COPPOCK].addMarker(new ValueMarker(0, Color.BLACK));

		// DPO 軸を設定します。
		//this.axes[DPO].setRangeType(RangeType.FULL);
		this.axes[DPO].setUpperPadding(0.05);
		this.axes[DPO].setLowerPadding(0.05);
		this.axes[DPO].addMarker(new ValueMarker(0, Color.BLACK));

		// DMI 軸を設定します。
		this.axes[DMI].setRangeType(RangeType.POSITIVE);
		this.axes[DMI].setFixedUpper(100.0);
		this.axes[DMI].setFixedLower(0.0);

		// EMV
		axes[EMV].setUpperPadding(0.05);
		axes[EMV].setLowerPadding(0.05);
		axes[EMV].addMarker(new ValueMarker(0, Color.BLACK));

		// ヒストリカル・ボラティリティ軸を設定します。
		this.axes[HV].setRangeType(RangeType.POSITIVE);
		this.axes[HV].setUpperPadding(0.05);
		this.axes[HV].setLowerPadding(0.05);

		// 移動平均乖離率軸を設定します。
		//this.axes[BIAS].setRangeType(RangeType.FULL);
		this.axes[KAIRI].setUpperPadding(0.05);
		this.axes[KAIRI].setLowerPadding(0.05);

		// マス・インデックス(MI)軸を設定します。
		this.axes[MI].setRangeType(RangeType.POSITIVE);
		this.axes[MI].setUpperPadding(0.05);
		this.axes[MI].setLowerPadding(0.05);
		this.axes[MI].addMarker(new ValueMarker(26.5, Color.BLACK));
		this.axes[MI].addMarker(new ValueMarker(27.0, Color.BLACK));

		// モメンタム軸を設定します。
		//this.axes[MOMENTUM].setRangeType(RangeType.FULL);
		this.axes[MOM].setUpperPadding(0.05);
		this.axes[MOM].setLowerPadding(0.05);
		this.axes[MOM].addMarker(new ValueMarker(0, Color.BLACK));

		// MFI軸を設定します。
		this.axes[MFI].setRangeType(RangeType.POSITIVE);
		this.axes[MFI].setFixedUpper(100.0);
		this.axes[MFI].setFixedLower(0.0);

		// MACD 軸を設定します。
		//this.axes[MACD].setRangeType(RangeType.FULL);
		this.axes[MACD].setUpperPadding(0.05);
		this.axes[MACD].setLowerPadding(0.05);
		this.axes[MACD].addMarker(new ValueMarker(0, Color.BLACK));

		// PAIN
		this.axes[PAIN].setUpperPadding(0.05);
		this.axes[PAIN].setLowerPadding(0.05);
		this.axes[PAIN].addMarker(new ValueMarker(0, Color.BLACK));

		// PCR
		axes[PCR].setRangeType(RangeType.NEGATIVE);
		axes[PCR].setFixedUpper(0.0);
		axes[PCR].setFixedLower(-100.0);

		// プライス・オシレータ(APO)軸を設定します。
		this.axes[APO].setUpperPadding(0.05);
		this.axes[APO].setLowerPadding(0.05);
		this.axes[APO].addMarker(new ValueMarker(0, Color.BLACK));

		// プライス・オシレータ(PPO)軸を設定します。
		this.axes[PPO].setUpperPadding(0.05);
		this.axes[PPO].setLowerPadding(0.05);
		this.axes[PPO].addMarker(new ValueMarker(0, Color.BLACK));

		// サイコロジカルライン軸を設定します。
		this.axes[PSY].setRangeType(RangeType.POSITIVE);
		this.axes[PSY].setFixedUpper(100.0);
		this.axes[PSY].setFixedLower(0.0);

		// QSTICK
		this.axes[QSTICK].setUpperPadding(0.05);
		this.axes[QSTICK].setLowerPadding(0.05);
		this.axes[QSTICK].addMarker(new ValueMarker(0, Color.BLACK));

		// RCI軸を設定します。
		//this.axes[RCI].setRangeType(RangeType.FULL);
		this.axes[RCI].setFixedUpper(100.0);
		this.axes[RCI].setFixedLower(-100.0);
		this.axes[RCI].addMarker(new ValueMarker(0, Color.BLACK));

		// ROC 軸を設定します。
		//this.axes[ROC].setRangeType(RangeType.FULL);
		this.axes[ROC].setUpperPadding(0.05);
		this.axes[ROC].setLowerPadding(0.05);
		this.axes[ROC].addMarker(new ValueMarker(0, Color.BLACK));

		// RSI軸を設定します。
		this.axes[RSI].setRangeType(RangeType.POSITIVE);
		this.axes[RSI].setFixedUpper(100.0);
		this.axes[RSI].setFixedLower(0.0);

		// RVI軸を設定します。
		axes[RVI].setRangeType(RangeType.POSITIVE);
		axes[RVI].setFixedUpper(100.0);
		axes[RVI].setFixedLower(0.0);
		axes[RVI].addMarker(new ValueMarker(50, Color.BLACK));

		// 強弱レシオ(篠原レシオ)軸を設定します。
		this.axes[SHINOHARA].setRangeType(RangeType.POSITIVE);
		this.axes[SHINOHARA].setUpperPadding(0.05);
		this.axes[SHINOHARA].setLowerPadding(0.05);
		this.axes[SHINOHARA].addMarker(new ValueMarker(100, Color.BLACK));

		// ストキャスティクス軸を設定します。
		this.axes[SRV_FAST].setRangeType(RangeType.POSITIVE);
		this.axes[SRV_FAST].setFixedUpper(100.0);
		this.axes[SRV_FAST].setFixedLower(0.0);

		// TII
		this.axes[TII].setRangeType(RangeType.POSITIVE);
		this.axes[TII].setFixedUpper(100.0);
		this.axes[TII].setFixedLower(0.0);
		this.axes[TII].addMarker(new ValueMarker(50, Color.BLACK));

		// トリックス(Trix)軸を設定します。
		//this.axes[TRIX].setRangeType(RangeType.FULL);
		this.axes[TRIX].setUpperPadding(0.05);
		this.axes[TRIX].setLowerPadding(0.05);
		this.axes[TRIX].addMarker(new ValueMarker(0, Color.BLACK));

		// TSI
		this.axes[TSI].setFixedUpper(100.0);
		this.axes[TSI].setFixedLower(-100.0);
		this.axes[TSI].addMarker(new ValueMarker(0, Color.BLACK));

		// 究極のオシレータ軸を設定します。
		this.axes[ULTIMATE].setRangeType(RangeType.POSITIVE);
		this.axes[ULTIMATE].setUpperPadding(0.05);
		this.axes[ULTIMATE].setLowerPadding(0.05);

		// ウィリアムズAD軸を設定します。
		//this.axes[WAD].setRangeType(RangeType.FULL);
		this.axes[WAD].setUpperPadding(0.05);
		this.axes[WAD].setLowerPadding(0.05);

		// ウィリアムズ%R軸を設定します。
		this.axes[WR].setRangeType(RangeType.NEGATIVE);
		this.axes[WR].setFixedUpper(0.0);
		this.axes[WR].setFixedLower(-100.0);

		// A/Dライン
		//this.axes[AD].setRangeType(RangeType.FULL);
		this.axes[AD].setUpperPadding(0.05);
		this.axes[AD].setLowerPadding(0.05);

		// チャイキンズA/Dオシレーター
		this.axes[CHO].setUpperPadding(0.05);
		this.axes[CHO].setLowerPadding(0.05);

		// ボリューム・オシレータ(AVO)軸を設定します。
		this.axes[AVO].setUpperPadding(0.05);
		this.axes[AVO].setLowerPadding(0.05);
		this.axes[AVO].addMarker(new ValueMarker(0, Color.BLACK));

		// ボリューム・オシレータ(PVO)軸を設定します。
		this.axes[PVO].setUpperPadding(0.05);
		this.axes[PVO].setLowerPadding(0.05);
		this.axes[PVO].addMarker(new ValueMarker(0, Color.BLACK));

		// 出来高離率軸を設定します。
		//this.axes[VBIAS].setRangeType(RangeType.FULL);
		this.axes[VKAIRI].setUpperPadding(0.05);
		this.axes[VKAIRI].setLowerPadding(0.05);

		// ボリュームレシオ１軸を設定します。
		this.axes[VR1].setRangeType(RangeType.POSITIVE);
		this.axes[VR1].setUpperPadding(0.05);
		this.axes[VR1].setLowerPadding(0.05);

		// ボリュームレシオ２軸を設定します。
		this.axes[VR2].setRangeType(RangeType.POSITIVE);
		this.axes[VR2].setFixedUpper(100.0);
		this.axes[VR2].setFixedLower(0.0);
		this.axes[VR2].addMarker(new ValueMarker(50, Color.BLACK));

		// 出来高ROC 軸を設定します。
		//this.axes[VROC].setRangeType(RangeType.FULL);
		this.axes[VROC].setUpperPadding(0.05);
		this.axes[VROC].setLowerPadding(0.05);
		this.axes[VROC].addMarker(new ValueMarker(0, Color.BLACK));

		// V-RSI軸を設定します。
		this.axes[VRSI].setRangeType(RangeType.POSITIVE);
		this.axes[VRSI].setFixedUpper(100.0);
		this.axes[VRSI].setFixedLower(0.0);

		// 和光ボリュームレシオ軸を設定します。
		//this.axes[WVR].setRangeType(RangeType.FULL);
		this.axes[WVR].setFixedUpper(100.0);
		this.axes[WVR].setFixedLower(-100.0);
		this.axes[WVR].addMarker(new ValueMarker(0, Color.BLACK));

		// PVI
		this.axes[PVI].setUpperPadding(0.05);
		this.axes[PVI].setLowerPadding(0.05);
		this.axes[PVI].addMarker(new ValueMarker(0, Color.BLACK));

		// NVI
		this.axes[NVI].setUpperPadding(0.05);
		this.axes[NVI].setLowerPadding(0.05);
		this.axes[NVI].addMarker(new ValueMarker(0, Color.BLACK));

		// OBV軸を設定します。
		//this.axes[OBV].setRangeType(RangeType.FULL);
		this.axes[OBV].setUpperPadding(0.05);
		this.axes[OBV].setLowerPadding(0.05);
		//this.axes[OBV].addMarker(new ValueMarker(0, Color.BLACK));

		// PVT
		this.axes[PVT].setUpperPadding(0.05);
		this.axes[PVT].setLowerPadding(0.05);

		// 信用倍率軸を設定します。
		this.axes[RATIO].setRangeType(RangeType.POSITIVE);
		this.axes[RATIO].setUpperPadding(0.05);
		this.axes[RATIO].setLowerPadding(0.05);

		// 騰落価格軸を設定します。
		//this.axes[FLUCTUATIONS_PRICE].setRangeType(RangeType.FULL);
		this.axes[PRICE_PERFORMANCE].setUpperPadding(0.05);
		this.axes[PRICE_PERFORMANCE].setLowerPadding(0.05);

		// 騰落価格軸を設定します。
		//this.axes[FLUCTUATIONS].setRangeType(RangeType.FULL);
		axes[PERCENT_PERFORMANCE].setUpperPadding(0.05);
		axes[PERCENT_PERFORMANCE].setLowerPadding(0.05);
		axes[PERCENT_PERFORMANCE].addMarker(new ValueMarker(0, Color.BLACK));

		// 	デリゲーションイベントモデルでマウスイベントを処理します。

		addMouseListener(new MouseAdapter() {
			@Override
			public void mouseExited(final MouseEvent e) {
				processPosition(null);
				repaint();
			}
		});

		addMouseMotionListener(new MouseMotionAdapter() {
			@Override
			public void mouseMoved(final MouseEvent e) {
				processPosition(new Point(e.getX(), e.getY()));
				repaint();
			}
		});

		addComponentListener(new ComponentAdapter() {
			@Override
			public void componentResized(final ComponentEvent e) {
				processLayout();
			}
		});
	}

	/**
	 * トレース座標を処理します。
	 * このメソッドは必要により、XXX を呼出してイベント通知を行います。
	 * 
	 * @param point マウス座標
	 */
	private void processPosition(final Point point) {
		// ポジションを初期化します。
		this.mousePosition = null;
		this.datePosition = this.start + this.period - 1;
		this.traceAxis = null;
		this.traceArea = null;

		if (point != null) {
			if (this.vpArea != null && this.vpArea.contains(point)) {
				this.mousePosition = point;
				this.traceAxis = this.vpVolumeAxis;
				this.traceArea = this.vpArea;
			} else {
				for (int i = 0; i < this.areas.length; i++) {
					final Rectangle2D area = this.areas[i];
					if (area != null && area.contains(point)) {
						this.mousePosition = point;
						this.datePosition = this.start + Math.min((int) Math.floor((point.x - area.getMinX()) / this.periodWidth), this.period - 1);
						this.traceAxis = this.axes[i];
						this.traceArea = area;
						break;
					}				
				}
			}
		}

		// イベントを通知します。
		this.parent.sendPositionChanged(new ChartScreenEvent(this, this.mousePosition, this.start, this.period, this.datePosition, getVolumePriceHistogram(point)));
	}

	/**
	 * マウス座標から価格帯別出来高情報を返します。
	 * 価格帯別出来高指標がオフの場合は、<code>null</code> を返します。
	 * 
	 * @param point マウス座標
	 */
	private VolumePriceHistogram getVolumePriceHistogram(final Point point) {
		if (this.vpArea == null) {
			return null;
		}

		final VolumePriceHistogram vph = new VolumePriceHistogram();
		if (point != null && (vpArea.contains(point) || areas[PRICE].contains(point))) {
			final int n = (int) Math.ceil((axes[PRICE].java2DToValue(point.y, areas[PRICE]) - vpLower) / vpStep);
			if (n >= 0 && n < vp.length) {
				vph.upper = vpLower + n * vpStep;
				vph.lower = vpLower + (n - 1) * vpStep;
				vph.volume = vp[n];
			}
		}
		return vph;
	}

	/**
	 * 左右のマージンです。
	 */
	private static final int MARGIN = 6;

	/**
	 * 1期間の幅に対するローソク足の幅の比率です。
	 */
	private static final double ITEM_WIDTH_FACTOR = 4.5 / 7;	// 0.64285714285714285714285714285714

	/**
	 * 1期間の描画幅を保持します。
	 */
	private double periodWidth;

	/**
	 * ローソク足や棒足の幅を保持します。
	 */
	private double itemWidth;

	/**
	 * レイアウトを処理します。
	 */
	public void processLayout() {
		// チャートの行数を算出します。
		final TimeSeriesSettings settings = this.settings.timeSeries;
		int count = 1;
		if (settings.volumeType != VolumeType.NONE) count = count + 1;
		if (settings.aroon) count = count + 2;
		if (settings.atr) count = count + 1;
		if (settings.natr) count = count + 1;
		if (settings.bop) count = count + 1;
		if (settings.chv) count = count + 1;
		if (settings.cci) count = count + 1;
		if (settings.cmf) count = count + 1;
		if (settings.cmo) count = count + 1;
		if (settings.coppock) count = count + 1;
		if (settings.dpo) count = count + 1;
		if (settings.dmi) count = count + 1;
		if (settings.emv) count = count + 1;
		if (settings.hv) count = count + 1;
		if (settings.kairi) count = count + 1;
		if (settings.mi) count = count + 1;
		if (settings.mom) count = count + 1;
		if (settings.mfi) count = count + 1;
		if (settings.macd) count = count + 1;
		if (settings.pain) count = count + 1;
		if (settings.pcr) count = count + 1;
		if (settings.apo) count = count + 1;
		if (settings.ppo) count = count + 1;
		if (settings.psy) count = count + 1;
		if (settings.qstick) count = count + 1;
		if (settings.rci) count = count + 1;
		if (settings.roc) count = count + 1;
		if (settings.rsi) count = count + 1;
		if (settings.rvi) count = count + 1;
		if (settings.shinohara) count = count + 1;
		if (settings.srv) count = count + 1;
		if (settings.tii) count = count + 1;
		if (settings.trix) count = count + 1;
		if (settings.tsi) count = count + 1;
		if (settings.ultimate) count = count + 1;
		if (settings.wvr) count = count + 1;
		if (settings.wad) count = count + 1;
		if (settings.williamsR) count = count + 1;

		if (settings.ad) count = count + 1;
		if (settings.cho) count = count + 1;
		if (settings.avo) count = count + 1;
		if (settings.pvo) count = count + 1;
		if (settings.vkairi) count = count + 1;
		if (settings.vr1) count = count + 1;
		if (settings.vr2) count = count + 1;
		if (settings.vroc) count = count + 1;
		if (settings.vrsi) count = count + 1;
		if (settings.pvi) count = count + 1;
		if (settings.nvi) count = count + 1;
		if (settings.obv) count = count + 1;
		if (settings.pvt) count = count + 1;

		if (settings.ratio) count = count + 1;
		if (settings.price_performance) count = count + 1;
		if (settings.percent_performance) count = count + 1;

		final Graphics2D g2 = (Graphics2D) getGraphics();

		final int axisWidth = axes[PRICE].getSpace(g2);
		final int axisHeight = dateAxis.getSpace(g2);

		final int x = MARGIN + axisWidth;
		final int basePlotWidth = getWidth() - x - MARGIN;
		final int plotWidth = basePlotWidth;
		final int plotHeight = getHeight() - axisHeight;	// Plot 可能な領域の高さ

		final FontMetrics fm = g2.getFontMetrics(NumberAxis.FONT);
		final double marginHeight = fm.getAscent() * 0.5 + fm.getDescent();

		// チャート領域
		this.chartArea = new Rectangle2D.Double(x, marginHeight, plotWidth, plotHeight - marginHeight);
		final double chartHeight = Math.min(plotHeight / count, plotHeight * 0.20);	// 1チャートの高さを算出
		final double vpWidth = settings.vp ? chartArea.getWidth() * 0.20 : 0;

		final double _x = chartArea.getMinX();
		final double _width = chartArea.getWidth() - vpWidth;
		final double _height = chartHeight - marginHeight;

		// 価格領域
		double y = initChartArea(PRICE, true, _x, 0, _width, (plotHeight - chartHeight * (count - 1)) - marginHeight, marginHeight);

		// 価格帯別出来高領域
		if (settings.vp)
			this.vpArea = new Rectangle2D.Double(areas[PRICE].getMaxX() + 4, areas[PRICE].getMinY(), vpWidth - 4, areas[PRICE].getHeight());
		else
			this.vpArea = null;

		// 出来高領域
		y = initChartArea(VOLUME, settings.volumeType != VolumeType.NONE, _x, y, _width, _height, marginHeight);

		// アルーンおよびアルーンオシレータ領域
		y = initChartArea(AROON_INDICATOR, settings.aroon, _x, y, _width, _height, marginHeight);
		y = initChartArea(AROON_OSCILLATOR, settings.aroon, _x, y, _width, _height, marginHeight);
		// ATR
		y = initChartArea(ATR, settings.atr, _x, y, _width, _height, marginHeight);
		// NATR
		y = initChartArea(NATR, settings.natr, _x, y, _width, _height, marginHeight);
		// BMP
		y = initChartArea(BOP, settings.bop, _x, y, _width, _height, marginHeight);
		// チャイキンズ・ボラティリティ
		y = initChartArea(CHV, settings.chv, _x, y, _width, _height, marginHeight);
		// CCI
		y = initChartArea(CCI, settings.cci, _x, y, _width, _height, marginHeight);
		// CMF
		y = initChartArea(CMF, settings.cmf, _x, y, _width, _height, marginHeight);
		// CMO
		y = initChartArea(CMO, settings.cmo, _x, y, _width, _height, marginHeight);
		// コポック買い指標
		y = initChartArea(COPPOCK, settings.coppock, _x, y, _width, _height, marginHeight);
		// DPO
		y = initChartArea(DPO, settings.dpo, _x, y, _width, _height, marginHeight);
		// DMI
		y = initChartArea(DMI, settings.dmi, _x, y, _width, _height, marginHeight);
		// EMV
		y = initChartArea(EMV, settings.emv, _x, y, _width, _height, marginHeight);
		// ヒストリカル・ボラティリティ
		y = initChartArea(HV, settings.hv, _x, y, _width, _height, marginHeight);
		// 移動平均乖離率領域
		y = initChartArea(KAIRI, settings.kairi, _x, y, _width, _height, marginHeight);
		// MI
		y = initChartArea(MI, settings.mi, _x, y, _width, _height, marginHeight);
		// モメンタム
		y = initChartArea(MOM, settings.mom, _x, y, _width, _height, marginHeight);
		// MFI
		y = initChartArea(MFI, settings.mfi, _x, y, _width, _height, marginHeight);
		// MACD
		y = initChartArea(MACD, settings.macd, _x, y, _width, _height, marginHeight);
		// PAIN
		y = initChartArea(PAIN, settings.pain, _x, y, _width, _height, marginHeight);
		// PCR
		y = initChartArea(PCR, settings.pcr, _x, y, _width, _height, marginHeight);
		// プライス・オシレータ(APO)領域
		y = initChartArea(APO, settings.apo, _x, y, _width, _height, marginHeight);
		// プライス・オシレータ(PPO)領域
		y = initChartArea(PPO, settings.ppo, _x, y, _width, _height, marginHeight);
		// サイコロジカルライン
		y = initChartArea(PSY, settings.psy, _x, y, _width, _height, marginHeight);
		// QSTICK
		y = initChartArea(QSTICK, settings.qstick, _x, y, _width, _height, marginHeight);
		// RCI
		y = initChartArea(RCI, settings.rci, _x, y, _width, _height, marginHeight);
		// ROC
		y = initChartArea(ROC, settings.roc, _x, y, _width, _height, marginHeight);
		// RSI
		y = initChartArea(RSI, settings.rsi, _x, y, _width, _height, marginHeight);
		// RVI
		y = initChartArea(RVI, settings.rvi, _x, y, _width, _height, marginHeight);
		// 強弱レシオ(篠原レシオ)
		y = initChartArea(SHINOHARA, settings.shinohara, _x, y, _width, _height, marginHeight);
		// ストキャスティクス
		y = initChartArea(SRV_FAST, settings.srv, _x, y, _width, _height, marginHeight);
		// TII
		y = initChartArea(TII, settings.tii, _x, y, _width, _height, marginHeight);
		// トリックス(Trix)
		y = initChartArea(TRIX, settings.trix, _x, y, _width, _height, marginHeight);
		// TSI
		y = initChartArea(TSI, settings.tsi, _x, y, _width, _height, marginHeight);
		// 究極のオシレーター
		y = initChartArea(ULTIMATE, settings.ultimate, _x, y, _width, _height, marginHeight);
		// ウィリアムズAD
		y = initChartArea(WAD, settings.wad, _x, y, _width, _height, marginHeight);
		// ウィリアムズ%R
		y = initChartArea(WR, settings.williamsR, _x, y, _width, _height, marginHeight);

		// A/Dライン
		y = initChartArea(AD, settings.ad, _x, y, _width, _height, marginHeight);
		// チャイキンズA/Dオシレーター
		y = initChartArea(CHO, settings.cho, _x, y, _width, _height, marginHeight);
		// ボリューム・オシレータ(AVO)領域
		y = initChartArea(AVO, settings.avo, _x, y, _width, _height, marginHeight);
		// ボリューム・オシレータ(PVO)領域
		y = initChartArea(PVO, settings.pvo, _x, y, _width, _height, marginHeight);
		// 出来高乖離率領域
		y = initChartArea(VKAIRI, settings.vkairi, _x, y, _width, _height, marginHeight);
		// ボリュームレシオ1
		y = initChartArea(VR1, settings.vr1, _x, y, _width, _height, marginHeight);
		// ボリュームレシオ2
		y = initChartArea(VR2, settings.vr2, _x, y, _width, _height, marginHeight);
		// 出来高ROC
		y = initChartArea(VROC, settings.vroc, _x, y, _width, _height, marginHeight);
		// V-RSI
		y = initChartArea(VRSI, settings.vrsi, _x, y, _width, _height, marginHeight);
		// 和光ボリュームレシオ
		y = initChartArea(WVR, settings.wvr, _x, y, _width, _height, marginHeight);
		// PVI
		y = initChartArea(PVI, settings.pvi, _x, y, _width, _height, marginHeight);
		// NVI
		y = initChartArea(NVI, settings.nvi, _x, y, _width, _height, marginHeight);
		// OBV
		y = initChartArea(OBV, settings.obv, _x, y, _width, _height, marginHeight);
		// PVT
		y = initChartArea(PVT, settings.pvt, _x, y, _width, _height, marginHeight);

		// 信用倍率
		y = initChartArea(RATIO, settings.ratio, _x, y, _width, _height, marginHeight);
		// 騰落価格領域
		y = initChartArea(PRICE_PERFORMANCE, settings.price_performance, _x, y, _width, _height, marginHeight);
		// 騰落率領域
		y = initChartArea(PERCENT_PERFORMANCE, settings.percent_performance, _x, y, _width, _height, marginHeight);

		// ラベルの最大個数を算出します。
		if (this.dataset != null) {
			this.dateAxis.refreshTicks(g2, areas[PRICE]);
		}
		for (int i = 0; i < areas.length; i++) {
			if (areas[i] != null) {
				axes[i].refreshTicks(g2, areas[i]);
			}
		}

		this.periodWidth = (areas[PRICE].getWidth() - 1) / this.period;
		this.itemWidth = this.periodWidth * ITEM_WIDTH_FACTOR;// XXX - 画面の大きさでITEM_WIDTH_FACTORの値を変えるとよいね

		this.screenCache = null;
	}

	private double initChartArea(final int index, final boolean setup, final double x, final double y, final double width, final double height, final double marginTop) {
		if (setup) {
			areas[index] = new Rectangle2D.Double(x, y + marginTop, width, height);
			return areas[index].getMaxY();
		}

		areas[index] = null;
		return y;
	}

	@Override
	public void switchDataset(final ChartEvent e) {
		// メンバーへ保存します。
		this.dataset = (TimeSeriesChartDataset) e.getDataset();
		if (!e.isIgnoreStart())
			this.start = e.getStart();
		this.period = e.getPeriod();
		this.settings = e.getSettings();

		// 日付軸を処理します。
		this.dateAxis.prepare(e.getType(), this.period);

		// 価格軸の範囲に0を含めるかどうか処理します。
		final TimeSeriesSettings settings = this.settings.timeSeries;
		if (settings.fixed)
			axes[PRICE].setFixedLower(0.0);
		else
			axes[PRICE].setFixedLower(null);

		// 価格軸の目盛り計算方法を設定します
		this.axes[PRICE].setScale(settings.scale);

		// 各チャートの説明を初期化します。
		StringBuilder description;

		// 価格チャートの説明を初期化します。
		description = new StringBuilder();
		addDescription(description, settings.chartType.toString());
		if (settings.avgprice)
			addDescription(description, StringManager.getString("avgprice"));
		if (settings.mp)
			addDescription(description, StringManager.getString("mp"));
		if (settings.tp)
			addDescription(description, StringManager.getString("tp"));
		if (settings.wc)
			addDescription(description, StringManager.getString("wc"));
		if (settings.ma)
			addDescription(description, StringManager.getString("ma"));
		if (settings.bandType != BandType.NONE)
			addDescription(description, settings.bandType.getLabel());
		this.priceDescription = description.toString();

		// 出来高チャートの説明を初期化します。
		description = new StringBuilder();
		addDescription(description, settings.volumeType.toString());
		this.volumeDescription = description.toString();

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

		// 各指標の売りシグ・買いシグ(インターバルマーカー)を初期化します。
		for (final NumberAxis axis : this.axes)
			axis.removeIntervalMarkers();

		if (settings.signalMarker) {
			// 移動平均乖離率
			this.axes[KAIRI].addMarker(new ValueMarker(0, Color.BLACK));
			this.axes[KAIRI].addMarker(new IntervalMarker(null, settings.biasUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[KAIRI].addMarker(new IntervalMarker(settings.biasLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// CCI
			this.axes[CCI].addMarker(new IntervalMarker(null, settings.cciUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[CCI].addMarker(new IntervalMarker(settings.cciLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// CMO
			this.axes[CMO].addMarker(new IntervalMarker(null, settings.cmo_upper_signal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[CMO].addMarker(new IntervalMarker(settings.cmo_lower_signal, null, ChartColor.VERY_LIGHT_GRAY));
			// MFI
			this.axes[MFI].addMarker(new IntervalMarker(null, settings.mfiUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[MFI].addMarker(new IntervalMarker(settings.mfiLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// PCR
			axes[PCR].addMarker(new IntervalMarker(null, settings.pcr_upper_signal, ChartColor.VERY_LIGHT_GRAY));
			axes[PCR].addMarker(new IntervalMarker(settings.pcr_lower_signal, null, ChartColor.VERY_LIGHT_GRAY));
			// サイコロジカル・ライン
			this.axes[PSY].addMarker(new IntervalMarker(null, settings.psy_upper_signal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[PSY].addMarker(new IntervalMarker(settings.psy_lower_signal, null, ChartColor.VERY_LIGHT_GRAY));
			// RSI
			this.axes[RSI].addMarker(new IntervalMarker(null, settings.rsiUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[RSI].addMarker(new IntervalMarker(settings.rsiLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// RVI
			axes[RVI].addMarker(new IntervalMarker(null, settings.rvi_upper_signal, ChartColor.VERY_LIGHT_GRAY));
			axes[RVI].addMarker(new IntervalMarker(settings.rvi_lower_signal, null, ChartColor.VERY_LIGHT_GRAY));
			// RCI
			this.axes[RCI].addMarker(new IntervalMarker(null, settings.rciUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[RCI].addMarker(new IntervalMarker(settings.rciLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// ストキャスティクス
			this.axes[SRV_FAST].addMarker(new IntervalMarker(null, settings.srvUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[SRV_FAST].addMarker(new IntervalMarker(settings.srvLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// TII
			this.axes[TII].addMarker(new IntervalMarker(null, settings.tii_upper_signal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[TII].addMarker(new IntervalMarker(settings.tii_lower_signal, null, ChartColor.VERY_LIGHT_GRAY));
			// TSI
			this.axes[TSI].addMarker(new IntervalMarker(null, settings.tsi_upper_signal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[TSI].addMarker(new IntervalMarker(settings.tsi_lower_signal, null, ChartColor.VERY_LIGHT_GRAY));
			// ウィリアムズ%R
			this.axes[WR].addMarker(new IntervalMarker(null, settings.williamsRUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[WR].addMarker(new IntervalMarker(settings.williamsRLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// 出来高乖離率
			this.axes[VKAIRI].addMarker(new ValueMarker(0, Color.BLACK));
			// ボリュームレシオ1
			this.axes[VR1].addMarker(new IntervalMarker(null, settings.vr1UpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[VR1].addMarker(new IntervalMarker(settings.vr1LowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// ボリュームレシオ2
			this.axes[VR2].addMarker(new IntervalMarker(null, settings.vr2UpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[VR2].addMarker(new IntervalMarker(settings.vr2LowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// VRSI
			this.axes[VRSI].addMarker(new IntervalMarker(null, settings.vrsiUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[VRSI].addMarker(new IntervalMarker(settings.vrsiLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// 和光ボリュームレシオ
			this.axes[WVR].addMarker(new IntervalMarker(null, settings.wvrUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[WVR].addMarker(new IntervalMarker(settings.wvrLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
			// 信用倍率
			this.axes[RATIO].addMarker(new IntervalMarker(null, settings.ratioUpperSignal, ChartColor.VERY_LIGHT_GRAY));
			this.axes[RATIO].addMarker(new IntervalMarker(settings.ratioLowerSignal, null, ChartColor.VERY_LIGHT_GRAY));
		}

		// ------------------------------ 最小値/最大値

		// 各軸の最大値/最小値を事前処理します。
		// 固定範囲のチャートについては、既に事前処理できているので以下の処理ブロックでは処理を行いません。
		if (this.dataset != null) {
			// 価格チャート
			this.axes[PRICE].prepare(new Number[][] {
				this.dataset.techHigh,
				this.dataset.techLow,
				this.dataset.ma1,
				this.dataset.ma2,
				this.dataset.lr1_upper2,
				this.dataset.lr1_lower2,
				this.dataset.lr2_upper2,
				this.dataset.lr2_lower2,
				this.dataset.zigzag,
				this.dataset.donchian_upper,
				this.dataset.donchian_lower,
				this.dataset.hma,
				this.dataset.lma,
				this.dataset.envelope_upper2,
				this.dataset.envelope_lower2,
				this.dataset.bb_upper2,
				this.dataset.bb_lower2,
				this.dataset.pivot_hbop,
				this.dataset.pivot_lbop,
				this.dataset.vi_upper,
				this.dataset.vi_lower,
				this.dataset.sar,
				this.dataset.kijun,
				this.dataset.tenkan,
				this.dataset.senkou1,
				this.dataset.senkou2,
				this.dataset.chikou,
				this.dataset.mama,
				this.dataset.fama,
				this.dataset.vidya
			});

			// 価格帯別出来高
			this.vpPriceAxis.prepare(new Number[][] {
				this.dataset.high, this.dataset.low, this.dataset.close
			});

			// 出来高チャート
			if (this.dataset.volume != null || this.dataset.sold != null || this.dataset.bought != null || this.dataset.vma1 != null || this.dataset.vma2 != null) {
				this.axes[VOLUME].prepare(new Number[][] {
					settings.volumeType != VolumeType.NONE ? this.dataset.volume : null,
					settings.volumeType == VolumeType.MARGIN ? this.dataset.sold : null,
					settings.volumeType == VolumeType.MARGIN ? this.dataset.bought : null,
					this.dataset.vma1,
					this.dataset.vma2
				});
			}
			// ATR
			if (dataset.atr != null) axes[ATR].prepare(new Number[][] { dataset.atr });
			// NATR
			if (dataset.natr != null) axes[NATR].prepare(new Number[][] { dataset.natr });
			// BMP
			//if (dataset.bmp != null) axes[BMP].prepare(new Number[][] { dataset.bmp });
			// チャイキンズ・ボラティリティ
			if (this.dataset.chv != null)
				this.axes[CHV].prepare(new Number[][] { this.dataset.chv });
			// CCI
			if (this.dataset.cci != null) this.axes[CCI].prepare(new Number[][] { this.dataset.cci });
			// CMF
			if (this.dataset.cmf != null) this.axes[CMF].prepare(new Number[][] { this.dataset.cmf });
			// CMO
			if (this.dataset.cmo != null) this.axes[CMO].prepare(new Number[][] { this.dataset.cmo });
			// コポック買い指標
			if (this.dataset.coppock != null)
				this.axes[COPPOCK].prepare(new Number[][] { this.dataset.coppock });
			// DPO
			if (this.dataset.dpo != null)
				this.axes[DPO].prepare(new Number[][] { this.dataset.dpo });
			// DMI
			if (this.dataset.pdi != null || this.dataset.mdi != null || this.dataset.adx != null)
				this.axes[DMI].prepare(new Number[][] {
					this.dataset.pdi, this.dataset.mdi, this.dataset.adx
				});
			// EMV
			if (dataset.emv != null) axes[EMV].prepare(new Number[][] { dataset.emv });
			// ヒストリカル・ボラティリティ
			if (this.dataset.hv != null)
				this.axes[HV].prepare(new Number[][] { this.dataset.hv });
			// 移動平均乖離率
			if (this.dataset.kairi != null)
				this.axes[KAIRI].prepare(new Number[][] { this.dataset.kairi });
			// MI
			if (this.dataset.mi != null)
				this.axes[MI].prepare(new Number[][] { this.dataset.mi });
			// モメンタム
			if (this.dataset.mom != null) this.axes[MOM].prepare(new Number[][] { this.dataset.mom });
			// PAIN
			if (this.dataset.pain != null) this.axes[PAIN].prepare(new Number[][] { this.dataset.pain });
			// MACD
			if (this.dataset.macd != null || this.dataset.macdSignal != null) {
				this.axes[MACD].prepare(new Number[][] {
					this.dataset.macd, this.dataset.macdSignal, this.dataset.macdHistogram
				});
			}
			// プライス・オシレータ(APO)
			if (this.dataset.apo != null) this.axes[APO].prepare(new Number[][] { this.dataset.apo });
			// プライス・オシレータ(PPO)
			if (this.dataset.ppo != null) this.axes[PPO].prepare(new Number[][] { this.dataset.ppo });
			// QSTICK
			if (this.dataset.qstick != null) this.axes[QSTICK].prepare(new Number[][] { this.dataset.qstick });
			// ROC
			if (this.dataset.roc != null) this.axes[ROC].prepare(new Number[][] { this.dataset.roc });
			// 強弱レシオ(篠原レシオ)
			if (dataset.shinohara_a != null || dataset.shinohara_b != null)
				axes[SHINOHARA].prepare(new Number[][] { dataset.shinohara_a, dataset.shinohara_b });
			// トリックス(Trix)
			if (this.dataset.trix != null || this.dataset.trixSignal != null) {
				this.axes[TRIX].prepare(new Number[][] { this.dataset.trix, this.dataset.trixSignal });
			}
			// ウィリアムズAD
			if (this.dataset.wad != null) this.axes[WAD].prepare(new Number[][] { this.dataset.wad });
			// 究極のオシレーター
			if (this.dataset.ultimate != null) this.axes[ULTIMATE].prepare(new Number[][] { this.dataset.ultimate });

			// A/Dライン
			if (this.dataset.ad != null) this.axes[AD].prepare(new Number[][] { this.dataset.ad });
			// チャイキンズA/Dオシレーター
			if (this.dataset.cho != null) this.axes[CHO].prepare(new Number[][] { this.dataset.cho });
			// 出来高乖離率
			if (this.dataset.vkairi != null) this.axes[VKAIRI].prepare(new Number[][] { this.dataset.vkairi });
			// ボリューム・オシレータ(AVO)
			if (this.dataset.avo != null) this.axes[AVO].prepare(new Number[][] { this.dataset.avo });
			// ボリューム・オシレータ(PVO)
			if (this.dataset.pvo != null) this.axes[PVO].prepare(new Number[][] { this.dataset.pvo });
			// 出来高ROC
			if (this.dataset.vroc != null) this.axes[VROC].prepare(new Number[][] { this.dataset.vroc });
			// ボリュームレシオ1
			if (this.dataset.vr1 != null) this.axes[VR1].prepare(new Number[][] { this.dataset.vr1 });
			// PVI
			if (this.dataset.pvi != null) this.axes[PVI].prepare(new Number[][] { this.dataset.pvi, this.dataset.pvi_ma1, this.dataset.pvi_ma2 });
			// NVI
			if (this.dataset.nvi != null) this.axes[NVI].prepare(new Number[][] { this.dataset.nvi, this.dataset.nvi_ma1, this.dataset.nvi_ma2 });
			// OBV
			if (this.dataset.obv != null) this.axes[OBV].prepare(new Number[][] { this.dataset.obv });
			// PVT
			if (this.dataset.pvt != null) this.axes[PVT].prepare(new Number[][] { this.dataset.pvt });

			// 信用倍率
			if (this.dataset.ratio != null) this.axes[RATIO].prepare(new Number[][] { this.dataset.ratio });
			// 騰落価格
			if (this.dataset.price_performance != null) this.axes[PRICE_PERFORMANCE].prepare(new Number[][] { this.dataset.price_performance });
			// 騰落率
			if (this.dataset.percent_performance != null) this.axes[PERCENT_PERFORMANCE].prepare(new Number[][] { this.dataset.percent_performance });
		}

		processLayout();
		update();
	}

	private void adjustTicks() {
		if (this.dataset != null) {
			for (int i = 0; i < this.areas.length; i++) {
				if (this.areas[i] == null)
					continue;

				final NumberAxis axis = this.axes[i];
				axis.autoAdjustRange(this.start, this.period);
				axis.refreshTicks();
			}
			if (this.vpArea != null) {
				this.vpPriceAxis.autoAdjustRange(this.start, this.period);
				calculateVolumePriceHistogram();
				this.vpVolumeAxis.refreshTicks();
			}
		}
	}

	@Override
	public void setStart(final int start) {
		this.start = start;
		update();
	}

	private void update() {
		adjustTicks();
		processPosition(null);
		this.screenCache = null;
		repaint();
	}

	/**
	 * 画面の基礎イメージを保持します。
	 */
	private Image screenCache;

	/**
	 * 画面を描画します。
	 */
	@Override
	public void draw(final Graphics2D g2) {
		if (this.screenCache == null) {
			this.screenCache = createImage();
		}

		g2.drawImage(this.screenCache, 0, 0, this);

		if (this.settings.trace) {
			drawAxisTrace(g2);
		}
	}

	/**
	 * 画面の基礎イメージを作成して返します。
	 * 
	 * @return 画面の基礎イメージ
	 */
	private Image createImage() {
		final Image image = createImage(getWidth(), getHeight());
		final Graphics2D g2 = (Graphics2D) image.getGraphics();
		setRenderingHints(g2);

		// 縦目盛りと縦グリッド線を描画します。
		for (int i = 0; i < this.areas.length; i++)
			if (this.areas[i] != null)
				this.axes[i].draw(g2, this.areas[i]);

		if (this.vpArea != null) {
			this.vpVolumeAxis.draw(g2, this.vpArea);
			this.axes[PRICE].drawGlidline(g2, this.vpArea);
		}

		// 横目盛り(日付)と横グリッド線を描画します。
		this.dateAxis.draw(g2, this.chartArea,
			this.areas,
			this.periodWidth,
			this.dataset == null ? null : this.dataset.date,
			this.start, this.period);

		// チャートを描画します。
		if (this.dataset != null)
			drawChart(g2);

		g2.dispose();

		return image;
	}

	/**
	 * トレ－ス目盛りを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 */
	private void drawAxisTrace(final Graphics2D g2) {
		if (this.mousePosition != null) {
			final Rectangle2D priceArea = this.areas[PRICE];

			// (横軸に対する)縦の線
			if (this.traceArea.equals(this.vpArea)) {
				this.vpVolumeAxis.drawAxisTrace(g2, this.vpArea, this.mousePosition.x, 2);
			} else if (this.dataset != null) {
				final double x = priceArea.getMinX() + ((this.datePosition - this.start) * this.periodWidth) + (this.periodWidth * 0.5);
				this.dateAxis.drawAxisTrace(g2, this.chartArea, x, 2, this.dataset.date[this.datePosition]);
			}

			// 横
			if (this.vpArea != null && (this.traceArea.equals(this.vpArea) || this.traceArea.equals(this.areas[PRICE]))) {
				this.axes[PRICE].drawAxisTrace(g2, new Rectangle2D.Double(priceArea.getMinX(), priceArea.getMinY(), this.vpArea.getMaxX() - priceArea.getMinX(), priceArea.getHeight()), this.mousePosition.y, 2);
			} else {
				this.traceAxis.drawAxisTrace(g2, this.traceArea, this.mousePosition.y, 2);
			}
		}
	}

	private static final Color LINE_COLOR = ChartColor.BLUE;

	/**
	 * チャートを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 */
	private void drawChart(final Graphics2D g2) {
		final Shape saved = g2.getClip();
		final TimeSeriesSettings settings = this.settings.timeSeries;

		// 価格チャートを描画します。
		if (this.areas[PRICE] != null) {
			final Rectangle2D priceArea = this.areas[PRICE];
			final NumberAxis priceAxis = this.axes[PRICE];
			g2.setClip(priceArea);

			// 一目均衡表の雲を描画します。
			if (this.dataset.senkou1 != null && this.dataset.senkou2 != null)
				drawDifference(g2, priceArea, priceAxis,
					this.dataset.senkou1, this.dataset.senkou2,
					ChartColor.VERY_LIGHT_GRAY, ChartColor.GRAY, ChartColor.WHITE, ChartColor.LIGHT_GRAY);

			// 売買シグナルを描画します。
			if (settings.signalMarker) {
				// ローソク足パターン
				if (settings.bullishLongWhiteDay) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishLongWhiteDay);
				if (settings.bullishBeltHold) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishBeltHold);
				if (settings.bullishHammer) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishHammer);
				if (settings.bullishDragonflyDoji) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishDragonflyDoji);
				if (settings.bullishLongLeggedDoji) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishLongLeggedDoji);
				if (settings.bullishInvertedHammer) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishInvertedHammer);
				if (settings.bullishHarami) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishHarami);
				if (settings.bullishHaramiCross) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishHaramiCross);
				if (settings.bullishEngulfing) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishEngulfing);
				if (settings.bullishGravestoneDoji) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishGravestoneDoji);
				if (settings.bullishMeetingLines) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishMeetingLines);
				if (settings.bullishDojiStar) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishDojiStar);
				if (settings.bullishHomingPigeon) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishHomingPigeon);
				if (settings.bullishMatchingLow) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishMatchingLow);
				if (settings.bullishTweezerBottom) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishTweezerBottom);
				if (settings.bullishKicking) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishKicking);
				if (settings.bullishPiercingLine) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishPiercingLine);
				if (settings.bullishSeparatingLines) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishSeparatingLines);
				if (settings.bullishTriStar) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishTriStar);
				if (settings.bullishThreeStarsInTheSouth) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishThreeStarsInTheSouth);
				if (settings.bullishUniqueThreeRiverBottom) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishUniqueThreeRiverBottom);
				if (settings.bullishStickSandwich) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishStickSandwich);
				if (settings.bullishAbandonedBaby) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishAbandonedBaby);
				if (settings.bullishMorningDojiStar) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishMorningDojiStar);
				if (settings.bullishMorningStar) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishMorningStar);
				if (settings.bullishThreeOutsideUp) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishThreeOutsideUp);
				if (settings.bullishThreeInsideUp) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishThreeInsideUp);
				if (settings.bullishThreeWhiteSoldiers) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishThreeWhiteSoldiers);
				if (settings.bullishUpsideTasukiGap) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishUpsideTasukiGap);
				if (settings.bullishUpsideGapThreeMethods) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishUpsideGapThreeMethods);
				if (settings.bullishSideBySideWhiteLines) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishSideBySideWhiteLines);
				if (settings.bullishConcealingBabySwallow) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishConcealingBabySwallow);
				if (settings.bullishThreeLineStrike) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishThreeLineStrike);
				if (settings.bullishThreeGaps) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishThreeGaps);
				if (settings.bullishBreakaway) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishBreakaway);
				if (settings.bullishLadderBottom) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishLadderBottom);
				if (settings.bullishRisingThreeMethods) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishRisingThreeMethods);
				if (settings.bullishMatHold) drawSignal(g2, CandlePattern.BULLISH, dataset.bullishMatHold);

				if (settings.bearishLongBlackDay) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishLongBlackDay);
				if (settings.bearishBeltHold) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishBeltHold);
				if (settings.bearishHangingMan) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishHangingMan);
				if (settings.bearishDragonflyDoji) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishDragonflyDoji);
				if (settings.bearishLongLeggedDoji) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishLongLeggedDoji);
				if (settings.bearishShootingStar) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishShootingStar);
				if (settings.bearishHarami) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishHarami);
				if (settings.bearishHaramiCross) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishHaramiCross);
				if (settings.bearishEngulfing) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishEngulfing);
				if (settings.bearishGravestoneDoji) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishGravestoneDoji);
				if (settings.bearishMeetingLines) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishMeetingLines);
				if (settings.bearishDojiStar) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishDojiStar);
				if (settings.bearishDescendingHawk) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishDescendingHawk);
				if (settings.bearishMatchingHigh) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishMatchingHigh);
				if (settings.bearishTweezerTop) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishTweezerTop);
				if (settings.bearishKicking) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishKicking);
				if (settings.bearishDarkCloudCover) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishDarkCloudCover);
				if (settings.bearishSeparatingLines) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishSeparatingLines);
				if (settings.bearishThrusting) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishThrusting);
				if (settings.bearishOnNeck) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishOnNeck);
				if (settings.bearishInNeck) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishInNeck);
				if (settings.bearishTriStar) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishTriStar);
				if (settings.bearishTwoCrows) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishTwoCrows);
				if (settings.bearishAdvanceBlock) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishAdvanceBlock);
				if (settings.bearishDeliberation) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishDeliberation);
				if (settings.bearishAbandonedBaby) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishAbandonedBaby);
				if (settings.bearishEveningDojiStar) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishEveningDojiStar);
				if (settings.bearishEveningStar) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishEveningStar);
				if (settings.bearishThreeOutsideDown) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishThreeOutsideDown);
				if (settings.bearishThreeInsideDown) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishThreeInsideDown);
				if (settings.bearishUpsideGapTwoCrows) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishUpsideGapTwoCrows);
				if (settings.bearishThreeBlackCrows) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishThreeBlackCrows);
				if (settings.bearishIdenticalThreeCrows) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishIdenticalThreeCrows);
				if (settings.bearishDownsideTasukiGap) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishDownsideTasukiGap);
				if (settings.bearishDownsideGapThreeMethods) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishDownsideGapThreeMethods);
				if (settings.bearishSideBySideWhiteLines) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishSideBySideWhiteLines);
				if (settings.bearishThreeLineStrike) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishThreeLineStrike);
				if (settings.bearishThreeGaps) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishThreeGaps);
				if (settings.bearishBreakaway) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishBreakaway);
				if (settings.bearishFallingThreeMethods) drawSignal(g2, CandlePattern.BEARISH, dataset.bearishFallingThreeMethods);

				// 移動平均線(GC・DC)
				if (settings.ma)
					drawLineCrossMarker(g2, priceArea, priceAxis, dataset.ma1, dataset.ma2);

				// エンベロープ
				if (settings.bandType == BandType.ENVELOPE) {
					drawSignal(g2, priceArea, priceAxis,
						this.dataset.high, this.dataset.low,
						this.dataset.envelope_upper2, this.dataset.envelope_lower2);
				// チャネルシステム
				} else if (settings.bandType == BandType.HIGH_LOW_MOVING_AVERAGE) {
					drawSignal(g2, priceArea, priceAxis,
						this.dataset.close, this.dataset.close,
						this.dataset.hma, this.dataset.lma);
				// ドンチャンズ
				} else if (settings.bandType == BandType.DONCHIAN) {
					drawSignal(g2, priceArea, priceAxis,
						this.dataset.close, this.dataset.close,
						this.dataset.donchian_upper, this.dataset.donchian_lower);
				// ボリンジャーバンド
				} else if (settings.bandType == BandType.BOLLINGER_BANDS) {
					drawSignal(g2, priceArea, priceAxis,
						this.dataset.high, this.dataset.low,
//						this.dataset.close, this.dataset.close,
						this.dataset.bb_upper2, this.dataset.bb_lower2);
				} else if (settings.bandType == BandType.VIX) {
					drawSignal(g2, priceArea, priceAxis,
						this.dataset.high, this.dataset.low,
						this.dataset.vi_upper, this.dataset.vi_lower);
				} else if (settings.bandType == BandType.MESA) {
					drawLineCrossMarker(g2, priceArea, priceAxis, this.dataset.mama, this.dataset.fama);
				}
			}

			// 4本値を描画します。
			if (settings.chartType == PriceChartType.LINE) {
				g2.setColor(LINE_COLOR);
				drawLine(g2, priceArea, priceAxis, this.dataset.techClose);
			} else if (settings.chartType == PriceChartType.BAR)
				drawBar(g2);
			else if (settings.chartType == PriceChartType.BOH)
				drawBoh(g2);
			else if (settings.chartType == PriceChartType.CANDLESTICK || settings.chartType == PriceChartType.HEIKIN)
				drawCandlestick(g2);
			else
				throw new RuntimeException();

			// 時系列カギ足
			if (settings.kagi && dataset.kagi != null)
				drawKagi(g2);

			// トレンド系

			// 線形回帰トレンド
			if (settings.lr) {
				if (this.dataset.lr1_upper2 != null) {
					g2.setColor(settings.lr2UpperColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr1_upper2);
				}
				if (this.dataset.lr1_upper1 != null) {
					g2.setColor(settings.lr1UpperColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr1_upper1);
				}
				if (this.dataset.lr1 != null) {
					g2.setColor(settings.lrColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr1);
				}
				if (this.dataset.lr1_lower1 != null) {
					g2.setColor(settings.lr1LowerColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr1_lower1);
				}
				if (this.dataset.lr1_lower2 != null) {
					g2.setColor(settings.lr2LowerColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr1_lower2);
				}
				if (this.dataset.lr2_upper2 != null) {
					g2.setColor(settings.lr2UpperColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr2_upper2);
				}
				if (this.dataset.lr2_upper1 != null) {
					g2.setColor(settings.lr1UpperColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr2_upper1);
				}
				if (this.dataset.lr2 != null) {
					g2.setColor(settings.lrColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr2);
				}
				if (this.dataset.lr2_lower1 != null) {
					g2.setColor(settings.lr1LowerColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr2_lower1);
				}
				if (this.dataset.lr2_lower2 != null) {
					g2.setColor(settings.lr2LowerColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.lr2_lower2);
				}
			}
			// 移動平均線
			if (settings.ma) {
				if (this.dataset.ma1 != null) {
					g2.setColor(settings.ma1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.ma1);
				}
				if (this.dataset.ma2 != null) {
					g2.setColor(settings.ma2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.ma2);
				}
			}

			if (settings.avgprice) {
				if (this.dataset.avgprice != null) {
					g2.setColor(LINE_COLOR);
					drawLine(g2, priceArea, priceAxis, this.dataset.avgprice);
				}
			}
			if (settings.mp) {
				if (this.dataset.mp != null) {
					g2.setColor(LINE_COLOR);
					drawLine(g2, priceArea, priceAxis, this.dataset.mp);
				}
			}
			// ティピカル・プライス
			if (settings.tp) {
				if (this.dataset.tp != null) {
					g2.setColor(LINE_COLOR);
					drawLine(g2, priceArea, priceAxis, this.dataset.tp);
				}
			}
			// 加重終値
			if (settings.wc) {
				if (this.dataset.wc != null) {
					g2.setColor(LINE_COLOR);
					drawLine(g2, priceArea, priceAxis, this.dataset.wc);
				}
			}

			// ZigZag
			if (settings.zigzag && dataset.zigzag != null)
				drawLine(g2, priceArea, priceAxis, dataset.zigzag, this.settings.updownLineColors);

			// エンベロープ
			if (settings.bandType == BandType.ENVELOPE) {
				if (this.dataset.envelope_upper2 != null) {
					g2.setColor(settings.envelopeUpper2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.envelope_upper2);
				}
				if (this.dataset.envelope_upper1 != null) {
					g2.setColor(settings.envelopeUpper1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.envelope_upper1);
				}
				if (this.dataset.envelope != null) {
					g2.setColor(settings.envelopeColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.envelope);
				}
				if (this.dataset.envelope_lower1 != null) {
					g2.setColor(settings.envelopeLower1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.envelope_lower1);
				}
				if (this.dataset.envelope_lower2 != null) {
					g2.setColor(settings.envelopeLower2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.envelope_lower2);
				}
			// チャネルシステム(高値安値移動平均)
			} else if (settings.bandType == BandType.HIGH_LOW_MOVING_AVERAGE) {
				if (this.dataset.hma != null) {
					g2.setColor(settings.ma1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.hma);
				}
				if (this.dataset.lma != null) {
					g2.setColor(settings.ma2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.lma);
				}
			// ドンチャンズ
			} else if (settings.bandType == BandType.DONCHIAN) {
				if (this.dataset.donchian_upper != null) {
					g2.setColor(settings.ma1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.donchian_upper);
				}
				if (this.dataset.donchian_lower != null) {
					g2.setColor(settings.ma2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.donchian_lower);
				}
			// ボリンジャーバンド(シグマバンド)を描画します。
			} else if (settings.bandType == BandType.BOLLINGER_BANDS) {
				if (this.dataset.bb_upper2 != null) {
					g2.setColor(settings.bbUpper2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.bb_upper2);
				}
				if (this.dataset.bb_upper1 != null) {
					g2.setColor(settings.bbUpper1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.bb_upper1);
				}
				if (this.dataset.bb_tpma != null) {
					g2.setColor(settings.bbColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.bb_tpma);
				}
				if (this.dataset.bb_lower1 != null) {
					g2.setColor(settings.bbLower1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.bb_lower1);
				}
				if (this.dataset.bb_lower2 != null) {
					g2.setColor(settings.bbLower2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.bb_lower2);
				}
			} else if (settings.bandType == BandType.PARABOLIC_TIME_PRICE) {
				// パラボリックを描画します。
				drawSAR(g2);
			} else if (settings.bandType == BandType.PIVOT) {
				// ピボットを描画します。
				if (this.dataset.pivot_hbop != null) {
					g2.setColor(settings.pivotHBOPColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.pivot_hbop);
				}
				if (this.dataset.pivot_s2 != null) {
					g2.setColor(settings.pivotS2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.pivot_s2);
				}
				if (this.dataset.pivot_s1 != null) {
					g2.setColor(settings.pivotS1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.pivot_s1);
				}
/*
				if (this.dataset.pivot != null) {
					g2.setColor(settings.PIVOT);
					drawLine(g2, priceArea, priceAxis, this.dataset.pivot);
				}
*/
				if (this.dataset.pivot_b1 != null) {
					g2.setColor(settings.pivotB1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.pivot_b1);
				}
				if (this.dataset.pivot_b2 != null) {
					g2.setColor(settings.pivotB2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.pivot_b2);
				}
				if (this.dataset.pivot_lbop != null) {
					g2.setColor(settings.pivotLBOPColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.pivot_lbop);
				}
			} else if (settings.bandType == BandType.VIX) {
				// ボラティリティ・システムを描画します。
				if (this.dataset.vi_upper != null) {
					g2.setColor(settings.viUpperColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.vi_upper);
				}
				if (this.dataset.vi_lower != null) {
					g2.setColor(settings.viLowerColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.vi_lower);
				}
			} else if (settings.bandType == BandType.ICHIMOKU) {
				// 一目均衡表を描画します。
				// 基準線を描画します。
				g2.setColor(settings.ichimokuKijunColor);
				drawLine(g2, priceArea, priceAxis, this.dataset.kijun);
				// 転換線を描画します。
				g2.setColor(settings.ichimokuTenkanColor);
				drawLine(g2, priceArea, priceAxis, this.dataset.tenkan);
				// 先行スパン1、先行スパン2を描画します。
				g2.setColor(settings.ichimokuSenkou1Color);
				drawLine(g2, priceArea, priceAxis, this.dataset.senkou1);
				g2.setColor(settings.ichimokuSenkou2Color);
				drawLine(g2, priceArea, priceAxis, this.dataset.senkou2);
				// 遅行スパンを描画します。
				g2.setColor(settings.ichimokuChikouColor);
				drawLine(g2, priceArea, priceAxis, this.dataset.chikou);
			} else if (settings.bandType == BandType.MESA) {
				if (this.dataset.mama != null) {
					g2.setColor(settings.ma1Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.mama);
				}
				if (this.dataset.fama != null) {
					g2.setColor(settings.ma2Color);
					drawLine(g2, priceArea, priceAxis, this.dataset.fama);
				}
			} else if (settings.bandType == BandType.VIDYA) {
				if (this.dataset.vidya != null) {
					g2.setColor(settings.bbColor);
					drawLine(g2, priceArea, priceAxis, this.dataset.vidya);
				}
			}

			// 株式分割数を描画します。
			if (settings.splitMarker)
				drawSplitMarker(g2);

			drawDescription(g2, priceArea, this.priceDescription);
		}

		// 価格帯別出来高
		if (this.vpArea != null) {
			g2.setClip(this.vpArea);

			// 目盛り設定の絡みがあるので価格チャートの情報を使用します。
			final Rectangle2D priceArea = this.areas[PRICE];
			final NumberAxis priceAxis = this.axes[PRICE];

			for (int i = 0; i < this.vp.length; i++) {
				if (this.vp[i] == null)
					continue;

				// 座標を算出します
				final double y1 = priceAxis.valueToJava2D(this.vpLower + (i + 1) * this.vpStep, priceArea);
				final double y2 = priceAxis.valueToJava2D(this.vpLower + i * this.vpStep, priceArea);
				final double maxY = Math.max(y1, y2);
				final double minY = Math.min(y1, y2);

				final double x1 = this.vpArea.getMinX();
				final double x2 = this.vpVolumeAxis.valueToJava2D(this.vp[i].doubleValue(), this.vpArea);
				// 描画します
				g2.setPaint(new GradientPaint((float) x1, (float) maxY, settings.volumeColor1, (float) x2, (float) minY, settings.volumeColor2));
				final Shape bar = new Rectangle2D.Double(x1, maxY, x2 - x1, maxY - minY);
				g2.fill(bar);
			}

			drawDescription(g2, this.vpArea, StringManager.getString("vp"));
		}

		// 出来高チャートを描画します。
		if (this.areas[VOLUME] != null) {
			final Rectangle2D volumeArea = this.areas[VOLUME];
			final NumberAxis volumeAxis = this.axes[VOLUME];
			g2.setClip(volumeArea);

			// 出来高
			if (settings.volumeType != VolumeType.NONE) {
				for (int i = 0; i < this.period; i++) {
					final int n = this.start + i;
					if (n < 0 || n >= this.dataset.getCount())
						continue;
					if (this.dataset.volume[n] == null)
						continue;

					// 座標を算出します
					final double y = volumeAxis.valueToJava2D(this.dataset.volume[n].doubleValue(), volumeArea);
					final double x = volumeArea.getMinX() + i * this.periodWidth;

					// 描画します
					g2.setPaint(new GradientPaint((float) x, (float) y, settings.volumeColor1, (float) (x + this.periodWidth), (float) volumeArea.getMaxY(), settings.volumeColor2));
					final Shape bar = new Rectangle2D.Double(x, y, this.periodWidth, volumeArea.getMaxY() - y);
					g2.fill(bar);
				}
			}

			// 信用残
			if (settings.volumeType == VolumeType.MARGIN) {
				// 信用売残
				if (this.dataset.sold != null) {
					g2.setColor(settings.soldColor);
					drawLine(g2, volumeArea, volumeAxis, this.dataset.sold);
				}
				// 信用買残
				if (this.dataset.bought != null) {
					g2.setColor(settings.boughtColor);
					drawLine(g2, volumeArea, volumeAxis, this.dataset.bought);
				}
			} else if (settings.volumeType == VolumeType.MOVING_AVERAGE) {
				// 出来高移動平均
				if (this.dataset.vma1 != null) {
					g2.setColor(settings.ma1Color);
					drawLine(g2, volumeArea, volumeAxis, this.dataset.vma1);
				}
				if (this.dataset.vma2 != null) {
					g2.setColor(settings.ma2Color);
					drawLine(g2, volumeArea, volumeAxis, this.dataset.vma2);
				}
			}

			drawDescription(g2, volumeArea, this.volumeDescription);
		}

		// アルーンを描画します。
		if (this.areas[AROON_INDICATOR] != null) {
			final Rectangle2D area = this.areas[AROON_INDICATOR];
			final NumberAxis axis = this.axes[AROON_INDICATOR];
			g2.setClip(area);

			if (this.dataset.aroonUp != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.aroonUp);
			}
			if (this.dataset.aroonDown != null) {
				g2.setColor(settings.oscillatorColor2);
				drawLine(g2, area, axis, this.dataset.aroonDown);
			}
			drawDescription(g2, area, StringManager.getString("aroon.updown"));
		}

		// アルーンオシレータを描画します。
		if (this.areas[AROON_OSCILLATOR] != null) {
			final Rectangle2D area = this.areas[AROON_OSCILLATOR];
			final NumberAxis axis = this.axes[AROON_OSCILLATOR];
			g2.setClip(area);

			if (this.dataset.aroon != null) {
				g2.setColor(settings.oscillatorColor3);
				drawLine(g2, area, axis, this.dataset.aroon);
			}
			drawDescription(g2, area, StringManager.getString("aroon"));
		}

		// ATR
		if (settings.atr && dataset.atr != null) {
			final Rectangle2D area = areas[ATR];
			final NumberAxis axis = axes[ATR];
			g2.setClip(area);
			g2.setColor(settings.oscillatorColor1);
			drawLine(g2, area, axis, dataset.atr);
			drawDescription(g2, area, StringManager.getString("atr"));
		}
		// NATR
		if (settings.natr && dataset.natr != null) {
			final Rectangle2D area = areas[NATR];
			final NumberAxis axis = axes[NATR];
			g2.setClip(area);
			g2.setColor(settings.oscillatorColor1);
			drawLine(g2, area, axis, dataset.natr);
			drawDescription(g2, area, StringManager.getString("natr"));
		}

		// BMP
		if (settings.bop && dataset.bop != null) {
			final Rectangle2D area = areas[BOP];
			final NumberAxis axis = axes[BOP];
			g2.setClip(area);
			g2.setColor(settings.oscillatorColor1);
			drawLine(g2, area, axis, dataset.bop);
			drawDescription(g2, area, StringManager.getString("bmp"));
		}

		// チャイキンズ・ボラティリティを描画します。
		if (this.areas[CHV] != null) {
			final Rectangle2D area = this.areas[CHV];
			g2.setClip(area);

			// RCI を描画します。
			if (this.dataset.chv != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[CHV], this.dataset.chv);
			}
			drawDescription(g2, area, StringManager.getString("cv"));
		}

		// CCIを描画します。
		if (this.areas[CCI] != null) {
			final Rectangle2D area = this.areas[CCI];
			final NumberAxis axis = this.axes[CCI];
			g2.setClip(area);

			// RCI を描画します。
			if (this.dataset.cci != null) {
				if (settings.signalMarker)
					drawSignal(g2, area, axis, this.dataset.cci, settings.cciUpperSignal, settings.cciLowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.cci);
			}
			drawDescription(g2, area, StringManager.getString("cci"));
		}

		// CMFを描画します。
		if (this.areas[CMF] != null) {
			final Rectangle2D area = this.areas[CMF];
			final NumberAxis axis = this.axes[CMF];
			g2.setClip(area);

			// RCI を描画します。
			if (this.dataset.cmf != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.cmf);
			}
			drawDescription(g2, area, StringManager.getString("cmf"));
		}

		// CMO
		if (this.areas[CMO] != null) {
			final Rectangle2D area = this.areas[CMO];
			final NumberAxis axis = this.axes[CMO];
			g2.setClip(area);

			if (this.dataset.cmo != null) {
				if (settings.signalMarker)
					drawSignal(g2, area, axis, this.dataset.cmo, settings.cmo_upper_signal, settings.cmo_lower_signal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.cmo);
			}
			drawDescription(g2, area, StringManager.getString("cmo"));
		}

		// コポック買い指標を描画します。
		if (this.areas[COPPOCK] != null) {
			final Rectangle2D area = this.areas[COPPOCK];
			g2.setClip(area);

			if (this.dataset.coppock != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[COPPOCK], this.dataset.coppock);
			}
			drawDescription(g2, area, StringManager.getString("coppock"));
		}

		// DPO を描画します。
		if (this.areas[DPO] != null) {
			final Rectangle2D area = this.areas[DPO];
			g2.setClip(area);

			if (this.dataset.dpo != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[DPO], this.dataset.dpo);
			}
			drawDescription(g2, area, StringManager.getString("dpo"));
		}

		// DMI を描画します。
		if (this.areas[DMI] != null) {
			final Rectangle2D area = this.areas[DMI];
			final NumberAxis axis = this.axes[DMI];
			g2.setClip(area);

			// +DI
			if (this.dataset.pdi != null) {
				g2.setColor(settings.pdiColor);
				drawLine(g2, area, axis, this.dataset.pdi);
			}
			// -DI
			if (this.dataset.mdi != null) {
				g2.setColor(settings.mdiColor);
				drawLine(g2, area, axis, this.dataset.mdi);
			}
			// ADX
			if (this.dataset.adx != null) {
				g2.setColor(settings.adxColor);
				drawLine(g2, area, axis, this.dataset.adx);
			}
			drawDescription(g2, area, StringManager.getString("dmi"));
		}

		// EMV
		if (areas[EMV] != null) {
			final Rectangle2D area = areas[EMV];
			g2.setClip(area);

			if (dataset.emv != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axes[EMV], dataset.emv);
			}
			drawDescription(g2, area, StringManager.getString("emv"));
		}

		// ヒストリカル・ボラティリティを描画します。
		if (this.areas[HV] != null) {
			final Rectangle2D area = this.areas[HV];
			g2.setClip(area);

			// RCI を描画します。
			if (this.dataset.hv != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[HV], this.dataset.hv);
			}
			drawDescription(g2, area, StringManager.getString("hv"));
		}

		// 移動平均乖離率を描画します。
		if (this.areas[KAIRI] != null) {
			final Rectangle2D area = this.areas[KAIRI];
			final NumberAxis axis = this.axes[KAIRI];
			g2.setClip(area);

			if (this.dataset.kairi != null) {
				if (settings.signalMarker)
					drawSignal(g2, area, axis, this.dataset.kairi, settings.biasUpperSignal, settings.biasLowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.kairi);
			}
			drawDescription(g2, area, StringManager.getString("bias"));
		}

		// MIを描画します。
		if (this.areas[MI] != null) {
			final Rectangle2D area = this.areas[MI];
			final NumberAxis axis = this.axes[MI];
			g2.setClip(area);

			// MI を描画します。
			if (this.dataset.mi != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.mi);
			}
			drawDescription(g2, area, StringManager.getString("mi"));
		}

		// モメンタムを描画します。
		if (this.areas[MOM] != null) {
			final Rectangle2D area = this.areas[MOM];
			g2.setClip(area);

			if (this.dataset.mom != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[MOM], this.dataset.mom);
			}
			drawDescription(g2, area, StringManager.getString("momentum"));
		}

		// MFIを描画します。
		if (this.areas[MFI] != null) {
			final Rectangle2D area = this.areas[MFI];
			final NumberAxis axis = this.axes[MFI];
			g2.setClip(area);

			// MFI を描画します。
			if (this.dataset.mfi != null) {
				if (settings.signalMarker)
					drawSignal(g2, area, axis, this.dataset.mfi, settings.mfiUpperSignal, settings.mfiLowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.mfi);
			}
			drawDescription(g2, area, StringManager.getString("mfi"));
		}

		// MACD を描画します。
		if (this.areas[MACD] != null) {
			final Rectangle2D area = this.areas[MACD];
			final NumberAxis axis = this.axes[MACD];
			g2.setClip(area);

			// MACD
			if (this.dataset.macd != null) {
				g2.setColor(settings.macdColor);
				drawLine(g2, area, axis, this.dataset.macd);
			}
			// シグナル
			if (this.dataset.macdSignal != null) {
				g2.setColor(settings.macdSignalColor);
				drawLine(g2, area, axis, this.dataset.macdSignal);
			}
			drawDescription(g2, area, StringManager.getString("macd"));
		}

		// PAIN
		if (this.areas[PAIN] != null) {
			final Rectangle2D area = this.areas[PAIN];
			final NumberAxis axis = this.axes[PAIN];
			g2.setClip(area);

			// MFI を描画します。
			if (this.dataset.pain != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.pain);
			}
			drawDescription(g2, area, StringManager.getString("pain"));
		}

		// PCR
		if (areas[PCR] != null) {
			final Rectangle2D area = areas[PCR];
			final NumberAxis axis = axes[PCR];
			g2.setClip(area);

			if (dataset.pcr != null) {
				if (settings.signalMarker)
					drawSignal(g2, area, axis, dataset.pcr, settings.pcr_upper_signal, settings.pcr_lower_signal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, dataset.pcr);
			}
			drawDescription(g2, area, StringManager.getString("pcr"));
		}

		// プライス・オシレータ(APO)を描画します。
		if (this.areas[APO] != null) {
			final Rectangle2D apoArea = this.areas[APO];
			g2.setClip(apoArea);

			if (this.dataset.apo != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, apoArea, this.axes[APO], this.dataset.apo);
			}
			drawDescription(g2, apoArea, StringManager.getString("apo"));
		}

		// プライス・オシレータ(PPO)を描画します。
		if (this.areas[PPO] != null) {
			final Rectangle2D ppoArea = this.areas[PPO];
			g2.setClip(ppoArea);

			if (this.dataset.ppo != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, ppoArea, this.axes[PPO], this.dataset.ppo);
			}
			drawDescription(g2, ppoArea, StringManager.getString("ppo"));
		}

		// サイコロジカルラインを描画します。
		if (this.areas[PSY] != null) {
			final Rectangle2D psychologicalArea = this.areas[PSY];
			final NumberAxis psychologicalAxis = this.axes[PSY];
			g2.setClip(psychologicalArea);

			// サイコロジカルラインを描画します。
			if (this.dataset.psy != null) {
				if (settings.signalMarker)
					drawSignal(g2, psychologicalArea, psychologicalAxis, this.dataset.psy,
						settings.psy_upper_signal, settings.psy_lower_signal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, psychologicalArea, psychologicalAxis, this.dataset.psy);
			}
			drawDescription(g2, psychologicalArea, StringManager.getString("psychological"));
		}

		// Qスティック
		if (this.areas[QSTICK] != null) {
			final Rectangle2D area = this.areas[QSTICK];
			final NumberAxis axis = this.axes[QSTICK];
			g2.setClip(area);

			if (this.dataset.qstick != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.qstick);
			}
			drawDescription(g2, area, StringManager.getString("qstick"));
		}

		// RCIを描画します。
		if (this.areas[RCI] != null) {
			final Rectangle2D rciArea = this.areas[RCI];
			final NumberAxis rciAxis = this.axes[RCI];
			g2.setClip(rciArea);

			// RCI を描画します。
			if (this.dataset.rci != null) {
				if (settings.signalMarker)
					drawSignal(g2, rciArea, rciAxis, this.dataset.rci, settings.rciUpperSignal, settings.rciLowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, rciArea, rciAxis, this.dataset.rci);
			}
			drawDescription(g2, rciArea, StringManager.getString("rci"));
		}

		// ROC を描画します。
		if (this.areas[ROC] != null) {
			final Rectangle2D rocArea = this.areas[ROC];
			g2.setClip(rocArea);

			if (this.dataset.roc != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, rocArea, this.axes[ROC], this.dataset.roc);
			}
			drawDescription(g2, rocArea, StringManager.getString("roc"));
		}

		// RSIを描画します。
		if (this.areas[RSI] != null) {
			final Rectangle2D rsiArea = this.areas[RSI];
			final NumberAxis rsiAxis = this.axes[RSI];
			g2.setClip(rsiArea);

			// RSI を描画します。
			if (this.dataset.rsi != null) {
				if (settings.signalMarker)
					drawSignal(g2, rsiArea, rsiAxis, this.dataset.rsi, settings.rsiUpperSignal, settings.rsiLowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, rsiArea, rsiAxis, this.dataset.rsi);
			}
			drawDescription(g2, rsiArea, StringManager.getString("rsi"));
		}

		// RVIを描画します。
		if (areas[RVI] != null) {
			final Rectangle2D area = areas[RVI];
			final NumberAxis axis = axes[RVI];
			g2.setClip(area);

			if (dataset.rvi != null) {
				if (settings.signalMarker)
					drawSignal(g2, area, axis, dataset.rvi, settings.rvi_upper_signal, settings.rvi_lower_signal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, dataset.rvi);
			}
			drawDescription(g2, area, StringManager.getString("rvi"));
		}

		// 強弱レシオ(篠原レシオ)を描画します。
		if (areas[SHINOHARA] != null) {
			final Rectangle2D area = areas[SHINOHARA];
			final NumberAxis axis = axes[SHINOHARA];
			g2.setClip(area);

			if (dataset.shinohara_a != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, dataset.shinohara_a);
			}
			if (dataset.shinohara_b != null) {
				g2.setColor(settings.oscillatorColor2);
				drawLine(g2, area, axis, dataset.shinohara_b);
			}
			drawDescription(g2, area, StringManager.getString("shinohara"));
		}

		// ストキャスティクスを描画します。
		if (this.areas[SRV_FAST] != null) {
			final Rectangle2D srvFastArea = this.areas[SRV_FAST];
			final NumberAxis srvFastAxis = this.axes[SRV_FAST];
			g2.setClip(srvFastArea);

			if (settings.signalMarker) {
				if (this.dataset.srvK != null) {
					drawSignal(g2, srvFastArea, srvFastAxis, this.dataset.srvK, settings.srvUpperSignal, settings.srvLowerSignal);
					if (this.dataset.srvD != null) {
						// FIXME - シグナル点灯部分でのみ○を出すようにすること
						//drawMACross(g2, this.srvArea, this.srvAxis, this.dataset.srvK, this.dataset.srvD);
					}
				}
			}
			if (this.dataset.srvK != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, srvFastArea, srvFastAxis, this.dataset.srvK);
			}
			if (this.dataset.srvD != null) {
				g2.setColor(settings.oscillatorColor2);
				drawLine(g2, srvFastArea, srvFastAxis, this.dataset.srvD);
			}
/*
			if (this.dataset.srvSD != null) {
				g2.setColor(ChartColor.OSI3);
				drawLine(g2, srvArea, srvAxis, this.dataset.srvSD);
			}
*/
			drawDescription(g2, srvFastArea, StringManager.getString("srv.fast"));
		}

		// TII
		if (this.areas[TII] != null) {
			final Rectangle2D area = this.areas[TII];
			final NumberAxis axis = this.axes[TII];
			g2.setClip(area);

			// トリックス(Trix)
			if (this.dataset.tii != null) {
				if (settings.signalMarker)
					drawSignal(g2, area, axis, this.dataset.tii, settings.tii_upper_signal, settings.tii_lower_signal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, axis, this.dataset.tii);
			}
			drawDescription(g2, area, StringManager.getString("tii"));
		}

		// トリックス(Trix) を描画します。
		if (this.areas[TRIX] != null) {
			final Rectangle2D trixArea = this.areas[TRIX];
			final NumberAxis trixAxis = this.axes[TRIX];
			g2.setClip(trixArea);

			// トリックス(Trix)
			if (this.dataset.trix != null) {
				g2.setColor(settings.macdColor);
				drawLine(g2, trixArea, trixAxis, this.dataset.trix);
			}
			// シグナル
			if (this.dataset.trixSignal != null) {
				g2.setColor(settings.macdSignalColor);
				drawLine(g2, trixArea, trixAxis, this.dataset.trixSignal);
			}
			drawDescription(g2, trixArea, StringManager.getString("trix"));
		}

		// TSI
		if (this.areas[TSI] != null) {
			final Rectangle2D area = this.areas[TSI];
			final NumberAxis axis = this.axes[TSI];
			g2.setClip(area);

			// トリックス(Trix)
			if (this.dataset.tsi != null) {
				if (settings.signalMarker)
					drawSignal(g2, area, axis, this.dataset.tsi, settings.tsi_upper_signal, settings.tsi_lower_signal);
				g2.setColor(settings.macdColor);
				drawLine(g2, area, axis, this.dataset.tsi);
			}
			// シグナル
			if (this.dataset.tsi_signal != null) {
				g2.setColor(settings.macdSignalColor);
				drawLine(g2, area, axis, this.dataset.tsi_signal);
			}
			drawDescription(g2, area, StringManager.getString("tsi"));
		}

		// 究極のオシレーターを描画します。
		if (this.areas[ULTIMATE] != null) {
			final Rectangle2D ultimateArea = this.areas[ULTIMATE];
			g2.setClip(ultimateArea);

			// 究極のオシレーターを描画します。
			if (this.dataset.ultimate != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, ultimateArea, this.axes[ULTIMATE], this.dataset.ultimate);
			}
			drawDescription(g2, ultimateArea, StringManager.getString("ultimate"));
		}

		// ウィリアムズADを描画します。
		if (this.areas[WAD] != null) {
			final Rectangle2D wadArea = this.areas[WAD];
			g2.setClip(wadArea);

			// ウィリアムズADを描画します。
			if (this.dataset.wad != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, wadArea, this.axes[WAD], this.dataset.wad);
			}
			drawDescription(g2, wadArea, StringManager.getString("wad"));
		}

		// ウィリアムズ%Rを描画します。
		if (this.areas[WR] != null) {
			final Rectangle2D williamsRArea = this.areas[WR];
			final NumberAxis williamsRAxis = this.axes[WR];
			g2.setClip(williamsRArea);

			// ウィリアムズ%Rを描画します。
			if (this.dataset.williamsR != null) {
				if (settings.signalMarker)
					drawSignal(g2, williamsRArea, williamsRAxis, this.dataset.williamsR,
						settings.williamsRUpperSignal, settings.williamsRLowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, williamsRArea, williamsRAxis, this.dataset.williamsR);
			}
			drawDescription(g2, williamsRArea, StringManager.getString("williamsr"));
		}

		// A/Dラインを描画します。
		if (this.areas[AD] != null) {
			final Rectangle2D adArea = this.areas[AD];
			g2.setClip(adArea);

			// A/Dラインを描画します。
			if (this.dataset.ad != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, adArea, this.axes[AD], this.dataset.ad);
			}
			drawDescription(g2, adArea, StringManager.getString("ad"));
		}

		// A/Dオシレーターを描画します。
		if (this.areas[CHO] != null) {
			final Rectangle2D area = this.areas[CHO];
			g2.setClip(area);

			if (this.dataset.cho != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[CHO], this.dataset.cho);
			}
			drawDescription(g2, area, StringManager.getString("cho"));
		}

		// ボリューム・オシレータ(AVO)を描画します。
		if (this.areas[AVO] != null) {
			final Rectangle2D avoArea = this.areas[AVO];
			g2.setClip(avoArea);

			if (this.dataset.avo != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, avoArea, this.axes[AVO], this.dataset.avo);
			}
			drawDescription(g2, avoArea, StringManager.getString("avo"));
		}

		// ボリューム・オシレータ(PVO)を描画します。
		if (this.areas[PVO] != null) {
			final Rectangle2D pvoArea = this.areas[PVO];
			g2.setClip(pvoArea);

			if (this.dataset.pvo != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, pvoArea, this.axes[PVO], this.dataset.pvo);
			}
			drawDescription(g2, pvoArea, StringManager.getString("pvo"));
		}

		// 出来高乖離率を描画します。
		if (this.areas[VKAIRI] != null) {
			final Rectangle2D vbiasArea = this.areas[VKAIRI];
			final NumberAxis vbiasAxis = this.axes[VKAIRI];
			g2.setClip(vbiasArea);

			if (this.dataset.vkairi != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, vbiasArea, vbiasAxis, this.dataset.vkairi);
			}
			drawDescription(g2, vbiasArea, StringManager.getString("vbias"));
		}

		// ボリュームレシオ1を描画します。
		if (this.areas[VR1] != null) {
			final Rectangle2D vr1Area = this.areas[VR1];
			final NumberAxis vr1Axis = this.axes[VR1];
			g2.setClip(vr1Area);

			if (this.dataset.vr1 != null) {
				if (settings.signalMarker)
					drawSignal(g2, vr1Area, vr1Axis, this.dataset.vr1, settings.vr1UpperSignal, settings.vr1LowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, vr1Area, vr1Axis, this.dataset.vr1);
			}
			drawDescription(g2, vr1Area, StringManager.getString("vr1"));
		}

		// ボリュームレシオ2を描画します。
		if (this.areas[VR2] != null) {
			final Rectangle2D vr2Area = this.areas[VR2];
			final NumberAxis vr2Axis = this.axes[VR2];
			g2.setClip(vr2Area);

			if (this.dataset.vr2 != null) {
				if (settings.signalMarker)
					drawSignal(g2, vr2Area, vr2Axis, this.dataset.vr2, settings.vr2UpperSignal, settings.vr2LowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, vr2Area, vr2Axis, this.dataset.vr2);
			}
			drawDescription(g2, vr2Area, StringManager.getString("vr2"));
		}

		// 出来高ROC を描画します。
		if (this.areas[VROC] != null) {
			final Rectangle2D vrocArea = this.areas[VROC];
			g2.setClip(vrocArea);

			if (this.dataset.vroc != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, vrocArea, this.axes[VROC], this.dataset.vroc);
			}
			drawDescription(g2, vrocArea, StringManager.getString("vroc"));
		}

		// V-RSIを描画します。
		if (this.areas[VRSI] != null) {
			final Rectangle2D vrsiArea = this.areas[VRSI];
			final NumberAxis vrsiAxis = this.axes[VRSI];
			g2.setClip(vrsiArea);

			// V-RSI を描画します。
			if (this.dataset.vrsi != null) {
				if (settings.signalMarker)
					drawSignal(g2, vrsiArea, vrsiAxis, this.dataset.vrsi, settings.vrsiUpperSignal, settings.vrsiLowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, vrsiArea, vrsiAxis, this.dataset.vrsi);
			}
			drawDescription(g2, vrsiArea, StringManager.getString("vrsi"));
		}

		// 和光ボリュームレシオを描画します。
		if (this.areas[WVR] != null) {
			final Rectangle2D wvrArea = this.areas[WVR];
			final NumberAxis wvrAxis = this.axes[WVR];
			g2.setClip(wvrArea);

			if (this.dataset.wvr != null) {
				if (settings.signalMarker)
					drawSignal(g2, wvrArea, wvrAxis, this.dataset.wvr, settings.wvrUpperSignal, settings.wvrLowerSignal);
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, wvrArea, wvrAxis, this.dataset.wvr);
			}
			drawDescription(g2, wvrArea, StringManager.getString("wvr"));
		}

		// PVI を描画します。
		if (this.areas[PVI] != null) {
			final Rectangle2D area = this.areas[PVI];
			g2.setClip(area);

//			if (settings.signalMarker)
//				drawLineCrossMarker(g2, area, this.axes[PVI], this.dataset.pvi_ma1, this.dataset.pvi_ma2);

			if (this.dataset.pvi != null) {
				g2.setColor(settings.oscillatorColor3);
				drawLine(g2, area, this.axes[PVI], this.dataset.pvi);
			}
			if (this.dataset.pvi_ma1 != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[PVI], this.dataset.pvi_ma1);
			}
			if (this.dataset.pvi_ma2 != null) {
				g2.setColor(settings.oscillatorColor2);
				drawLine(g2, area, this.axes[PVI], this.dataset.pvi_ma2);
			}
			drawDescription(g2, area, StringManager.getString("pvi"));
		}

		// NVI を描画します。
		if (this.areas[NVI] != null) {
			final Rectangle2D area = this.areas[NVI];
			g2.setClip(area);

//			if (settings.signalMarker)
//				drawLineCrossMarker(g2, area, this.axes[NVI], this.dataset.nvi_ma1, this.dataset.nvi_ma2);

			if (this.dataset.nvi != null) {
				g2.setColor(settings.oscillatorColor3);
				drawLine(g2, area, this.axes[NVI], this.dataset.nvi);
			}
			if (this.dataset.nvi_ma1 != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[NVI], this.dataset.nvi_ma1);
			}
			if (this.dataset.nvi_ma2 != null) {
				g2.setColor(settings.oscillatorColor2);
				drawLine(g2, area, this.axes[NVI], this.dataset.nvi_ma2);
			}
			drawDescription(g2, area, StringManager.getString("nvi"));
		}

		// OBV を描画します。
		if (this.areas[OBV] != null) {
			final Rectangle2D area = this.areas[OBV];
			g2.setClip(area);

			if (this.dataset.obv != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[OBV], this.dataset.obv);
			}
			drawDescription(g2, area, StringManager.getString("obv"));
		}

		// PVT
		if (this.areas[PVT] != null) {
			final Rectangle2D area = this.areas[PVT];
			g2.setClip(area);

			if (this.dataset.pvt != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, area, this.axes[PVT], this.dataset.pvt);
			}
			drawDescription(g2, area, StringManager.getString("pvt"));
		}

		// 信用倍率を描画します。
		if (this.areas[RATIO] != null) {
			final Rectangle2D ratioArea = this.areas[RATIO];
			final NumberAxis ratioAxis = this.axes[RATIO];
			g2.setClip(ratioArea);

			if (this.dataset.ratio != null) {
				if (settings.signalMarker)
					drawSignal(g2, ratioArea, ratioAxis, this.dataset.ratio, settings.ratioUpperSignal, settings.ratioLowerSignal);
				g2.setColor(ChartColor.RED);
				drawLine(g2, ratioArea, ratioAxis, this.dataset.ratio);
			}
			drawDescription(g2, ratioArea, StringManager.getString("ratio"));
		}

		// 騰落価格を描画します。
		if (this.areas[PRICE_PERFORMANCE] != null) {
			final Rectangle2D priceFluctuationsArea = this.areas[PRICE_PERFORMANCE];
			g2.setClip(priceFluctuationsArea);

			if (this.dataset.price_performance != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, priceFluctuationsArea, this.axes[PRICE_PERFORMANCE], this.dataset.price_performance);
			}
			drawDescription(g2, priceFluctuationsArea, StringManager.getString("fluctuations.price"));
		}

		// 騰落率を描画します。
		if (this.areas[PERCENT_PERFORMANCE] != null) {
			final Rectangle2D fluctuationsArea = this.areas[PERCENT_PERFORMANCE];
			g2.setClip(fluctuationsArea);

			if (this.dataset.percent_performance != null) {
				g2.setColor(settings.oscillatorColor1);
				drawLine(g2, fluctuationsArea, this.axes[PERCENT_PERFORMANCE], this.dataset.percent_performance);
			}
			drawDescription(g2, fluctuationsArea, StringManager.getString("fluctuations"));
		}

		g2.setClip(saved);
	}

	/**
	 * 株式分割マーカーを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 */
	private void drawSplitMarker(final Graphics2D g2) {
		final Rectangle2D priceArea = this.areas[PRICE];
		final FontMetrics fm = g2.getFontMetrics();

		for (int i = 0; i < this.period; i++) {
			final int n = this.start + i;
			if (n < 0 || n >= this.dataset.getCount())
				continue;
			if (this.dataset.split == null || this.dataset.split[n] == null)
				continue;

			// 座標を算出します
			final double x = priceArea.getMinX() + (i * this.periodWidth) + (this.periodWidth - this.splitIcon.getWidth(null)) * 0.5;
			final double y = priceArea.getMaxY() - this.splitIcon.getHeight(null);
			g2.drawImage(this.splitIcon, (int) x, (int) y, this);
			final double d = (this.splitIcon.getHeight(null) - fm.getHeight()) * 0.5;
			DrawUtils.drawText(g2, "1", (float) (x - fm.stringWidth("1") - 1), (float) (priceArea.getMaxY() + d));
			DrawUtils.drawText(g2, this.dataset.split[n].toString(), (float) (x + this.splitIcon.getWidth(null) + 1), (float) (priceArea.getMaxY() + d));
		}
	}

	/**
	 * 透明度 50% の Composite オブジェクトです。
	 */
	private static final Composite COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5F);

	private void drawLineCrossMarker(
		final Graphics2D g2,
		final Rectangle2D area,
		final NumberAxis axis,
		final Number[] shortTermData,
		final Number[] longTermData) {

		final UpDownColorType colors = this.settings.updownLineColors;
		final double w = Math.min(20, this.itemWidth) * 5;

		for (int i = -1; i < (this.period + 1); i++) {
			final int n = this.start + i;
			if (n < 0 || n >= shortTermData.length)
				continue;
			if (shortTermData[n] == null || longTermData[n] == null)
				continue;
			if ((n + 1) < shortTermData.length && (shortTermData[n + 1] == null))
				continue;
			if ((n + 1) < longTermData.length && (longTermData[n + 1] == null))
				continue;

			if (n < (shortTermData.length - 1)) {
				final float x1 = (float) (area.getMinX() + i * this.periodWidth + this.periodWidth * 0.5);
				final float x2 = (float) (x1 + this.periodWidth);
				final float shortY1 = (float) (axis.valueToJava2D(shortTermData[n].doubleValue(), area));
				final float shortY2 = (float) (axis.valueToJava2D(shortTermData[n + 1].doubleValue(), area));
				final float longY1 = (float) (axis.valueToJava2D(longTermData[n].doubleValue(), area));
				final float longY2 = (float) (axis.valueToJava2D(longTermData[n + 1].doubleValue(), area));

				final Point2D point = getIntersectPoint(x1, shortY1, x2, shortY2, x1, longY1, x2, longY2);
				if (point != null) {
					// GC
					if (shortY1 < longY1 && shortY2 > longY2) {
						final Ellipse2D ellipse = new Ellipse2D.Double(point.getX() - w * 0.5, point.getY() - w * 0.5, w, w);
						g2.setColor(colors.getDownLineColor());
						g2.draw(ellipse);

						final Composite originalComposite = g2.getComposite();
						g2.setComposite(COMPOSITE);
						g2.setColor(colors.getDownColor1());
						g2.fill(ellipse);
						g2.setComposite(originalComposite);
					// DC
					} else if (shortY1 > longY1 && shortY2 < longY2) {
						final Ellipse2D ellipse = new Ellipse2D.Double(point.getX() - w * 0.5, point.getY() - w * 0.5, w, w);
						g2.setColor(colors.getUpLineColor());
						g2.draw(ellipse);

						final Composite originalComposite = g2.getComposite();
						g2.setComposite(COMPOSITE);
						g2.setColor(colors.getUpColor1());
						g2.fill(ellipse);
						g2.setComposite(originalComposite);
					}
				}
			}
		}
	}

	/**
	 * オシレーター系指標の売買シグナルを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param axis 数値軸
	 * @param data データ
	 * @param upperSignal 売りシグナル
	 * @param lowerSignal 買いシグナル
	 */
	private void drawSignal(final Graphics2D g2, final Rectangle2D area, final NumberAxis axis, final Number[] data, final Number upperSignal, final Number lowerSignal) {
		if (upperSignal != null)
			drawUpperSignal(g2, area, axis, data, upperSignal.doubleValue());
		if (lowerSignal != null)
			drawLowerSignal(g2, area, axis, data, lowerSignal.doubleValue());
	}

	/**
	 * オシレーター系指標の売りシグナルを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param axis 数値軸
	 * @param data データ
	 * @param signal シグナル値
	 */
	private void drawUpperSignal(final Graphics2D g2, final Rectangle2D area, final NumberAxis axis, final Number[] data, final double signal) {
		final Composite originalComposite = g2.getComposite();
		g2.setComposite(COMPOSITE);
		g2.setColor(this.settings.updownLineColors.getUpColor1());

		final float signalY = (float) axis.valueToJava2D(signal, area);

		for (int i = -1; i < (this.period + 1); i++) {
			final int n = this.start + i;
			if (n < 0 || n >= data.length)
				continue;
			if (data[n] == null)
				continue;
			if ((n + 1) < data.length && (data[n + 1] == null))
				continue;

			if (n < (data.length - 1)) {
				// シグナルに到達していない場合は以下の処理ブロックを実行しません。
				if (data[n].doubleValue() < signal && data[n + 1].doubleValue() < signal)
					continue;

				final float x1 = (float) (area.getMinX() + i * this.periodWidth + this.periodWidth * 0.5);
				final float x2 = (float) (x1 + this.periodWidth);

				final float y1 = (float) (axis.valueToJava2D(data[n].doubleValue(), area));
				final float y2 = (float) (axis.valueToJava2D(data[n + 1].doubleValue(), area));

				// シグナルラインと交差しているかどうか検証します。
				final Point2D point = getIntersectPoint(x1, y1, x2, y2, x1, signalY, x2, signalY);
				final Shape body;
				// シグナルラインと交差している場合
				if (point != null) {
					if (y1 > y2) {
						// 上昇(右半分描画)
						body = new Rectangle2D.Double(point.getX(), area.getMinY(), x2 - point.getX(), area.getHeight());
					} else if (y1 < y2) {
						// 下降(左半分描画)
						body = new Rectangle2D.Double(x1, area.getMinY(), point.getX() - x1, area.getHeight());
					} else {
						// 平行な場合
						body = new Rectangle2D.Double(x1, area.getMinY(), x2 - x1, area.getHeight());
					}
				} else {
					body = new Rectangle2D.Double(x1, area.getMinY(), x2 - x1, area.getHeight());
				}
				g2.fill(body);
			}
		}
		g2.setComposite(originalComposite);
	}

	/**
	 * オシレーター系指標の買いシグナルを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param axis 数値軸
	 * @param data データ
	 * @param signal シグナル値
	 */
	private void drawLowerSignal(final Graphics2D g2, final Rectangle2D area, final NumberAxis axis, final Number[] data, final double signal) {
		final Composite originalComposite = g2.getComposite();
		g2.setComposite(COMPOSITE);
		g2.setColor(this.settings.updownLineColors.getDownColor1());

		final float signalY = (float) axis.valueToJava2D(signal, area);

		for (int i = -1; i < (this.period + 1); i++) {
			final int n = this.start + i;
			if (n < 0 || n >= data.length)
				continue;
			if (data[n] == null)
				continue;
			if ((n + 1) < data.length && (data[n + 1] == null))
				continue;

			if (n < (data.length - 1)) {
				// シグナルに到達していない場合は以下の処理ブロックを実行しません。
				if (data[n].doubleValue() > signal && data[n + 1].doubleValue() > signal)
					continue;

				final float x1 = (float) (area.getMinX() + i * this.periodWidth + this.periodWidth * 0.5);
				final float x2 = (float) (x1 + this.periodWidth);

				final float y1 = (float) (axis.valueToJava2D(data[n].doubleValue(), area));
				final float y2 = (float) (axis.valueToJava2D(data[n + 1].doubleValue(), area));

				// シグナルラインと交差しているかどうか検証します。
				final Point2D point = getIntersectPoint(x1, y1, x2, y2, x1, signalY, x2, signalY);
				final Shape body;
				// シグナルラインと交差している場合
				if (point != null) {
					if (y1 < y2) {
						body = new Rectangle2D.Double(point.getX(), area.getMinY(), x2 - point.getX(), area.getHeight());
					} else if (y1 > y2) {
						body = new Rectangle2D.Double(x1, area.getMinY(), point.getX() - x1, area.getHeight());
					} else {
						body = new Rectangle2D.Double(x1, area.getMinY(), x2 - x1, area.getHeight());
					}
				} else {
					body = new Rectangle2D.Double(x1, area.getMinY(), x2 - x1, area.getHeight());
				}
				g2.fill(body);
			}
		}
		g2.setComposite(originalComposite);
	}

	/**
	 * バンド系指標の売買シグナルを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param axis 数値軸
	 * @param upperData 上限データ
	 * @param lowerData 下限データ
	 * @param upperSignal 売りシグナルデータ
	 * @param lowerSignal 買いシグナルデータ
	 */
	private void drawSignal(final Graphics2D g2, final Rectangle2D area, final NumberAxis axis, final Number[] upperData, final Number[] lowerData, final Number[] upperSignal, final Number[] lowerSignal) {
		if (upperSignal != null)
			drawUpperSignal(g2, area, axis, upperData, upperSignal);
		if (lowerSignal != null)
			drawLowerSignal(g2, area, axis, lowerData, lowerSignal);
	}

	/**
	 * バンド系指標の売りシグナルを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param axis 数値軸
	 * @param data データ
	 * @param signal シグナルデータ
	 */
	private void drawUpperSignal(final Graphics2D g2, final Rectangle2D area, final NumberAxis axis, final Number[] data, final Number[] signal) {
		final Composite originalComposite = g2.getComposite();
		g2.setComposite(COMPOSITE);
		g2.setColor(this.settings.updownLineColors.getUpColor1());

		for (int i = -1; i < (period + 1); i++) {
			final int n = start + i;
			if (n < 0 || n >= data.length)
				continue;
			if (data[n] == null || signal[n] == null)
				continue;
			if ((n + 1) < data.length && (data[n + 1] == null))
				continue;
			if ((n + 1) < signal.length && (signal[n + 1] == null))
				continue;

			if (n < (data.length - 1)) {
				final float x1 = (float) (area.getMinX() + i * this.periodWidth + this.periodWidth * 0.5);
				final float x2 = (float) (x1 + this.periodWidth);
				final float y1 = (float) (axis.valueToJava2D(data[n].doubleValue(), area));
				final float y2 = (float) (axis.valueToJava2D(data[n + 1].doubleValue(), area));

				// シグナルに到達していない場合は以下の処理ブロックを実行しません。
				if (data[n].doubleValue() >= signal[n].doubleValue() || data[n + 1].doubleValue() >= signal[n + 1].doubleValue()) {
					final float signalY1 = (float) (axis.valueToJava2D(signal[n].doubleValue(), area));
					final float signalY2 = (float) (axis.valueToJava2D(signal[n + 1].doubleValue(), area));

					final Point2D point = getIntersectPoint(x1, y1, x2, y2, x1, signalY1, x2, signalY2);

					// シグナルラインと交差している場合
					final Shape body;
					if (point != null) {
						if (y1 > y2) {
							// 上昇(右半分描画)
							body = new Rectangle2D.Double(point.getX(), area.getMinY(), x2 - point.getX(), area.getHeight());
						} else if (y1 < y2) {
							// 下降(左半分描画)
							body = new Rectangle2D.Double(x1, area.getMinY(), point.getX() - x1, area.getHeight());
						} else {
							// 平行な場合
							body = new Rectangle2D.Double(x1, area.getMinY(), x2 - x1, area.getHeight());
						}
					} else {
						body = new Rectangle2D.Double(x1, area.getMinY(), x2 - x1, area.getHeight());
					}
					g2.fill(body);
				}
			}
		}

		g2.setComposite(originalComposite);
	}

	/**
	 * バンド系指標の買いシグナルを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param axis 数値軸
	 * @param data データ
	 * @param signal シグナルデータ
	 */
	private void drawLowerSignal(final Graphics2D g2, final Rectangle2D area, final NumberAxis axis, final Number[] data, final Number[] signal) {
		final Composite originalComposite = g2.getComposite();
		g2.setComposite(COMPOSITE);
		g2.setColor(this.settings.updownLineColors.getDownColor1());

		for (int i = -1; i < (period + 1); i++) {
			final int n = start + i;
			if (n < 0 || n >= data.length)
				continue;
			if (data[n] == null || signal[n] == null)
				continue;
			if ((n + 1) < data.length && (data[n + 1] == null))
				continue;
			if ((n + 1) < signal.length && (signal[n + 1] == null))
				continue;

			if (n < (data.length - 1)) {
				final float x1 = (float) (area.getMinX() + i * this.periodWidth + this.periodWidth * 0.5);
				final float x2 = (float) (x1 + this.periodWidth);
				final float y1 = (float) (axis.valueToJava2D(data[n].doubleValue(), area));
				final float y2 = (float) (axis.valueToJava2D(data[n + 1].doubleValue(), area));

				// シグナルに到達していない場合は以下の処理ブロックを実行しません。
				if (data[n].doubleValue() <= signal[n].doubleValue() || data[n + 1].doubleValue() <= signal[n + 1].doubleValue()) {
					final float signalY1 = (float) (axis.valueToJava2D(signal[n].doubleValue(), area));
					final float signalY2 = (float) (axis.valueToJava2D(signal[n + 1].doubleValue(), area));

					final Point2D point = getIntersectPoint(x1, y1, x2, y2, x1, signalY1, x2, signalY2);
					final Shape body;

					// シグナルラインと交差している場合
					if (point != null) {
						if (y1 < y2) {
							body = new Rectangle2D.Double(point.getX(), area.getMinY(), x2 - point.getX(), area.getHeight());
						} else if (y1 > y2) {
							body = new Rectangle2D.Double(x1, area.getMinY(), point.getX() - x1, area.getHeight());
						} else {
							body = new Rectangle2D.Double(x1, area.getMinY(), x2 - x1, area.getHeight());
						}
					} else {
						body = new Rectangle2D.Double(x1, area.getMinY(), x2 - x1, area.getHeight());
					}
					g2.fill(body);
				}
			}
		}

		g2.setComposite(originalComposite);
	}

	/**
	 * ローソク足パターンのシグナルを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param type ローソク足パターン情報
	 * @param signal シグナルデータ
	 */
	private void drawSignal(final Graphics2D g2, final CandlePattern type, final boolean[] signal) {
		final Composite originalComposite = g2.getComposite();
		g2.setComposite(COMPOSITE);

		final Rectangle2D area = areas[PRICE];
		for (int i = 0; i < period; i++) {
			final int n = start + i;
			if (n < 0 || n >= dataset.getCount())
				continue;
			if (!signal[n])
				continue;

			final double x = area.getMinX() + i * periodWidth;
			final Shape body = new Rectangle2D.Double(x, area.getMinY(), periodWidth, area.getHeight());

			switch (type) {
				case BEARISH:
					g2.setColor(settings.updownLineColors.getUpColor1());
					break;
				case BULLISH:
					g2.setColor(settings.updownLineColors.getDownColor1());
					break;
			}
			g2.fill(body);
		}
		g2.setComposite(originalComposite);
	}

	/**
	 * 比較チャートを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param axis 数値軸
	 * @param data1 データ1
	 * @param data2 データ2
	 * @param positiveColor1
	 * @param positiveColor2
	 * @param negativeColor1
	 * @param negativeColor2
	 */
	private void drawDifference(
		final Graphics2D g2,
		final Rectangle2D area, final NumberAxis axis,
		final Number[] data1, final Number[] data2,
		final Color positiveColor1, final Color positiveColor2,
		final Color negativeColor1, final Color negativeColor2) {

		final Paint positivePaint = new GradientPaint(0, (float) area.getMinY(), positiveColor1, 0, (float) area.getMaxY(), positiveColor2);
		final Paint negativePaint = new GradientPaint(0, (float) area.getMinY(), negativeColor1, 0, (float) area.getMaxY(), negativeColor2);

		final Composite originalComposite = g2.getComposite();
		g2.setComposite(COMPOSITE);

		for (int i = -1; i < (this.period + 1); i++) {
			final int n = this.start + i;
			if (n < 0 || n >= data1.length)
				continue;
			if (data1[n] == null || data2[n] == null)
				continue;
			if ((n + 1) < data1.length && (data1[n + 1] == null))
				continue;
			if ((n + 1) < data2.length && (data2[n + 1] == null))
				continue;

			if (n < (data1.length - 1)) {
				final float x1 = (float) (area.getMinX() + i * this.periodWidth + this.periodWidth * 0.5);
				final float x2 = (float) (x1 + this.periodWidth);
				final float ya1 = (float) (axis.valueToJava2D(data1[n].doubleValue(), area));
				final float ya2 = (float) (axis.valueToJava2D(data1[n + 1].doubleValue(), area));
				final float yb1 = (float) (axis.valueToJava2D(data2[n].doubleValue(), area));
				final float yb2 = (float) (axis.valueToJava2D(data2[n + 1].doubleValue(), area));

				final Point2D point = getIntersectPoint(x1, ya1, x2, ya2, x1, yb1, x2, yb2);
				if (point != null) {
					final GeneralPath left = new GeneralPath();
					left.moveTo(x1, ya1);
					left.lineTo((float)point.getX(), (float)point.getY());
					left.lineTo(x1, yb1);
					left.closePath();

					if (ya1 > yb1)
						g2.setPaint(negativePaint);
					else
						g2.setPaint(positivePaint);

					g2.fill(left);

					final GeneralPath right = new GeneralPath();
					right.moveTo(x2, ya2);
					right.lineTo((float)point.getX(), (float)point.getY());
					right.lineTo(x2, yb2);
					right.closePath();

					if (ya2 > yb2)
						g2.setPaint(negativePaint);
					else
						g2.setPaint(positivePaint);

					g2.fill(right);
				} else {
					final GeneralPath path = new GeneralPath();
					path.moveTo(x1, ya1);
					path.lineTo(x2, ya2);
					path.lineTo(x2, yb2);
					path.lineTo(x1, yb1);
					path.closePath();

					if (ya1 > yb1)
						g2.setPaint(negativePaint);
					else
						g2.setPaint(positivePaint);

					g2.fill(path);
				}

			}
		}
		g2.setComposite(originalComposite);
	}

	private static Point2D getIntersectPoint(
		final double x1, final double y1, final double x2, final double y2,
		final double x3, final double y3, final double x4, final double y4) {

		if (!Line2D.linesIntersect(x1, y1, x2, y2, x3, y3, x4, y4))
			return null;

		final double a = (y2 - y1) / (x2 - x1);
		final double b = (y4 - y3) / (x4 - x3);

		// 線1が垂直の場合
		if (x1 != x2 && x3 != x4) {
			final double x = ((x1 * a) - y1 - (x3 * b) + y3) / (a - b);
			final double y = a * (x - x1) + y1;
			return new Point2D.Double(x, y);
		} else if (x1 == x2 && x3 != x4) {
			final double x = x1;
			final double y = b * (x - x3) + y3;
			return new Point2D.Double(x, y);
		} else if (x1 != x2 && x3 == x4) {
			final double x = x3;
			final double y = a * (x - x1) + y1;
			return new Point2D.Double(x, y);
		}

		return null;
	}

	/**
	 * 折れ線グラフを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 * @param area 描画領域
	 * @param axis 目盛り情報
	 * @param data 描画データ
	 */
	private void drawLine(final Graphics2D g2, final Rectangle2D area, final NumberAxis axis, final Number[] data) {
		// 線グラフの場合はひとつ前から開始します
		for (int i = -1; i < (this.period + 1); i++) {
			final int n = this.start + i;
			// 処理をすべきか検証します
			if (n < 0 || n >= (data.length - 1))
				continue;
			if (data[n] == null)
				continue;
			if ((n + 1) < data.length && (data[n + 1] == null))
				continue;

			// 座標を算出します
			final double x1 = area.getMinX() + i * this.periodWidth + this.periodWidth * 0.5;
			final double x2 = x1 + this.periodWidth;
			final double y1 = axis.valueToJava2D(data[n].doubleValue(), area);
			final double y2 = axis.valueToJava2D(data[n + 1].doubleValue(), area);
			// 描画します
			g2.draw(new Line2D.Double(x1, y1, x2, y2));
		}
	}

	private void drawLine(final Graphics2D g2, final Rectangle2D area, final NumberAxis axis, final Number[] data, final UpDownColorType colors) {
		// 線グラフの場合はひとつ前から開始します
		for (int i = -1; i < (period + 1); i++) {
			final int n = start + i;
			// 処理をすべきか検証します
			if (n < 0 || n >= (data.length - 1))
				continue;
			if (data[n] == null)
				continue;
			if ((n + 1) < data.length && (data[n + 1] == null))
				continue;

			// 座標を算出します
			final double x1 = area.getMinX() + i * periodWidth + periodWidth * 0.5;
			final double x2 = x1 + periodWidth;
			final double y1 = axis.valueToJava2D(data[n].doubleValue(), area);
			final double y2 = axis.valueToJava2D(data[n + 1].doubleValue(), area);
			final boolean up = data[n].doubleValue() < data[n + 1].doubleValue();
			g2.setColor(up ? colors.getUpLineColor() : colors.getDownLineColor());
			// 描画します
			g2.draw(new Line2D.Double(x1, y1, x2, y2));
		}
	}

	/**
	 * OHLC バーを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 */
	private void drawBar(final Graphics2D g2) {
		final Rectangle2D priceArea = this.areas[PRICE];
		final NumberAxis priceAxis = this.axes[PRICE];
		final UpDownColorType colors = this.settings.updownLineColors;

		for (int i = 0; i < this.period; i++) {
			final int n = this.start + i;
			if (n < 0 || n >= this.dataset.getCount())
				continue;
			if (this.dataset.open[n] == null || this.dataset.high[n] == null || this.dataset.low[n] == null || this.dataset.close[n] == null)
				continue;

			final double openY = priceAxis.valueToJava2D(this.dataset.open[n].doubleValue(), priceArea);
			final double highY = priceAxis.valueToJava2D(this.dataset.high[n].doubleValue(), priceArea);
			final double lowY = priceAxis.valueToJava2D(this.dataset.low[n].doubleValue(), priceArea);
			final double closeY = priceAxis.valueToJava2D(this.dataset.close[n].doubleValue(), priceArea);

			final boolean down = closeY > openY;
			g2.setColor(down ? colors.getDownLineColor() : colors.getUpLineColor());

			final double x = priceArea.getMinX() + i * this.periodWidth + this.periodWidth * 0.5;
			g2.draw(new Line2D.Double(x, highY, x, lowY));
			final double delta = 2.0;
			g2.draw(new Line2D.Double(x - delta, openY, x, openY));
			g2.draw(new Line2D.Double(x, closeY, x + delta, closeY));
		}
	}

	/**
	 * ローソク足を描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 */
	private void drawCandlestick(final Graphics2D g2) {
		final Rectangle2D priceArea = this.areas[PRICE];
		final NumberAxis priceAxis = this.axes[PRICE];
		final UpDownColorType colors = this.settings.updownBarColors;

		for (int i = 0; i < this.period; i++) {
			final int n = this.start + i;
			if (n < 0 || n >= this.dataset.getCount())
				continue;
			if (this.dataset.open[n] == null || this.dataset.high[n] == null || this.dataset.low[n] == null || this.dataset.close[n] == null)
				continue;

			// 座標を算出します
			final double openY = priceAxis.valueToJava2D(this.dataset.open[n].doubleValue(), priceArea);
			final double highY = priceAxis.valueToJava2D(this.dataset.high[n].doubleValue(), priceArea);
			final double lowY = priceAxis.valueToJava2D(this.dataset.low[n].doubleValue(), priceArea);
			final double closeY = priceAxis.valueToJava2D(this.dataset.close[n].doubleValue(), priceArea);
			final double maxOpenCloseY = Math.max(openY, closeY);
			final double minOpenCloseY = Math.min(openY, closeY);

			final boolean down = closeY > openY;

			// ヒゲを描画します
			final double x = priceArea.getMinX() + i * this.periodWidth + this.periodWidth * 0.5;
			g2.setColor(down ? colors.getDownLineColor() : colors.getUpLineColor());
			g2.draw(new Line2D.Double(x, highY, x, maxOpenCloseY));
			g2.draw(new Line2D.Double(x, lowY, x, minOpenCloseY));

			// 本体を描画します
			final double x1 = priceArea.getMinX() + i * this.periodWidth + ((this.periodWidth - this.itemWidth) * 0.5);
			final double x2 = x1 + this.itemWidth;

			if (down) {
				// 陰線
				g2.setPaint(
					new GradientPaint(
						(float) x1, (float) minOpenCloseY, colors.getDownColor1(),
						(float) x2, (float) maxOpenCloseY, colors.getDownColor2()
					)
				);
			} else {
				// 陽線
				g2.setPaint(
					new GradientPaint(
						(float) x1, (float) minOpenCloseY, colors.getUpColor1(),
						(float) x2, (float) maxOpenCloseY, colors.getUpColor2()
					)
				);
			}

			final Shape body = new Rectangle2D.Double(x1, minOpenCloseY, this.itemWidth, maxOpenCloseY - minOpenCloseY);
			g2.fill(body);

			g2.setColor(down ? colors.getDownLineColor() : colors.getUpLineColor());
			g2.draw(body);
		}
	}

	/**
	 * 棒足を描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 */
	private void drawBoh(final Graphics2D g2) {
		final Rectangle2D priceArea = this.areas[PRICE];
		final NumberAxis priceAxis = this.axes[PRICE];
		final UpDownColorType colors = this.settings.updownBarColors;

		for (int i = 0; i < this.period; i++) {
			final int n = this.start + i;
			if (n < 0 || n >= this.dataset.getCount())
				continue;
			if (this.dataset.open[n] == null || this.dataset.high[n] == null || this.dataset.low[n] == null || this.dataset.close[n] == null)
				continue;

			// 座標を算出します
			final double highY = priceAxis.valueToJava2D(this.dataset.high[n].doubleValue(), priceArea);
			final double lowY = priceAxis.valueToJava2D(this.dataset.low[n].doubleValue(), priceArea);
			final double maxOpenCloseY = Math.max(highY, lowY);
			final double minOpenCloseY = Math.min(highY, lowY);

			final boolean down = this.dataset.close[n].doubleValue() < this.dataset.open[n].doubleValue();

			// 本体を描画します
			final double x1 = priceArea.getMinX() + i * this.periodWidth + ((this.periodWidth - this.itemWidth) * 0.5);
			final double x2 = x1 + this.itemWidth;

			if (down) {
				// 陰線
				g2.setPaint(
					new GradientPaint(
						(float) x1, (float) minOpenCloseY, colors.getDownColor1(),
						(float) x2, (float) maxOpenCloseY, colors.getDownColor2()
					)
				);
			} else {
				// 陽線
				g2.setPaint(
					new GradientPaint(
						(float) x1, (float) minOpenCloseY, colors.getUpColor1(),
						(float) x2, (float) maxOpenCloseY, colors.getUpColor2()
					)
				);
			}

			final Shape body = new Rectangle2D.Double(x1, minOpenCloseY, this.itemWidth, maxOpenCloseY - minOpenCloseY);
			g2.fill(body);

			g2.setColor(down ? colors.getDownLineColor() : colors.getUpLineColor());
			g2.draw(body);
		}
	}

	/**
	 * カギ足を描画します。
	 * 
	 * @param g2
	 */
	private void drawKagi(final Graphics2D g2) {
		final Step[] data = dataset.kagi.toArray(new Step[]{});
		final int s = Math.max(start, 0);
		final int _start = Step.indexOf(data, dataset.date[s]);
		final int _end = Math.min(Step.lastIndexOf(data, dataset.date[Math.min(start + period, dataset.getCount() - 1)]) + 1, data.length - 1);

		final UpDownColorType colors = settings.updownLineColors;
		boolean up = data[_start].open < data[_start].high;
		g2.setColor(up ? colors.getUpLineColor() : colors.getDownLineColor());

		final Rectangle2D area = this.areas[PRICE];
		final NumberAxis axis = this.axes[PRICE];
		double x1 = area.getMinX();

		for (int i = _start; i <= _end; i++) {
			final double high = data[i].high;
			final double low = data[i].low;

			// 縦線
			final double x2 = area.getMinX() + (DateArrayUtils.indexOf(dataset.date, data[i].openDate) - s) * periodWidth + periodWidth * 0.5;
			final double y1 = axis.valueToJava2D(high, area);
			final double y2 = axis.valueToJava2D(low, area);
			g2.draw(new Line2D.Double(x2, y1, x2, y2));

			if (data[i].open < high) {
				// 横線
				g2.draw(new Line2D.Double(x1, y2, x2, y2));
				// トレンド反転の縦線
				if (!up) {
					final double prevHigh = data[i - 1].high;
					if (high > prevHigh) {
						g2.setColor(colors.getUpLineColor());
						g2.draw(new Line2D.Double(x2, y1, x2, axis.valueToJava2D(prevHigh, area)));
						up = true;
					}
				}
			} else {
				// 横線
				g2.draw(new Line2D.Double(x1, y1, x2, y1));
				// トレンド反転の縦線
				if (up) {
					final double prevLow = data[i - 1].low;
					if (low < prevLow) {
						g2.setColor(colors.getDownLineColor());
						g2.draw(new Line2D.Double(x2, axes[PRICE].valueToJava2D(prevLow, areas[PRICE]), x2, y2));
						up = false;
					}
				}
			}
			x1 = x2;
		}
	}

	/**
	 * パラボリックを描画します。
	 * 
	 * @param g2 Graphics2D オブジェクト
	 */
	private void drawSAR(final Graphics2D g2) {
		final Rectangle2D priceArea = this.areas[PRICE];
		final NumberAxis priceAxis = this.axes[PRICE];
		final UpDownColorType colors = this.settings.updownLineColors;

		double w = Math.min(20, this.itemWidth);
		final Object originalStrokeControl = g2.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL);
		g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);

		for (int i = 0; i < this.period; i++) {
			final int n = this.start + i;
			if (n < 0 || n >= this.dataset.getCount())
				continue;
			if (this.dataset.techClose[n] == null || this.dataset.sar[n] == null)
				continue;

			// 座標を算出します
			final double x1 = priceArea.getMinX() + i * this.periodWidth + ((this.periodWidth - w) * 0.5);
			final double y = priceAxis.valueToJava2D(this.dataset.sar[n].doubleValue(), priceArea);
			final double closeY = priceAxis.valueToJava2D(this.dataset.techClose[n].doubleValue(), priceArea);

			final boolean down = closeY > y;
			g2.setColor(down ? colors.getDownLineColor() : colors.getUpLineColor());
			g2.drawOval((int) x1, (int) (y  - w * 0.5), (int) w, (int) w);
		}
		g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, originalStrokeControl);
	}

	private int vpLower;
	private int vpStep;

	/**
	 * 価格帯出来高を処理します。
	 */
	private void calculateVolumePriceHistogram() {
		final Range range = this.vpPriceAxis.getRange();
		this.vpStep = (int) Math.ceil(range.getLength() * ((double) 100 / (double) settings.timeSeries.vpCount) / 100);// 価格帯の単位
		this.vpLower = (int) Math.floor((range.getLower() / this.vpStep) * this.vpStep);
		int count = (int) Math.floor(((range.getUpper() - this.vpLower) / this.vpStep) + 2);	// 価格帯の数
//		if (count <= 0)
//			count = 1;

		this.vp = new Number[count];

		for (int i = this.start; i < (this.start + this.period); i++) {
			if (i < 0 || i >= this.dataset.getCount())
				continue;
			if (this.dataset.close[i] == null)
				continue;

			int n = (int) Math.ceil((this.dataset.close[i].doubleValue() - this.vpLower) / this.vpStep);
//			if (n >= 0) {
				if (this.vp[n] == null)
					this.vp[n] = this.dataset.volume[i];
				else
					this.vp[n] = this.vp[n].doubleValue() + this.dataset.volume[i].doubleValue();
//			}
		}

		this.vpVolumeAxis.prepare(new Number[][] { this.vp });
		this.vpVolumeAxis.autoAdjustRange(0, this.vp.length);

	}

}
