/******************************************************************************* | |
* Copyright 2011 See AUTHORS file. | |
* | |
* 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.badlogic.gdx.tools.particleeditor; | |
import java.awt.BasicStroke; | |
import java.awt.Color; | |
import java.awt.Font; | |
import java.awt.FontMetrics; | |
import java.awt.Graphics; | |
import java.awt.Graphics2D; | |
import java.awt.GridBagLayout; | |
import java.awt.event.MouseAdapter; | |
import java.awt.event.MouseEvent; | |
import java.awt.event.MouseMotionListener; | |
import java.util.ArrayList; | |
import javax.swing.JPanel; | |
public class Chart extends JPanel { | |
static private final int POINT_SIZE = 6; | |
static private final int POINT_SIZE_EXPANDED = 10; | |
ArrayList<Point> points = new ArrayList(); | |
private int numberHeight; | |
int chartX, chartY; | |
int chartWidth, chartHeight; | |
int maxX, maxY; | |
int overIndex = -1; | |
int movingIndex = -1; | |
boolean isExpanded; | |
String title; | |
boolean moveAll = false; | |
boolean moveAllProportionally = false; | |
int moveAllPrevY; | |
public Chart (String title) { | |
this.title = title; | |
setLayout(new GridBagLayout()); | |
addMouseListener(new MouseAdapter() { | |
public void mousePressed (MouseEvent event) { | |
movingIndex = overIndex; | |
moveAll = event.isControlDown(); | |
if (moveAll) { | |
moveAllProportionally = event.isShiftDown(); | |
moveAllPrevY = event.getY(); | |
} | |
} | |
public void mouseReleased (MouseEvent event) { | |
movingIndex = -1; | |
moveAll = false; | |
} | |
public void mouseClicked (MouseEvent event) { | |
if (event.getClickCount() == 2) { | |
if (overIndex <= 0 || overIndex >= points.size()) return; | |
points.remove(overIndex); | |
pointsChanged(); | |
repaint(); | |
return; | |
} | |
if (movingIndex != -1) return; | |
if (overIndex != -1) return; | |
int mouseX = event.getX(); | |
int mouseY = event.getY(); | |
if (mouseX < chartX || mouseX > chartX + chartWidth) return; | |
if (mouseY < chartY || mouseY > chartY + chartHeight) return; | |
Point newPoint = pixelToPoint(mouseX, mouseY); | |
int i = 0; | |
Point lastPoint = null; | |
for (Point point : points) { | |
if (point.x > newPoint.x) { | |
if (Math.abs(point.x - newPoint.x) < 0.001f) return; | |
if (lastPoint != null && Math.abs(lastPoint.x - newPoint.x) < 0.001f) return; | |
points.add(i, newPoint); | |
overIndex = i; | |
pointsChanged(); | |
repaint(); | |
return; | |
} | |
lastPoint = point; | |
i++; | |
} | |
overIndex = points.size(); | |
points.add(newPoint); | |
pointsChanged(); | |
repaint(); | |
} | |
}); | |
addMouseMotionListener(new MouseMotionListener() { | |
public void mouseDragged (MouseEvent event) { | |
if (movingIndex == -1 || movingIndex >= points.size()) return; | |
if (moveAll){ | |
int newY = event.getY(); | |
float deltaY = (moveAllPrevY - newY) / (float)chartHeight * maxY; | |
for (Point point : points){ | |
point.y = Math.min(maxY, Math.max(0, point.y + (moveAllProportionally ? deltaY * point.y : deltaY))); | |
} | |
moveAllPrevY = newY; | |
} else { | |
float nextX = movingIndex == points.size() - 1 ? maxX : points.get(movingIndex + 1).x - 0.001f; | |
if (movingIndex == 0) nextX = 0; | |
float prevX = movingIndex == 0 ? 0 : points.get(movingIndex - 1).x + 0.001f; | |
Point point = points.get(movingIndex); | |
point.x = Math.min(nextX, Math.max(prevX, (event.getX() - chartX) / (float)chartWidth * maxX)); | |
point.y = Math.min(maxY, Math.max(0, chartHeight - (event.getY() - chartY)) / (float)chartHeight * maxY); | |
} | |
pointsChanged(); | |
repaint(); | |
} | |
public void mouseMoved (MouseEvent event) { | |
int mouseX = event.getX(); | |
int mouseY = event.getY(); | |
int oldIndex = overIndex; | |
overIndex = -1; | |
int pointSize = isExpanded ? POINT_SIZE_EXPANDED : POINT_SIZE; | |
int i = 0; | |
for (Point point : points) { | |
int x = chartX + (int)(chartWidth * (point.x / (float)maxX)); | |
int y = chartY + chartHeight - (int)(chartHeight * (point.y / (float)maxY)); | |
if (Math.abs(x - mouseX) <= pointSize && Math.abs(y - mouseY) <= pointSize) { | |
overIndex = i; | |
break; | |
} | |
i++; | |
} | |
if (overIndex != oldIndex) repaint(); | |
} | |
}); | |
} | |
public void addPoint (float x, float y) { | |
points.add(new Point(x, y)); | |
} | |
public void pointsChanged () { | |
} | |
public float[] getValuesX () { | |
float[] values = new float[points.size()]; | |
int i = 0; | |
for (Point point : points) | |
values[i++] = point.x; | |
return values; | |
} | |
public float[] getValuesY () { | |
float[] values = new float[points.size()]; | |
int i = 0; | |
for (Point point : points) | |
values[i++] = point.y; | |
return values; | |
} | |
public void setValues (float[] x, float[] y) { | |
points.clear(); | |
for (int i = 0; i < x.length; i++) | |
points.add(new Point(x[i], y[i])); | |
} | |
Point pixelToPoint (float x, float y) { | |
Point point = new Point(); | |
point.x = Math.min(maxX, Math.max(0, x - chartX) / (float)chartWidth * maxX); | |
point.y = Math.min(maxY, Math.max(0, chartHeight - (y - chartY)) / (float)chartHeight * maxY); | |
return point; | |
} | |
Point pointToPixel (Point point) { | |
Point pixel = new Point(); | |
pixel.x = chartX + (int)(chartWidth * (point.x / (float)maxX)); | |
pixel.y = chartY + chartHeight - (int)(chartHeight * (point.y / (float)maxY)); | |
return pixel; | |
} | |
protected void paintComponent (Graphics graphics) { | |
// setOpaque(true); | |
// setBackground(Color.red); | |
super.paintComponent(graphics); | |
Graphics2D g = (Graphics2D)graphics; | |
FontMetrics metrics = g.getFontMetrics(); | |
if (numberHeight == 0) { | |
numberHeight = getFont().layoutGlyphVector(g.getFontRenderContext(), new char[] {'0'}, 0, 1, Font.LAYOUT_LEFT_TO_RIGHT) | |
.getGlyphPixelBounds(0, g.getFontRenderContext(), 0, 0).height; | |
} | |
int width = getWidth(); | |
if (!isExpanded) width = Math.min(150, width); | |
width = Math.max(100, width); | |
int height = getHeight(); | |
int maxAxisLabelWidth; | |
int yAxisWidth; | |
if (isExpanded) { | |
maxAxisLabelWidth = metrics.stringWidth("100%"); | |
yAxisWidth = maxAxisLabelWidth + 8; | |
chartX = yAxisWidth; | |
chartY = numberHeight / 2 + 1; | |
chartWidth = width - yAxisWidth - 2; | |
chartHeight = height - chartY - numberHeight - 8; | |
} else { | |
maxAxisLabelWidth = 0; | |
yAxisWidth = 2; | |
chartX = yAxisWidth; | |
chartY = 2; | |
chartWidth = width - yAxisWidth - 2; | |
chartHeight = height - chartY - 3; | |
} | |
g.setColor(Color.white); | |
g.fillRect(chartX, chartY, chartWidth, chartHeight); | |
g.setColor(Color.black); | |
g.drawRect(chartX, chartY, chartWidth, chartHeight); | |
maxX = 1; | |
{ | |
int y = height; | |
if (isExpanded) | |
y -= numberHeight; | |
else | |
y += 5; | |
int xSplit = (int)Math.min(10, chartWidth / (maxAxisLabelWidth * 1.5f)); | |
for (int i = 0; i <= xSplit; i++) { | |
float percent = i / (float)xSplit; | |
String label = axisLabel(maxX * percent); | |
int labelWidth = metrics.stringWidth(label); | |
int x = (int)(yAxisWidth + chartWidth * percent); | |
if (i != 0 && i != xSplit) { | |
g.setColor(Color.lightGray); | |
g.drawLine(x, chartY + 1, x, chartY + chartHeight); | |
g.setColor(Color.black); | |
} | |
g.drawLine(x, y - 4, x, y - 8); | |
if (isExpanded) { | |
x -= labelWidth / 2; | |
if (i == xSplit) x = Math.min(x, width - labelWidth); | |
g.drawString(label, x, y + numberHeight); | |
} | |
} | |
} | |
maxY = 1; | |
{ | |
int ySplit = isExpanded ? Math.min(10, chartHeight / (numberHeight * 3)) : 4; | |
for (int i = 0; i <= ySplit; i++) { | |
float percent = i / (float)ySplit; | |
String label = axisLabel(maxY * percent); | |
int labelWidth = metrics.stringWidth(label); | |
int y = (int)(chartY + chartHeight - chartHeight * percent); | |
if (isExpanded) g.drawString(label, yAxisWidth - 6 - labelWidth, y + numberHeight / 2); | |
if (i != 0 && i != ySplit) { | |
g.setColor(Color.lightGray); | |
g.drawLine(chartX, y, chartX + chartWidth - 1, y); | |
g.setColor(Color.black); | |
} | |
g.drawLine(yAxisWidth - 4, y, yAxisWidth, y); | |
} | |
} | |
{ | |
int titleWidth = metrics.stringWidth(title); | |
int x = yAxisWidth + chartWidth / 2 - titleWidth / 2; | |
int y = chartY + chartHeight / 2 - numberHeight / 2; | |
g.setColor(Color.white); | |
g.fillRect(x - 2, y - 2, titleWidth + 4, numberHeight + 4); | |
g.setColor(Color.lightGray); | |
g.drawString(title, x, y + numberHeight); | |
} | |
g.setColor(Color.blue); | |
g.setStroke(new BasicStroke(isExpanded ? 3 : 2)); | |
int lastX = -1, lastY = -1; | |
for (Point point : points) { | |
Point pixel = pointToPixel(point); | |
if (lastX != -1) g.drawLine(lastX, lastY, (int)pixel.x, (int)pixel.y); | |
lastX = (int)pixel.x; | |
lastY = (int)pixel.y; | |
} | |
g.drawLine(lastX, lastY, chartX + chartWidth - 1, lastY); | |
for (int i = 0, n = points.size(); i < n; i++) { | |
Point point = points.get(i); | |
Point pixel = pointToPixel(point); | |
if (overIndex == i) | |
g.setColor(Color.red); | |
else | |
g.setColor(Color.black); | |
String label = valueLabel(point.y); | |
int labelWidth = metrics.stringWidth(label); | |
int pointSize = isExpanded ? POINT_SIZE_EXPANDED : POINT_SIZE; | |
int x = (int)pixel.x - pointSize / 2; | |
int y = (int)pixel.y - pointSize / 2; | |
g.fillOval(x, y, pointSize, pointSize); | |
if (isExpanded) { | |
g.setColor(Color.black); | |
x = Math.max(chartX + 2, Math.min(chartX + chartWidth - labelWidth, x)); | |
y -= 3; | |
if (y < chartY + numberHeight + 3) | |
y += 27; | |
else if (n > 1) { | |
Point comparePoint = i == n - 1 ? points.get(i - 1) : points.get(i + 1); | |
if (y < chartY + chartHeight - 27 && comparePoint.y > point.y) y += 27; | |
} | |
g.drawString(label, x, y); | |
} | |
} | |
} | |
private String valueLabel (float value) { | |
value = (int)(value * 1000) / 10f; | |
if (value % 1 == 0) | |
return String.valueOf((int)value) + '%'; | |
else | |
return String.valueOf(value) + '%'; | |
} | |
private String axisLabel (float value) { | |
value = (int)(value * 100); | |
if (value % 1 == 0) | |
return String.valueOf((int)value) + '%'; | |
else | |
return String.valueOf(value) + '%'; | |
} | |
static public class Point { | |
public float x; | |
public float y; | |
public Point () { | |
} | |
public Point (float x, float y) { | |
this.x = x; | |
this.y = y; | |
} | |
} | |
public boolean isExpanded () { | |
return isExpanded; | |
} | |
public void setExpanded (boolean isExpanded) { | |
this.isExpanded = isExpanded; | |
} | |
public void setTitle(String title){ | |
this.title = title; | |
} | |
} |