Visualization for parse trees on Android

This commit is contained in:
kr.angelov
2014-07-02 11:30:06 +00:00
parent 42dea944d9
commit ba1e95dacb
5 changed files with 331 additions and 69 deletions

View File

@@ -12,16 +12,18 @@
android:paddingRight="5dp" />
<TextView
android:id="@+id/lexical_desc"
android:id="@+id/alternative_desc"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/arrow"
android:paddingLeft="10dp"
android:textSize="25sp"/>
<WebView
android:id="@+id/lexical_inflection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/lexical_desc"/>
<org.grammaticalframework.ui.android.ParseTreeView
android:id="@+id/desc_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/alternative_desc"
android:textSize="25sp"
/>
</RelativeLayout>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<ImageView
android:id="@+id/arrow"
android:layout_width="30dp"
android:layout_height="30dp"
android:contentDescription="@string/open_image"
android:src="@drawable/open_arrow"
android:paddingLeft="10dp"
android:paddingRight="5dp" />
<TextView
android:id="@+id/lexical_desc"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/arrow"
android:paddingLeft="10dp"
android:textSize="25sp"/>
<WebView
android:id="@+id/desc_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/lexical_desc"/>
</RelativeLayout>

View File

@@ -103,8 +103,8 @@ public class AlternativesActivity extends ListActivity {
ImageView arrow = (ImageView) expandedView.findViewById(R.id.arrow);
arrow.setImageResource(R.drawable.open_arrow);
WebView inflectionView = (WebView) expandedView.findViewById(R.id.lexical_inflection);
((RelativeLayout) expandedView).removeView(inflectionView);
View view = (View) expandedView.findViewById(R.id.desc_details);
((RelativeLayout) expandedView).removeView(view);
expandedView = null;
}
@@ -117,10 +117,10 @@ public class AlternativesActivity extends ListActivity {
ImageView arrow = (ImageView) view.findViewById(R.id.arrow);
arrow.setImageResource(R.drawable.close_arrow);
WebView inflectionView = (WebView) view.findViewById(R.id.lexical_inflection);
WebView inflectionView = (WebView) view.findViewById(R.id.desc_details);
if (inflectionView == null) {
inflectionView = new WebView(this);
inflectionView.setId(R.id.lexical_inflection);
inflectionView.setId(R.id.desc_details);
RelativeLayout.LayoutParams params =
new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.BELOW, R.id.lexical_desc);
@@ -136,18 +136,18 @@ public class AlternativesActivity extends ListActivity {
ImageView arrow = (ImageView) view.findViewById(R.id.arrow);
arrow.setImageResource(R.drawable.close_arrow);
WebView inflectionView = (WebView) view.findViewById(R.id.lexical_inflection);
if (inflectionView == null) {
inflectionView = new WebView(this);
inflectionView.setId(R.id.lexical_inflection);
ParseTreeView parseView = (ParseTreeView) view.findViewById(R.id.desc_details);
if (parseView == null) {
parseView = new ParseTreeView(this);
parseView.setId(R.id.desc_details);
RelativeLayout.LayoutParams params =
new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.BELOW, R.id.lexical_desc);
((RelativeLayout) view).addView(inflectionView, params);
params.addRule(RelativeLayout.BELOW, R.id.alternative_desc);
((RelativeLayout) view).addView(parseView, params);
}
String content = String.format("[%.4f] %s", ep.getProb(), ep.getExpr());
inflectionView.loadData(content, "text/plain; charset=UTF-8", null);
Object[] brackets = mTranslator.bracketedLinearize(ep.getExpr());
parseView.setBrackets(brackets);
expandedView = view;
}
@@ -160,16 +160,16 @@ public class AlternativesActivity extends ListActivity {
public View getView(int position, View convertView, ViewGroup parent) {
Object item = getItem(position);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater)
getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.alternative_item, null);
}
TextView descView = (TextView)
convertView.findViewById(R.id.lexical_desc);
if (item instanceof MorphoAnalysis) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater)
getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.lexical_item, null);
}
TextView descView = (TextView)
convertView.findViewById(R.id.lexical_desc);
final String lemma = ((MorphoAnalysis) item).getLemma();
String phrase = mTranslator.generateLexiconEntry(lemma);
@@ -188,55 +188,62 @@ public class AlternativesActivity extends ListActivity {
}
}
});
} else {
if (item instanceof ExprProb) {
final ExprProb ep = (ExprProb) item;
String phrase = mTranslator.linearize(ep.getExpr());
} else if (item instanceof ExprProb) {
final ExprProb ep = (ExprProb) item;
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater)
getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.alternative_item, null);
}
// parse by words, marked by %, darkest red color
if (phrase.charAt(0) == '%') {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg));
phrase = phrase.substring(2);
}
TextView descView = (TextView)
convertView.findViewById(R.id.alternative_desc);
// parse by chunks, marked by *, red color
else if (phrase.charAt(0) == '*') {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_chunk_utterance_bg));
phrase = phrase.substring(2);
}
String phrase = mTranslator.linearize(ep.getExpr());
// parse error or unknown translations (in []) present, darkest red color
else if (phrase.contains("parse error:") || phrase.contains("[")) {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg));
}
// parse by words, marked by %, darkest red color
if (phrase.charAt(0) == '%') {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg));
phrase = phrase.substring(2);
}
// parse by domain grammar, marked by +, green color
else if (phrase.charAt(0) == '+') {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_best_utterance_bg));
phrase = phrase.substring(2);
}
else {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_utterance_bg));
}
// parse by chunks, marked by *, red color
else if (phrase.charAt(0) == '*') {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_chunk_utterance_bg));
phrase = phrase.substring(2);
}
descView.setText(phrase);
// parse error or unknown translations (in []) present, darkest red color
else if (phrase.contains("parse error:") || phrase.contains("[")) {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_worst_utterance_bg));
}
convertView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (expandedView == view)
collapse();
else if (expandedView == null)
expandExpr(view, ep);
else {
collapse();
expandExpr(view, ep);
}
// parse by domain grammar, marked by +, green color
else if (phrase.charAt(0) == '+') {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_best_utterance_bg));
phrase = phrase.substring(2);
}
else {
descView.setBackgroundDrawable(getResources().getDrawable(R.drawable.second_person_utterance_bg));
}
descView.setText(phrase);
convertView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (expandedView == view)
collapse();
else if (expandedView == null)
expandExpr(view, ep);
else {
collapse();
expandExpr(view, ep);
}
});
}
}
});
}
return convertView;

View File

@@ -0,0 +1,221 @@
package org.grammaticalframework.ui.android;
import java.util.HashMap;
import java.util.Map;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
import org.grammaticalframework.pgf.Bracket;
public class ParseTreeView extends View {
private static final float SISTER_SKIP = 25;
private static final float PARENT_SKIP = 0.5f;
private static final float ABOVE_LINE_SKIP = 0.1f;
private static final float BELOW_LINE_SKIP = 0.1f;
private Paint paint;
private Object[] brackets;
public ParseTreeView(Context context) {
super(context);
initialize();
}
public ParseTreeView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
private void initialize() {
paint = new Paint();
paint.setTextSize(60);
brackets = null;
}
public Object[] getBrackets() {
return brackets;
}
public void setBrackets(Object[] brackets) {
this.brackets = brackets;
}
static class MeasureResult {
float width = 0.0f;
float height = 0.0f;
float nodeTab = 0.0f;
float nodeCenter = 0.0f;
float childTab = 0.0f;
float localWidth = 0.0f;
float localHeight = 0.0f;
}
private MeasureResult mr = new MeasureResult();
private SparseArray<PointF> coords = new SparseArray<PointF>();
private void measureTree(Object o, PointF zeroPoint) {
if (o instanceof Bracket) {
Bracket bracket = (Bracket) o;
Rect bounds = new Rect();
paint.getTextBounds(bracket.cat,0,bracket.cat.length(),bounds);
float localWidth = bounds.width();
float localHeight = bounds.height();
float layerHeight = localHeight * (1.0f + BELOW_LINE_SKIP + ABOVE_LINE_SKIP + PARENT_SKIP);
PointF local = coords.get(bracket.fid);
if (local == null) {
if (zeroPoint != null)
coords.put(bracket.fid, zeroPoint);
} else {
localWidth = 0;
}
float subWidth = 0.0f;
float subHeight = 0.0f;
float nodeCenter = 0.0f;
for (int i = 0; i < bracket.children.length; i++) {
measureTree(bracket.children[i], zeroPoint);
if (i == 0) {
nodeCenter += (subWidth + mr.nodeCenter) / 2.0;
}
if (i == bracket.children.length - 1) {
nodeCenter += (subWidth + mr.nodeCenter) / 2.0;
}
subWidth += mr.width;
if (i < bracket.children.length - 1) {
subWidth += SISTER_SKIP;
}
if (subHeight < mr.height)
subHeight = mr.height;
}
float localLeft = localWidth / 2.0f;
float subLeft = nodeCenter;
float totalLeft = Math.max(localLeft, subLeft);
float localRight = localWidth / 2.0f;
float subRight = subWidth - nodeCenter;
float totalRight = Math.max(localRight, subRight);
mr.width = totalLeft + totalRight;
mr.height = layerHeight + subHeight;
mr.childTab = totalLeft - subLeft;
mr.nodeTab = totalLeft - localLeft;
mr.nodeCenter = nodeCenter + mr.childTab;
mr.localWidth = localWidth;
mr.localHeight = localHeight;
} else {
String word = o.toString();
Rect bounds = new Rect();
paint.getTextBounds(word,0,word.length(),bounds);
mr.width = bounds.width();
mr.height = bounds.height();
mr.nodeTab = 0.0f;
mr.nodeCenter = bounds.width() / 2.0f;
mr.childTab = 0.0f;
mr.localWidth = bounds.width();
mr.localHeight = bounds.height();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (brackets == null) {
setMeasuredDimension(0, 0);
return;
}
float width = 0.0f;
float height = 0.0f;
PointF zeroPoint = new PointF();
for (int i = 0; i < brackets.length; i++) {
measureTree(brackets[i], zeroPoint);
width += mr.width;
if (i < brackets.length - 1) {
width += SISTER_SKIP;
}
if (height < mr.height)
height = mr.height;
}
width += 10;
height += 10;
int w = getPaddingLeft() + (int) width + getPaddingRight();
int h = getPaddingTop() + (int) height + getPaddingBottom();
setMeasuredDimension(w, h);
}
private void drawTree(Canvas canvas,
float x, float y, float bottom, PointF parentPoint,
Object o) {
if (o instanceof Bracket) {
Bracket bracket = (Bracket) o;
PointF lineStart = coords.get(bracket.fid);
if (lineStart == null) {
lineStart = new PointF(x + mr.nodeCenter, y + mr.localHeight * (1.0f + BELOW_LINE_SKIP));
coords.put(bracket.fid, lineStart);
if (parentPoint != null) {
float lineEndX = x + mr.nodeCenter;
float lineEndY = y;
canvas.drawLine(parentPoint.x, parentPoint.y, lineEndX, lineEndY, paint);
}
canvas.drawText(bracket.cat, x+mr.nodeTab, y+mr.localHeight, paint);
}
float layerMultiplier = (1.0f + BELOW_LINE_SKIP + ABOVE_LINE_SKIP + PARENT_SKIP);
float layerHeight = mr.localHeight * layerMultiplier;
float childStartX = x + mr.childTab;
float childStartY = y + layerHeight;
for (int i = 0; i < bracket.children.length; i++) {
Object child = bracket.children[i];
measureTree(child, null);
float w = mr.width;
drawTree(canvas, childStartX, childStartY, bottom, lineStart, child);
childStartX += w + SISTER_SKIP;
}
} else {
float lineEndX = x + mr.nodeCenter;
float lineEndY = bottom - mr.height;
canvas.drawLine(parentPoint.x, parentPoint.y, lineEndX, lineEndY, paint);
canvas.drawText(o.toString(), x, bottom, paint);
}
}
@Override
protected void onDraw (Canvas canvas) {
super.onDraw(canvas);
if (brackets == null) {
return;
}
coords.clear();
float startX = getPaddingLeft();
for (int i = 0; i < brackets.length; i++) {
Object child = brackets[i];
measureTree(child, null);
float w = mr.width;
drawTree(canvas, startX, getPaddingTop(), getPaddingTop()+mr.height, null, child);
startX += w + SISTER_SKIP;
}
}
}

View File

@@ -340,6 +340,11 @@ public class Translator {
return targetLang.linearize(expr);
}
public Object[] bracketedLinearize(Expr expr) {
Concr targetLang = getTargetConcr();
return targetLang.bracketedLinearize(expr);
}
public String generateLexiconEntry(String lemma) {
Concr sourceLang = getSourceConcr();
Concr targetLang = getTargetConcr();