Skip to content
This repository has been archived by the owner on Mar 29, 2023. It is now read-only.

Commit

Permalink
Merge branch '#36-discrete-text-explanations'
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-markl committed Jul 6, 2020
2 parents 7a27469 + 54e88ad commit 60403b1
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 58 deletions.
9 changes: 4 additions & 5 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,18 @@ class App extends React.Component<{}, StateType> {
this.setState({ loading: true });
try {
const participantsData = this.getEmptyParticipantsData();
const discretePartData = this.getEmptyParticipantsData();
const discreteParticipantsData = this.getEmptyParticipantsData();
for (let i = 0; i < participantsData.length; i++) {
let engagementData = await loadEngagementData(
const engagementData = await loadEngagementData(
this.state.username,
this.state.password,
participantsData[i].dataURL,
false
);
participantsData[i].dataContainer = engagementData[0];
discretePartData[i].dataContainer = engagementData[1];
discreteParticipantsData[i].dataContainer = engagementData[1];
}
this.setState({ participantsData });
this.setState({ discreteParticipantsData: discretePartData });
this.setState({ participantsData, discreteParticipantsData });
} catch (error) {
this.setState({ error });
} finally {
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/BarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import createBarChartOptions from "./createBarChartOptions";
import {ResizeSensor} from "@blueprintjs/core";
import styled from "styled-components";
import {StrongestOutputExplanationsType} from "./sortAndSelectTopmostFeatures";
import {CategoryValueDescription} from "./FeaturesToTextMapping";

type PropsType = {
strongestOutputExplanations: StrongestOutputExplanationsType,
strongestOutputIdx: number,
maxExplanationValue: number
maxExplanationValue: number,
categoryActivationsObject: CategoryValueDescription
}

const StyledChart = styled(HorizontalBar)`
Expand All @@ -28,7 +30,7 @@ class BarChart extends React.Component<PropsType, { width: number, height: numbe
})

render() {
const {strongestOutputIdx, strongestOutputExplanations, maxExplanationValue} = this.props
const {strongestOutputIdx, strongestOutputExplanations, maxExplanationValue, categoryActivationsObject} = this.props
const {width, height} = this.state
const colorPalette = strongestOutputIdx < 2 ? ENGAGEMENT_NEGATIVE_COLOR_PALETTE : ENGAGEMENT_POSITIVE_COLOR_PALETTE;

Expand All @@ -38,7 +40,7 @@ class BarChart extends React.Component<PropsType, { width: number, height: numbe
width={width}
height={height}
data={{
labels: strongestOutputExplanations.topMostLabels,
labels: categoryActivationsObject.categoryActivations.map(item => item.mainActivationAsText),
datasets: [
{
label: "Testing Explanations",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/EngagementDefinitions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Colors } from "@blueprintjs/core";
export const ENGAGEMENT_LABELS = ["very unattentive", "slightly unattentive", "slightly engaged", "very engaged"];
export const ENGAGEMENT_LABELS = ["very inattentive", "slightly inattentive", "slightly engaged", "very engaged"];
export const ENGAGEMENT_COLORS = [Colors.RED3, Colors.ORANGE3, Colors.TURQUOISE2, Colors.GREEN5]; //assuming 3 is highest engagement

export const ENGAGEMENT_POSITIVE_COLOR_PALETTE = [
Expand Down
33 changes: 22 additions & 11 deletions frontend/src/ExplanationsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import React from "react";
import styled from "styled-components";
import WordCloud from "./WordCloud";
import FeatureActivationTextDescription from "./FeatureActivationTextDescription";
import { Gender } from "./FeaturesToTextMapping";
import { ENGAGEMENT_NEGATIVE_COLOR_PALETTE, ENGAGEMENT_POSITIVE_COLOR_PALETTE } from "./EngagementDefinitions";
import {Gender, generateDescriptionObject} from "./FeaturesToTextMapping";
import {ENGAGEMENT_NEGATIVE_COLOR_PALETTE, ENGAGEMENT_POSITIVE_COLOR_PALETTE} from "./EngagementDefinitions";
import sortAndSelectTopmostFeatures from "./sortAndSelectTopmostFeatures";
import calculateBlur from "./calculateBlur";
import ExplanationsHeading from "./ExplanationsHeading";
Expand Down Expand Up @@ -79,11 +79,13 @@ function ExplanationsContainer(props: {
username: string;
gender: Gender;
}) {
const { maxInputValues, minInputValues, maxExplanationValue, dataPoint, discreteDataPoint, username, gender,
labels, mode } = props;
if (!dataPoint) return <Container />;
if (!discreteDataPoint) return <Container />;
const { input, output, explanations } = dataPoint;
const {
maxInputValues, minInputValues, maxExplanationValue, dataPoint, discreteDataPoint, username, gender,
labels, mode
} = props;
if (!dataPoint) return <Container/>;
if (!discreteDataPoint) return <Container/>;
const {input, output, explanations} = dataPoint;
const strongestOutputIdx = output.indexOf(Math.max(...output));
const discreteStrongestOutputIdx = discreteDataPoint.output.indexOf(Math.max(...discreteDataPoint.output));
const confidence = Math.round(output[strongestOutputIdx] * 1000) / 10;
Expand All @@ -109,13 +111,20 @@ function ExplanationsContainer(props: {
true
);

const categoryActivationsObject = generateDescriptionObject(
strongestOutputExplanations.topMostLabels,
strongestOutputExplanations.topMostInputs,
username,
gender
);

const blur = calculateBlur(confidence);
const colorPalette = strongestOutputIdx < 2 ? ENGAGEMENT_NEGATIVE_COLOR_PALETTE : ENGAGEMENT_POSITIVE_COLOR_PALETTE;

return (
<Container>
<Explanations style={{ filter: `blur(${blur}px)` }}>
<ExplanationsHeading strongestOutputIdx={discreteStrongestOutputIdx} />
<Explanations style={{filter: `blur(${blur}px)`}}>
<ExplanationsHeading strongestOutputIdx={discreteStrongestOutputIdx}/>
<BasedOn>
<span>Based on: </span>
<FeatureActivationTextDescription
Expand All @@ -126,16 +135,18 @@ function ExplanationsContainer(props: {
popOverDisabled={blur > 0}
/>
</BasedOn>
<ChartContainer style={{ width: "90%" }}>
<ChartContainer style={{width: "90%"}}>
{mode === "bar" ? (
<BarChart
categoryActivationsObject={categoryActivationsObject}
strongestOutputIdx={strongestOutputIdx}
strongestOutputExplanations={strongestOutputExplanations}
maxExplanationValue={maxExplanationValue}
/>
) : (
<WordCloud
allLabels={labels}
categoryActivationsObject={categoryActivationsObject}
maxExplanationValue={maxExplanationValue}
strongestFeatures={strongestOutputExplanations.topMostExplanations}
strongestLabels={strongestOutputExplanations.topMostLabels}
Expand All @@ -144,7 +155,7 @@ function ExplanationsContainer(props: {
)}
</ChartContainer>
</Explanations>
<Unsure style={{ opacity: blur > 0 ? 1 : 0 }}>UNSURE</Unsure>
<Unsure style={{opacity: blur > 0 ? 1 : 0}}>UNSURE</Unsure>
</Container>
);
}
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/ExplanationsHeading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import {ENGAGEMENT_COLORS, ENGAGEMENT_LABELS,} from "./EngagementDefinitions";
const StyledDiv = styled.div`
display: flex;
align-items: center;
font-size: 1.6em;
padding: 4px 6px;
border: 2px solid ${Colors.LIGHT_GRAY2}55;
margin: 0 0 6px 0;
margin: 0 0 12px 0;
transition: 0.2s filter linear, 0.2s -webkit-filter linear;
b {
text-transform: uppercase;
margin: 0 0 0 4px;
font-size: 1.6em;
margin: 0 0 0 12px;
}
`;

Expand Down
22 changes: 10 additions & 12 deletions frontend/src/FeatureActivationTextDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import styled from "styled-components";
import { generateDescriptionObject, Gender, CATEGORY_DEFINITIONS } from "./FeaturesToTextMapping";
import { Popover, PopoverInteractionKind, Colors } from "@blueprintjs/core";
import {CATEGORY_DEFINITIONS, Gender, generateDescriptionObject} from "./FeaturesToTextMapping";
import {Colors, Popover, PopoverInteractionKind} from "@blueprintjs/core";

const MainActivation = styled.span`
white-space: pre;
Expand Down Expand Up @@ -56,7 +56,7 @@ function FeatureActivationTextDescription(props: {

let contextDialogContent = (
<Definition>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{display: "flex", alignItems: "center"}}>
<Emoji>{categoryDesc.emoji}</Emoji> <h2>{categoryDesc.id}</h2>
</div>
<p>{categoryDesc.definition}</p>
Expand All @@ -66,12 +66,10 @@ function FeatureActivationTextDescription(props: {
activationSpans.push(
<span key={activation.categoryId}>
{activation.prefix}
<Popover
interactionKind={PopoverInteractionKind.HOVER}
hoverOpenDelay={50}
hoverCloseDelay={50}
disabled={props.popOverDisabled}
>
<Popover interactionKind={PopoverInteractionKind.HOVER}
hoverOpenDelay={50}
hoverCloseDelay={50}
disabled={props.popOverDisabled}>
<MainActivation className={props.popOverDisabled ? "" : "allowHover"}>
{activation.mainActivationAsText}
</MainActivation>
Expand All @@ -82,15 +80,15 @@ function FeatureActivationTextDescription(props: {
{i === activations.length - 2
? categoryActivationsObject.lastConnector
: i < activations.length - 2
? categoryActivationsObject.connector
: ""}
? categoryActivationsObject.connector
: ""}
</span>
);
i++;
}

return (
<span style={{ fontSize: "1.1rem", margin: 0 }}>
<span style={{fontSize: "1.1rem", margin: 0}}>
{categoryActivationsObject.username} {activationSpans}.
</span>
);
Expand Down
21 changes: 13 additions & 8 deletions frontend/src/WordCloud.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from "react";
import styled from "styled-components";
import {CategoryValueDescription} from "./FeaturesToTextMapping";

const StyledText = styled.text`
transition: 0.5s;
Expand All @@ -11,28 +12,32 @@ class WordCloud extends React.PureComponent<{
strongestFeatures: number[];
maxExplanationValue: number;
colorPalette: string[];
categoryActivationsObject: CategoryValueDescription;

}> {
render() {
const { strongestLabels, strongestFeatures, maxExplanationValue, allLabels, colorPalette } = this.props;
const {strongestLabels, strongestFeatures, maxExplanationValue, allLabels, colorPalette, categoryActivationsObject} = this.props;

return (
<svg style={{ width: "40%", height: "40%" }} transform="scale(2)">
{allLabels.map((value, index) => {
const sortedIndex = strongestLabels.indexOf(value);
<svg style={{width: "40%", height: "40%"}} transform="scale(2)">
{allLabels.map((category, index) => {
const sortedIndex = strongestLabels.indexOf(category);
const scalar = sortedIndex > -1 ? strongestFeatures[sortedIndex] / maxExplanationValue : 0;
if (sortedIndex === -1 || sortedIndex > 4) {
return (
<StyledText
key={value}
key={category}
fill={colorPalette[index % colorPalette.length]}
style={{
transform: "scale(0)",
fontSize: "15px",
fontWeight: 300,
opacity: 0
}}>{value}</StyledText>
}} />
);
}
const activation = categoryActivationsObject.categoryActivations
.find(value => value.categoryId === category)?.mainActivationAsText
const transform = [
"translate(40px, 50px)",
"translate(80px, 20px)",
Expand All @@ -46,14 +51,14 @@ class WordCloud extends React.PureComponent<{

return (
<StyledText
key={value}
key={category}
fill={colorPalette[index % colorPalette.length]}
style={{
transform,
transformOrigin,
fontSize,
fontWeight
}}>{value}</StyledText>
}}>{activation}</StyledText>
);
})}
</svg>
Expand Down
28 changes: 12 additions & 16 deletions frontend/src/loadEngagementData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ const loadEngagementData = async (
dataContainer.data = smoothData(dataContainer.data, windowSize);
}

let discreteDataContainer: DataContainerType = JSON.parse(JSON.stringify(dataContainer));
const discreteDataContainer: DataContainerType = JSON.parse(JSON.stringify(dataContainer));
discreteDataContainer.data = discretizeDataPoints(discreteDataContainer.data, dataContainer.sampleRate * 3, false);
dataContainer.data = discretizeDataPoints(dataContainer.data, dataContainer.sampleRate * 3, true);

let maxValues = maxExplanationsAndMinMaxInputValue(dataContainer.data);
const maxValues = maxExplanationsAndMinMaxInputValue(dataContainer.data);
dataContainer.maxExplanationValue = maxValues.maxExplanation;
dataContainer.maxInputs = maxValues.maxInputValues;
dataContainer.minInputs = maxValues.minInputValues;
Expand Down Expand Up @@ -271,18 +271,14 @@ const smoothUsingPredictions = (dataContainer: DataContainerType, windowSize: nu
};

const discretizeDataPoints = (data: DataPointType[], intervalFrames: number, outputOnly = false): DataPointType[] => {
let inputLength = data[0].input.length;
let outputLength = data[0].output.length;
let explanationsLength = data[0].explanations.length;
const inputLength = data[0].input.length;
const outputLength = data[0].output.length;
const explanationsLength = data[0].explanations.length;
const smoothedData: DataPointType[] = Array(data.length);
for (let currentIntervalStart = 0; currentIntervalStart < data.length; currentIntervalStart += intervalFrames) {
let currentIntervalEnd = currentIntervalStart + intervalFrames;
if (currentIntervalEnd > data.length) currentIntervalEnd = data.length;
let currentIntervalLength = currentIntervalEnd - currentIntervalStart;
if (currentIntervalLength === 0) {
break;
}
let averagePoint: DataPointType = {
let currentIntervalEnd = Math.min(currentIntervalStart + intervalFrames, data.length)
const currentIntervalLength = currentIntervalEnd - currentIntervalStart;
const averagePoint: DataPointType = {
input: new Array(inputLength).fill(0),
output: new Array(outputLength).fill(0),
explanations: new Array(explanationsLength).fill(new Array(data[0].explanations[0].length).fill(0))
Expand All @@ -294,10 +290,10 @@ const discretizeDataPoints = (data: DataPointType[], intervalFrames: number, out
row.map((val, k) => val + data[i].explanations[j][k])
);
}
averagePoint.input = averagePoint.input.map((val) => val / currentIntervalLength);
averagePoint.output = averagePoint.output.map((val) => val / currentIntervalLength);
averagePoint.explanations = averagePoint.explanations.map((row) =>
row.map((val) => val / currentIntervalLength)
averagePoint.input = averagePoint.input.map(val => val / currentIntervalLength);
averagePoint.output = averagePoint.output.map(val => val / currentIntervalLength);
averagePoint.explanations = averagePoint.explanations.map(row =>
row.map(val => val / currentIntervalLength)
);

for (let i = currentIntervalStart; i < currentIntervalEnd; i++) {
Expand Down

0 comments on commit 60403b1

Please sign in to comment.