diff --git a/src/app/base/services/project/project-apollo.service.ts b/src/app/base/services/project/project-apollo.service.ts index 76890779..022d5092 100644 --- a/src/app/base/services/project/project-apollo.service.ts +++ b/src/app/base/services/project/project-apollo.service.ts @@ -199,6 +199,24 @@ export class ProjectApolloService { ); } + getConfidenceDistributions(projectId: string, labelingTaskId: string = null, sliceId: string = null) { + return this.apollo + .query({ + query: queries.GET_CONFIDENCE_DISTRIBUTION, + variables: { + projectId: projectId, + labelingTaskId: labelingTaskId, + sliceId: sliceId, + }, + fetchPolicy: 'no-cache', + }) + .pipe( + map((result) => { + return JSON.parse(result['data']['confidenceDistribution']); + }) + ); + } + getProjectByIdQuery( projectId: string ) { diff --git a/src/app/base/services/project/project-queries.ts b/src/app/base/services/project/project-queries.ts index c82fff2d..330dd5e9 100644 --- a/src/app/base/services/project/project-queries.ts +++ b/src/app/base/services/project/project-queries.ts @@ -119,6 +119,12 @@ export const queries = { } `, + GET_CONFIDENCE_DISTRIBUTION: gql` + query ($projectId: ID!, $labelingTaskId: ID, $sliceId: ID) { + confidenceDistribution(projectId: $projectId, labelingTaskId: $labelingTaskId, sliceId: $sliceId) + } + `, + GET_ATTRIBUTES_BY_PROJECT_ID: gql` query($projectId: ID!){ attributesByProjectId(projectId: $projectId) { diff --git a/src/app/charts/charts.module.ts b/src/app/charts/charts.module.ts index 7fa0d4a2..9e68224f 100644 --- a/src/app/charts/charts.module.ts +++ b/src/app/charts/charts.module.ts @@ -6,21 +6,23 @@ import { BarChartComponent } from './components/bar-chart/bar-chart.component'; import { HorizontalGroupedBarChartComponent } from './components/horizontal-grouped-bar-chart/horizontal-grouped-bar-chart.component'; import { BoxplotComponent } from './components/boxplot/boxplot.component'; import { ConfusionMatrixComponent } from './components/confusion-matrix/confusion-matrix.component'; +import { LineChartComponent } from './components/line-chart/line-chart.component'; @NgModule({ - declarations: [GroupedBarChartComponent, BarChartComponent, HorizontalGroupedBarChartComponent, BoxplotComponent, ConfusionMatrixComponent], + declarations: [LineChartComponent, GroupedBarChartComponent, BarChartComponent, HorizontalGroupedBarChartComponent, BoxplotComponent, ConfusionMatrixComponent], imports: [ CommonModule, AppRoutingModule ], exports: [ + LineChartComponent, GroupedBarChartComponent, BarChartComponent, HorizontalGroupedBarChartComponent, BoxplotComponent, - ConfusionMatrixComponent + ConfusionMatrixComponent, ] }) export class ChartsModule { } diff --git a/src/app/charts/components/line-chart/line-chart.component.html b/src/app/charts/components/line-chart/line-chart.component.html new file mode 100644 index 00000000..330dbb47 --- /dev/null +++ b/src/app/charts/components/line-chart/line-chart.component.html @@ -0,0 +1,2 @@ +
+
\ No newline at end of file diff --git a/src/app/charts/components/line-chart/line-chart.component.scss b/src/app/charts/components/line-chart/line-chart.component.scss new file mode 100644 index 00000000..0befd653 --- /dev/null +++ b/src/app/charts/components/line-chart/line-chart.component.scss @@ -0,0 +1,13 @@ +.lineChartTooltip { + position: absolute; + display: none; + width: auto; + height: auto; + background: #F3F4F6; + border: 0 none; + border-radius: 4px; + color: #0C052E; + font: 14px sans-serif; + padding: 5px; + text-align: center; +} \ No newline at end of file diff --git a/src/app/charts/components/line-chart/line-chart.component.spec.ts b/src/app/charts/components/line-chart/line-chart.component.spec.ts new file mode 100644 index 00000000..400ba17b --- /dev/null +++ b/src/app/charts/components/line-chart/line-chart.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LineChartComponent } from './line-chart.component'; + +describe('LineChartComponent', () => { + let component: LineChartComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [LineChartComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LineChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/charts/components/line-chart/line-chart.component.ts b/src/app/charts/components/line-chart/line-chart.component.ts new file mode 100644 index 00000000..6b81a0fd --- /dev/null +++ b/src/app/charts/components/line-chart/line-chart.component.ts @@ -0,0 +1,119 @@ +import { Component, Input, OnChanges, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; +import { fromEvent, Observable, Subscription } from "rxjs"; + +import * as d3 from 'd3v4'; + +@Component({ + selector: 'kern-line-chart', + encapsulation: ViewEncapsulation.None, + templateUrl: './line-chart.component.html', + styleUrls: ['./line-chart.component.scss'], +}) +export class LineChartComponent implements OnInit, OnChanges { + @Input() data: any; + + resizeObservable$: Observable + resizeSubscription$: Subscription + + constructor() { } + + ngOnInit(): void { + this.resizeObservable$ = fromEvent(window, 'resize') + this.resizeSubscription$ = this.resizeObservable$.subscribe(evt => { + this.initSVG(this.data); + }) + } + + + ngOnChanges(changes: SimpleChanges): void { + this.initSVG(this.data); + } + + initSVG(data_) { + + let data = [] + for (var i = 0; i < data_.length; i++) { + data.push({ idx: i, value: data_[i] * 100 }); + } + + // set the dimensions and margins of the graph + var margin = { top: 40, right: 20, bottom: 40, left: 50 }, + width = +(window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth) * 0.78 + margin.left + margin.right, + height = 450 - margin.top - margin.bottom; + + d3.select("#line-chart").selectAll("*").remove(); + + // append the svg object to the body of the page + var svg = d3.select("#line-chart") + .append("svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom) + .append("g") + .attr("transform", + "translate(" + margin.left + "," + margin.top + ")"); + + // Add X axis + var x = d3.scaleLinear() + .domain(d3.extent(data, function (d) { return d.idx; })) + .range([0, width]); + + + // Add Y axis + var y = d3.scaleLinear() + .domain([0, d3.max(data, function (d) { return +d.value; })]) + .range([height, 0]); + + + // Add the area + svg.append("path") + .datum(data) + .attr("fill", "#bbf7d0") + .attr("stroke", "#22c55e") + .attr("stroke-width", 1.5) + .attr("d", d3.area() + .x(function (d) { return x(d.idx) }) + .y0(y(0)) + .y1(function (d) { return y(d.value) }) + ) + + svg.append("g") + .style("font-size", 14) + .style('font-family', '"DM Sans", sans-serif') + .call(d3.axisLeft(y)) + .append("text") + .attr("x", 0) + .attr("dy", 15) + .attr("fill", "#000") + .attr("text-anchor", "end") + .attr("transform", "rotate(-90)") + .text("Confidence score (%)"); + + if (data.length >= 100) { + svg.append("g") + .attr("transform", "translate(0," + height + ")") + .style("font-size", 14) + .style('font-family', '"DM Sans", sans-serif') + .call(d3.axisBottom(x)) + .append("text") + .attr("y", -5) + .attr("dx", width) + .attr("fill", "#000") + .attr("text-anchor", "end") + .text("Percentile (%)"); + } else { + svg.append("g") + .attr("transform", "translate(0," + height + ")") + .style("font-size", 14) + .style('font-family', '"DM Sans", sans-serif') + .call(d3.axisBottom(x)) + .append("text") + .attr("y", -5) + .attr("dx", width) + .attr("fill", "#000") + .attr("text-anchor", "end") + .text("Record"); + } + + }; + +} diff --git a/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.html b/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.html new file mode 100644 index 00000000..ce365129 --- /dev/null +++ b/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.html @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.scss b/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.spec.ts b/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.spec.ts new file mode 100644 index 00000000..0dd3f4b7 --- /dev/null +++ b/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ConfidenceLineChartComponent } from './confidence-line-chart.component'; + +describe('ConfidenceLineChartComponent', () => { + let component: ConfidenceLineChartComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ConfidenceLineChartComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfidenceLineChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.ts b/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.ts new file mode 100644 index 00000000..d1523aed --- /dev/null +++ b/src/app/monitor/components/confidence-line-chart/confidence-line-chart.component.ts @@ -0,0 +1,14 @@ +import { Component, Input, OnInit } from '@angular/core'; + +@Component({ + selector: 'kern-confidence-line-chart', + templateUrl: './confidence-line-chart.component.html', + styleUrls: ['./confidence-line-chart.component.scss'], +}) +export class ConfidenceLineChartComponent implements OnInit { + constructor() { } + @Input() dataInput: any; + + ngOnInit(): void { + } +} diff --git a/src/app/project-overview/components/project-overview/project-overview.component.html b/src/app/project-overview/components/project-overview/project-overview.component.html index 601d82d2..29fbcbf9 100644 --- a/src/app/project-overview/components/project-overview/project-overview.component.html +++ b/src/app/project-overview/components/project-overview/project-overview.component.html @@ -219,6 +219,22 @@ +
+
+ Confidence distribution +
+
See the confidence distribution + of your weakly supervised records.
+
+
+ + +
+
+
+
diff --git a/src/app/project-overview/components/project-overview/project-overview.component.ts b/src/app/project-overview/components/project-overview/project-overview.component.ts index 450cb4ee..fbd34fdd 100644 --- a/src/app/project-overview/components/project-overview/project-overview.component.ts +++ b/src/app/project-overview/components/project-overview/project-overview.component.ts @@ -34,6 +34,8 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy { description: string = ''; descriptionOpen: boolean = false; + lineChartData: any; + newLabel = new FormControl(''); colors = schemeCategory24; downloadMessage: DownloadState = DownloadState.NONE; @@ -113,6 +115,7 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy { this.labels = this.labelingTasksMap.get(labelingTaskId).labels; this.setDisplayNERConfusion(currentProjectID, labelingTaskId); this.getLabelDistributions(currentProjectID, labelingTaskId); + this.getConfidenceDistributions(currentProjectID, labelingTaskId); this.getConfusionMatrix(currentProjectID, labelingTaskId); this.getInterAnnotatorMatrix(currentProjectID, labelingTaskId); this.refreshProjectStats(currentProjectID); @@ -121,6 +124,7 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy { this.dataSliceForm.valueChanges.pipe(debounceTime(50)).subscribe((sliceId) => { this.getLabelDistributions(currentProjectID, this.labelingTasksForm.value); + this.getConfidenceDistributions(currentProjectID, this.labelingTasksForm.value); this.getConfusionMatrix(currentProjectID, this.labelingTasksForm.value); this.getInterAnnotatorMatrix(currentProjectID, this.labelingTasksForm.value); this.refreshProjectStats(currentProjectID); @@ -313,6 +317,18 @@ export class ProjectOverviewComponent implements OnInit, OnDestroy { }); } + getConfidenceDistributions(projectId: string, labelingTaskId: string): void { + const dataSliceId = this.dataSliceForm.value == "@@NO_SLICE@@" ? null : this.dataSliceForm.value; + this.labelDistribution = null; + this.projectApolloService.getConfidenceDistributions( + projectId, + labelingTaskId, + dataSliceId + ).pipe(first()).subscribe((confidenceDist) => { + this.lineChartData = confidenceDist; + }); + } + private matchAndMergeLabelDistributionData(data) { let returnData = []; data.forEach(e => { diff --git a/src/app/project-overview/components/project-overview/project-overview.helper.ts b/src/app/project-overview/components/project-overview/project-overview.helper.ts index 66d072a3..2c116cd8 100644 --- a/src/app/project-overview/components/project-overview/project-overview.helper.ts +++ b/src/app/project-overview/components/project-overview/project-overview.helper.ts @@ -5,6 +5,7 @@ export enum DisplayGraphs { CONFUSION_MATRIX, INTER_ANNOTATOR, LABEL_DISTRIBUTION, + CONFIDENCE_DISTRIBUTION, } export function displayGraphsTypeToString(source: DisplayGraphs) { @@ -13,6 +14,7 @@ export function displayGraphsTypeToString(source: DisplayGraphs) { case DisplayGraphs.CONFUSION_MATRIX: return "Confusion Matrix"; case DisplayGraphs.INTER_ANNOTATOR: return "Inter Annotator"; case DisplayGraphs.LABEL_DISTRIBUTION: return "Label Distribution"; + case DisplayGraphs.CONFIDENCE_DISTRIBUTION: return "Confidence Distribution"; default: return ""; } } diff --git a/src/app/project-overview/project-overview.module.ts b/src/app/project-overview/project-overview.module.ts index e90aa324..10b358d6 100644 --- a/src/app/project-overview/project-overview.module.ts +++ b/src/app/project-overview/project-overview.module.ts @@ -9,13 +9,15 @@ import { ChartsModule } from '../charts/charts.module'; import { LabelDistributionBarChartComponent } from '../monitor/components/label-distribution-bar-chart/label-distribution-bar-chart.component'; import { ConfusionHeatmapComponent } from '../monitor/components/confusion-heatmap/confusion-heatmap.component'; import { InterAnnotatorComponent } from '../monitor/components/inter-annotator/inter-annotator.component'; +import { ConfidenceLineChartComponent } from '../monitor/components/confidence-line-chart/confidence-line-chart.component'; @NgModule({ - declarations: [ProjectOverviewComponent, LabelDistributionBarChartComponent, ConfusionHeatmapComponent, InterAnnotatorComponent], + declarations: [ProjectOverviewComponent, LabelDistributionBarChartComponent, ConfusionHeatmapComponent, InterAnnotatorComponent, ConfidenceLineChartComponent], imports: [ CommonModule, BaseModule, ImportModule, - ChartsModule], + ChartsModule + ], }) export class ProjectOverviewModule { }