Skip to content
This repository was archived by the owner on Jun 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions frontend/MainContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ const Container: React.FunctionComponent<ContainerProps> = ({
}
}

const getDetailedAnalysisComponent = () => {
if (selectedTextIds.length == 0) {
return (<React.Fragment />);
} else {
return (
<TextDetailedAnalysis
selectedText={analysisResults.filter(item => selectedTextIds.includes(item.id))} />
);
}
}

return (
<div className="m-4 font-sans">
<TextGenerationControl generateText={generateText} />
Expand All @@ -56,8 +67,7 @@ const Container: React.FunctionComponent<ContainerProps> = ({
error={error}
onSelectTextId={onSelectTextId}
selectedTextIds={selectedTextIds} />
<TextDetailedAnalysis
selectedText={analysisResults.filter(item => selectedTextIds.includes(item.id))} />
{getDetailedAnalysisComponent()}
</div>
</div>
);
Expand Down
151 changes: 151 additions & 0 deletions frontend/components/PerspectiveScoresSpiderChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, { Component } from "react";
import ReactDOM from "react-dom";
import * as d3 from "d3";
import { DirectionalHint } from 'office-ui-fabric-react/lib/Tooltip';
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';


type PerspectiveScoresSpiderChartProps = {
scores: any,
axes: any,
colors: any
}

class PerspectiveScoresSpiderChart extends Component<PerspectiveScoresSpiderChartProps, null> {

constructor(props) {
super(props);

this.node = React.createRef<HTMLDivElement>();
this.domID = `perspectivescoresradarchart`;
}

node: React.RefObject<HTMLDivElement>

domID: string

componentDidMount() {
this.createDistributionBarChart();
}

componentDidUpdate() {
this.createDistributionBarChart();
}

setSelectedScore = (selectedScore: string) => {
this.setState({
selectedScore: selectedScore
});
}

createDistributionBarChart = () => {
var _this = this;
var body = d3.select(this.node.current);

var margin = { top: 5, right: 15, bottom: 50, left: 55 }
var h = 111 - margin.top - margin.bottom
var w = 200;

// SVG
d3.select(`#${this.domID}`).remove();
var svg = body.append('svg')
.attr('id', this.domID)
.attr('height', 310)
.attr('width', 310)
.attr('float', 'left');

var radialScale = d3.scaleLinear()
.domain([0.0, 1.0])
.range([0,100]);
var ticks = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];

ticks.map(t =>
svg.append("circle")
.attr("cx", 155)
.attr("cy", 155)
.attr("fill", "none")
.attr("stroke", "gray")
.attr("r", radialScale(t))
);

ticks.map(t =>
svg.append("text")
.attr("x", 160)
.attr("y", 155 - radialScale(t))
.attr("font-size", "8px")
.text(t.toString())
);

const angleToCoordinate = (angle: number, value: number) => {
let x = Math.cos(angle) * radialScale(value);
let y = Math.sin(angle) * radialScale(value);
return {"x": 155 + x, "y": 155 - y};
}

for (var i = 0; i < this.props.axes.length; i++) {
let axisName = this.props.axes[i][0];
let angle = (Math.PI / 2) + (2 * Math.PI * i / this.props.axes.length);
let line_coordinate = angleToCoordinate(angle, 1.0);
let label_coordinate = angleToCoordinate(angle, 1.05);

//draw axis line
svg.append("line")
.attr("x1", 155)
.attr("y1", 155)
.attr("x2", line_coordinate.x)
.attr("y2", line_coordinate.y)
.attr("stroke","black");

//draw axis label
svg.append("text")
.attr("x", label_coordinate.x)
.attr("y", label_coordinate.y)
.attr("font-size", "12px")
.text(axisName);
}

let line = d3.line()
.x(d => d.x)
.y(d => d.y);
let colors = ["darkorange", "gray", "navy"];

const getPathCoordinates = (scoreData: any) => {
let coordinates = [];
for (var i = 0; i < this.props.axes.length; i++){
let axisFeatureName = this.props.axes[i][1];
let angle = (Math.PI / 2) + (2 * Math.PI * i / this.props.axes.length);
coordinates.push(angleToCoordinate(angle, scoreData[axisFeatureName]));
}
return coordinates;
}

for (var i = 0; i < this.props.scores.length; i ++){
let d = this.props.scores[i];
let color = this.props.colors[i % this.props.colors.length];
let coordinates = getPathCoordinates(d);

//draw the path element
svg.append("path")
.datum(coordinates)
.attr("d",line)
.attr("stroke-width", 3)
.attr("stroke", color)
.attr("fill", color)
.attr("stroke-opacity", 1)
.attr("opacity", 0.5);
}

}

render() {
return (
<div className="plot plot-distribution">
<div ref={this.node}/>
</div>
);
}
}



export default PerspectiveScoresSpiderChart;
17 changes: 16 additions & 1 deletion frontend/components/TextDetailedAnalysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as _ from "lodash"
import ReactDOM from "react-dom";
import AnalyzedText from "../models/AnalyzedText";
import {BiEdit} from "react-icons/bi";
import PerspectiveScoresSpiderChart from "./PerspectiveScoresSpiderChart";

type TextDetailedAnalysisProps = {
selectedText: AnalyzedText[],
Expand All @@ -17,10 +18,24 @@ class TextGenerationResults extends React.Component<TextDetailedAnalysisProps, n

render() {
const tabColors = ["#FD8A40", "#60B877", "#F03C92", "#8B3CF0"];

return (
<div>
<div className="mb-4 text-2xl">
d3 SVG chart here
<PerspectiveScoresSpiderChart
scores={this.props.selectedText}
axes={[
["Toxicity", "toxicity"],
["Severe Toxicity", "severeToxicity"],
["Identity Attack", "identityAttack"],
["Insult", "insult"],
["Profanity", "profanity"],
["Threat", "threat"],
["Sexually Explicit", "sexuallyExplicit"],
["Flirtation", "flirtation"]
]}
colors={tabColors}
/>
</div>
{this.props.selectedText.map((item, index) =>
<div className="flex items-stretch mb-4" style={{height: "110px"}}>
Expand Down