1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.tools.particleeditor; 18 19 import java.awt.BasicStroke; 20 import java.awt.Color; 21 import java.awt.Font; 22 import java.awt.FontMetrics; 23 import java.awt.Graphics; 24 import java.awt.Graphics2D; 25 import java.awt.GridBagLayout; 26 import java.awt.event.MouseAdapter; 27 import java.awt.event.MouseEvent; 28 import java.awt.event.MouseMotionListener; 29 import java.util.ArrayList; 30 31 import javax.swing.JPanel; 32 33 public class Chart extends JPanel { 34 static private final int POINT_SIZE = 6; 35 static private final int POINT_SIZE_EXPANDED = 10; 36 37 ArrayList<Point> points = new ArrayList(); 38 private int numberHeight; 39 int chartX, chartY; 40 int chartWidth, chartHeight; 41 int maxX, maxY; 42 int overIndex = -1; 43 int movingIndex = -1; 44 boolean isExpanded; 45 String title; 46 47 boolean moveAll = false; 48 boolean moveAllProportionally = false; 49 int moveAllPrevY; 50 Chart(String title)51 public Chart (String title) { 52 this.title = title; 53 54 setLayout(new GridBagLayout()); 55 56 addMouseListener(new MouseAdapter() { 57 public void mousePressed (MouseEvent event) { 58 movingIndex = overIndex; 59 moveAll = event.isControlDown(); 60 if (moveAll) { 61 moveAllProportionally = event.isShiftDown(); 62 moveAllPrevY = event.getY(); 63 } 64 } 65 66 public void mouseReleased (MouseEvent event) { 67 movingIndex = -1; 68 moveAll = false; 69 } 70 71 public void mouseClicked (MouseEvent event) { 72 if (event.getClickCount() == 2) { 73 if (overIndex <= 0 || overIndex >= points.size()) return; 74 points.remove(overIndex); 75 pointsChanged(); 76 repaint(); 77 return; 78 } 79 if (movingIndex != -1) return; 80 if (overIndex != -1) return; 81 int mouseX = event.getX(); 82 int mouseY = event.getY(); 83 if (mouseX < chartX || mouseX > chartX + chartWidth) return; 84 if (mouseY < chartY || mouseY > chartY + chartHeight) return; 85 Point newPoint = pixelToPoint(mouseX, mouseY); 86 int i = 0; 87 Point lastPoint = null; 88 for (Point point : points) { 89 if (point.x > newPoint.x) { 90 if (Math.abs(point.x - newPoint.x) < 0.001f) return; 91 if (lastPoint != null && Math.abs(lastPoint.x - newPoint.x) < 0.001f) return; 92 points.add(i, newPoint); 93 overIndex = i; 94 pointsChanged(); 95 repaint(); 96 return; 97 } 98 lastPoint = point; 99 i++; 100 } 101 overIndex = points.size(); 102 points.add(newPoint); 103 pointsChanged(); 104 repaint(); 105 } 106 }); 107 addMouseMotionListener(new MouseMotionListener() { 108 public void mouseDragged (MouseEvent event) { 109 if (movingIndex == -1 || movingIndex >= points.size()) return; 110 if (moveAll){ 111 int newY = event.getY(); 112 float deltaY = (moveAllPrevY - newY) / (float)chartHeight * maxY; 113 for (Point point : points){ 114 point.y = Math.min(maxY, Math.max(0, point.y + (moveAllProportionally ? deltaY * point.y : deltaY))); 115 } 116 moveAllPrevY = newY; 117 } else { 118 float nextX = movingIndex == points.size() - 1 ? maxX : points.get(movingIndex + 1).x - 0.001f; 119 if (movingIndex == 0) nextX = 0; 120 float prevX = movingIndex == 0 ? 0 : points.get(movingIndex - 1).x + 0.001f; 121 Point point = points.get(movingIndex); 122 point.x = Math.min(nextX, Math.max(prevX, (event.getX() - chartX) / (float)chartWidth * maxX)); 123 point.y = Math.min(maxY, Math.max(0, chartHeight - (event.getY() - chartY)) / (float)chartHeight * maxY); 124 } 125 pointsChanged(); 126 repaint(); 127 } 128 129 public void mouseMoved (MouseEvent event) { 130 int mouseX = event.getX(); 131 int mouseY = event.getY(); 132 int oldIndex = overIndex; 133 overIndex = -1; 134 int pointSize = isExpanded ? POINT_SIZE_EXPANDED : POINT_SIZE; 135 int i = 0; 136 for (Point point : points) { 137 int x = chartX + (int)(chartWidth * (point.x / (float)maxX)); 138 int y = chartY + chartHeight - (int)(chartHeight * (point.y / (float)maxY)); 139 if (Math.abs(x - mouseX) <= pointSize && Math.abs(y - mouseY) <= pointSize) { 140 overIndex = i; 141 break; 142 } 143 i++; 144 } 145 if (overIndex != oldIndex) repaint(); 146 } 147 }); 148 } 149 addPoint(float x, float y)150 public void addPoint (float x, float y) { 151 points.add(new Point(x, y)); 152 } 153 pointsChanged()154 public void pointsChanged () { 155 } 156 getValuesX()157 public float[] getValuesX () { 158 float[] values = new float[points.size()]; 159 int i = 0; 160 for (Point point : points) 161 values[i++] = point.x; 162 return values; 163 } 164 getValuesY()165 public float[] getValuesY () { 166 float[] values = new float[points.size()]; 167 int i = 0; 168 for (Point point : points) 169 values[i++] = point.y; 170 return values; 171 } 172 setValues(float[] x, float[] y)173 public void setValues (float[] x, float[] y) { 174 points.clear(); 175 for (int i = 0; i < x.length; i++) 176 points.add(new Point(x[i], y[i])); 177 } 178 pixelToPoint(float x, float y)179 Point pixelToPoint (float x, float y) { 180 Point point = new Point(); 181 point.x = Math.min(maxX, Math.max(0, x - chartX) / (float)chartWidth * maxX); 182 point.y = Math.min(maxY, Math.max(0, chartHeight - (y - chartY)) / (float)chartHeight * maxY); 183 return point; 184 } 185 pointToPixel(Point point)186 Point pointToPixel (Point point) { 187 Point pixel = new Point(); 188 pixel.x = chartX + (int)(chartWidth * (point.x / (float)maxX)); 189 pixel.y = chartY + chartHeight - (int)(chartHeight * (point.y / (float)maxY)); 190 return pixel; 191 } 192 paintComponent(Graphics graphics)193 protected void paintComponent (Graphics graphics) { 194 // setOpaque(true); 195 // setBackground(Color.red); 196 super.paintComponent(graphics); 197 198 Graphics2D g = (Graphics2D)graphics; 199 FontMetrics metrics = g.getFontMetrics(); 200 if (numberHeight == 0) { 201 numberHeight = getFont().layoutGlyphVector(g.getFontRenderContext(), new char[] {'0'}, 0, 1, Font.LAYOUT_LEFT_TO_RIGHT) 202 .getGlyphPixelBounds(0, g.getFontRenderContext(), 0, 0).height; 203 } 204 205 int width = getWidth(); 206 if (!isExpanded) width = Math.min(150, width); 207 width = Math.max(100, width); 208 int height = getHeight(); 209 int maxAxisLabelWidth; 210 int yAxisWidth; 211 if (isExpanded) { 212 maxAxisLabelWidth = metrics.stringWidth("100%"); 213 yAxisWidth = maxAxisLabelWidth + 8; 214 chartX = yAxisWidth; 215 chartY = numberHeight / 2 + 1; 216 chartWidth = width - yAxisWidth - 2; 217 chartHeight = height - chartY - numberHeight - 8; 218 } else { 219 maxAxisLabelWidth = 0; 220 yAxisWidth = 2; 221 chartX = yAxisWidth; 222 chartY = 2; 223 chartWidth = width - yAxisWidth - 2; 224 chartHeight = height - chartY - 3; 225 } 226 227 g.setColor(Color.white); 228 g.fillRect(chartX, chartY, chartWidth, chartHeight); 229 g.setColor(Color.black); 230 g.drawRect(chartX, chartY, chartWidth, chartHeight); 231 232 maxX = 1; 233 { 234 int y = height; 235 if (isExpanded) 236 y -= numberHeight; 237 else 238 y += 5; 239 int xSplit = (int)Math.min(10, chartWidth / (maxAxisLabelWidth * 1.5f)); 240 for (int i = 0; i <= xSplit; i++) { 241 float percent = i / (float)xSplit; 242 String label = axisLabel(maxX * percent); 243 int labelWidth = metrics.stringWidth(label); 244 int x = (int)(yAxisWidth + chartWidth * percent); 245 if (i != 0 && i != xSplit) { 246 g.setColor(Color.lightGray); 247 g.drawLine(x, chartY + 1, x, chartY + chartHeight); 248 g.setColor(Color.black); 249 } 250 g.drawLine(x, y - 4, x, y - 8); 251 if (isExpanded) { 252 x -= labelWidth / 2; 253 if (i == xSplit) x = Math.min(x, width - labelWidth); 254 g.drawString(label, x, y + numberHeight); 255 } 256 } 257 } 258 259 maxY = 1; 260 { 261 int ySplit = isExpanded ? Math.min(10, chartHeight / (numberHeight * 3)) : 4; 262 for (int i = 0; i <= ySplit; i++) { 263 float percent = i / (float)ySplit; 264 String label = axisLabel(maxY * percent); 265 int labelWidth = metrics.stringWidth(label); 266 int y = (int)(chartY + chartHeight - chartHeight * percent); 267 if (isExpanded) g.drawString(label, yAxisWidth - 6 - labelWidth, y + numberHeight / 2); 268 if (i != 0 && i != ySplit) { 269 g.setColor(Color.lightGray); 270 g.drawLine(chartX, y, chartX + chartWidth - 1, y); 271 g.setColor(Color.black); 272 } 273 g.drawLine(yAxisWidth - 4, y, yAxisWidth, y); 274 } 275 } 276 277 { 278 int titleWidth = metrics.stringWidth(title); 279 int x = yAxisWidth + chartWidth / 2 - titleWidth / 2; 280 int y = chartY + chartHeight / 2 - numberHeight / 2; 281 g.setColor(Color.white); 282 g.fillRect(x - 2, y - 2, titleWidth + 4, numberHeight + 4); 283 g.setColor(Color.lightGray); 284 g.drawString(title, x, y + numberHeight); 285 } 286 287 g.setColor(Color.blue); 288 g.setStroke(new BasicStroke(isExpanded ? 3 : 2)); 289 int lastX = -1, lastY = -1; 290 for (Point point : points) { 291 Point pixel = pointToPixel(point); 292 if (lastX != -1) g.drawLine(lastX, lastY, (int)pixel.x, (int)pixel.y); 293 lastX = (int)pixel.x; 294 lastY = (int)pixel.y; 295 } 296 g.drawLine(lastX, lastY, chartX + chartWidth - 1, lastY); 297 for (int i = 0, n = points.size(); i < n; i++) { 298 Point point = points.get(i); 299 Point pixel = pointToPixel(point); 300 if (overIndex == i) 301 g.setColor(Color.red); 302 else 303 g.setColor(Color.black); 304 String label = valueLabel(point.y); 305 int labelWidth = metrics.stringWidth(label); 306 int pointSize = isExpanded ? POINT_SIZE_EXPANDED : POINT_SIZE; 307 int x = (int)pixel.x - pointSize / 2; 308 int y = (int)pixel.y - pointSize / 2; 309 g.fillOval(x, y, pointSize, pointSize); 310 if (isExpanded) { 311 g.setColor(Color.black); 312 x = Math.max(chartX + 2, Math.min(chartX + chartWidth - labelWidth, x)); 313 y -= 3; 314 if (y < chartY + numberHeight + 3) 315 y += 27; 316 else if (n > 1) { 317 Point comparePoint = i == n - 1 ? points.get(i - 1) : points.get(i + 1); 318 if (y < chartY + chartHeight - 27 && comparePoint.y > point.y) y += 27; 319 } 320 g.drawString(label, x, y); 321 } 322 } 323 } 324 valueLabel(float value)325 private String valueLabel (float value) { 326 value = (int)(value * 1000) / 10f; 327 if (value % 1 == 0) 328 return String.valueOf((int)value) + '%'; 329 else 330 return String.valueOf(value) + '%'; 331 } 332 axisLabel(float value)333 private String axisLabel (float value) { 334 value = (int)(value * 100); 335 if (value % 1 == 0) 336 return String.valueOf((int)value) + '%'; 337 else 338 return String.valueOf(value) + '%'; 339 } 340 341 static public class Point { 342 public float x; 343 public float y; 344 Point()345 public Point () { 346 } 347 Point(float x, float y)348 public Point (float x, float y) { 349 this.x = x; 350 this.y = y; 351 } 352 } 353 isExpanded()354 public boolean isExpanded () { 355 return isExpanded; 356 } 357 setExpanded(boolean isExpanded)358 public void setExpanded (boolean isExpanded) { 359 this.isExpanded = isExpanded; 360 } 361 setTitle(String title)362 public void setTitle(String title){ 363 this.title = title; 364 } 365 } 366