/* * JaamSim Discrete Event Simulation * Copyright (C) 2009-2012 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.Graphics; import java.util.ArrayList; import com.jaamsim.Samples.SampleListInput; import com.jaamsim.Samples.SampleProvider; import com.jaamsim.datatypes.DoubleVector; import com.jaamsim.events.ProcessTarget; import com.jaamsim.input.ColorListInput; import com.jaamsim.input.ColourInput; import com.jaamsim.input.Input; import com.jaamsim.input.IntegerInput; import com.jaamsim.input.Keyword; import com.jaamsim.input.UnitTypeInput; import com.jaamsim.input.ValueListInput; import com.jaamsim.math.Color4d; import com.jaamsim.units.DimensionlessUnit; import com.jaamsim.units.TimeUnit; import com.jaamsim.units.Unit; import com.jaamsim.units.UserSpecifiedUnit; public class Graph extends GraphBasics { // Key Inputs category @Keyword(description = "The number of data points that can be displayed on the graph.\n" + " This parameter determines the resolution of the graph.", exampleList = {"200"}) protected final IntegerInput numberOfPoints; @Keyword(description = "The unit type for the primary y-axis.", exampleList = {"DistanceUnit"}) private final UnitTypeInput unitType; @Keyword(description = "One or more sources of data to be graphed on the primary y-axis.\n" + "Each source is graphed as a separate line and is specified by an Expression. Also" + "acceptable are: a constant value, a Probability Distribution, TimeSeries, or a " + "Calculation Object.", exampleList = {"{ [Entity1].Output1 } { [Entity2].Output2 }"}) protected final SampleListInput dataSource; @Keyword(description = "A list of colors for the line series to be displayed.\n" + "Each color can be specified by either a color keyword or an RGB value.\n" + "For multiple lines, each color must be enclosed in braces.\n" + "If only one color is provided, it is used for all the lines.", exampleList = {"{ red } { green }"}) protected final ColorListInput lineColorsList; @Keyword(description = "A list of line widths (in pixels) for the line series to be displayed.\n" + "If only one line width is provided, it is used for all the lines.", exampleList = {"2 1"}) protected final ValueListInput lineWidths; @Keyword(description = "The unit type for the secondary y-axis.", exampleList = {"DistanceUnit"}) private final UnitTypeInput secondaryUnitType; @Keyword(description = "One or more sources of data to be graphed on the secondary y-axis.\n" + "Each source is graphed as a separate line and is specified by an Expression. Also" + "acceptable are: a constant value, a Probability Distribution, TimeSeries, or a " + "Calculation Object.", exampleList = {"{ [Entity1].Output1 } { [Entity2].Output2 }"}) protected final SampleListInput secondaryDataSource; @Keyword(description = "A list of colors for the secondary line series to be displayed.\n" + "Each color can be specified by either a color keyword or an RGB value.\n" + "For multiple lines, each color must be enclosed in braces.\n" + "If only one color is provided, it is used for all the lines.", exampleList = {"{ red } { green }"}) protected final ColorListInput secondaryLineColorsList; @Keyword(description = "A list of line widths (in pixels) for the seconardy line series to be displayed.\n" + "If only one line width is provided, it is used for all the lines.", exampleList = {"2 1"}) protected final ValueListInput secondaryLineWidths; { // Key Inputs category numberOfPoints = new IntegerInput("NumberOfPoints", "Key Inputs", 100); numberOfPoints.setValidRange(0, Integer.MAX_VALUE); this.addInput(numberOfPoints); unitType = new UnitTypeInput("UnitType", "Key Inputs", UserSpecifiedUnit.class); unitType.setRequired(true); this.addInput(unitType); dataSource = new SampleListInput("DataSource", "Key Inputs", null); dataSource.setUnitType(UserSpecifiedUnit.class); dataSource.setEntity(this); dataSource.setRequired(true); this.addInput(dataSource); ArrayList<Color4d> defLineColor = new ArrayList<>(0); defLineColor.add(ColourInput.getColorWithName("red")); lineColorsList = new ColorListInput("LineColours", "Key Inputs", defLineColor); lineColorsList.setValidCountRange(1, Integer.MAX_VALUE); this.addInput(lineColorsList); this.addSynonym(lineColorsList, "LineColors"); DoubleVector defLineWidths = new DoubleVector(1); defLineWidths.add(1.0); lineWidths = new ValueListInput("LineWidths", "Key Inputs", defLineWidths); lineWidths.setUnitType(DimensionlessUnit.class); lineWidths.setValidCountRange(1, Integer.MAX_VALUE); this.addInput(lineWidths); secondaryUnitType = new UnitTypeInput("SecondaryUnitType", "Key Inputs", UserSpecifiedUnit.class); this.addInput(secondaryUnitType); secondaryDataSource = new SampleListInput("SecondaryDataSource", "Key Inputs", null); secondaryDataSource.setUnitType(UserSpecifiedUnit.class); secondaryDataSource.setEntity(this); this.addInput(secondaryDataSource); ArrayList<Color4d> defSecondaryLineColor = new ArrayList<>(0); defSecondaryLineColor.add(ColourInput.getColorWithName("black")); secondaryLineColorsList = new ColorListInput("SecondaryLineColours", "Key Inputs", defSecondaryLineColor); secondaryLineColorsList.setValidCountRange(1, Integer.MAX_VALUE); this.addInput(secondaryLineColorsList); this.addSynonym(secondaryLineColorsList, "SecondaryLineColors"); DoubleVector defSecondaryLineWidths = new DoubleVector(1); defSecondaryLineWidths.add(1.0); secondaryLineWidths = new ValueListInput("SecondaryLineWidths", "Key Inputs", defSecondaryLineWidths); secondaryLineWidths.setUnitType(DimensionlessUnit.class); secondaryLineWidths.setValidCountRange(1, Integer.MAX_VALUE); this.addInput(secondaryLineWidths); } public Graph() { timeTrace = true; this.setXAxisUnit(TimeUnit.class); } @Override public void updateForInput( Input<?> in ) { super.updateForInput( in ); if (in == unitType) { Class<? extends Unit> ut = unitType.getUnitType(); dataSource.setUnitType(ut); this.setYAxisUnit(ut); return; } if (in == secondaryUnitType) { Class<? extends Unit> ut = secondaryUnitType.getUnitType(); showSecondaryYAxis = (ut != UserSpecifiedUnit.class); secondaryDataSource.setUnitType(ut); this.setSecondaryYAxisUnit(ut); return; } if (in == dataSource) { // Hack for backwards compatibility // When an entity and output are entered, the unit type will be set automatically // FIXME remove when backwards compatibility is no longer required if (dataSource.getValue() != null && dataSource.getValue().size() > 0) { Class<? extends Unit> ut = dataSource.getValue().get(0).getUnitType(); if (ut != null && ut != UserSpecifiedUnit.class) this.setYAxisUnit(ut); } return; } if (in == secondaryDataSource) { // Hack for backwards compatibility // When an entity and output are entered, the unit type will be set automatically // FIXME remove when backwards compatibility is no longer required if (secondaryDataSource.getValue() != null && secondaryDataSource.getValue().size() > 0) { Class<? extends Unit> ut = secondaryDataSource.getValue().get(0).getUnitType(); if (ut != null && ut != UserSpecifiedUnit.class) { this.setSecondaryYAxisUnit(ut); showSecondaryYAxis = (ut != UserSpecifiedUnit.class); } } return; } if (in == lineColorsList) { for (int i = 0; i < primarySeries.size(); ++ i) { SeriesInfo info = primarySeries.get(i); info.lineColour = getLineColor(i, lineColorsList.getValue()); } return; } if (in == lineWidths) { for (int i = 0; i < primarySeries.size(); ++ i) { SeriesInfo info = primarySeries.get(i); info.lineWidth = getLineWidth(i, lineWidths.getValue()); } return; } if (in == secondaryLineColorsList) { for (int i = 0; i < secondarySeries.size(); ++ i) { SeriesInfo info = secondarySeries.get(i); info.lineColour = getLineColor(i, secondaryLineColorsList.getValue()); } return; } if (in == secondaryLineWidths) { for (int i = 0; i < secondarySeries.size(); ++ i) { SeriesInfo info = secondarySeries.get(i); info.lineWidth = getLineWidth(i, secondaryLineWidths.getValue()); } return; } } @Override public void earlyInit(){ super.earlyInit(); primarySeries.clear(); secondarySeries.clear(); // Populate the primary series data structures populateSeriesInfo(primarySeries, dataSource); populateSeriesInfo(secondarySeries, secondaryDataSource); } private void populateSeriesInfo(ArrayList<SeriesInfo> infos, SampleListInput data) { ArrayList<SampleProvider> sampList = data.getValue(); if( sampList == null ) return; for (int i = 0; i < sampList.size(); ++i) { SeriesInfo info = new SeriesInfo(); info.samp = sampList.get(i); info.yValues = new double[numberOfPoints.getValue()]; info.xValues = new double[numberOfPoints.getValue()]; infos.add(info); } } @Override public void startUp() { super.startUp(); extraStartGraph(); for (int i = 0; i < primarySeries.size(); ++ i) { SeriesInfo info = primarySeries.get(i); info.lineColour = getLineColor(i, lineColorsList.getValue()); info.lineWidth = getLineWidth(i, lineWidths.getValue()); } for (int i = 0; i < secondarySeries.size(); ++i) { SeriesInfo info = secondarySeries.get(i); info.lineColour = getLineColor(i, secondaryLineColorsList.getValue()); info.lineWidth = getLineWidth(i, secondaryLineWidths.getValue()); } double xLength = xAxisEnd.getValue() - xAxisStart.getValue(); double xInterval = xLength/(numberOfPoints.getValue() -1); for (SeriesInfo info : primarySeries) { setupSeriesData(info, xLength, xInterval); } for (SeriesInfo info : secondarySeries) { setupSeriesData(info, xLength, xInterval); } processGraph(); } /** * Hook for sub-classes to do some processing at startup */ protected void extraStartGraph() {} protected Color4d getLineColor(int index, ArrayList<Color4d> colorList) { index = Math.min(index, colorList.size()-1); return colorList.get(index); } protected double getLineWidth(int index, DoubleVector widthList) { index = Math.min(index, widthList.size()-1); return widthList.get(index); } /** * Initialize the data for the specified series */ private void setupSeriesData(SeriesInfo info, double xLength, double xInterval) { info.numPoints = 0; info.indexOfLastEntry = -1; for( int i = 0; i * xInterval < xAxisEnd.getValue(); i++ ) { double t = i * xInterval; info.numPoints++; info.xValues[info.numPoints] = t; info.yValues[info.numPoints] = this.getCurrentValue(t, info); } } /** * A hook method for descendant graph types to grab some processing time */ protected void extraProcessing() {} private static class ProcessGraphTarget extends ProcessTarget { final Graph graph; ProcessGraphTarget(Graph graph) { this.graph = graph; } @Override public String getDescription() { return graph.getName() + ".processGraph"; } @Override public void process() { graph.processGraph(); } } private final ProcessTarget processGraph = new ProcessGraphTarget(this); /** * Calculate values for the data series on the graph */ public void processGraph() { // Give processing time to sub-classes extraProcessing(); // stop the processing loop if (primarySeries.isEmpty() && secondarySeries.isEmpty()) return; // Calculate values for the primary y-axis for (SeriesInfo info : primarySeries) { processGraph(info); } // Calculate values for the secondary y-axis for (SeriesInfo info : secondarySeries) { processGraph(info); } double xLength = xAxisEnd.getValue() - xAxisStart.getValue(); double xInterval = xLength / (numberOfPoints.getValue() - 1); scheduleProcess(xInterval, 7, processGraph); } /** * Calculate values for the data series on the graph * @param info - the information for the series to be rendered */ public void processGraph(SeriesInfo info) { // Entity has been removed if (info.samp == null) { return; } double t = getSimTime() + xAxisEnd.getValue(); double presentValue = this.getCurrentValue(t, info); info.indexOfLastEntry++; if (info.indexOfLastEntry == info.yValues.length) { info.indexOfLastEntry = 0; } info.xValues[info.indexOfLastEntry] = t; info.yValues[info.indexOfLastEntry] = presentValue; if (info.numPoints < info.yValues.length) { info.numPoints++; } } /** * Return the current value for the series * @return double */ protected double getCurrentValue(double simTime, SeriesInfo info) { return info.samp.getNextSample(simTime); } public ArrayList<SeriesInfo> getPrimarySeries() { return primarySeries; } public ArrayList<SeriesInfo> getSecondarySeries() { return secondarySeries; } public int getNumberOfPoints() { return numberOfPoints.getValue(); } }