From 0b53dd9baf26781c6ab34dbe456c055d1072bfa1 Mon Sep 17 00:00:00 2001 From: Jonas Thiel Date: Fri, 15 Mar 2013 11:27:14 +0100 Subject: [PATCH] Refector and documentations --- config.properties | 2 +- src/ColorHelper.java | 20 ++++- src/Configuration.java | 73 ++++++++++++----- src/InputWords.java | 42 ---------- src/TagCloud.java | 18 ++++- src/TagCloudColorer.java | 42 ++++++++++ src/TagCloudGenerator.java | 80 +++++++++++++++++++ ...peBasedPlacer.java => TagCloudPlacer.java} | 14 ++-- src/TagCloudSizer.java | 63 +++++++++++++++ src/Tree.java | 73 ----------------- src/TreeColorer.java | 26 ------ src/WordSizer.java | 33 -------- src/WordsReader.java | 69 ++++++++++++++++ 13 files changed, 348 insertions(+), 207 deletions(-) delete mode 100644 src/InputWords.java create mode 100644 src/TagCloudColorer.java create mode 100644 src/TagCloudGenerator.java rename src/{ShapeBasedPlacer.java => TagCloudPlacer.java} (91%) create mode 100644 src/TagCloudSizer.java delete mode 100644 src/Tree.java delete mode 100644 src/TreeColorer.java delete mode 100644 src/WordSizer.java create mode 100644 src/WordsReader.java diff --git a/config.properties b/config.properties index a5f61ce..f48cdae 100644 --- a/config.properties +++ b/config.properties @@ -3,7 +3,7 @@ inputFile=input.txt outputFile=out.png width=500 height=636 -minSize=4 +minSize=8 maxSize=38 colors=#000000,#3a3a3a,#787878,#b2b2b2 background=#ffffff diff --git a/src/ColorHelper.java b/src/ColorHelper.java index 3ed998d..d6ae80e 100644 --- a/src/ColorHelper.java +++ b/src/ColorHelper.java @@ -1,10 +1,26 @@ import java.awt.*; + +/** + * Utility class to transform and parse colors. + */ public class ColorHelper { - public static int transform(Color c){ + /** + * Transforms a java.awt.Color to processings internal int representation. + * + * @param c the color to be transformed + * @return A processing color value + */ + public static int transform(Color c) { return (c.getAlpha() << 24) | (c.getRed() << 16) | (c.getGreen() << 8) | c.getBlue(); } - public static int decode(String c){ + /** + * Decodes a color string in HEX-Values + * + * @param c the color string to be transformed + * @return A processing color value + */ + public static int decodeHex(String c) { return transform(Color.decode(c)); } } diff --git a/src/Configuration.java b/src/Configuration.java index c98dead..17fffed 100644 --- a/src/Configuration.java +++ b/src/Configuration.java @@ -1,37 +1,70 @@ -import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.Properties; +/** + * Singleton holding the TagCloud configuration. + */ public class Configuration { private static Configuration instance = null; private Properties prop = new Properties(); - private Configuration(){} + private Configuration() { + } - public String getShapeFile(){ return prop.getProperty("shapeFile"); } - public String getInputFile(){ return prop.getProperty("inputFile"); } - public String getOutputFile(){ return prop.getProperty("outputFile"); } - public int getWidth() { return Integer.parseInt(prop.getProperty("width")); } - public int getHeight(){ return Integer.parseInt(prop.getProperty("height")); } - public int getMinSize(){ return Integer.parseInt(prop.getProperty("minSize")); } - public int getMaxSize(){ return Integer.parseInt(prop.getProperty("maxSize")); } - public String getColors(){ return prop.getProperty("colors"); } - public String getBackgroundColor(){return prop.getProperty("background"); } - public boolean isDebug(){ + public String getShapeFile() { + return prop.getProperty("shapeFile"); + } + + public String getInputFile() { + return prop.getProperty("inputFile"); + } + + public String getOutputFile() { + return prop.getProperty("outputFile"); + } + + public int getWidth() { + return Integer.parseInt(prop.getProperty("width")); + } + + public int getHeight() { + return Integer.parseInt(prop.getProperty("height")); + } + + public int getMinSize() { + return Integer.parseInt(prop.getProperty("minSize")); + } + + public int getMaxSize() { + return Integer.parseInt(prop.getProperty("maxSize")); + } + + public String getColors() { + return prop.getProperty("colors"); + } + + public String getBackgroundColor() { + return prop.getProperty("background"); + } + + public boolean isDebug() { return prop.getProperty("debug") != null && prop.getProperty("debug").equals("true"); } - public void load(){ - try { - prop.load(new FileInputStream("config.properties")); - } catch (IOException e) { - e.printStackTrace(); - } + /** + * Load configuration from an input stream + * + * @param inputStream to load from + * @throws IOException + */ + public void load(InputStream inputStream) throws IOException { + prop.load(inputStream); } - public static Configuration getInstance(){ - if(instance == null){ + public static Configuration getInstance() { + if (instance == null) { instance = new Configuration(); } return instance; diff --git a/src/InputWords.java b/src/InputWords.java deleted file mode 100644 index 4f1ea32..0000000 --- a/src/InputWords.java +++ /dev/null @@ -1,42 +0,0 @@ -import wordcram.Word; - -import java.io.BufferedReader; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.util.ArrayList; - -public class InputWords { - private String file; - - public InputWords(String file){ - this.file = file; - } - - public Word[] getWords(){ - ArrayList words = new ArrayList(); - - BufferedReader reader = null; - try { - reader = new BufferedReader(new FileReader(file)); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return new Word[0]; - } - - String line = null; - try { - while((line = reader.readLine()) != null){ - String[] values = line.split(","); - if(values.length == 2){ - words.add( new Word(values[0].trim(), Float.parseFloat(values[1].trim()))); - } - } - } catch (IOException e) { - e.printStackTrace(); - return new Word[0]; - } - - return words.toArray(new Word[words.size()]); - } -} diff --git a/src/TagCloud.java b/src/TagCloud.java index 6e35f9a..3b8bb9e 100644 --- a/src/TagCloud.java +++ b/src/TagCloud.java @@ -1,9 +1,21 @@ import processing.core.PApplet; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * Simple application wrapper to run the TagCloudGenerator from command line. + * Please provide a config.properties for configuration. + */ public class TagCloud { - public static void main(String[] args){ - Configuration.getInstance().load(); - PApplet.main(new String[] { "--present", "Tree" }); + public static void main(String[] args) { + try { + Configuration.getInstance().load(new FileInputStream("config.properties")); + } catch (IOException e) { + e.printStackTrace(); + return; + } + PApplet.main(new String[]{"--present", "TagCloudGenerator"}); } } diff --git a/src/TagCloudColorer.java b/src/TagCloudColorer.java new file mode 100644 index 0000000..0f5839c --- /dev/null +++ b/src/TagCloudColorer.java @@ -0,0 +1,42 @@ +import wordcram.Word; +import wordcram.WordColorer; + +import java.util.ArrayList; +import java.util.Random; + +/** + * Colors a tag cloud instance by using a random color per word, from a CSV list of + * HEX string colors. + */ +public class TagCloudColorer implements WordColorer { + + private ArrayList colors = new ArrayList(); + private Random randomGenerator = new Random(); + + public static final int FALLBACK_COLOR = ColorHelper.decodeHex("#000000"); + public static final String SEPARATOR = ","; + + /** + * Initializes a instance with CSV of HEX color strings. + * Will use FALLBACK_COLOR as default, if no color could be parsed. + * + * @param colorString A CSV of HEX color string, e.g. "#000000,#111111,#222222" + */ + public TagCloudColorer(String colorString) { + loadColorString(colorString); + if (colors.isEmpty()) { + colors.add(FALLBACK_COLOR); + } + } + + @Override + public int colorFor(Word word) { + return colors.get(randomGenerator.nextInt(colors.size())); + } + + private void loadColorString(String colorString) { + for (String c : colorString.split(SEPARATOR)) { + colors.add(ColorHelper.decodeHex(c)); + } + } +} diff --git a/src/TagCloudGenerator.java b/src/TagCloudGenerator.java new file mode 100644 index 0000000..8716be4 --- /dev/null +++ b/src/TagCloudGenerator.java @@ -0,0 +1,80 @@ +import processing.core.*; +import wordcram.WordCram; + +import java.awt.*; + +import wordcram.*; + +/** + * Generate a TagCloud image of a text file and a shape image. + *

+ * Uses WordsReader to parse the text file. + * The shape image should have a white (#FFFFFF) background and a black (#000000) foreground. + *

+ * The draw method is called by the main Processing application. As this generator mustn't show more than one + * frame, it save the image and exists the main application after the first drawing. + *

+ * Images are saved in TIFF, TARGA, JPEG, and PNG format depending on the extension within the config output file name. + */ +public class TagCloudGenerator extends PApplet { + + private WordCram cram; + + /** + * Called before the first draw from the processing main application + */ + public void setup() { + Configuration config = Configuration.getInstance(); + size(config.getWidth(), config.getHeight()); + background(ColorHelper.decodeHex(config.getBackgroundColor())); + + TagCloudPlacer placer = TagCloudPlacer.fromFile(config.getShapeFile(), Color.black); + WordsReader input = new WordsReader(config.getInputFile()); + Word[] words = input.getWords(); + TagCloudColorer colorer = new TagCloudColorer(config.getColors()); + TagCloudSizer sizer = new TagCloudSizer(config.getMinSize(), config.getMaxSize(), words); + + cram = new WordCram(this) + .fromWords(words) + .withColorer(colorer) + .withPlacer(placer) + .withNudger(placer) + .withSizer(sizer); + } + + /** + * Called repeatedly by the main processing application. As only one render run is needed + * to generate the image, this function saves the generated image and + * exists after the first frame. + */ + public void draw() { + cram.drawAll(); + + if (Configuration.getInstance().isDebug()) { + printDebug(); + } + + save(Configuration.getInstance().getOutputFile()); + exit(); + } + + private void printDebug() { + int skippedBecauseOfNoSpace = 0; + int skippedBecauseOfTooSmall = 0; + Word[] skippedWords = cram.getSkippedWords(); + Word[] placedWords = cram.getWords(); + for (Word skipped : skippedWords) { + if (skipped.wasSkippedBecause() == WordCram.NO_SPACE) { + skippedBecauseOfNoSpace++; + } else if (skipped.wasSkippedBecause() == WordCram.SHAPE_WAS_TOO_SMALL) { + skippedBecauseOfTooSmall++; + } + } + System.out.println("- STATISTICS ---------------------------------------"); + System.out.println("Total placed Words: " + placedWords.length); + System.out.println("Total skipped Words: " + skippedWords.length); + System.out.println("-> because of no space: " + skippedBecauseOfNoSpace); + System.out.println("-> because of too small: " + skippedBecauseOfTooSmall); + System.out.println("----------------------------------------------------"); + } +} diff --git a/src/ShapeBasedPlacer.java b/src/TagCloudPlacer.java similarity index 91% rename from src/ShapeBasedPlacer.java rename to src/TagCloudPlacer.java index 10795e3..43e2ce6 100644 --- a/src/ShapeBasedPlacer.java +++ b/src/TagCloudPlacer.java @@ -21,7 +21,7 @@ import processing.core.PVector; import wordcram.Word; import wordcram.WordNudger; import wordcram.WordPlacer; -class ShapeBasedPlacer implements WordPlacer, WordNudger { +class TagCloudPlacer implements WordPlacer, WordNudger { public static int TOLERANCE = 5; public static boolean PRECISE = false; @@ -36,7 +36,7 @@ class ShapeBasedPlacer implements WordPlacer, WordNudger { Random random; BufferedImage image = null; - public ShapeBasedPlacer(Shape shape) { + public TagCloudPlacer(Shape shape) { this.area = new Area(shape); init(); } @@ -50,7 +50,7 @@ class ShapeBasedPlacer implements WordPlacer, WordNudger { this.maxY = (float) areaBounds.getMaxY(); } - public static ShapeBasedPlacer fromTextGlyphs(String text, String fontName) { + public static TagCloudPlacer fromTextGlyphs(String text, String fontName) { Font font = null; Graphics2D g2d; FontRenderContext frc; @@ -60,21 +60,21 @@ class ShapeBasedPlacer implements WordPlacer, WordNudger { g2d = img.createGraphics(); frc = g2d.getFontRenderContext(); gv = font.createGlyphVector(frc, text); - return new ShapeBasedPlacer(gv.getOutline(GLYPH_SIZE / 10,GLYPH_SIZE)); + return new TagCloudPlacer(gv.getOutline(GLYPH_SIZE / 10,GLYPH_SIZE)); } - private ShapeBasedPlacer(BufferedImage image) { + private TagCloudPlacer(BufferedImage image) { this.image = image; } - public static ShapeBasedPlacer fromFile(String path, Color color) { + public static TagCloudPlacer fromFile(String path, Color color) { BufferedImage image = null; try { image = ImageIO.read(new File(path)); } catch (IOException e) { e.printStackTrace(); } - ShapeBasedPlacer result = new ShapeBasedPlacer(image); + TagCloudPlacer result = new TagCloudPlacer(image); if (PRECISE) { result.fromImagePrecise(color); } else { diff --git a/src/TagCloudSizer.java b/src/TagCloudSizer.java new file mode 100644 index 0000000..111554f --- /dev/null +++ b/src/TagCloudSizer.java @@ -0,0 +1,63 @@ +import processing.core.PApplet; +import wordcram.Word; + +/** + * Determines the size of words in a TagCloud instance. + * The size will be determined as following: + * A) All words have the same weight: use (maxSize - minSize) / 3 + * B) Words have different weights: use a linear interpolation between [minSize, maxSize] for [0,1] + *

+ * Hint: WordCramp normalizes after loading all word the word weight's to [0,1] + */ +public class TagCloudSizer implements wordcram.WordSizer { + + private boolean allEqual = true; + private float minSize; + private float maxSize; + + public static final float DEFAULT_TOLERANCE = 0.1f; + + /** + * Initialize an instance for minSize to maxSize for a given array of words. + * Determines if all words have equal weight using the DEFAULT_TOLERANCE + * + * @param minSize minimum text size + * @param maxSize maximum text size + * @param words the list of words + */ + public TagCloudSizer(float minSize, float maxSize, Word[] words) { + this(minSize, maxSize, words, DEFAULT_TOLERANCE); + } + + /** + * Initialize an instance for minSize to maxSize for a given array of words. + * Determines if all words have equal weight using the tolerance + * + * @param minSize minimum text size + * @param maxSize maximum text size + * @param words the list of words + * @param tolerance the maximum difference beween weight to be equal + */ + public TagCloudSizer(float minSize, float maxSize, Word[] words, float tolerance) { + this.minSize = minSize; + this.maxSize = maxSize; + if (words.length > 0) { + float initial = words[0].weight; + for (Word w : words) { + if (Math.abs(w.weight - initial) > tolerance) { + allEqual = false; + break; + } + } + } + } + + @Override + public float sizeFor(Word word, int wordRank, int wordCount) { + if (allEqual) { + return (maxSize - minSize) / 3.0f; + } else { + return PApplet.lerp(minSize, maxSize, word.weight); + } + } +} diff --git a/src/Tree.java b/src/Tree.java deleted file mode 100644 index 0fed92f..0000000 --- a/src/Tree.java +++ /dev/null @@ -1,73 +0,0 @@ -import processing.core.*; -import wordcram.WordCram; - -import java.awt.*; -import java.awt.image.PackedColorModel; -import java.io.*; -import java.util.ArrayList; - -import wordcram.*; -import wordcram.text.TextSource; - -public class Tree extends PApplet { - - private WordCram cram; - private boolean draw = true; - - public void setup(){ - Configuration config = Configuration.getInstance(); - size(config.getWidth(),config.getHeight()); - background(ColorHelper.decode(config.getBackgroundColor())); - - ShapeBasedPlacer placer = ShapeBasedPlacer.fromFile(config.getShapeFile(), Color.black); - InputWords input = new InputWords(config.getInputFile()); - Word[] words = input.getWords(); - TreeColorer colorer = new TreeColorer(config.getColors()); - WordSizer sizer = new WordSizer(config.getMinSize(), config.getMaxSize(), words); - - cram = new WordCram(this) - .fromWords(words) - .withColorer(colorer) - .withPlacer(placer) - .withNudger(placer) - .withSizer(sizer); - } - - - - public void draw() { - if(this.draw){ - System.out.println("Start drawing tag cloud..."); - cram.drawAll(); - this.draw = false; - System.out.println("Finished drawing"); - - if(Configuration.getInstance().isDebug()){ - printDebug(); - } - - save(Configuration.getInstance().getOutputFile()); - exit(); - } - } - - private void printDebug(){ - //tell me what didn’t get drawn - int noSpace = 0; - int tooSmall = 0; - Word[] skippedWords = cram.getSkippedWords(); - Word[] placedWords = cram.getWords(); - for (Word skipped: skippedWords) { - if (skipped.wasSkippedBecause() == WordCram.NO_SPACE) { - noSpace++; - } else if (skipped.wasSkippedBecause() == WordCram.SHAPE_WAS_TOO_SMALL) { - tooSmall++; - } - } - System.out.println("Total placed Words: " + placedWords.length); - System.out.println("Total Skipped Words: " + skippedWords.length); - System.out.println("Skipped because no Space: " + noSpace); - System.out.println("Skipped because too small: " + tooSmall); - System.out.println("Finished"); - } -} diff --git a/src/TreeColorer.java b/src/TreeColorer.java deleted file mode 100644 index 38c5919..0000000 --- a/src/TreeColorer.java +++ /dev/null @@ -1,26 +0,0 @@ -import wordcram.Word; -import wordcram.WordColorer; - -import java.awt.*; -import java.util.ArrayList; -import java.util.Random; - -public class TreeColorer implements WordColorer { - - private ArrayList colors = new ArrayList(); - private Random randomGenerator = new Random(); - - public TreeColorer(String colorString){ - for(String c : colorString.split(",")){ - colors.add(ColorHelper.decode(c)); - } - if(colors.isEmpty()){ - colors.add(ColorHelper.decode("#000000")); - } - } - - @Override - public int colorFor(Word word) { - return colors.get(randomGenerator.nextInt(colors.size())); - } -} diff --git a/src/WordSizer.java b/src/WordSizer.java deleted file mode 100644 index b03ab3f..0000000 --- a/src/WordSizer.java +++ /dev/null @@ -1,33 +0,0 @@ -import processing.core.PApplet; -import wordcram.Word; - -public class WordSizer implements wordcram.WordSizer{ - - private boolean allEqual = true; - private float minSize = 4; - private float maxSize = 38; - - public WordSizer(float minSize, float maxSize, Word[] words){ - this.minSize = minSize; - this.maxSize = maxSize; - if(words.length > 0){ - float initial = words[0].weight; - for(Word w : words){ - if(Math.abs(w.weight - initial) > 0.1){ - allEqual = false; - break; - } - } - } - } - - @Override - public float sizeFor(Word word, int wordRank, int wordCount) { - if(allEqual){ - return (maxSize - minSize) / 3.0f; - } - else{ - return PApplet.lerp(minSize, maxSize, word.weight); - } - } -} diff --git a/src/WordsReader.java b/src/WordsReader.java new file mode 100644 index 0000000..02d6fd7 --- /dev/null +++ b/src/WordsReader.java @@ -0,0 +1,69 @@ +import wordcram.Word; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; + +/** + * Helper to read list of Words with initial weights from a simple Textfile. + * Supported text format: + * WORD1,WEIGHT1 + * WORD2,WEIGHT2 + * + * WORD must not contain a comma, but can include a space character. + * WEIGHT will be parsed as a Float + */ +public class WordsReader { + private String file; + public static final String SEPARATOR = ","; + + /** + * Constructs a reader + * @param file to be parsed + */ + public WordsReader(String file){ + this.file = file; + } + + /** + * Read the file and parse a list of words. + * @return an array of weighted words. + */ + public Word[] getWords(){ + ArrayList words = new ArrayList(); + + BufferedReader reader; + try { + reader = new BufferedReader(new FileReader(file)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return new Word[0]; + } + + String line; + try { + while((line = reader.readLine()) != null){ + Word word = parse(line); + if(word != null) + words.add(word); + } + } catch (IOException e) { + e.printStackTrace(); + return new Word[0]; + } + + return words.toArray(new Word[words.size()]); + } + + private Word parse(String line){ + String[] values = line.split(SEPARATOR); + if(values.length == 2){ + return new Word(values[0].trim(), Float.parseFloat(values[1].trim())); + } + else { + return null; + } + } +}