blob: da8ae1eed78fea2f657109b232536e0759aef764 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2012, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.rtm.ggplot.core;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.ecommons.collections.IntArrayMap;
import org.eclipse.statet.ecommons.collections.IntMap;
import org.eclipse.statet.rtm.base.core.AbstractRCodeGenerator;
import org.eclipse.statet.rtm.base.util.RExprTypes;
import org.eclipse.statet.rtm.ggplot.FacetLayout;
import org.eclipse.statet.rtm.ggplot.GGPlot;
import org.eclipse.statet.rtm.ggplot.GGPlotPackage;
import org.eclipse.statet.rtm.ggplot.GGPlotPackage.Literals;
import org.eclipse.statet.rtm.ggplot.GeomBarLayer;
import org.eclipse.statet.rtm.ggplot.GeomLineLayer;
import org.eclipse.statet.rtm.ggplot.GeomPointLayer;
import org.eclipse.statet.rtm.ggplot.GridFacetLayout;
import org.eclipse.statet.rtm.ggplot.Layer;
import org.eclipse.statet.rtm.ggplot.PropDataProvider;
import org.eclipse.statet.rtm.ggplot.PropStatProvider;
import org.eclipse.statet.rtm.ggplot.Stat;
import org.eclipse.statet.rtm.ggplot.SummaryStat;
import org.eclipse.statet.rtm.ggplot.TextStyle;
import org.eclipse.statet.rtm.ggplot.WrapFacetLayout;
import org.eclipse.statet.rtm.ggplot.core.GGPlotRCodeGen.E2R.Property;
import org.eclipse.statet.rtm.rtdata.types.RTypedExpr;
public class GGPlotRCodeGen extends AbstractRCodeGenerator {
/**
* Static mapping
*/
static class E2R {
static class Property {
private final EStructuralFeature eFeature;
private final String rArg;
private final boolean allowMapped;
private final boolean allowDirect;
public Property(final EStructuralFeature eFeature, final String rArg,
final boolean allowMapped, final boolean allowDirect) {
this.eFeature= eFeature;
this.rArg= rArg;
this.allowMapped= allowMapped;
this.allowDirect= allowDirect;
}
public boolean allowMapped() {
return this.allowMapped;
}
public boolean allowDirect() {
return this.allowDirect;
}
public String getRArg() {
return this.rArg;
}
public EStructuralFeature getEFeature() {
return this.eFeature;
}
}
private final EClass eClass;
private final String rFun;
private final List<Property> properties;
public E2R(final EClass eClass, final String rFun) {
this.eClass= eClass;
this.rFun= rFun;
final List<Property> list= new ArrayList<>();
addProperty(list, Literals.PROP_XVAR_PROVIDER__XVAR, "x"); //$NON-NLS-1$
addProperty(list, Literals.PROP_YVAR_PROVIDER__YVAR, "y"); //$NON-NLS-1$
addProperty(list, Literals.GEOM_TEXT_LAYER__LABEL, "label"); //$NON-NLS-1$
addProperty(list, Literals.GEOM_ABLINE_LAYER__INTERCEPT_VAR, "intercept"); //$NON-NLS-1$
addProperty(list, Literals.GEOM_ABLINE_LAYER__SLOPE_VAR, "slope"); //$NON-NLS-1$
addProperty(list, Literals.PROP_GROUP_VAR_PROVIDER__GROUP_VAR, "group"); //$NON-NLS-1$
addProperty(list, Literals.PROP_SHAPE_PROVIDER__SHAPE, "shape"); //$NON-NLS-1$
addProperty(list, Literals.PROP_LINE_TYPE_PROVIDER__LINE_TYPE, "linetype"); //$NON-NLS-1$
addProperty(list, Literals.TEXT_STYLE__FONT_FAMILY, "family"); //$NON-NLS-1$
addProperty(list, Literals.TEXT_STYLE__FONT_FACE, "face"); //$NON-NLS-1$
addProperty(list, Literals.PROP_SIZE_PROVIDER__SIZE, "size"); //$NON-NLS-1$
addProperty(list, Literals.PROP_COLOR_PROVIDER__COLOR, "colour"); //$NON-NLS-1$
addProperty(list, Literals.PROP_FILL_PROVIDER__FILL, "fill"); //$NON-NLS-1$
addProperty(list, Literals.TEXT_STYLE__HJUST, "hjust"); //$NON-NLS-1$
addProperty(list, Literals.TEXT_STYLE__VJUST, "vjust"); //$NON-NLS-1$
addProperty(list, Literals.TEXT_STYLE__ANGLE, "angle"); //$NON-NLS-1$
this.properties= list;
}
private void addProperty(final List<Property> list, final EStructuralFeature eFeature, final String rArg) {
if (!this.eClass.getEAllStructuralFeatures().contains(eFeature)) {
return;
}
final RExprTypes types= GGPlotExprTypesProvider.INSTANCE.getTypes(this.eClass, eFeature);
final boolean allowMapped= types.contains(RTypedExpr.MAPPED);
final boolean allowDirect= types.contains(RTypedExpr.R) || types.contains(RTypedExpr.CHAR);
list.add(new Property(eFeature, rArg, allowMapped, allowDirect));
}
public EClass getEClass() {
return this.eClass;
}
public String getRFun() {
return this.rFun;
}
public List<Property> getProperties() {
return this.properties;
}
}
private static final IntMap<E2R> E2R_PROPERTIES;
static {
final List<E2R> list= new ArrayList<>();
list.add(new E2R(Literals.GG_PLOT, "ggplot")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_ABLINE_LAYER, "geom_abline")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_BAR_LAYER, "geom_bar")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_BOXPLOT_LAYER, "geom_boxplot")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_HISTOGRAM_LAYER, "geom_histogram")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_LINE_LAYER, "geom_line")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_POINT_LAYER, "geom_point")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_SMOOTH_LAYER, "geom_smooth")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_TEXT_LAYER, "geom_text")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_TILE_LAYER, "geom_tile")); //$NON-NLS-1$
list.add(new E2R(Literals.GEOM_VIOLIN_LAYER, "geom_violin")); //$NON-NLS-1$
list.add(new E2R(Literals.TEXT_STYLE, "text_theme")); //$NON-NLS-1$
list.add(new E2R(Literals.GRID_FACET_LAYOUT, "facet_grid")); //$NON-NLS-1$
list.add(new E2R(Literals.WRAP_FACET_LAYOUT, "facet_wrap")); //$NON-NLS-1$
E2R_PROPERTIES= new IntArrayMap<>();
for (final E2R e2r : list) {
final int id= e2r.getEClass().getClassifierID();
E2R_PROPERTIES.put(id, e2r);
}
}
private static void appendMappedProperties(final FunBuilder fun, final EObject obj, final List<Property> properties) {
for (final Property property : properties) {
if (property.allowMapped()) {
final Object value= obj.eGet(property.getEFeature());
fun.appendExpr(property.getRArg(), (RTypedExpr) value, RTypedExpr.MAPPED);
}
}
}
private static final ImList<String> DIRECT_TYPES= ImCollections.newList(
RTypedExpr.R,
RTypedExpr.CHAR );
private static void appendDirectProperties(final FunBuilder fun, final EObject obj, final List<Property> properties) {
for (final Property property : properties) {
if (property.allowDirect()) {
final Object value= obj.eGet(property.getEFeature());
fun.appendExpr(property.getRArg(), (RTypedExpr) value, DIRECT_TYPES);
}
}
}
private final String plotVar= "p"; //$NON-NLS-1$
@Override
public void generate(final EObject root) {
reset();
addRequirePackage("ggplot2"); //$NON-NLS-1$
if (root == null) {
return;
}
if (root.eClass().getClassifierID() != GGPlotPackage.GG_PLOT) {
throw new IllegalArgumentException("root: " + root.eClass().getName()); //$NON-NLS-1$
}
appendNewLine();
genRCode((GGPlot) root);
appendNewLine();
final FunBuilder printFun= appendFun("print"); //$NON-NLS-1$
printFun.append(null, this.plotVar);
printFun.close();
appendNewLine();
}
public void genRCode(final GGPlot plot) {
appendAssign(this.plotVar);
{ final E2R e2r= E2R_PROPERTIES.get(GGPlotPackage.GG_PLOT);
final FunBuilder fun= appendFun(e2r.getRFun());
appendData(fun, plot);
appendAes(fun, plot, e2r);
fun.close();
appendNewLine();
addFacet(plot.getFacet());
addLab("title", plot.getMainTitle(), null, null); //$NON-NLS-1$
addTheme("plot.title", plot.getMainTitleStyle()); //$NON-NLS-1$
addLab("x", plot.getAxXLabel(), null, null); //$NON-NLS-1$
addTheme("axis.title.x ", plot.getAxXLabelStyle()); //$NON-NLS-1$
addTheme("axis.text.x ", plot.getAxXTextStyle()); //$NON-NLS-1$
addLab("y", plot.getAxYLabel(), null, null); //$NON-NLS-1$
addTheme("axis.title.y ", plot.getAxYLabelStyle()); //$NON-NLS-1$
addTheme("axis.text.y ", plot.getAxYTextStyle()); //$NON-NLS-1$
}
final EList<Layer> layers= plot.getLayers();
for (final Layer layer : layers) {
appendAssign(this.plotVar);
this.builder.append(this.plotVar);
this.builder.append(" + "); //$NON-NLS-1$
final E2R e2r= E2R_PROPERTIES.get(layer.eClass().getClassifierID());
final FunBuilder fun= appendFun(e2r.getRFun());
appendData(fun, layer);
appendAes(fun, layer, e2r);
switch (layer.eClass().getClassifierID()) {
case GGPlotPackage.GEOM_BAR_LAYER:
appendStat(fun, (GeomBarLayer) layer);
break;
case GGPlotPackage.GEOM_LINE_LAYER:
appendStat(fun, (GeomLineLayer) layer);
break;
case GGPlotPackage.GEOM_POINT_LAYER:
appendPosition(fun, (GeomPointLayer) layer);
break;
}
fun.close();
appendNewLine();
}
}
private void appendData(final FunBuilder fun, final PropDataProvider obj) {
if (obj.getData() != null) {
String expr= obj.getData().getExpr();
if (obj instanceof GGPlot) {
final RTypedExpr dataFilter= ((GGPlot) obj).getDataFilter();
if (dataFilter != null) {
expr+= dataFilter.getExpr();
}
}
fun.append("data", expr); //$NON-NLS-1$
}
}
private void appendAes(final FunBuilder fun, final EObject obj, final E2R e2r) {
final List<Property> aesProperties= e2r.getProperties();
final FunBuilder aesFun= fun.appendFun(null, "aes"); //$NON-NLS-1$
appendMappedProperties(aesFun, obj, aesProperties);
aesFun.closeOrRemove();
appendDirectProperties(fun, obj, aesProperties);
}
private void addFacet(final FacetLayout obj) {
if (obj == null) {
return;
}
appendAssign(this.plotVar);
this.builder.append(this.plotVar);
this.builder.append(" + "); //$NON-NLS-1$
FunBuilder facetFun;
switch (obj.eClass().getClassifierID()) {
case GGPlotPackage.GRID_FACET_LAYOUT: {
final GridFacetLayout layout= (GridFacetLayout) obj;
facetFun= appendFun("facet_grid"); //$NON-NLS-1$
appendExprList(layout.getRowVars(), " + ", "."); //$NON-NLS-1$ //$NON-NLS-2$
this.builder.append(" ~ "); //$NON-NLS-1$
appendExprList(layout.getColVars(), " + ", "."); //$NON-NLS-1$ //$NON-NLS-2$
break; }
case GGPlotPackage.WRAP_FACET_LAYOUT: {
final WrapFacetLayout layout= (WrapFacetLayout) obj;
facetFun= appendFun("facet_wrap"); //$NON-NLS-1$
this.builder.append("~ "); //$NON-NLS-1$
appendExprList(layout.getColVars(), " + ", "."); //$NON-NLS-1$ //$NON-NLS-2$
facetFun.appendExpr("ncol", ((WrapFacetLayout) obj).getColNum()); //$NON-NLS-1$
break; }
default:
throw new UnsupportedOperationException(obj.eClass().getName());
}
facetFun.close();
appendNewLine();
}
private void appendPosition(final FunBuilder fun, final GeomPointLayer obj) {
RTypedExpr xJitter= obj.getPositionXJitter();
RTypedExpr yJitter= obj.getPositionYJitter();
if ((xJitter == null && yJitter == null) ) {
return;
}
if (xJitter == null) {
xJitter= R_NUM_ZERO_EXPR;
}
if (yJitter == null) {
yJitter= R_NUM_ZERO_EXPR;
}
final FunBuilder jitterFun= fun.appendFun("position", "position_jitter"); //$NON-NLS-1$ //$NON-NLS-2$
jitterFun.appendExpr("w", xJitter); //$NON-NLS-1$
jitterFun.appendExpr("h", yJitter); //$NON-NLS-1$
jitterFun.close();
}
private void appendStat(final FunBuilder fun, final PropStatProvider obj) {
final Stat stat= obj.getStat();
if (stat == null) {
return;
}
switch (stat.eClass().getClassifierID()) {
case GGPlotPackage.IDENTITY_STAT:
fun.append("stat", "\"identity\""); //$NON-NLS-1$ //$NON-NLS-2$
return;
case GGPlotPackage.SUMMARY_STAT:
fun.append("stat", "\"summary\""); //$NON-NLS-1$ //$NON-NLS-2$
fun.appendExpr("fun.y", ((SummaryStat) stat).getYFun()); //$NON-NLS-1$
return;
}
}
private void addLab(final String lab, final RTypedExpr text, final String lab2, final RTypedExpr text2) {
if (text == null && text2 == null) {
return;
}
appendAssign(this.plotVar);
this.builder.append(this.plotVar);
this.builder.append(" + "); //$NON-NLS-1$
final FunBuilder labsFun= appendFun("labs"); //$NON-NLS-1$
labsFun.appendExpr(lab, text, DIRECT_TYPES);
if (lab2 != null) {
labsFun.appendExpr(lab2, text2, DIRECT_TYPES);
}
labsFun.close();
appendNewLine();
}
private void addTheme(final String text, final TextStyle obj) {
if (obj.getFontFamily() == null && obj.getFontFace() == null
&& obj.getSize() == null && obj.getColor() == null
&& obj.getHJust() == null && obj.getVJust() == null && obj.getAngle() == null) {
return;
}
final E2R e2r= E2R_PROPERTIES.get(obj.eClass().getClassifierID());
appendAssign(this.plotVar);
this.builder.append(this.plotVar);
this.builder.append(" + "); //$NON-NLS-1$
final FunBuilder themeFun= appendFun("theme"); //$NON-NLS-1$
final FunBuilder textFun= themeFun.appendFun(text, "element_text"); //$NON-NLS-1$
appendDirectProperties(textFun, obj, e2r.getProperties());
textFun.close();
themeFun.close();
appendNewLine();
}
}