diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 952d4a0..57c72c4 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -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 { diff --git a/frontend/src/BarChart.tsx b/frontend/src/BarChart.tsx index 00ccbd2..5290848 100644 --- a/frontend/src/BarChart.tsx +++ b/frontend/src/BarChart.tsx @@ -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)` @@ -28,7 +30,7 @@ class BarChart extends React.Component item.mainActivationAsText), datasets: [ { label: "Testing Explanations", diff --git a/frontend/src/EngagementDefinitions.tsx b/frontend/src/EngagementDefinitions.tsx index baa34ba..bf87f77 100644 --- a/frontend/src/EngagementDefinitions.tsx +++ b/frontend/src/EngagementDefinitions.tsx @@ -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 = [ diff --git a/frontend/src/ExplanationsContainer.tsx b/frontend/src/ExplanationsContainer.tsx index fb5c004..c061992 100644 --- a/frontend/src/ExplanationsContainer.tsx +++ b/frontend/src/ExplanationsContainer.tsx @@ -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"; @@ -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 ; - if (!discreteDataPoint) return ; - const { input, output, explanations } = dataPoint; + const { + maxInputValues, minInputValues, maxExplanationValue, dataPoint, discreteDataPoint, username, gender, + labels, mode + } = props; + if (!dataPoint) return ; + if (!discreteDataPoint) return ; + 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; @@ -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 ( - - + + Based on: 0} /> - + {mode === "bar" ? ( - 0 ? 1 : 0 }}>UNSURE + 0 ? 1 : 0}}>UNSURE ); } diff --git a/frontend/src/ExplanationsHeading.tsx b/frontend/src/ExplanationsHeading.tsx index 68a889d..78d0d5d 100644 --- a/frontend/src/ExplanationsHeading.tsx +++ b/frontend/src/ExplanationsHeading.tsx @@ -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; } `; diff --git a/frontend/src/FeatureActivationTextDescription.tsx b/frontend/src/FeatureActivationTextDescription.tsx index 76621be..3e3e117 100644 --- a/frontend/src/FeatureActivationTextDescription.tsx +++ b/frontend/src/FeatureActivationTextDescription.tsx @@ -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; @@ -56,7 +56,7 @@ function FeatureActivationTextDescription(props: { let contextDialogContent = ( -
+
{categoryDesc.emoji}

{categoryDesc.id}

{categoryDesc.definition}

@@ -66,12 +66,10 @@ function FeatureActivationTextDescription(props: { activationSpans.push( {activation.prefix} - + {activation.mainActivationAsText} @@ -82,15 +80,15 @@ function FeatureActivationTextDescription(props: { {i === activations.length - 2 ? categoryActivationsObject.lastConnector : i < activations.length - 2 - ? categoryActivationsObject.connector - : ""} + ? categoryActivationsObject.connector + : ""} ); i++; } return ( - + {categoryActivationsObject.username} {activationSpans}. ); diff --git a/frontend/src/WordCloud.tsx b/frontend/src/WordCloud.tsx index 26f294f..f68db4a 100644 --- a/frontend/src/WordCloud.tsx +++ b/frontend/src/WordCloud.tsx @@ -1,5 +1,6 @@ import React from "react"; import styled from "styled-components"; +import {CategoryValueDescription} from "./FeaturesToTextMapping"; const StyledText = styled.text` transition: 0.5s; @@ -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 ( - - {allLabels.map((value, index) => { - const sortedIndex = strongestLabels.indexOf(value); + + {allLabels.map((category, index) => { + const sortedIndex = strongestLabels.indexOf(category); const scalar = sortedIndex > -1 ? strongestFeatures[sortedIndex] / maxExplanationValue : 0; if (sortedIndex === -1 || sortedIndex > 4) { return ( {value} + }} /> ); } + const activation = categoryActivationsObject.categoryActivations + .find(value => value.categoryId === category)?.mainActivationAsText const transform = [ "translate(40px, 50px)", "translate(80px, 20px)", @@ -46,14 +51,14 @@ class WordCloud extends React.PureComponent<{ return ( {value} + }}>{activation} ); })} diff --git a/frontend/src/loadEngagementData.ts b/frontend/src/loadEngagementData.ts index dd21c8b..d94274f 100644 --- a/frontend/src/loadEngagementData.ts +++ b/frontend/src/loadEngagementData.ts @@ -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; @@ -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)) @@ -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++) {