如何使用Openload以编程方式布局动态文字云?

时间:2012-10-09 09:22:38

标签: java swing tag-cloud

我已经到了可以使用这个OpenCloud和Swing在Java中显示我的code词云的舞台。它从文件中逐行加载文本,吞噬它并吐出我想要显示的单词。这会传递给我的云,然后每次读取一批行并添加更新时都会添加它们。目前我正在使用默认的布局管理器,但它只是将新单词附加到窗口的底部,最终流过窗口的底部。 这不是我想要的。

我所追求的更像是一句话:

enter image description here

理想情况下,我希望它:

  • 瞄准JPanel的中心
  • 径向向外添加单词
  • 每行允许多个字词(注意方式想要之间一个当前在新线上。)
  • Trickier :修剪得分较低的单词,当达到边缘时不适合。

根据我链接的解决方案,我将这些添加为JLabel,其字体基于每个标记的权重。

我已经浏览了所有不同的摇摆布局管理器,但它们似乎都不适合编程。我唯一的选择,如果我想自定义我的布局,因为我坚持使用Swing(是的,几乎完全卡住),似乎是使用absolute layout positioning。这似乎是一个驯服的狡猾的野兽。不幸的是,对于那些想要建议一些非常棒的非标准LayoutManager 的人,我无法将任何软件包添加到我的开发机器/访问互联网。我认为我需要做的是在页面的某处随机添加单词,然后对其他单词执行相同操作并调整它们,使它们不会相互碰撞窗口的边缘。

所以我觉得我需要知道:

  • 是否存在使用标准布局管理器之一进行此类布局随机/延迟调整的“已知”方法?
  • 有没有一种方法可以在没有布局管理器的情况下进行这种布局?这更容易吗?
  • 编写自己的布局管理器会更容易吗?当然这是一项艰巨的任务,不是吗?

道歉,如果这看起来我只是想在盘子上寻求解决方案,我对Java有点新鲜(我可以多长时间说出来!)我不知道从哪里开始这样的事情,任何帮助/提示都将不胜感激。

1 个答案:

答案 0 :(得分:3)

以下是使用WrapLayout的示例,它似乎正常工作:

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

import org.mcavallo.opencloud.Cloud;
import org.mcavallo.opencloud.Tag;

public class TestOpenCloud {

    private static final String[] WORDS = { "art", "australia", "baby", "beach", "birthday", "blue", "bw", "california", "canada", "canon",
            "cat", "chicago", "china", "christmas", "city", "dog", "england", "europe", "family", "festival", "flower", "flowers", "food",
            "france", "friends", "fun", "germany", "holiday", "india", "italy", "japan", "london", "me", "mexico", "music", "nature",
            "new", "newyork", "night", "nikon", "nyc", "paris", "park", "party", "people", "portrait", "sanfrancisco", "sky", "snow",
            "spain", "summer", "sunset", "taiwan", "tokyo", "travel", "trip", "uk", "usa", "vacation", "water", "wedding" };

    protected void initUI() {
        JFrame frame = new JFrame(TestOpenCloud.class.getSimpleName());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final JPanel panel = new JPanel(new WrapLayout());
        Cloud cloud = new Cloud();
        final Random random = new Random();
        for (String s : WORDS) {
            for (int i = random.nextInt(50); i > 0; i--) {
                cloud.addTag(s);
            }
        }
        for (Tag tag : cloud.tags()) {
            final JLabel label = new JLabel(tag.getName());
            label.setOpaque(false);
            label.setFont(label.getFont().deriveFont((float) tag.getWeight() * 10));
            panel.add(label);
        }
        JScrollPane scrollPane = new JScrollPane(panel);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        frame.add(scrollPane);
        frame.setSize(500, 550);
        frame.setVisible(true);
        Timer t = new Timer(1000, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {

                final JLabel label = new JLabel(WORDS[random.nextInt(WORDS.length)]);
                label.setOpaque(false);
                int fontSize = random.nextInt(20) + 8;
                label.setFont(label.getFont().deriveFont((float) fontSize));
                panel.add(label);
                panel.revalidate();
                panel.repaint();
            }
        });
        t.start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TestOpenCloud().initUI();
            }
        });
    }

    public class WrapLayout extends FlowLayout {
        private Dimension preferredLayoutSize;

        /**
         * Constructs a new <code>WrapLayout</code> with a left alignment and a default 5-unit horizontal and vertical gap.
         */
        public WrapLayout() {
            super();
        }

        /**
         * Constructs a new <code>FlowLayout</code> with the specified alignment and a default 5-unit horizontal and vertical gap. The value
         * of the alignment argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>, or <code>WrapLayout</code>.
         * 
         * @param align
         *            the alignment value
         */
        public WrapLayout(int align) {
            super(align);
        }

        /**
         * Creates a new flow layout manager with the indicated alignment and the indicated horizontal and vertical gaps.
         * <p>
         * The value of the alignment argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>, or <code>WrapLayout</code>.
         * 
         * @param align
         *            the alignment value
         * @param hgap
         *            the horizontal gap between components
         * @param vgap
         *            the vertical gap between components
         */
        public WrapLayout(int align, int hgap, int vgap) {
            super(align, hgap, vgap);
        }

        /**
         * Returns the preferred dimensions for this layout given the <i>visible</i> components in the specified target container.
         * 
         * @param target
         *            the component which needs to be laid out
         * @return the preferred dimensions to lay out the subcomponents of the specified container
         */
        @Override
        public Dimension preferredLayoutSize(Container target) {
            return layoutSize(target, true);
        }

        /**
         * Returns the minimum dimensions needed to layout the <i>visible</i> components contained in the specified target container.
         * 
         * @param target
         *            the component which needs to be laid out
         * @return the minimum dimensions to lay out the subcomponents of the specified container
         */
        @Override
        public Dimension minimumLayoutSize(Container target) {
            Dimension minimum = layoutSize(target, false);
            minimum.width -= getHgap() + 1;
            return minimum;
        }

        /**
         * Returns the minimum or preferred dimension needed to layout the target container.
         * 
         * @param target
         *            target to get layout size for
         * @param preferred
         *            should preferred size be calculated
         * @return the dimension to layout the target container
         */
        private Dimension layoutSize(Container target, boolean preferred) {
            synchronized (target.getTreeLock()) {
                // Each row must fit with the width allocated to the containter.
                // When the container width = 0, the preferred width of the container
                // has not yet been calculated so lets ask for the maximum.

                int targetWidth = target.getSize().width;

                if (targetWidth == 0) {
                    targetWidth = Integer.MAX_VALUE;
                }

                int hgap = getHgap();
                int vgap = getVgap();
                Insets insets = target.getInsets();
                int horizontalInsetsAndGap = insets.left + insets.right + hgap * 2;
                int maxWidth = targetWidth - horizontalInsetsAndGap;

                // Fit components into the allowed width

                Dimension dim = new Dimension(0, 0);
                int rowWidth = 0;
                int rowHeight = 0;

                int nmembers = target.getComponentCount();

                for (int i = 0; i < nmembers; i++) {
                    Component m = target.getComponent(i);

                    if (m.isVisible()) {
                        Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();

                        // Can't add the component to current row. Start a new row.

                        if (rowWidth + d.width > maxWidth) {
                            addRow(dim, rowWidth, rowHeight);
                            rowWidth = 0;
                            rowHeight = 0;
                        }

                        // Add a horizontal gap for all components after the first

                        if (rowWidth != 0) {
                            rowWidth += hgap;
                        }

                        rowWidth += d.width;
                        rowHeight = Math.max(rowHeight, d.height);
                    }
                }

                addRow(dim, rowWidth, rowHeight);

                dim.width += horizontalInsetsAndGap;
                dim.height += insets.top + insets.bottom + vgap * 2;

                // When using a scroll pane or the DecoratedLookAndFeel we need to
                // make sure the preferred size is less than the size of the
                // target container so shrinking the container size works
                // correctly. Removing the horizontal gap is an easy way to do this.

                Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);

                if (scrollPane != null) {
                    dim.width -= hgap + 1;
                }

                return dim;
            }
        }

        /*
         *  A new row has been completed. Use the dimensions of this row
         *  to update the preferred size for the container.
         *
         *  @param dim update the width and height when appropriate
         *  @param rowWidth the width of the row to add
         *  @param rowHeight the height of the row to add
         */
        private void addRow(Dimension dim, int rowWidth, int rowHeight) {
            dim.width = Math.max(dim.width, rowWidth);

            if (dim.height > 0) {
                dim.height += getVgap();
            }

            dim.height += rowHeight;
        }
    }
}