fix class importance weights callout truncated in small view for accessibility (#2462)

This commit is contained in:
Ilya Matiach 2023-12-18 14:15:32 -05:00 коммит произвёл GitHub
Родитель 6cb95c0dcc
Коммит 2a6ff82b4b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 71 добавлений и 157 удалений

Просмотреть файл

@ -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 (
<div className={classNames.calloutContainer}>
{this.props.type === "button" ? (
@ -58,7 +68,7 @@ export class LabelWithCallout extends React.Component<
{this.props.label}
</Text>
<IconButton
id={"label-callout-info"}
id={iconButtonId}
iconProps={{ iconName: "Info" }}
title={localization.Interpret.calloutTitle}
onClick={this.toggleCallout}
@ -68,10 +78,11 @@ export class LabelWithCallout extends React.Component<
{this.state.showCallout && (
<FabricCallout
doNotLayer={!this.props.renderOnNewLayer}
target={`#${id}`}
target={calloutTarget}
setInitialFocus
onDismiss={this.toggleCallout}
role="alertdialog"
directionalHint={this.props.directionalHint}
styles={{ container: FluentUIStyles.calloutContainer }}
>
<div className={classNames.calloutWrapper}>

Просмотреть файл

@ -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<ILabelWithCalloutStyles> =
() => {
return mergeStyleSets<ILabelWithCalloutStyles>({
calloutHeader: [FluentUIStyles.calloutHeader],
calloutInner: [FluentUIStyles.calloutInner],
calloutTitle: [FluentUIStyles.calloutTitle],
calloutWrapper: [FluentUIStyles.calloutWrapper],
multiclassWeightLabel: {
display: "inline-flex",
paddingTop: "10px"
},
multiclassWeightLabelText: {
fontWeight: "600",
paddingTop: "5px"
}
});
};

Просмотреть файл

@ -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<string>;
disabled?: boolean;
}
interface IClassImportanceWeightsState {
crossClassInfoVisible: boolean;
telemetryHook?: (message: ITelemetryEvent) => void;
}
export class ClassImportanceWeights extends React.Component<
IClassImportanceWeightsProps,
IClassImportanceWeightsState
> {
export class ClassImportanceWeights extends React.Component<IClassImportanceWeightsProps> {
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 (
<div id="ClassImportanceWeights">
<div className={classNames.multiclassWeightLabel}>
<Text
variant={"medium"}
className={classNames.multiclassWeightLabelText}
>
{localization.Interpret.GlobalTab.weightOptions}
</Text>
<IconButton
id={"cross-class-weight-info"}
iconProps={{ iconName: "Info" }}
title={localization.Interpret.CrossClass.info}
onClick={this.toggleCrossClassInfo}
/>
</div>
{this.weightOptions && (
<Dropdown
options={this.weightOptions}
selectedKey={this.props.selectedWeightVector}
onChange={this.setWeightOption}
ariaLabel={localization.Interpret.GlobalTab.weightOptions}
disabled={this.props.disabled ?? false}
/>
)}
{this.state.crossClassInfoVisible && (
<Callout
doNotLayer
target={"#cross-class-weight-info"}
setInitialFocus
onDismiss={this.toggleCrossClassInfo}
directionalHint={DirectionalHint.leftCenter}
role="alertdialog"
styles={{ container: FluentUIStyles.calloutContainer }}
>
<div className={classNames.calloutWrapper}>
<div className={classNames.calloutHeader}>
<Text className={classNames.calloutTitle}>
{localization.Interpret.CrossClass.crossClassWeights}
</Text>
</div>
<div className={classNames.calloutInner}>
<Text>{localization.Interpret.CrossClass.overviewInfo}</Text>
<ul>
<li>
<Text>
{localization.Interpret.CrossClass.absoluteValInfo}
</Text>
</li>
<li>
<Text>
{localization.Interpret.CrossClass.enumeratedClassInfo}
</Text>
</li>
</ul>
</div>
</div>
</Callout>
<div>
<LabelWithCallout
calloutTitle={localization.Interpret.CrossClass.crossClassWeights}
label={localization.Interpret.GlobalTab.weightOptions}
telemetryHook={this.props.telemetryHook}
calloutEventName={
TelemetryEventName.FeatureImportancesCrossClassWeightsCalloutClick
}
iconButtonId={iconButtonId}
calloutTarget={calloutTarget}
renderOnNewLayer
directionalHint={DirectionalHint.leftCenter}
>
<Text>{localization.Interpret.CrossClass.overviewInfo}</Text>
<ul>
<li>
<Text>
{localization.Interpret.CrossClass.absoluteValInfo}
</Text>
</li>
<li>
<Text>
{localization.Interpret.CrossClass.enumeratedClassInfo}
</Text>
</li>
</ul>
</LabelWithCallout>
<Dropdown
id={"classWeightDropdown"}
options={this.weightOptions}
selectedKey={this.props.selectedWeightVector}
onChange={this.setWeightOption}
ariaLabel={localization.Interpret.GlobalTab.weightOptionsDropdown}
disabled={this.props.disabled ?? false}
/>
</div>
)}
</div>
);
}
private toggleCrossClassInfo = (): void => {
this.setState({ crossClassInfoVisible: !this.state.crossClassInfoVisible });
};
private setWeightOption = (
_event: React.FormEvent<HTMLDivElement>,
item?: IDropdownOption

Просмотреть файл

@ -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 && (
<div>
<LabelWithCallout
calloutTitle={
localization.Interpret.CrossClass.crossClassWeights
}
label={localization.Interpret.GlobalTab.weightOptions}
telemetryHook={this.props.telemetryHook}
calloutEventName={
TelemetryEventName.FeatureImportancesCrossClassWeightsCalloutClick
}
>
<Text>{localization.Interpret.CrossClass.overviewInfo}</Text>
<ul>
<li>
<Text>
{localization.Interpret.CrossClass.absoluteValInfo}
</Text>
</li>
<li>
<Text>
{localization.Interpret.CrossClass.enumeratedClassInfo}
</Text>
</li>
</ul>
</LabelWithCallout>
<Dropdown
id={"classWeightDropdown"}
options={this.state.weightOptions}
selectedKey={this.props.selectedWeightVector}
onChange={this.setWeightOption}
<ClassImportanceWeights
onWeightChange={this.props.onWeightChange}
selectedWeightVector={this.props.selectedWeightVector}
weightOptions={this.props.weightOptions}
weightLabels={this.props.weightLabels}
disabled={this.props.loading}
ariaLabel={
localization.Interpret.GlobalTab.weightOptionsDropdown
}
telemetryHook={this.props.telemetryHook}
/>
</div>
)}
@ -176,16 +149,6 @@ export class SidePanel extends React.Component<
return undefined;
}
private setWeightOption = (
_event: React.FormEvent<HTMLDivElement>,
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;