// This file is part of MicropolisJ. // Copyright (C) 2013 Jason Long // Portions Copyright (C) 1989-2007 Electronic Arts Inc. // // MicropolisJ is free software; you can redistribute it and/or modify // it under the terms of the GNU GPLv3, with additional terms. // See the README file, included in this distribution, for details. package micropolisj.gui; import java.awt.*; import java.awt.event.*; import java.awt.geom.Path2D; import java.text.*; import java.util.*; import javax.swing.*; import micropolisj.engine.*; import static micropolisj.gui.ColorParser.parseColor; public class GraphsPane extends JPanel implements Micropolis.Listener { Micropolis engine; JToggleButton tenYearsBtn; JToggleButton onetwentyYearsBtn; GraphArea graphArea; static enum TimePeriod { TEN_YEARS, ONETWENTY_YEARS; } static enum GraphData { RESPOP, COMPOP, INDPOP, MONEY, CRIME, POLLUTION; } EnumMap dataBtns = new EnumMap<>(GraphData.class); static ResourceBundle strings = MainWindow.strings; static final int LEFT_MARGIN = 4; static final int RIGHT_MARGIN = 4; static final int TOP_MARGIN = 2; static final int BOTTOM_MARGIN = 2; static final int LEGEND_PADDING = 6; public GraphsPane(Micropolis engine) { super(new BorderLayout()); assert engine != null; this.engine = engine; engine.addListener(this); JButton dismissBtn = new JButton(strings.getString("dismiss_graph")); dismissBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { onDismissClicked(); }}); add(dismissBtn, BorderLayout.SOUTH); JPanel b1 = new JPanel(new BorderLayout()); add(b1, BorderLayout.CENTER); JPanel toolsPane = new JPanel(new GridBagLayout()); b1.add(toolsPane, BorderLayout.WEST); GridBagConstraints c = new GridBagConstraints(); c.gridx = c.gridy = 0; c.gridwidth = 2; c.fill = GridBagConstraints.BOTH; c.insets = new Insets(1,1,1,1); tenYearsBtn = new JToggleButton(strings.getString("ten_years")); tenYearsBtn.setMargin(new Insets(0,0,0,0)); tenYearsBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { setTimePeriod(TimePeriod.TEN_YEARS); }}); toolsPane.add(tenYearsBtn, c); c.gridy++; onetwentyYearsBtn = new JToggleButton(strings.getString("onetwenty_years")); onetwentyYearsBtn.setMargin(new Insets(0,0,0,0)); onetwentyYearsBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { setTimePeriod(TimePeriod.ONETWENTY_YEARS); }}); toolsPane.add(onetwentyYearsBtn, c); c.gridx = 0; c.gridy = 2; c.gridwidth = 1; c.anchor = GridBagConstraints.NORTH; c.weightx = 0.5; toolsPane.add(makeDataBtn(GraphData.RESPOP), c); c.gridy = 3; toolsPane.add(makeDataBtn(GraphData.COMPOP), c); c.gridy = 4; toolsPane.add(makeDataBtn(GraphData.INDPOP), c); c.gridx = 1; c.gridy = 2; toolsPane.add(makeDataBtn(GraphData.MONEY), c); c.gridy = 3; toolsPane.add(makeDataBtn(GraphData.CRIME), c); c.gridy = 4; toolsPane.add(makeDataBtn(GraphData.POLLUTION), c); graphArea = new GraphArea(); b1.add(graphArea, BorderLayout.CENTER); setTimePeriod(TimePeriod.TEN_YEARS); dataBtns.get(GraphData.MONEY).setSelected(true); dataBtns.get(GraphData.POLLUTION).setSelected(true); } public void setEngine(Micropolis newEngine) { if (engine != null) { //old engine engine.removeListener(this); } engine = newEngine; if (engine != null) { //new engine engine.addListener(this); graphArea.repaint(); } } private void onDismissClicked() { setVisible(false); } //implements Micropolis.Listener public void cityMessage(MicropolisMessage message, CityLocation loc, boolean isPic) {} public void citySound(Sound sound, CityLocation loc) {} public void demandChanged() {} public void evaluationChanged() {} public void fundsChanged() {} public void optionsChanged() {} //implements Micropolis.Listener public void censusChanged() { graphArea.repaint(); } private JToggleButton makeDataBtn(GraphData graph) { String icon1name = strings.getString("graph_button."+graph.name()); String icon2name = strings.getString("graph_button."+graph.name()+".selected"); ImageIcon icon1 = new ImageIcon(getClass().getResource("/"+icon1name)); ImageIcon icon2 = new ImageIcon(getClass().getResource("/"+icon2name)); JToggleButton btn = new JToggleButton(); btn.setIcon(icon1); btn.setSelectedIcon(icon2); btn.setBorder(null); btn.setBorderPainted(false); btn.setFocusPainted(false); btn.setContentAreaFilled(false); btn.setMargin(new Insets(0,0,0,0)); btn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { graphArea.repaint(); }}); dataBtns.put(graph, btn); return btn; } int getHistoryValue(GraphData graph, int pos) { assert pos >= 0 && pos < 240; switch(graph) { case RESPOP: return engine.history.res[pos]; case COMPOP: return engine.history.com[pos]; case INDPOP: return engine.history.ind[pos]; case MONEY: return engine.history.money[pos]; case CRIME: return engine.history.crime[pos]; case POLLUTION: return engine.history.pollution[pos]; default: throw new Error("unexpected"); } } void setTimePeriod(TimePeriod period) { tenYearsBtn.setSelected(period == TimePeriod.TEN_YEARS); onetwentyYearsBtn.setSelected(period == TimePeriod.ONETWENTY_YEARS); graphArea.repaint(); } class GraphArea extends JComponent { GraphArea() { setBorder(BorderFactory.createLoweredBevelBorder()); } @Override public void paintComponent(Graphics gr1) { Graphics2D gr = (Graphics2D)gr1; FontMetrics fm = gr.getFontMetrics(); gr.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); gr.setColor(Color.WHITE); gr.fill(gr.getClipBounds()); // determine length of longest label int maxLabelWidth = 0; for (GraphData gd : GraphData.values()) { String labelStr = strings.getString("graph_label."+gd.name()); int adv = fm.stringWidth(labelStr); if (adv > maxLabelWidth) { maxLabelWidth = adv; } } int leftEdge = getInsets().left + LEFT_MARGIN; int topEdge = getInsets().top + TOP_MARGIN + fm.getHeight()*2; int bottomEdge = getHeight() - getInsets().bottom - getInsets().top - BOTTOM_MARGIN; int rightEdge = getWidth() - getInsets().right - getInsets().left - RIGHT_MARGIN - maxLabelWidth - LEGEND_PADDING; // draw graph lower, upper borders gr.setColor(Color.BLACK); gr.drawLine(leftEdge,topEdge,rightEdge,topEdge); gr.drawLine(leftEdge,bottomEdge,rightEdge,bottomEdge); // draw vertical bars and label the dates boolean isOneTwenty = onetwentyYearsBtn.isSelected(); int unitPeriod = isOneTwenty ? 12*Micropolis.CENSUSRATE : Micropolis.CENSUSRATE; int hashPeriod = isOneTwenty ? 10*unitPeriod : 12*unitPeriod; int startTime = ((engine.history.cityTime / unitPeriod) - 119) * unitPeriod; double x_interval = (rightEdge - leftEdge) / 120.0; for (int i = 0; i < 120; i++) { int t = startTime + i * unitPeriod; // t might be negative if (t % hashPeriod == 0) { // year int year = 1900+(t/(12*Micropolis.CENSUSRATE)); int numHashes = t/hashPeriod; int x = (int)Math.round(leftEdge+i*x_interval); int y = getInsets().top + TOP_MARGIN + (numHashes % 2 == 0 ? fm.getHeight() : 0) + fm.getAscent(); gr.drawString(Integer.toString(year), x, y); gr.drawLine(x,topEdge,x,bottomEdge); } } int H = isOneTwenty ? 239 : 119; final HashMap paths = new HashMap<>(); for (GraphData gd : GraphData.values()) { if (dataBtns.get(gd).isSelected()) { Path2D.Double path = new Path2D.Double(); for (int i = 0; i < 120; i++) { double xp = leftEdge + i * x_interval; double yp = bottomEdge - getHistoryValue(gd,H-i) * (bottomEdge-topEdge) / 256.0; if (i == 0) { path.moveTo(xp, yp); } else { path.lineTo(xp, yp); } } paths.put(gd, path); } } GraphData [] myGraphs = paths.keySet().toArray(new GraphData[0]); Arrays.sort(myGraphs, new Comparator() { public int compare(GraphData a, GraphData b) { double y0 = paths.get(a).getCurrentPoint().getY(); double y1 = paths.get(b).getCurrentPoint().getY(); return -Double.compare(y0,y1); }}); int lbottom = bottomEdge; for (GraphData gd : myGraphs) { String labelStr = strings.getString("graph_label."+gd.name()); String colStr = strings.getString("graph_color."+gd.name()); Color col = parseColor(colStr); Path2D.Double path = paths.get(gd); gr.setColor(col); gr.setStroke(new BasicStroke(2)); gr.draw(path); int x = rightEdge + LEGEND_PADDING; int y = (int)Math.round(path.getCurrentPoint().getY()+fm.getAscent()/2); y = Math.min(lbottom, y); lbottom = y - fm.getAscent(); gr.setColor(col); gr.drawString(labelStr, x-1, y); gr.drawString(labelStr, x, y-1); gr.setColor(Color.BLACK); gr.drawString(labelStr, x, y); } } } }