/* * JaamSim Discrete Event Simulation * Copyright (C) 2013 Ausenco Engineering Canada Inc. * * 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.jaamsim.DisplayModels; import java.util.ArrayList; import java.util.List; import com.jaamsim.Graphics.Graph; import com.jaamsim.basicsim.Entity; import com.jaamsim.controllers.RenderManager; import com.jaamsim.datatypes.DoubleVector; import com.jaamsim.input.ColourInput; import com.jaamsim.input.EntityInput; import com.jaamsim.input.Keyword; import com.jaamsim.input.ValueInput; import com.jaamsim.math.Color4d; import com.jaamsim.math.Mat4d; import com.jaamsim.math.MathUtils; import com.jaamsim.math.Transform; import com.jaamsim.math.Vec3d; import com.jaamsim.math.Vec4d; import com.jaamsim.render.DisplayModelBinding; import com.jaamsim.render.LineProxy; import com.jaamsim.render.PolygonProxy; import com.jaamsim.render.RenderProxy; import com.jaamsim.render.RenderUtils; import com.jaamsim.render.StringProxy; import com.jaamsim.render.TessFontKey; import com.jaamsim.units.DimensionlessUnit; import com.jaamsim.units.Unit; public class GraphModel extends DisplayModel { @Keyword(description = "The text height for the graph title.", example = "Graph1 TitleTextHeight { 0.05 }") private final ValueInput titleTextHeight; @Keyword(description = "The text height for the x-axis title.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 XAxisTitleTextHeight { 0.05 }") private final ValueInput xAxisTitleTextHeight; @Keyword(description = "The text height for the y-axis title.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 YAxisTitleTextHeight { 0.05 }") private final ValueInput yAxisTitleTextHeight; @Keyword(description = "The text height for both x- and y-axis labels.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 LabelTextHeight { 0.025 }") private final ValueInput labelTextHeight; @Keyword(description = "The gap between the title and top of the graph.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 TitleGap { 0.025 }") private final ValueInput titleGap; @Keyword(description = "The gap between the x-axis labels and the x-axis.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 XAxisLabelGap { 0.025 }") private final ValueInput xAxisLabelGap; @Keyword(description = "The gap between the x-axis title and the x-axis labels.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 xAxisTitleGap { 0.025 }") private final ValueInput xAxisTitleGap; @Keyword(description = "The gap between the y-axis and its labels.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 YAxisLabelGap { 0.025 }") private final ValueInput yAxisLabelGap; @Keyword(description = "The gap between the y-axis title and the y-axis labels.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 yAxisTitleGap { 0.025 }") private final ValueInput yAxisTitleGap; @Keyword(description = "The margin between the top of the graph and the top of the graph object.\n" + "Expressed as a fraction of the total graph height." + "side of the graph.", example = "Graph1 TopMargin { 0.10 }") private final ValueInput topMargin; @Keyword(description = "The margin between the bottom of the graph and the bottom of the graph object.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 BottomMargin { 0.10 }") private final ValueInput bottomMargin; @Keyword(description = "The margin between the left side of the graph and the left side of the graph object.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 LeftMargin { 0.20 }") private final ValueInput leftMargin; @Keyword(description = "The margin between the right side of the graph and the right side of the graph object.\n" + "Expressed as a fraction of the total graph height.", example = "Graph1 RightMargin { 0.20 }") private final ValueInput rightMargin; @Keyword(description = "The text model to be used for the graph title.\n" + "Determines the font, color, and style (bold, italics) for the text.", example = "Graph1 TitleTextModel { TextModelDefault }") protected final EntityInput<TextModel> titleTextModel; @Keyword(description = "The text model to be used for the axis titles (x-axis, y-axis, and secondary y-axis).\n" + "Determines the font, color, and style (bold, italics) for the text.", example = "Graph1 AxisTitleTextModel { TextModelDefault }") protected final EntityInput<TextModel> axisTitleTextModel; @Keyword(description = "The text model to be used for the numbers next to the tick marks on each axis" + " (x-axis, y-axis, and secondary y-axis).\n" + "Determines the font, color, and style (bold, italics) for the text.", example = "Graph1 LabelTextModel { TextModelDefault }") protected final EntityInput<TextModel> labelTextModel; @Keyword(description = "The color of the graph background, defined by a color keyword or an RGB value.", example = "Graph1 GraphColor { floralwhite }") private final ColourInput graphColor; @Keyword(description = "The color for the outer pane background, defined using a color keyword or an RGB value.", example = "Graph1 BackgroundColor { floralwhite }") private final ColourInput backgroundColor; @Keyword(description = "The color of the graph border, defined using a color keyword or an RGB value.", example = "Graph1 BorderColor { red }") private final ColourInput borderColor; private static final int maxTicks = 100; { titleTextHeight = new ValueInput("TitleTextHeight", "Key Inputs", 0.05d); titleTextHeight.setUnitType(DimensionlessUnit.class); this.addInput(titleTextHeight); xAxisTitleTextHeight = new ValueInput("XAxisTitleTextHeight", "Key Inputs", 0.05d); xAxisTitleTextHeight.setUnitType(DimensionlessUnit.class); this.addInput(xAxisTitleTextHeight); yAxisTitleTextHeight = new ValueInput("YAxisTitleTextHeight", "Key Inputs", 0.05d); yAxisTitleTextHeight.setUnitType(DimensionlessUnit.class); this.addInput(yAxisTitleTextHeight); labelTextHeight = new ValueInput("LabelTextHeight", "Key Inputs", 0.025d); labelTextHeight.setUnitType(DimensionlessUnit.class); this.addInput(labelTextHeight); titleGap = new ValueInput("TitleGap", "Key Inputs", 0.05d); titleGap.setUnitType(DimensionlessUnit.class); this.addInput(titleGap); xAxisTitleGap = new ValueInput("XAxisTitleGap", "Key Inputs", 0.025d); xAxisTitleGap.setUnitType(DimensionlessUnit.class); this.addInput(xAxisTitleGap); xAxisLabelGap = new ValueInput("XAxisLabelGap", "Key Inputs", 0.025d); xAxisLabelGap.setUnitType(DimensionlessUnit.class); this.addInput(xAxisLabelGap); yAxisTitleGap = new ValueInput("YAxisTitleGap", "Key Inputs", 0.025d); yAxisTitleGap.setUnitType(DimensionlessUnit.class); this.addInput(yAxisTitleGap); yAxisLabelGap = new ValueInput("YAxisLabelGap", "Key Inputs", 0.025d); yAxisLabelGap.setUnitType(DimensionlessUnit.class); this.addInput(yAxisLabelGap); topMargin = new ValueInput("TopMargin", "Key Inputs", 0.15d); topMargin.setUnitType(DimensionlessUnit.class); this.addInput(topMargin); bottomMargin = new ValueInput("BottomMargin", "Key Inputs", 0.175d); bottomMargin.setUnitType(DimensionlessUnit.class); this.addInput(bottomMargin); leftMargin = new ValueInput("LeftMargin", "Key Inputs", 0.21d); leftMargin.setUnitType(DimensionlessUnit.class); this.addInput(leftMargin); rightMargin = new ValueInput("RightMargin", "Key Inputs", 0.21d); rightMargin.setUnitType(DimensionlessUnit.class); this.addInput(rightMargin); titleTextModel = new EntityInput<>(TextModel.class, "TitleTextModel", "Key Inputs", null); this.addInput(titleTextModel); axisTitleTextModel = new EntityInput<>(TextModel.class, "AxisTitleTextModel", "Key Inputs", null); this.addInput(axisTitleTextModel); labelTextModel = new EntityInput<>(TextModel.class, "LabelTextModel", "Key Inputs", null); this.addInput(labelTextModel); graphColor = new ColourInput("GraphColor", "Key Inputs", ColourInput.getColorWithName("ivory")); this.addInput(graphColor); this.addSynonym(graphColor, "GraphColour"); backgroundColor = new ColourInput("BackgroundColor", "Key Inputs", ColourInput.getColorWithName("gray95")); this.addInput(backgroundColor); this.addSynonym(backgroundColor, "BackgroundColour"); borderColor = new ColourInput("BorderColor", "Key Inputs", ColourInput.BLACK); this.addInput(borderColor); this.addSynonym(borderColor, "BorderColour"); } @Override public DisplayModelBinding getBinding(Entity ent) { return new Binding(ent, this); } @Override public boolean canDisplayEntity(Entity ent) { return ent instanceof Graph; } private class Binding extends DisplayModelBinding { // Size and position of the graph area (excluding the titles, labels, etc.) scaled to the unit cube protected Vec3d graphSize; // graph size protected Vec3d graphOrigin; // bottom left position of the graph area, protected Vec3d graphCenter; // Center point of the graph area private Graph graphObservee; private List<Vec4d> graphRectPoints = null; private Mat4d graphAreaTrans = null; private Mat4d graphToWorldTrans = null; private Mat4d objectTransComp = null; // The composed transform and scale as a Matrix4d private Transform objectTrans = null; private Vec3d objectScale = null; private long pickingID; private double xScaleFactor; private Vec3d xScaleVec; private Vec3d yScaleVec; private double xMin; private double xMax; private double xRange; private double xAxisInterval; private double yMin; private double yMax; private double yRange; private double yAxisInterval; private double secYMin; private double secYMax; private double secYRange; private double secYAxisInterval; private boolean timeTrace; private double yAxisTitleHeight; private TessFontKey axisTitleFontKey; private Color4d axisTitleFontColor; private TessFontKey labelFontKey; private Color4d labelFontColor; private double labelHeight; // height of the label text private double xAxisTickSize; // vertical tick marks for the x-axis private double yAxisTickSize; // horizontal tick marks for the y-axis private double zBump; public Binding(Entity ent, DisplayModel dm) { super(ent, dm); graphSize = new Vec3d(); graphOrigin = new Vec3d(); graphCenter = new Vec3d(); try { graphObservee = (Graph)observee; if (graphObservee != null) { pickingID = graphObservee.getEntityNumber(); } } catch (ClassCastException e) { // The observee is not a display entity graphObservee = null; } } @Override public void collectProxies(double simTime, ArrayList<RenderProxy> out) { if (graphObservee == null || !graphObservee.getShow()) { return; } updateObjectTrans(simTime); registerCacheMiss("Graph"); // This factor is applied to lengths expressed as a fraction of the graph's y-extent and // converts then to fractions of the graph's x-extent Vec3d objectSize = graphObservee.getSize(); xScaleFactor = objectSize.y / objectSize.x; // These two matrices are needed to cancel out the object level non-uniform scaling for text objects // xScale if for horizontal text, yScale is for vertical text xScaleVec = new Vec3d(xScaleFactor, 1, 1); yScaleVec = new Vec3d(1/xScaleFactor, 1, 1); xMin = graphObservee.getXAxisStart(); xMax = graphObservee.getXAxisEnd(); xRange = xMax - xMin; xAxisInterval = graphObservee.getXAxisInterval(); yMin= graphObservee.getYAxisStart(); yMax= graphObservee.getYAxisEnd(); yRange = yMax - yMin; yAxisInterval = graphObservee.getYAxisInterval(); secYMin= graphObservee.getSecondaryYAxisStart(); secYMax= graphObservee.getSecondaryYAxisEnd(); secYRange = secYMax - secYMin; secYAxisInterval = graphObservee.getSecondaryYAxisInterval(); timeTrace = graphObservee.getTimeTrace(); yAxisTitleHeight = yAxisTitleTextHeight.getValue()*xScaleFactor; // scaled height of the y-axis title if( axisTitleTextModel.getValue() == null ) { axisTitleFontKey = TextModel.getDefaultTessFontKey(); axisTitleFontColor = ColourInput.BLACK; } else { axisTitleFontKey = axisTitleTextModel.getValue().getTessFontKey(); axisTitleFontColor = axisTitleTextModel.getValue().getFontColor(); } if( labelTextModel.getValue() == null ) { labelFontKey = TextModel.getDefaultTessFontKey(); labelFontColor = ColourInput.BLACK; } else { labelFontKey = labelTextModel.getValue().getTessFontKey(); labelFontColor = labelTextModel.getValue().getFontColor(); } labelHeight = labelTextHeight.getValue(); xAxisTickSize = labelHeight/2; // vertical tick marks for the x-axis yAxisTickSize = xAxisTickSize * xScaleFactor; // horizontal tick marks for the y-axis zBump = 0.001 * objectSize.x; // z-coordinate; // Draw the main frame out.add(new PolygonProxy(RenderUtils.RECT_POINTS, objectTrans, objectScale, backgroundColor.getValue(), false, 1, getVisibilityInfo(), pickingID)); out.add(new PolygonProxy(RenderUtils.RECT_POINTS, objectTrans, objectScale, borderColor.getValue(), true, 1, getVisibilityInfo(), pickingID)); // Draw the graph frame out.add(new PolygonProxy(graphRectPoints, objectTrans, objectScale, graphColor.getValue(), false, 1, getVisibilityInfo(), pickingID)); out.add(new PolygonProxy(graphRectPoints, objectTrans, objectScale, borderColor.getValue(), true, 1, getVisibilityInfo(), pickingID)); // Draw the graph title drawGraphTitle(out); // Draw the x-axis, y-axis, and axis titles drawXAxis(out); drawYAxis(out); // Draw the secondary y-axis title (if used) if( graphObservee.showSecondaryYAxis() ) drawSecondaryYAxis(out); // Draw the selected grid lines drawXLines(out); drawYLines(out); // Draw the primary series ArrayList<Graph.SeriesInfo> primarySeries = graphObservee.getPrimarySeries(); for (int i = 0; i < primarySeries.size(); ++i) { drawSeries(primarySeries.get(i), yMin, yMax, simTime, out); } // Draw the secondary series ArrayList<Graph.SeriesInfo> secondarySeries = graphObservee.getSecondarySeries(); for (int i = 0; i < secondarySeries.size(); ++i) { drawSeries(secondarySeries.get(i), secYMin, secYMax, simTime, out); } } private void drawSeries(Graph.SeriesInfo series, double yMinimum, double yMaximum, double simTime, ArrayList<RenderProxy> out) { if (series.numPoints < 2) return; // Nothing to display yet double yRange = yMaximum - yMinimum; // yRange can be either the primary or secondary range double[] yVals = new double[series.numPoints]; double[] xVals = new double[series.numPoints]; for (int i = 0; i < series.numPoints; i++) { if( timeTrace ) xVals[i] = MathUtils.bound((series.xValues[i] - simTime - xMin) / xRange, 0, 1) - 0.5; else xVals[i] = MathUtils.bound((series.xValues[i] - xMin) / xRange, 0, 1) - 0.5; yVals[i] = MathUtils.bound((series.yValues[i] - yMinimum) / yRange, 0, 1) - 0.5; } ArrayList<Vec4d> seriesPoints = new ArrayList<>((series.numPoints-1)*2); for (int i=0; i<series.numPoints; i++) { if (i != series.indexOfLastEntry) { seriesPoints.add(new Vec4d(xVals[i], yVals[i], zBump, 1.0d)); int k = i + 1; if (k == series.numPoints) k = 0; seriesPoints.add(new Vec4d(xVals[k], yVals[k], zBump, 1.0d)); } } // Transform from graph area to world space for (int i = 0; i < seriesPoints.size(); ++i) { seriesPoints.get(i).mult4(graphToWorldTrans, seriesPoints.get(i)); } out.add(new LineProxy(seriesPoints, series.lineColour, series.lineWidth, getVisibilityInfo(), pickingID)); } private void drawGraphTitle(ArrayList<RenderProxy> out) { String titleText = graphObservee.getTitle(); TessFontKey titleFontKey; Color4d titleFontColor; if( titleTextModel.getValue() == null ) { titleFontKey = TextModel.getDefaultTessFontKey(); titleFontColor = ColourInput.BLACK; } else { titleFontKey = titleTextModel.getValue().getTessFontKey(); titleFontColor = titleTextModel.getValue().getFontColor(); } Vec4d titleCenter = new Vec4d(0, graphOrigin.y + graphSize.y + titleGap.getValue() + titleTextHeight.getValue()/2, zBump, 1.0d); Mat4d titleTrans = new Mat4d(); titleTrans.setTranslate3(titleCenter); titleTrans.mult4(objectTransComp, titleTrans); titleTrans.scaleCols3(xScaleVec); out.add(new StringProxy(titleText, titleFontKey, titleFontColor, titleTrans, titleTextHeight.getValue(), getVisibilityInfo(), pickingID)); } private void drawXAxis(ArrayList<RenderProxy> out) { String xAxisFormat = graphObservee.getXAxisLabelFormat(); ArrayList<Vec4d> tickPoints = new ArrayList<>(); double xAxisFactor = 1.0; if( graphObservee.getXAxisUnit() != null ) xAxisFactor = graphObservee.getXAxisUnit().getConversionFactorToSI(); for (int i = 0; xMin + i*xAxisInterval <= xMax; ++i) { if( i > maxTicks ) break; double x = (xMin + i * xAxisInterval); String text; if( timeTrace && x == 0 ) { text = "Now"; } else { text = String.format( xAxisFormat, x/xAxisFactor); } double xPos = graphOrigin.x + ( i * xAxisInterval * graphSize.x)/xRange; double yPos = graphOrigin.y - xAxisTickSize - xAxisLabelGap.getValue() - labelHeight/2; Mat4d labelTrans = new Mat4d(); labelTrans.setTranslate3(new Vec3d(xPos, yPos, zBump)); labelTrans.mult4(objectTransComp, labelTrans); labelTrans.scaleCols3(xScaleVec); out.add(new StringProxy(text, labelFontKey, labelFontColor, labelTrans, labelHeight, getVisibilityInfo(), pickingID)); // Prepare the tick marks Vec4d tickPointA = new Vec4d(xPos, graphOrigin.y, zBump, 1.0d); Vec4d tickPointB = new Vec4d(xPos, graphOrigin.y - xAxisTickSize, zBump, 1.0d); tickPointA.mult4(objectTransComp, tickPointA); tickPointB.mult4(objectTransComp, tickPointB); tickPoints.add(tickPointA); tickPoints.add(tickPointB); } out.add(new LineProxy(tickPoints, labelFontColor, 1, getVisibilityInfo(), pickingID)); // X-Axis Title String xAxisTitle = graphObservee.getXAxisTitle(); Vec4d titleCenter = new Vec4d(0, graphOrigin.y - xAxisTickSize - xAxisLabelGap.getValue() - labelHeight - xAxisTitleGap.getValue() - xAxisTitleTextHeight.getValue()/2, zBump, 1.0d); Mat4d xtitleTrans = new Mat4d(); xtitleTrans.setTranslate3(titleCenter); xtitleTrans.mult4(objectTransComp, xtitleTrans); xtitleTrans.scaleCols3(xScaleVec); out.add(new StringProxy(xAxisTitle, axisTitleFontKey, axisTitleFontColor, xtitleTrans, xAxisTitleTextHeight.getValue(), getVisibilityInfo(), pickingID)); } private void drawYAxis(ArrayList<RenderProxy> out) { String yAxisLabelFormat = graphObservee.getYAxisLabelFormat(); Unit yAxisUnit = graphObservee.getYAxisUnit(); double yAxisFactor = 1.0; if( yAxisUnit != null ) yAxisFactor = yAxisUnit.getConversionFactorToSI(); ArrayList<Vec4d> tickPoints = new ArrayList<>(); double minYLabelXPos = graphOrigin.x; for (int i = 0; i * yAxisInterval <= yRange; ++i) { if( i > maxTicks ) break; String text = String.format( yAxisLabelFormat, ( i * yAxisInterval + yMin )/yAxisFactor); double yPos = graphOrigin.y + (i * yAxisInterval * graphSize.y )/yRange; // Right justify the labels double stringLength = RenderManager.inst().getRenderedStringLength(labelFontKey, labelHeight*xScaleFactor, text); double xPos = graphOrigin.x - yAxisTickSize - yAxisLabelGap.getValue()*xScaleFactor - stringLength/2; // Save the left-most extent of the labels minYLabelXPos = Math.min(minYLabelXPos, xPos - stringLength/2); Mat4d labelTrans = new Mat4d(); labelTrans.setTranslate3(new Vec3d(xPos, yPos, zBump)); labelTrans.mult4(objectTransComp, labelTrans); labelTrans.scaleCols3(xScaleVec); out.add(new StringProxy(text, labelFontKey, labelFontColor, labelTrans, labelHeight, getVisibilityInfo(), pickingID)); // Prepare the tick marks Vec4d tickPointA = new Vec4d(graphOrigin.x , yPos, zBump, 1.0d); Vec4d tickPointB = new Vec4d(graphOrigin.x - yAxisTickSize, yPos, zBump, 1.0d); tickPointA.mult4(objectTransComp, tickPointA); tickPointB.mult4(objectTransComp, tickPointB); tickPoints.add(tickPointA); tickPoints.add(tickPointB); } out.add(new LineProxy(tickPoints, labelFontColor, 1, getVisibilityInfo(), pickingID)); // Primary Y-Axis Title String yAxisTitle = graphObservee.getYAxisTitle(); double xPos = minYLabelXPos - yAxisTitleGap.getValue()*xScaleFactor - yAxisTitleHeight/2; Mat4d ytitleTrans = new Mat4d(); ytitleTrans.setTranslate3(new Vec3d(xPos, 0, zBump)); ytitleTrans.setEuler3(new Vec3d(0, 0, Math.PI/2)); ytitleTrans.mult4(objectTransComp, ytitleTrans); ytitleTrans.scaleCols3(yScaleVec); out.add(new StringProxy(yAxisTitle, axisTitleFontKey, axisTitleFontColor, ytitleTrans, yAxisTitleHeight, getVisibilityInfo(), pickingID)); } private void drawSecondaryYAxis(ArrayList<RenderProxy> out) { ArrayList<Vec4d> tickPoints = new ArrayList<>(); // Secondary Y-Axis Labels and Tick Marks String secYAxisLabelFormat = graphObservee.getSecondaryYAxisLabelFormat(); Unit secYAxisUnit = graphObservee.getSecondaryYAxisUnit(); double secYAxisFactor = 1.0; if( secYAxisUnit != null ) secYAxisFactor = secYAxisUnit.getConversionFactorToSI(); double maxYLabelXPos = graphOrigin.x + graphSize.x; for (int i = 0; i * secYAxisInterval <= secYRange; ++i) { if( i > maxTicks ) break; String text = String.format( secYAxisLabelFormat, ( i * secYAxisInterval + secYMin )/secYAxisFactor); double yPos = graphOrigin.y + (i * secYAxisInterval * graphSize.y )/secYRange; // Right justify the labels double stringLength = RenderManager.inst().getRenderedStringLength(labelFontKey, labelHeight*xScaleFactor, text); double xPos = graphOrigin.x + graphSize.x + yAxisTickSize + yAxisLabelGap.getValue()*xScaleFactor + stringLength/2; // Save the right-most extent of the labels maxYLabelXPos = Math.max(maxYLabelXPos, xPos + stringLength/2); Mat4d labelTrans = new Mat4d(); labelTrans.setTranslate3(new Vec3d(xPos, yPos, zBump)); labelTrans.mult4(objectTransComp, labelTrans); labelTrans.scaleCols3(xScaleVec); out.add(new StringProxy(text, labelFontKey, labelFontColor, labelTrans, labelHeight, getVisibilityInfo(), pickingID)); // Prepare the tick marks Vec4d tickPointA = new Vec4d(graphOrigin.x + graphSize.x , yPos, zBump, 1.0d); Vec4d tickPointB = new Vec4d(graphOrigin.x + graphSize.x + yAxisTickSize, yPos, zBump, 1.0d); tickPointA.mult4(objectTransComp, tickPointA); tickPointB.mult4(objectTransComp, tickPointB); tickPoints.add(tickPointA); tickPoints.add(tickPointB); } out.add(new LineProxy(tickPoints, labelFontColor, 1, getVisibilityInfo(), pickingID)); // Secondary Y-Axis Title String secYAxisTitle = graphObservee.getSecondaryYAxisTitle(); double secXPos = maxYLabelXPos + yAxisTitleGap.getValue()*xScaleFactor + yAxisTitleHeight/2; Mat4d secYtitleTrans = new Mat4d(); secYtitleTrans.setTranslate3(new Vec3d(secXPos, 0, zBump)); secYtitleTrans.setEuler3(new Vec3d(0, 0, Math.PI/2)); secYtitleTrans.mult4(objectTransComp, secYtitleTrans); secYtitleTrans.scaleCols3(yScaleVec); out.add(new StringProxy(secYAxisTitle, axisTitleFontKey, axisTitleFontColor, secYtitleTrans, yAxisTitleHeight, getVisibilityInfo(), pickingID)); } private void drawXLines(ArrayList<RenderProxy> out) { DoubleVector xLines = graphObservee.getXLines(); ArrayList<Color4d> xLineColours = graphObservee.getXLineColours(); for (int i = 0; i < xLines.size(); ++i) { double xPos = (xLines.get(i) - xMin) / xRange - 0.5; ArrayList<Vec4d> linePoints = new ArrayList<>(2); linePoints.add(new Vec4d(xPos, -0.5, zBump, 1.0d)); linePoints.add(new Vec4d(xPos, 0.5, zBump, 1.0d)); int ind = Math.min(i, xLineColours.size()-1); Color4d colour = xLineColours.get(ind); // Transform to world space for (int j = 0; j < linePoints.size(); ++j) { linePoints.get(j).mult4(graphToWorldTrans, linePoints.get(j)); } out.add(new LineProxy(linePoints, colour, 1, getVisibilityInfo(), graphObservee.getEntityNumber())); } } private void drawYLines(ArrayList<RenderProxy> out) { DoubleVector yLines = graphObservee.getYLines(); ArrayList<Color4d> yLineColours = graphObservee.getYLineColours(); for (int i = 0; i < yLines.size(); ++i) { double yPos = (yLines.get(i) - yMin) / yRange - 0.5; ArrayList<Vec4d> linePoints = new ArrayList<>(2); linePoints.add(new Vec4d(-0.5, yPos, zBump, 1.0d)); linePoints.add(new Vec4d( 0.5, yPos, zBump, 1.0d)); int ind = Math.min(i, yLineColours.size()-1); Color4d colour = yLineColours.get(ind); // Transform to world space for (int j = 0; j < linePoints.size(); ++j) { linePoints.get(j).mult4(graphToWorldTrans, linePoints.get(j)); } out.add(new LineProxy(linePoints, colour, 1, getVisibilityInfo(), pickingID)); } } private void updateObjectTrans(double simTime) { // Set graph proportions Vec3d graphExtent = graphObservee.getSize(); double xScaleFactor = graphExtent.y / graphExtent.x; // Draw graphic rectangle graphSize.x = 1.0 - ( leftMargin.getValue() + rightMargin.getValue() ) * xScaleFactor; graphSize.y = 1.0 - ( topMargin.getValue() + bottomMargin.getValue() ); graphSize.z = 1; // Center position of the graph graphCenter = new Vec3d( leftMargin.getValue()/2 - rightMargin.getValue()/2, bottomMargin.getValue()/2 - topMargin.getValue()/2, 0.0 ); graphOrigin = new Vec3d( graphCenter.x - graphSize.x/2, graphCenter.y - graphSize.y/2, 0.0 ); objectTrans = graphObservee.getGlobalTrans(); objectScale = graphObservee.getSize(); objectScale.mul3(getModelScale()); objectTransComp = new Mat4d(); objectTrans.getMat4d(objectTransComp); objectTransComp.scaleCols3(objectScale); graphAreaTrans = new Mat4d(); graphAreaTrans.setTranslate3(graphCenter); graphAreaTrans.scaleCols3(graphSize); graphRectPoints = RenderUtils.transformPoints(graphAreaTrans, RenderUtils.RECT_POINTS, 0); graphToWorldTrans = new Mat4d(); graphToWorldTrans.mult4(objectTransComp, graphAreaTrans); } } }