disable zoom in error analysis tree (#469)
* disable zoom Signed-off-by: Ke Xu <xuke@microsoft.com> * fix label Signed-off-by: Ke Xu <xuke@microsoft.com> * remove transition group Signed-off-by: Ke Xu <xuke@microsoft.com> * reformat tree Signed-off-by: Ke Xu <xuke@microsoft.com> * fix lint Signed-off-by: Ke Xu <xuke@microsoft.com> * add width Signed-off-by: Ke Xu <xuke@microsoft.com> * adjust size Signed-off-by: Ke Xu <xuke@microsoft.com> * fix lint Signed-off-by: Ke Xu <xuke@microsoft.com> * adjust tree Signed-off-by: Ke Xu <xuke@microsoft.com> * adjust width & hright Signed-off-by: Ke Xu <xuke@microsoft.com> * adjust min width Signed-off-by: Ke Xu <xuke@microsoft.com> * update snapshot Signed-off-by: Ke Xu <xuke@microsoft.com> * fix lint Signed-off-by: Ke Xu <xuke@microsoft.com>
This commit is contained in:
Родитель
deccbb7ffa
Коммит
e7785e5fb3
|
@ -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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
{
|
||||
"globals": {
|
||||
"JQuery": "readonly"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["src/plugins/index.js"],
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Chainable<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
// declare namespace Cypress {
|
||||
// interface Chainable<Subject> {
|
||||
// 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) => { ... })
|
||||
|
|
|
@ -44,9 +44,6 @@ export class App extends React.Component<IAppSetting, IAppState> {
|
|||
<div
|
||||
style={{
|
||||
backgroundColor: theme.semanticColors.bodyBackground,
|
||||
borderColor: "gray",
|
||||
borderStyle: "solid",
|
||||
borderWidth: 10,
|
||||
height: "calc(100% - 70px)",
|
||||
minHeight: "500px",
|
||||
width: "calc(100%-20px)"
|
||||
|
|
|
@ -16,9 +16,6 @@ exports[`FairnessWizardV1 should render successfully 1`] = `
|
|||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderColor": "gray",
|
||||
"borderStyle": "solid",
|
||||
"borderWidth": 10,
|
||||
"height": "calc(100% - 70px)",
|
||||
"minHeight": "500px",
|
||||
"width": "calc(100%-20px)",
|
||||
|
|
|
@ -11,7 +11,6 @@ import {
|
|||
HelpMessageDict
|
||||
} from "@responsible-ai/error-analysis";
|
||||
import { Language } from "@responsible-ai/localization";
|
||||
import _ from "lodash";
|
||||
import { ITheme } from "office-ui-fabric-react";
|
||||
import React from "react";
|
||||
|
||||
|
@ -157,8 +156,9 @@ export class App extends React.Component<IAppProps> {
|
|||
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
|
||||
),
|
||||
|
|
|
@ -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<IAppProps> {
|
|||
modelExplanationData: this.props.modelExplanationData,
|
||||
requestDebugML: generateJsonTreeAdultCensusIncome,
|
||||
requestImportances: createJsonImportancesGenerator(
|
||||
this.props.dataset.featureNames!,
|
||||
this.props.dataset.featureNames,
|
||||
false
|
||||
),
|
||||
requestMatrix: generateJsonMatrix,
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
declare namespace Cypress {
|
||||
interface Chainable<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
// declare namespace Cypress {
|
||||
// interface Chainable<Subject> {
|
||||
// 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) => { ... })
|
||||
|
|
|
@ -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<
|
|||
</Stack>
|
||||
);
|
||||
}
|
||||
private onViewTypeChange = (
|
||||
item?: PivotItem,
|
||||
_ev?: React.MouseEvent<HTMLElement>
|
||||
): void => {
|
||||
private onViewTypeChange = (item?: PivotItem): void => {
|
||||
if (
|
||||
item &&
|
||||
item.props.itemKey &&
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -87,7 +87,7 @@ export class CasualIndividualChart extends React.PureComponent<
|
|||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
const featuresOption = new Array(
|
||||
this.context.jointDataset.datasetFeatureCount
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,7 +31,7 @@ export class CasualCallout extends React.Component<
|
|||
public context: React.ContextType<
|
||||
typeof ModelAssessmentContext
|
||||
> = defaultModelAssessmentContext;
|
||||
constructor(props: Record<string, unknown>) {
|
||||
public constructor(props: Record<string, unknown>) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showCallout: false
|
||||
|
|
|
@ -141,13 +141,7 @@ export class CohortList extends React.Component<
|
|||
case "column1":
|
||||
if (item.name !== "All data") {
|
||||
return (
|
||||
<Link
|
||||
onClick={() =>
|
||||
this.props.onEditCohortClick(
|
||||
this.getErrorCohort.bind(this)(item.name)
|
||||
)
|
||||
}
|
||||
>
|
||||
<Link onClick={this.onEditCohortClick.bind(this, item.name)}>
|
||||
{fieldContent}
|
||||
</Link>
|
||||
);
|
||||
|
@ -158,13 +152,16 @@ export class CohortList extends React.Component<
|
|||
return <span>{fieldContent}</span>;
|
||||
}
|
||||
}
|
||||
return <div></div>;
|
||||
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[] {
|
||||
|
|
|
@ -73,7 +73,7 @@ export class ShiftCohort extends React.Component<
|
|||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
const savedCohorts = this.context.errorCohorts.filter(
|
||||
(errorCohort) => !errorCohort.isTemporary
|
||||
);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -146,7 +146,6 @@ export class AxisConfigDialog extends React.PureComponent<
|
|||
);
|
||||
const minVal = this.getMinValue(selectedMeta);
|
||||
const maxVal = this.getMaxValue(selectedMeta);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
id="AxisConfigPanel"
|
||||
|
|
|
@ -38,8 +38,9 @@ export class SVGToolTip extends React.Component<
|
|||
}
|
||||
}
|
||||
public render(): React.ReactNode {
|
||||
if (!this.state.svgElement || !this.state.isMouseOver)
|
||||
if (!this.state.svgElement || !this.state.isMouseOver) {
|
||||
return React.Fragment;
|
||||
}
|
||||
const classNames = SVGToolTipStyles();
|
||||
return ReactDom.createPortal(
|
||||
<g
|
||||
|
@ -83,7 +84,11 @@ export class SVGToolTip extends React.Component<
|
|||
this.setState({ isMouseOver: false });
|
||||
};
|
||||
|
||||
private svgPoint = (svg: SVGSVGElement, x: number, y: number) => {
|
||||
private svgPoint = (
|
||||
svg: SVGSVGElement,
|
||||
x: number,
|
||||
y: number
|
||||
): { x: number; y: number } => {
|
||||
const ctm = svg.getScreenCTM();
|
||||
if (svg.createSVGPoint && ctm) {
|
||||
let point = svg.createSVGPoint();
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
import { v4 } from "uuid";
|
||||
|
||||
export function getRandomId() {
|
||||
export function getRandomId(): string {
|
||||
return `Id_${v4()}`;
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ export class DatasetExplorerTab extends React.PureComponent<
|
|||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
let initialCohortIndex: number;
|
||||
if (this.props.showCohortSelection) {
|
||||
initialCohortIndex = 0;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Customizer
|
||||
|
@ -250,12 +253,15 @@ export class FeatureList extends React.Component<
|
|||
private renderRow: IRenderFunction<IDetailsRowProps> = (
|
||||
props?: IDetailsRowProps
|
||||
): JSX.Element | null => {
|
||||
if (!props) {
|
||||
return <div></div>;
|
||||
}
|
||||
return (
|
||||
<DetailsRow rowFieldsAs={this.renderRowFields.bind(this)} {...props!} />
|
||||
<DetailsRow rowFieldsAs={this.renderRowFields.bind(this)} {...props} />
|
||||
);
|
||||
};
|
||||
|
||||
private renderRowFields(props: IDetailsRowFieldsProps) {
|
||||
private renderRowFields(props: IDetailsRowFieldsProps): JSX.Element {
|
||||
if (this.props.isEnabled) {
|
||||
return <DetailsRowFields {...props} />;
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<Stack>
|
||||
<Stack.Item align="start">
|
||||
<span id={p!.labelId} className="ms-ChoiceFieldLabel">
|
||||
{p!.text}
|
||||
<span id={p?.labelId} className="ms-ChoiceFieldLabel">
|
||||
{p?.text}
|
||||
</span>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="start" styles={stackItemStyles}>
|
||||
|
|
|
@ -239,7 +239,7 @@ export class MatrixArea extends React.PureComponent<
|
|||
selectedCells = new Array<boolean>(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(
|
||||
[],
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ export class MatrixCells extends React.PureComponent<IMatrixCellsProps> {
|
|||
<div
|
||||
key={`${i}_${j}cell`}
|
||||
className={classNames.matrixCell}
|
||||
onClick={() =>
|
||||
onClick={(): void =>
|
||||
this.props.selectedCellHandler(i, j, matrixLength, row.length)
|
||||
}
|
||||
role="button"
|
||||
|
@ -181,11 +181,11 @@ export class MatrixCells extends React.PureComponent<IMatrixCellsProps> {
|
|||
return outputMin + (outputMax - outputMin) * value;
|
||||
}
|
||||
|
||||
private colorgradRatio(value: number): string {
|
||||
private colorgradRatio(value: number): string | undefined {
|
||||
return d3scaleLinear<string>()
|
||||
.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<IMatrixCellsProps> {
|
|||
|
||||
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";
|
||||
}
|
||||
|
|
|
@ -131,7 +131,7 @@ export class Navigation extends React.Component<INavigationProps> {
|
|||
className="ms-Breadcrumb-itemLink"
|
||||
href={item.href}
|
||||
onClick={(e: React.MouseEvent<HTMLElement>): void =>
|
||||
item.onClick!(e, item)
|
||||
item.onClick?.(e, item)
|
||||
}
|
||||
color="blue"
|
||||
>
|
||||
|
|
|
@ -67,7 +67,7 @@ const onRenderDetailsHeader: IRenderFunction<IDetailsHeaderProps> = (
|
|||
) => <TooltipHost {...tooltipHostProps} />;
|
||||
return (
|
||||
<div>
|
||||
{defaultRender!({
|
||||
{defaultRender?.({
|
||||
...props,
|
||||
onRenderColumnHeaderTooltip,
|
||||
selectAllVisibility: SelectAllVisibility.hidden
|
||||
|
|
|
@ -62,8 +62,7 @@ export const treeLegendStyles: () => IProcessedStyleSet<
|
|||
pointerEvents: "auto"
|
||||
},
|
||||
treeLegend: {
|
||||
paddingLeft: "35px",
|
||||
zIndex: "-1"
|
||||
width: "15em"
|
||||
},
|
||||
valueBlack: mergeStyles(value, {
|
||||
color: theme.palette.black,
|
||||
|
|
|
@ -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<ITreeLegendProps> {
|
||||
private readonly _errorRateIconId = "errorRateIconId";
|
||||
|
@ -43,99 +31,83 @@ export class TreeLegend extends React.Component<ITreeLegendProps> {
|
|||
const classNames = treeLegendStyles();
|
||||
return (
|
||||
<div className={classNames.treeLegend}>
|
||||
<Stack
|
||||
styles={legendDescriptionStyle}
|
||||
tokens={legendDescriptionPadding}
|
||||
>
|
||||
<ExpandableText
|
||||
expandedText={
|
||||
localization.ErrorAnalysis.TreeView.treeDescriptionExpanded
|
||||
}
|
||||
iconName="Info"
|
||||
variant={"smallPlus"}
|
||||
>
|
||||
{localization.ErrorAnalysis.TreeView.treeDescription}
|
||||
</ExpandableText>
|
||||
</Stack>
|
||||
<Stack tokens={stackTokens}>
|
||||
<Text variant={"xLarge"} block>
|
||||
Cohort: {this.props.baseCohort.cohort.name}
|
||||
</Text>
|
||||
<Stack horizontal>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<div className={classNames.metricBarBlack}></div>
|
||||
<Stack tokens={cellTokens}>
|
||||
<div className={classNames.smallHeader}>
|
||||
{localization.ErrorAnalysis.errorCoverage}
|
||||
<InfoCallout
|
||||
iconId={this._errorCoverageIconId}
|
||||
infoText={localization.ErrorAnalysis.errorCoverageInfo}
|
||||
title={localization.ErrorAnalysis.errorCoverageTitle}
|
||||
></InfoCallout>
|
||||
</div>
|
||||
<div className={classNames.valueBlack}>
|
||||
{this.props.selectedCohort.errorCoverage.toFixed(2)}%
|
||||
</div>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<div className={classNames.metricBarBlack}></div>
|
||||
<Stack tokens={cellTokens}>
|
||||
<div className={classNames.smallHeader}>
|
||||
{localization.ErrorAnalysis.errorCoverage}
|
||||
<InfoCallout
|
||||
iconId={this._errorCoverageIconId}
|
||||
infoText={localization.ErrorAnalysis.errorCoverageInfo}
|
||||
title={localization.ErrorAnalysis.errorCoverageTitle}
|
||||
></InfoCallout>
|
||||
</div>
|
||||
<div className={classNames.valueBlack}>
|
||||
{this.props.selectedCohort.errorCoverage.toFixed(2)}%
|
||||
</div>
|
||||
</Stack>
|
||||
<svg
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="-2 -2 56 56"
|
||||
pointerEvents="auto"
|
||||
>
|
||||
<mask id="detailMask">
|
||||
<rect x="-26" y="-26" width="52" height="52" fill="white" />
|
||||
</mask>
|
||||
<g className={classNames.opacityToggleCircle}>
|
||||
</Stack>
|
||||
<svg
|
||||
width="60"
|
||||
height="60"
|
||||
viewBox="-2 -2 56 56"
|
||||
pointerEvents="auto"
|
||||
>
|
||||
<mask id="detailMask">
|
||||
<rect x="-26" y="-26" width="52" height="52" fill="white" />
|
||||
</mask>
|
||||
<g className={classNames.opacityToggleCircle}>
|
||||
<circle
|
||||
r="26"
|
||||
className={classNames.node}
|
||||
style={this.props.nodeDetail.errorColor}
|
||||
/>
|
||||
<g
|
||||
style={this.props.nodeDetail.maskDown}
|
||||
mask="url(#detailMask)"
|
||||
className={classNames.nopointer}
|
||||
>
|
||||
<circle
|
||||
r="26"
|
||||
className={classNames.node}
|
||||
style={this.props.nodeDetail.errorColor}
|
||||
fill={ColorPalette.FillStyle}
|
||||
style={this.props.nodeDetail.maskUp}
|
||||
/>
|
||||
<g
|
||||
style={this.props.nodeDetail.maskDown}
|
||||
mask="url(#detailMask)"
|
||||
className={classNames.nopointer}
|
||||
>
|
||||
<circle
|
||||
r="26"
|
||||
className={classNames.node}
|
||||
fill={ColorPalette.FillStyle}
|
||||
style={this.props.nodeDetail.maskUp}
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<div className={classNames.metricBarRed}></div>
|
||||
<Stack tokens={cellTokens}>
|
||||
<div className={classNames.smallHeader}>
|
||||
{localization.ErrorAnalysis.errorRate}
|
||||
<InfoCallout
|
||||
iconId={this._errorRateIconId}
|
||||
infoText={localization.ErrorAnalysis.errorRateInfo}
|
||||
title={localization.ErrorAnalysis.errorRateTitle}
|
||||
></InfoCallout>
|
||||
</div>
|
||||
<div className={classNames.valueBlack}>
|
||||
{this.props.selectedCohort.errorRate.toFixed(2)}%
|
||||
</div>
|
||||
</Stack>
|
||||
</g>
|
||||
</svg>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Stack horizontal>
|
||||
<div className={classNames.metricBarRed}></div>
|
||||
<Stack tokens={cellTokens}>
|
||||
<div className={classNames.smallHeader}>
|
||||
{localization.ErrorAnalysis.errorRate}
|
||||
<InfoCallout
|
||||
iconId={this._errorRateIconId}
|
||||
infoText={localization.ErrorAnalysis.errorRateInfo}
|
||||
title={localization.ErrorAnalysis.errorRateTitle}
|
||||
></InfoCallout>
|
||||
</div>
|
||||
<div className={classNames.valueBlack}>
|
||||
{this.props.selectedCohort.errorRate.toFixed(2)}%
|
||||
</div>
|
||||
</Stack>
|
||||
<svg width="60" height="60" viewBox="0 0 40 40">
|
||||
<g>
|
||||
<ErrorRateGradient
|
||||
max={this.props.max}
|
||||
minPct={0}
|
||||
selectedCohort={this.props.selectedCohort}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</Stack>
|
||||
<svg width="60" height="60" viewBox="0 0 40 40">
|
||||
<g>
|
||||
<ErrorRateGradient
|
||||
max={this.props.max}
|
||||
minPct={0}
|
||||
selectedCohort={this.props.selectedCohort}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</div>
|
||||
|
|
|
@ -90,7 +90,7 @@ export class TreeViewNode extends React.Component<ITreeViewNodeProps> {
|
|||
private getNodeClassName(
|
||||
classNames: IProcessedStyleSet<ITreeViewRendererStyles>,
|
||||
ratio: number,
|
||||
fill: string
|
||||
fill: string | undefined
|
||||
): string {
|
||||
let nodeTextClassName = classNames.nodeText;
|
||||
if (ratio > 50 && isColorDark(fill)) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<any[]>;
|
||||
staticTreeNodes?: any;
|
||||
getTreeNodes?: (
|
||||
request: any[],
|
||||
abortSignal: AbortSignal
|
||||
) => Promise<IRequestNode[]>;
|
||||
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<SVGSVGElement> = React.createRef();
|
||||
const treeZoomPane: React.RefObject<SVGSVGElement> = 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 <div></div>;
|
||||
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<SVGSVGElement, ISVGDatum>()
|
||||
.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<SVGSVGElement, undefined>(
|
||||
svgOuterFrame.current!
|
||||
).datum<ISVGDatum>({
|
||||
const svg = select<SVGSVGElement, undefined>(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<any, HierarchyPointNode<ITreeNode>>()
|
||||
.x((d: HierarchyPointNode<ITreeNode>) => d!.x!)
|
||||
.y((d: HierarchyPointNode<ITreeNode>) => d!.y!);
|
||||
const linkVertical = d3linkVertical<
|
||||
unknown,
|
||||
HierarchyPointNode<ITreeNode>
|
||||
>()
|
||||
.x((d: HierarchyPointNode<ITreeNode>) => d.x)
|
||||
.y((d: HierarchyPointNode<ITreeNode>) => 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<ITreeNode>) => {
|
||||
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<ITreeNode>) => d.data.nodeState.onSelectedPath
|
||||
)
|
||||
.map((d: HierarchyPointNode<ITreeNode>) => {
|
||||
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<HierarchyPointNode<ITreeNode>> = rootDescendents.map(
|
||||
const nodeData: Array<HierarchyPointNode<ITreeNode>> = rootDescendants.map(
|
||||
(d: HierarchyPointNode<ITreeNode>): HierarchyPointNode<ITreeNode> => {
|
||||
let selectedStyle: Record<string, number | string> = {
|
||||
let selectedStyle: Record<string, number | string | undefined> = {
|
||||
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 (
|
||||
<div className={classNames.mainFrame} id="mainFrame">
|
||||
<div className={classNames.innerFrame}>
|
||||
<div className={classNames.legend}>
|
||||
<Stack tokens={{ childrenGap: "l1", padding: "l1" }}>
|
||||
<Stack.Item>
|
||||
<ExpandableText
|
||||
expandedText={
|
||||
localization.ErrorAnalysis.TreeView.treeDescriptionExpanded
|
||||
}
|
||||
iconName="Info"
|
||||
variant={"smallPlus"}
|
||||
>
|
||||
{localization.ErrorAnalysis.TreeView.treeDescription}
|
||||
</ExpandableText>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack horizontal className={classNames.svgContainer}>
|
||||
<TreeLegend
|
||||
selectedCohort={this.props.selectedCohort}
|
||||
baseCohort={this.props.baseCohort}
|
||||
|
@ -240,76 +275,48 @@ export class TreeViewRenderer extends React.PureComponent<
|
|||
minPct={minPct}
|
||||
max={max}
|
||||
/>
|
||||
</div>
|
||||
<svg
|
||||
ref={svgOuterFrame}
|
||||
className={classNames.svgOuterFrame}
|
||||
id="svgOuterFrame"
|
||||
viewBox="0 0 952 1100"
|
||||
>
|
||||
<mask id="Mask">
|
||||
<rect
|
||||
className="nodeMask"
|
||||
x="-26"
|
||||
y="-26"
|
||||
width="52"
|
||||
height="52"
|
||||
fill="white"
|
||||
/>
|
||||
</mask>
|
||||
|
||||
<g ref={treeZoomPane} className="treeZoomPane">
|
||||
{/* Tree */}
|
||||
<TransitionGroup
|
||||
component="g"
|
||||
className={classNames.linksTransitionGroup}
|
||||
>
|
||||
{links.map((link) => (
|
||||
<CSSTransition
|
||||
key={link.id}
|
||||
in={true}
|
||||
timeout={200}
|
||||
className="links"
|
||||
>
|
||||
<svg
|
||||
ref={svgOuterFrame}
|
||||
className={classNames.svgOuterFrame}
|
||||
id="svgOuterFrame"
|
||||
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
|
||||
style={{
|
||||
minWidth: svgWidth,
|
||||
width: svgWidth * 1.5
|
||||
}}
|
||||
>
|
||||
<g className={containerStyles}>
|
||||
<g>
|
||||
{links.map((link) => (
|
||||
<path
|
||||
key={link.id}
|
||||
id={link.id}
|
||||
d={link.d}
|
||||
style={link.style}
|
||||
/>
|
||||
</CSSTransition>
|
||||
))}
|
||||
</TransitionGroup>
|
||||
<g className={classNames.nodesGroup}>
|
||||
{nodeData.map((node, index) => {
|
||||
return (
|
||||
<TreeViewNode
|
||||
key={index}
|
||||
node={node}
|
||||
onSelect={this.onSelectNode}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
<TransitionGroup
|
||||
component="g"
|
||||
className={classNames.linkLabelsTransitionGroup}
|
||||
>
|
||||
{linkLabels.map((linkLabel) => (
|
||||
<CSSTransition
|
||||
key={linkLabel.id}
|
||||
in={true}
|
||||
timeout={200}
|
||||
className="linkLabels"
|
||||
>
|
||||
))}
|
||||
</g>
|
||||
<g>
|
||||
{nodeData.map((node, index) => {
|
||||
return (
|
||||
<TreeViewNode
|
||||
key={index}
|
||||
node={node}
|
||||
onSelect={this.onSelectNode}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
<g>
|
||||
{linkLabels.map((linkLabel) => (
|
||||
<g
|
||||
key={linkLabel.id}
|
||||
style={linkLabel.style}
|
||||
pointerEvents="none"
|
||||
>
|
||||
<rect
|
||||
x={-linkLabel.bbX}
|
||||
y={-linkLabel.bbY}
|
||||
x={linkLabel.bbX}
|
||||
y={linkLabel.bbY}
|
||||
width={linkLabel.bbWidth}
|
||||
height={linkLabel.bbHeight}
|
||||
fill="white"
|
||||
|
@ -326,35 +333,16 @@ export class TreeViewRenderer extends React.PureComponent<
|
|||
{linkLabel.text}
|
||||
</text>
|
||||
</g>
|
||||
</CSSTransition>
|
||||
))}
|
||||
</TransitionGroup>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
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>
|
||||
): 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<ITreeViewRendererState>
|
||||
|
@ -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<ITreeNode>;
|
||||
|
||||
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<ITreeViewRendererStyles>
|
||||
): 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<ITreeNode>): 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<ITreeNode>): 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<any, ISVGDatum>): 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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ export class WhatIf extends React.Component<IWhatIfProps, IWhatIfState> {
|
|||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
this.createCopyOfFirstRow();
|
||||
this.buildRowOptions();
|
||||
|
||||
|
|
|
@ -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<
|
|||
>
|
||||
<Pivot
|
||||
selectedKey={this.state.activeGlobalTab}
|
||||
onLinkClick={this.handleGlobalTabClick.bind(this)}
|
||||
onLinkClick={this.handleGlobalTabClick}
|
||||
linkSize={PivotLinkSize.normal}
|
||||
headersOnly={true}
|
||||
styles={{ root: classNames.pivotLabelWrapper }}
|
||||
|
@ -592,9 +570,7 @@ export class ErrorAnalysisDashboard extends React.PureComponent<
|
|||
}}
|
||||
customPoints={this.state.customPoints}
|
||||
selectedCohort={this.state.selectedCohort}
|
||||
setWhatIfDatapoint={(index: number) =>
|
||||
this.setState({ selectedWhatIfIndex: index })
|
||||
}
|
||||
setWhatIfDatapoint={this.setWhatIfDatapoint}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -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}
|
||||
/>
|
||||
</Layer>
|
||||
|
@ -659,14 +635,33 @@ export class ErrorAnalysisDashboard extends React.PureComponent<
|
|||
</ModelAssessmentContext.Provider>
|
||||
);
|
||||
}
|
||||
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({
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<string, number | string> | undefined;
|
||||
errorStyle: Record<string, number | string | undefined> | 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: []
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -98,7 +98,7 @@ export class CohortEditor extends React.PureComponent<
|
|||
return (
|
||||
<>
|
||||
<Panel
|
||||
onOuterClick={() => {
|
||||
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<void> => {
|
||||
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 });
|
||||
};
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<MissingParametersPlaceholder>
|
||||
{localization.Interpret.DatasetExplorer.missingParameters}
|
||||
</MissingParametersPlaceholder>
|
||||
);
|
||||
}
|
||||
if (this.state.chartProps === undefined) {
|
||||
return <div />;
|
||||
}
|
||||
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 (
|
||||
<div className={classNames.page}>
|
||||
<div className={classNames.infoWithText}>
|
||||
<ExpandableText
|
||||
expandedText={localization.Interpret.DatasetExplorer.helperText}
|
||||
iconName="Info"
|
||||
>
|
||||
{localization.Interpret.DatasetExplorer.collapsedHelperText}
|
||||
</ExpandableText>
|
||||
</div>
|
||||
<div className={classNames.cohortPickerWrapper}>
|
||||
<Text variant="mediumPlus" className={classNames.cohortPickerLabel}>
|
||||
{localization.Interpret.ModelPerformance.cohortPickerLabel}
|
||||
</Text>
|
||||
{cohortOptions && (
|
||||
<Dropdown
|
||||
styles={{ dropdown: { width: 150 } }}
|
||||
options={cohortOptions}
|
||||
selectedKey={this.state.selectedCohortIndex}
|
||||
onChange={this.setSelectedCohort}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={classNames.mainArea} id={this.chartAndConfigsId}>
|
||||
<div className={classNames.chartWithAxes}>
|
||||
{this.state.yDialogOpen && (
|
||||
<AxisConfigDialog
|
||||
jointDataset={this.context.jointDataset}
|
||||
orderedGroupTitles={yAxisCategories}
|
||||
selectedColumn={this.state.chartProps.yAxis}
|
||||
canBin={false}
|
||||
mustBin={false}
|
||||
canDither={
|
||||
this.state.chartProps.chartType === ChartTypes.Scatter
|
||||
}
|
||||
onAccept={this.onYSet}
|
||||
onCancel={this.setYOpen.bind(this, false)}
|
||||
/>
|
||||
)}
|
||||
{this.state.xDialogOpen && (
|
||||
<AxisConfigDialog
|
||||
jointDataset={this.context.jointDataset}
|
||||
orderedGroupTitles={[
|
||||
ColumnCategories.Index,
|
||||
ColumnCategories.Dataset,
|
||||
ColumnCategories.Outcome
|
||||
]}
|
||||
selectedColumn={this.state.chartProps.xAxis}
|
||||
canBin={
|
||||
this.state.chartProps.chartType === ChartTypes.Histogram ||
|
||||
this.state.chartProps.chartType === ChartTypes.Box
|
||||
}
|
||||
mustBin={
|
||||
this.state.chartProps.chartType === ChartTypes.Histogram ||
|
||||
this.state.chartProps.chartType === ChartTypes.Box
|
||||
}
|
||||
canDither={
|
||||
this.state.chartProps.chartType === ChartTypes.Scatter
|
||||
}
|
||||
onAccept={this.onXSet}
|
||||
onCancel={this.setXOpen.bind(this, false)}
|
||||
/>
|
||||
)}
|
||||
{this.state.colorDialogOpen && this.state.chartProps.colorAxis && (
|
||||
<AxisConfigDialog
|
||||
jointDataset={this.context.jointDataset}
|
||||
orderedGroupTitles={[
|
||||
ColumnCategories.Index,
|
||||
ColumnCategories.Dataset,
|
||||
ColumnCategories.Outcome
|
||||
]}
|
||||
selectedColumn={this.state.chartProps.colorAxis}
|
||||
canBin={true}
|
||||
mustBin={false}
|
||||
canDither={false}
|
||||
onAccept={this.onColorSet}
|
||||
onCancel={this.setColorClose}
|
||||
/>
|
||||
)}
|
||||
<div className={classNames.chartWithVertical}>
|
||||
<div className={classNames.verticalAxis}>
|
||||
<div className={classNames.rotatedVerticalBox}>
|
||||
<div>
|
||||
<DefaultButton
|
||||
onClick={this.setYOpen.bind(this, true)}
|
||||
text={
|
||||
this.context.jointDataset.metaDict[
|
||||
this.state.chartProps.yAxis.property
|
||||
].abbridgedLabel
|
||||
}
|
||||
title={
|
||||
this.context.jointDataset.metaDict[
|
||||
this.state.chartProps.yAxis.property
|
||||
].label
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{canRenderChart ? (
|
||||
<AccessibleChart plotlyProps={plotlyProps} theme={getTheme()} />
|
||||
) : (
|
||||
<MissingParametersPlaceholder>
|
||||
{localization.Interpret.ValidationErrors.datasizeError}
|
||||
</MissingParametersPlaceholder>
|
||||
)}
|
||||
</div>
|
||||
<div className={classNames.horizontalAxisWithPadding}>
|
||||
<div className={classNames.paddingDiv}></div>
|
||||
<div className={classNames.horizontalAxis}>
|
||||
<div>
|
||||
<DefaultButton
|
||||
onClick={this.setXOpen.bind(this, true)}
|
||||
text={
|
||||
this.context.jointDataset.metaDict[
|
||||
this.state.chartProps.xAxis.property
|
||||
].abbridgedLabel
|
||||
}
|
||||
title={
|
||||
this.context.jointDataset.metaDict[
|
||||
this.state.chartProps.xAxis.property
|
||||
].label
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<SidePanel
|
||||
chartProps={this.state.chartProps}
|
||||
cohorts={this.context.errorCohorts.map(
|
||||
(errorCohort) => errorCohort.cohort
|
||||
)}
|
||||
jointDataset={this.context.jointDataset}
|
||||
selectedCohortIndex={this.state.selectedCohortIndex}
|
||||
setColorOpen={this.setColorOpen}
|
||||
onChartTypeChange={this.onChartTypeChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private setSelectedCohort = (
|
||||
_: React.FormEvent<HTMLDivElement>,
|
||||
item?: IDropdownOption
|
||||
): void => {
|
||||
if (item?.key !== undefined) {
|
||||
this.setState({ selectedCohortIndex: item.key as number });
|
||||
}
|
||||
};
|
||||
|
||||
private onChartTypeChange = (
|
||||
_ev?: React.SyntheticEvent<HTMLElement>,
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -110,7 +110,7 @@ export class GlobalExplanationTab extends React.PureComponent<
|
|||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
if (!this.context.jointDataset.hasLocalExplanations) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ export class ModelPerformanceTab extends React.PureComponent<
|
|||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
this.setState({ chartProps: this.generateDefaultChartAxes() });
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -130,7 +130,7 @@ export class WhatIfTab extends React.PureComponent<
|
|||
};
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
public componentDidMount(): void {
|
||||
this.createCopyOfFirstRow();
|
||||
this.buildRowOptions(0);
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<IAddTabButtonStyles>({
|
||||
button: {
|
||||
|
|
|
@ -101,19 +101,23 @@ export class AddTabButton extends React.Component<
|
|||
private onChange = (
|
||||
_: React.FormEvent<HTMLDivElement>,
|
||||
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
|
||||
|
|
|
@ -18,7 +18,7 @@ export class DashboardSettingDeleteButton extends React.PureComponent<
|
|||
);
|
||||
}
|
||||
|
||||
private removeTab = () => {
|
||||
private removeTab = (): void => {
|
||||
this.props.removeTab(this.props.index);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
<DashboardSettingDeleteButton
|
||||
index={index}
|
||||
|
|
|
@ -55,13 +55,15 @@ export class MainMenu extends React.PureComponent<
|
|||
> {
|
||||
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
|
||||
}));
|
||||
|
|
|
@ -21,7 +21,7 @@ export interface INavigationProps {
|
|||
export class Navigation extends React.PureComponent<INavigationProps> {
|
||||
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<INavigationProps> {
|
|||
}
|
||||
}
|
||||
|
||||
function onRenderGroupHeader(group?: INavLinkGroup) {
|
||||
function onRenderGroupHeader(group?: INavLinkGroup): JSX.Element {
|
||||
return (
|
||||
<h6 style={{ paddingLeft: "20px" }}>
|
||||
{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 (
|
||||
<Text
|
||||
|
|
|
@ -6,17 +6,19 @@ import { mergeStyleSets, IStyle } from "office-ui-fabric-react";
|
|||
export interface IModelAssessmentDashboardStyles {
|
||||
page: IStyle;
|
||||
section: IStyle;
|
||||
buttonSection: IStyle;
|
||||
}
|
||||
|
||||
export const modelAssessmentDashboardStyles = mergeStyleSets<
|
||||
IModelAssessmentDashboardStyles
|
||||
>({
|
||||
buttonSection: {
|
||||
textAlign: "center"
|
||||
},
|
||||
page: {
|
||||
boxSizing: "border-box",
|
||||
padding: "16px 40px 0 14px",
|
||||
width: "100%"
|
||||
},
|
||||
section: {
|
||||
textAlign: "center"
|
||||
}
|
||||
section: {}
|
||||
});
|
||||
|
|
|
@ -92,125 +92,145 @@ export class ModelAssessmentDashboard extends CohortBasedComponent<
|
|||
<Stack>
|
||||
{this.state.activeGlobalTabs[0]?.key !==
|
||||
GlobalTabKeys.ErrorAnalysisTab && (
|
||||
<Stack.Item className={modelAssessmentDashboardStyles.section}>
|
||||
<Stack.Item
|
||||
className={modelAssessmentDashboardStyles.buttonSection}
|
||||
>
|
||||
<AddTabButton tabIndex={0} onAdd={this.addTab} />
|
||||
</Stack.Item>
|
||||
)}
|
||||
{this.state.activeGlobalTabs.map((t, i) => (
|
||||
<Stack.Item
|
||||
key={i}
|
||||
className={modelAssessmentDashboardStyles.section}
|
||||
>
|
||||
{t.key === GlobalTabKeys.ErrorAnalysisTab && (
|
||||
<ErrorAnalysisViewTab
|
||||
messages={
|
||||
this.props.stringParams
|
||||
? this.props.stringParams.contextualHelp
|
||||
: undefined
|
||||
}
|
||||
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
|
||||
) => {
|
||||
if (this.state.selectedCohort !== this.state.baseCohort) {
|
||||
this.setState({ treeViewState });
|
||||
<>
|
||||
<Stack.Item
|
||||
key={i}
|
||||
className={modelAssessmentDashboardStyles.section}
|
||||
>
|
||||
{t.key === GlobalTabKeys.ErrorAnalysisTab && (
|
||||
<ErrorAnalysisViewTab
|
||||
messages={
|
||||
this.props.stringParams
|
||||
? this.props.stringParams.contextualHelp
|
||||
: undefined
|
||||
}
|
||||
}}
|
||||
matrixAreaState={this.state.matrixAreaState}
|
||||
matrixFilterState={this.state.matrixFilterState}
|
||||
setMatrixAreaState={(matrixAreaState: IMatrixAreaState) => {
|
||||
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 && (
|
||||
<ModelPerformanceTab />
|
||||
)}
|
||||
{t.key === GlobalTabKeys.DataExplorerTab && (
|
||||
<DatasetExplorerTab showCohortSelection={false} />
|
||||
)}
|
||||
{t.key === GlobalTabKeys.GlobalExplanationTab && (
|
||||
<GlobalExplanationTab
|
||||
cohorts={this.state.cohorts.map(
|
||||
(cohort) => 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 && (
|
||||
<ModelPerformanceTab />
|
||||
)}
|
||||
{t.key === GlobalTabKeys.DataExplorerTab && (
|
||||
<DatasetExplorerTab showCohortSelection={false} />
|
||||
)}
|
||||
{t.key === GlobalTabKeys.GlobalExplanationTab && (
|
||||
<GlobalExplanationTab
|
||||
cohorts={this.state.cohorts.map((cohort) => 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 && (
|
||||
<InstanceView
|
||||
messages={
|
||||
this.props.stringParams
|
||||
? this.props.stringParams.contextualHelp
|
||||
: undefined
|
||||
}
|
||||
features={this.props.dataset.featureNames}
|
||||
invokeModel={this.props.requestPredictions}
|
||||
selectedWeightVector={this.state.selectedWeightVector}
|
||||
weightOptions={this.state.weightVectorOptions}
|
||||
weightLabels={this.state.weightVectorLabels}
|
||||
onWeightChange={this.onWeightVectorChange}
|
||||
activePredictionTab={this.state.predictionTab}
|
||||
setActivePredictionTab={(key: PredictionTabKeys): void => {
|
||||
this.setState({
|
||||
predictionTab: key
|
||||
});
|
||||
}}
|
||||
customPoints={this.state.customPoints}
|
||||
selectedCohort={this.state.selectedCohort}
|
||||
setWhatIfDatapoint={(index: number) =>
|
||||
this.setState({ selectedWhatIfIndex: index })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{t.key === GlobalTabKeys.CausalAnalysisTab && (
|
||||
<CausalInsightsTab data={this.props.casualAnalysisData} />
|
||||
)}
|
||||
{/*
|
||||
/>
|
||||
)}
|
||||
{t.key === GlobalTabKeys.LocalExplanationTab && (
|
||||
<InstanceView
|
||||
messages={
|
||||
this.props.stringParams
|
||||
? this.props.stringParams.contextualHelp
|
||||
: undefined
|
||||
}
|
||||
features={this.props.dataset.featureNames}
|
||||
invokeModel={this.props.requestPredictions}
|
||||
selectedWeightVector={this.state.selectedWeightVector}
|
||||
weightOptions={this.state.weightVectorOptions}
|
||||
weightLabels={this.state.weightVectorLabels}
|
||||
onWeightChange={this.onWeightVectorChange}
|
||||
activePredictionTab={this.state.predictionTab}
|
||||
setActivePredictionTab={(
|
||||
key: PredictionTabKeys
|
||||
): void => {
|
||||
this.setState({
|
||||
predictionTab: key
|
||||
});
|
||||
}}
|
||||
customPoints={this.state.customPoints}
|
||||
selectedCohort={this.state.selectedCohort}
|
||||
setWhatIfDatapoint={(index: number): void =>
|
||||
this.setState({ selectedWhatIfIndex: index })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{t.key === GlobalTabKeys.CausalAnalysisTab && (
|
||||
<CausalInsightsTab data={this.props.casualAnalysisData} />
|
||||
)}
|
||||
{/*
|
||||
{t.key === GlobalTabKeys.CounterfactualsTab && (
|
||||
<CounterfactualsTab />
|
||||
)} */}
|
||||
<AddTabButton tabIndex={i + 1} onAdd={this.addTab} />
|
||||
</Stack.Item>
|
||||
</Stack.Item>
|
||||
<Stack.Item
|
||||
className={modelAssessmentDashboardStyles.buttonSection}
|
||||
>
|
||||
<AddTabButton tabIndex={0} onAdd={this.addTab} />
|
||||
</Stack.Item>
|
||||
</>
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
</ModelAssessmentContext.Provider>
|
||||
);
|
||||
}
|
||||
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 });
|
||||
|
|
|
@ -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<any[]>;
|
||||
requestDebugML?: (request: any[], abortSignal: AbortSignal) => Promise<any[]>;
|
||||
requestDebugML?: (
|
||||
request: any[],
|
||||
abortSignal: AbortSignal
|
||||
) => Promise<IRequestNode[]>;
|
||||
requestMatrix?: (request: any[], abortSignal: AbortSignal) => Promise<any[]>;
|
||||
requestImportances?: (
|
||||
request: any[],
|
||||
|
|
|
@ -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",
|
||||
|
|
32
yarn.lock
32
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"
|
||||
|
|
Загрузка…
Ссылка в новой задаче