/*
* Copyright 2008-2013, ETH Zürich, Samuel Welten, Michael Kuhn, Tobias Langner,
* Sandro Affentranger, Lukas Bossard, Michael Grob, Rahul Jain,
* Dominic Langenegger, Sonia Mayor Alonso, Roger Odermatt, Tobias Schlueter,
* Yannick Stucki, Sebastian Wendland, Samuel Zehnder, Samuel Zihlmann,
* Samuel Zweifel
*
* This file is part of Jukefox.
*
* Jukefox is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or any later version. Jukefox is
* distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* Jukefox. If not, see <http://www.gnu.org/licenses/>.
*/
package ch.ethz.dcg.pancho3.view.statistics;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import ch.ethz.dcg.jukefox.model.collection.statistics.IStatisticsData;
import ch.ethz.dcg.pancho3.tablet.widget.FlowLayout;
import ch.ethz.dcg.pancho3.tablet.widget.SmallestAreaTextView;
public class StatisticsTagCloudDisplay implements IStatisticsDisplay {
private StatisticsActivity statisticsActivity;
public StatisticsTagCloudDisplay(StatisticsActivity statisticsActivity) {
super();
this.statisticsActivity = statisticsActivity;
}
/**
* Shows a tag cloud of the data. The font size is calculated as a linear
* distribution over all values. If we are looking at top data, then the
* slope of the distribution is taken from the range [mean, max] with f(max)
* = MAX_FONTSIZE, and f(mean) = MEAN_FONTSIZE. If we are looking at flop
* data ({@link StatisticsRatingListAdapter#inverted} == true), then the
* slope of the distribution is taken from the range [min, mean] with f(min)
* = MAX_FONTSIZE and f(mean) = MEAN_FONTSIZE. The max and min values can
* take arbitrary values but are forced to be at least +-2 to get a more
* accurate display when only little data is available. Note, that if data
* is sorted in descending direction, the biggest fontsize will be assgned
* to the item with the lowest value.<br/>
* <br/>
* The datas values have to be an instance of {@link Float}.
*/
@Override
public <T extends IStatisticsData> void inflate(List<T> data, final OnClickListener onClickListener,
final OnLongClickListener onLongClickListener, int parentId) {
// Set the click listeners
View.OnClickListener viewOnClickListener = null;
if (onClickListener != null) {
viewOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
IStatisticsData item = (IStatisticsData) ((TextView) v).getTag();
onClickListener.onClick(item);
}
};
}
View.OnLongClickListener viewOnLongClickListener = null;
if (onLongClickListener != null) {
viewOnLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
IStatisticsData item = (IStatisticsData) ((TextView) v).getTag();
return onLongClickListener.onLongClick(item);
}
};
}
// Find the data range
float minRating = 0f;
float maxRating = 0f;
float meanRating = 0f;
boolean inverted = false;
if (data.size() > 0) {
minRating = (Float) data.get(data.size() - 1).getValue();
maxRating = (Float) data.get(0).getValue();
for (IStatisticsData item : data) {
meanRating += (Float) item.getValue();
}
meanRating /= data.size();
if (minRating > maxRating) {
inverted = true;
float tmp = minRating;
minRating = maxRating;
maxRating = tmp;
}
if (maxRating > 0) {
maxRating = Math.max(2, maxRating);
}
if (minRating < 0) {
minRating = Math.min(-2, minRating);
}
}
// get a shuffled access strategy to get a more interresting tag cloud
List<Integer> accessStrategy = getShuffledAccessStrategy(data.size());
final byte MIN_FONTSIZE = 8;
final byte MAX_FONTSIZE = 40;
byte meanFontSize = (MAX_FONTSIZE + MIN_FONTSIZE) / 2;
LinearLayout l = (LinearLayout) statisticsActivity.findViewById(parentId);
l.removeAllViews(); // Remove old elements
// Create a scroll container
ScrollView sv = new ScrollView(statisticsActivity);
// TODO: Sämy, add 20px margin at the bottom.
sv.setPadding(0, 0, 0, 80); // To be able to scroll all data above the settings button.
sv.setFadingEdgeLength(0);
// Create the table layout
FlowLayout fl = new FlowLayout(statisticsActivity);
// Create the tag cloud
for (int index : accessStrategy) {
IStatisticsData item = data.get(index);
// Calculate the weight for this item
float fontSize;
float itemRating = (Float) item.getValue();
if (!inverted) {
// Top data
fontSize = (MAX_FONTSIZE - meanFontSize) / (maxRating - meanRating) * (itemRating - maxRating) + MAX_FONTSIZE;
} else {
// Flop data
fontSize = (MAX_FONTSIZE - meanFontSize) / (minRating - meanRating) * (itemRating - minRating) + MAX_FONTSIZE;
}
fontSize = Math.min(Math.max(fontSize, MIN_FONTSIZE), MAX_FONTSIZE); // Cut off at the boundaries
// Create the text view
TextView txt = createText(fontSize, item, viewOnClickListener, viewOnLongClickListener);
fl.addView(txt);
}
// Add the flow layout to the scroll container
sv.addView(fl);
// Add the scroll container to the layout
l.addView(sv, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
/**
* Returns a (fixed) shuffled access strategy for the given list size.
*
* @param n
* The list size
* @return A list of indices in [0, n) which all appear only once.
*/
private List<Integer> getShuffledAccessStrategy(int n) {
List<Integer> ret = new ArrayList<Integer>(n);
for (int i = 0; i < n; ++i) {
ret.add(i);
}
Random rnd = new Random(2340983240982340932l); // Chosen u.a.r. ;)
for (int i = n - 1; i >= 0; --i) {
int index = rnd.nextInt(i + 1);
Collections.swap(ret, index, i);
}
return ret;
}
/**
* Creates a new {@link TextView} with the given {@code fontSize} and the
* title of the {@code item} as the text.
*
* @param fontSize
* @param item
* @return The {@link TextView}
*/
private TextView createText(float fontSize, final IStatisticsData item, View.OnClickListener onClickListener,
View.OnLongClickListener onLongClickListener) {
TextView txt = new SmallestAreaTextView(statisticsActivity);
txt.setLayoutParams(new FlowLayout.LayoutParams(true, 5, 3));
txt.setGravity(Gravity.CENTER);
txt.setTextSize(fontSize);
txt.setText(item.getTitle());
txt.setTag(item);
if (onClickListener != null) {
txt.setOnClickListener(onClickListener);
}
if (onLongClickListener != null) {
txt.setOnLongClickListener(onLongClickListener);
}
return txt;
}
}