All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.d3x.morpheus.viz.jfree.JFXyDataset Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014-2018 D3X Systems - All Rights Reserved
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.d3x.morpheus.viz.jfree;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import java.util.stream.IntStream;

import org.jfree.data.xy.AbstractXYDataset;
import org.jfree.data.xy.IntervalXYDataset;
import org.jfree.data.xy.TableXYDataset;
import org.jfree.data.xy.XYZDataset;

import com.d3x.morpheus.array.Array;
import com.d3x.morpheus.frame.DataFrame;
import com.d3x.morpheus.viz.chart.xy.XyDataset;

/**
 * An implementation of the Morpheus XyDataset interface and various JFreeChart interfaces to support plotting of a DataFrame in JFreeChart
 *
 * @author Xavier Witdouck
 *
 * 

This is open source software released under the Apache 2.0 License

*/ class JFXyDataset extends AbstractXYDataset implements XyDataset, IntervalXYDataset, TableXYDataset, XYZDataset { private static final Double NAN = Double.NaN; private DataFrame frame; private Array colOrdinals; private Supplier> domainType; private IntFunction domainValueFunction; private Consumer> refreshHandler; private Function lowerDomainIntervalFunction; private Function upperDomainIntervalFunction; /** * Constructor * @param refreshHandler the refresh handler for this model */ private JFXyDataset(Consumer> refreshHandler) { this.refreshHandler = refreshHandler; this.refresh(); } /** * Returns a newly created model using a frame supplier where the domain is presented by the DataFrame row keys * @param frameSupplier the DataFrame supplier for this model * @param the domain key type * @param the series key type * @return the newly created model */ static JFXyDataset of(Supplier> frameSupplier) { return new JFXyDataset<>(dataset -> { try { final DataFrame frame = frameSupplier.get(); if (frame != null) { final Supplier> domainType = () -> frame.rows().keyClass(); final Array colOrdinals = Array.of(IntStream.range(0, frame.colCount()).toArray()); dataset.update(frame, colOrdinals, domainType, rowIndex -> frame.rows().key(rowIndex)); } else { dataset.clear(true); } } catch (Exception ex) { ex.printStackTrace(); } }); } /** * Returns a newly created model using a frame supplier where the domain is presented by a column in the DataFrame * @param domainAxisKey the DataFrame column key for the domain * @param frameSupplier the DataFrame supplier for this model * @param the domain key type * @param the series key type * @return the newly created model */ @SuppressWarnings("unchecked") static JFXyDataset of(S domainAxisKey, Supplier> frameSupplier) { return new JFXyDataset<>(dataset -> { try { final DataFrame frame = frameSupplier.get(); if (frame != null) { final int domainAxisColOrdinal = frame.cols().ordinal(domainAxisKey); final Supplier> domainType = () -> (Class)frame.cols().type(domainAxisKey); final Array colOrdinals = Array.of(IntStream.range(0, frame.colCount()).filter(i -> i != domainAxisColOrdinal).toArray()); dataset.update(frame, colOrdinals, domainType, rowIndex -> frame.getValueAt(rowIndex, domainAxisColOrdinal)); } else { dataset.clear(true); } } catch (Exception ex) { ex.printStackTrace(); } }); } /** * Updates this model with the DataFrame, series column ordinals and domain value function * @param frame the DataFrame to accept * @param colOrdinals the series column ordinals * @param domainValueFunction the domain value function */ private void update(DataFrame frame, Array colOrdinals, Supplier> domainType, IntFunction domainValueFunction) { try { this.frame = frame; this.domainType = domainType; this.colOrdinals = colOrdinals; this.domainValueFunction = domainValueFunction; } finally { fireDatasetChanged(); } } @Override public void refresh() { this.refreshHandler.accept(this); } @Override public final boolean isEmpty() { return frame == null || frame.rowCount() == 0 || colOrdinals == null || colOrdinals.length() == 0; } @Override public final void clear(boolean notify) { this.frame = null; this.colOrdinals = null; if (notify) { fireDatasetChanged(); } } @Override public Class domainType() { return isEmpty() ? null : domainType.get(); } @Override @SuppressWarnings("unchecked") public final DataFrame frame() { return (DataFrame)frame; } @Override public final IntFunction domainFunction() { return domainValueFunction; } @Override public final boolean contains(S seriesKey) { return !isEmpty() && frame.cols().contains(seriesKey); } @Override public final XyDataset withLowerDomainInterval(Function lowerIntervalFunction) { this.lowerDomainIntervalFunction = lowerIntervalFunction; return this; } @Override public final XyDataset withUpperDomainInterval(Function upperIntervalFunction) { this.upperDomainIntervalFunction = upperIntervalFunction; return null; } @Override public final int getItemCount() { return isEmpty() ? 0 : frame.rowCount(); } @Override public final int getSeriesCount() { return isEmpty() ? 0 : colOrdinals.length(); } @Override public final S getSeriesKey(int series) { if (isEmpty()) { return null; } else { final int colOrdinal = colOrdinals.getInt(series); return frame.cols().key(colOrdinal); } } @Override public final int getItemCount(int series) { return isEmpty() ? 0 : frame.rowCount(); } @Override public final Number getZ(int series, int item) { return null; } @Override public final double getZValue(int series, int item) { return 0; } @Override public final Number getX(int series, int item) { final double value = getXValue(series, item); return Double.isNaN(value) ? NAN : value; } @Override public final Number getY(int series, int item) { final double value = getYValue(series, item); return Double.isNaN(value) ? NAN : value; } @Override public final Number getStartX(int series, int item) { final double value = getStartXValue(series, item); return Double.isNaN(value) ? NAN : value; } @Override public final Number getEndX(int series, int item) { final double value = getEndXValue(series, item); return Double.isNaN(value) ? NAN : value; } @Override public final Number getStartY(int series, int item) { final double value = getStartYValue(series, item); return Double.isNaN(value) ? NAN : value; } @Override public Number getEndY(int series, int item) { final double value = getEndYValue(series, item); return Double.isNaN(value) ? NAN : value; } @Override public final double getXValue(int series, int item) { if (isEmpty()) { return Double.NaN; } else { final X domainValue = domainValueFunction.apply(item); return toNumber(domainValue).doubleValue(); } } @Override public final double getYValue(int series, int item) { if (isEmpty()) { return Double.NaN; } else { final int colOrdinal = colOrdinals.getInt(series); return frame.getDoubleAt(item, colOrdinal); } } @Override public final double getStartXValue(int series, int item) { if (isEmpty()) { return Double.NaN; } else { final X domainValue = domainValueFunction.apply(item); if (lowerDomainIntervalFunction != null) { final X startValueKey = lowerDomainIntervalFunction.apply(domainValue); final Number startValue = toNumber(startValueKey); return startValue != null ? startValue.doubleValue() : Double.NaN; } else { final Number startValue = toNumber(domainValue); return startValue != null ? startValue.doubleValue() : Double.NaN; } } } @Override public final double getEndXValue(int series, int item) { if (isEmpty()) { return Double.NaN; } else { final X domainValue = domainValueFunction.apply(item); if (upperDomainIntervalFunction != null) { final X endValueKey = upperDomainIntervalFunction.apply(domainValue); final Number endValue = toNumber(endValueKey); return endValue != null ? endValue.doubleValue() : Double.NaN; } else { final Number endValue = toNumber(domainValue); return endValue != null ? endValue.doubleValue() : Double.NaN; } } } @Override public final double getStartYValue(int series, int item) { if (isEmpty()) { return Double.NaN; } else { final int colOrdinal = colOrdinals.getInt(series); return frame.getDoubleAt(item, colOrdinal); } } @Override public final double getEndYValue(int series, int item) { if (isEmpty()) { return Double.NaN; } else { final int colOrdinal = colOrdinals.getInt(series); return frame.getDoubleAt(item, colOrdinal); } } @Override public void fireDatasetChanged() { super.fireDatasetChanged(); } /** * Returns a numeric representation of the value argument * @param value the value to turn into a number * @return the numeric value */ private Number toNumber(Object value) { if (value == null) { return Double.NaN; } else if (value instanceof Number) { return (Number)value; } else if (value instanceof Date) { return ((Date)value).getTime(); } else if (value instanceof LocalDate) { return ((LocalDate)value).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(); } else if (value instanceof LocalDateTime) { return ((LocalDateTime) value).toInstant(ZoneOffset.UTC).toEpochMilli(); } else if (value instanceof ZonedDateTime) { return ((ZonedDateTime)value).toInstant().toEpochMilli(); } else if (value instanceof Calendar) { return ((Calendar)value).getTimeInMillis(); } else { throw new IllegalArgumentException("Unable to convert value to a Number: " + value); } } }