Skip to content

Instantly share code, notes, and snippets.

@born2snipe
Last active February 8, 2019 04:29
Show Gist options
  • Save born2snipe/72c9c425622755017241a240b4205122 to your computer and use it in GitHub Desktop.
Save born2snipe/72c9c425622755017241a240b4205122 to your computer and use it in GitHub Desktop.
Example usage of LineChart
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.ui.Widget;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.ObjectMap;
import static com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType.Filled;
import static com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType.Line;
/**
* This widget draws lines of on a chart over time. The original intent for chart was for
* drawing the FPS as timed passed to make it easier to visualize spikes.
*/
public class LineChart extends Widget {
private static final float MINIMUM_STEP_SIZE = 1.0f;
private final float durationBetweenQueryingProvider;
private int maxNumberOfValues;
private float elapsedTime;
private ShapeRenderer shapeRenderer;
private float maxValue = 100.0f;
private float minValue = 0f;
private Color backgroundColor;
private float graphStep;
private Color graphStepColor;
private ObjectMap<ValueProvider, Array<GraphPoint>> valueProviderToValues = new ObjectMap<>();
private ObjectMap<ValueProvider, GraphPoint> valueProviderToInitialPoint = new ObjectMap<>();
/**
* By default it will query the value providers every 10th of a second and store
* data for a 10 second window.
*
* @param shapeRenderer - used to draw the lines
* @param valueProviders - the values you would like to be plotted on the graph
*/
public LineChart(ShapeRenderer shapeRenderer, ValueProvider... valueProviders) {
this(shapeRenderer, 0.1f, 10f, valueProviders);
}
/**
* @param shapeRenderer - used to draw the lines
* @param secondsBetweenQueryingProvider - the number seconds in between the querying of the ValueProviders collection
* @param maxTimeWindowSizeInSeconds - how big of a time window of data to hold in memory
* @param valueProviders - the values you would like to be plotted on the graph
*/
public LineChart(ShapeRenderer shapeRenderer, float secondsBetweenQueryingProvider, float maxTimeWindowSizeInSeconds, ValueProvider... valueProviders) {
this.durationBetweenQueryingProvider = secondsBetweenQueryingProvider;
this.maxNumberOfValues = Math.round(maxTimeWindowSizeInSeconds / secondsBetweenQueryingProvider);
this.shapeRenderer = shapeRenderer;
for (ValueProvider valueProvider : valueProviders) {
valueProviderToValues.put(valueProvider, new Array<GraphPoint>(maxNumberOfValues));
}
setTouchable(Touchable.enabled);
}
@Override
public void act(float delta) {
super.act(delta);
elapsedTime += delta;
if (elapsedTime >= durationBetweenQueryingProvider) {
captureValue();
elapsedTime = 0;
}
}
@Override
public void draw(Batch batch, float parentAlpha) {
SpriteBatch mainWidgetBatch = (SpriteBatch) batch;
super.draw(mainWidgetBatch, parentAlpha);
mainWidgetBatch.end();
shapeRenderer.setTransformMatrix(mainWidgetBatch.getTransformMatrix());
shapeRenderer.setProjectionMatrix(mainWidgetBatch.getProjectionMatrix());
if (backgroundColor != null) {
drawBackgroundColor(parentAlpha);
}
if (graphStepColor != null) {
drawGraphMeasurementLines(parentAlpha);
}
for (ObjectMap.Entry<ValueProvider, Array<GraphPoint>> entry : valueProviderToValues) {
drawLineOnGraph(parentAlpha, entry);
}
mainWidgetBatch.begin();
}
@Override
public float getPrefWidth() {
return 200f;
}
@Override
public float getPrefHeight() {
return 100f;
}
/**
* Set the min/max values that will displayed on the chart
* <p>
* By default range is 0..100
*/
public void setValueRange(float minValue, float maxValue) {
this.minValue = minValue;
this.maxValue = maxValue;
}
public void setBackgroundColor(Color backgroundColor) {
this.backgroundColor = backgroundColor;
}
/**
* Horizontal lines drawn on the graph to allow visual measuring
*
* @param graphStep - how often to draw a line
* @param graphStepColor - the color of the line
*/
public void setGraphStep(float graphStep, Color graphStepColor) {
this.graphStep = graphStep;
this.graphStepColor = graphStepColor;
}
private void drawLineOnGraph(float parentAlpha, ObjectMap.Entry<ValueProvider, Array<GraphPoint>> entry) {
GraphPoint initialPoint = valueProviderToInitialPoint.get(entry.key);
if (entry.value.size < 1 || initialPoint == null) {
return;
} else if (initialPoint.position == null) {
initialPoint.position = new Vector2(getX(), convertToY(initialPoint.value));
}
final float xStep = Math.max(getWidth() / maxNumberOfValues, MINIMUM_STEP_SIZE);
final float maxY = convertToY(maxValue);
final float minY = convertToY(minValue);
shapeRenderer.begin(Line);
shapeRenderer.setColor(applyAlphaTo(entry.key.getColor(), parentAlpha));
Vector2 previousPoint = initialPoint.position;
Array<GraphPoint> values = entry.value;
for (int i = 0; i < values.size; i++) {
GraphPoint graphPoint = values.get(i);
float value = graphPoint.value;
Vector2 point = graphPoint.position;
if (point == null) {
float x = previousPoint.x + xStep;
point = new Vector2(x, convertToY(value));
graphPoint.position = point;
if (isOutsideHorizontalView(x)) {
maxNumberOfValues = entry.value.size;
}
}
if (previousPoint == null) {
continue;
} else if (previousPoint.equals(point)) {
continue;
} else if (isOutsideVerticalGraphingArea(minY, maxY, previousPoint, point)) {
continue;
}
float slope = (point.y - previousPoint.y) / (point.x - previousPoint.x);
float yIntercept = point.y - (slope * point.x);
float firstX = previousPoint.x;
float firstY = previousPoint.y;
float secondX = point.x;
float secondY = point.y;
if (previousPoint.y < maxY && point.y > maxY) {
// draw partial line going up (middle to max)
secondY = maxY;
secondX = (maxY - yIntercept) / slope;
} else if (previousPoint.y > maxY && point.y < maxY) {
// draw partial line going down (max to middle)
firstY = maxY;
firstX = (maxY - yIntercept) / slope;
} else if (previousPoint.y < minY && point.y > minY) {
// draw partial line going up (min to middle)
firstY = minY;
firstX = (minY - yIntercept) / slope;
} else if (previousPoint.y > minY && point.y < minY) {
// draw partial line going down (middle to min)
secondY = minY;
secondX = (minY - yIntercept) / slope;
}
shapeRenderer.line(firstX, firstY, secondX, secondY);
previousPoint = point;
}
shapeRenderer.end();
}
private boolean isOutsideVerticalGraphingArea(float minY, float maxY, Vector2 previousPoint, Vector2 point) {
return (previousPoint.y >= maxY && point.y >= maxY)
|| (previousPoint.y <= minY && point.y <= minY);
}
private boolean isOutsideHorizontalView(float x) {
return x >= (getX() + getWidth());
}
private void drawGraphMeasurementLines(float parentAlpha) {
shapeRenderer.begin(Line);
shapeRenderer.setColor(applyAlphaTo(graphStepColor, parentAlpha));
for (float value = minValue + graphStep; value < maxValue; value += graphStep) {
float y = convertToY(value);
shapeRenderer.line(getX(), y, getX() + getWidth(), y);
}
// draw border around chart
shapeRenderer.rect(getX(), getY(), getWidth(), getHeight());
shapeRenderer.end();
}
private void drawBackgroundColor(float parentAlpha) {
shapeRenderer.begin(Filled);
shapeRenderer.setColor(applyAlphaTo(backgroundColor, parentAlpha));
shapeRenderer.rect(getX(), getY(), getWidth(), getHeight());
shapeRenderer.end();
}
private void captureValue() {
final float xStep = Math.max(getWidth() / maxNumberOfValues, MINIMUM_STEP_SIZE);
for (ObjectMap.Entry<ValueProvider, Array<GraphPoint>> entry : valueProviderToValues) {
float value = entry.key.get();
GraphPoint point = new GraphPoint(value);
entry.value.add(point);
if (!valueProviderToInitialPoint.containsKey(entry.key)) {
valueProviderToInitialPoint.put(entry.key, point);
} else if (entry.value.size > maxNumberOfValues) {
removeOldData(entry.value);
moveToLeft(entry.value, xStep);
valueProviderToInitialPoint.put(entry.key, entry.value.first());
}
}
}
private void moveToLeft(Array<GraphPoint> points, float xAmountToMove) {
for (GraphPoint point : points) {
if (point.position != null) {
point.position.x -= xAmountToMove;
// ensure we are not outside of the widget's bounds
if (point.position.x < getX()) {
point.position.x = getX();
}
}
}
}
private float convertToY(float value) {
float yValueRange = maxValue - minValue;
if (yValueRange > 0.0f) {
float percentOfRange = value / yValueRange;
float chartVerticalArea = getHeight();
return (chartVerticalArea * percentOfRange) + getY();
}
return 0;
}
private void removeOldData(Array values) {
int diff = values.size - maxNumberOfValues;
for (int i = 0; i < diff; i++) {
values.removeIndex(0);
}
}
private Color applyAlphaTo(Color originalColor, float alpha) {
Color color = new Color(originalColor);
color.a *= alpha;
return color;
}
/**
* The class the queries for the data you would like charted
*/
public interface ValueProvider {
/**
* The value to be added to chart a this given point in time
*/
float get();
/**
* The color used to draw the line
*/
Color getColor();
}
private class GraphPoint {
final float value;
Vector2 position;
public GraphPoint(float value) {
this.value = value;
}
}
}
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.sun.management.OperatingSystemMXBean;
import libgdx.boot.widget.LineChart;
import org.lwjgl.opengl.Display;
import java.lang.management.ManagementFactory;
import java.util.Random;
import static com.badlogic.gdx.graphics.Color.DARK_GRAY;
import static com.badlogic.gdx.graphics.Color.LIGHT_GRAY;
public class LineChartTest {
private static final Color FPS_COLOR = Color.GREEN;
private static final Color CPU_COLOR = Color.RED;
private static final String title = "Line Chart";
public static void main(String[] args) {
LwjglApplicationConfiguration config = new LwjglApplicationConfiguration();
config.title = title;
config.useGL30 = false;
config.width = 320;
config.height = 240;
config.foregroundFPS = 120;
config.backgroundFPS = 2;
new LwjglApplication(newAppListener(), config);
}
private static ApplicationListener newAppListener() {
return new ApplicationAdapter() {
private Stage stage;
private ShapeRenderer shapeRenderer;
private SpriteBatch batch;
@Override
public void create() {
batch = new SpriteBatch();
shapeRenderer = new ShapeRenderer();
stage = new Stage(new ScreenViewport(), batch);
final com.sun.management.OperatingSystemMXBean operatingSystemMXBean = (com.sun.management.OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
LineChart chart = new LineChart(shapeRenderer, 0.1f, 60.0f,
fpsProvider(),
cpuProvider(operatingSystemMXBean)
);
chart.setBackgroundColor(DARK_GRAY);
chart.setValueRange(0, 65);
chart.setGraphStep(10, LIGHT_GRAY);
Table table = new Table();
table.setFillParent(true);
table.pad(2);
table.add(chart).expand(true, true).row();
stage.addActor(table);
}
@Override
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 0);
Gdx.gl.glClear(Gdx.gl.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
Display.setTitle(title + " - FPS: " + Gdx.graphics.getFramesPerSecond());
}
@Override
public void dispose() {
stage.dispose();
batch.dispose();
shapeRenderer.dispose();
}
};
}
private static LineChart.ValueProvider cpuProvider(final OperatingSystemMXBean operatingSystemMXBean) {
return new LineChart.ValueProvider() {
public float get() {
return (float) operatingSystemMXBean.getProcessCpuLoad();
}
public Color getColor() {
return FPS_COLOR;
}
};
}
private static LineChart.ValueProvider fpsProvider() {
return new LineChart.ValueProvider() {
public float get() {
return Gdx.graphics.getFramesPerSecond();
}
public Color getColor() {
return CPU_COLOR;
}
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment