Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 9dfae3d

Browse files
authored
UX: Convert sentiment analysis overview to horizontal bars (#1216)
The sentiment analysis report page initially showcases sentiments via category/tag by doughnut visualizations. However, this isn't an optimal view for quickly scanning and comparing each result. This PR updates the overview to include a table visualization with horizontal bars to represent sentiment analysis instead of doughnuts. Doughnut visualizations are still maintained however when accessing the sentiment data in the drill down for individual entries. This approach is an intermediary step, as we will eventually add whole clustering and sizing visualization instead of a table. As such, no relevant tests are added in this PR.
1 parent 76a4878 commit 9dfae3d

File tree

4 files changed

+157
-23
lines changed

4 files changed

+157
-23
lines changed

assets/javascripts/discourse/components/admin-report-sentiment-analysis.gjs

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import closeOnClickOutside from "discourse/modifiers/close-on-click-outside";
2323
import { i18n } from "discourse-i18n";
2424
import DTooltip from "float-kit/components/d-tooltip";
2525
import DoughnutChart from "discourse/plugins/discourse-ai/discourse/components/doughnut-chart";
26+
import AiSentimentHorizontalBar from "../components/ai-sentiment-horizontal-bar";
2627

2728
export default class AdminReportSentimentAnalysis extends Component {
2829
@service router;
@@ -82,6 +83,15 @@ export default class AdminReportSentimentAnalysis extends Component {
8283
}
8384
}
8485

86+
get groupingType() {
87+
const dataSample = this.args.model.data[0];
88+
const localePrefix =
89+
"discourse_ai.sentiments.sentiment_analysis.group_types";
90+
return dataSample.category_name
91+
? i18n(`${localePrefix}.category`)
92+
: i18n(`${localePrefix}.tag`);
93+
}
94+
8595
get colors() {
8696
return ["#2ecc71", "#95a5a6", "#e74c3c"];
8797
}
@@ -107,7 +117,17 @@ export default class AdminReportSentimentAnalysis extends Component {
107117
this.calculateNeutralScore(data),
108118
data.negative_count,
109119
],
120+
score_map: {
121+
positive: data.positive_count,
122+
neutral: this.calculateNeutralScore(data),
123+
negative: data.negative_count,
124+
},
110125
total_score: data.total_count,
126+
widths: {
127+
positive: (data.positive_count / data.total_count) * 100,
128+
neutral: (this.calculateNeutralScore(data) / data.total_count) * 100,
129+
negative: (data.negative_count / data.total_count) * 100,
130+
},
111131
};
112132
});
113133
}
@@ -302,29 +322,56 @@ export default class AdminReportSentimentAnalysis extends Component {
302322

303323
{{#unless this.showingSelectedChart}}
304324
<div class="admin-report-sentiment-analysis">
305-
{{#each this.transformedData as |data|}}
306-
<div
307-
class="admin-report-sentiment-analysis__chart-wrapper"
308-
role="button"
309-
{{on "click" (fn this.showDetails data)}}
310-
{{closeOnClickOutside
311-
(fn (mut this.selectedChart) null)
312-
(hash
313-
targetSelector=".admin-report-sentiment-analysis-details"
314-
secondaryTargetSelector=".admin-report-sentiment-analysis"
315-
)
316-
}}
317-
>
318-
<DoughnutChart
319-
@labels={{@model.labels}}
320-
@colors={{this.colors}}
321-
@data={{data.scores}}
322-
@totalScore={{data.total_score}}
323-
@doughnutTitle={{data.title}}
324-
@displayLegend={{true}}
325-
/>
326-
</div>
327-
{{/each}}
325+
<table class="sentiment-analysis-table md-table">
326+
<thead>
327+
<th>{{this.groupingType}}</th>
328+
<th>{{i18n
329+
"discourse_ai.sentiments.sentiment_analysis.table.total_count"
330+
}}</th>
331+
<th>{{i18n
332+
"discourse_ai.sentiments.sentiment_analysis.table.sentiment"
333+
}}</th>
334+
</thead>
335+
336+
<tbody>
337+
{{#each this.transformedData as |data|}}
338+
<tr
339+
class="sentiment-analysis-table__row"
340+
role="button"
341+
{{on "click" (fn this.showDetails data)}}
342+
{{closeOnClickOutside
343+
(fn (mut this.selectedChart) null)
344+
(hash
345+
targetSelector=".admin-report-sentiment-analysis-details"
346+
secondaryTargetSelector=".admin-report-sentiment-analysis"
347+
)
348+
}}
349+
>
350+
<td class="sentiment-analysis-table__title">{{data.title}}</td>
351+
<td
352+
class="sentiment-analysis-table__total-score"
353+
>{{data.total_score}}</td>
354+
<td class="sentiment-horizontal-bar">
355+
<AiSentimentHorizontalBar
356+
@type="positive"
357+
@score={{data.score_map.positive}}
358+
@width={{data.widths.positive}}
359+
/>
360+
<AiSentimentHorizontalBar
361+
@type="negative"
362+
@score={{data.score_map.negative}}
363+
@width={{data.widths.negative}}
364+
/>
365+
<AiSentimentHorizontalBar
366+
@type="neutral"
367+
@score={{data.score_map.neutral}}
368+
@width={{data.widths.neutral}}
369+
/>
370+
</td>
371+
</tr>
372+
{{/each}}
373+
</tbody>
374+
</table>
328375
</div>
329376
{{/unless}}
330377

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { concat } from "@ember/helper";
2+
import { htmlSafe } from "@ember/template";
3+
import { gt } from "truth-helpers";
4+
import { i18n } from "discourse-i18n";
5+
import DTooltip from "float-kit/components/d-tooltip";
6+
7+
const AiSentimentHorizontalBar = <template>
8+
{{#if (gt @score 0)}}
9+
<DTooltip
10+
class={{concat "sentiment-horizontal-bar__" @type}}
11+
style={{htmlSafe (concat "width: " @width "%")}}
12+
>
13+
<:trigger>
14+
<span class="sentiment-horizontal-bar__count">
15+
{{@score}}
16+
</span>
17+
</:trigger>
18+
<:content>
19+
{{i18n
20+
(concat
21+
"discourse_ai.sentiments.sentiment_analysis.filter_types." @type
22+
)
23+
}}:
24+
{{@score}}
25+
</:content>
26+
</DTooltip>
27+
{{/if}}
28+
</template>;
29+
30+
export default AiSentimentHorizontalBar;

assets/stylesheets/modules/sentiment/common/dashboard.scss

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,54 @@
248248
display: none;
249249
}
250250
}
251+
252+
.sentiment-analysis-table {
253+
margin: 1rem;
254+
255+
&__total-score {
256+
font-weight: bold;
257+
font-size: var(--font-up-1);
258+
}
259+
260+
&__row {
261+
cursor: pointer;
262+
}
263+
}
264+
265+
.sentiment-horizontal-bar {
266+
display: flex;
267+
268+
&__count {
269+
font-weight: bold;
270+
font-size: var(--font-down-1);
271+
color: var(--secondary);
272+
}
273+
274+
&__positive,
275+
&__neutral,
276+
&__negative {
277+
display: flex;
278+
flex-flow: column nowrap;
279+
justify-content: flex-end;
280+
align-items: center;
281+
padding: 0.75rem;
282+
border-left: 2px solid var(--secondary);
283+
border-right: 2px solid var(--secondary);
284+
}
285+
286+
&__positive {
287+
background: rgb(var(--d-sentiment-report-positive-rgb));
288+
border-top-left-radius: var(--d-border-radius);
289+
border-bottom-left-radius: var(--d-border-radius);
290+
}
291+
292+
&__negative {
293+
background: rgb(var(--d-sentiment-report-negative-rgb));
294+
}
295+
296+
&__neutral {
297+
background: rgb(var(--d-sentiment-report-neutral-rgb));
298+
border-top-right-radius: var(--d-border-radius);
299+
border-bottom-right-radius: var(--d-border-radius);
300+
}
301+
}

config/locales/client.en.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,12 @@ en:
700700
positive: "Positive"
701701
neutral: "Neutral"
702702
negative: "Negative"
703+
group_types:
704+
category: "Category"
705+
tag: "Tag"
706+
table:
707+
sentiment: "Sentiment"
708+
total_count: "Total"
703709

704710
summarization:
705711
chat:

0 commit comments

Comments
 (0)