From 2a6ff82b4bc5fb46735b26f33f5a2fd4fc48f932 Mon Sep 17 00:00:00 2001 From: Ilya Matiach Date: Mon, 18 Dec 2023 14:15:32 -0500 Subject: [PATCH] fix class importance weights callout truncated in small view for accessibility (#2462) --- .../src/lib/components/LabelWithCallout.tsx | 17 ++- .../ClassImportanceWeights.styles.ts | 32 ----- .../ClassImportanceWeights.tsx | 124 +++++++----------- .../GlobalExplanationTab/SidePanel.tsx | 55 ++------ 4 files changed, 71 insertions(+), 157 deletions(-) delete mode 100644 libs/interpret/src/lib/MLIDashboard/Controls/ClassImportanceWeights/ClassImportanceWeights.styles.ts diff --git a/libs/core-ui/src/lib/components/LabelWithCallout.tsx b/libs/core-ui/src/lib/components/LabelWithCallout.tsx index 7d6c8237ea..e2b707414d 100644 --- a/libs/core-ui/src/lib/components/LabelWithCallout.tsx +++ b/libs/core-ui/src/lib/components/LabelWithCallout.tsx @@ -5,7 +5,8 @@ import { Callout as FabricCallout, CommandBarButton, IconButton, - Text + Text, + DirectionalHint } from "@fluentui/react"; import { localization } from "@responsible-ai/localization"; import React from "react"; @@ -24,6 +25,9 @@ export interface ILabelWithCalloutProps { renderOnNewLayer?: boolean; type?: "label" | "button"; telemetryHook?: (message: ITelemetryEvent) => void; + iconButtonId?: string; + calloutTarget?: string; + directionalHint?: DirectionalHint; } interface ILabelWithCalloutState { showCallout: boolean; @@ -42,6 +46,12 @@ export class LabelWithCallout extends React.Component< public render(): React.ReactNode { const classNames = labelWithCalloutStyles(); const id = `callout-${v4()}`; + const iconButtonId = this.props.iconButtonId + ? this.props.iconButtonId + : "label-callout-info"; + const calloutTarget = this.props.calloutTarget + ? this.props.calloutTarget + : `#${id}`; return (
{this.props.type === "button" ? ( @@ -58,7 +68,7 @@ export class LabelWithCallout extends React.Component< {this.props.label}
diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/ClassImportanceWeights/ClassImportanceWeights.styles.ts b/libs/interpret/src/lib/MLIDashboard/Controls/ClassImportanceWeights/ClassImportanceWeights.styles.ts deleted file mode 100644 index e6cbc5fbc9..0000000000 --- a/libs/interpret/src/lib/MLIDashboard/Controls/ClassImportanceWeights/ClassImportanceWeights.styles.ts +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { IStyle, mergeStyleSets, IProcessedStyleSet } from "@fluentui/react"; -import { FluentUIStyles } from "@responsible-ai/core-ui"; - -export interface ILabelWithCalloutStyles { - calloutHeader: IStyle; - calloutInner: IStyle; - calloutTitle: IStyle; - calloutWrapper: IStyle; - multiclassWeightLabel: IStyle; - multiclassWeightLabelText: IStyle; -} - -export const classImportanceWeightsStyles: () => IProcessedStyleSet = - () => { - return mergeStyleSets({ - calloutHeader: [FluentUIStyles.calloutHeader], - calloutInner: [FluentUIStyles.calloutInner], - calloutTitle: [FluentUIStyles.calloutTitle], - calloutWrapper: [FluentUIStyles.calloutWrapper], - multiclassWeightLabel: { - display: "inline-flex", - paddingTop: "10px" - }, - multiclassWeightLabelText: { - fontWeight: "600", - paddingTop: "5px" - } - }); - }; diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/ClassImportanceWeights/ClassImportanceWeights.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/ClassImportanceWeights/ClassImportanceWeights.tsx index 24d4c62a62..e0d2120988 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/ClassImportanceWeights/ClassImportanceWeights.tsx +++ b/libs/interpret/src/lib/MLIDashboard/Controls/ClassImportanceWeights/ClassImportanceWeights.tsx @@ -2,40 +2,34 @@ // Licensed under the MIT License. import { - Callout, DirectionalHint, Dropdown, - IconButton, IDropdownOption, Text } from "@fluentui/react"; -import { WeightVectorOption, FluentUIStyles } from "@responsible-ai/core-ui"; +import { + WeightVectorOption, + LabelWithCallout, + ITelemetryEvent, + TelemetryEventName +} from "@responsible-ai/core-ui"; import { localization } from "@responsible-ai/localization"; +import { Dictionary } from "lodash"; import React from "react"; -import { classImportanceWeightsStyles } from "./ClassImportanceWeights.styles"; - export interface IClassImportanceWeightsProps { onWeightChange: (option: WeightVectorOption) => void; selectedWeightVector: WeightVectorOption; weightOptions: WeightVectorOption[]; - weightLabels: any; + weightLabels: Dictionary; disabled?: boolean; -} -interface IClassImportanceWeightsState { - crossClassInfoVisible: boolean; + telemetryHook?: (message: ITelemetryEvent) => void; } -export class ClassImportanceWeights extends React.Component< - IClassImportanceWeightsProps, - IClassImportanceWeightsState -> { +export class ClassImportanceWeights extends React.Component { private weightOptions: IDropdownOption[] | undefined; public constructor(props: IClassImportanceWeightsProps) { super(props); - this.state = { - crossClassInfoVisible: false - }; this.weightOptions = this.props.weightOptions.map((option) => { return { key: option, @@ -44,74 +38,52 @@ export class ClassImportanceWeights extends React.Component< }); } public render(): React.ReactNode { - const classNames = classImportanceWeightsStyles(); + const iconButtonId = "cross-class-weight-info"; + const calloutTarget = `#${iconButtonId}`; return (
-
- - {localization.Interpret.GlobalTab.weightOptions} - - -
{this.weightOptions && ( - - )} - {this.state.crossClassInfoVisible && ( - -
-
- - {localization.Interpret.CrossClass.crossClassWeights} - -
-
- {localization.Interpret.CrossClass.overviewInfo} -
    -
  • - - {localization.Interpret.CrossClass.absoluteValInfo} - -
  • -
  • - - {localization.Interpret.CrossClass.enumeratedClassInfo} - -
  • -
-
-
-
+
+ + {localization.Interpret.CrossClass.overviewInfo} +
    +
  • + + {localization.Interpret.CrossClass.absoluteValInfo} + +
  • +
  • + + {localization.Interpret.CrossClass.enumeratedClassInfo} + +
  • +
+
+ +
)}
); } - private toggleCrossClassInfo = (): void => { - this.setState({ crossClassInfoVisible: !this.state.crossClassInfoVisible }); - }; - private setWeightOption = ( _event: React.FormEvent, item?: IDropdownOption diff --git a/libs/interpret/src/lib/MLIDashboard/Controls/GlobalExplanationTab/SidePanel.tsx b/libs/interpret/src/lib/MLIDashboard/Controls/GlobalExplanationTab/SidePanel.tsx index 4da15dbd12..1be3b8c6ca 100644 --- a/libs/interpret/src/lib/MLIDashboard/Controls/GlobalExplanationTab/SidePanel.tsx +++ b/libs/interpret/src/lib/MLIDashboard/Controls/GlobalExplanationTab/SidePanel.tsx @@ -6,8 +6,7 @@ import { Dropdown, IChoiceGroupOption, IDropdownOption, - Stack, - Text + Stack } from "@fluentui/react"; import { Cohort, @@ -15,9 +14,7 @@ import { IsClassifier, WeightVectorOption, ChartTypes, - LabelWithCallout, ITelemetryEvent, - TelemetryEventName, ifEnableLargeData, ModelAssessmentContext, defaultModelAssessmentContext @@ -26,6 +23,8 @@ import { localization } from "@responsible-ai/localization"; import { Dictionary } from "lodash"; import React from "react"; +import { ClassImportanceWeights } from "../ClassImportanceWeights/ClassImportanceWeights"; + import { globalTabStyles } from "./GlobalExplanationTab.styles"; import { IGlobalSeries } from "./IGlobalSeries"; @@ -106,39 +105,13 @@ export class SidePanel extends React.Component< {IsClassifier(this.props.metadata.modelType) && this.state.weightOptions && (
- - {localization.Interpret.CrossClass.overviewInfo} -
    -
  • - - {localization.Interpret.CrossClass.absoluteValInfo} - -
  • -
  • - - {localization.Interpret.CrossClass.enumeratedClassInfo} - -
  • -
-
-
)} @@ -176,16 +149,6 @@ export class SidePanel extends React.Component< return undefined; } - private setWeightOption = ( - _event: React.FormEvent, - item?: IDropdownOption - ): void => { - if (item?.key !== undefined) { - const newIndex = item.key as WeightVectorOption; - this.props.onWeightChange(newIndex); - } - }; - private getChartOptions(): IChoiceGroupOption[] { if (ifEnableLargeData(this.context.dataset)) { return this.largeDataChartOptions;