package org.chartsy.main.data; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.Point2D; import java.io.Serializable; import java.text.DecimalFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import javax.swing.event.EventListenerList; import org.chartsy.main.ChartFrame; import org.chartsy.main.ChartFrameListener; import org.chartsy.main.chart.Annotation; import org.chartsy.main.chart.Chart; import org.chartsy.main.chart.Indicator; import org.chartsy.main.chart.Overlay; import org.chartsy.main.events.DatasetEvent; import org.chartsy.main.events.DatasetListener; import org.chartsy.main.intervals.Interval; import org.chartsy.main.intervals.MonthlyInterval; import org.chartsy.main.intervals.WeeklyInterval; import org.chartsy.main.managers.DataProviderManager; import org.chartsy.main.managers.DatasetUsage; import org.chartsy.main.utils.Bounds; import org.chartsy.main.utils.Range; import org.chartsy.main.utils.RectangleInsets; import org.chartsy.main.utils.SerialVersion; /** * * @author viorel.gheba */ public class ChartData implements Serializable, ChartFrameListener { private static final long serialVersionUID = SerialVersion.APPVERSION; public static final int MIN_ITEMS = 40; public static final int MAX_ITEMS = 1000; public static final RectangleInsets axisOffset = new RectangleInsets(5.0, 5.0, 5.0, 5.0); public static final RectangleInsets dataOffset = new RectangleInsets(2.0, 20.0, 40.0, 55.0); private Stock stock; private Interval interval; private Chart chart; private String dataProviderName; private String datasetKey; private Dataset visible; private Range visibleRange; private List<Indicator> savedIndicators; private List<Overlay> savedOverlays; private List<Integer> annotationsCount; private List<Annotation> annotations; private int period = -1; private int last = -1; private int size = -1; public ChartData() { } public Stock getStock() { return stock; } public void setStock(Stock stock) { this.stock = stock; } public boolean isStockNull() { return stock == null; } public Interval getInterval() { return interval; } public void setInterval(Interval interval) { this.interval = interval; } public boolean isIntervalNull() { return interval == null; } public boolean updateDataset(Interval newInterval) { /*Dataset newDataset = dataProvider.getDataset(stock, newInterval); if (newDataset != null) { setInterval(newInterval); setDataset(newDataset); setLast(-1); return fireDatasetEvent(new DatasetEvent(this)); }*/ return false; } public void updateDataset() { //fireDatasetEvent(new DatasetEvent(this)); } public Chart getChart() { return chart; } public void setChart(Chart chart) { this.chart = chart; } public boolean isChartNull() { return chart == null; } public DataProvider getDataProvider() { return DataProviderManager.getDefault().getDataProvider(dataProviderName); } public void setDataProviderName(String dataProviderName) { this.dataProviderName = dataProviderName; } public String getDataProviderName() { return dataProviderName; } public boolean isDataProviderNull() { return dataProviderName == null; } public Dataset getDataset() { return DatasetUsage.getInstance().getDatasetFromMemory(datasetKey); } public Dataset getDataset(boolean b) { return getDataset(); } public void setDatasetKey(String datasetKey) { this.datasetKey = datasetKey; size = getDataset().getItemsCount(); } public String getDatasetKey() { return datasetKey; } public boolean isDatasetNull() { return datasetKey == null; } public Dataset getVisible() { return visible; } private void setVisible(Dataset d) { visible = d; } public boolean isVisibleNull() { return visible == null; } public int getPeriod() { return period; } public void setPeriod(int period) { this.period = period; } public int getLast() { return last; } public void setLast(int last) { this.last = last; } public void setSavedIndicators(List<Indicator> list) { savedIndicators = list; } public List<Indicator> getSavedIndicators() { return savedIndicators; } public void clearSavedIndicators() { if (savedIndicators != null) savedIndicators.clear(); savedIndicators = null; } public void setSavedOverlays(List<Overlay> list) { savedOverlays = list; } public List<Overlay> getSavedOverlays() { return savedOverlays; } public void clearSavedOverlays() { if (savedOverlays != null) savedOverlays.clear(); savedOverlays = null; } public void setAnnotationsCount(List<Integer> list) { annotationsCount = list; } public List<Integer> getAnnotationsCount() { return annotationsCount; } public void clearAnnotationsCount() { if (annotationsCount != null) annotationsCount.clear(); annotationsCount = null; } public void setAnnotations(List<Annotation> list) { annotations = list; } public List<Annotation> getAnnotations() { return annotations; } public void clearAnnotations() { if (annotations != null) annotations.clear(); annotations = null; } public void setVisibleRange(Range r) { visibleRange = r; } public Range getVisibleRange() { return visibleRange; } public void calculateRange(ChartFrame chartFrame, List<Overlay> overlays) { Range range = new Range(); if (!isVisibleNull()) { double min = getVisible().getMinNotZero(); double max = getVisible().getMaxNotZero(); range = new Range(min - (max - min) * 0.01, max + (max - min) * 0.01); for (int i = 0; i < overlays.size(); i++) { Overlay overlay = overlays.get(i); if (overlay.isIncludedInRange()) { Range oRange = overlay.getRange(chartFrame, overlay.getPrice()); if (oRange != null) { if (oRange.getLowerBound() > 0) range = Range.expandToInclude(range, oRange.getLowerBound()); if (!Double.isInfinite(oRange.getUpperBound())) range = Range.expandToInclude(range, oRange.getUpperBound()); } } } } setVisibleRange(range); } public void calculate(ChartFrame chartFrame) { double barWidth = chartFrame.getChartProperties().getBarWidth(); Rectangle rect = chartFrame.getSplitPanel().getBounds(); rect.grow(-2, -2); last = last == -1 ? size : last; period = (int) (rect.getWidth() / (barWidth + 2)); if (period == 0) period = 150; if (period > size) period = size; if (getDataset() != null) { setVisible(getDataset().getVisibleDataset(period, last)); int index = chartFrame.getSplitPanel().getIndex(); chartFrame.getSplitPanel().setIndex(index > period - 1 ? period - 1 : index); chartFrame.updateHorizontalScrollBar(); } } public double[] getDateValues() { if (!isVisibleNull()) { int count = getVisible().getItemsCount(); Interval itrv = getInterval(); double[] list = new double[getVisible().getItemsCount()]; if (!itrv.isIntraDay()) { Calendar cal = Calendar.getInstance(); cal.setFirstDayOfWeek(Calendar.MONDAY); cal.setTimeInMillis(getVisible().getTimeAt(0)); list[0] = 0; if (itrv instanceof MonthlyInterval) { int year = cal.get(Calendar.YEAR); for (int i = 2; i < count + 1; i++) { cal.setTimeInMillis(getVisible().getTimeAt(i - 1)); if (year != cal.get(Calendar.YEAR)) { list[i - 1] = i - 1; year = cal.get(Calendar.YEAR); } else { list[i - 1] = -1; } } } else { int month = cal.get(Calendar.MONTH); for (int i = 2; i < count + 1; i++) { cal.setTimeInMillis(getVisible().getTimeAt(i - 1)); if (month != cal.get(Calendar.MONTH)) { list[i - 1] = i - 1; month = cal.get(Calendar.MONTH); } else { list[i - 1] = -1; } } } } else { for (int i = 0; i < count; i++) { if (i % 10 == 0) list[i] = i; else list[i] = -1; } } return list; } return new double[0]; } public boolean isFirstWorkingDayOfMonth(long time) { Calendar calendar = Calendar.getInstance(); calendar.setFirstDayOfWeek(Calendar.MONDAY); calendar.setTimeInMillis(time); if (getInterval() instanceof WeeklyInterval) { int day = calendar.get(Calendar.DAY_OF_WEEK); int week = getFirstWeekMondayOfMonth( calendar.get(Calendar.MONTH), calendar.get(Calendar.YEAR)); if (week == calendar.get(Calendar.WEEK_OF_MONTH)) { return day == Calendar.MONDAY; } return false; } else { int week = getFirstWorkingWeekOfMonth( calendar.get(Calendar.MONTH), calendar.get(Calendar.YEAR)); if (calendar.get(Calendar.WEEK_OF_MONTH) == week) return calendar.get(Calendar.DAY_OF_WEEK) == getFirstWorkingDayOfMonth( calendar.get(Calendar.MONTH), calendar.get(Calendar.YEAR)); else return false; } } private int getFirstWeekMondayOfMonth(int month, int year) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONDAY, month); calendar.set(Calendar.DAY_OF_MONTH, 1); if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY) { return calendar.get(Calendar.WEEK_OF_MONTH); } else { while (calendar.get(Calendar.DAY_OF_WEEK) != Calendar.MONDAY) calendar.add(Calendar.DAY_OF_MONTH, 1); return calendar.get(Calendar.WEEK_OF_MONTH); } } private static int getFirstWorkingWeekOfMonth(int month, int year) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONDAY, month); calendar.set(Calendar.DAY_OF_MONTH, 1); switch (calendar.get(Calendar.DAY_OF_WEEK)) { case Calendar.SATURDAY: calendar.add(Calendar.DAY_OF_MONTH, 2); return calendar.get(Calendar.WEEK_OF_MONTH); case Calendar.SUNDAY: calendar.add(Calendar.DAY_OF_MONTH, 1); return calendar.get(Calendar.WEEK_OF_MONTH); default: return calendar.get(Calendar.WEEK_OF_MONTH); } } private int getFirstWorkingDayOfMonth(int month, int year) { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONDAY, month); calendar.set(Calendar.DAY_OF_MONTH, 1); switch (calendar.get(Calendar.DAY_OF_WEEK)) { case Calendar.SATURDAY: calendar.add(Calendar.DAY_OF_MONTH, 2); return calendar.get(Calendar.DAY_OF_WEEK); case Calendar.SUNDAY: calendar.add(Calendar.DAY_OF_MONTH, 1); return calendar.get(Calendar.DAY_OF_WEEK); default: return calendar.get(Calendar.DAY_OF_WEEK); } } private static DecimalFormat D1 = new DecimalFormat("0.###"); private static DecimalFormat D2 = new DecimalFormat("0.0"); public double[] getYValues(Rectangle rectangle, Range range, int fontHeight) { int count = 15; while (((rectangle.height / count) < (fontHeight + 20)) && (count > -2)) count--; double rangeMin = range.getLowerBound(); double rangeMax = range.getUpperBound(); double vRange = rangeMax - rangeMin; double rangeUnit = vRange / count; int roundedExponent = (int)Math.round(Math.log10(rangeUnit)) - 1; double factor = Math.pow(10, -roundedExponent); int adjustedValue = (int)(rangeUnit * factor); rangeUnit = (double)adjustedValue / factor; if (rangeUnit < 0.001) { rangeUnit = 0.001d; } else if (rangeUnit >= 0.001 && rangeUnit < 0.005) { String unitStr = D1.format(rangeUnit); try { rangeUnit = D1.parse(unitStr.trim()).doubleValue(); } catch (ParseException ex) { } } else if (rangeUnit >= 0.005 && rangeUnit < 1) { String unitStr = D2.format(rangeUnit); try { rangeUnit = D2.parse(unitStr.trim()).doubleValue(); } catch (ParseException ex) { } } rangeMin = (int)(rangeMin / rangeUnit) * rangeUnit; count = (int)(vRange / rangeUnit); if (count + 2 > 0) { double[] result = new double[count + 2]; for (int i = 0; i < count + 2; i++) result[i] = rangeMin + rangeUnit * i; return result; } else { List<Double> list = getPriceValues(rectangle, range); double[] result = new double[list.size()]; for (int i = 0; i < list.size(); i++) result[i] = list.get(i).doubleValue(); return result; } } public List<Double> getPriceValues(Rectangle rect, Range range) { List<Double> values = new ArrayList<Double>(); double diff = range.getUpperBound() - range.getLowerBound(); if (diff > 10) { int step = (int) (diff / 10) + 1; double low = Math.ceil(range.getUpperBound() - (diff / 10) * 9); for (double i = low; i <= range.getUpperBound(); i += step) { values.add(new Double(i)); } } else { double step = diff / 10; for (double i = range.getLowerBound(); i <= range.getUpperBound(); i += step) { values.add(new Double(i)); } } return values; } public double calculateWidth(int width) { int count = getDataset().getItemsCount(); int items = getPeriod(); double w = width / items; return w * count; } public Point2D.Double valueToJava2D(final double xvalue, final double yvalue, Rectangle bounds, Range range, boolean isLog) { double px = getX(xvalue, bounds); double py = getY(yvalue, bounds, range, isLog); Point2D.Double p = new Point2D.Double(px, py); return p; } public Point2D getPoint(double x, double y, Range range, Rectangle rect, boolean isLog) { return new Point2D.Double(getX(x, rect), getY(y, rect, range, isLog)); } public double getX(double value, Rectangle rect) { return rect.getMinX() + (((value + 0.5D) / (double) getPeriod()) * rect.getWidth()); } private double getY(double value, Rectangle rect, Range range) { return rect.getMinY() + (range.getUpperBound() - value) / (range.getUpperBound() - range.getLowerBound()) * rect.getHeight(); } public double getY(double value, Rectangle rect, Range range, boolean isLog) { if (isLog) return getLogY(value, rect, range); return getY(value, rect, range); } private double getLogY(double value, Rectangle rect, Range range) { double base = 0; if (range.getLowerBound() < 0) base = Math.abs(range.getLowerBound()) + 1.0D; double scale = (rect.getHeight() / (Math.log(range.getUpperBound() + base) - Math.log(range.getLowerBound() + base))); return rect.getMinY() + Math.round( (Math.log(range.getUpperBound() + base) - Math.log(value + base)) * scale); } public int getIndex(int x, Rectangle rect) { return getIndex(x, 1, rect); } public int getIndex(int x, int y, Rectangle rect) { return getIndex(new Point(x, y), rect); } public int getIndex(Point p, Rectangle rect) { int index = -1; int items = getPeriod(); double w = rect.getWidth() / items; for (int i = 0; i < items; i++) { Bounds r = new Bounds(rect.getMinX() + (i * w), 0, w, 10); if (r.contains(p.x, 1)) { index = i; break; } } return index; } private transient EventListenerList datasetListeners; private EventListenerList datasetListeners() { if (datasetListeners == null) datasetListeners = new EventListenerList(); return datasetListeners; } public void addIndicatorsDatasetListeners(DatasetListener listener) { datasetListeners().add(DatasetListener.class, listener); } public void removeIndicatorsDatasetListeners(DatasetListener listener) { datasetListeners().remove(DatasetListener.class, listener); } public void removeAllIndicatorsDatasetListeners() { DatasetListener[] listeners = datasetListeners().getListeners(DatasetListener.class); for (int i = 0; i < listeners.length; i++) { if (listeners[i] instanceof Indicator) datasetListeners().remove(DatasetListener.class, listeners[i]); } } public void addOverlaysDatasetListeners(DatasetListener listener) { datasetListeners().add(DatasetListener.class, listener); } public void removeOverlaysDatasetListeners(DatasetListener listener) { datasetListeners().remove(DatasetListener.class, listener); } public void removeAllOverlaysDatasetListeners() { DatasetListener[] listeners = datasetListeners().getListeners(DatasetListener.class); for (int i = 0; i < listeners.length; i++) { if (listeners[i] instanceof Overlay) datasetListeners().remove(DatasetListener.class, listeners[i]); } } public boolean fireDatasetEvent(DatasetEvent evt) { DatasetListener[] listeners = datasetListeners().getListeners(DatasetListener.class); for (int i = 0; i < listeners.length; i++) listeners[i].datasetChanged(evt); return true; } @Override public void stockChanged(Stock newStock) { setStock(newStock); } @Override public void intervalChanged(Interval newInterval) { setInterval(newInterval); } @Override public void chartChanged(Chart newChart) { setChart(newChart); } @Override public void indicatorAdded(Indicator indicator) { } @Override public void indicatorRemoved(Indicator indicator) { } @Override public void overlayAdded(Overlay overlay) { } @Override public void overlayRemoved(Overlay overlay) { } @Override public double zoomIn(double barWidth) { double newWidth = barWidth + 1; int i = (int) ((period * barWidth) / newWidth); newWidth = i < MIN_ITEMS ? barWidth : newWidth; return newWidth; } @Override public double zoomOut(double barWidth) { double newWidth = barWidth - 1; int i = (int) ((period * barWidth) / newWidth); newWidth = i > getDataset().getItemsCount() ? barWidth : newWidth; return newWidth; } @Override public void datasetKeyChanged(String datasetKey) { setDatasetKey(datasetKey); } }