com.d3x.morpheus.viz.google.GXyPlot Maven / Gradle / Ivy
/*
* 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.google;
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.d3x.morpheus.index.Index;
import com.d3x.morpheus.viz.chart.ChartShape;
import com.d3x.morpheus.viz.chart.xy.XyAxes;
import com.d3x.morpheus.viz.chart.xy.XyModel;
import com.d3x.morpheus.viz.chart.xy.XyOrient;
import com.d3x.morpheus.viz.chart.xy.XyPlotBase;
import com.d3x.morpheus.viz.chart.xy.XyRender;
import com.d3x.morpheus.viz.chart.xy.XyTrend;
import com.d3x.morpheus.viz.js.JsObject;
import com.d3x.morpheus.viz.util.ColorModel;
/**
* The XyPlot implementation for the Google Charts adapter
*
* @author Xavier Witdouck
*
* This is open source software released under the Apache 2.0 License
*/
class GXyPlot extends XyPlotBase {
private GXyAxes axes = new GXyAxes(this);
private GXyOrient orient = new GXyOrient();
private GXyModel model = new GXyModel<>(this);
private Map renderMap = new HashMap<>();
private Map trendLineMap = new HashMap<>();
private ChartShape.Provider shapeProvider = new ChartShape.DefaultProvider();
/**
* Constructor
*/
GXyPlot() {
super();
}
@Override
public XyAxes axes() {
return axes;
}
@Override
@SuppressWarnings("unchecked")
public XyModel data() {
return (XyModel)model;
}
@Override
public XyOrient orient() {
return orient;
}
@Override
public XyTrend trend(S seriesKey) {
GTrendLine trend = trendLineMap.get(seriesKey);
if (trend == null) {
trend = new GTrendLine(seriesKey);
trendLineMap.put(seriesKey, trend);
}
return trend;
}
@Override
public XyRender render(int index) {
GXyRender render = renderMap.get(index);
if (render == null) {
render = new GXyRender(this);
renderMap.put(index, render);
}
return render;
}
/**
* Returns the render with a more specific type
* @param index the dataset index
* @return the renderer
*/
private GXyRender renderAt(int index) {
return (GXyRender)render(index);
}
/**
* Enriches the JsObject with options for these axes
* @param options the options to enrich
*/
public void accept(JsObject options) {
options.setIgnoreNulls(true);
options.newAttribute("dataOpacity", 1.0);
options.newAttribute("axisTitlesPosition", "out");
this.axisOptions(options);
this.seriesOptions(options);
this.trendLineOptions(options);
if (renderAt(0).isBars()) {
options.newAttribute("isStacked", renderAt(0).isStacked());
options.newObject("bar", bar -> bar.newAttribute("groupWidth", "70%"));
} else if (renderAt(0).isArea()) {
options.newAttribute("isStacked", renderAt(0).isStacked());
} else if (renderAt(0).isLines()) {
options.newAttribute("lineWidth", 1);
options.newAttribute("curveType", renderAt(0).getCurveType());
}
if (!renderAt(0).isBars()) {
options.newAttribute("orientation", isVertical() ? "vertical" : "horizontal");
}
}
/**
* Enriches the options with axis configuration
* @param options the Javascript options object
*/
private void axisOptions(JsObject options) {
final String domain = renderAt(0).isBars() && isHorizontal() ? "vAxis" : "hAxis";
final String range1 = renderAt(0).isBars() && isHorizontal() ? "hAxis" : "vAxis";
final String range2 = renderAt(0).isBars() && isHorizontal() ? "hAxes" : "vAxes";
options.newObject(domain, axis -> {
final GXyDataset dataset = model.getUnifiedDataset();
final GXyAxis domainAxis = (GXyAxis)axes().domain();
domainAxis.accept(axis, dataset);
});
if (axes.rangeAxisCount() == 1) {
options.newObject(range1, axis -> {
final GXyDataset dataset = model.getUnifiedDataset();
final GXyAxis rangeAxis = (GXyAxis)axes.range(0);
rangeAxis.accept(axis, dataset);
});
} else {
options.newObject(range2, vAxes -> {
for (int i=0; i rangeAxis.accept(axis, dataset));
}
});
}
}
/**
* Enriches the options with series configuration
* @param options the Javascript options object
*/
@SuppressWarnings("unchecked")
private void seriesOptions(JsObject options) {
options.newObject("series", seriesList -> {
final GXyDataset dataset = model.getUnifiedDataset();
for (int i=0; i lineWidth = render.getSeriesLineWidth(seriesKey);
final Optional shape = render.getSeriesPointShape(seriesKey);
seriesList.newObject(i, series -> {
series.setIgnoreNulls(true);
series.newAttribute("targetAxisIndex", axisIndex);
series.newAttribute("visibleInLegend", true);
series.newAttribute("color", ColorModel.toHexString(color));
if (render.hasShapesOrPoints()) {
series.newAttribute("pointsVisible", render.isSeriesPointsVisible(seriesKey));
series.newAttribute("pointSize", render.getPointSize());
}
if (render.isLines()) {
series.newAttribute("lineWidth", lineWidth.orElse(1f));
series.newAttribute("curveType", render.getCurveType());
applyShape(series, shape.orElse(null));
if (render.isSeriesDashedLine(seriesKey)) {
series.newArray("lineDashStyle", true, array -> array.append(10).append(2));
}
}
});
}
});
}
/**
* Enriches the options with trend line configuration
* @param options the Javascript options
*/
@SuppressWarnings("unchecked")
private void trendLineOptions(JsObject options) {
if (trendLineMap.size() > 0) {
final GXyDataset dataset = model.getUnifiedDataset();
final Iterable seriesKeys = dataset.getSeriesKeys();
final Index index = Index.of(seriesKeys);
if (seriesKeys != null) {
options.newObject("trendlines", trendLines -> {
trendLineMap.forEach((key, trend) -> {
final int ordinal = index.getOrdinalForKey(key);
trendLines.newObject(ordinal, entry -> {
entry.newAttribute("type", "linear");
entry.newAttribute("color", ColorModel.toHexString(trend.getColor()));
entry.newAttribute("lineWidth", trend.getLineWidth());
entry.newAttribute("opacity", 1d);
entry.newAttribute("showR2", false);
entry.newAttribute("visibleInLegend", false);
});
});
});
}
}
}
/**
* Applies a point shape style to the series entry
* @param object the series object
* @param shape the chart shape
*/
private void applyShape(JsObject object, ChartShape shape) {
if (shape != null) {
switch (shape) {
case CIRCLE: object.newAttribute("pointShape", "circle"); break;
case SQUARE: object.newAttribute("pointShape", "square"); break;
case DIAMOND: object.newAttribute("pointShape", "diamond"); break;
case TRIANGLE_UP: object.newAttribute("pointShape", "triangle"); break;
case TRIANGLE_DOWN: object.newObject("pointShape", x -> x.newAttribute("type", "triangle").newAttribute("rotation", 180)); break;
case TRIANGLE_RIGHT: object.newObject("pointShape", x -> x.newAttribute("type", "triangle").newAttribute("rotation", 90)); break;
case TRIANGLE_LEFT: object.newObject("pointShape", x -> x.newAttribute("type", "triangle").newAttribute("rotation", 270)); break;
default: System.err.println("Unsupported shape: " + shape);
}
}
}
/**
* Returns true if the orientation is set to horizontal
* @return true if horizontal orientation
*/
private boolean isHorizontal() {
return !isVertical();
}
/**
* Returns the shape provider for this plot
* @return the shape provider
*/
ChartShape.Provider getShapeProvider() {
return shapeProvider;
}
/**
* Returns true if the orientation is set to vertical
* @return true if vertical orientation
*/
boolean isVertical() {
if (orient.horizontal != null) {
return !orient.horizontal;
} else if (renderAt(0).isBars()) {
return true;
} else {
return false;
}
}
/**
* An adapter implementation for the ChartOrientation interface
*/
private class GXyOrient implements XyOrient {
private Boolean horizontal;
@Override
public void vertical() {
this.horizontal = false;
}
@Override
public void horizontal() {
this.horizontal = true;
}
}
}