Methapolis  0.27
 All Classes Namespaces Files Functions Variables Enumerator
GraphsPane.java
Go to the documentation of this file.
1 // This file is part of MicropolisJ.
2 // Copyright (C) 2013 Jason Long
3 // Portions Copyright (C) 1989-2007 Electronic Arts Inc.
4 //
5 // MicropolisJ is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU GPLv3, with additional terms.
7 // See the README file, included in this distribution, for details.
8 
9 package micropolisj.gui;
10 
11 import java.awt.*;
12 import java.awt.event.*;
13 import java.awt.geom.Path2D;
14 import java.text.*;
15 import java.util.*;
16 import javax.swing.*;
17 
18 import micropolisj.engine.*;
19 import static micropolisj.gui.ColorParser.parseColor;
20 
21 public class GraphsPane extends JPanel
22  implements Micropolis.Listener
23 {
24  Micropolis engine;
25 
26  JToggleButton tenYearsBtn;
27  JToggleButton onetwentyYearsBtn;
28  GraphArea graphArea;
29 
30  static enum TimePeriod
31  {
32  TEN_YEARS,
33  ONETWENTY_YEARS;
34  }
35 
36  static enum GraphData
37  {
38  RESPOP,
39  COMPOP,
40  INDPOP,
41  MONEY,
42  CRIME,
43  POLLUTION;
44  }
45  EnumMap<GraphData,JToggleButton> dataBtns = new EnumMap<GraphData,JToggleButton>(GraphData.class);
46 
47  static ResourceBundle strings = MainWindow.strings;
48  static final int LEFT_MARGIN = 4;
49  static final int RIGHT_MARGIN = 4;
50  static final int TOP_MARGIN = 2;
51  static final int BOTTOM_MARGIN = 2;
52  static final int LEGEND_PADDING = 6;
53 
54  public GraphsPane(Micropolis engine)
55  {
56  super(new BorderLayout());
57 
58  assert engine != null;
59  this.engine = engine;
60  engine.addListener(this);
61 
62  JButton dismissBtn = new JButton(strings.getString("dismiss_graph"));
63  dismissBtn.addActionListener(new ActionListener() {
64  public void actionPerformed(ActionEvent evt) {
66  }});
67  add(dismissBtn, BorderLayout.SOUTH);
68 
69  JPanel b1 = new JPanel(new BorderLayout());
70  add(b1, BorderLayout.CENTER);
71 
72  JPanel toolsPane = new JPanel(new GridBagLayout());
73  b1.add(toolsPane, BorderLayout.WEST);
74 
75  GridBagConstraints c = new GridBagConstraints();
76  c.gridx = c.gridy = 0;
77  c.gridwidth = 2;
78  c.fill = GridBagConstraints.BOTH;
79  c.insets = new Insets(1,1,1,1);
80  tenYearsBtn = new JToggleButton(strings.getString("ten_years"));
81  tenYearsBtn.setMargin(new Insets(0,0,0,0));
82  tenYearsBtn.addActionListener(new ActionListener() {
83  public void actionPerformed(ActionEvent evt) {
84  setTimePeriod(TimePeriod.TEN_YEARS);
85  }});
86  toolsPane.add(tenYearsBtn, c);
87 
88  c.gridy++;
89  onetwentyYearsBtn = new JToggleButton(strings.getString("onetwenty_years"));
90  onetwentyYearsBtn.setMargin(new Insets(0,0,0,0));
91  onetwentyYearsBtn.addActionListener(new ActionListener() {
92  public void actionPerformed(ActionEvent evt) {
93  setTimePeriod(TimePeriod.ONETWENTY_YEARS);
94  }});
95  toolsPane.add(onetwentyYearsBtn, c);
96 
97  c.gridx = 0;
98  c.gridy = 2;
99  c.gridwidth = 1;
100  c.anchor = GridBagConstraints.NORTH;
101  c.weightx = 0.5;
102  toolsPane.add(makeDataBtn(GraphData.RESPOP), c);
103 
104  c.gridy = 3;
105  toolsPane.add(makeDataBtn(GraphData.COMPOP), c);
106 
107  c.gridy = 4;
108  toolsPane.add(makeDataBtn(GraphData.INDPOP), c);
109 
110  c.gridx = 1;
111  c.gridy = 2;
112  toolsPane.add(makeDataBtn(GraphData.MONEY), c);
113 
114  c.gridy = 3;
115  toolsPane.add(makeDataBtn(GraphData.CRIME), c);
116 
117  c.gridy = 4;
118  toolsPane.add(makeDataBtn(GraphData.POLLUTION), c);
119 
120  graphArea = new GraphArea();
121  b1.add(graphArea, BorderLayout.CENTER);
122 
123  setTimePeriod(TimePeriod.TEN_YEARS);
124  dataBtns.get(GraphData.MONEY).setSelected(true);
125  dataBtns.get(GraphData.POLLUTION).setSelected(true);
126  }
127 
128  public void setEngine(Micropolis newEngine)
129  {
130  if (engine != null) { //old engine
131  engine.removeListener(this);
132  }
133  engine = newEngine;
134  if (engine != null) { //new engine
135  engine.addListener(this);
136  graphArea.repaint();
137  }
138  }
139 
140  private void onDismissClicked()
141  {
142  setVisible(false);
143  }
144 
145  //implements Micropolis.Listener
146  public void cityMessage(MicropolisMessage message, CityLocation loc) {}
147  public void citySound(Sound sound, CityLocation loc) {}
148  public void demandChanged() {}
149  public void evaluationChanged() {}
150  public void fundsChanged() {}
151  public void optionsChanged() {}
152 
153  //implements Micropolis.Listener
154  public void censusChanged()
155  {
156  graphArea.repaint();
157  }
158 
159  private JToggleButton makeDataBtn(GraphData graph)
160  {
161  String icon1name = strings.getString("graph_button."+graph.name());
162  String icon2name = strings.getString("graph_button."+graph.name()+".selected");
163 
164  ImageIcon icon1 = new ImageIcon(getClass().getResource("/"+icon1name));
165  ImageIcon icon2 = new ImageIcon(getClass().getResource("/"+icon2name));
166 
167  JToggleButton btn = new JToggleButton();
168  btn.setIcon(icon1);
169  btn.setSelectedIcon(icon2);
170  btn.setBorder(null);
171  btn.setBorderPainted(false);
172  btn.setFocusPainted(false);
173  btn.setContentAreaFilled(false);
174  btn.setMargin(new Insets(0,0,0,0));
175 
176  btn.addActionListener(new ActionListener() {
177  public void actionPerformed(ActionEvent evt) {
178  graphArea.repaint();
179  }});
180 
181  dataBtns.put(graph, btn);
182  return btn;
183  }
184 
185  int getHistoryMax()
186  {
187  int max = 0;
188  for (GraphData g : GraphData.values()) {
189  for (int pos = 0; pos < 240; pos++) {
190  max = Math.max(max, getHistoryValue(g, pos));
191  }
192  }
193  return max;
194  }
195 
196  int getHistoryValue(GraphData graph, int pos)
197  {
198  assert pos >= 0 && pos < 240;
199  switch(graph) {
200  case RESPOP: return engine.history.res[pos];
201  case COMPOP: return engine.history.com[pos];
202  case INDPOP: return engine.history.ind[pos];
203  case MONEY: return engine.history.money[pos];
204  case CRIME: return engine.history.crime[pos];
205  case POLLUTION: return engine.history.pollution[pos];
206  default: throw new Error("unexpected");
207  }
208  }
209 
210  void setTimePeriod(TimePeriod period)
211  {
212  tenYearsBtn.setSelected(period == TimePeriod.TEN_YEARS);
213  onetwentyYearsBtn.setSelected(period == TimePeriod.ONETWENTY_YEARS);
214  graphArea.repaint();
215  }
216 
217  class GraphArea extends JComponent
218  {
219  GraphArea()
220  {
221  setBorder(BorderFactory.createLoweredBevelBorder());
222  }
223 
224  @Override
225  public void paintComponent(Graphics gr1)
226  {
227  Graphics2D gr = (Graphics2D)gr1;
228  FontMetrics fm = gr.getFontMetrics();
229 
230  gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
231  gr.setColor(Color.WHITE);
232  gr.fill(gr.getClipBounds());
233 
234  // determine length of longest label
235  int maxLabelWidth = 0;
236  for (GraphData gd : GraphData.values())
237  {
238  String labelStr = strings.getString("graph_label."+gd.name());
239  int adv = fm.stringWidth(labelStr);
240  if (adv > maxLabelWidth) {
241  maxLabelWidth = adv;
242  }
243  }
244 
245  int leftEdge = getInsets().left + LEFT_MARGIN;
246  int topEdge = getInsets().top + TOP_MARGIN + fm.getHeight()*2;
247  int bottomEdge = getHeight() - getInsets().bottom - getInsets().top - BOTTOM_MARGIN;
248  int rightEdge = getWidth() - getInsets().right - getInsets().left - RIGHT_MARGIN - maxLabelWidth - LEGEND_PADDING;
249 
250  // draw graph lower, upper borders
251  gr.setColor(Color.BLACK);
252  gr.drawLine(leftEdge,topEdge,rightEdge,topEdge);
253  gr.drawLine(leftEdge,bottomEdge,rightEdge,bottomEdge);
254 
255  // draw vertical bars and label the dates
256  boolean isOneTwenty = onetwentyYearsBtn.isSelected();
257  int unitPeriod = isOneTwenty ? 12*Micropolis.CENSUSRATE : Micropolis.CENSUSRATE;
258  int hashPeriod = isOneTwenty ? 10*unitPeriod : 12*unitPeriod;
259  int startTime = ((engine.history.cityTime / unitPeriod) - 119) * unitPeriod;
260 
261  double x_interval = (rightEdge - leftEdge) / 120.0;
262  for (int i = 0; i < 120; i++) {
263  int t = startTime + i * unitPeriod; // t might be negative
264  if (t % hashPeriod == 0) {
265  // year
266  int year = 1900+(t/(12*Micropolis.CENSUSRATE));
267  int numHashes = t/hashPeriod;
268  int x = (int)Math.round(leftEdge+i*x_interval);
269  int y = getInsets().top + TOP_MARGIN +
270  (numHashes % 2 == 0 ? fm.getHeight() : 0) +
271  fm.getAscent();
272  gr.drawString(Integer.toString(year), x, y);
273  gr.drawLine(x,topEdge,x,bottomEdge);
274  }
275  }
276 
277  int H = isOneTwenty ? 239 : 119;
278  final HashMap<GraphData, Path2D.Double> paths = new HashMap<GraphData,Path2D.Double>();
279  double scale = Math.max(256.0, getHistoryMax());
280  for (GraphData gd : GraphData.values())
281  {
282  if (dataBtns.get(gd).isSelected()) {
283 
284  Path2D.Double path = new Path2D.Double();
285  for (int i = 0; i < 120; i++) {
286  double xp = leftEdge + i * x_interval;
287  double yp = bottomEdge - getHistoryValue(gd,H-i) * (bottomEdge-topEdge) / scale;
288  if (i == 0) {
289  path.moveTo(xp, yp);
290  } else {
291  path.lineTo(xp, yp);
292  }
293  }
294  paths.put(gd, path);
295  }
296  }
297 
298  GraphData [] myGraphs = paths.keySet().toArray(new GraphData[0]);
299  Arrays.sort(myGraphs, new Comparator<GraphData>() {
300  public int compare(GraphData a, GraphData b) {
301  double y0 = paths.get(a).getCurrentPoint().getY();
302  double y1 = paths.get(b).getCurrentPoint().getY();
303  return -Double.compare(y0,y1);
304  }});
305 
306  int lbottom = bottomEdge;
307  for (GraphData gd : myGraphs)
308  {
309  String labelStr = strings.getString("graph_label."+gd.name());
310  String colStr = strings.getString("graph_color."+gd.name());
311  Color col = parseColor(colStr);
312  Path2D.Double path = paths.get(gd);
313 
314  gr.setColor(col);
315  gr.setStroke(new BasicStroke(2));
316  gr.draw(path);
317 
318  int x = rightEdge + LEGEND_PADDING;
319  int y = (int)Math.round(path.getCurrentPoint().getY()+fm.getAscent()/2);
320  y = Math.min(lbottom, y);
321  lbottom = y - fm.getAscent();
322 
323  gr.setColor(col);
324  gr.drawString(labelStr, x-1, y);
325  gr.drawString(labelStr, x, y-1);
326 
327  gr.setColor(Color.BLACK);
328  gr.drawString(labelStr, x, y);
329  }
330  }
331  }
332 }