/*
* Copyright 2014 TWO SIGMA OPEN SOURCE, LLC
*
* 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.twosigma.beaker.chart.xychart;
import com.twosigma.beaker.chart.Color;
import com.twosigma.beaker.chart.xychart.plotitem.Line;
import com.twosigma.beaker.chart.xychart.plotitem.Points;
import com.twosigma.beaker.chart.xychart.plotitem.XYGraphics;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
public class SimpleTimePlot extends TimePlot{
private List<Map<String, Object>> data;
private String timeColumn = "time";
private List<String> columns;
private List<String> displayNames;
private List<Object> colors;
private boolean displayLines = true;
private boolean displayPoints = false;
public SimpleTimePlot(List<Map<String, Object>> data, List<String> columns) {
this(null, data, columns);
}
public SimpleTimePlot(Map<String, Object> parameters,
List<Map<String, Object>> data,
List<String> columns) {
this.data = data;
this.columns = columns;
//default values
setUseToolTip(true);
setShowLegend(true);
setXLabel("Time");
if (parameters != null) {
for (Map.Entry<String, Object> entry : parameters.entrySet()) {
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
ReflectionUtils.set(this, fieldName, fieldValue);
}
}
//default the names for the lines and points to the same as the column
if (displayNames == null || displayNames.size() == 0) {
displayNames = columns;
}
reinitialize();
}
private List<Color> getChartColors() {
List<Color> chartColors = new ArrayList<>();
if (colors != null) {
for (int i = 0; i < columns.size(); i++) {
if (i < colors.size()) {
chartColors.add(createChartColor(colors.get(i)));
}
}
}
return chartColors;
}
private Color createChartColor(Object color) {
if (color instanceof Color) {
return (Color) color;
}
if (color instanceof java.awt.Color) {
return new Color((java.awt.Color) color);
}
if (color instanceof List) {
try {
return new Color((int) ((List) color).get(0),
(int) ((List) color).get(1),
(int) ((List) color).get(2));
} catch (IndexOutOfBoundsException x) {
throw new RuntimeException("Color list too short");
}
}
String colorAsStr = (String) color;
if (colorAsStr.indexOf("#") == 0) {
return Color.decode(colorAsStr);
}
return colorFromName(colorAsStr);
}
private Color colorFromName(String color) {
try {
Field field = Class.forName("com.twosigma.beaker.chart.Color").getField(color);
return (Color) field.get(null);
} catch (ClassNotFoundException | IllegalAccessException | NoSuchFieldException x) {
throw new RuntimeException(String.format("Can not parse color '%s'", color), x);
}
}
private void reinitialize() {
List<XYGraphics> graphics = getGraphics();
filter(graphics, new Predicate<XYGraphics>() {
public boolean test(XYGraphics graphic) {
return !(graphic instanceof Line || graphic instanceof Points);
}
});
List<Object> xs = new ArrayList<>();
List<List<Number>> yss = new ArrayList<>();
Set<String> dataColumnsNames = new HashSet<>();
if (data != null && columns != null) {
for (Map<String, Object> row : data) {
dataColumnsNames.addAll(row.keySet());
xs.add(getNumberforTimeColumn(row.get(timeColumn)));
for (int i = 0; i < columns.size(); i++) {
String column = columns.get(i);
if (i >= yss.size()) {
yss.add(new ArrayList<Number>());
}
yss.get(i).add(getNumberforTimeColumn(row.get(column)));
}
}
final HashSet<String> columnsWithoutData = getColumnsWithoutData(dataColumnsNames);
if (!columnsWithoutData.isEmpty()) {
throw new IllegalArgumentException(String.format("Chart data not found for columns: %s", columnsWithoutData));
}
List<Color> colors = getChartColors();
for (int i = 0; i < yss.size(); i++) {
List<Number> ys = yss.get(i);
if (displayLines) {
Line line = new Line();
line.setX(xs);
line.setY(ys);
if (displayNames != null && i < displayNames.size()) {
line.setDisplayName(displayNames.get(i));
} else {
line.setDisplayName(columns.get(i));
}
if(i < colors.size()){
line.setColor(colors.get(i));
}
add(line);
}
if (displayPoints) {
Points points = new Points();
points.setX(xs);
points.setY(ys);
if (displayNames != null && i < displayNames.size()) {
points.setDisplayName(displayNames.get(i));
} else {
points.setDisplayName(columns.get(i));
}
if(i < colors.size()){
points.setColor(colors.get(i));
}
add(points);
}
}
}
}
private Number getNumberforTimeColumn(Object o){
if(o instanceof Number){
return (Number) o;
} else if (o instanceof Date) {
Date date = (Date)o;
return date.getTime();
} else {
throw new IllegalArgumentException("time column accepts numbers or java.util.Date objects");
}
}
public List<Map<String, Object>> getData() {
return data;
}
public void setData(List<Map<String, Object>> data) {
this.data = data;
reinitialize();
}
public List<String> getColumns() {
return columns;
}
public void setColumns(List<String> columns) {
this.columns = columns;
reinitialize();
}
public void setDisplayNames(List<String> displayNames) {
this.displayNames = displayNames;
if (displayNames != null) {
List<XYGraphics> graphics = getGraphics();
int i = 0;
for (XYGraphics graphic : graphics) {
if (graphic instanceof Line) {
graphic.setDisplayName(displayNames.get(++i));
}
}
}
}
public List<String> getDisplayNames() {
return displayNames;
}
public void setColors(List<Object> colors) {
this.colors = colors;
}
public List<Object> getColors() {
return colors;
}
public String getTimeColumn() {
return timeColumn;
}
public void setTimeColumn(String timeColumn) {
this.timeColumn = timeColumn;
reinitialize();
}
public boolean isDisplayLines() {
return displayLines;
}
public void setDisplayLines(boolean displayLines) {
this.displayLines = displayLines;
reinitialize();
}
public boolean isDisplayPoints() {
return displayPoints;
}
public void setDisplayPoints(boolean displayPoints) {
this.displayPoints = displayPoints;
reinitialize();
}
private HashSet<String> getColumnsWithoutData(Set<String> dataColumnsNames) {
final HashSet<String> columnsCopy = new HashSet<>(columns);
columnsCopy.removeAll(dataColumnsNames);
return columnsCopy;
}
private interface Predicate<T> {
boolean test(T o);
}
private static <T> void filter(Collection<T> collection, Predicate<T> predicate) {
if ((collection != null) && (predicate != null)) {
Iterator<T> itr = collection.iterator();
while (itr.hasNext()) {
T obj = itr.next();
if (!predicate.test(obj)) {
itr.remove();
}
}
}
}
private static class ReflectionUtils {
private static Map<String, Method> SETTERS_MAP = new HashMap<String, Method>();
static boolean set(Object object, String fieldName, Object fieldValue) {
Class<?> clazz = object.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, fieldValue);
return true;
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
} catch (Exception expected) {
//nothing to do
}
}
return callSetter(object, fieldName, fieldValue);
}
private static boolean callSetter(Object obj, String fieldName, Object fieldValue) {
String key = String.format("%s.%s(%s)", obj.getClass().getName(),
fieldName, fieldValue.getClass().getName());
Method m = null;
if (!SETTERS_MAP.containsKey(key)) {
m = findMethod(obj, fieldName, fieldValue);
SETTERS_MAP.put(key, m);
} else {
m = SETTERS_MAP.get(key);
}
if (m != null) {
try {
m.invoke(obj, fieldValue);
return true;
} catch (Throwable ignored) {
//expected
}
}
return false;
}
private static Method findMethod(Object obj, String fieldName, Object fieldValue) {
Method m = null;
Class<?> theClass = obj.getClass();
String setter = String.format("set%C%s",
fieldName.charAt(0), fieldName.substring(1));
Class paramType = fieldValue.getClass();
while (paramType != null) {
try {
m = theClass.getMethod(setter, paramType);
return m;
} catch (NoSuchMethodException ex) {
// try on the interfaces of this class
for (Class iface : paramType.getInterfaces()) {
try {
m = theClass.getMethod(setter, iface);
return m;
} catch (NoSuchMethodException ignored) {
}
}
paramType = paramType.getSuperclass();
}
}
return m;
}
}
}