diff --git a/libs/core-ui/src/lib/components/LabelWithCallout.tsx b/libs/core-ui/src/lib/components/LabelWithCallout.tsx index 1ee3a01c0d..c1acea3709 100644 --- a/libs/core-ui/src/lib/components/LabelWithCallout.tsx +++ b/libs/core-ui/src/lib/components/LabelWithCallout.tsx @@ -18,6 +18,7 @@ import { labelWithCalloutStyles } from "./LabelWithCallout.styles"; export interface ILabelWithCalloutProps { label: string; calloutTitle: string | undefined; + renderOnNewLayer?: boolean; type?: "label" | "button"; } interface ILabelWithCalloutState { @@ -62,7 +63,7 @@ export class LabelWithCallout extends React.Component< )} {this.state.showCallout && ( IProcessedStyleSet = + () => { + const theme = getTheme(); + return mergeStyleSets({ + chevronButton: { + marginLeft: 48, + paddingTop: 6, + width: 36 + }, + header: { + margin: `8px 0`, + padding: 8, + // Overlay the sizer bars + position: "relative", + zIndex: 100 + }, + headerCount: [ + "headerCount", + theme.fonts.medium, + { + paddingTop: 4 + } + ], + headerTitle: [ + theme.fonts.medium, + { + paddingTop: 4 + } + ], + selectionCounter: { + paddingTop: 12 + } + }); + }; diff --git a/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/IndividualFeatureImportanceView.tsx b/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/IndividualFeatureImportanceView.tsx index 6cb38bf9b7..d1cabedc14 100644 --- a/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/IndividualFeatureImportanceView.tsx +++ b/libs/model-assessment/src/lib/ModelAssessmentDashboard/Controls/IndividualFeatureImportanceView.tsx @@ -11,7 +11,8 @@ import { FabricStyles, constructRows, constructCols, - ModelTypes + ModelTypes, + LabelWithCallout } from "@responsible-ai/core-ui"; import { IGlobalSeries, LocalImportancePlots } from "@responsible-ai/interpret"; import { localization } from "@responsible-ai/localization"; @@ -34,10 +35,14 @@ import { TooltipHost, IColumn, IGroup, - Text + Text, + IDetailsGroupDividerProps, + Icon } from "office-ui-fabric-react"; import React from "react"; +import { individualFeatureImportanceViewStyles } from "./IndividualFeatureImportanceView.styles"; + export interface IIndividualFeatureImportanceProps { features: string[]; jointDataset: JointDataset; @@ -59,6 +64,8 @@ export interface IIndividualFeatureImportanceTableState { export interface IIndividualFeatureImportanceState extends IIndividualFeatureImportanceTableState { featureImportances: IGlobalSeries[]; + indexToUnselect?: number; + selectedIndices: number[]; sortArray: number[]; sortingSeriesIndex?: number; } @@ -70,9 +77,22 @@ export class IndividualFeatureImportanceView extends React.Component< public static contextType = ModelAssessmentContext; public context: React.ContextType = defaultModelAssessmentContext; + private readonly maxSelectable = 5; private selection: Selection = new Selection({ onSelectionChanged: (): void => { + const c = this.selection.getSelectedCount(); + const indices = this.selection.getSelectedIndices(); + if (c === this.maxSelectable) { + this.setState({ selectedIndices: indices }); + } + if (c > this.maxSelectable) { + for (const index of indices) { + if (!this.state.selectedIndices.includes(index)) { + this.setState({ indexToUnselect: index }); + } + } + } this.updateViewedFeatureImportances(); } }); @@ -84,6 +104,8 @@ export class IndividualFeatureImportanceView extends React.Component< this.state = { featureImportances: [], + indexToUnselect: undefined, + selectedIndices: [], sortArray: [], ...tableState }; @@ -95,6 +117,10 @@ export class IndividualFeatureImportanceView extends React.Component< if (this.props.selectedCohort !== prevProps.selectedCohort) { this.setState(this.updateItems()); } + if (this.state.indexToUnselect) { + this.selection.toggleIndexSelected(this.state.indexToUnselect); + this.setState({ indexToUnselect: undefined }); + } } public render(): React.ReactNode { @@ -132,6 +158,7 @@ export class IndividualFeatureImportanceView extends React.Component< text: meta.abbridgedLabel }; }); + const classNames = individualFeatureImportanceViewStyles(); return ( @@ -140,6 +167,22 @@ export class IndividualFeatureImportanceView extends React.Component< {localization.ModelAssessment.FeatureImportances.IndividualFeature} + + + + {localization.ModelAssessment.FeatureImportances.SelectionLimit} + + +
@@ -155,10 +198,12 @@ export class IndividualFeatureImportanceView extends React.Component< onRenderDetailsHeader={this.onRenderDetailsHeader} selectionPreservedOnEmptyClick ariaLabelForSelectionColumn="Toggle selection" - ariaLabelForSelectAllCheckbox="Toggle selection for all items" checkButtonAriaLabel="Row checkbox" // checkButtonGroupAriaLabel="Group checkbox" - groupProps={{ showEmptyGroups: true }} + groupProps={{ + onRenderHeader: this._onRenderGroupHeader, + showEmptyGroups: true + }} selectionMode={SelectionMode.multiple} selection={this.selection} /> @@ -329,4 +374,32 @@ export class IndividualFeatureImportanceView extends React.Component<
); }; + + private _onRenderGroupHeader = (props?: IDetailsGroupDividerProps) => { + const classNames = individualFeatureImportanceViewStyles(); + const iconName = props?.group?.isCollapsed + ? "ChevronRightMed" + : "ChevronDownMed"; + return ( + + + {props?.group!.name} +   + + {`(${props?.group!.count})`} + + + ); + }; + + private _onToggleCollapse = (props?: IDetailsGroupDividerProps) => { + return () => { + props!.onToggleCollapse!(props!.group!); + }; + }; }