Skip to content
Snippets Groups Projects
Commit 657f38f4 authored by stepan's avatar stepan
Browse files

FastR Grid: LPolygon + support for 'stringwidth' units

parent f4c1cec6
No related branches found
No related tags found
No related merge requests found
Showing
with 331 additions and 161 deletions
......@@ -107,7 +107,7 @@ class DoSetViewPort extends RBaseNode {
}
}
UnitConversionContext conversionCtx = new UnitConversionContext(parentSize, parentContext, drawingContext);
UnitConversionContext conversionCtx = new UnitConversionContext(parentSize, parentContext, device, drawingContext);
double xInches = unitsToInches.convertX(vpl.x, 0, conversionCtx);
double yInches = unitsToInches.convertY(vpl.y, 0, conversionCtx);
double width = unitsToInches.convertWidth(vpl.width, 0, conversionCtx);
......@@ -141,7 +141,7 @@ class DoSetViewPort extends RBaseNode {
if (!isNull(viewPort.getDataAt(ViewPort.VP_LAYOUT))) {
ViewPortContext vpCtx = vpContextFromVP.execute(viewPort);
DrawingContext drawingCtx = GPar.asDrawingContext(asList(viewPort.getDataAt(ViewPort.PVP_GPAR)));
calcViewPortLayout(viewPort, new Size(width, height), vpCtx, drawingCtx);
calcViewPortLayout(viewPort, new Size(width, height), vpCtx, device, drawingCtx);
}
Object[] viewPortData = viewPort.getDataWithoutCopying();
......@@ -151,13 +151,13 @@ class DoSetViewPort extends RBaseNode {
viewPortData[ViewPort.PVP_TRANS] = RDataFactory.createDoubleVector(flatten(transform), RDataFactory.COMPLETE_VECTOR, new int[]{3, 3});
}
private void calcViewPortLayout(RList viewPort, Size size, ViewPortContext parentVPCtx, DrawingContext drawingCtx) {
private void calcViewPortLayout(RList viewPort, Size size, ViewPortContext parentVPCtx, GridDevice device, DrawingContext drawingCtx) {
LayoutSize layoutSize = LayoutSize.fromViewPort(viewPort);
double[] npcWidths = new double[layoutSize.ncol];
double[] npcHeights = new double[layoutSize.nrow];
boolean[] relativeWidths = new boolean[layoutSize.ncol];
boolean[] relativeHeights = new boolean[layoutSize.nrow];
UnitConversionContext conversionCtx = new UnitConversionContext(size, parentVPCtx, drawingCtx);
UnitConversionContext conversionCtx = new UnitConversionContext(size, parentVPCtx, device, drawingCtx);
// For both dimensions we find out which units are other than "null" for those we can
// immediately calculate the physical size in npcWidth/npcHeights. The reducedWidth/Height
......@@ -178,8 +178,8 @@ class DoSetViewPort extends RBaseNode {
int respect = RRuntime.asInteger(layoutAsList.getDataAt(ViewPort.LAYOUT_VRESPECT));
int[] layoutRespectMat = ((RAbstractIntVector) layoutAsList.getDataAt(ViewPort.LAYOUT_MRESPECT)).materialize().getDataWithoutCopying();
if ((reducedHeight > 0 || reducedWidth > 0) && respect > 0) {
double sumRelWidth = sumRelativeDimension(layoutSize, layoutWidths, relativeWidths, parentVPCtx, drawingCtx, true);
double sumRelHeight = sumRelativeDimension(layoutSize, layoutHeights, relativeHeights, parentVPCtx, drawingCtx, false);
double sumRelWidth = sumRelativeDimension(layoutSize, layoutWidths, relativeWidths, parentVPCtx, device, drawingCtx, true);
double sumRelHeight = sumRelativeDimension(layoutSize, layoutHeights, relativeHeights, parentVPCtx, device, drawingCtx, false);
double tempWidth = reducedWidth;
double tempHeight = reducedHeight;
double denom;
......@@ -233,8 +233,8 @@ class DoSetViewPort extends RBaseNode {
}
// Secondly, allocate remaining relative widths and heights in the remaining space
allocateRelativeDim(layoutSize, layoutWidths, npcWidths, relativeWidths, reducedWidth, respect, layoutRespectMat, drawingCtx, parentVPCtx, true);
allocateRelativeDim(layoutSize, layoutHeights, npcHeights, relativeHeights, reducedHeight, respect, layoutRespectMat, drawingCtx, parentVPCtx, false);
allocateRelativeDim(layoutSize, layoutWidths, npcWidths, relativeWidths, reducedWidth, respect, layoutRespectMat, device, drawingCtx, parentVPCtx, true);
allocateRelativeDim(layoutSize, layoutHeights, npcHeights, relativeHeights, reducedHeight, respect, layoutRespectMat, device, drawingCtx, parentVPCtx, false);
// Create the result
Object[] vpData = viewPort.getDataWithoutCopying();
......@@ -243,8 +243,8 @@ class DoSetViewPort extends RBaseNode {
}
private void allocateRelativeDim(LayoutSize layoutSize, RAbstractContainer layoutItems, double[] npcItems, boolean[] relativeItems, double reducedDim, int respect, int[] layoutRespectMat,
DrawingContext drawingCtx, ViewPortContext parentVPCtx, boolean isWidth) {
UnitConversionContext layoutModeCtx = new UnitConversionContext(new Size(0, 0), parentVPCtx, drawingCtx, 1, 0);
GridDevice device, DrawingContext drawingCtx, ViewPortContext parentVPCtx, boolean isWidth) {
UnitConversionContext layoutModeCtx = new UnitConversionContext(new Size(0, 0), parentVPCtx, device, drawingCtx, 1, 0);
double totalUnrespectedSize = 0;
if (reducedDim > 0) {
for (int i = 0; i < layoutSize.ncol; i++) {
......@@ -293,8 +293,9 @@ class DoSetViewPort extends RBaseNode {
return false;
}
private double sumRelativeDimension(LayoutSize layoutSize, RAbstractContainer layoutItems, boolean[] relativeItems, ViewPortContext parentVPCtx, DrawingContext drawingCtx, boolean isWidth) {
UnitConversionContext layoutModeCtx = new UnitConversionContext(new Size(0, 0), parentVPCtx, drawingCtx, 0, 1);
private double sumRelativeDimension(LayoutSize layoutSize, RAbstractContainer layoutItems, boolean[] relativeItems, ViewPortContext parentVPCtx, GridDevice device, DrawingContext drawingCtx,
boolean isWidth) {
UnitConversionContext layoutModeCtx = new UnitConversionContext(new Size(0, 0), parentVPCtx, device, drawingCtx, 0, 1);
double totalWidth = 0;
for (int i = 0; i < layoutSize.ncol; i++) {
if (relativeItems[i]) {
......
/*
* This material is distributed under the GNU General Public License
* Version 2. You may review the terms of this license at
* http://www.gnu.org/licenses/gpl-2.0.html
*
* Copyright (C) 2001-3 Paul Murrell
* Copyright (c) 1998-2013, The R Core Team
* Copyright (c) 2017, Oracle and/or its affiliates
*
* All rights reserved.
*/
package com.oracle.truffle.r.library.fastrGrid;
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asIntVector;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext;
import com.oracle.truffle.r.library.fastrGrid.ViewPortContext.VPContextFromVPNode;
import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode;
import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
import com.oracle.truffle.r.runtime.data.RList;
import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
/**
* Common code shared between {@code L_lines} and {@code L_polygon} externals. Both draw a series of
* lines, but only the later connects the last point with the first point and only the former draws
* arrows (which is not implemented yet). Note: the third parameter contains sequences
* {@code 1:max(length(x),length(y))}, where the 'length' dispatches to S3 method giving us unit
* length like {@link com.oracle.truffle.r.library.fastrGrid.Unit.UnitLengthNode}. This means that
* we do not have to use the {@link com.oracle.truffle.r.library.fastrGrid.Unit.UnitLengthNode} to
* get the length.
*/
public abstract class GridLinesNode extends Node {
public static GridLinesNode createLines() {
return new GridLinesImpl();
}
public static GridLinesNode createPolygon() {
return new GridLinesPolygon();
}
@Child private Unit.UnitToInchesNode unitToInches = Unit.createToInchesNode();
@Child private GetViewPortTransformNode getViewPortTransform = new GetViewPortTransformNode();
@Child private VPContextFromVPNode vpContextFromVP = new VPContextFromVPNode();
void execute(RAbstractVector x, RAbstractVector y, RList lengths) {
GridContext ctx = GridContext.getContext();
GridDevice dev = ctx.getCurrentDevice();
RList currentVP = ctx.getGridState().getViewPort();
DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar());
ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx);
// Convert the list of vectors of indexes to type-safe array and calculate the max length of
// the vectors.
RAbstractIntVector[] unitIndexesList = new RAbstractIntVector[lengths.getLength()];
int maxIndexesLen = 0;
for (int i = 0; i < lengths.getLength(); i++) {
unitIndexesList[i] = asIntVector(lengths.getDataAt(i));
maxIndexesLen = Math.max(maxIndexesLen, unitIndexesList[i].getLength());
}
double[] xx = new double[maxIndexesLen + 1]; // plus one for polygons
double[] yy = new double[maxIndexesLen + 1];
for (RAbstractIntVector unitIndexes : unitIndexesList) {
boolean oldIsFinite = false;
int start = 0;
int unitIndexesLen = unitIndexes.getLength();
// following loop finds series of valid points (finite x and y values) and draws each
// such series as a polyline
for (int i = 0; i < unitIndexesLen; i++) {
int unitIndex = unitIndexes.getDataAt(i) - 1; // coverting R's 1-based index
Point origLoc = Point.fromUnits(unitToInches, x, y, unitIndex, conversionCtx);
Point loc = TransformMatrix.transLocation(origLoc, vpTransform.transform);
xx[i] = loc.x;
yy[i] = loc.y;
boolean currIsFinite = loc.isFinite();
boolean lastIter = i == (unitIndexesLen - 1);
if (currIsFinite && !oldIsFinite) {
start = i; // start a new series
} else if (oldIsFinite && (!currIsFinite || lastIter)) {
// draw the previous points series because
// (1) current is invalid point. Note: in (one of) the next iteration(s), the
// oldIsFinite will be false and we will update the start and start a new series
// (2) we are in the last iteration
if (lastIter || i - start > 1) {
// we draw only if the previous series of points was at least of length 3 or
// it's last iteration. This seems slightly weird, but that's how GnuR seems
// to work
drawPolylines(dev, drawingCtx, yy, xx, start, (i - start) + 1);
}
}
oldIsFinite = currIsFinite;
}
}
}
abstract void drawPolylines(GridDevice dev, DrawingContext drawingCtx, double[] yy, double[] xx, int start, int length);
private static final class GridLinesImpl extends GridLinesNode {
@Override
void drawPolylines(GridDevice dev, DrawingContext drawingCtx, double[] yy, double[] xx, int start, int length) {
dev.drawPolyLines(drawingCtx, xx, yy, start, length);
}
}
private static final class GridLinesPolygon extends GridLinesNode {
@Override
void drawPolylines(GridDevice dev, DrawingContext drawingCtx, double[] yy, double[] xx, int start, int length) {
xx[start + length] = xx[start];
yy[start + length] = yy[start];
dev.drawPolyLines(drawingCtx, xx, yy, start, length + 1);
}
}
}
......@@ -97,7 +97,7 @@ public final class GridTextNode extends RBaseNode {
DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar());
ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx);
int length = GridUtils.maxLength(unitLength, x, y);
......
......@@ -21,6 +21,7 @@ import com.oracle.truffle.r.runtime.RError.Message;
import com.oracle.truffle.r.runtime.data.RAttributable;
import com.oracle.truffle.r.runtime.data.RDataFactory;
import com.oracle.truffle.r.runtime.data.RList;
import com.oracle.truffle.r.runtime.data.RNull;
import com.oracle.truffle.r.runtime.data.RStringVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractContainer;
import com.oracle.truffle.r.runtime.data.model.RAbstractDoubleVector;
......@@ -89,6 +90,16 @@ final class GridUtils {
return false;
}
static RList asListOrNull(Object value) {
if (value == null || value == RNull.instance) {
return null;
}
if (!(value instanceof RList)) {
throw RError.error(RError.NO_CALLER, Message.GENERIC, "Expected list");
}
return (RList) value;
}
static RList asList(Object value) {
if (!(value instanceof RList)) {
throw RError.error(RError.NO_CALLER, Message.GENERIC, "Expected list");
......
......@@ -51,7 +51,7 @@ public abstract class LCircle extends RExternalBuiltinNode.Arg3 {
DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar());
ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx);
int length = GridUtils.maxLength(unitLength, xVec, yVec, radiusVec);
for (int i = 0; i < length; i++) {
......
......@@ -19,6 +19,7 @@ import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVect
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.numericValue;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.r.library.fastrGrid.Unit.AxisOrDimension;
import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext;
import com.oracle.truffle.r.library.fastrGrid.ViewPortContext.VPContextFromVPNode;
import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode;
......@@ -59,7 +60,7 @@ public abstract class LConvert extends RExternalBuiltinNode.Arg4 {
DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar());
ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx);
int length = unitLength.execute(units);
double[] result = new double[length];
......@@ -69,15 +70,11 @@ public abstract class LConvert extends RExternalBuiltinNode.Arg4 {
for (int i = 0; i < length; i++) {
// scalar values used in current iteration
int axisFrom = axisFromVec.getDataAt(i % axisFromVec.getLength());
int axisTo = axisToVec.getDataAt(i % axisToVec.getLength());
boolean compatibleAxes = axisFrom == axisTo ||
(axisFrom == 0 && axisTo == 2) ||
(axisFrom == 2 && axisTo == 0) ||
(axisFrom == 1 && axisTo == 3) ||
(axisFrom == 3 && axisTo == 1);
double vpToSize = isXAxis(axisTo) ? vpTransform.size.getWidth() : vpTransform.size.getHeight();
double vpFromSize = isXAxis(axisFrom) ? vpTransform.size.getWidth() : vpTransform.size.getHeight();
AxisOrDimension axisFrom = AxisOrDimension.fromInt(axisFromVec.getDataAt(i % axisFromVec.getLength()));
AxisOrDimension axisTo = AxisOrDimension.fromInt(axisToVec.getDataAt(i % axisToVec.getLength()));
boolean compatibleAxes = axisFrom.isHorizontal() == axisTo.isHorizontal();
double vpToSize = axisTo.isHorizontal() ? vpTransform.size.getWidth() : vpTransform.size.getHeight();
double vpFromSize = axisFrom.isHorizontal() ? vpTransform.size.getWidth() : vpTransform.size.getHeight();
int unitTo = unitToVec.getDataAt(i % unitToVec.getLength());
int fromUnitId = unitIds.getDataAt(i % unitIds.getLength());
......@@ -94,26 +91,26 @@ public abstract class LConvert extends RExternalBuiltinNode.Arg4 {
result[i] = transformFromNPC(tranfromToNPC(fromValue, fromUnitId, axisFrom, vpContext), unitTo, axisTo, vpContext);
} else {
double inches = toInches(units, i, axisFrom, conversionCtx);
boolean isX = isXAxis(axisTo);
boolean isX = axisTo.isHorizontal();
double scalemin = isX ? vpContext.xscalemin : vpContext.yscalemin;
double scalemax = isX ? vpContext.xscalemax : vpContext.yscalemax;
result[i] = Unit.convertFromInches(inches, unitTo, vpToSize, scalemin, scalemax, isDimension(axisTo), drawingCtx);
result[i] = Unit.convertFromInches(inches, unitTo, vpToSize, scalemin, scalemax, axisTo.isDimension(), drawingCtx);
}
}
return RDataFactory.createDoubleVector(result, RDataFactory.COMPLETE_VECTOR);
}
private double toInches(RAbstractVector units, int index, int axisFrom, UnitConversionContext conversionCtx) {
private double toInches(RAbstractVector units, int index, AxisOrDimension axisFrom, UnitConversionContext conversionCtx) {
double inches;
if (isXAxis(axisFrom)) {
if (isDimension(axisFrom)) {
if (axisFrom.isHorizontal()) {
if (axisFrom.isDimension()) {
inches = unitToInches.convertWidth(units, index, conversionCtx);
} else {
inches = unitToInches.convertX(units, index, conversionCtx);
}
} else {
if (isDimension(axisFrom)) {
if (axisFrom.isDimension()) {
inches = unitToInches.convertHeight(units, index, conversionCtx);
} else {
inches = unitToInches.convertY(units, index, conversionCtx);
......@@ -122,30 +119,30 @@ public abstract class LConvert extends RExternalBuiltinNode.Arg4 {
return inches;
}
private static double tranfromToNPC(double value, int fromUnitId, int axisFrom, ViewPortContext vpContext) {
private static double tranfromToNPC(double value, int fromUnitId, AxisOrDimension axisFrom, ViewPortContext vpContext) {
if (fromUnitId == Unit.NPC) {
return value;
}
assert fromUnitId == Unit.NATIVE : "relative conversion should only happen when units are NPC or NATIVE";
boolean isX = isXAxis(axisFrom);
boolean isX = axisFrom.isHorizontal();
double min = isX ? vpContext.xscalemin : vpContext.yscalemin;
double max = isX ? vpContext.xscalemax : vpContext.yscalemax;
if (isDimension(axisFrom)) {
if (axisFrom.isDimension()) {
return value / (max - min);
} else {
return (value - min) / (max - min);
}
}
private static double transformFromNPC(double value, int unitTo, int axisTo, ViewPortContext vpContext) {
private static double transformFromNPC(double value, int unitTo, AxisOrDimension axisTo, ViewPortContext vpContext) {
if (unitTo == Unit.NPC) {
return value;
}
assert unitTo == Unit.NATIVE : "relative conversion should only happen when units are NPC or NATIVE";
boolean isX = isXAxis(axisTo);
boolean isX = axisTo.isHorizontal();
double min = isX ? vpContext.xscalemin : vpContext.yscalemin;
double max = isX ? vpContext.xscalemax : vpContext.yscalemax;
if (isDimension(axisTo)) {
if (axisTo.isDimension()) {
return value * (max - min);
} else {
return min + value * (max - min);
......@@ -157,13 +154,4 @@ public abstract class LConvert extends RExternalBuiltinNode.Arg4 {
private static boolean isRelative(int unitId) {
return unitId == NPC || unitId == NATIVE;
}
// what = 0 means x, 1 means y, 2 means width, 3 means height
private static boolean isXAxis(int what) {
return what % 2 == 0;
}
private static boolean isDimension(int what) {
return what >= 2;
}
}
/*
* This material is distributed under the GNU General Public License
* Version 2. You may review the terms of this license at
* http://www.gnu.org/licenses/gpl-2.0.html
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* Copyright (C) 2001-3 Paul Murrell
* Copyright (c) 1998-2013, The R Core Team
* Copyright (c) 2017, Oracle and/or its affiliates
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* All rights reserved.
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.r.library.fastrGrid;
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asIntVector;
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVectorValue;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.r.library.fastrGrid.Unit.UnitConversionContext;
import com.oracle.truffle.r.library.fastrGrid.ViewPortContext.VPContextFromVPNode;
import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode;
import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
import com.oracle.truffle.r.runtime.data.RList;
import com.oracle.truffle.r.runtime.data.RNull;
import com.oracle.truffle.r.runtime.data.model.RAbstractIntVector;
import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
/**
* Note: the third parameter contains sequences {@code 1:max(length(x),length(y))}, where the
* 'length' dispatches to S3 method giving us unit length like
* {@link com.oracle.truffle.r.library.fastrGrid.Unit.UnitLengthNode}.
*/
public abstract class LLines extends RExternalBuiltinNode.Arg4 {
@Child private Unit.UnitToInchesNode unitToInches = Unit.createToInchesNode();
@Child private GetViewPortTransformNode getViewPortTransform = new GetViewPortTransformNode();
@Child private VPContextFromVPNode vpContextFromVP = new VPContextFromVPNode();
@Child private GridLinesNode gridLinesNode = GridLinesNode.createLines();
static {
Casts casts = new Casts(LLines.class);
......@@ -48,59 +45,9 @@ public abstract class LLines extends RExternalBuiltinNode.Arg4 {
}
@Specialization
Object doLines(RAbstractVector x, RAbstractVector y, RList lengths, Object arrowIgnored) {
GridContext ctx = GridContext.getContext();
GridDevice dev = ctx.getCurrentDevice();
RList currentVP = ctx.getGridState().getViewPort();
DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar());
ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx);
// Convert the list of vectors of indexes to type-safe array and calculate the max length of
// the vectors.
RAbstractIntVector[] unitIndexesList = new RAbstractIntVector[lengths.getLength()];
int maxIndexesLen = 0;
for (int i = 0; i < lengths.getLength(); i++) {
unitIndexesList[i] = asIntVector(lengths.getDataAt(i));
maxIndexesLen = Math.max(maxIndexesLen, unitIndexesList[i].getLength());
}
double[] xx = new double[maxIndexesLen];
double[] yy = new double[maxIndexesLen];
for (RAbstractIntVector unitIndexes : unitIndexesList) {
boolean oldIsFinite = false;
int start = 0;
int unitIndexesLen = unitIndexes.getLength();
// following loop finds series of valid points (finite x and y values) and draws each
// such series as a polyline
for (int i = 0; i < unitIndexesLen; i++) {
int unitIndex = unitIndexes.getDataAt(i) - 1; // coverting R's 1-based index
Point origLoc = Point.fromUnits(unitToInches, x, y, unitIndex, conversionCtx);
Point loc = TransformMatrix.transLocation(origLoc, vpTransform.transform);
xx[i] = loc.x;
yy[i] = loc.y;
boolean currIsFinite = loc.isFinite();
boolean lastIter = i == (unitIndexesLen - 1);
if (currIsFinite && !oldIsFinite) {
start = i; // start a new series
} else if (oldIsFinite && (!currIsFinite || lastIter)) {
// draw the previous points series because
// (1) current is invalid point. Note: in (one of) the next iteration(s), the
// oldIsFinite will be false and we will update the start and start a new series
// (2) we are in the last iteration
if (lastIter || i - start > 1) {
// we draw only if the previous series of points was at least of length 3 or
// it's last iteration. This seems slightly weird, but that's how GnuR seems
// to work
dev.drawPolyLines(drawingCtx, xx, yy, start, (i - start) + 1);
}
}
oldIsFinite = currIsFinite;
}
}
Object doLines(RAbstractVector x, RAbstractVector y, RList lengths, @SuppressWarnings("unused") Object arrowIgnored) {
// TODO: implement arrows
gridLinesNode.execute(x, y, lengths);
return RNull.instance;
}
}
......@@ -68,7 +68,7 @@ public abstract class LPoints extends RExternalBuiltinNode.Arg4 {
double cex = GPar.getCex(gpar);
ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx);
// Note: unlike in other drawing primitives, we only consider length of x
int length = unitLength.execute(xVec);
......
/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.r.library.fastrGrid;
import static com.oracle.truffle.r.nodes.builtin.CastBuilder.Predef.abstractVectorValue;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.r.nodes.builtin.RExternalBuiltinNode;
import com.oracle.truffle.r.runtime.data.RList;
import com.oracle.truffle.r.runtime.data.RNull;
import com.oracle.truffle.r.runtime.data.model.RAbstractVector;
public abstract class LPolygon extends RExternalBuiltinNode.Arg3 {
@Child private GridLinesNode gridLinesNode = GridLinesNode.createPolygon();
static {
Casts casts = new Casts(LPolygon.class);
casts.arg(0).mustBe(abstractVectorValue());
casts.arg(1).mustBe(abstractVectorValue());
casts.arg(2).mustBe(RList.class);
}
public static LPolygon create() {
return LPolygonNodeGen.create();
}
@Specialization
Object doLines(RAbstractVector x, RAbstractVector y, RList lengths) {
gridLinesNode.execute(x, y, lengths);
return RNull.instance;
}
}
......@@ -56,7 +56,7 @@ public abstract class LRect extends RExternalBuiltinNode.Arg6 {
DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar());
ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx);
int length = GridUtils.maxLength(unitLength, xVec, yVec, wVec, hVec);
for (int i = 0; i < length; i++) {
......
......@@ -55,7 +55,7 @@ public abstract class LSegments extends RExternalBuiltinNode.Arg5 {
DrawingContext drawingCtx = GPar.asDrawingContext(ctx.getGridState().getGpar());
ViewPortTransform vpTransform = getViewPortTransform.execute(currentVP);
ViewPortContext vpContext = vpContextFromVP.execute(currentVP);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, drawingCtx);
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, dev, drawingCtx);
int length = GridUtils.maxLength(unitLength, x0, y0, x1, y1);
double[] xx = new double[2];
......
......@@ -15,6 +15,7 @@ import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asAbstractContain
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asDouble;
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asIntVector;
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asList;
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.asListOrNull;
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.fmax;
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.fmin;
import static com.oracle.truffle.r.library.fastrGrid.GridUtils.hasRClass;
......@@ -34,6 +35,7 @@ import com.oracle.truffle.r.library.fastrGrid.UnitFactory.UnitToInchesNodeGen;
import com.oracle.truffle.r.library.fastrGrid.ViewPortContext.VPContextFromVPNode;
import com.oracle.truffle.r.library.fastrGrid.ViewPortTransform.GetViewPortTransformNode;
import com.oracle.truffle.r.library.fastrGrid.device.DrawingContext;
import com.oracle.truffle.r.library.fastrGrid.device.GridDevice;
import com.oracle.truffle.r.nodes.attributes.GetFixedAttributeNode;
import com.oracle.truffle.r.nodes.helpers.InheritsCheckNode;
import com.oracle.truffle.r.nodes.unary.CastNode;
......@@ -177,13 +179,14 @@ public class Unit {
}
}
private static double convertToInches(double value, int unitId, double vpSize, double scalemin, double scalemax, int nullLMode, int nullAMode, boolean isDimension, DrawingContext drawingCtx) {
private static double convertToInches(double value, int unitId, RList data, UnitConversionContext ctx, AxisOrDimension axisOrDim) {
double vpSize = ctx.getViewPortSize(axisOrDim);
switch (unitId) {
case INCHES:
return value;
case NATIVE:
double tmp = isDimension ? value : (value - scalemin);
return (tmp / (scalemax - scalemin)) * vpSize;
double tmp = axisOrDim.isDimension() ? value : (value - ctx.getScaleMin(axisOrDim));
return (tmp / (ctx.getScaleMax(axisOrDim) - ctx.getScaleMin(axisOrDim))) * vpSize;
case NPC:
return value * vpSize;
case POINTS:
......@@ -194,12 +197,18 @@ public class Unit {
return value / (CM_IN_INCH * 10);
case CHAR:
case MYCHAR:
return (value * drawingCtx.getFontSize()) / INCH_TO_POINTS_FACTOR;
return (value * ctx.drawingContext.getFontSize()) / INCH_TO_POINTS_FACTOR;
case LINES:
case MYLINES:
return (value * drawingCtx.getFontSize() * drawingCtx.getLineHeight()) / INCH_TO_POINTS_FACTOR;
return (value * ctx.drawingContext.getFontSize() * ctx.drawingContext.getLineHeight()) / INCH_TO_POINTS_FACTOR;
case STRINGWIDTH:
case MYSTRINGWIDTH:
return ctx.device.getStringWidth(ctx.drawingContext, RRuntime.asString(data.getDataAt(0)));
case STRINGHEIGHT:
case MYSTRINGHEIGHT:
return ctx.device.getStringHeight(ctx.drawingContext, RRuntime.asString(data.getDataAt(0)));
case NULL:
return evaluateNullUnit(value, vpSize, nullLMode, nullAMode);
return evaluateNullUnit(value, vpSize, ctx.nullLayoutMode, ctx.nullArithmeticMode);
default:
throw RInternalError.unimplemented("unit type " + unitId + " in convertToInches");
}
......@@ -387,6 +396,33 @@ public class Unit {
}
}
/**
* Used to discriminate between x axis, y axis, width, and height when doing unit conversions.
* The order should be the same as used in e.g. {@code L_convert}, which is 0 means x, 1 means
* y, 2 means width, 3 means height.
*/
public enum AxisOrDimension {
X,
Y,
WIDTH,
HEIGHT;
private static final AxisOrDimension[] enumValues = values();
static AxisOrDimension fromInt(int value) {
assert value >= 0 && value < 4;
return enumValues[value];
}
public boolean isHorizontal() {
return this == X || this == WIDTH;
}
public boolean isDimension() {
return this == WIDTH || this == HEIGHT;
}
}
/**
* Wraps the data necessary for converting a unit to another unit. Note: {@code nullLMode} and
* {@code nullAMode} is only used for converting 'NULL' units and is only explicitly set when
......@@ -397,20 +433,34 @@ public class Unit {
public final Size viewPortSize;
public final ViewPortContext viewPortContext;
public final DrawingContext drawingContext;
public final GridDevice device;
public final int nullLayoutMode;
public final int nullArithmeticMode;
public UnitConversionContext(Size viewPortSize, ViewPortContext viewPortContext, DrawingContext drawingContext) {
this(viewPortSize, viewPortContext, drawingContext, 0, 0);
public UnitConversionContext(Size viewPortSize, ViewPortContext viewPortContext, GridDevice device, DrawingContext drawingContext) {
this(viewPortSize, viewPortContext, device, drawingContext, 0, 0);
}
public UnitConversionContext(Size viewPortSize, ViewPortContext viewPortContext, DrawingContext drawingContext, int nullLMode, int nullAMode) {
public UnitConversionContext(Size viewPortSize, ViewPortContext viewPortContext, GridDevice device, DrawingContext drawingContext, int nullLMode, int nullAMode) {
this.viewPortSize = viewPortSize;
this.viewPortContext = viewPortContext;
this.device = device;
this.drawingContext = drawingContext;
this.nullLayoutMode = nullLMode;
this.nullArithmeticMode = nullAMode;
}
public double getViewPortSize(AxisOrDimension forAxisOrDim) {
return forAxisOrDim.isHorizontal() ? viewPortSize.getWidth() : viewPortSize.getHeight();
}
public double getScaleMin(AxisOrDimension forAxisOrDim) {
return forAxisOrDim.isHorizontal() ? viewPortContext.xscalemin : viewPortContext.yscalemin;
}
public double getScaleMax(AxisOrDimension forAxisOrDim) {
return forAxisOrDim.isHorizontal() ? viewPortContext.xscalemax : viewPortContext.yscalemax;
}
}
/**
......@@ -425,34 +475,29 @@ public class Unit {
}
public double convertX(RAbstractContainer vector, int index, UnitConversionContext ctx) {
return execute(vector, index, ctx.viewPortSize.getWidth(), ctx.viewPortContext.xscalemin, ctx.viewPortContext.xscalemax, ctx.nullLayoutMode, ctx.nullArithmeticMode, false,
ctx.drawingContext);
return execute(vector, index, ctx, AxisOrDimension.X);
}
public double convertY(RAbstractContainer vector, int index, UnitConversionContext ctx) {
return execute(vector, index, ctx.viewPortSize.getHeight(), ctx.viewPortContext.yscalemin, ctx.viewPortContext.yscalemax, ctx.nullLayoutMode, ctx.nullArithmeticMode, false,
ctx.drawingContext);
return execute(vector, index, ctx, AxisOrDimension.Y);
}
public double convertWidth(RAbstractContainer vector, int index, UnitConversionContext ctx) {
return execute(vector, index, ctx.viewPortSize.getWidth(), ctx.viewPortContext.xscalemin, ctx.viewPortContext.xscalemax, ctx.nullLayoutMode, ctx.nullArithmeticMode, true,
ctx.drawingContext);
return execute(vector, index, ctx, AxisOrDimension.WIDTH);
}
public double convertHeight(RAbstractContainer vector, int index, UnitConversionContext ctx) {
return execute(vector, index, ctx.viewPortSize.getHeight(), ctx.viewPortContext.yscalemin, ctx.viewPortContext.yscalemax, ctx.nullLayoutMode, ctx.nullArithmeticMode, true,
ctx.drawingContext);
return execute(vector, index, ctx, AxisOrDimension.HEIGHT);
}
public double convertDimension(RAbstractContainer vector, int index, UnitConversionContext ctx, boolean isWidth) {
return isWidth ? convertWidth(vector, index, ctx) : convertHeight(vector, index, ctx);
}
public abstract double execute(RAbstractContainer vector, int index, double vpSize, double scalemin, double scalemax, int nullLMode, int nullAMode, boolean isDimension,
DrawingContext drawingCtx);
public abstract double execute(RAbstractContainer vector, int index, UnitConversionContext ctx, AxisOrDimension axisOrDim);
@Specialization(guards = "isSimple(value)")
double doNormal(RAbstractContainer value, int index, double vpSize, double scalemin, double scalemax, int nullLMode, int nullAMode, boolean isDimension, DrawingContext drawingCtx,
double doNormal(RAbstractContainer value, int index, UnitConversionContext ctx, AxisOrDimension axisOrDim,
@Cached("createAsDoubleCast()") CastNode asDoubleCast,
@Cached("create()") UnitUnitIdNode unitUnitId) {
int unitId = unitUnitId.execute(value, index);
......@@ -460,29 +505,27 @@ public class Unit {
double scalarValue = vector.getDataAt(index % vector.getLength());
if (isGrobUnit(unitId)) {
RList grobList = asList(vector.getAttr("data"));
return grobUnitToInches.execute(scalarValue, unitId, grobList.getDataAt(index % grobList.getLength()), nullLMode, nullAMode);
return grobUnitToInches.execute(scalarValue, unitId, grobList.getDataAt(index % grobList.getLength()), ctx);
}
return convertToInches(scalarValue, unitId, vpSize, scalemin, scalemax, nullLMode, nullAMode, isDimension, drawingCtx);
return convertToInches(scalarValue, unitId, asListOrNull(vector.getAttr("data")), ctx, axisOrDim);
}
@Specialization(guards = "isUnitList(value)")
double doList(RList value, int index, double vpSize, double scalemin, double scalemax, int nullLMode, int nullAMode, boolean isDimension, DrawingContext drawingCtx,
double doList(RList value, int index, UnitConversionContext ctx, AxisOrDimension axisOrDim,
@Cached("create()") UnitToInchesNode recursiveNode) {
Object unwrapped = value.getDataAt(index % value.getLength());
if (unwrapped instanceof RAbstractVector) {
return recursiveNode.execute((RAbstractContainer) unwrapped, 0, vpSize, scalemin, scalemax, nullLMode, nullAMode, isDimension, drawingCtx);
return recursiveNode.execute((RAbstractContainer) unwrapped, 0, ctx, axisOrDim);
}
throw error(Message.GENERIC, "Unexpected unit list with non-vector like element at index " + index);
}
@Specialization(guards = "isArithmetic(list)")
double doArithmetic(RList list, int index, double vpSize, double scalemin, double scalemax, int nullLMode, int nullAMode, boolean isDimension, DrawingContext drawingCtx,
double doArithmetic(RList list, int index, UnitConversionContext ctx, AxisOrDimension axisOrDim,
@Cached("create()") UnitLengthNode unitLengthNode,
@Cached("create()") UnitToInchesNode recursiveNode) {
ArithmeticUnit expr = ArithmeticUnit.asArithmeticUnit(list);
// Note the catch: newNullAMode is applied only if isDimension == true
BiFunction<RAbstractContainer, Integer, Double> recursive = (x, newNullAMode) -> recursiveNode.execute(x, index, vpSize, scalemin, scalemax, nullLMode,
isDimension ? newNullAMode : nullAMode, isDimension, drawingCtx);
BiFunction<RAbstractContainer, Integer, Double> recursive = (x, newNullAMode) -> recursiveNode.execute(x, index, getNewCtx(ctx, axisOrDim, newNullAMode), axisOrDim);
switch (expr.op) {
case "+":
return recursive.apply(expr.arg1, L_adding) + recursive.apply(expr.arg2, L_adding);
......@@ -496,11 +539,11 @@ public class Unit {
}
// must be aggregate operation
int newNullAMode = isDimension ? getNullAMode(expr.op) : nullAMode;
UnitConversionContext newCtx = getNewCtx(ctx, axisOrDim, getNullAMode(expr.op));
int len = unitLengthNode.execute(expr.arg1);
double[] values = new double[len];
for (int i = 0; i < len; i++) {
values[i] = recursiveNode.execute(expr.arg1, i, vpSize, scalemin, scalemax, nullLMode, newNullAMode, isDimension, drawingCtx);
values[i] = recursiveNode.execute(expr.arg1, i, newCtx, axisOrDim);
}
switch (expr.op) {
......@@ -515,6 +558,12 @@ public class Unit {
}
}
// Note the catch: newNullAMode is applied only if the axisOrDim is dimension
private static UnitConversionContext getNewCtx(UnitConversionContext ctx, AxisOrDimension axisOrDim, int newNullAMode) {
return new UnitConversionContext(ctx.viewPortSize, ctx.viewPortContext, ctx.device, ctx.drawingContext, ctx.nullLayoutMode,
axisOrDim.isDimension() ? newNullAMode : ctx.nullArithmeticMode);
}
private static int getNullAMode(String op) {
switch (op) {
case "min":
......@@ -543,7 +592,7 @@ public class Unit {
// transcribed from unit.c function evaluateGrobUnit
public double execute(double value, int unitId, Object grob, int nullLMode, int nullAMode) {
public double execute(double value, int unitId, Object grob, UnitConversionContext conversionCtx) {
GridContext ctx = GridContext.getContext();
RList currentVP = ctx.getGridState().getViewPort();
getViewPortTransform.execute(currentVP);
......@@ -571,10 +620,10 @@ public class Unit {
case GROBY:
if (unitId == GROBY && isRelativeUnit.execute(unitxy.getDataAt(1), 0)) {
double nullUnitValue = pureNullUnitValue((RAbstractContainer) unitxy.getDataAt(1), 0);
result = evaluateNullUnit(nullUnitValue, vpTransform.size.getHeight(), nullLMode, nullAMode);
result = evaluateNullUnit(nullUnitValue, vpTransform.size.getHeight(), conversionCtx.nullLayoutMode, conversionCtx.nullArithmeticMode);
} else if (isRelativeUnit.execute(unitxy.getDataAt(0), 0)) {
double nullUnitValue = pureNullUnitValue((RAbstractContainer) unitxy.getDataAt(0), 0);
result = evaluateNullUnit(nullUnitValue, vpTransform.size.getWidth(), nullLMode, nullAMode);
result = evaluateNullUnit(nullUnitValue, vpTransform.size.getWidth(), conversionCtx.nullLayoutMode, conversionCtx.nullArithmeticMode);
} else {
throw RInternalError.unimplemented("GrobUnitToInches from unit.c: 610");
}
......@@ -585,16 +634,16 @@ public class Unit {
// Note: GnuR uses equivalent of vpTransform.size.getWidth() even for
// GROBHEIGHT, bug?
double nullUnitValue = pureNullUnitValue((RAbstractContainer) unitxy.getDataAt(0), 0);
result = evaluateNullUnit(nullUnitValue, vpTransform.size.getWidth(), nullLMode, nullAMode);
result = evaluateNullUnit(nullUnitValue, vpTransform.size.getWidth(), conversionCtx.nullLayoutMode, conversionCtx.nullArithmeticMode);
} else {
UnitConversionContext conversionCtx = new UnitConversionContext(vpTransform.size, vpContext, GPar.asDrawingContext(currentGP));
UnitConversionContext newConversionCtx = new UnitConversionContext(vpTransform.size, vpContext, conversionCtx.device, GPar.asDrawingContext(currentGP));
initUnitToInchesNode();
if (unitId == GROBWIDTH) {
result = unitToInchesNode.convertWidth((RAbstractContainer) unitxy.getDataAt(0), 0, conversionCtx);
result = unitToInchesNode.convertWidth((RAbstractContainer) unitxy.getDataAt(0), 0, newConversionCtx);
} else {
// Note: GnuR uses height transform for both grobascent, grobdescent and
// for height
result = unitToInchesNode.convertHeight((RAbstractContainer) unitxy.getDataAt(0), 0, conversionCtx);
result = unitToInchesNode.convertHeight((RAbstractContainer) unitxy.getDataAt(0), 0, newConversionCtx);
}
}
break;
......
......@@ -35,6 +35,7 @@ import com.oracle.truffle.r.library.fastrGrid.LInitViewPortStack;
import com.oracle.truffle.r.library.fastrGrid.LLines;
import com.oracle.truffle.r.library.fastrGrid.LNewPage;
import com.oracle.truffle.r.library.fastrGrid.LPoints;
import com.oracle.truffle.r.library.fastrGrid.LPolygon;
import com.oracle.truffle.r.library.fastrGrid.LRect;
import com.oracle.truffle.r.library.fastrGrid.LSegments;
import com.oracle.truffle.r.library.fastrGrid.LText;
......@@ -702,6 +703,8 @@ public class CallAndExternalFunctions {
return LRect.create();
case "L_lines":
return LLines.create();
case "L_polygon":
return LPolygon.create();
case "L_text":
return LText.create();
case "L_textBounds":
......
......@@ -783,7 +783,7 @@ com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridUtil
com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/Unit.java,gnu_r_murrel_core.copyright
com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortLocation.java,gnu_r_murrel_core.copyright
com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortContext.java,gnu_r_murrel_core.copyright
com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LLines.java,gnu_r_murrel_core.copyright
com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridLinesNode.java,gnu_r_murrel_core.copyright
com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/ViewPortTransform.java,gnu_r_murrel_core.copyright
com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/LGridDirty.java,gnu_r_murrel_core.copyright
com.oracle.truffle.r.library/src/com/oracle/truffle/r/library/fastrGrid/GridColorUtils.java,gnu_r.copyright
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment