Browse files

adding original code

  • Loading branch information...
0 parents commit 2cdf0ffb252368fe0b44b7cc9adde3505c14768a Lee Byron committed Mar 30, 2010
BIN .DS_Store
Binary file not shown.
24 BasicLateOnsetSort.java
@@ -0,0 +1,24 @@
+import java.util.*;
+
+/**
+ * BasicLateOnsetSort
+ * Sorts by onset, but does not partition to the outsides of the graph in
+ * order to illustrate short-sighted errors found during design process.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class BasicLateOnsetSort extends LayerSort {
+
+ public String getName() {
+ return "Late Onset Sorting, Top to Bottom";
+ }
+
+ public Layer[] sort(Layer[] layers) {
+ // first sort by onset
+ Arrays.sort(layers, new OnsetComparator(true));
+
+ return layers;
+ }
+
+}
58 BelievableDataSource.java
@@ -0,0 +1,58 @@
+import java.util.*;
+
+/**
+ * BelievableDataSource
+ * Create test data for layout engine.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class BelievableDataSource implements DataSource {
+
+ public Random rnd;
+
+ public BelievableDataSource() {
+ // seeded, so we can reproduce results
+ this(2);
+ }
+
+ public BelievableDataSource(int seed) {
+ rnd = new Random(seed);
+ }
+
+ public Layer[] make(int numLayers, int sizeArrayLength) {
+ Layer[] layers = new Layer[numLayers];
+
+ for (int i = 0; i < numLayers; i++) {
+ String name = "Layer #" + i;
+ float[] size = new float[sizeArrayLength];
+ size = makeRandomArray(sizeArrayLength);
+ layers[i] = new Layer(name, size);
+ }
+
+ return layers;
+ }
+
+ protected float[] makeRandomArray(int n) {
+ float[] x = new float[n];
+
+ // add a handful of random bumps
+ for (int i=0; i<5; i++) {
+ addRandomBump(x);
+ }
+
+ return x;
+ }
+
+ protected void addRandomBump(float[] x) {
+ float height = 1 / rnd.nextFloat();
+ float cx = (float)(2 * rnd.nextFloat() - 0.5);
+ float r = rnd.nextFloat() / 10;
+
+ for (int i = 0; i < x.length; i++) {
+ float a = (i / (float)x.length - cx) / r;
+ x[i] += height * Math.exp(-a * a);
+ }
+ }
+
+}
23 COPYRIGHT
@@ -0,0 +1,23 @@
+Copyright (c) 2008, Lee Byron, Martin Wattenberg
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * The names of the contributors may NOT be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL LEE BYRON BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14 ColorPicker.java
@@ -0,0 +1,14 @@
+/**
+ * ColorPicker
+ * Interface for new coloring algorithms.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public interface ColorPicker {
+
+ public void colorize(Layer[] layers);
+
+ public String getName();
+
+}
12 DataSource.java
@@ -0,0 +1,12 @@
+/**
+ * DataSource
+ * Interface for creating a data source
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public interface DataSource {
+
+ public Layer[] make(int numLayers, int sizeArrayLength);
+
+}
25 InverseVolatilitySort.java
@@ -0,0 +1,25 @@
+import java.util.*;
+
+/**
+ * InverseVolatilitySort
+ * Sorts an array of layers by their volatility, placing the most volatile
+ * layers along the insides of the graph, illustrating how disruptive this
+ * volatility can be to a stacked graph.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class InverseVolatilitySort extends LayerSort {
+
+ public String getName() {
+ return "Inverse Volatility Sorting, Evenly Weighted";
+ }
+
+ public Layer[] sort(Layer[] layers) {
+ // first sort by volatility
+ Arrays.sort(layers, new VolatilityComparator(false));
+
+ return orderToOutside(layers);
+ }
+
+}
53 LastFMColorPicker.java
@@ -0,0 +1,53 @@
+import processing.core.*;
+
+/**
+ * LastFMColorPicker
+ * Loads in an image and uses it as a two-dimensional gradient
+ * Supply two [0,1) numbers and get the color of the gradient at that point
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class LastFMColorPicker implements ColorPicker {
+
+ public PImage source;
+
+ public LastFMColorPicker(PApplet parent, String src) {
+ source = parent.loadImage(src);
+ }
+
+ public String getName() {
+ return "Listening History Color Scheme";
+ }
+
+ public void colorize(Layer[] layers) {
+ // find the largest layer to use as a normalizer
+ float maxSum = 0;
+ for (int i=0; i<layers.length; i++) {
+ maxSum = (float) Math.max(maxSum, layers[i].sum);
+ }
+
+ // find the color for each layer
+ for (int i = 0; i < layers.length; i++) {
+ float normalizedOnset = (float)layers[i].onset / layers[i].size.length;
+ float normalizedSum = layers[i].sum / maxSum;
+ float shapedSum = (float)(1.0 - Math.sqrt(normalizedSum));
+
+ layers[i].rgb = get(normalizedOnset, shapedSum);
+ }
+ }
+
+ protected int get(float g1, float g2) {
+ // get pixel coordinate based on provided parameters
+ int x = PApplet.floor(g1 * source.width);
+ int y = PApplet.floor(g2 * source.height);
+
+ // ensure that the pixel is within bounds.
+ x = PApplet.constrain(x, 0, source.width - 1);
+ y = PApplet.constrain(y, 0, source.height - 1);
+
+ // return the color at the requested pixel
+ return source.pixels[x + y * source.width];
+ }
+
+}
61 LateOnsetDataSource.java
@@ -0,0 +1,61 @@
+import java.util.*;
+
+/**
+ * LateOnsetData
+ * Creates false data which resembles late onset time-series.
+ * Such as band popularity or movie box-office income.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class LateOnsetDataSource implements DataSource {
+
+ public Random rnd;
+
+ public LateOnsetDataSource() {
+ // seeded, so we can reproduce results
+ this(2);
+ }
+
+ public LateOnsetDataSource(int seed) {
+ rnd = new Random(seed);
+ }
+
+ public Layer[] make(int numLayers, int sizeArrayLength) {
+ Layer[] layers = new Layer[numLayers];
+
+ for (int i = 0; i < numLayers; i++) {
+ String name = "Layer #" + i;
+ int onset = (int)(sizeArrayLength * (rnd.nextFloat() * 1.25 - 0.25));
+ int duration = (int)(rnd.nextFloat() * 0.75 * sizeArrayLength);
+ float[] size = new float[sizeArrayLength];
+ size = makeRandomArray(sizeArrayLength, onset, duration);
+ layers[i] = new Layer(name, size);
+ }
+
+ return layers;
+ }
+
+ protected float[] makeRandomArray(int n, int onset, int duration) {
+ float[] x = new float[n];
+
+ // add a single random bump
+ addRandomBump(x, onset, duration);
+
+ return x;
+ }
+
+ protected void addRandomBump(float[] x, int onset, int duration) {
+ float height = rnd.nextFloat();
+ int start = Math.max(0, onset);
+ int end = Math.min(x.length, onset + duration);
+ int len = end - onset;
+
+ for (int i = start; i < x.length && i < onset + duration; i++) {
+ float xx = (float)(i - onset) / duration;
+ float yy = (float)(xx * Math.exp(-10 * xx));
+ x[i] += height * yy;
+ }
+ }
+
+}
26 LateOnsetSort.java
@@ -0,0 +1,26 @@
+import java.util.*;
+
+/**
+ * LateOnsetSort
+ * Sorts by onset, and orders to the outsides of the graph.
+ *
+ * This is the sort technique preferred when using late-onset data, which the
+ * Streamgraph technique is best suited to represent
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class LateOnsetSort extends LayerSort {
+
+ public String getName() {
+ return "Late Onset Sorting, Evenly Weighted";
+ }
+
+ public Layer[] sort(Layer[] layers) {
+ // first sort by onset
+ Arrays.sort(layers, new OnsetComparator(true));
+
+ return orderToOutside(layers);
+ }
+
+}
63 Layer.java
@@ -0,0 +1,63 @@
+/**
+ * Layer
+ * Represents a layer in a layered graph, maintaining properties which
+ * define it's position, size, color and mathemetical characteristics
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class Layer {
+
+ public String name;
+ public float[] size;
+ public float[] yBottom;
+ public float[] yTop;
+ public int rgb;
+ public int onset;
+ public int end;
+ public float sum;
+ public float volatility;
+
+ public Layer(String name, float[] size) {
+
+ // check for reasonable data
+ for (int i = 0; i < size.length; i++) {
+ if (size[i] < 0) {
+ throw new IllegalArgumentException("No negative sizes allowed.");
+ }
+ }
+
+ this.name = name;
+ this.size = size;
+ yBottom = new float[size.length];
+ yTop = new float[size.length];
+ sum = 0;
+ volatility = 0;
+ onset = -1;
+
+ for (int i = 0; i < size.length; i++) {
+
+ // sum is the summation of all points
+ sum += size[i];
+
+ // onset is the first non-zero point
+ // end is the last non-zero point
+ if (size[i] > 0) {
+ if (onset == -1) {
+ onset = i;
+ } else {
+ end = i;
+ }
+ }
+
+ // volatility is the maximum change between any two consecutive points
+ if (i > 0) {
+ volatility = Math.max(
+ volatility,
+ Math.abs(size[i] - size[i-1])
+ );
+ }
+ }
+ }
+
+}
33 LayerLayout.java
@@ -0,0 +1,33 @@
+/**
+ * LayerLayout
+ * Abstract Class for new stacked graph layout algorithms
+ *
+ * Note: you do not need to worry about scaling to screen dimensions.
+ * The display applet will do that automatically for you.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public abstract class LayerLayout {
+
+ abstract void layout(Layer[] layers);
+
+ abstract String getName();
+
+ /**
+ * We define our stacked graphs by layers atop a baseline.
+ * This method does the work of assigning the positions of each layer in an
+ * ordered array of layers based on an initial baseline.
+ */
+ protected void stackOnBaseline(Layer[] layers, float[] baseline) {
+ // Put layers on top of the baseline.
+ for (int i = 0; i < layers.length; i++) {
+ System.arraycopy(baseline, 0, layers[i].yBottom, 0, baseline.length);
+ for (int j = 0; j < baseline.length; j++) {
+ baseline[j] -= layers[i].size[j];
+ }
+ System.arraycopy(baseline, 0, layers[i].yTop, 0, baseline.length);
+ }
+ }
+
+}
54 LayerSort.java
@@ -0,0 +1,54 @@
+/**
+ * LayerSort
+ * Interface to sorting layers
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public abstract class LayerSort {
+
+ abstract String getName();
+
+ abstract Layer[] sort(Layer[] layers);
+
+ /**
+ * Creates a 'top' and 'bottom' collection.
+ * Iterating through the previously sorted list of layers, place each layer
+ * in whichever collection has less total mass, arriving at an evenly
+ * weighted graph. Reassemble such that the layers that appeared earliest
+ * end up in the 'center' of the graph.
+ */
+ protected Layer[] orderToOutside(Layer[] layers) {
+ int j = 0;
+ int n = layers.length;
+ Layer[] newLayers = new Layer[n];
+ int topCount = 0;
+ float topSum = 0;
+ int[] topList = new int[n];
+ int botCount = 0;
+ float botSum = 0;
+ int[] botList = new int[n];
+
+ // partition to top or bottom containers
+ for (int i=0; i<n; i++) {
+ if (topSum < botSum) {
+ topList[topCount++] = i;
+ topSum += layers[i].sum;
+ } else {
+ botList[botCount++] = i;
+ botSum += layers[i].sum;
+ }
+ }
+
+ // reassemble into single array
+ for (int i = botCount - 1; i >= 0; i--) {
+ newLayers[j++] = layers[botList[i]];
+ }
+ for (int i = 0; i < topCount; i++) {
+ newLayers[j++] = layers[topList[i]];
+ }
+
+ return newLayers;
+ }
+
+}
34 MinimizedWiggleLayout.java
@@ -0,0 +1,34 @@
+/**
+ * MinimizedWiggleLayout
+ * Minimizes the sum of squares of the layer slopes at each value
+ *
+ * We present this as a reasonable alternative to the Stream Graph for
+ * real-time use. While it has some drawbacks compared to StreamLayout, it is
+ * much faster to execute and is reasonable for real-time applications.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class MinimizedWiggleLayout extends LayerLayout {
+
+ public String getName() {
+ return "Minimized Wiggle Layout";
+ }
+
+ public void layout(Layer[] layers) {
+ int n = layers[0].size.length;
+ int m = layers.length;
+ float[] baseline = new float[n];
+
+ // Set shape of baseline values.
+ for (int i = 0; i < n; i++) {
+ for (int j = 0; j < m; j++) {
+ baseline[i] += (m - j - 0.5) * layers[j].size[i];
+ }
+ baseline[i] /= m;
+ }
+
+ // Put layers on top of the baseline.
+ stackOnBaseline(layers, baseline);
+ }
+}
18 NoLayerSort.java
@@ -0,0 +1,18 @@
+/**
+ * NoLayerSort
+ * Does no sorting. Identity function.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class NoLayerSort extends LayerSort {
+
+ public String getName() {
+ return "No Sorting";
+ }
+
+ public Layer[] sort(Layer[] layers) {
+ return layers;
+ }
+
+}
30 OnsetComparator.java
@@ -0,0 +1,30 @@
+import java.util.*;
+
+/**
+ * OnsetSort
+ * Compares two Layers based on their onset
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class OnsetComparator implements Comparator {
+
+ public boolean ascending;
+
+ public OnsetComparator(boolean ascending) {
+ this.ascending = ascending;
+ }
+
+ public int compare(Object p, Object q){
+ Layer pL = (Layer)p;
+ Layer qL = (Layer)q;
+ return (ascending ? 1 : -1) * (pL.onset - qL.onset);
+ }
+
+ public boolean equals(Object p, Object q){
+ Layer pL = (Layer)p;
+ Layer qL = (Layer)q;
+ return pL.onset == qL.onset;
+ }
+
+}
6 README
@@ -0,0 +1,6 @@
+This is the processing application used to generate the images in the paper:
+Stacked Graphs - Geometry & Aesthetics
+
+It is published here as an educational library, to provide code examples to support the paper.
+
+This code is copyright under the BSD license.
100 RandomColorPicker.java
@@ -0,0 +1,100 @@
+import processing.core.*;
+import java.util.*;
+
+/**
+ * RandomColorPicker
+ * Chooses random colors within an acceptable HSB color spectrum
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class RandomColorPicker implements ColorPicker {
+
+ public Random rnd;
+ public PApplet parent;
+
+ public RandomColorPicker(PApplet parent) {
+ this(parent, 2);
+ }
+
+ public RandomColorPicker(PApplet parent, int seed) {
+ this.parent = parent;
+ parent.colorMode(PApplet.RGB, 255);
+
+ // seeded, so we can reproduce results
+ rnd = new Random(seed);
+ }
+
+ public String getName() {
+ return "Random Colors";
+ }
+
+ public void colorize(Layer[] layers) {
+ for (int i = 0; i < layers.length; i++) {
+ float h = PApplet.lerp(0.6f, 0.65f, rnd.nextFloat());
+ float s = PApplet.lerp(0.2f, 0.25f, rnd.nextFloat());
+ float b = PApplet.lerp(0.4f, 0.95f, rnd.nextFloat());
+
+ layers[i].rgb = hsb2rgb(h, s, b);
+ }
+ }
+
+ protected int hsb2rgb(float x, float y, float z) {
+ float calcR = 0;
+ float calcG = 0;
+ float calcB = 0;
+ float calcA = 1;
+
+ if (y == 0) { // saturation == 0
+ calcR = calcG = calcB = z;
+ } else {
+ float which = (x - (int)x) * 6.0f;
+ float f = which - (int)which;
+ float p = z * (1.0f - y);
+ float q = z * (1.0f - y * f);
+ float t = z * (1.0f - (y * (1.0f - f)));
+
+ switch ((int)which) {
+ case 0:
+ calcR = z;
+ calcG = t;
+ calcB = p;
+ break;
+ case 1:
+ calcR = q;
+ calcG = z;
+ calcB = p;
+ break;
+ case 2:
+ calcR = p;
+ calcG = z;
+ calcB = t;
+ break;
+ case 3:
+ calcR = p;
+ calcG = q;
+ calcB = z;
+ break;
+ case 4:
+ calcR = t;
+ calcG = p;
+ calcB = z;
+ break;
+ case 5:
+ calcR = z;
+ calcG = p;
+ calcB = q;
+ break;
+ }
+ }
+
+ int calcRi = (int)(255 * calcR);
+ int calcGi = (int)(255 * calcG);
+ int calcBi = (int)(255 * calcB);
+ int calcAi = (int)(255 * calcA);
+ int calcColor = (calcAi << 24) | (calcRi << 16) | (calcGi << 8) | calcBi;
+
+ return calcColor;
+ }
+
+}
27 StackLayout.java
@@ -0,0 +1,27 @@
+import java.util.*;
+
+/**
+ * StackLayout
+ * Standard stacked graph layout, with a straight baseline
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class StackLayout extends LayerLayout {
+
+ public String getName() {
+ return "Stacked Layout";
+ }
+
+ public void layout(Layer[] layers) {
+ int n = layers[0].size.length;
+
+ // lay out layers, top to bottom.
+ float[] baseline = new float[n];
+ Arrays.fill(baseline, 0);
+
+ // Put layers on top of the baseline.
+ stackOnBaseline(layers, baseline);
+ }
+
+}
63 StreamLayout.java
@@ -0,0 +1,63 @@
+/**
+ * StreamLayout
+ * The layout used in the Streamgraph stacked graph
+ *
+ * Because this layout is using numeric integration, it is likely insufficient
+ * for real-time display, especially for larger data sets.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class StreamLayout extends LayerLayout {
+
+ public String getName() {
+ return "Original Streamgraph Layout";
+ }
+
+ public void layout(Layer[] layers) {
+ int n = layers[0].size.length;
+ int m = layers.length;
+ float[] baseline = new float[n];
+ float[] center = new float[n];
+ float totalSize;
+ float moveUp;
+ float increase;
+ float belowSize;
+
+ // Set shape of baseline values.
+ for (int i = 0; i < n; i++) {
+ // the 'center' is a rolling point. It is initialized as the previous
+ // iteration's center value
+ center[i] = i == 0 ? 0 : center[i-1];
+
+ // find the total size of all layers at this point
+ totalSize = 0;
+ for (int j = 0; j < m; j++) {
+ totalSize += layers[j].size[i];
+ }
+
+ // account for the change of every layer to offset the center point
+ for (int j = 0; j < m; j++) {
+ if (i == 0) {
+ increase = layers[j].size[i];
+ moveUp = 0.5f;
+ } else {
+ belowSize = 0.5f * layers[j].size[i];
+ for (int k = j + 1; k < m; k++) {
+ belowSize += layers[k].size[i];
+ }
+ increase = layers[j].size[i] - layers[j].size[i - 1];
+ moveUp = totalSize == 0 ? 0 : (belowSize / totalSize);
+ }
+ center[i] += (moveUp - 0.5) * increase;
+ }
+
+ // set baseline to the bottom edge according to the center line
+ baseline[i] = center[i] + 0.5f * totalSize;
+ }
+
+ // Put layers on top of the baseline.
+ stackOnBaseline(layers, baseline);
+ }
+
+}
33 ThemeRiverLayout.java
@@ -0,0 +1,33 @@
+/**
+ * ThemeRiverLayout
+ * Layout used by the authors of the ThemeRiver paper
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class ThemeRiverLayout extends LayerLayout {
+
+ public String getName() {
+ return "ThemeRiver";
+ }
+
+ public void layout(Layer[] layers) {
+ // Set shape of baseline values.
+ int n=layers[0].size.length;
+ int m=layers.length;
+ float[] baseline = new float[n];
+
+ // ThemeRiver is perfectly symmetrical
+ // the baseline is 1/2 of the total height at any point
+ for (int i = 0; i < n; i++) {
+ baseline[i] = 0;
+ for (int j = 0; j < m; j++) {
+ baseline[i] += layers[j].size[i];
+ }
+ baseline[i] *= 0.5;
+ }
+
+ // Put layers on top of the baseline.
+ stackOnBaseline(layers, baseline);
+ }
+}
31 VolatilityComparator.java
@@ -0,0 +1,31 @@
+import java.util.*;
+
+/**
+ * VolatilityComparator
+ * Compares two Layers based on their volatility
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class VolatilityComparator implements Comparator {
+
+ public boolean ascending;
+
+ public VolatilityComparator(boolean ascending) {
+ this.ascending = ascending;
+ }
+
+ public int compare(Object p, Object q) {
+ Layer pL = (Layer)p;
+ Layer qL = (Layer)q;
+ float volatilityDifference = pL.volatility - qL.volatility;
+ return (ascending ? 1 : -1) * (int)(10000000 * volatilityDifference);
+ }
+
+ public boolean equals(Object p, Object q) {
+ Layer pL = (Layer)p;
+ Layer qL = (Layer)q;
+ return pL.volatility == qL.volatility;
+ }
+
+}
29 VolatilitySort.java
@@ -0,0 +1,29 @@
+import java.util.*;
+
+/**
+ * VolatilitySort
+ * Sorts an array of layers by their volatility, placing the most volatile
+ * layers along the outsides of the graph, thus minimizing unneccessary
+ * distortion.
+ *
+ * First sort by volatility, then creates a 'top' and 'bottom' collection.
+ * Iterating through the sorted list of layers, place each layer in whichever
+ * collection has less total mass, arriving at an evenly weighted graph.
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+public class VolatilitySort extends LayerSort {
+
+ public String getName() {
+ return "Volatility Sorting, Evenly Weighted";
+ }
+
+ public Layer[] sort(Layer[] layers) {
+ // first sort by volatility
+ Arrays.sort(layers, new VolatilityComparator(true));
+
+ return orderToOutside(layers);
+ }
+
+}
BIN data/layers-nyt.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN data/layers.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/example-00-standard.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/example-01-themeriver.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/example-02-layout.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/example-03-color.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/example-04-ordering.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/example-05-nytcoloring.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/example2-00-presort.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN images/example2-01-postsort.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
186 streamgraph_generator.pde
@@ -0,0 +1,186 @@
+/**
+ * streamgraph_generator
+ * Processing Sketch
+ * Explores different stacked graph layout, ordering and coloring methods
+ * Used to generate example graphics for the Streamgraph paper
+ *
+ * Press Enter to save image
+ *
+ * @author Lee Byron
+ * @author Martin Wattenberg
+ */
+
+boolean isGraphCurved = true; // catmull-rom interpolation
+int seed = 28; // random seed
+
+float DPI = 300;
+float widthInches = 3.5;
+float heightInches = 0.7;
+int numLayers = 50;
+int layerSize = 100;
+
+DataSource data;
+LayerLayout layout;
+LayerSort ordering;
+ColorPicker coloring;
+
+Layer[] layers;
+
+void setup() {
+
+ size(int(widthInches*DPI), int(heightInches*DPI));
+ smooth();
+ noLoop();
+
+ // GENERATE DATA
+ data = new LateOnsetDataSource();
+ //data = new BelievableDataSource();
+
+ // ORDER DATA
+ ordering = new LateOnsetSort();
+ //ordering = new VolatilitySort();
+ //ordering = new InverseVolatilitySort();
+ //ordering = new BasicLateOnsetSort();
+ //ordering = new NoLayerSort();
+
+ // LAYOUT DATA
+ layout = new StreamLayout();
+ //layout = new MinimizedWiggleLayout();
+ //layout = new ThemeRiverLayout();
+ //layout = new StackLayout();
+
+ // COLOR DATA
+ coloring = new LastFMColorPicker(this, "layers-nyt.jpg");
+ //coloring = new LastFMColorPicker(this, "layers.jpg");
+ //coloring = new RandomColorPicker(this);
+
+ //=========================================================================
+
+ // calculate time to generate graph
+ long time = System.currentTimeMillis();
+
+ // generate graph
+ layers = data.make(numLayers, layerSize);
+ layers = ordering.sort(layers);
+ layout.layout(layers);
+ coloring.colorize(layers);
+
+ // fit graph to viewport
+ scaleLayers(layers, 1, height - 1);
+
+ // give report
+ long layoutTime = System.currentTimeMillis()-time;
+ int numLayers = layers.length;
+ int layerSize = layers[0].size.length;
+ println("Data has " + numLayers + " layers, each with " +
+ layerSize + " datapoints.");
+ println("Layout Method: " + layout.getName());
+ println("Ordering Method: " + ordering.getName());
+ println("Coloring Method: " + layout.getName());
+ println("Elapsed Time: " + layoutTime + "ms");
+}
+
+// adding a pixel to the top compensate for antialiasing letting
+// background through. This is overlapped by following layers, so no
+// distortion is made to data.
+// detail: a pixel is not added to the top-most layer
+// detail: a shape is only drawn between it's non 0 values
+void draw() {
+
+ int n = layers.length;
+ int m = layers[0].size.length;
+ int start;
+ int end;
+ int lastIndex = m - 1;
+ int lastLayer = n - 1;
+ int pxl;
+
+ background(255);
+ noStroke();
+
+ // calculate time to draw graph
+ long time = System.currentTimeMillis();
+
+ // generate graph
+ for (int i = 0; i < n; i++) {
+ start = max(0, layers[i].onset - 1);
+ end = min(m - 1, layers[i].end);
+ pxl = i == lastLayer ? 0 : 1;
+
+ // set fill color of layer
+ fill(layers[i].rgb);
+
+ // draw shape
+ beginShape();
+
+ // draw top edge, left to right
+ graphVertex(start, layers[i].yTop, isGraphCurved, i == lastLayer);
+ for (int j = start; j <= end; j++) {
+ graphVertex(j, layers[i].yTop, isGraphCurved, i == lastLayer);
+ }
+ graphVertex(end, layers[i].yTop, isGraphCurved, i == lastLayer);
+
+ // draw bottom edge, right to left
+ graphVertex(end, layers[i].yBottom, isGraphCurved, false);
+ for (int j = end; j >= start; j--) {
+ graphVertex(j, layers[i].yBottom, isGraphCurved, false);
+ }
+ graphVertex(start, layers[i].yBottom, isGraphCurved, false);
+
+ endShape(CLOSE);
+ }
+
+ // give report
+ long layoutTime = System.currentTimeMillis() - time;
+ println("Draw Time: " + layoutTime + "ms");
+}
+
+void graphVertex(int point, float[] source, boolean curve, boolean pxl) {
+ float x = map(point, 0, layerSize - 1, 0, width);
+ float y = source[point] - (pxl ? 1 : 0);
+ if (curve) {
+ curveVertex(x, y);
+ } else {
+ vertex(x, y);
+ }
+}
+
+void scaleLayers(Layer[] layers, int screenTop, int screenBottom) {
+ // Figure out max and min values of layers.
+ float min = Float.MAX_VALUE;
+ float max = Float.MIN_VALUE;
+ for (int i = 0; i < layers[0].size.length; i++) {
+ for (int j = 0; j < layers.length; j++) {
+ min = min(min, layers[j].yTop[i]);
+ max = max(max, layers[j].yBottom[i]);
+ }
+ }
+
+ float scale = (screenBottom - screenTop) / (max - min);
+ for (int i = 0; i < layers[0].size.length; i++) {
+ for (int j = 0; j < layers.length; j++) {
+ layers[j].yTop[i] = screenTop + scale * (layers[j].yTop[i] - min);
+ layers[j].yBottom[i] = screenTop + scale * (layers[j].yBottom[i] - min);
+ }
+ }
+}
+
+void keyPressed() {
+ if (keyCode == ENTER) {
+ println();
+ println("Rendering image...");
+ String fileName = "images/streamgraph-" + dateString() + ".png";
+ save(fileName);
+ println("Rendered image to: " + fileName);
+ }
+
+ // hack for un-responsive non looping p5 sketches
+ if (keyCode == ESC) {
+ redraw();
+ }
+}
+
+String dateString() {
+ return year() + "-" + nf(month(), 2) + "-" + nf(day(), 2) + "@" +
+ nf(hour(), 2) + "-" + nf(minute(), 2) + "-" + nf(second(), 2);
+}

0 comments on commit 2cdf0ff

Please sign in to comment.