Prefuse – Dashboard BSJUG Views (2012 – 09/07/2021)

Prefuse – Dashboard BSJUG Views

Prefuse é mais uma biblioteca para visualização de gráficos em Java.

A classe utilizada. Atenção para a utilização do uso do arquivo graph.xml e também da API prefuse.

import java.awt.Color;

import javax.swing.JFrame;
import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.layout.graph.BalloonTreeLayout;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.DragControl;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.data.Graph;
import prefuse.data.io.DataIOException;
import prefuse.data.io.GraphMLReader;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.visual.VisualItem;

public class ExampleI {

	public static void main(String[] argv) {

		// -- 1. load the data ------------------------------------------------

		// load the socialnet.xml file. it is assumed that the file can be
		// found at the root of the java classpath
		Graph graph = null;
		try {
			graph = new GraphMLReader().readGraph("data/graph.xml");
			// graph = new GraphMLReader().readGraph("data/socialnet.xml");

		} catch (DataIOException e) {
			e.printStackTrace();
			System.err.println("Error loading graph. Exiting...");
			System.exit(1);
		}

		// -- 2. the visualization --------------------------------------------

		// add the graph to the visualization as the data group "graph"
		// nodes and edges are accessible as "graph.nodes" and "graph.edges"
		Visualization vis = new Visualization();
		vis.add("graph", graph);
		vis.setInteractive("graph.edges", null, false);

		// -- 3. the renderers and renderer factory ---------------------------

		// draw the "name" label for NodeItems
		LabelRenderer r = new LabelRenderer("name");
		r.setRoundedCorner(8, 8); // round the corners

		// create a new default renderer factory
		// return our name label renderer as the default for all non-EdgeItems
		// includes straight line edges for EdgeItems by default
		vis.setRendererFactory(new DefaultRendererFactory(r));

		// -- 4. the processing actions ---------------------------------------

		// create our nominal color palette
		// pink for females, baby blue for males
		int[] palette = new int[] { ColorLib.rgb(255, 180, 180), ColorLib.rgb(190, 190, 255) };
		// map nominal data values to colors using our provided palette
		DataColorAction fill = new DataColorAction("graph.nodes", "type", Constants.NOMINAL, VisualItem.FILLCOLOR,
				palette);
		// use black for node text
		ColorAction text = new ColorAction("graph.nodes", VisualItem.TEXTCOLOR, ColorLib.gray(0));
		// use light grey for edges
		ColorAction edges = new ColorAction("graph.edges", VisualItem.STROKECOLOR, ColorLib.gray(200));

		// create an action list containing all color assignments
		ActionList color = new ActionList();
		color.add(fill);
		color.add(text);
		color.add(edges);

		// create an action list with an animated layout
		ActionList layout = new ActionList(Activity.INFINITY);
		// ActionList layout = new ActionList(Activity.DEFAULT_STEP_TIME);

		layout.add(new ForceDirectedLayout("graph"));
		layout.add(new RepaintAction());

		// add the actions to the visualization
		vis.putAction("color", color);
		vis.putAction("layout", layout);

		BalloonTreeLayout balloonlayout = new BalloonTreeLayout("graph", 300);
		layout.add(balloonlayout);

		// -- 5. the display and interactive controls -------------------------

		Display d = new Display(vis);
		d.setSize(720, 500); // set display size
		d.pan(350, 350); // Configura o centro do Display.
		// d.setForeground(Color.GRAY);
		// d.setBackground(Color.WHITE);
		// Cor cinza da aplicação Java
		d.setBackground(new Color(238, 238, 238));
		// drag individual items around
		d.addControlListener(new DragControl());
		// pan with left-click drag on background
		d.addControlListener(new PanControl());
		// zoom with right-click drag
		d.addControlListener(new ZoomControl());
		// d.addControlListener(new WheelZoomControl());
		// d.addControlListener(new ZoomToFitControl());
		// d.addControlListener(new NeighborHighlightControl());

		// assign the colors
		vis.run("color");
		// start up the animated layout
		vis.run("layout");

		// -- 6. launch the visualization -------------------------------------

		// create a new window to hold the visualization
		JFrame frame = new JFrame("Prefuse - Dashboard BSJUG Views (2012 - 09/07/2021)");
		// ensure application exits when window is closed
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(d);
		frame.pack(); // layout components in window
		frame.setVisible(true); // show the window
	}
}

Quem puder melhorar esse pequeno exemplo por favor fiquem a vontade e postem aqui.

prefuse.jar

grapho.xml

***

Dashboard BSJUG (2012 – 09/07/2021)

Dashboard BSJUG – Baixada Santista Java Users Group Views (2012 – 09/07/2021)

Dashboard do blog de 2012 até 09-07-2021 das visualizações e países e línguas. Usando JTable para visualização dos dados. Uso de imagens (ImageIcon) na tabela.

Para o código fonte não ficar muito grande aqui nessa postagem resolvi reduzir ele um pouco mesmo assim ainda ficou grande, mas tem como estudar e entender.

A casse DashboardBSJUG.java é a classe principal, mas perceba também o uso da classe BrowserOpener para abri o link da aplicação no browser, no seu navegador padrão. Para isso utilizei um pequeno arquivo html que chamei de BSJUG.html que esta declarado em um JEditorPane. Atenção para a classe ToolTipHeader.java que vai auxiliar a classe principal.

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.text.AttributeSet;
import javax.swing.text.Element;
import javax.swing.text.html.HTML;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.RowSorter;
import javax.swing.SwingConstants;
import javax.swing.ToolTipManager;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.ImageIcon;
import javax.swing.JTextField;

public class DashboardBSJUG extends JFrame {

	private static final long serialVersionUID = 1L;
	private JPanel contentPane;
	private JEditorPane editorPane;
	private static JTable table;
	private static JTextField filterText;
	private JLabel lblNewLabel;
	//DefaultTableModel model; 
    private TableModel model;
	
	DefaultTableCellRenderer esquerda = new DefaultTableCellRenderer();
	DefaultTableCellRenderer centralizado = new DefaultTableCellRenderer();
	DefaultTableCellRenderer direita = new DefaultTableCellRenderer();	

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					DashboardBSJUG frame = new DashboardBSJUG();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	public void showPage(String page) {
		URL pageURL = getClass().getResource(page);
		try {
			editorPane.setPage(pageURL);
		} catch (IOException ioe) {
			Toolkit.getDefaultToolkit().beep();
			JOptionPane.showMessageDialog(null, "ERRO while loading help page!", "Dashboard BSJUG",
					JOptionPane.ERROR_MESSAGE);
			System.out.println("ERRO while loading help page!");
		}
	}	

	public DashboardBSJUG() {
		setTitle("Dashboard BSJUG – Baixada Santista Java Users Group Views (2012 - 09/07/2021)");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 900, 600);
		setResizable(false);
		setLocationRelativeTo(null);

		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
		setContentPane(contentPane);
		contentPane.setLayout(null);

		editorPane = new JEditorPane();
		editorPane.setBounds(302, 77, 295, 166);
		ToolTipManager.sharedInstance().registerComponent(editorPane);
		
		editorPane.setEditable(false);
		editorPane.setContentType("text/html");
		editorPane.addHyperlinkListener(new BrowserOpener(this));
		editorPane.addHyperlinkListener(new MyHyperlinkListener(this));
		showPage("BSJUG.html");
		// A cor cinza da aplicação Java
		editorPane.setBackground(new Color(238, 238, 238));
		contentPane.add(editorPane);

		JLabel lblTitle = new JLabel("Dashboard BSJUG Views");
		lblTitle.setToolTipText("Dashboard BSJUG Views");
		lblTitle.setFont(new Font("Tahoma", Font.BOLD, 28));
		lblTitle.setHorizontalAlignment(SwingConstants.CENTER);
		lblTitle.setBounds(10, 11, 874, 46);
		contentPane.add(lblTitle);
		
		String[] toolTips = {"Índice", "País", "Língua", "Código Língua / País", "País / Visualizações", "Bandeira"};

	    String[] columns = { "Index", "Country", "Language", "Locale", "Country / Views", "Flag" };			
	    Object[][] rows = { 		
	    		 { "1º", "Brazil", "Portuguese", "pt-BR", "Brasil, 90379", new ImageIcon(DashboardBSJUG.class.getResource("/flagsII/Brazil.png")) },	
	    		 { "2º", "United States", "English", "en-US", "Estados Unidos, 12198", new ImageIcon(DashboardBSJUG.class.getResource("/flagsII/United States.png")) },
	    		 { "3º", "Portugal", "Portuguese", "pt-PT", "Portugal, 3277", new ImageIcon(DashboardBSJUG.class.getResource("/flagsII/Portugal.png")) },
	    		 { "4º", "Angola", "Portuguese", "pt-AO", "Angola, 1007", new ImageIcon(DashboardBSJUG.class.getResource("/flagsII/Angola.png")) },
	    		 { "5º", "Hong Kong SAR, China", "Chinese", "zh-HK", "Hong Kong, RAE Da China, 775", new ImageIcon(DashboardBSJUG.class.getResource("/flagsII/Hong Kong.png")) },
				 { "148º", "Burundi", "French", "fr-BI", "Burundi, 1", new ImageIcon(DashboardBSJUG.class.getResource("/flagsII/Burundi.png")) } };	
	    
		   //Create a table with a sorter.
           model = new DefaultTableModel(rows, columns) {
			
			private static final long serialVersionUID = 1L;

			public Class getColumnClass(int column) {
				Class returnValue;
				if ((column >= 0) && (column < getColumnCount())) {
					returnValue = getValueAt(0, column).getClass();
				} else {
					returnValue = Object.class;
				}
				return returnValue;
			}
		};	
		    
		    
		table = new JTable(model);
		TableRowSorter sorter = new TableRowSorter(model);
		table.setRowSorter(sorter);
		//Tabela sem deixar editar
		//table.setEnabled(false);
		//table.setEnabled(false);
		//Tabela deixa selecionar mas não deixar editar
		table.setDefaultEditor(Object.class, null);
		// table.setBounds(236, 306, 622, 244); 
		// Move as colunas
		// table.getTableHeader().setReorderingAllowed(false);
		// Cor cinza da aplicação Java
		table.setBackground(new Color(238, 238, 238));
		table.setCursor(new Cursor(Cursor.HAND_CURSOR));
		 
		ToolTipHeader tooltipHeader = new ToolTipHeader(table.getColumnModel());
		tooltipHeader.setToolTipStrings(toolTips);
		tooltipHeader.setCursor(new Cursor(Cursor.HAND_CURSOR));
		table.setTableHeader(tooltipHeader); 
		 
		// JTable Header Sort Data
		table.setAutoCreateRowSorter(true);
		//Largura das colunas 0, 4 e 5
		table.getColumnModel().getColumn(0).setPreferredWidth(10);
		table.getColumnModel().getColumn(3).setPreferredWidth(10);
		//table.getColumnModel().getColumn(4).setPreferredWidth(10);
		table.getColumnModel().getColumn(5).setPreferredWidth(10);
		esquerda.setHorizontalAlignment(2);
		centralizado.setHorizontalAlignment(0);
		direita.setHorizontalAlignment(4);
		table.getColumnModel().getColumn(3).setCellRenderer(centralizado);
		//table.getColumnModel().getColumn(4).setCellRenderer(centralizado);
		//largura das linhas da tabela 
		table.setRowHeight(50);
		//Hide Column in the JTable
		//table.removeColumn(table.getColumnModel().getColumn(4));
		table.setFillsViewportHeight(true);
		// For the purposes of this example, better to have a single
		// selection.
		table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
			public void valueChanged(ListSelectionEvent event) {

				int viewRow = DashboardBSJUG.table.getSelectedRow();
				if (viewRow < 0) {
					System.out.println(" ");
				} else {
					//int modelRow = Teste.table.convertRowIndexToModel(viewRow);
					String subtitleName = (String) DashboardBSJUG.table.getValueAt(DashboardBSJUG.table.getSelectedRow(), 1);		
					System.out.println(String.valueOf(subtitleName));	
				}
			}
		});
		
		JScrollPane scrollPane = new JScrollPane();		
		scrollPane.setBounds(30, 254, 839, 263);			
		contentPane.add(scrollPane);
		scrollPane.setViewportView(table);
		
		filterText = new JTextField();				
		filterText.setBounds(574, 528, 295, 20);
		//Whenever filterText changes, invoke newFilter.
        filterText.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				
				 RowSorter rs = table.getRowSorter();
			        if (rs == null) {
			            table.setAutoCreateRowSorter(true);
			            rs = table.getRowSorter();
			        }

			        TableRowSorter rowSorter =
			                (rs instanceof TableRowSorter) ? (TableRowSorter) rs : null;

			        if (rowSorter == null) {
			            throw new RuntimeException("Cannot find appropriate rowSorter: " + rs);
			        }
			    
			        filterText.getDocument().addDocumentListener(new DocumentListener() {
			            @Override
			            public void insertUpdate(DocumentEvent e) {
			                update(e);
			            }

			            @Override
			            public void removeUpdate(DocumentEvent e) {
			                update(e);
			            }

			            @Override
			            public void changedUpdate(DocumentEvent e) {
			                update(e);
			            }

			            private void update(DocumentEvent e) {
			                String text = filterText.getText();
			                if (text.trim().length() == 0) {
			                    rowSorter.setRowFilter(null);
			                } else {
			                	//O acento circonflex faz com que o regex reconheça acentuação
			                    rowSorter.setRowFilter(RowFilter.regexFilter("(?i)^" + text));
			                }
			                lblNewLabel.setText("Result(s): " + rowSorter.getViewRowCount() + " found.");
			            }
			        });				
			}
		});
		contentPane.add(filterText);
		
		JLabel lblPesquisa = new JLabel("Search:");
		lblPesquisa.setToolTipText("Pesquisa:");
		lblPesquisa.setBounds(505, 534, 59, 14);
		contentPane.add(lblPesquisa);
		
		lblNewLabel = new JLabel("");
		lblNewLabel.setBounds(30, 531, 465, 17);
		contentPane.add(lblNewLabel);
		// contentPane.add(table);		  
		  
	}//******************************************Fim do construtor

	class WindowEventHandler extends WindowAdapter {
		DashboardBSJUG guia;

		WindowEventHandler(DashboardBSJUG guia) {
			this.guia = guia;
		}

		public void windowClosing(WindowEvent evt) {
			DashboardBSJUG.this.dispose();
		}
	}
	
	//************************************************************
	class MyHyperlinkListener implements HyperlinkListener {
		private String tooltip;

		DashboardBSJUG guia;

		MyHyperlinkListener(DashboardBSJUG guia) {
			this.guia = guia;
		}

		public void hyperlinkUpdate(HyperlinkEvent e) {
			JEditorPane editor = (JEditorPane) e.getSource();
			if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED)
				if (e.getEventType() == HyperlinkEvent.EventType.ENTERED) {
					tooltip = editor.getToolTipText();
					Element elem = e.getSourceElement();
					if (elem != null) {
						AttributeSet attr = elem.getAttributes();
						AttributeSet a = (AttributeSet) attr.getAttribute(HTML.Tag.A);
						if (a != null)
							editor.setToolTipText((String) a.getAttribute(HTML.Attribute.TITLE));
					}
				} else if (e.getEventType() == HyperlinkEvent.EventType.EXITED) {
					editor.setToolTipText(this.tooltip);
				}
		}
	}	
	
	//*******************************************************************
	class BrowserOpener implements HyperlinkListener {
		DashboardBSJUG guia;

		BrowserOpener(DashboardBSJUG guia) {
			this.guia = guia;
		}

		public void hyperlinkUpdate(HyperlinkEvent event) {
			if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
				try {
					Desktop.getDesktop().browse(event.getURL().toURI());
				} catch (IOException e) {
					e.printStackTrace();
				} catch (URISyntaxException e) {
					e.printStackTrace();
				}
		}
	}
}

Essa aplicação demonstra a utilização dessa ferramentas Java para elaboração de um Dashboard ou para muitas outras coisas dependendo da sua necessidade.

Dashboard BSJUG – Baixada Santista Java Users Group Views.jar (535 KB)

Bom é isso.

Prefuse – ZipCode (Java)

Prefuse – ZipCode

Um outro exemplo utilizando a API prefuse bem interessante. Essa aplicação se resume em ler dois arquivos de .txt um chamado zipcode.txt e o outro state.txt ambos localizado dentro do Classpath do projeto. Mais informações sobre a biblioteca aqui: https://github.com/prefuse/Prefuse

A tela é bem simples. Existe um campo de pesquisar no qual você vai digital um zipcode (que equivalente para nós aqui no Brasil como o CEP).

A classe ZipDecode.java

import java.awt.Font;
import java.awt.Shape;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.BorderFactory;
import javax.swing.JFrame;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.Action;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.LocationAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.layout.AxisLayout;
import prefuse.activity.Activity;
import prefuse.activity.ActivityAdapter;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.data.Schema;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.expression.FunctionExpression;
import prefuse.data.expression.FunctionTable;
import prefuse.data.expression.Predicate;
import prefuse.data.expression.parser.ExpressionParser;
import prefuse.data.io.DataIOException;
import prefuse.data.io.DelimitedTextTableReader;
import prefuse.data.query.SearchQueryBinding;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.ShapeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.PrefuseLib;
import prefuse.util.ui.JSearchPanel;
import prefuse.visual.VisualItem;
import prefuse.visual.VisualTable;

/**
 * Re-implementation of Ben Fry's Zipdecode. Check out the original at
 * <a href="http://acg.media.mit.edu/people/fry/zipdecode/">
 * http://acg.media.mit.edu/people/fry/zipdecode/</a>.
 * 
 * This demo showcases creating new functions in the prefuse expression
 * language, creating derived columns, and provides an example of using a
 * dedicated focus set of items to support more efficient data handling.
 * 
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */

// https://github.com/prefuse/Prefuse
public class ZipDecode extends Display implements Constants {

	private static final long serialVersionUID = 1L;
	public static final String ZIPCODES = "/zipcode.txt";
	public static final String STATES = "/state.txt";

	// data groups
	private static final String DATA = "data";
	private static final String LABELS = "labels";
	private static final String FOCUS = Visualization.FOCUS_ITEMS;

	public static class StateLookupFunction extends FunctionExpression {
		private static Table s_states;
		static {
			try {
				s_states = new DelimitedTextTableReader().readTable(STATES);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		public StateLookupFunction() {
			super(1);
		}

		public String getName() {
			return "STATE";
		}

		public Class getType(Schema s) {
			return String.class;
		}

		public Object get(Tuple t) {
			int code = s_states.index("code").get(param(0).getInt(t));
			return s_states.getString(code, "alpha");
		}
	}

	// add state function to the FunctionTable
	static {
		FunctionTable.addFunction("STATE", StateLookupFunction.class);
	}

	public ZipDecode(final Table t) {
		super(new Visualization());

		// this predicate makes sure only the continental states are included
		Predicate filter = (Predicate) ExpressionParser.parse("state &gt;= 1 &amp;&amp; state  getWidth() / 2 ? RIGHT : LEFT);
				// now return shape as usual
				return super.getShape(item);
			}
		});
		m_vis.setRendererFactory(rf);

		// -- actions ---------------------------------------------------------

		ActionList layout = new ActionList();
		layout.add(new AxisLayout(DATA, "lat", Y_AXIS));
		layout.add(new AxisLayout(DATA, "lon", X_AXIS));
		m_vis.putAction("layout", layout);

		// the update list updates the colors of data points and sets the visual
		// properties for any labels. Color updating is limited only to the
		// current focus items, ensuring faster performance.
		final Action update = new ZipColorAction(FOCUS);
		m_vis.putAction("update", update);

		// animate a change in color in the interface. this animation is quite
		// short, only 200ms, so that it does not impede with interaction.
		// color animation of data points looks only at the focus items,
		// ensuring faster performance.
		ActionList animate = new ActionList(200);
		animate.add(new ColorAnimator(FOCUS, VisualItem.FILLCOLOR));
		animate.add(new ColorAnimator(LABELS, VisualItem.TEXTCOLOR));
		animate.add(new RepaintAction());
		animate.addActivityListener(new ActivityAdapter() {
			public void activityCancelled(Activity a) {
				// if animation is canceled, set colors to final state
				update.run(1.0);
			}
		});
		m_vis.putAction("animate", animate);

		// update items after a resize of the display, animating them to their
		// new locations. this animates all data points, so is noticeably slow.
		ActionList resize = new ActionList(1500);
		resize.setPacingFunction(new SlowInSlowOutPacer());
		resize.add(new LocationAnimator(DATA));
		resize.add(new LocationAnimator(LABELS));
		resize.add(new RepaintAction());
		m_vis.putAction("resize", resize);

		// -- display ---------------------------------------------------------

		setSize(720, 360);
		setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		setBackground(ColorLib.getGrayscale(50));
		setFocusable(false);

		// -- search ----------------------------------------------------------

		// zipcode text search is performed using a prefix based search,
		// provided by a search dynamic query. to make this application run
		// more efficiently, we optimize data handling by taking all search
		// results (both added and removed) and shuttling them into a
		// focus set. we work with this reduced set for updating and
		// animating color changes in the action definitions above.

		// create a final reference to the focus set, so that the following
		// search listener can access it.
		final TupleSet focus = m_vis.getFocusGroup(FOCUS);

		// create the search query binding
		SearchQueryBinding searchQ = new SearchQueryBinding(vt, "zipstr");
		final SearchTupleSet search = searchQ.getSearchSet();

		// create the listener that collects search results into a focus set
		search.addTupleSetListener(new TupleSetListener() {
			public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
				m_vis.cancel("animate");

				// invalidate changed tuples, add them all to the focus set
				focus.clear();
				for (int i = 0; i &lt; add.length; ++i) {
					((VisualItem) add[i]).setValidated(false);
					focus.addTuple(add[i]);
				}
				for (int i = 0; i "); // the search box label
		searcher.setShowCancel(false); // don't show the cancel query button
		searcher.setShowBorder(false); // don't show the search box border
		searcher.setFont(FontLib.getFont("Georgia", Font.PLAIN, 22));
		searcher.setBackground(ColorLib.getGrayscale(50));
		searcher.setForeground(ColorLib.getColor(100, 100, 75));
		add(searcher); // add the search box as a sub-component of the display
		searcher.setBounds(10, getHeight() - 40, 120, 30);

		addComponentListener(new ComponentAdapter() {
			public void componentResized(ComponentEvent e) {
				m_vis.run("layout");
				m_vis.run("update");
				m_vis.run("resize");
				searcher.setBounds(10, getHeight() - 40, 120, 30);
				invalidate();
			}
		});

		// -- launch ----------------------------------------------------------

		m_vis.run("layout");
		m_vis.run("animate");
	}

	private static Schema getDataSchema() {
		Schema s = PrefuseLib.getVisualItemSchema();
		s.setDefault(VisualItem.INTERACTIVE, false);
		s.setDefault(VisualItem.FILLCOLOR, ColorLib.rgb(100, 100, 75));
		return s;
	}

	private static Schema getLabelSchema() {
		Schema s = PrefuseLib.getMinimalVisualSchema();
		s.setDefault(VisualItem.INTERACTIVE, false);

		// default font is 16 point Georgia
		s.addInterpolatedColumn(VisualItem.FONT, Font.class, FontLib.getFont("Georgia", 16));

		// default fill color should be invisible
		s.addInterpolatedColumn(VisualItem.FILLCOLOR, int.class);
		s.setInterpolatedDefault(VisualItem.FILLCOLOR, 0);

		s.addInterpolatedColumn(VisualItem.TEXTCOLOR, int.class);
		// default text color is white
		s.setInterpolatedDefault(VisualItem.TEXTCOLOR, ColorLib.gray(255));
		// default start text color is fully transparent
		s.setDefault(VisualItem.STARTTEXTCOLOR, ColorLib.gray(255, 0));
		return s;
	}

	// ------------------------------------------------------------------------

	public static void main(String[] args) {
		String datafile = ZIPCODES;
		if (args.length &gt; 0)
			datafile = args[0];

		try {
			JFrame frame = demo(datafile);
			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			frame.setVisible(true);
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	public static JFrame demo() {
		try {
			return demo(ZIPCODES);
		} catch (Exception e) {
			return null;
		}
	}

	public static JFrame demo(String table) throws DataIOException {
		DelimitedTextTableReader tr = new DelimitedTextTableReader();
		Table t = tr.readTable(table);
		ZipDecode zd = new ZipDecode(t);

		JFrame frame = new JFrame("Prefuse - Zipdecode");
		frame.getContentPane().add(zd);
		frame.pack();
		return frame;
	}

	public static class ZipColorAction extends ColorAction {
		public ZipColorAction(String group) {
			super(group, VisualItem.FILLCOLOR);
		}

		public int getColor(VisualItem item) {
			if (item.isInGroup(Visualization.SEARCH_ITEMS)) {
				return ColorLib.gray(255);
			} else {
				return ColorLib.rgb(100, 100, 75);
			}
		}
	}

} // end of class ZipDecode

***

Bom, é isso.

Prefuse Radialgraph – Dashboard BSJUG

Prefuse Radialgraph – Dashboard BSJUG Views (2012 – 09/07/2021)

Uso da API prefuse (Java) para demonstração de resultados, em forma de gráficos. Infelizmente não obtive o resultado desejado devido a API ter sido meio que abandonada pelos desenvolvedores deixando pouco material como exemplo e estudo. Tentei entrar em contado com um dos desenvolvedores, mas não obtive ajuda. Então aqui fica o registro do que fiz e conseguir.

Quanto maio o volume de dados mais emaranhado ficará o gráfico em um modo geral. Olhando a imagem acima você verá o emaranhado de dados de visualizações do blog entre os anos de 2012 ano em que foi criado e 2021 o ano vigente em curso. Você pode arrastar com o mouse os dados no gráfico e veja também que existe um campo de pesquisa na parte inferior direita onde você pode pesquisar pelo o nome dos países que ganha um destaque em vermelho. Exemplo: Brazil.

O código fonte pode ser encontrado no link http://www.sfu.ca/siatclass/IAT355/Spring2012Old/Resources/prefuseDist/demos/prefuse/demos/RadialGraphView.java dos desenvolvedores você também vai precisar da API perfuse.jar e um arquivo .xml com a configuração desses dados. no caso o arquivo que eu usei aqui foi o graph.xml que também posso disponibilizar aqui.

A classe RadialGraphView.java


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.util.Iterator;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.GroupAction;
import prefuse.action.ItemAction;
import prefuse.action.RepaintAction;
import prefuse.action.animate.ColorAnimator;
import prefuse.action.animate.PolarLocationAnimator;
import prefuse.action.animate.QualityControlAnimator;
import prefuse.action.animate.VisibilityAnimator;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.FontAction;
import prefuse.action.layout.CollapsedSubtreeLayout;
import prefuse.action.layout.graph.RadialTreeLayout;
import prefuse.activity.SlowInSlowOutPacer;
import prefuse.controls.ControlAdapter;
import prefuse.controls.DragControl;
import prefuse.controls.FocusControl;
import prefuse.controls.HoverActionControl;
import prefuse.controls.PanControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.GraphMLReader;
import prefuse.data.query.SearchQueryBinding;
import prefuse.data.search.PrefixSearchTupleSet;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.data.tuple.TupleSet;
import prefuse.render.AbstractShapeRenderer;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.EdgeRenderer;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.FontLib;
import prefuse.util.ui.JFastLabel;
import prefuse.util.ui.JSearchPanel;
import prefuse.util.ui.UILib;
import prefuse.visual.VisualItem;
import prefuse.visual.expression.InGroupPredicate;
import prefuse.visual.sort.TreeDepthItemSorter;

/**
 * Demonstration of a node-link tree viewer
 *
 * @version 1.0
 * @author <a href="http://jheer.org">jeffrey heer</a>
 */

//http://www.sfu.ca/siatclass/IAT355/Spring2012Old/Resources/prefuseDist/demos/prefuse/demos/RadialGraphView.java
public class RadialGraphView extends Display {

	private static final long serialVersionUID = 1L;

	public static final String DATA_FILE = "data/graph.xml";

	private static final String tree = "tree";
	private static final String treeNodes = "tree.nodes";
	private static final String treeEdges = "tree.edges";
	private static final String linear = "linear";

	private LabelRenderer m_nodeRenderer;
	private EdgeRenderer m_edgeRenderer;

	private String m_label = "label";

	public RadialGraphView(Graph g, String label) {
		super(new Visualization());
		m_label = label;

		// -- set up visualization --
		m_vis.add(tree, g);
		m_vis.setInteractive(treeEdges, null, false);

		// -- set up renderers --
		m_nodeRenderer = new LabelRenderer(m_label);
		m_nodeRenderer.setRenderType(AbstractShapeRenderer.RENDER_TYPE_FILL);
		m_nodeRenderer.setHorizontalAlignment(Constants.CENTER);
		m_nodeRenderer.setRoundedCorner(8, 8);
		m_edgeRenderer = new EdgeRenderer();

		DefaultRendererFactory rf = new DefaultRendererFactory(m_nodeRenderer);
		rf.add(new InGroupPredicate(treeEdges), m_edgeRenderer);
		m_vis.setRendererFactory(rf);

		// -- set up processing actions --

		// colors
		ItemAction nodeColor = new NodeColorAction(treeNodes);
		ItemAction textColor = new TextColorAction(treeNodes);
		m_vis.putAction("textColor", textColor);

		ItemAction edgeColor = new ColorAction(treeEdges, VisualItem.STROKECOLOR, ColorLib.rgb(200, 200, 200));

		FontAction fonts = new FontAction(treeNodes, FontLib.getFont("Tahoma", 10));
		fonts.add("ingroup('_focus_')", FontLib.getFont("Tahoma", 11));

		// recolor
		ActionList recolor = new ActionList();
		recolor.add(nodeColor);
		recolor.add(textColor);
		m_vis.putAction("recolor", recolor);

		// repaint
		ActionList repaint = new ActionList();
		repaint.add(recolor);
		repaint.add(new RepaintAction());
		m_vis.putAction("repaint", repaint);

		// animate paint change
		ActionList animatePaint = new ActionList(400);
		animatePaint.add(new ColorAnimator(treeNodes));
		animatePaint.add(new RepaintAction());
		m_vis.putAction("animatePaint", animatePaint);

		// create the tree layout action
		RadialTreeLayout treeLayout = new RadialTreeLayout(tree);
		// treeLayout.setAngularBounds(-Math.PI/2, Math.PI);
		m_vis.putAction("treeLayout", treeLayout);

		CollapsedSubtreeLayout subLayout = new CollapsedSubtreeLayout(tree);
		m_vis.putAction("subLayout", subLayout);

		// create the filtering and layout
		ActionList filter = new ActionList();
		filter.add(new TreeRootAction(tree));
		filter.add(fonts);
		filter.add(treeLayout);
		filter.add(subLayout);
		filter.add(textColor);
		filter.add(nodeColor);
		filter.add(edgeColor);
		m_vis.putAction("filter", filter);

		// animated transition
		ActionList animate = new ActionList(1250);
		animate.setPacingFunction(new SlowInSlowOutPacer());
		animate.add(new QualityControlAnimator());
		animate.add(new VisibilityAnimator(tree));
		animate.add(new PolarLocationAnimator(treeNodes, linear));
		animate.add(new ColorAnimator(treeNodes));
		animate.add(new RepaintAction());
		m_vis.putAction("animate", animate);
		m_vis.alwaysRunAfter("filter", "animate");

		// ------------------------------------------------

		// initialize the display
		setSize(800, 600);
		setItemSorter(new TreeDepthItemSorter());
		addControlListener(new DragControl());
		addControlListener(new ZoomToFitControl());
		addControlListener(new ZoomControl());
		addControlListener(new PanControl());
		addControlListener(new FocusControl(1, "filter"));
		addControlListener(new HoverActionControl("repaint"));		

		// ------------------------------------------------

		// filter graph and perform layout
		m_vis.run("filter");

		// maintain a set of items that should be interpolated linearly
		// this isn't absolutely necessary, but makes the animations nicer
		// the PolarLocationAnimator should read this set and act accordingly
		m_vis.addFocusGroup(linear, new DefaultTupleSet());
		m_vis.getGroup(Visualization.FOCUS_ITEMS).addTupleSetListener(new TupleSetListener() {
			public void tupleSetChanged(TupleSet t, Tuple[] add, Tuple[] rem) {
				TupleSet linearInterp = m_vis.getGroup(linear);
				if (add.length  1) {
			infile = argv[0];
			label = argv[1];
		}

		UILib.setPlatformLookAndFeel();

		JFrame frame = new JFrame("Prefuse Radialgraph - Dashboard BSJUG Views (2012 - 09/07/2021)");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setContentPane(demo(infile, label));
		frame.pack();
		frame.setVisible(true);
	}

	public static JPanel demo() {
		return demo(DATA_FILE, "name");
	}

	public static JPanel demo(String datafile, final String label) {
		Graph g = null;
		try {
			g = new GraphMLReader().readGraph(datafile);
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
		return demo(g, label);
	}

	public static JPanel demo(Graph g, final String label) {
		// create a new radial tree view
		final RadialGraphView gview = new RadialGraphView(g, label);
		Visualization vis = gview.getVisualization();

		// create a search panel for the tree map
		SearchQueryBinding sq = new SearchQueryBinding((Table) vis.getGroup(treeNodes), label,
				(SearchTupleSet) vis.getGroup(Visualization.SEARCH_ITEMS));
		
		JSearchPanel search = sq.createSearchPanel();
		search.setShowResultCount(true);
		search.setBorder(BorderFactory.createEmptyBorder(5, 5, 4, 0));
		search.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 11));

		final JFastLabel title = new JFastLabel("                 ");
		title.setPreferredSize(new Dimension(350, 20));
		title.setVerticalAlignment(SwingConstants.BOTTOM);
		title.setBorder(BorderFactory.createEmptyBorder(3, 0, 0, 0));
		title.setFont(FontLib.getFont("Tahoma", Font.PLAIN, 16));

		gview.addControlListener(new ControlAdapter() {
			public void itemEntered(VisualItem item, MouseEvent e) {
				if (item.canGetString(label))
					title.setText(item.getString(label));
			}

			public void itemExited(VisualItem item, MouseEvent e) {
				title.setText(null);
			}
		});

		Box box = new Box(BoxLayout.X_AXIS);
		box.add(Box.createHorizontalStrut(10));
		box.add(title);
		box.add(Box.createHorizontalGlue());
		box.add(search);
		box.add(Box.createHorizontalStrut(3));

		JPanel panel = new JPanel(new BorderLayout());
		panel.add(gview, BorderLayout.CENTER);
		panel.add(box, BorderLayout.SOUTH);

		//Color BACKGROUND = Color.WHITE;
		Color BACKGROUND = Color.GRAY;
		Color FOREGROUND = Color.DARK_GRAY;
		UILib.setColor(panel, BACKGROUND, FOREGROUND);

		return panel;
	}

	// ------------------------------------------------------------------------

	/**
	 * Switch the root of the tree by requesting a new spanning tree at the desired
	 * root
	 */
	public static class TreeRootAction extends GroupAction {
		public TreeRootAction(String graphGroup) {
			super(graphGroup);
		}

		public void run(double frac) {
			TupleSet focus = m_vis.getGroup(Visualization.FOCUS_ITEMS);
			if (focus == null || focus.getTupleCount() == 0)
				return;

			Graph g = (Graph) m_vis.getGroup(m_group);
			Node f = null;
			Iterator tuples = focus.tuples();
			while (tuples.hasNext() &amp;&amp; !g.containsTuple(f = (Node) tuples.next())) {
				f = null;
			}
			if (f == null)
				return;
			g.getSpanningTree(f);
		}
	}

	/**
	 * Set node fill colors
	 */
	public static class NodeColorAction extends ColorAction {
		public NodeColorAction(String group) {
			super(group, VisualItem.FILLCOLOR, ColorLib.rgba(255, 255, 255, 0));
			add("_hover", ColorLib.gray(220, 230));
			add("ingroup('_search_')", ColorLib.rgb(255, 190, 190));
			add("ingroup('_focus_')", ColorLib.rgb(198, 229, 229));
		}

	} // end of inner class NodeColorAction

	/**
	 * Set node text colors
	 */
	public static class TextColorAction extends ColorAction {
		public TextColorAction(String group) {
			super(group, VisualItem.TEXTCOLOR, ColorLib.gray(0));
			add("_hover", ColorLib.rgb(255, 0, 0));
		}
	} // end of inner class TextColorAction

} // end of class RadialGraphView

Essa API também pode e consegue gerar outros tipos de gráficos que com um tempo posso ir postando por aqui. Esse é apenas um dos tipos de gráfico que essa API pode gerar.

API Java: prefuse.jar (Size: 4,36 MB)

graph.xml

***

Bom é isso. Quem puder contribuir seria de bom agrado.

Java Flag Quiz Game

Jogo simples em Java Swing com bandeiras de países.

Fonte: https://slidetodoc.com/1-tutorial-16-flag-quiz-application-introducing-onedimensional/

Classe java:


// Tutorial 16: FlagQuiz java 
// Quiz the user on their knowledge of flags The user must try to 
// match five flags to their countries. 
import java.util.*;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.event.*;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.border.*;

//https://slidetodoc.com/1-tutorial-16-flag-quiz-application-introducing-onedimensional/
public class FlagQuiz extends JFrame {

	private static final long serialVersionUID = 1L;

	// array of country names
	private String[] countries = { "Russia", "China", "United States", "Italy", "Australia", "South Africa", "Brazil",
			"Spain" };

	// boolean array tracks displayed flags
	private boolean[] flagsUsed = new boolean[countries.length];
	// contains the index of current flag
	private int currentIndex;
	// tracks the number of flags that have been displayed
	private int count = 1;
	
	int score = 0;

	// JPanel and JLabel for displaying a flag image
	private JPanel flagJPanel;
	private JLabel flagIconJLabel;
	// JLabel and JComboBox for choosing a country
	private JLabel selectCountryJLabel;
	private JComboBox selectCountryJComboBox;
	// JTextField for giving the user feedback
	private JTextField feedbackJTextField;
	// JButton to submit an answer
	private JButton submitJButton;

	// JButton to display the next flag
	private JButton nextFlagJButton;

	// no-argument constructor
	public FlagQuiz() {
		createUserInterface();
	}

	// create and position GUI components; register event handlers
	private void createUserInterface() {
		// get content pane for attaching GUI components
		Container contentPane = getContentPane();
		// enable explicit positioning of GUI components
		contentPane.setLayout(null);
		// set up flagJPanel
		flagJPanel = new JPanel();
		flagJPanel.setBounds(10, 11, 100, 90);
		flagJPanel.setLayout(null);
		flagJPanel.setBorder(new TitledBorder("Flag"));
		contentPane.add(flagJPanel);

		// set up flagIconJLabel
		flagIconJLabel = new JLabel();
		flagIconJLabel.setBounds(10, 11, 80, 68);
		flagIconJLabel.setHorizontalAlignment(JLabel.CENTER);
		flagJPanel.add(flagIconJLabel);

		// set up selectCountryJLabel
		selectCountryJLabel = new JLabel();
		selectCountryJLabel.setBounds(120, 8, 151, 21);
		selectCountryJLabel.setText("Select country: ");
		contentPane.add(selectCountryJLabel);

		Arrays.sort(countries); // sort the array

		// set up selectCountryJComboBox
		selectCountryJComboBox = new JComboBox(countries);
		selectCountryJComboBox.setBounds(120, 32, 151, 21);
		selectCountryJComboBox.setMaximumRowCount(3);
		contentPane.add(selectCountryJComboBox);

		displayFlag(); // display first flag

		// set up feedbackJTextField
		feedbackJTextField = new JTextField();
		feedbackJTextField.setBounds(120, 64, 151, 32);
		feedbackJTextField.setHorizontalAlignment(JTextField.CENTER);
		feedbackJTextField.setEditable(false);
		contentPane.add(feedbackJTextField);

		// set up submitJButton
		submitJButton = new JButton();
		submitJButton.setBounds(281, 26, 88, 32);
		submitJButton.setText("Submit");
		submitJButton.setToolTipText("Submit");
		submitJButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
		contentPane.add(submitJButton);
		submitJButton.addActionListener(new ActionListener()// anonymous inner class
		{
			// event handler called when submitJButton is pressed
			public void actionPerformed(ActionEvent event) {
				submitJButtonActionPerformed(event);
			}
		}// end anonymous inner class
		); // end call to addActionListener

		// set up nextFlagJButton
		nextFlagJButton = new JButton();
		nextFlagJButton.setBounds(281, 66, 88, 32);
		nextFlagJButton.setText("Next Flag");
		nextFlagJButton.setToolTipText("Next Flag");
		nextFlagJButton.setCursor(new Cursor(Cursor.HAND_CURSOR));
		nextFlagJButton.setEnabled(false);
		contentPane.add(nextFlagJButton);
		nextFlagJButton.addActionListener(new ActionListener() // anonymous inner class
		{
			// event handler called when nextFlagJButton is pressed
			public void actionPerformed(ActionEvent event) {
				nextFlagJButtonActionPerformed(event);
			}
		} // end anonymous inner class
		); // end call to add. Action. Listener

		// set properties of application’s window
		setTitle("Flag Quiz"); // set title bar string
		setSize(400, 200); // set window size
		setVisible(true);

	} // end method createUserInterface
	 // return an unused random number

	private int getUniqueRandomNumber() {
		Random generator = new Random();
		int randomNumber;
		// generate random numbers until unused flag is found
		do {
			randomNumber = generator.nextInt(8);
		} while (flagsUsed[randomNumber] == true);
		// indicate that flag has been used
		flagsUsed[randomNumber] = true;
		return randomNumber;
	}// end method getUniqueRandomNumber

	// choose a flag and display it in the JLabel
	private void displayFlag() {

		currentIndex = getUniqueRandomNumber();

		// get an unused flag
		// create the path for that flag
		String country = (String) selectCountryJComboBox.getItemAt(currentIndex);
		String countryPath = "images/" + country + ".png";
		
		// set the flagIconJLabel to display the
		// flagIconJLabel.setIcon( new ImageIcon( countryPath ) );
		flagIconJLabel.setIcon(new ImageIcon(FlagQuiz.class.getResource(countryPath)));
		
	}// end method displayFlag

	// check the answer and update the quiz
	private void submitJButtonActionPerformed(ActionEvent event) {
		// determine whether the answer was correct
		if (selectCountryJComboBox.getSelectedIndex() == currentIndex) {
			feedbackJTextField.setText("Correct!");		
			
			score = score + 1;
			System.out.println("Score: " + score);
			
		} else {
			// if an incorrect answer is given
			feedbackJTextField.setText("Sorry, incorrect!");
			
			score = score - 1;
			System.out.println("Score: " + score);
		}

		// inform user if quiz is over
		if (count == 5) {
			feedbackJTextField.setText(feedbackJTextField.getText() + " Game Over!");
			nextFlagJButton.setEnabled(false);
			submitJButton.setEnabled(false);
			selectCountryJComboBox.setEnabled(false);
		} else // if less than 5 flags have been displayed
		{
			submitJButton.setEnabled(false);
			nextFlagJButton.setEnabled(true);
		}
	} // end method submitJButtonActionPerformed
// display next flag in the quiz 

	private void nextFlagJButtonActionPerformed(ActionEvent event) {
		displayFlag(); // display next flag

		count++;		
		
		// reset GUI components to initial states
		feedbackJTextField.setText("");
		selectCountryJComboBox.setSelectedIndex(0);
		submitJButton.setEnabled(true);
		nextFlagJButton.setEnabled(false);
	} // end method nextFlagJButton.ActionPerformed
// main method 

	public static void main(String args[]) {
		FlagQuiz application = new FlagQuiz();
		application.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	} // end method main
} // end class FlagQuiz

Bem simples, mas eficaz.

South Africa
Brazil
Italy
China
Russia
United States
Spain
Australia