package net.sf.egonet.web.page;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.RoundRectangle2D;
import java.awt.Polygon;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.geom.AffineTransform;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.link.Link;
import org.apache.commons.collections15.Transformer;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.base.Joiner;
import net.sf.egonet.model.Answer;
import net.sf.egonet.model.Question;
import net.sf.egonet.model.Study;
import net.sf.egonet.model.Alter;
import net.sf.egonet.model.Interview;
import net.sf.egonet.model.QuestionOption;
import net.sf.egonet.model.Answer.AnswerType;
import net.sf.egonet.persistence.Answers;
import net.sf.egonet.persistence.Interviewing;
import net.sf.egonet.persistence.Studies;
import net.sf.egonet.persistence.Interviews;
import net.sf.egonet.persistence.Alters;
import net.sf.egonet.persistence.Expressions;
import net.sf.egonet.persistence.Analysis;
import net.sf.egonet.persistence.Questions;
import net.sf.egonet.persistence.Options;
import net.sf.egonet.web.panel.AnswerFormFieldPanel;
import net.sf.egonet.web.panel.InterviewingPanel;
import net.sf.egonet.web.component.NetworkImage;
import net.sf.functionalj.tuple.PairUni;
import net.sf.egonet.network.Network;
import net.sf.egonet.network.NetworkService;
import edu.uci.ics.jung.algorithms.layout.Layout;
import static net.sf.egonet.web.page.InterviewingQuestionIntroPage.possiblyReplaceNextQuestionPageWithPreface;
/*
* A class to create an interview page that will display a network graph,
* with nodes for the alters specified during the interview. The edges will
* be created based on a condition specified when the question is created.
* The network graph can be customized to modify parameters based on the
* interviewee's response to other questions. These parameters are:
*
* node size (based on any ALTER question)
* node color (based on any ALTER question)
* node shape (based on any ALTER question)
* edge width (based on any ALTER_PAIR question)
* edge color (based on any ALTER_PAIR question)
*
* All parameters above are optional during question creation, but the
* question author must specify an expression to define the edges between
* vertices.
*/
public class InterviewingNetworkPage extends InterviewingPage {
private static class InterviewQLayout
{
public Long interviewId;
public Long expressionId;
public NetworkService.LayoutOption layoutOption;
public Layout<Alter, PairUni<Alter>> networkLayout;
}
private Long interviewId;
private Question question;
private InterviewingPanel interviewingPanel;
private NetworkImage<Alter> networkImage;
private static List<InterviewQLayout> networkLayouts = Collections.synchronizedList(new ArrayList<InterviewQLayout>());
public InterviewingNetworkPage(Long interviewId, Question question) {
super(interviewId);
this.interviewId = interviewId;
this.question = question;
build();
}
private void build() {
buildNetworkImage();
Form form = new Form("form") {
public void onSubmit() {
List<String> pageFlags = interviewingPanel.pageFlags();
List<AnswerFormFieldPanel> answerFields = interviewingPanel.getAnswerFields();
boolean okayToContinue =
AnswerFormFieldPanel.okayToContinue(answerFields, pageFlags);
boolean consistent =
AnswerFormFieldPanel.allConsistent(answerFields, pageFlags);
for(AnswerFormFieldPanel field : interviewingPanel.getAnswerFields()) {
if(okayToContinue) {
Answers.setAnswerForInterviewAndQuestion(interviewId, question,
field.getAnswer(),field.getOtherText(),field.getSkipReason(pageFlags));
} else if(consistent) {
field.setNotification(
field.answeredOrRefused(pageFlags) ?
"" : "Unanswered");
} else {
field.setNotification(
field.consistent(pageFlags) ?
"" : field.inconsistencyReason(pageFlags));
}
}
if(okayToContinue) {
setResponsePage(
askNextUnanswered(interviewId,question,
new InterviewingNetworkPage(interviewId,question)));
}
}
};
AnswerFormFieldPanel field = AnswerFormFieldPanel.getInstance("question",question,interviewId);
Answer answer = Answers.getAnswerForInterviewAndQuestion(interviewId, question);
if(answer != null) {
field = AnswerFormFieldPanel.getInstance("question",
question,answer.getValue(),answer.getOtherSpecifyText(),answer.getSkipReason(),interviewId);
}
field.setAutoFocus();
form.add(field);
interviewingPanel =
new InterviewingPanel("interviewingPanel",question,Lists.newArrayList(field),interviewId);
form.add(interviewingPanel);
add(form);
add(new Link("backwardLink") {
public void onClick() {
EgonetPage page =
askPrevious(interviewId,question,new InterviewingNetworkPage(interviewId,question));
if(page != null) {
setResponsePage(page);
}
}
});
Link forwardLink = new Link("forwardLink") {
public void onClick() {
EgonetPage page =
askNext(interviewId,question,new InterviewingNetworkPage(interviewId,question));
if(page != null) {
setResponsePage(page);
}
}
};
add(forwardLink);
if(! AnswerFormFieldPanel.okayToContinue(
interviewingPanel.getAnswerFields(),
interviewingPanel.pageFlags()))
{
forwardLink.setVisible(false);
}
}
private static Layout<Alter, PairUni<Alter>> GetLayout(Long interviewId, Long expressionId, NetworkService.LayoutOption layoutOption)
{
for (InterviewQLayout l : networkLayouts)
{
if (interviewId.equals(l.interviewId) &&
expressionId.equals(l.expressionId) &&
(layoutOption.equals(l.layoutOption) || layoutOption == null))
return l.networkLayout;
}
return null;
}
private static void AddLayout(Long interviewId,
Long expressionId,
NetworkService.LayoutOption layoutOption,
Layout<Alter, PairUni<Alter>> networkLayout)
{
InterviewQLayout l = new InterviewQLayout();
l.interviewId = interviewId;
l.expressionId = expressionId;
l.layoutOption = layoutOption;
l.networkLayout = networkLayout;
networkLayouts.add(l);
}
private void buildNetworkImage()
{
Interview interview = Interviews.getInterview(this.interviewId);
List<Alter> interviewAlters = Alters.getForInterview(this.interviewId);
/*
* Retrieve ID numbers for the questions used to set node/edge parameters,
* and for the Expression that defines whether edges are present between
* pairs of alters. For the parameter questions, retrieve the interviewee's
* answers to these ALTER/ALTER_PAIR questions and create an associative map
* matching each alter/alter-pair to the interviewee's answer about them
* from the relevant question.
*/
Long relExprId = question.getNetworkRelationshipExprId();
Long nSizeQId = question.getNetworkNSizeQId();
Long nShapeQId = question.getNetworkNShapeQId();
Long nColorQId = question.getNetworkNColorQId();
Long eSizeQId = question.getNetworkESizeQId();
Long eColorQId = question.getNetworkEColorQId();
Answer nSizeAnswer = null;
Answer nShapeAnswer = null;
Answer nColorAnswer = null;
Answer eSizeAnswer = null;
Answer eColorAnswer = null;
Question nSizeQuestion = null;
Question nShapeQuestion = null;
Question nColorQuestion = null;
Question eSizeQuestion = null;
Question eColorQuestion = null;
Map<Long, String> nSizeResponseMap = null;
Map<Long, String> nShapeResponseMap = null;
Map<Long, String> nColorResponseMap = null;
Map<PairUni<Long>, String> eSizeResponseMap = null;
Map<PairUni<Long>, String> eColorResponseMap = null;
/*
* iterate through alters, asking about each for the node size questions, and each pair for the edge questions
*/
if (relExprId != null)
{
List<Question> allQuestions = Lists.newArrayList();
if (nSizeQId != null)
{
nSizeQuestion = Questions.getQuestion(nSizeQId);
nSizeResponseMap = Maps.newHashMap();
allQuestions.add(nSizeQuestion);
}
if (nShapeQId != null)
{
nShapeQuestion = Questions.getQuestion(nShapeQId);
nShapeResponseMap = Maps.newHashMap();
allQuestions.add(nShapeQuestion);
}
if (nColorQId != null)
{
nColorQuestion = Questions.getQuestion(nColorQId);
nColorResponseMap = Maps.newHashMap();
allQuestions.add(nColorQuestion);
}
if (eSizeQId != null)
{
eSizeQuestion = Questions.getQuestion(eSizeQId);
eSizeResponseMap = Maps.newHashMap();
allQuestions.add(eSizeQuestion);
}
if (eColorQId != null)
{
eColorQuestion = Questions.getQuestion(eColorQId);
eColorResponseMap = Maps.newHashMap();
allQuestions.add(eColorQuestion);
}
Map<Long,String> optionIdToValue = Maps.newTreeMap();
for(Question question : allQuestions) {
if(question.getAnswerType().equals(Answer.AnswerType.SELECTION) ||
question.getAnswerType().equals(Answer.AnswerType.MULTIPLE_SELECTION))
{
for(QuestionOption option : Options.getOptionsForQuestion(question.getId())) {
optionIdToValue.put(option.getId(), option.getValue());
}
}
}
for (final Alter a1 : interviewAlters)
{
ArrayList<Alter> a1List = new ArrayList<Alter>(){{ add(a1); }};
if (nSizeQuestion != null)
{
nSizeResponseMap.put(a1.getId(), showAnswer(optionIdToValue,
nSizeQuestion,
Answers.getAnswerForInterviewQuestionAlters(interview, nSizeQuestion, a1List)));
}
if (nShapeQuestion != null)
{
nShapeResponseMap.put(a1.getId(), showAnswer(optionIdToValue,
nShapeQuestion,
Answers.getAnswerForInterviewQuestionAlters(interview, nShapeQuestion, a1List)));
}
if (nColorQuestion != null)
{
nColorResponseMap.put(a1.getId(), showAnswer(optionIdToValue,
nColorQuestion,
Answers.getAnswerForInterviewQuestionAlters(interview, nColorQuestion, a1List)));
}
for (final Alter a2 : interviewAlters)
{
if (a2.getId() > a1.getId())
{
ArrayList<Alter> a1a2List = new ArrayList<Alter>() {{ add(a1); add(a2); }};
if (eSizeQuestion != null)
{
eSizeResponseMap.put(new PairUni<Long>(a1.getId(), a2.getId()),
showAnswer(optionIdToValue,
eSizeQuestion,
Answers.getAnswerForInterviewQuestionAlters(interview, eSizeQuestion, a1a2List)));
}
if (eColorQuestion != null)
{
eColorResponseMap.put(new PairUni<Long>(a1.getId(), a2.getId()),
showAnswer(optionIdToValue,
eColorQuestion,
Answers.getAnswerForInterviewQuestionAlters(interview, eColorQuestion, a1a2List)));
}
}
}
}
}
else
{
/*
* ETHAN - This point should not be reached. No expression has been specified for edge-creation.
* TODO: Add exception/error warning code.
*/
}
/*
* Retrieve a network with the interviewee's alters as the graph nodes, and edges
* as defined by the selected Expression.
*/
Network<Alter> questionNetwork = null;
questionNetwork = Analysis.getNetworkForInterview(interview, Expressions.get(relExprId));
networkImage = new NetworkImage<Alter>("networkImage", questionNetwork);
NetworkService.LayoutOption layoutOption = NetworkService.LayoutOption.FR;
Layout<Alter, PairUni<Alter>> layout = GetLayout(this.interviewId, relExprId, layoutOption);
if (layout == null)
{
layout = networkImage.getOrCreateLayout();
AddLayout(interviewId, relExprId, layoutOption, layout);
}
else
{
networkImage.setLayout(layout);
}
/*
* Set node/edge transformers for the available parameters. If no question was selected for
* a parameter when the interview was first created, the transformer will be null here,
* and the parameter will not be varied during image-creation in NetworkService.java
*/
networkImage.setNodeColorizer(newAlterVtxPainter(questionNetwork, nColorResponseMap));
/*
* Set the node shape and size. If the size question has an answer in the
* NUMERICAL format, the transformer needs the question so that it can find
* the min/max value allowed for that answer.
*/
if (nSizeQuestion != null && nSizeQuestion.getAnswerType() == AnswerType.NUMERICAL)
networkImage.setNodeShaper(newAlterVtxShaper(questionNetwork, nShapeResponseMap, nSizeResponseMap, nSizeQuestion));
else
networkImage.setNodeShaper(newAlterVtxShaper(questionNetwork, nShapeResponseMap, nSizeResponseMap));
networkImage.setEdgeColorizer(newAlterEdgePainter(questionNetwork, eColorResponseMap));
networkImage.setEdgeSizer(newAlterEdgeSizer(questionNetwork, eSizeResponseMap));
networkImage.setDimensions(1200,800);
add(networkImage);
}
/*
* Essentially the same method as the one by the same name in Analysis.java
* The Analysis.java method is private, and also can return "0" in a case where it fails
* to parse out an answer. "0" is part of a valid answer for most SELECTION or
* MULTIPLE_SELECTION questions, so we instead return null in the case where answer.getValue() == null
*/
private String showAnswer(Map<Long, String> optionIdToValue, Question question, Answer answer)
{
if(answer == null) {
return null;
}
if(question.getAnswerType().equals(Answer.AnswerType.NUMERICAL) ||
question.getAnswerType().equals(Answer.AnswerType.TEXTUAL) ||
question.getAnswerType().equals(Answer.AnswerType.TEXTUAL_PP) ||
question.getAnswerType().equals(Answer.AnswerType.DATE) ||
question.getAnswerType().equals(Answer.AnswerType.TIME_SPAN))
{
return answer.getValue();
}
if(question.getAnswerType().equals(Answer.AnswerType.SELECTION) ||
question.getAnswerType().equals(Answer.AnswerType.MULTIPLE_SELECTION))
{
String value = answer.getValue();
if(value == null || value.isEmpty()) {
return null;
}
List<String> selectedOptions = Lists.newArrayList();
for(String optionIdString : value.split(",")) {
Long optionId = Long.parseLong(optionIdString);
String optionValue = optionIdToValue.get(optionId);
if(optionValue != null) {
selectedOptions.add(optionValue);
}
}
return Joiner.on("; ").join(selectedOptions);
}
throw new RuntimeException("Unable to answer for answer type: "+question.getAnswerType());
}
// forward, unansweredOnly
// askNextUnanswered: true, true
// askNext: true, false
// askPrevious: false, false
public static EgonetPage askNextUnanswered(
Long interviewId,Question currentQuestion, EgonetPage comeFrom)
{
Question nextNetworkQuestion =
Interviewing.nextNetworkQuestionForInterview(interviewId,currentQuestion,true,true);
if(nextNetworkQuestion != null) {
EgonetPage nextNetworkPage = new InterviewingNetworkPage(interviewId, nextNetworkQuestion);
return possiblyReplaceNextQuestionPageWithPreface(
interviewId,nextNetworkPage,currentQuestion,nextNetworkQuestion,
comeFrom,nextNetworkPage);
}
return new InterviewingConclusionPage(interviewId);
}
public static EgonetPage askNext(Long interviewId,Question currentQuestion, EgonetPage comeFrom)
{
Question nextNetworkQuestion =
Interviewing.nextNetworkQuestionForInterview(interviewId,currentQuestion,true,false);
if(nextNetworkQuestion != null) {
EgonetPage nextPage = new InterviewingNetworkPage(interviewId, nextNetworkQuestion);
return possiblyReplaceNextQuestionPageWithPreface(
interviewId,nextPage,currentQuestion,nextNetworkQuestion,
comeFrom,nextPage);
}
return new InterviewingConclusionPage(interviewId);
}
public static EgonetPage askPrevious(Long interviewId,Question currentQuestion, EgonetPage comeFrom) {
Question previousNetworkQuestion =
Interviewing.nextNetworkQuestionForInterview(interviewId,currentQuestion,false,false);
EgonetPage previousPage = previousNetworkQuestion == null ? null :
new InterviewingNetworkPage(interviewId, previousNetworkQuestion);
return possiblyReplaceNextQuestionPageWithPreface(
interviewId,previousPage,previousNetworkQuestion,currentQuestion,
previousPage,comeFrom);
}
public String toString() {
return question.getType()+" : "+question.getTitle();
}
/*
* Colors for the vertices/edges. Edge colors use essentially the same pallete
* as vertex colors, but are darker so the vertices are easier to see.
*/
private static Color[] vColors = new Color[] { new Color(117, 134, 144),
new Color(78, 205, 196),
new Color(199, 244, 100),
new Color(255, 107, 107),
new Color(220, 105, 135),
new Color(180, 105, 220),
new Color(210, 230, 255)
};
private static Color[] eColors = new Color[] { new Color(63, 74, 84),
new Color(59, 155, 147),
new Color(125, 152, 61),
new Color(145, 68, 128),
new Color(147, 57, 66),
new Color(55, 82, 162),
};
/*
* Given an alter, set that alter's vertex color depending on the interviewee's answer
* to the node-color question.
*/
private static Transformer<Alter, Paint> newAlterVtxPainter(final Network<Alter> network, final Map<Long, String> answers)
{
return
new Transformer<Alter, Paint>()
{
public Paint transform(Alter a)
{
Color defaultColor = vColors[6];
if (answers == null)
return defaultColor;
String ans = answers.get(a.getId());
if (ans == null)
{
return defaultColor;
}
if (ans == null)
return defaultColor;
/*
* Vertex color order was modified here from their array position after testing
* with questions where not all colors were used. Thus, the slightly odd sequence
* of array indices.
*/
if (ans.contains("0"))
return vColors[1];
if (ans.contains("1"))
return vColors[2];
if (ans.contains("2"))
return vColors[3];
if (ans.contains("3"))
return vColors[0];
if (ans.contains("4"))
return vColors[5];
return defaultColor;
}
};
}
/*
* Predefined shapes that can be used for vertex rendering. We use
* 4-, 5-, and 6-sided regular polygons, as well as (the default) circle.
* Also available are a rounded rectangle and an oval, both wider than they
* are tall.
*/
private static int vtxPentagonSize = 14 ;
private static int c1 = (int)(0.3090 * vtxPentagonSize);
private static int c2 = (int)(0.8090 * vtxPentagonSize);
private static int s1 = (int)(0.9511 * vtxPentagonSize);
private static int s2 = (int)(0.5878 * vtxPentagonSize);
private static int[] xValsPentagon = { 0, s1, s2, -s2, -s1 };
private static int[] yValsPentagon = { -vtxPentagonSize, -c1, c2, c2, -c1 };
private static Polygon vertexPentagon = new Polygon(xValsPentagon, yValsPentagon, 5);
private static int vtxHexagonSize = 13;
private static int hexX = (int)(0.866 * vtxHexagonSize);
private static int hexY = (int)(vtxHexagonSize / 2.0);
private static int[] xValsHexagon = { -hexX, -hexX, 0, hexX, hexX, 0 };
private static int[] yValsHexagon = { -hexY, hexY, vtxHexagonSize, hexY, -hexY, -vtxHexagonSize };
private static Polygon vertexHexagon = new Polygon(xValsHexagon, yValsHexagon, 6);
private static int vtxCircleRadius = 12;
private static Ellipse2D vertexCircle = new Ellipse2D.Float(-vtxCircleRadius, -vtxCircleRadius, vtxCircleRadius * 2, vtxCircleRadius * 2);
private static int vtxSquareHalfSize = 11;
private static Rectangle2D vertexSquare = new Rectangle2D.Float(-vtxSquareHalfSize, -vtxSquareHalfSize, vtxSquareHalfSize * 2, vtxSquareHalfSize * 2);
private static int vtxRoundRectHalfWidth = 14;
private static int vtxRoundRectHalfHeight = 8;
private static float vtxRoundRectArcWidth = 6;
private static RoundRectangle2D vertexRoundRect = new RoundRectangle2D.Float(-vtxRoundRectHalfWidth,
-vtxRoundRectHalfHeight,
vtxRoundRectHalfWidth * 2,
vtxRoundRectHalfHeight * 2,
vtxRoundRectArcWidth,
vtxRoundRectArcWidth);
private static int vtxOvalHalfWidth = 14;
private static int vtxOvalHalfHeight = 8;
private static Ellipse2D vertexOval = new Ellipse2D.Float(-vtxOvalHalfWidth, -vtxOvalHalfHeight, vtxOvalHalfWidth * 2, vtxOvalHalfHeight * 2);
/*
* For questions with NUMERICAL answer type and a defined min/max value, we can scale the vertex size
* based on where the interviewee's answer falls in the valid range.
*
* The scaling factor for the vertex will be:
* [(answer - minAnswer) / (maxAnswer - minAnswer)] * (maxScale - minScale) + minScale
*/
private static float minSizeScale = 1.0f;
private static float maxSizeScale = 4.0f;
private static float vtxSizeScaleRange = maxSizeScale - minSizeScale;
/*
* Given an alter, set that alter's vertex shape depending on the interviewee's answer
* to the node-color question. This transformer also provides the functionality for
* varying the vertex size, based on the node-size question. (If only one of the two
* is used for a particular NETWORK question, this transformer will leave the other
* parameter unmodified from its default for all vertices.)
*
* The shapes defined above
* all occupy roughly the same amount of pixel space as defined, so that shape and size
* can both be used without causing perception problems. (If the pentagon were smaller
* the circle, a "large" pentagon wouldn't necessarily have the proper size relative
* to a "medium" circle.)
*/
private static Transformer<Alter, Shape> newAlterVtxShaper(final Network<Alter> network,
final Map<Long, String> shapeAnswers,
final Map<Long, String> sizeAnswers)
{
return newAlterVtxShaper(network, shapeAnswers, sizeAnswers, null);
}
private static Transformer<Alter, Shape> newAlterVtxShaper(final Network<Alter> network,
final Map<Long, String> shapeAnswers,
final Map<Long, String> sizeAnswers,
final Question sizeQuestion)
{
return
new Transformer<Alter, Shape>()
{
public Shape transform(Alter a)
{
Shape vtxShape = null;
if (shapeAnswers != null)
{
String shapeAns = shapeAnswers.get(a.getId());
if (shapeAns != null)
{
if (shapeAns.contains("0"))
vtxShape = vertexSquare;
else if (shapeAns.contains("1"))
vtxShape = vertexPentagon;
else if (shapeAns.contains("2"))
vtxShape = vertexHexagon;
else if (shapeAns.contains("3"))
vtxShape = vertexRoundRect;
else if (shapeAns.contains("4"))
vtxShape = vertexOval;
}
}
if (vtxShape == null)
vtxShape = vertexCircle;
if (sizeAnswers == null)
{
//return vtxShape;
AffineTransform vtxResize = new AffineTransform();
vtxResize.scale(1.75, 1.75);
return vtxResize.createTransformedShape(vtxShape);
}
String sizeAns = sizeAnswers.get(a.getId());
if (sizeQuestion != null && sizeQuestion.getAnswerType() == AnswerType.NUMERICAL)
{
try
{
Integer minAnswer = sizeQuestion.getMinLiteral();
Integer maxAnswer = sizeQuestion.getMaxLiteral();
float answer = Float.parseFloat(sizeAns);
answer = (float)(answer > maxAnswer? maxAnswer : answer);
answer = (float)(answer < minAnswer? minAnswer : answer);
float scale = (answer - minAnswer) / (float)(maxAnswer - minAnswer) * vtxSizeScaleRange + minSizeScale;
AffineTransform vtxResize = new AffineTransform();
vtxResize.scale(scale, scale);
return vtxResize.createTransformedShape(vtxShape);
}
catch (RuntimeException rtx)
{
return vtxShape;
}
}
if (sizeAns != null)
{
AffineTransform vtxResize = new AffineTransform();
if (sizeAns.contains("0"))
{
vtxResize.scale(1.75, 1.75);
return vtxResize.createTransformedShape(vtxShape);
}
if (sizeAns.contains("1"))
{
vtxResize.scale(2.2, 2.2);
return vtxResize.createTransformedShape(vtxShape);
}
if (sizeAns.contains("2"))
{
vtxResize.scale(2.7, 2.7);
return vtxResize.createTransformedShape(vtxShape);
}
if (sizeAns.contains("3"))
{
vtxResize.scale(3.4, 3.4);
return vtxResize.createTransformedShape(vtxShape);
}
if (sizeAns.contains("4"))
{
vtxResize.scale(4.3, 4.3);
return vtxResize.createTransformedShape(vtxShape);
}
}
return vtxShape;
}
};
}
/*
* Given a pair of alters, determine the interviewee's response to the ALTER_PAIR
* edge-color question (if applicable). Set the edge color based on that response.
*/
private static Transformer<PairUni<Alter>, Paint> newAlterEdgePainter(final Network<Alter> network, final Map<PairUni<Long>, String> answers)
{
return
new Transformer<PairUni<Alter>, Paint>()
{
public Paint transform(PairUni<Alter> alters)
{
Long idA1 = alters.getFirst().getId();
Long idA2 = alters.getSecond().getId();
String ans = null;
Color defaultColor = eColors[3];
try
{
/*
* getNetworkForInterview creates a network with edges where
* the first alter id listed is lower than the second
*/
if (idA1 < idA2)
ans = answers.get(new PairUni<Long>(idA1, idA2));
else
ans = answers.get(new PairUni<Long>(idA2, idA1));
if (ans == null)
return defaultColor;
if (ans == null)
return defaultColor;
if (ans.contains("0"))
return eColors[1];
if (ans.contains("1"))
return eColors[2];
if (ans.contains("2"))
return eColors[3];
if (ans.contains("3"))
return eColors[4];
if (ans.contains("4"))
return eColors[5];
return defaultColor;
}
catch (RuntimeException rtx)
{
return defaultColor;
}
}
};
}
/*
* Given a pair of alters, determine the interviewee's response to the ALTER_PAIR
* edge-size question (if applicable). Set the edge width based on that response.
*/
private static Transformer<PairUni<Alter>, Stroke> newAlterEdgeSizer(final Network<Alter> network, final Map<PairUni<Long>, String> answers)
{
return
new Transformer<PairUni<Alter>, Stroke>()
{
public Stroke transform(PairUni<Alter> alters)
{
Long idA1 = alters.getFirst().getId();
Long idA2 = alters.getSecond().getId();
String ans = null;
BasicStroke defaultStroke = new BasicStroke(1.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f);
try
{
/*
* getNetworkForInterview creates a network with edges where
* the first alter id listed is lower than the second
*/
if (idA1 < idA2)
ans = answers.get(new PairUni<Long>(idA1, idA2));
else
ans = answers.get(new PairUni<Long>(idA2, idA1));
if (ans == null)
return defaultStroke;
if (ans == null)
return defaultStroke;
if (ans.contains("0"))
return new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f);
if (ans.contains("1"))
return new BasicStroke(3.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f);
if (ans.contains("2"))
return new BasicStroke(4.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f);
if (ans.contains("3"))
return new BasicStroke(6.2f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f);
if (ans.contains("4"))
return new BasicStroke(8.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f);
return defaultStroke;
}
catch (RuntimeException rtx)
{
return defaultStroke;
}
}
};
}
}