From e7785e5fb3294a3105179a61c4bd5d61790d5610 Mon Sep 17 00:00:00 2001 From: xuke444 <40614413+xuke444@users.noreply.github.com> Date: Mon, 10 May 2021 22:01:30 -0700 Subject: [PATCH] disable zoom in error analysis tree (#469) * disable zoom Signed-off-by: Ke Xu * fix label Signed-off-by: Ke Xu * remove transition group Signed-off-by: Ke Xu * reformat tree Signed-off-by: Ke Xu * fix lint Signed-off-by: Ke Xu * add width Signed-off-by: Ke Xu * adjust size Signed-off-by: Ke Xu * fix lint Signed-off-by: Ke Xu * adjust tree Signed-off-by: Ke Xu * adjust width & hright Signed-off-by: Ke Xu * adjust min width Signed-off-by: Ke Xu * update snapshot Signed-off-by: Ke Xu * fix lint Signed-off-by: Ke Xu --- .eslintrc.json | 31 +- .eslintrc.typescript.eslintrc | 1 + apps/dashboard-e2e/.eslintrc.json | 3 + .../describeDatasetExplorer.ts | 4 +- .../describeDataPointChart.ts | 2 +- .../describeIndividualFeatureImportance.ts | 4 +- .../describeModelPerformancePlot.ts | 12 +- apps/dashboard-e2e/src/support/commands.ts | 16 +- apps/dashboard/src/app/App.tsx | 3 - .../src/app/__snapshots__/App.test.tsx.snap | 3 - apps/dashboard/src/error-analysis/App.tsx | 6 +- apps/dashboard/src/model-assessment/App.tsx | 3 +- apps/widget-e2e/src/support/commands.ts | 16 +- .../CausalInsightsTab.tsx | 7 +- .../CasualAggregateTable.tsx | 6 +- .../CasualIndividualChart.tsx | 2 +- .../CasualIndividualView.tsx | 2 +- .../TreatmentView/TreatmentView.tsx | 2 +- .../Controls/Common/CasualCallout.tsx | 2 +- .../src/lib/Cohort/CohortList/CohortList.tsx | 19 +- .../lib/Cohort/ShiftCohort/ShiftCohort.tsx | 2 +- .../Interfaces/IWeightedDropdownContext.ts | 1 + .../src/lib/components/AxisConfigDialog.tsx | 1 - .../core-ui/src/lib/components/SVGToolTip.tsx | 9 +- libs/core-ui/src/lib/util/getRandomId.ts | 2 +- .../src/lib/DatasetExplorerTab.tsx | 2 +- .../src/lib/generatePlotlyProps.ts | 2 +- .../ErrorAnalysisDashboard/ColorPalette.ts | 5 +- .../ErrorAnalysisViewTab.tsx | 4 +- .../Controls/FeatureList/FeatureList.tsx | 16 +- .../Controls/InstanceView/InstanceView.tsx | 8 +- .../Controls/MatrixArea/MatrixArea.tsx | 8 +- .../Controls/MatrixArea/MatrixAreaUtils.ts | 26 +- .../Controls/MatrixCells/MatrixCells.tsx | 8 +- .../Controls/Navigation/Navigation.tsx | 2 +- .../TabularDataView/TabularDataView.tsx | 2 +- .../Controls/TreeLegend/TreeLegend.styles.ts | 3 +- .../Controls/TreeLegend/TreeLegend.tsx | 162 ++++---- .../TreeViewRenderer/TreeViewNode.tsx | 2 +- .../TreeViewRenderer.styles.ts | 70 +--- .../TreeViewRenderer/TreeViewRenderer.tsx | 373 ++++++++---------- .../Controls/WhatIf/WhatIf.tsx | 2 +- .../ErrorAnalysisDashboard.tsx | 83 ++-- .../IErrorAnalysisDashboardProps.ts | 16 + .../ErrorAnalysisDashboard/TreeViewState.ts | 10 +- .../Utils/DatasetUtils.ts | 6 +- .../Controls/Cohort/CohortEditor.tsx | 8 +- .../DatasetExplorerTab/DatasetExplorerTab.tsx | 370 ----------------- .../Controls/FeatureImportance/Beehive.tsx | 1 + .../FeatureImportanceBar.tsx | 5 +- .../GlobalExplanationTab.tsx | 2 +- .../ModelPerformanceTab.tsx | 2 +- .../Controls/Scatter/ScatterUtils.ts | 62 +-- .../Controls/WhatIfTab/WhatIfTab.tsx | 2 +- .../src/lib/components/AccessibleChart.tsx | 1 + .../src/lib/components/ChartBuilder.ts | 21 +- .../AddTabButton.styles.ts | 11 +- .../ModelAssessmentDashboard/AddTabButton.tsx | 14 +- .../Controls/DashboardSettingDeleteButton.tsx | 2 +- .../Controls/DashboardSettings.tsx | 6 +- .../Controls/MainMenu.tsx | 21 +- .../Controls/Navigation.tsx | 6 +- .../ModelAssessmentDashboard.styles.ts | 8 +- .../ModelAssessmentDashboard.tsx | 218 +++++----- .../ModelAssessmentDashboardProps.ts | 7 +- package.json | 2 - yarn.lock | 32 -- 67 files changed, 649 insertions(+), 1121 deletions(-) delete mode 100644 libs/interpret/src/lib/MLIDashboard/Controls/DatasetExplorerTab/DatasetExplorerTab.tsx diff --git a/.eslintrc.json b/.eslintrc.json index 6140802c7..c9c3bade7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,10 +2,15 @@ "root": true, "ignorePatterns": ["**/*"], "plugins": ["@nrwl/nx"], + "globals": { + "JSX": "readonly" + }, "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "extends": [ + "plugin:@nrwl/nx/typescript", + "plugin:@nrwl/nx/javascript", "./.eslintrc.comments.eslintrc", "./.eslintrc.filenames.eslintrc", "./.eslintrc.react.eslintrc", @@ -42,19 +47,6 @@ ], 2 ], - "@nrwl/nx/enforce-module-boundaries": [ - "error", - { - "enforceBuildableLibDependency": true, - "allow": [], - "depConstraints": [ - { - "sourceTag": "*", - "onlyDependOnLibsWithTags": ["*"] - } - ] - } - ], "camelcase": [ "error", { @@ -312,19 +304,6 @@ "no-unused-vars": "error", "no-useless-constructor": "error" } - }, - { - "files": ["*.ts", "*.tsx"], - "extends": ["plugin:@nrwl/nx/typescript"], - "parserOptions": { - "project": "./tsconfig.*?.json" - }, - "rules": {} - }, - { - "files": ["*.js", "*.jsx"], - "extends": ["plugin:@nrwl/nx/javascript"], - "rules": {} } ] } diff --git a/.eslintrc.typescript.eslintrc b/.eslintrc.typescript.eslintrc index bf4f397d3..7919a93ef 100644 --- a/.eslintrc.typescript.eslintrc +++ b/.eslintrc.typescript.eslintrc @@ -17,6 +17,7 @@ ], "@typescript-eslint/member-ordering": ["error"], "@typescript-eslint/no-for-in-array": "error", + "@typescript-eslint/no-non-null-assertion": "error", "@typescript-eslint/unbound-method": "error", "@typescript-eslint/naming-convention": [ "error", diff --git a/apps/dashboard-e2e/.eslintrc.json b/apps/dashboard-e2e/.eslintrc.json index 9e5558ab7..065d05332 100644 --- a/apps/dashboard-e2e/.eslintrc.json +++ b/apps/dashboard-e2e/.eslintrc.json @@ -1,4 +1,7 @@ { + "globals": { + "JQuery": "readonly" + }, "overrides": [ { "files": ["src/plugins/index.js"], diff --git a/apps/dashboard-e2e/src/describer/interpret/datasetExplorer/describeDatasetExplorer.ts b/apps/dashboard-e2e/src/describer/interpret/datasetExplorer/describeDatasetExplorer.ts index 4cf08bf4e..5a88ff697 100644 --- a/apps/dashboard-e2e/src/describer/interpret/datasetExplorer/describeDatasetExplorer.ts +++ b/apps/dashboard-e2e/src/describer/interpret/datasetExplorer/describeDatasetExplorer.ts @@ -13,7 +13,9 @@ export function describeDatasetExplorer( name: keyof typeof interpretDatasets ): void { const datasetShape = interpretDatasets[name]; - if (datasetShape.noDataset) return; + if (datasetShape.noDataset) { + return; + } describe(testName, () => { beforeEach(() => { cy.visit(`#/interpret/${name}/light/english/Version-2`); diff --git a/apps/dashboard-e2e/src/describer/interpret/individualFeatureImportance/describeDataPointChart.ts b/apps/dashboard-e2e/src/describer/interpret/individualFeatureImportance/describeDataPointChart.ts index 3ad2ca830..5548ebc0d 100644 --- a/apps/dashboard-e2e/src/describer/interpret/individualFeatureImportance/describeDataPointChart.ts +++ b/apps/dashboard-e2e/src/describer/interpret/individualFeatureImportance/describeDataPointChart.ts @@ -33,7 +33,7 @@ export function describeDataPointChart(dataShape: IInterpretData): void { !dataShape.noLocalImportance && !dataShape.noFeatureImportance ? "Select a point to see its local importance" : "Provide local feature importances to see how each feature impacts individual predictions."; - cy.get(`#subPlotContainer`).should("contain.text", message); + cy.get("#subPlotContainer").should("contain.text", message); }); it("should select the first point", () => { props.chart.clickNthPoint(0); diff --git a/apps/dashboard-e2e/src/describer/interpret/individualFeatureImportance/describeIndividualFeatureImportance.ts b/apps/dashboard-e2e/src/describer/interpret/individualFeatureImportance/describeIndividualFeatureImportance.ts index 96fa9a98f..5feb8893a 100644 --- a/apps/dashboard-e2e/src/describer/interpret/individualFeatureImportance/describeIndividualFeatureImportance.ts +++ b/apps/dashboard-e2e/src/describer/interpret/individualFeatureImportance/describeIndividualFeatureImportance.ts @@ -13,7 +13,9 @@ export function describeIndividualFeatureImportance( name: keyof typeof interpretDatasets ): void { const datasetShape = interpretDatasets[name]; - if (datasetShape.noDataset) return; + if (datasetShape.noDataset) { + return; + } describe(testName, () => { beforeEach(() => { cy.visit(`#/interpret/${name}/light/english/Version-2`); diff --git a/apps/dashboard-e2e/src/describer/interpret/modelPerformance/describeModelPerformancePlot.ts b/apps/dashboard-e2e/src/describer/interpret/modelPerformance/describeModelPerformancePlot.ts index 5105153b8..fbdfd2b68 100644 --- a/apps/dashboard-e2e/src/describer/interpret/modelPerformance/describeModelPerformancePlot.ts +++ b/apps/dashboard-e2e/src/describer/interpret/modelPerformance/describeModelPerformancePlot.ts @@ -12,9 +12,15 @@ export function describeModelPerformancePlot( name: keyof typeof interpretDatasets ): void { const datasetShape = interpretDatasets[name]; - if (datasetShape.noDataset) return; - if (datasetShape.noPredict) return; - if (datasetShape.noY) return; + if (datasetShape.noDataset) { + return; + } + if (datasetShape.noPredict) { + return; + } + if (datasetShape.noY) { + return; + } describe(testName, () => { beforeEach(() => { cy.visit(`#/interpret/${name}/light/english/Version-2`); diff --git a/apps/dashboard-e2e/src/support/commands.ts b/apps/dashboard-e2e/src/support/commands.ts index 593e7f39a..2f6d6f2e0 100644 --- a/apps/dashboard-e2e/src/support/commands.ts +++ b/apps/dashboard-e2e/src/support/commands.ts @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -declare namespace Cypress { - interface Chainable { - login(email: string, password: string): void; - } -} +// declare namespace Cypress { +// interface Chainable { +// login(email: string, password: string): void; +// } +// } // // -- This is a parent command -- -Cypress.Commands.add("login", (email, password) => { - console.log("Custom command example: Login", email, password); -}); +// Cypress.Commands.add("login", (email, password) => { +// console.log("Custom command example: Login", email, password); +// }); // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) diff --git a/apps/dashboard/src/app/App.tsx b/apps/dashboard/src/app/App.tsx index 9c7736a18..045450667 100644 --- a/apps/dashboard/src/app/App.tsx +++ b/apps/dashboard/src/app/App.tsx @@ -44,9 +44,6 @@ export class App extends React.Component {
{ localUrl: "https://www.bing.com/", requestDebugML: generateJsonTreeBreastCancer, requestImportances: createJsonImportancesGenerator( - "dataSummary" in this.props.dataset - ? this.props.dataset.dataSummary.featureNames! + "dataSummary" in this.props.dataset && + this.props.dataset.dataSummary.featureNames + ? this.props.dataset.dataSummary.featureNames : [], true ), diff --git a/apps/dashboard/src/model-assessment/App.tsx b/apps/dashboard/src/model-assessment/App.tsx index db6dcaf04..f3c3df72b 100644 --- a/apps/dashboard/src/model-assessment/App.tsx +++ b/apps/dashboard/src/model-assessment/App.tsx @@ -12,7 +12,6 @@ import { ModelAssessmentDashboard, IModelAssessmentDashboardProps } from "@responsible-ai/model-assessment"; -import _ from "lodash"; import { ITheme } from "office-ui-fabric-react"; import React from "react"; @@ -62,7 +61,7 @@ export class App extends React.Component { modelExplanationData: this.props.modelExplanationData, requestDebugML: generateJsonTreeAdultCensusIncome, requestImportances: createJsonImportancesGenerator( - this.props.dataset.featureNames!, + this.props.dataset.featureNames, false ), requestMatrix: generateJsonMatrix, diff --git a/apps/widget-e2e/src/support/commands.ts b/apps/widget-e2e/src/support/commands.ts index 593e7f39a..2f6d6f2e0 100644 --- a/apps/widget-e2e/src/support/commands.ts +++ b/apps/widget-e2e/src/support/commands.ts @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -declare namespace Cypress { - interface Chainable { - login(email: string, password: string): void; - } -} +// declare namespace Cypress { +// interface Chainable { +// login(email: string, password: string): void; +// } +// } // // -- This is a parent command -- -Cypress.Commands.add("login", (email, password) => { - console.log("Custom command example: Login", email, password); -}); +// Cypress.Commands.add("login", (email, password) => { +// console.log("Custom command example: Login", email, password); +// }); // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) diff --git a/libs/causality/src/lib/CasualAnalysisDashboard/CausalInsightsTab.tsx b/libs/causality/src/lib/CasualAnalysisDashboard/CausalInsightsTab.tsx index 6f8a97374..da70d7fcd 100644 --- a/libs/causality/src/lib/CasualAnalysisDashboard/CausalInsightsTab.tsx +++ b/libs/causality/src/lib/CasualAnalysisDashboard/CausalInsightsTab.tsx @@ -29,7 +29,7 @@ export class CausalInsightsTab extends React.PureComponent< typeof ModelAssessmentContext > = defaultModelAssessmentContext; - constructor(props: ICausalInsightsTabProps) { + public constructor(props: ICausalInsightsTabProps) { super(props); this.state = { viewOption: CasualAnalysisOptions.Aggregate }; } @@ -65,10 +65,7 @@ export class CausalInsightsTab extends React.PureComponent< ); } - private onViewTypeChange = ( - item?: PivotItem, - _ev?: React.MouseEvent - ): void => { + private onViewTypeChange = (item?: PivotItem): void => { if ( item && item.props.itemKey && diff --git a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualAggregateView/CasualAggregateTable.tsx b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualAggregateView/CasualAggregateTable.tsx index 06fdfc1a8..04764c3d7 100644 --- a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualAggregateView/CasualAggregateTable.tsx +++ b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualAggregateView/CasualAggregateTable.tsx @@ -7,7 +7,7 @@ import { ModelAssessmentContext } from "@responsible-ai/core-ui"; import { localization } from "@responsible-ai/localization"; -import _, { isEqual } from "lodash"; +import _ from "lodash"; import { DetailsList, DetailsListLayoutMode, @@ -93,11 +93,11 @@ export class CasualAggregateTable extends React.PureComponent< } public componentDidUpdate(prevProps: ICasualAggregateTableProps): void { - if (!isEqual(prevProps.data, this.props.data)) { + if (!_.isEqual(prevProps.data, this.props.data)) { this.forceUpdate(); } } - private getItems = (columns: IColumn[]) => { + private getItems = (columns: IColumn[]): unknown[] => { const props = columns.map((c) => c.key); const items = []; const len = this.props.data[props[0]].length; diff --git a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualIndividualView/CasualIndividualChart.tsx b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualIndividualView/CasualIndividualChart.tsx index c5b0e4185..5fa91a93a 100644 --- a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualIndividualView/CasualIndividualChart.tsx +++ b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualIndividualView/CasualIndividualChart.tsx @@ -87,7 +87,7 @@ export class CasualIndividualChart extends React.PureComponent< }; } - public componentDidMount() { + public componentDidMount(): void { const featuresOption = new Array( this.context.jointDataset.datasetFeatureCount ) diff --git a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualIndividualView/CasualIndividualView.tsx b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualIndividualView/CasualIndividualView.tsx index 4c069d505..dad167808 100644 --- a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualIndividualView/CasualIndividualView.tsx +++ b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/CasualIndividualView/CasualIndividualView.tsx @@ -34,7 +34,7 @@ export class CasualIndividualView extends React.PureComponent< public context: React.ContextType< typeof ModelAssessmentContext > = defaultModelAssessmentContext; - constructor(props: ICasualIndividualViewProps) { + public constructor(props: ICasualIndividualViewProps) { super(props); this.state = { selectedData: undefined diff --git a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/TreatmentView/TreatmentView.tsx b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/TreatmentView/TreatmentView.tsx index 94f36a5f6..65717a062 100644 --- a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/TreatmentView/TreatmentView.tsx +++ b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/CasualAnalysisView/TreatmentView/TreatmentView.tsx @@ -23,7 +23,7 @@ export class TreatmentView extends React.PureComponent< public context: React.ContextType< typeof ModelAssessmentContext > = defaultModelAssessmentContext; - constructor(props: ITreatmentViewProps) { + public constructor(props: ITreatmentViewProps) { super(props); this.state = { showModalHelp: false diff --git a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/Common/CasualCallout.tsx b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/Common/CasualCallout.tsx index ed64c9378..d6abf63d4 100644 --- a/libs/causality/src/lib/CasualAnalysisDashboard/Controls/Common/CasualCallout.tsx +++ b/libs/causality/src/lib/CasualAnalysisDashboard/Controls/Common/CasualCallout.tsx @@ -31,7 +31,7 @@ export class CasualCallout extends React.Component< public context: React.ContextType< typeof ModelAssessmentContext > = defaultModelAssessmentContext; - constructor(props: Record) { + public constructor(props: Record) { super(props); this.state = { showCallout: false diff --git a/libs/core-ui/src/lib/Cohort/CohortList/CohortList.tsx b/libs/core-ui/src/lib/Cohort/CohortList/CohortList.tsx index 13572cc03..624412151 100644 --- a/libs/core-ui/src/lib/Cohort/CohortList/CohortList.tsx +++ b/libs/core-ui/src/lib/Cohort/CohortList/CohortList.tsx @@ -141,13 +141,7 @@ export class CohortList extends React.Component< case "column1": if (item.name !== "All data") { return ( - - this.props.onEditCohortClick( - this.getErrorCohort.bind(this)(item.name) - ) - } - > + {fieldContent} ); @@ -158,13 +152,16 @@ export class CohortList extends React.Component< return {fieldContent}; } } - return
; + return React.Fragment; } - private getErrorCohort(name: string): ErrorCohort { - return this.props.cohorts.find( + private onEditCohortClick(name: string): void { + const cohort = this.props.cohorts.find( (errorCohort) => errorCohort.cohort.name === name - )!; + ); + if (cohort) { + this.props.onEditCohortClick(cohort); + } } private getCohortListItems(): ICohortListItem[] { diff --git a/libs/core-ui/src/lib/Cohort/ShiftCohort/ShiftCohort.tsx b/libs/core-ui/src/lib/Cohort/ShiftCohort/ShiftCohort.tsx index 7fe6d6065..f2020b9e5 100644 --- a/libs/core-ui/src/lib/Cohort/ShiftCohort/ShiftCohort.tsx +++ b/libs/core-ui/src/lib/Cohort/ShiftCohort/ShiftCohort.tsx @@ -73,7 +73,7 @@ export class ShiftCohort extends React.Component< }; } - public componentDidMount() { + public componentDidMount(): void { const savedCohorts = this.context.errorCohorts.filter( (errorCohort) => !errorCohort.isTemporary ); diff --git a/libs/core-ui/src/lib/Interfaces/IWeightedDropdownContext.ts b/libs/core-ui/src/lib/Interfaces/IWeightedDropdownContext.ts index 018465aae..86e333084 100644 --- a/libs/core-ui/src/lib/Interfaces/IWeightedDropdownContext.ts +++ b/libs/core-ui/src/lib/Interfaces/IWeightedDropdownContext.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { IComboBox, IComboBoxOption } from "office-ui-fabric-react"; +import React from "react"; export enum WeightVectors { Equal = "equal", diff --git a/libs/core-ui/src/lib/components/AxisConfigDialog.tsx b/libs/core-ui/src/lib/components/AxisConfigDialog.tsx index f67492c39..a75d6a0d0 100644 --- a/libs/core-ui/src/lib/components/AxisConfigDialog.tsx +++ b/libs/core-ui/src/lib/components/AxisConfigDialog.tsx @@ -146,7 +146,6 @@ export class AxisConfigDialog extends React.PureComponent< ); const minVal = this.getMinValue(selectedMeta); const maxVal = this.getMaxValue(selectedMeta); - return ( { + private svgPoint = ( + svg: SVGSVGElement, + x: number, + y: number + ): { x: number; y: number } => { const ctm = svg.getScreenCTM(); if (svg.createSVGPoint && ctm) { let point = svg.createSVGPoint(); diff --git a/libs/core-ui/src/lib/util/getRandomId.ts b/libs/core-ui/src/lib/util/getRandomId.ts index a5eed394e..22ad56577 100644 --- a/libs/core-ui/src/lib/util/getRandomId.ts +++ b/libs/core-ui/src/lib/util/getRandomId.ts @@ -3,6 +3,6 @@ import { v4 } from "uuid"; -export function getRandomId() { +export function getRandomId(): string { return `Id_${v4()}`; } diff --git a/libs/dataset-explorer/src/lib/DatasetExplorerTab.tsx b/libs/dataset-explorer/src/lib/DatasetExplorerTab.tsx index b0e4e7f8d..c35c93a88 100644 --- a/libs/dataset-explorer/src/lib/DatasetExplorerTab.tsx +++ b/libs/dataset-explorer/src/lib/DatasetExplorerTab.tsx @@ -68,7 +68,7 @@ export class DatasetExplorerTab extends React.PureComponent< }; } - public componentDidMount() { + public componentDidMount(): void { let initialCohortIndex: number; if (this.props.showCohortSelection) { initialCohortIndex = 0; diff --git a/libs/dataset-explorer/src/lib/generatePlotlyProps.ts b/libs/dataset-explorer/src/lib/generatePlotlyProps.ts index 8fc3f8512..80fddbee5 100644 --- a/libs/dataset-explorer/src/lib/generatePlotlyProps.ts +++ b/libs/dataset-explorer/src/lib/generatePlotlyProps.ts @@ -11,7 +11,7 @@ import { } from "@responsible-ai/core-ui"; import { IPlotlyProperty, PlotlyMode } from "@responsible-ai/mlchartlib"; import _ from "lodash"; -import { DataTransform } from "plotly.js"; +import Plotly, { DataTransform } from "plotly.js"; import { basePlotlyProperties } from "./basePlotlyProperties"; import { buildCustomData } from "./buildCustomData"; diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/ColorPalette.ts b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/ColorPalette.ts index cb121cccd..5edc5d059 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/ColorPalette.ts +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/ColorPalette.ts @@ -16,7 +16,10 @@ export enum ColorPalette { ErrorAnalysisDarkGreyText = "#555" } -export function isColorDark(colorStr: string): boolean { +export function isColorDark(colorStr: string | undefined): boolean { + if (!colorStr) { + return false; + } const val = Lab(colorStr).l; return val <= 65; } diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/ErrorAnalysisView/ErrorAnalysisViewTab.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/ErrorAnalysisView/ErrorAnalysisViewTab.tsx index 69a240ecd..cf64f1de5 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/ErrorAnalysisView/ErrorAnalysisViewTab.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/ErrorAnalysisView/ErrorAnalysisViewTab.tsx @@ -59,7 +59,7 @@ export class ErrorAnalysisViewTab extends React.PureComponent< typeof ModelAssessmentContext > = defaultModelAssessmentContext; - constructor(props: IErrorAnalysisViewTabProps) { + public constructor(props: IErrorAnalysisViewTabProps) { super(props); this.state = { openFeatureList: false }; } @@ -105,7 +105,7 @@ export class ErrorAnalysisViewTab extends React.PureComponent< ); } - private saveFeatures(features: string[]) { + private saveFeatures(features: string[]): void { this.props.selectFeatures(features); this.setState({ openFeatureList: false }); } diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/FeatureList/FeatureList.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/FeatureList/FeatureList.tsx index 971d6f3f9..efba1513c 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/FeatureList/FeatureList.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/FeatureList/FeatureList.tsx @@ -181,9 +181,12 @@ export class FeatureList extends React.Component< styles={searchBoxStyles} onSearch={this.onSearch.bind(this)} onClear={this.onSearch.bind(this)} - onChange={(_, newValue?: string): void => - this.onSearch.bind(this)(newValue!) - } + onChange={(_, newValue?: string): void => { + if (newValue === undefined) { + return; + } + this.onSearch.bind(this)(newValue); + }} /> = ( props?: IDetailsRowProps ): JSX.Element | null => { + if (!props) { + return
; + } return ( - + ); }; - private renderRowFields(props: IDetailsRowFieldsProps) { + private renderRowFields(props: IDetailsRowFieldsProps): JSX.Element { if (this.props.isEnabled) { return ; } diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/InstanceView/InstanceView.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/InstanceView/InstanceView.tsx index 5cb30ad19..83ab0e617 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/InstanceView/InstanceView.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/InstanceView/InstanceView.tsx @@ -245,7 +245,7 @@ export class InstanceView extends React.Component< this.state.selectionDetails.selectedAllSelectedIndexes } selectedCohort={this.props.selectedCohort} - setWhatIfDatapoint={(_: number) => { + setWhatIfDatapoint={(): void => { // do nothing. }} /> @@ -371,7 +371,7 @@ export class InstanceView extends React.Component< private onRenderLabel = (type: SelectionType) => ( p: IChoiceGroupOption | undefined - ) => { + ): JSX.Element => { const classNames = InstanceViewStyles(); let selectionText = ""; switch (type) { @@ -405,8 +405,8 @@ export class InstanceView extends React.Component< return ( - - {p!.text} + + {p?.text} diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixArea/MatrixArea.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixArea/MatrixArea.tsx index 815d3fb26..12428c65f 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixArea/MatrixArea.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixArea/MatrixArea.tsx @@ -239,7 +239,7 @@ export class MatrixArea extends React.PureComponent< selectedCells = new Array(matrixLength * rowLength); } else { // Need to make a copy so setState re-renders - selectedCells = [...this.state.selectedCells!]; + selectedCells = [...selectedCells]; } const index = j + i * rowLength; selectedCells[index] = !selectedCells[index]; @@ -260,11 +260,11 @@ export class MatrixArea extends React.PureComponent< this.updateStateFromSelectedCells(selectedCells); } - private updateStateFromSelectedCells(selectedCells: boolean[]) { + private updateStateFromSelectedCells(selectedCells: boolean[]): void { // Create a composite filter from the selected cells const compositeFilter = createCompositeFilterFromCells( selectedCells, - this.state.jsonMatrix!, + this.state.jsonMatrix, this.props.selectedFeature1, this.props.selectedFeature2, this.props.baseCohort, @@ -273,7 +273,7 @@ export class MatrixArea extends React.PureComponent< const cells = selectedCells.filter(Boolean).length; const cohortStats = createCohortStatsFromSelectedCells( selectedCells, - this.state.jsonMatrix! + this.state.jsonMatrix ); this.props.updateSelectedCohort( [], diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixArea/MatrixAreaUtils.ts b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixArea/MatrixAreaUtils.ts index c93b77a99..3de92e70b 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixArea/MatrixAreaUtils.ts +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixArea/MatrixAreaUtils.ts @@ -68,7 +68,7 @@ export function fetchMatrix( export function createCohortStatsFromSelectedCells( selectedCells: boolean[], - jsonMatrix: any + jsonMatrix: { matrix: unknown[] } ): CohortStats { let falseCohortCount = 0; let totalCohortCount = 0; @@ -108,7 +108,7 @@ export function createCohortStatsFromSelectedCells( export function createCompositeFilterFromCells( selectedCells: boolean[], - jsonMatrix: any, + jsonMatrix: { matrix: any[]; category1: any; category2: any }, selectedFeature1: string, selectedFeature2: string, baseCohort: ErrorCohort, @@ -151,50 +151,50 @@ export function createCompositeFilterFromCells( const index = j + i * numCols; const cellCompositeFilters: ICompositeFilter[] = []; if (selectedCells[index]) { - if (category1Values.length > 0) { + if (category1Values.length > 0 && keyFeature1) { if (cat1HasIntervals) { cellCompositeFilters.push({ arg: [ category1Values[i].minIntervalCat, category1Values[i].maxIntervalCat ], - column: keyFeature1!, + column: keyFeature1, method: FilterMethods.InTheRangeOf }); } else { let cat1arg = category1Values[i].value; if (typeof cat1arg == "string") { cat1arg = baseCohort.jointDataset.metaDict[ - keyFeature1! + keyFeature1 ].sortedCategoricalValues?.indexOf(cat1arg); } cellCompositeFilters.push({ arg: [cat1arg], - column: keyFeature1!, + column: keyFeature1, method: FilterMethods.Equal }); } } - if (category2Values.length > 0) { + if (category2Values.length > 0 && keyFeature2) { if (cat2HasIntervals) { cellCompositeFilters.push({ arg: [ category2Values[j].minIntervalCat, category2Values[j].maxIntervalCat ], - column: keyFeature2!, + column: keyFeature2, method: FilterMethods.InTheRangeOf }); } else { let cat2arg = category2Values[j].value; if (typeof cat2arg == "string") { cat2arg = baseCohort.jointDataset.metaDict[ - keyFeature2! + keyFeature2 ].sortedCategoricalValues?.indexOf(cat2arg); } cellCompositeFilters.push({ arg: [cat2arg], - column: keyFeature2!, + column: keyFeature2, method: FilterMethods.Equal }); } @@ -218,7 +218,11 @@ export function createCompositeFilterFromCells( return compositeFilters; } -export function extractCategories(category: any): [any[], boolean] { +export function extractCategories(category: { + values: any[]; + intervalMin: any[]; + intervalMax: any[]; +}): [any[], boolean] { if (category === undefined) { return [[], false]; } diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixCells/MatrixCells.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixCells/MatrixCells.tsx index 6a7bfbd2d..2379de6ca 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixCells/MatrixCells.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/MatrixCells/MatrixCells.tsx @@ -109,7 +109,7 @@ export class MatrixCells extends React.PureComponent {
+ onClick={(): void => this.props.selectedCellHandler(i, j, matrixLength, row.length) } role="button" @@ -181,11 +181,11 @@ export class MatrixCells extends React.PureComponent { return outputMin + (outputMax - outputMin) * value; } - private colorgradRatio(value: number): string { + private colorgradRatio(value: number): string | undefined { return d3scaleLinear() .domain([0, 1]) .interpolate(d3interpolateHcl) - .range([ColorPalette.MinColor, ColorPalette.MaxColor])(value)!; + .range([ColorPalette.MinColor, ColorPalette.MaxColor])(value); } private colorLookup(ratio: number): string { @@ -197,7 +197,7 @@ export class MatrixCells extends React.PureComponent { const rate = ratio / 100; if (rate > 0.01 && rate <= 1) { - result = this.colorgradRatio(this.mapRange(0, 1, rate)); + result = this.colorgradRatio(this.mapRange(0, 1, rate)) || "#ffffff"; } else { result = "#ffffff"; } diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/Navigation/Navigation.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/Navigation/Navigation.tsx index 52d3c3ee7..c528b0274 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/Navigation/Navigation.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/Navigation/Navigation.tsx @@ -131,7 +131,7 @@ export class Navigation extends React.Component { className="ms-Breadcrumb-itemLink" href={item.href} onClick={(e: React.MouseEvent): void => - item.onClick!(e, item) + item.onClick?.(e, item) } color="blue" > diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TabularDataView/TabularDataView.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TabularDataView/TabularDataView.tsx index ff07c0a16..f2b86a4f4 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TabularDataView/TabularDataView.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TabularDataView/TabularDataView.tsx @@ -67,7 +67,7 @@ const onRenderDetailsHeader: IRenderFunction = ( ) => ; return (
- {defaultRender!({ + {defaultRender?.({ ...props, onRenderColumnHeaderTooltip, selectAllVisibility: SelectAllVisibility.hidden diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeLegend/TreeLegend.styles.ts b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeLegend/TreeLegend.styles.ts index 151da051a..1ad2ac44e 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeLegend/TreeLegend.styles.ts +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeLegend/TreeLegend.styles.ts @@ -62,8 +62,7 @@ export const treeLegendStyles: () => IProcessedStyleSet< pointerEvents: "auto" }, treeLegend: { - paddingLeft: "35px", - zIndex: "-1" + width: "15em" }, valueBlack: mergeStyles(value, { color: theme.palette.black, diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeLegend/TreeLegend.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeLegend/TreeLegend.tsx index 64237f047..523b87d49 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeLegend/TreeLegend.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeLegend/TreeLegend.tsx @@ -1,14 +1,9 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { ErrorCohort, ExpandableText } from "@responsible-ai/core-ui"; +import { ErrorCohort } from "@responsible-ai/core-ui"; import { localization } from "@responsible-ai/localization"; -import { - IStackStyles, - IStackTokens, - Stack, - Text -} from "office-ui-fabric-react"; +import { IStackTokens, Stack, Text } from "office-ui-fabric-react"; import React from "react"; import { ColorPalette } from "../../ColorPalette"; @@ -28,13 +23,6 @@ export interface ITreeLegendProps { const stackTokens: IStackTokens = { childrenGap: 5 }; const cellTokens: IStackTokens = { padding: 10 }; -const legendDescriptionPadding: IStackTokens = { padding: "20px 0px 20px 0px" }; -const legendDescriptionStyle: IStackStyles = { - root: { - pointerEvents: "auto", - width: 500 - } -}; export class TreeLegend extends React.Component { private readonly _errorRateIconId = "errorRateIconId"; @@ -43,99 +31,83 @@ export class TreeLegend extends React.Component { const classNames = treeLegendStyles(); return (
- - - {localization.ErrorAnalysis.TreeView.treeDescription} - - Cohort: {this.props.baseCohort.cohort.name} - - - -
- -
- {localization.ErrorAnalysis.errorCoverage} - -
-
- {this.props.selectedCohort.errorCoverage.toFixed(2)}% -
-
+ + +
+ +
+ {localization.ErrorAnalysis.errorCoverage} + +
+
+ {this.props.selectedCohort.errorCoverage.toFixed(2)}% +
- - - - - +
+ + + + + + + - - - - -
- - -
- -
- {localization.ErrorAnalysis.errorRate} - -
-
- {this.props.selectedCohort.errorRate.toFixed(2)}% -
-
+ + +
+ + +
+ +
+ {localization.ErrorAnalysis.errorRate} + +
+
+ {this.props.selectedCohort.errorRate.toFixed(2)}% +
- - - - -
+ + + + +
diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewNode.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewNode.tsx index 9deaada6f..efb2e523b 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewNode.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewNode.tsx @@ -90,7 +90,7 @@ export class TreeViewNode extends React.Component { private getNodeClassName( classNames: IProcessedStyleSet, ratio: number, - fill: string + fill: string | undefined ): string { let nodeTextClassName = classNames.nodeText; if (ratio > 50 && isColorDark(fill)) { diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewRenderer.styles.ts b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewRenderer.styles.ts index 6ef37c8d8..8dd471da7 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewRenderer.styles.ts +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewRenderer.styles.ts @@ -5,7 +5,8 @@ import { IStyle, mergeStyleSets, IProcessedStyleSet, - mergeStyles + mergeStyles, + getTheme } from "office-ui-fabric-react"; import { ColorPalette } from "../../ColorPalette"; @@ -15,32 +16,20 @@ export interface ITreeViewRendererStyles { clickedNodeFull: IStyle; detailLines: IStyle; filledNodeText: IStyle; - innerFrame: IStyle; legend: IStyle; linkLabel: IStyle; - linkLabelsTransitionGroup: IStyle; - linksTransitionGroup: IStyle; - mainFrame: IStyle; node: IStyle; nodeText: IStyle; - nodesGroup: IStyle; nopointer: IStyle; svgOuterFrame: IStyle; + svgContainer: IStyle; treeDescription: IStyle; - tooltipTransitionGroup: IStyle; } export const treeViewRendererStyles: () => IProcessedStyleSet< ITreeViewRendererStyles > = () => { - const detailStyle = { - fill: "#777", - font: "normal 12px Arial", - fontFamily: "Arial, Helvetica, sans-serif", - fontWeight: "bold", - pointerEvents: "none", - textAnchor: "middle" - }; + const theme = getTheme(); const nodeTextStyle = { fontSize: "10px", fontWeight: "bolder", @@ -59,48 +48,27 @@ export const treeViewRendererStyles: () => IProcessedStyleSet< stroke: "#0078D4", strokeWidth: 2 }, - detailLines: mergeStyles([ - detailStyle, - { - textAnchor: "start", - transform: "translate(0px, 10px)" - } - ]), + detailLines: { + fill: theme.semanticColors.inputBackground, + fontWeight: "bold", + pointerEvents: "none", + textAnchor: "start", + transform: "translate(0px, 10px)" + }, filledNodeText: mergeStyles([ nodeTextStyle, { fill: ColorPalette.ErrorAnalysisLightText } ]), - innerFrame: { - height: "100%", - margin: "0", - padding: "0", - width: "100%" - }, legend: { - pointerEvents: "none", - position: "absolute" + pointerEvents: "none" }, linkLabel: { fill: "#777", - font: "normal 12px Arial", - fontFamily: "Arial, Helvetica, sans-serif", pointerEvents: "none", textAnchor: "middle" }, - linkLabelsTransitionGroup: { - transform: "translate(0px, 90px)" - }, - linksTransitionGroup: { - transform: "translate(0px, 90px)" - }, - mainFrame: { - height: "1100px", - margin: "0", - padding: "0", - width: "100%" - }, node: { ":hover": { strokeWidth: "3px" @@ -110,9 +78,6 @@ export const treeViewRendererStyles: () => IProcessedStyleSet< stroke: "#089acc", strokeWidth: "0px" }, - nodesGroup: { - transform: "translate(0px, 90px)" - }, nodeText: mergeStyles([ nodeTextStyle, { @@ -122,13 +87,14 @@ export const treeViewRendererStyles: () => IProcessedStyleSet< nopointer: { pointerEvents: "none" }, - svgOuterFrame: { - margin: "0", - padding: "0", + svgContainer: { + overflow: "auto", width: "100%" }, - tooltipTransitionGroup: { - transform: "translate(40px, 90px)" + svgOuterFrame: { + margin: 0, + outline: "none", + padding: 0 }, treeDescription: { padding: "30px 0px 0px 35px" diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewRenderer.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewRenderer.tsx index 1ca95fa34..cabefb812 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewRenderer.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/TreeViewRenderer/TreeViewRenderer.tsx @@ -7,8 +7,11 @@ import { FilterMethods, CohortSource, CohortStats, - ErrorCohort + ErrorCohort, + ExpandableText, + getRandomId } from "@responsible-ai/core-ui"; +import { localization } from "@responsible-ai/localization"; import { max as d3max } from "d3-array"; import { stratify as d3stratify, @@ -19,13 +22,17 @@ import { interpolateHcl as d3interpolateHcl } from "d3-interpolate"; import { scaleLinear as d3scaleLinear } from "d3-scale"; import { select } from "d3-selection"; import { linkVertical as d3linkVertical } from "d3-shape"; -import { D3ZoomEvent, zoom as d3zoom } from "d3-zoom"; -import { IProcessedStyleSet, ITheme } from "office-ui-fabric-react"; +import { + IProcessedStyleSet, + ITheme, + mergeStyles, + Stack +} from "office-ui-fabric-react"; import React from "react"; -import { CSSTransition, TransitionGroup } from "react-transition-group"; import { ColorPalette } from "../../ColorPalette"; import { FilterProps } from "../../FilterProps"; +import { IRequestNode } from "../../Interfaces/IErrorAnalysisDashboardProps"; import { HelpMessageDict } from "../../Interfaces/IStringsParam"; import { INodeDetail, @@ -43,13 +50,19 @@ import { // Importing this solely to set the selectedPanelId. This component is NOT a statefulContainer // import StatefulContainer from '../../ap/mixins/statefulContainer.js' +const viewerHeight = 300; +const viewerWidth = 800; + export interface ITreeViewRendererProps { theme?: ITheme; messages?: HelpMessageDict; features: string[]; selectedFeatures: string[]; - getTreeNodes?: (request: any[], abortSignal: AbortSignal) => Promise; - staticTreeNodes?: any; + getTreeNodes?: ( + request: any[], + abortSignal: AbortSignal + ) => Promise; + staticTreeNodes?: { data: IRequestNode[] }; updateSelectedCohort: ( filters: IFilter[], compositeFilters: ICompositeFilter[], @@ -63,23 +76,6 @@ export interface ITreeViewRendererProps { setTreeViewState: (treeViewState: ITreeViewRendererState) => void; } -// Represents the data retrieved from the backend -export interface IRequestNode { - arg: number; - condition: string; - error: number; - id: string; - method: string; - nodeIndex: number; - nodeName: string; - parentId: string; - parentNodeName: string; - pathFromRoot: string; - size: number; - sourceRowKeyHash: string; - success: number; -} - export interface ISVGDatum { width: number; height: number; @@ -87,7 +83,6 @@ export interface ISVGDatum { } const svgOuterFrame: React.RefObject = React.createRef(); -const treeZoomPane: React.RefObject = React.createRef(); const errorAvgColor = ColorPalette.ErrorAvgColor; const errorRatioThreshold = 1; @@ -101,6 +96,16 @@ export class TreeViewRenderer extends React.PureComponent< this.state = this.props.state; } + public componentDidMount(): void { + window.addEventListener("resize", this.onResize); + if (!this.state.treeNodes?.[0]) { + this.fetchTreeNodes(); + } else { + this.onResize(); + this.forceUpdate(); + } + } + public componentDidUpdate(prevProps: ITreeViewRendererProps): void { if ( this.props.selectedFeatures !== prevProps.selectedFeatures || @@ -111,40 +116,43 @@ export class TreeViewRenderer extends React.PureComponent< } } + public componentWillUnmount(): void { + window.removeEventListener("resize", this.onResize); + this.props.setTreeViewState(this.state); + } + public render(): React.ReactNode { if (!this.state.root) { - return
; + return React.Fragment; } const classNames = treeViewRendererStyles(); const labelPaddingX = 20; const labelPaddingY = 8; const labelYOffset = 3; - const rootDescendents = this.state.root.descendants(); - const max: number = d3max( - rootDescendents, - (d) => d.data.error / d.data.size - )!; - - const zoom = d3zoom() - .scaleExtent([1 / 3, 4]) - .on("zoom", this.zoomed.bind(this)); + const rootDescendants = this.state.root.descendants(); + const max = d3max(rootDescendants, (d) => d.data.error / d.data.size) || 0; if (svgOuterFrame.current) { - const svg = select( - svgOuterFrame.current! - ).datum({ + const svg = select(svgOuterFrame.current).datum< + ISVGDatum + >({ filterBrushEvent: true, - height: this.state.viewerHeight, - width: this.state.viewerWidth + height: viewerHeight, + width: viewerWidth }); - svg.style("pointer-events", "all").call(zoom as any); + svg.style("pointer-events", "all"); } + let pathMin = viewerWidth; + let pathMax = 0; - const linkVertical = d3linkVertical>() - .x((d: HierarchyPointNode) => d!.x!) - .y((d: HierarchyPointNode) => d!.y!); + const linkVertical = d3linkVertical< + unknown, + HierarchyPointNode + >() + .x((d: HierarchyPointNode) => d.x) + .y((d: HierarchyPointNode) => d.y); // GENERATES LINK DATA BETWEEN NODES // ------------------------------------------------------------------- // The links between the nodes in the tree view are generated below. @@ -153,18 +161,18 @@ export class TreeViewRenderer extends React.PureComponent< // or not we highlight it. We use the d3 linkVertical which is a curved // spline to draw the link. The thickness of the links depends on the // ratio of data going through the path versus overall data in the tree. - const links = rootDescendents + const links = rootDescendants .slice(1) .map((d: HierarchyPointNode) => { const thick = 1 + Math.floor(30 * (d.data.size / this.state.rootSize)); const lineColor = d.data.nodeState.onSelectedPath ? ColorPalette.SelectedLineColor : ColorPalette.UnselectedLineColor; - const id: string = d.id!; + const id = d.id || ""; const linkVerticalD = linkVertical({ source: d.parent, target: d }); return { - d: linkVerticalD!, - id: id + Math.random(), + d: linkVerticalD || "", + id: id + getRandomId(), style: { fill: "white", stroke: lineColor, strokeWidth: thick } }; }); @@ -174,43 +182,45 @@ export class TreeViewRenderer extends React.PureComponent< // Generates the labels on the links. This initially writes the text, // calculates the bounding box using getTextBB, and then returns the // properties (x, y, height, width and the text) of the link label. - const linkLabels = rootDescendents + const linkLabels = rootDescendants .slice(1) - .filter( - (d: HierarchyPointNode) => d.data.nodeState.onSelectedPath - ) .map((d: HierarchyPointNode) => { - const labelX = d!.x! + (d!.parent!.x! - d!.x!) * 0.5; - const labelY = 4 + d!.y! + (d!.parent!.y! - d!.y!) * 0.5; - let bb: DOMRect; - try { - bb = this.getTextBB(d!.data!.condition!, classNames); - } catch { - bb = new DOMRect(1, 1, 1, 1); - } - return { + const labelX = d.x + (d.parent?.x ? (d.parent.x - d.x) * 0.5 : 0); + const labelY = 4 + d.y + (d.parent?.x ? (d.parent.y - d.y) * 0.5 : 0); + const bb: DOMRect = + this.getTextBB(d.data.condition, classNames) || + new DOMRect(1, 1, 1, 1); + const element = { bbHeight: bb.height + labelPaddingY, bbWidth: bb.width + labelPaddingX, - bbX: 0.5 * (bb.width + labelPaddingX), - bbY: 0.5 * (bb.height + labelPaddingY) + labelYOffset, - id: `linkLabel${d!.id!}`, + bbX: -0.5 * (bb.width + labelPaddingX), + bbY: -0.5 * (bb.height + labelPaddingY) - labelYOffset, + id: `linkLabel${d.id}`, style: { + display: d.data.nodeState.onSelectedPath ? undefined : "none", transform: `translate(${labelX}px, ${labelY}px)` }, - text: `${d!.data!.condition!}` + text: d.data.condition }; + if (labelX + element.bbX < pathMin) { + pathMin = labelX + element.bbX; + } + if (labelX + element.bbX + element.bbWidth > pathMax) { + pathMax = labelX + element.bbX + element.bbWidth; + } + return element; }); // GENERATES THE ACTUAL NODE COMPONENTS AND THEIR INTERACTIONS // ------------------------------------------------------------------- // The code below generates the circular nodes in the tree view. - const nodeData: Array> = rootDescendents.map( + const nodeData: Array> = rootDescendants.map( (d: HierarchyPointNode): HierarchyPointNode => { - let selectedStyle: Record = { + let selectedStyle: Record = { fill: d.data.errorColor.fill }; - if (d.data.nodeState.onSelectedPath!) { + if (d.data.nodeState.onSelectedPath) { selectedStyle = { fill: d.data.errorColor.fill, strokeWidth: 3 }; } @@ -220,19 +230,44 @@ export class TreeViewRenderer extends React.PureComponent< isSelectedLeaf: d.data.nodeState.isSelectedLeaf, onSelectedPath: d.data.nodeState.onSelectedPath, style: { - transform: `translate(${d!.x!}px, ${d!.y!}px)` + transform: `translate(${d.x}px, ${d.y}px)` } }; return d; } ); + const x = rootDescendants.map((d) => d.x); + const y = rootDescendants.map((d) => d.y); + const minX = Math.min(Math.min(...x) - 40, pathMin); + //100:tooltip width + const maxX = Math.max(Math.max(...x) + 40 + 100, pathMax); + const minY = Math.min(...y) - 40; + //40:tooltip height + const maxY = Math.max(...y) + 40 + 40; + console.log(minY, maxY); + const containerStyles = mergeStyles({ + transform: `translate(${-minX}px, ${-minY}px)` + }); const nodeDetail = this.state.nodeDetail; const minPct = this.state.rootLocalError * errorRatioThreshold * 100; + const svgWidth = maxX - minX; + const svgHeight = maxY - minY; return ( -
-
-
+ + + + {localization.ErrorAnalysis.TreeView.treeDescription} + + + + -
- - - - - - - {/* Tree */} - - {links.map((link) => ( - + + + + {links.map((link) => ( - - ))} - - - {nodeData.map((node, index) => { - return ( - - ); - })} - - - {linkLabels.map((linkLabel) => ( - + ))} + + + {nodeData.map((node, index) => { + return ( + + ); + })} + + + {linkLabels.map((linkLabel) => ( - - ))} - - - -
-
+ ))} + + + + + + ); } - public componentDidMount(): void { - window.addEventListener("resize", this.onResize.bind(this)); - if ( - !this.state.treeNodes || - this.state.treeNodes.length === 0 || - !this.state.treeNodes[0] - ) { - this.fetchTreeNodes(); - } else { - this.onResize(); - this.forceUpdate(); - } - } - - public componentWillUnmount(): void { - window.removeEventListener("resize", this.onResize.bind(this)); - this.props.setTreeViewState(this.state); - } - private calculateFilterProps( node: IRequestNode, rootErrorSize: number @@ -381,34 +369,10 @@ export class TreeViewRenderer extends React.PureComponent< return cohortStats; } - private resizeFunc = ( - state: Readonly - ): ITreeViewRendererState => { - const height = 300; - const width = 800; - // if (document.querySelector("#mainFrame")) { - // height = document.querySelector("#mainFrame")!.clientHeight; - // width = document.querySelector("#mainFrame")!.clientWidth; - // } - return { - nodeDetail: state.nodeDetail, - request: state.request, - root: state.root, - rootErrorSize: state.rootErrorSize, - rootLocalError: state.rootLocalError, - rootSize: state.rootSize, - selectedNode: state.selectedNode, - transform: state.transform, - treeNodes: state.treeNodes, - viewerHeight: height, - viewerWidth: width - }; + private onResize = (): void => { + this.setState({}); }; - private onResize(): void { - this.setState(this.resizeFunc); - } - private reloadData(requestTreeNodes: IRequestNode[]): void { const reloadDataFunc = ( state: Readonly @@ -452,10 +416,12 @@ export class TreeViewRenderer extends React.PureComponent< const calcMaskShift = globalErrorPerc * 52; const filterProps = this.calculateFilterProps(node, rootErrorSize); - let heatmapStyle: { fill: string } = { fill: errorAvgColor }; + let heatmapStyle: { fill: string | undefined } = { + fill: errorAvgColor + }; if (node.error / node.size > rootLocalError * errorRatioThreshold) { - heatmapStyle = { fill: colorgrad(localErrorPerc)! }; + heatmapStyle = { fill: colorgrad(localErrorPerc) }; } return { @@ -494,7 +460,7 @@ export class TreeViewRenderer extends React.PureComponent< ); const tempRoot = d3stratify()(treeNodes); - const treemap = d3tree().size([state.viewerWidth, state.viewerHeight]); + const treemap = d3tree().size([viewerWidth, viewerHeight]); const root = treemap(tempRoot) as HierarchyPointNode; const selectedNode = state.selectedNode; @@ -519,9 +485,7 @@ export class TreeViewRenderer extends React.PureComponent< rootSize, selectedNode: root, transform: state.transform, - treeNodes, - viewerHeight: state.viewerHeight, - viewerWidth: state.viewerWidth + treeNodes }; }; this.setState(reloadDataFunc); @@ -543,7 +507,7 @@ export class TreeViewRenderer extends React.PureComponent< private getTextBB( labelText: string, classNames: IProcessedStyleSet - ): DOMRect { + ): DOMRect | undefined { const temp = select(svgOuterFrame.current).append("g"); temp.selectAll("*").remove(); temp @@ -551,7 +515,7 @@ export class TreeViewRenderer extends React.PureComponent< .attr("className", classNames.linkLabel) .text(`${labelText}`); - const bb = temp!.node()!.getBBox(); + const bb = temp.node()?.getBBox(); temp.selectAll("*").remove(); return bb; } @@ -560,7 +524,7 @@ export class TreeViewRenderer extends React.PureComponent< if (!d) { return; } - d.data.nodeState!.onSelectedPath = true; + d.data.nodeState.onSelectedPath = true; this.selectParentNodes(d.parent); } @@ -568,9 +532,9 @@ export class TreeViewRenderer extends React.PureComponent< if (!d) { return; } - d.data.nodeState!.onSelectedPath = false; - d.data.nodeState!.isSelectedLeaf = false; - this.unselectParentNodes(d.parent!); + d.data.nodeState.onSelectedPath = false; + d.data.nodeState.isSelectedLeaf = false; + this.unselectParentNodes(d.parent); } private getFilters(d: HierarchyPointNode): IFilter[] { @@ -588,7 +552,7 @@ export class TreeViewRenderer extends React.PureComponent< column: d.parent.data.nodeName, method: d.data.method as FilterMethods }; - return [filter, ...this.getFilters(d.parent!)]; + return [filter, ...this.getFilters(d.parent)]; } private onSelectNode = (node: HierarchyPointNode): void => { @@ -623,9 +587,7 @@ export class TreeViewRenderer extends React.PureComponent< rootSize: state.rootSize, selectedNode: node, transform: state.transform, - treeNodes: state.treeNodes, - viewerHeight: state.viewerHeight, - viewerWidth: state.viewerWidth + treeNodes: state.treeNodes }; }; @@ -659,7 +621,7 @@ export class TreeViewRenderer extends React.PureComponent< // Use set timeout as reloadData state update needs to be done outside constructor similar to fetch call this.onResize(); this.forceUpdate(); - this.reloadData(this.props.staticTreeNodes.data as IRequestNode[]); + this.reloadData(this.props.staticTreeNodes.data); } return; } @@ -683,20 +645,7 @@ export class TreeViewRenderer extends React.PureComponent< .then((result) => { this.onResize(); this.forceUpdate(); - this.reloadData(result as IRequestNode[]); + this.reloadData(result); }); } - - private zoomed(zoomEvent: D3ZoomEvent): void { - const newTransform: any = zoomEvent.transform; - select(treeZoomPane.current).attr("transform", newTransform); - if ( - this.state.transform === undefined || - newTransform.x !== this.state.transform.x || - newTransform.y !== this.state.transform.y || - newTransform.r !== this.state.transform.r - ) { - this.setState({ transform: newTransform }); - } - } } diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/WhatIf/WhatIf.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/WhatIf/WhatIf.tsx index 637329f65..8ba29f6f7 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/WhatIf/WhatIf.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Controls/WhatIf/WhatIf.tsx @@ -67,7 +67,7 @@ export class WhatIf extends React.Component { }; } - public componentDidMount() { + public componentDidMount(): void { this.createCopyOfFirstRow(); this.buildRowOptions(); diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/ErrorAnalysisDashboard.tsx b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/ErrorAnalysisDashboard.tsx index 562ae001b..d0bd6d74e 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/ErrorAnalysisDashboard.tsx +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/ErrorAnalysisDashboard.tsx @@ -25,7 +25,6 @@ import { CohortStats, ICompositeFilter, IFilter, - IGenericChartProps, WeightVectorOption, EditCohort, ShiftCohort @@ -92,11 +91,6 @@ export class ErrorAnalysisDashboard extends React.PureComponent< public constructor(props: IErrorAnalysisDashboardProps) { super(props); - this.onModelConfigChanged = this.onModelConfigChanged.bind(this); - this.onConfigChanged = this.onConfigChanged.bind(this); - this.onWhatIfConfigChanged = this.onWhatIfConfigChanged.bind(this); - this.onDependenceChange = this.onDependenceChange.bind(this); - this.handleGlobalTabClick = this.handleGlobalTabClick.bind(this); this.setSortVector = this.setSortVector.bind(this); if (this.props.locale) { localization.setLanguage(this.props.locale); @@ -507,27 +501,11 @@ export class ErrorAnalysisDashboard extends React.PureComponent< selectedCohort={this.state.selectedCohort} baseCohort={this.state.baseCohort} treeViewState={this.state.treeViewState} - setTreeViewState={( - treeViewState: ITreeViewRendererState - ) => { - if (this.state.selectedCohort !== this.state.baseCohort) { - this.setState({ treeViewState }); - } - }} + setTreeViewState={this.setTreeViewState} matrixAreaState={this.state.matrixAreaState} matrixFilterState={this.state.matrixFilterState} - setMatrixAreaState={(matrixAreaState: IMatrixAreaState) => { - if (this.state.selectedCohort !== this.state.baseCohort) { - this.setState({ matrixAreaState }); - } - }} - setMatrixFilterState={( - matrixFilterState: IMatrixFilterState - ) => { - if (this.state.selectedCohort !== this.state.baseCohort) { - this.setState({ matrixFilterState }); - } - }} + setMatrixAreaState={this.setMatrixAreaState} + setMatrixFilterState={this.setMatrixFilterState} /> )} {this.state.viewType === ViewTypeKeys.ExplanationView && ( @@ -536,7 +514,7 @@ export class ErrorAnalysisDashboard extends React.PureComponent< > - this.setState({ selectedWhatIfIndex: index }) - } + setWhatIfDatapoint={this.setWhatIfDatapoint} /> )}
@@ -641,7 +617,7 @@ export class ErrorAnalysisDashboard extends React.PureComponent< currentCohort={this.state.selectedCohort} invokeModel={this.props.requestPredictions} customPoints={this.state.customPoints} - addCustomPoint={this.addCustomPoint.bind(this)} + addCustomPoint={this.addCustomPoint} selectedIndex={this.state.selectedWhatIfIndex} /> @@ -659,14 +635,33 @@ export class ErrorAnalysisDashboard extends React.PureComponent< ); } + private setTreeViewState = (treeViewState: ITreeViewRendererState): void => { + if (this.state.selectedCohort !== this.state.baseCohort) { + this.setState({ treeViewState }); + } + }; + private setMatrixAreaState = (matrixAreaState: IMatrixAreaState): void => { + if (this.state.selectedCohort !== this.state.baseCohort) { + this.setState({ matrixAreaState }); + } + }; + private setMatrixFilterState = ( + matrixFilterState: IMatrixFilterState + ): void => { + if (this.state.selectedCohort !== this.state.baseCohort) { + this.setState({ matrixFilterState }); + } + }; + private setWhatIfDatapoint = (index: number): void => + this.setState({ selectedWhatIfIndex: index }); - private addCustomPoint(temporaryPoint: { [key: string]: any }): void { + private addCustomPoint = (temporaryPoint: { [key: string]: any }): void => { this.setState({ customPoints: [...this.state.customPoints, temporaryPoint], openWhatIf: false, predictionTab: PredictionTabKeys.WhatIfDatapointsTab }); - } + }; private updateSelectedCohort( filters: IFilter[], @@ -717,25 +712,9 @@ export class ErrorAnalysisDashboard extends React.PureComponent< }); } - private onConfigChanged(newConfig: IGenericChartProps): void { - this.setState({ dataChartConfig: newConfig }); - } - - private onModelConfigChanged(newConfig: IGenericChartProps): void { - this.setState({ modelChartConfig: newConfig }); - } - - private onWhatIfConfigChanged(newConfig: IGenericChartProps): void { - this.setState({ whatIfChartConfig: newConfig }); - } - - private onDependenceChange(newConfig: IGenericChartProps): void { - this.setState({ dependenceProps: newConfig }); - } - - private handleGlobalTabClick(item: PivotItem | undefined): void { - if (item !== undefined) { - const itemKey: string = item.props.itemKey!; + private handleGlobalTabClick = (item: PivotItem | undefined): void => { + if (item?.props.itemKey) { + const itemKey: string = item.props.itemKey; const index: GlobalTabKeys = GlobalTabKeys[itemKey]; const predictionTab = PredictionTabKeys.CorrectPredictionTab; this.setState({ @@ -744,7 +723,7 @@ export class ErrorAnalysisDashboard extends React.PureComponent< predictionTab }); } - } + }; private setSortVector(): void { this.setState({ diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Interfaces/IErrorAnalysisDashboardProps.ts b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Interfaces/IErrorAnalysisDashboardProps.ts index c93997a18..32ebf0da9 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Interfaces/IErrorAnalysisDashboardProps.ts +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Interfaces/IErrorAnalysisDashboardProps.ts @@ -24,6 +24,22 @@ import { IStringsParam } from "./IStringsParam"; * @property {number[][] | number[]} [probabilityY] - model probabilities for output values. Dim(rows) x [Dim(classes)] */ +// Represents the data retrieved from the backend +export interface IRequestNode { + arg: number; + condition: string; + error: number; + id: string; + method: string; + nodeIndex: number; + nodeName: string; + parentId: string; + parentNodeName: string; + pathFromRoot: string; + size: number; + sourceRowKeyHash: string; + success: number; +} export interface IErrorAnalysisDashboardProps extends IExplanationDashboardData, IOfficeFabricProps { diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/TreeViewState.ts b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/TreeViewState.ts index f41e3385b..6011c6b0c 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/TreeViewState.ts +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/TreeViewState.ts @@ -6,7 +6,7 @@ import { HierarchyPointNode } from "d3-hierarchy"; import { FilterProps } from "./FilterProps"; export interface IErrorColorStyle { - fill: string; + fill: string | undefined; } export interface IShowSelectedStyle { @@ -32,8 +32,6 @@ export interface INodeDetail { export interface ITreeViewRendererState { request?: AbortController; nodeDetail: INodeDetail; - viewerWidth: number; - viewerHeight: number; selectedNode: any; transform: any; treeNodes: any[]; @@ -74,7 +72,7 @@ export interface IFillStyleUp { // Contains node state that changes with UI clicks export interface INodeState { - errorStyle: Record | undefined; + errorStyle: Record | undefined; onSelectedPath: boolean; isSelectedLeaf: boolean; style: ITransform | undefined; @@ -106,8 +104,6 @@ export function createInitialTreeViewState(): ITreeViewRendererState { rootSize: 0, selectedNode: undefined, transform: undefined, - treeNodes: [], - viewerHeight: 0, - viewerWidth: 0 + treeNodes: [] }; } diff --git a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Utils/DatasetUtils.ts b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Utils/DatasetUtils.ts index 9c37cef0a..de2e8c635 100644 --- a/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Utils/DatasetUtils.ts +++ b/libs/error-analysis/src/lib/ErrorAnalysisDashboard/Utils/DatasetUtils.ts @@ -88,7 +88,7 @@ export function constructCols( columns.push({ fieldName: `${index}`, isResizable: true, - key: `color`, + key: "color", maxWidth: 100, minWidth: 50, name: "Color" @@ -137,8 +137,8 @@ function pushRowData( jointDataset: JointDataset, row: { [key: string]: number } ): void { - if (jointDataset.metaDict[property].isCategorical) { - const categories = jointDataset.metaDict[property].sortedCategoricalValues!; + const categories = jointDataset.metaDict[property].sortedCategoricalValues; + if (jointDataset.metaDict[property].isCategorical && categories) { tableRow.push(categories[row[property]]); } else { tableRow.push(row[property]); diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/Cohort/CohortEditor.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/Cohort/CohortEditor.tsx index 1af154f86..d1222323d 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/Cohort/CohortEditor.tsx +++ b/libs/interpret/src/lib/MLIDashboard/Controls/Cohort/CohortEditor.tsx @@ -98,7 +98,7 @@ export class CohortEditor extends React.PureComponent< return ( <> { + onOuterClick={(): number => { return 0; }} // https://github.com/microsoft/fluentui/issues/6476 id="cohortEditPanel" @@ -211,11 +211,11 @@ export class CohortEditor extends React.PureComponent< this.props.onDelete(); }; - private readonly onCancelClick = () => { + private readonly onCancelClick = (): void => { this.setState({ showConfirmation: true }); }; - private readonly onCancelConfirm = async () => { + private readonly onCancelConfirm = async (): Promise => { const callback = this.props.isNewCohort ? this.props.closeCohortEditorPanel : this.props.closeCohortEditor; @@ -223,7 +223,7 @@ export class CohortEditor extends React.PureComponent< this.setState({ showConfirmation: false }); }; - private readonly onCancelClose = () => { + private readonly onCancelClose = (): void => { this.setState({ showConfirmation: false }); }; diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/DatasetExplorerTab/DatasetExplorerTab.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/DatasetExplorerTab/DatasetExplorerTab.tsx deleted file mode 100644 index 9dd3a67ca..000000000 --- a/libs/interpret/src/lib/MLIDashboard/Controls/DatasetExplorerTab/DatasetExplorerTab.tsx +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { - ExpandableText, - JointDataset, - ColumnCategories, - cohortKey, - ChartTypes, - IGenericChartProps, - ISelectorConfig, - MissingParametersPlaceholder, - defaultModelAssessmentContext, - ModelAssessmentContext -} from "@responsible-ai/core-ui"; -import { localization } from "@responsible-ai/localization"; -import { AccessibleChart } from "@responsible-ai/mlchartlib"; -import _ from "lodash"; -import { - getTheme, - IDropdownOption, - Dropdown, - DefaultButton, - Text, - IChoiceGroupOption -} from "office-ui-fabric-react"; -import React from "react"; - -import { newExplanationDashboardRowErrorSize } from "../../newExplanationDashboardRowErrorSize"; -import { AxisConfigDialog } from "../AxisConfigurationDialog/AxisConfigDialog"; - -import { datasetExplorerTabStyles } from "./DatasetExplorerTab.styles"; -import { generatePlotlyProps } from "./generatePlotlyProps"; -import { SidePanel } from "./SidePanel"; - -export interface IDatasetExplorerTabProps { - initialCohortIndex?: number; -} - -export interface IDatasetExplorerTabState { - xDialogOpen: boolean; - yDialogOpen: boolean; - colorDialogOpen: boolean; - selectedCohortIndex: number; - calloutVisible: boolean; - chartProps?: IGenericChartProps; -} - -export class DatasetExplorerTab extends React.PureComponent< - IDatasetExplorerTabProps, - IDatasetExplorerTabState -> { - public static contextType = ModelAssessmentContext; - public context: React.ContextType< - typeof ModelAssessmentContext - > = defaultModelAssessmentContext; - - private readonly chartAndConfigsId = "DatasetExplorerChart"; - - public constructor(props: IDatasetExplorerTabProps) { - super(props); - let initialCohortIndex = 0; - if (this.props.initialCohortIndex !== undefined) { - initialCohortIndex = this.props.initialCohortIndex; - } - this.state = { - calloutVisible: false, - colorDialogOpen: false, - selectedCohortIndex: initialCohortIndex, - xDialogOpen: false, - yDialogOpen: false - }; - } - - public componentDidMount() { - this.setState({ chartProps: this.generateDefaultChartAxes() }); - } - - public render(): React.ReactNode { - const classNames = datasetExplorerTabStyles(); - - if (!this.context.jointDataset.hasDataset) { - return ( - - {localization.Interpret.DatasetExplorer.missingParameters} - - ); - } - if (this.state.chartProps === undefined) { - return
; - } - const plotlyProps = generatePlotlyProps( - this.context.jointDataset, - this.state.chartProps, - this.context.errorCohorts.map((errorCohort) => errorCohort.cohort)[ - this.state.selectedCohortIndex - ] - ); - const cohortOptions = - this.state.chartProps.xAxis.property !== cohortKey - ? this.context.errorCohorts.map((errorCohort, index) => { - return { key: index, text: errorCohort.cohort.name }; - }) - : undefined; - const cohortLength = this.context.errorCohorts[ - this.state.selectedCohortIndex - ].cohort.filteredData.length; - const canRenderChart = - cohortLength < newExplanationDashboardRowErrorSize || - this.state.chartProps.chartType !== ChartTypes.Scatter; - const yAxisCategories = [ - ColumnCategories.Index, - ColumnCategories.Dataset, - ColumnCategories.Outcome - ]; - if (this.state.chartProps.chartType !== ChartTypes.Scatter) { - yAxisCategories.push(ColumnCategories.None); - } - return ( -
-
- - {localization.Interpret.DatasetExplorer.collapsedHelperText} - -
-
- - {localization.Interpret.ModelPerformance.cohortPickerLabel} - - {cohortOptions && ( - - )} -
-
-
- {this.state.yDialogOpen && ( - - )} - {this.state.xDialogOpen && ( - - )} - {this.state.colorDialogOpen && this.state.chartProps.colorAxis && ( - - )} -
-
-
-
- -
-
-
- {canRenderChart ? ( - - ) : ( - - {localization.Interpret.ValidationErrors.datasizeError} - - )} -
-
-
-
-
- -
-
-
-
- errorCohort.cohort - )} - jointDataset={this.context.jointDataset} - selectedCohortIndex={this.state.selectedCohortIndex} - setColorOpen={this.setColorOpen} - onChartTypeChange={this.onChartTypeChange} - /> -
-
- ); - } - - private setSelectedCohort = ( - _: React.FormEvent, - item?: IDropdownOption - ): void => { - if (item?.key !== undefined) { - this.setState({ selectedCohortIndex: item.key as number }); - } - }; - - private onChartTypeChange = ( - _ev?: React.SyntheticEvent, - item?: IChoiceGroupOption - ): void => { - const newProps = _.cloneDeep(this.state.chartProps); - if (item?.key === undefined || !newProps) { - return; - } - newProps.chartType = item.key as ChartTypes; - if (newProps.yAxis.property === ColumnCategories.None) { - newProps.yAxis = this.generateDefaultYAxis(); - } - this.setState({ chartProps: newProps }); - }; - - private readonly setXOpen = (val: boolean): void => { - if (val && this.state.xDialogOpen === false) { - this.setState({ xDialogOpen: true }); - return; - } - this.setState({ xDialogOpen: false }); - }; - - private readonly setColorOpen = (): void => { - this.setState({ colorDialogOpen: true }); - }; - - private readonly setColorClose = (): void => { - this.setState({ colorDialogOpen: false }); - }; - - private readonly setYOpen = (val: boolean): void => { - if (val && this.state.yDialogOpen === false) { - this.setState({ yDialogOpen: true }); - return; - } - this.setState({ yDialogOpen: false }); - }; - - private onXSet = (value: ISelectorConfig): void => { - if (!this.state.chartProps) { - return; - } - const newProps = _.cloneDeep(this.state.chartProps); - newProps.xAxis = value; - this.setState({ chartProps: newProps, xDialogOpen: false }); - }; - - private onYSet = (value: ISelectorConfig): void => { - if (!this.state.chartProps) { - return; - } - const newProps = _.cloneDeep(this.state.chartProps); - newProps.yAxis = value; - this.setState({ chartProps: newProps, yDialogOpen: false }); - }; - - private onColorSet = (value: ISelectorConfig): void => { - if (!this.state.chartProps) { - return; - } - const newProps = _.cloneDeep(this.state.chartProps); - newProps.colorAxis = value; - this.setState({ chartProps: newProps, colorDialogOpen: false }); - }; - - private generateDefaultChartAxes(): IGenericChartProps | undefined { - if (!this.context.jointDataset.hasDataset) { - return; - } - const chartProps: IGenericChartProps = { - chartType: ChartTypes.Histogram, - colorAxis: { - options: {}, - property: this.context.jointDataset.hasPredictedY - ? JointDataset.PredictedYLabel - : JointDataset.IndexLabel - }, - xAxis: { - options: {}, - property: JointDataset.IndexLabel - }, - yAxis: this.generateDefaultYAxis() - }; - return chartProps; - } - - private generateDefaultYAxis(): ISelectorConfig { - const yKey = JointDataset.DataLabelRoot + "0"; - const yIsDithered = this.context.jointDataset.metaDict[yKey] - .treatAsCategorical; - return { - options: { - bin: false, - dither: yIsDithered - }, - property: yKey - }; - } -} diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/FeatureImportance/Beehive.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/FeatureImportance/Beehive.tsx index 7b3d6ea52..f3138f45c 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/FeatureImportance/Beehive.tsx +++ b/libs/interpret/src/lib/MLIDashboard/Controls/FeatureImportance/Beehive.tsx @@ -26,6 +26,7 @@ import { IDropdownOption, Slider } from "office-ui-fabric-react"; +import Plotly from "plotly.js"; import React from "react"; import { LoadingSpinner } from "../../SharedComponents/LoadingSpinner"; diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/FeatureImportanceBar/FeatureImportanceBar.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/FeatureImportanceBar/FeatureImportanceBar.tsx index 1606a30d9..64f4711ec 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/FeatureImportanceBar/FeatureImportanceBar.tsx +++ b/libs/interpret/src/lib/MLIDashboard/Controls/FeatureImportanceBar/FeatureImportanceBar.tsx @@ -11,6 +11,7 @@ import { localization } from "@responsible-ai/localization"; import { IPlotlyProperty, AccessibleChart } from "@responsible-ai/mlchartlib"; import _ from "lodash"; import { getTheme, Text } from "office-ui-fabric-react"; +import Plotly from "plotly.js"; import React from "react"; import { LoadingSpinner } from "../../SharedComponents/LoadingSpinner"; @@ -165,7 +166,9 @@ export class FeatureImportanceBar extends React.PureComponent< const xText = sortedIndexVector.map((i) => this.props.unsortedX[i]); const xOriginText = sortedIndexVector.map((i) => { - if (this.props.originX) return this.props.originX[i]; + if (this.props.originX) { + return this.props.originX[i]; + } return this.props.unsortedX[i]; }); if (this.props.chartType === ChartTypes.Bar) { diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/GlobalExplanationTab/GlobalExplanationTab.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/GlobalExplanationTab/GlobalExplanationTab.tsx index db7530f97..384bcb7df 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/GlobalExplanationTab/GlobalExplanationTab.tsx +++ b/libs/interpret/src/lib/MLIDashboard/Controls/GlobalExplanationTab/GlobalExplanationTab.tsx @@ -110,7 +110,7 @@ export class GlobalExplanationTab extends React.PureComponent< }; } - public componentDidMount() { + public componentDidMount(): void { if (!this.context.jointDataset.hasLocalExplanations) { return; } diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/ModelPerformanceTab/ModelPerformanceTab.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/ModelPerformanceTab/ModelPerformanceTab.tsx index fd008a346..15053be77 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/ModelPerformanceTab/ModelPerformanceTab.tsx +++ b/libs/interpret/src/lib/MLIDashboard/Controls/ModelPerformanceTab/ModelPerformanceTab.tsx @@ -64,7 +64,7 @@ export class ModelPerformanceTab extends React.PureComponent< }; } - public componentDidMount() { + public componentDidMount(): void { this.setState({ chartProps: this.generateDefaultChartAxes() }); } diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/Scatter/ScatterUtils.ts b/libs/interpret/src/lib/MLIDashboard/Controls/Scatter/ScatterUtils.ts index 48c26f8bb..34741cefd 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/Scatter/ScatterUtils.ts +++ b/libs/interpret/src/lib/MLIDashboard/Controls/Scatter/ScatterUtils.ts @@ -354,16 +354,18 @@ export class ScatterUtils { PlotlyUtils.setColorProperty(props, colorOption, modelData, colorbarTitle); props.data[0].yAccessor = yAccessor; props.data[0].xAccessor = xAccessor; - props.data[0].datapointLevelAccessors!["text"].path = [ - xAccessor, - yAccessor, - colorAccessor - ]; - props.data[0].datapointLevelAccessors!["text"].mapArgs = [ - localization.Interpret.ExplanationScatter.index, - modelData.featureNames[maxIndex], - localization.Interpret.ExplanationScatter.predictedY - ]; + if (props.data[0].datapointLevelAccessors) { + props.data[0].datapointLevelAccessors["text"].path = [ + xAccessor, + yAccessor, + colorAccessor + ]; + props.data[0].datapointLevelAccessors["text"].mapArgs = [ + localization.Interpret.ExplanationScatter.index, + modelData.featureNames[maxIndex], + localization.Interpret.ExplanationScatter.predictedY + ]; + } _.set( props, @@ -417,25 +419,27 @@ export class ScatterUtils { PlotlyUtils.setColorProperty(props, colorOption, modelData, colorbarTitle); props.data[0].xAccessor = xAccessor; props.data[0].yAccessor = yAccessor; - props.data[0].datapointLevelAccessors!["text"].path = [ - xAccessor, - yAccessor, - colorAccessor - ]; - props.data[0].datapointLevelAccessors!["text"].mapArgs = [ - localization.formatString( - localization.Interpret.ExplanationScatter.dataLabel, - modelData.featureNames[maxIndex] - ), - localization.formatString( - localization.Interpret.ExplanationScatter.importanceLabel, - modelData.featureNames[maxIndex] - ), - localization.formatString( - localization.Interpret.ExplanationScatter.dataLabel, - modelData.featureNames[secondIndex] - ) - ]; + if (props.data[0].datapointLevelAccessors) { + props.data[0].datapointLevelAccessors["text"].path = [ + xAccessor, + yAccessor, + colorAccessor + ]; + props.data[0].datapointLevelAccessors["text"].mapArgs = [ + localization.formatString( + localization.Interpret.ExplanationScatter.dataLabel, + modelData.featureNames[maxIndex] + ), + localization.formatString( + localization.Interpret.ExplanationScatter.importanceLabel, + modelData.featureNames[maxIndex] + ), + localization.formatString( + localization.Interpret.ExplanationScatter.dataLabel, + modelData.featureNames[secondIndex] + ) + ]; + } const yAxisLabel = modelData.modelType === ModelTypes.Binary diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/WhatIfTab/WhatIfTab.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/WhatIfTab/WhatIfTab.tsx index ccff99ff0..f2b95d2ac 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/WhatIfTab/WhatIfTab.tsx +++ b/libs/interpret/src/lib/MLIDashboard/Controls/WhatIfTab/WhatIfTab.tsx @@ -130,7 +130,7 @@ export class WhatIfTab extends React.PureComponent< }; } - public componentDidMount() { + public componentDidMount(): void { this.createCopyOfFirstRow(); this.buildRowOptions(0); diff --git a/libs/mlchartlib/src/lib/components/AccessibleChart.tsx b/libs/mlchartlib/src/lib/components/AccessibleChart.tsx index 2313b09b2..618dcf61d 100644 --- a/libs/mlchartlib/src/lib/components/AccessibleChart.tsx +++ b/libs/mlchartlib/src/lib/components/AccessibleChart.tsx @@ -3,6 +3,7 @@ import _ from "lodash"; import { ITheme } from "office-ui-fabric-react"; +import Plotly from "plotly.js"; import React from "react"; import Plot from "react-plotly.js"; import { v4 } from "uuid"; diff --git a/libs/mlchartlib/src/lib/components/ChartBuilder.ts b/libs/mlchartlib/src/lib/components/ChartBuilder.ts index cfb991bbf..3f05ca3dd 100644 --- a/libs/mlchartlib/src/lib/components/ChartBuilder.ts +++ b/libs/mlchartlib/src/lib/components/ChartBuilder.ts @@ -19,7 +19,7 @@ export class ChartBuilder { ? ", " + Object.keys(datum.datapointLevelAccessors) .map((key) => { - return `${key}: [${datum.datapointLevelAccessors![key].path.join( + return `${key}: [${datum.datapointLevelAccessors?.[key].path.join( ", " )}]`; }) @@ -121,15 +121,18 @@ export class ChartBuilder { if (datum.sizeAccessor) { const size = (row.size * (datum.maxMarkerSize || 40) ** 2) / (2 * maxBubbleValue); - (series.marker!.size as number[]).push(Math.abs(size)); + (series.marker?.size as number[]).push(Math.abs(size)); } if (datum.datapointLevelAccessors !== undefined) { Object.keys(datum.datapointLevelAccessors).forEach((key) => { - const accessor = datum.datapointLevelAccessors![key]; + if (!datum.datapointLevelAccessors) { + return; + } + const accessor = datum.datapointLevelAccessors[key]; const plotlyPath = accessor.plotlyPath; let value = accessor.mapFunction !== undefined - ? accessorMappingFunctions[accessor.mapFunction!]( + ? accessorMappingFunctions[accessor.mapFunction]( row[key], datum, accessor.mapArgs || [] @@ -169,12 +172,16 @@ export class ChartBuilder { series.y = []; } if (datum.sizeAccessor) { - series.marker!.size = []; + if (series.marker) { + series.marker.size = []; + } } if (datum.datapointLevelAccessors !== undefined) { Object.keys(datum.datapointLevelAccessors).forEach((key) => { - const plotlyPath = datum.datapointLevelAccessors![key].plotlyPath; - _.set(series, plotlyPath, []); + const plotlyPath = datum.datapointLevelAccessors?.[key].plotlyPath; + if (plotlyPath) { + _.set(series, plotlyPath, []); + } }); } return series; diff --git a/libs/model-assessment/src/lib/ModelAssessmentDashboard/AddTabButton.styles.ts b/libs/model-assessment/src/lib/ModelAssessmentDashboard/AddTabButton.styles.ts index 3639715a0..489e1123e 100644 --- a/libs/model-assessment/src/lib/ModelAssessmentDashboard/AddTabButton.styles.ts +++ b/libs/model-assessment/src/lib/ModelAssessmentDashboard/AddTabButton.styles.ts @@ -1,7 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import { mergeStyleSets, IStyle, getTheme } from "office-ui-fabric-react"; +import { + mergeStyleSets, + IStyle, + getTheme, + IProcessedStyleSet +} from "office-ui-fabric-react"; export interface IAddTabButtonStyles { callout: IStyle; @@ -9,7 +14,9 @@ export interface IAddTabButtonStyles { splitter: IStyle; } -export const addTabButtonStyles = () => { +export const addTabButtonStyles = (): IProcessedStyleSet< + IAddTabButtonStyles +> => { const theme = getTheme(); return mergeStyleSets({ button: { diff --git a/libs/model-assessment/src/lib/ModelAssessmentDashboard/AddTabButton.tsx b/libs/model-assessment/src/lib/ModelAssessmentDashboard/AddTabButton.tsx index afe2738e3..384a23482 100644 --- a/libs/model-assessment/src/lib/ModelAssessmentDashboard/AddTabButton.tsx +++ b/libs/model-assessment/src/lib/ModelAssessmentDashboard/AddTabButton.tsx @@ -101,19 +101,23 @@ export class AddTabButton extends React.Component< private onChange = ( _: React.FormEvent, option?: IDropdownOption | undefined - ) => { - if (!option) return; + ): void => { + if (!option) { + return; + } this.setState({ tabSelected: option.key as GlobalTabKeys }); }; - private addTab = () => { - if (!this.state.tabSelected) return; + private addTab = (): void => { + if (!this.state.tabSelected) { + return; + } this.props.onAdd(this.props.tabIndex, this.state.tabSelected); this.setState({ isCalloutVisible: false }); }; - private toggleIsCalloutVisible = () => { + private toggleIsCalloutVisible = (): void => { this.setState((prev) => ({ isCalloutVisible: !prev.isCalloutVisible, tabSelected: undefined diff --git a/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/DashboardSettingDeleteButton.tsx b/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/DashboardSettingDeleteButton.tsx index 94353ea9f..077235687 100644 --- a/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/DashboardSettingDeleteButton.tsx +++ b/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/DashboardSettingDeleteButton.tsx @@ -18,7 +18,7 @@ export class DashboardSettingDeleteButton extends React.PureComponent< ); } - private removeTab = () => { + private removeTab = (): void => { this.props.removeTab(this.props.index); }; } diff --git a/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/DashboardSettings.tsx b/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/DashboardSettings.tsx index 36a2f4305..277a51024 100644 --- a/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/DashboardSettings.tsx +++ b/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/DashboardSettings.tsx @@ -70,8 +70,10 @@ export class DashboardSettings extends React.PureComponent< private renderDelete = ( _item?: IModelAssessmentDashboardTab, index?: number | undefined - ) => { - if (!index) return React.Fragment; + ): React.ReactNode => { + if (!index) { + return React.Fragment; + } return ( { public static contextType = ModelAssessmentContext; public context: IModelAssessmentContext = defaultModelAssessmentContext; - public componentDidMount() { + public componentDidMount(): void { this.setState({ editedCohort: this.context.errorCohorts[0] }); } public render(): React.ReactNode { - if (!this.state) return React.Fragment; + if (!this.state) { + return React.Fragment; + } const farItems: ICommandBarItemProps[] = []; const helpItems: ICommandBarItemProps[] = [ { @@ -162,16 +164,17 @@ export class MainMenu extends React.PureComponent< ); } - private toggleInfoPanel = () => + private toggleInfoPanel = (): void => this.setState((prev) => ({ infoPanelVisible: !prev.infoPanelVisible })); - private toggleCohortListPanel = () => + private toggleCohortListPanel = (): void => this.setState((prev) => ({ cohortListPanelVisible: !prev.cohortListPanelVisible })); - private openEditCohort = (editedCohort: ErrorCohort) => + private openEditCohort = (editedCohort: ErrorCohort): void => this.setState({ editCohortVisible: true, editedCohort }); - private closeEditCohort = () => this.setState({ editCohortVisible: true }); + private closeEditCohort = (): void => + this.setState({ editCohortVisible: true }); private saveEditCohort = ( originalCohort: ErrorCohort, editedCohort: ErrorCohort @@ -197,13 +200,13 @@ export class MainMenu extends React.PureComponent< ); }; - private toggleShiftCohort = () => + private toggleShiftCohort = (): void => this.setState((prev) => ({ shiftCohortVisible: !prev.shiftCohortVisible })); - private toggleSaveCohort = () => + private toggleSaveCohort = (): void => this.setState((prev) => ({ saveCohortVisible: !prev.saveCohortVisible })); - private toggleDashboardSettings = () => + private toggleDashboardSettings = (): void => this.setState((prev) => ({ dashboardSettingsVisible: !prev.dashboardSettingsVisible })); diff --git a/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/Navigation.tsx b/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/Navigation.tsx index 46641644a..3f6cc396e 100644 --- a/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/Navigation.tsx +++ b/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/Navigation.tsx @@ -21,7 +21,7 @@ export interface INavigationProps { export class Navigation extends React.PureComponent { private navLinkGroups: INavLinkGroup[] = []; - constructor(props: INavigationProps) { + public constructor(props: INavigationProps) { super(props); this.navLinkGroups.push({ @@ -104,7 +104,7 @@ export class Navigation extends React.PureComponent { } } -function onRenderGroupHeader(group?: INavLinkGroup) { +function onRenderGroupHeader(group?: INavLinkGroup): JSX.Element { return (
{group ? group.name?.toUpperCase() : ""} @@ -112,7 +112,7 @@ function onRenderGroupHeader(group?: INavLinkGroup) { ); } -function onRenderLink(link?: INavLink) { +function onRenderLink(link?: INavLink): JSX.Element { const theme = getTheme(); return ( ({ + buttonSection: { + textAlign: "center" + }, page: { boxSizing: "border-box", padding: "16px 40px 0 14px", width: "100%" }, - section: { - textAlign: "center" - } + section: {} }); diff --git a/libs/model-assessment/src/lib/ModelAssessmentDashboard/ModelAssessmentDashboard.tsx b/libs/model-assessment/src/lib/ModelAssessmentDashboard/ModelAssessmentDashboard.tsx index a2b658839..6a0d3d695 100644 --- a/libs/model-assessment/src/lib/ModelAssessmentDashboard/ModelAssessmentDashboard.tsx +++ b/libs/model-assessment/src/lib/ModelAssessmentDashboard/ModelAssessmentDashboard.tsx @@ -92,125 +92,145 @@ export class ModelAssessmentDashboard extends CohortBasedComponent< {this.state.activeGlobalTabs[0]?.key !== GlobalTabKeys.ErrorAnalysisTab && ( - + )} {this.state.activeGlobalTabs.map((t, i) => ( - - {t.key === GlobalTabKeys.ErrorAnalysisTab && ( - { - if (this.state.selectedCohort !== this.state.baseCohort) { - this.setState({ treeViewState }); + <> + + {t.key === GlobalTabKeys.ErrorAnalysisTab && ( + { - if (this.state.selectedCohort !== this.state.baseCohort) { - this.setState({ matrixAreaState }); + getTreeNodes={this.props.requestDebugML} + getMatrix={this.props.requestMatrix} + updateSelectedCohort={this.updateSelectedCohort} + features={this.props.dataset.featureNames} + selectedFeatures={this.state.selectedFeatures} + errorAnalysisOption={this.state.errorAnalysisOption} + selectedCohort={this.state.selectedCohort} + baseCohort={this.state.baseCohort} + treeViewState={this.state.treeViewState} + setTreeViewState={( + treeViewState: ITreeViewRendererState + ): void => { + if ( + this.state.selectedCohort !== this.state.baseCohort + ) { + this.setState({ treeViewState }); + } + }} + matrixAreaState={this.state.matrixAreaState} + matrixFilterState={this.state.matrixFilterState} + setMatrixAreaState={( + matrixAreaState: IMatrixAreaState + ): void => { + if ( + this.state.selectedCohort !== this.state.baseCohort + ) { + this.setState({ matrixAreaState }); + } + }} + setMatrixFilterState={( + matrixFilterState: IMatrixFilterState + ): void => { + if ( + this.state.selectedCohort !== this.state.baseCohort + ) { + this.setState({ matrixFilterState }); + } + }} + stringParams={this.props.stringParams} + selectFeatures={(features: string[]): void => + this.setState({ selectedFeatures: features }) } - }} - setMatrixFilterState={( - matrixFilterState: IMatrixFilterState - ) => { - if (this.state.selectedCohort !== this.state.baseCohort) { - this.setState({ matrixFilterState }); + importances={this.state.importances} + /> + )} + {t.key === GlobalTabKeys.ModelStatisticsTab && ( + + )} + {t.key === GlobalTabKeys.DataExplorerTab && ( + + )} + {t.key === GlobalTabKeys.GlobalExplanationTab && ( + cohort.cohort + )} + cohortIDs={cohortIDs} + selectedWeightVector={this.state.selectedWeightVector} + weightOptions={this.state.weightVectorOptions} + weightLabels={this.state.weightVectorLabels} + onWeightChange={this.onWeightVectorChange} + explanationMethod={ + this.props.modelExplanationData.explanationMethod } - }} - stringParams={this.props.stringParams} - selectFeatures={(features: string[]): void => - this.setState({ selectedFeatures: features }) - } - importances={this.state.importances} - /> - )} - {t.key === GlobalTabKeys.ModelStatisticsTab && ( - - )} - {t.key === GlobalTabKeys.DataExplorerTab && ( - - )} - {t.key === GlobalTabKeys.GlobalExplanationTab && ( - cohort.cohort)} - cohortIDs={cohortIDs} - selectedWeightVector={this.state.selectedWeightVector} - weightOptions={this.state.weightVectorOptions} - weightLabels={this.state.weightVectorLabels} - onWeightChange={this.onWeightVectorChange} - explanationMethod={ - this.props.modelExplanationData.explanationMethod - } - /> - )} - {t.key === GlobalTabKeys.LocalExplanationTab && ( - { - this.setState({ - predictionTab: key - }); - }} - customPoints={this.state.customPoints} - selectedCohort={this.state.selectedCohort} - setWhatIfDatapoint={(index: number) => - this.setState({ selectedWhatIfIndex: index }) - } - /> - )} - {t.key === GlobalTabKeys.CausalAnalysisTab && ( - - )} - {/* + /> + )} + {t.key === GlobalTabKeys.LocalExplanationTab && ( + { + this.setState({ + predictionTab: key + }); + }} + customPoints={this.state.customPoints} + selectedCohort={this.state.selectedCohort} + setWhatIfDatapoint={(index: number): void => + this.setState({ selectedWhatIfIndex: index }) + } + /> + )} + {t.key === GlobalTabKeys.CausalAnalysisTab && ( + + )} + {/* {t.key === GlobalTabKeys.CounterfactualsTab && ( )} */} - - + + + + + ))}
); } - private addTab = (index: number, tab: GlobalTabKeys) => { + private addTab = (index: number, tab: GlobalTabKeys): void => { const tabs = [...this.state.activeGlobalTabs]; tabs.splice(index, 0, { dataCount: 0, key: tab }); this.setState({ activeGlobalTabs: tabs }); }; - private removeTab = (index: number) => { + private removeTab = (index: number): void => { const tabs = [...this.state.activeGlobalTabs]; tabs.splice(index, 1); this.setState({ activeGlobalTabs: tabs }); diff --git a/libs/model-assessment/src/lib/ModelAssessmentDashboard/ModelAssessmentDashboardProps.ts b/libs/model-assessment/src/lib/ModelAssessmentDashboard/ModelAssessmentDashboardProps.ts index 4e4249e5d..d73b0457b 100644 --- a/libs/model-assessment/src/lib/ModelAssessmentDashboard/ModelAssessmentDashboardProps.ts +++ b/libs/model-assessment/src/lib/ModelAssessmentDashboard/ModelAssessmentDashboardProps.ts @@ -10,7 +10,7 @@ import { ITelemetryMessage, ICasualAnalysisData } from "@responsible-ai/core-ui"; -import { IStringsParam } from "@responsible-ai/error-analysis"; +import { IRequestNode, IStringsParam } from "@responsible-ai/error-analysis"; export interface IModelAssessmentDashboardProps extends IOfficeFabricProps, @@ -30,7 +30,10 @@ export interface IModelAssessmentDashboardProps abortSignal: AbortSignal, explanationAlgorithm?: string ) => Promise; - requestDebugML?: (request: any[], abortSignal: AbortSignal) => Promise; + requestDebugML?: ( + request: any[], + abortSignal: AbortSignal + ) => Promise; requestMatrix?: (request: any[], abortSignal: AbortSignal) => Promise; requestImportances?: ( request: any[], diff --git a/package.json b/package.json index d1d0bfa5f..93d8e97a8 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "react-dom": "17.0.1", "react-plotly.js": "^2.5.0", "react-router-dom": "^5.0.0", - "react-transition-group": "^4.2.4", "tslib": "^2.2.0", "uuid": "^8.3.0" }, @@ -103,7 +102,6 @@ "@types/react-dom": "16.9.9", "@types/react-plotly.js": "^2.2.4", "@types/react-router-dom": "5.1.6", - "@types/react-transition-group": "^4.2.4", "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "4.3.0", "@typescript-eslint/parser": "4.3.0", diff --git a/yarn.lock b/yarn.lock index 07b3a5ed1..56041a22d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1888,13 +1888,6 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.8.7": - version "7.12.1" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" - integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== - dependencies: - regenerator-runtime "^0.13.4" - "@babel/template@^7.10.4", "@babel/template@^7.3.3", "@babel/template@^7.8.6": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -3363,13 +3356,6 @@ "@types/history" "*" "@types/react" "*" -"@types/react-transition-group@^4.2.4": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d" - integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w== - dependencies: - "@types/react" "*" - "@types/react@*": version "16.9.49" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872" @@ -6876,14 +6862,6 @@ dom-accessibility-api@^0.5.4: resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.4.tgz#b06d059cdd4a4ad9a79275f9d414a5c126241166" integrity sha512-TvrjBckDy2c6v6RLxPv5QXOnU+SmF9nBII5621Ve5fu6Z/BDrENurBEvlC1f44lKEUVqOpK4w9E5Idc5/EgkLQ== -dom-helpers@^5.0.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b" - integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -14171,16 +14149,6 @@ react-test-renderer@^16.0.0-0: react-is "^16.8.6" scheduler "^0.19.1" -react-transition-group@^4.2.4: - version "4.4.1" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" - integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== - dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" - react@17.0.1: version "17.0.1" resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127"