Skip to content

Commit

Permalink
Adds faceting support to Confusion Matrix Module.
Browse files Browse the repository at this point in the history
Confusion Matrix module now displays two sections of content. The first section
will always contain a confusion matrix for the entire dataset, and possibly a
matrix for the current selection, if the user enables it.

The second section displays a confusion matrix for each of the user-defined
facet groups. You can configure the faceting behavior using the faceting
control in the toolbar. Facet group matrices are sorted into ascending
alphabetical order by title, and wrap to the next row when they overflow their
parent container.

This change removes the ability to "add" new matrices manually, to simplify
the user experience. Now, all displayed matrices will have the same rows and
columns, which you can select using the drop-downs in the toolbar.

PiperOrigin-RevId: 430255969
  • Loading branch information
RyanMullins authored and LIT team committed Feb 22, 2022
1 parent 405a157 commit 8993f9b
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 310 deletions.
4 changes: 4 additions & 0 deletions lit_nlp/client/core/faceting_control.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
line-height: 22px;
}

.active-facets.disabled {
color: var(--lit-neutral-400);
}

.config-panel {
position: absolute;
top: 26px;
Expand Down
33 changes: 23 additions & 10 deletions lit_nlp/client/core/faceting_control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export class FacetingControl extends ReactiveElement {
@observable private features: string[] = [];
@observable private bins: NumericFeatureBins = {};

@observable @property({type: Boolean}) disabled = false;
@observable @property({type: String}) contextName?: string;
@observable @property({type: Number}) binLimit = DEFAULT_BIN_LIMIT;
@observable @property({type: Number}) choiceLimit?: number;
Expand All @@ -78,6 +79,16 @@ export class FacetingControl extends ReactiveElement {
this.initFeatureConfigs();
}

/**
* Resets to the default faceting behavior (i.e., do not facet). Typically
* called if the avaialble features change.
*/
reset() {
this.features = [];
this.updateBins();
this.initFeatureConfigs();
}

/**
* Resets the faceting configurations to use the Equal Interval binning method
* and establish the number of bins that would be generated if the user
Expand Down Expand Up @@ -146,13 +157,7 @@ export class FacetingControl extends ReactiveElement {

override firstUpdated() {
const numericFeatures = () => this.groupService.denseFeatureNames;
this.reactImmediately(numericFeatures, () => {
// If the avaialble features change, reset the faceting configurations and
// user selections
this.features = [];
this.updateBins();
this.initFeatureConfigs();
});
this.reactImmediately(numericFeatures, () => {this.reset();});

const onBodyClick = (event: MouseEvent) => {this.clickToClose(event);};
this.reactImmediately(() => this.expanded, () => {
Expand Down Expand Up @@ -344,18 +349,26 @@ export class FacetingControl extends ReactiveElement {
'None';

const forContext = this.contextName ? ` for ${this.contextName}` : '';
const title =
`${this.expanded ? 'Hide' :
'Show'} the faceting configuration${forContext}`;

const activeFacetsClass = classMap({
'active-facets': true,
'disabled': this.disabled
});

const closeButtonClick = () => {this.expanded = false;};

// clang-format off
return html`
<div class="faceting-info">
<button class="hairline-button" @click=${this.toggleExpanded}
title="Show or hide the faceting configuration${forContext}">
<button class="hairline-button" title=${title}
?disabled=${this.disabled} @click=${this.toggleExpanded}>
<span class="material-icon">dashboard</span>
Facets
</button>
<span class="active-facets">: ${facetsList}</span>
<span class=${activeFacetsClass}>: ${facetsList}</span>
<div class="config-panel popup-container" style=${configPanelStyles}>
<div class="panel-header">
<span class="panel-label">Faceting Config${forContext}</span>
Expand Down
43 changes: 33 additions & 10 deletions lit_nlp/client/core/faceting_control_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,6 @@ describe('faceting control test', () => {
let configPanel: HTMLDivElement;
let closeButton: HTMLElement;

const facetChangeHandler = (event: Event) => {
const customEvent = event as CustomEvent<FacetsChange>;
expect(customEvent.detail.features.length).toBe(1);
expect(customEvent.detail.features[0]).toBe('label');
expect(customEvent.detail.bins).toBeTruthy();
};

beforeEach(async () => {
// Set up.
const app = new LitApp();
Expand All @@ -36,7 +29,6 @@ describe('faceting control test', () => {
const groupService = new GroupService(appState, dataService);
facetCtrl = new FacetingControl(groupService);
document.body.appendChild(facetCtrl);
document.body.addEventListener('facets-change', facetChangeHandler);
await facetCtrl.updateComplete;

facetButton =
Expand All @@ -48,7 +40,6 @@ describe('faceting control test', () => {
});

afterEach(() => {
document.body.removeEventListener('facets-change', facetChangeHandler);
document.body.removeChild(facetCtrl);
});

Expand All @@ -68,7 +59,7 @@ describe('faceting control test', () => {
const [facetButton, facetList, configPanel] = innerDiv.children;
expect(facetButton instanceof HTMLButtonElement).toBeTrue();
expect(facetList instanceof HTMLSpanElement).toBeTrue();
expect((facetList as HTMLSpanElement).className).toEqual('active-facets');
expect((facetList as HTMLSpanElement).className).toEqual(' active-facets ');
expect(configPanel instanceof HTMLDivElement).toBeTrue();
expect((configPanel as HTMLDivElement).className)
.toEqual('config-panel popup-container');
Expand Down Expand Up @@ -109,6 +100,13 @@ describe('faceting control test', () => {
});

it('emits a custom facets-change event after checkbox click', async () => {
const facetChangeHandler = (event: Event) => {
const customEvent = event as CustomEvent<FacetsChange>;
expect(customEvent.detail.features.length).toBe(1);
expect(customEvent.detail.features[0]).toBe('label');
expect(customEvent.detail.bins).toBeTruthy();
};
document.body.addEventListener('facets-change', facetChangeHandler);
facetButton.click();
await facetCtrl.updateComplete;

Expand All @@ -120,5 +118,30 @@ describe('faceting control test', () => {
input.click();
await facetCtrl.updateComplete;
expect(input.checked).toBeTrue();
document.body.removeEventListener('facets-change', facetChangeHandler);
});

it('can be reset programmatically', async () => {
let expectedFacetCount = 1;
const facetChangeHandler = (event: Event) => {
const customEvent = event as CustomEvent<FacetsChange>;
expect(customEvent.detail.features.length).toBe(expectedFacetCount);
expect(customEvent.detail.bins).toBeTruthy();
};

document.body.addEventListener('facets-change', facetChangeHandler);
facetButton.click();
await facetCtrl.updateComplete;

const featureRow = configPanel.querySelector('div.feature-options-row') as HTMLDivElement;
const litCheckbox = featureRow.querySelector('lit-checkbox') as LitCheckbox;
const mwcCheckbox = litCheckbox.renderRoot.querySelector('lit-mwc-checkbox-internal') as Checkbox;
const input = mwcCheckbox.renderRoot.querySelector("input[type='checkbox']") as HTMLInputElement;
input.click();
await facetCtrl.updateComplete;
expectedFacetCount = 0;
facetCtrl.reset();
await facetCtrl.updateComplete;
document.body.removeEventListener('facets-change', facetChangeHandler);
});
});
17 changes: 1 addition & 16 deletions lit_nlp/client/elements/data_matrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,21 +256,6 @@ export class DataMatrix extends LitElement {
// clang-format on
}

private renderDeleteButton() {
const deleteMatrix = () => {
const event = new CustomEvent('delete-matrix', {});
this.dispatchEvent(event);
};

// clang-format off
return html`
<mwc-icon class="icon-button" @click=${deleteMatrix}>
delete_outline
</mwc-icon>
`;
// clang-format on
}

override render() {
if (this.matrixCells.length === 0) {
return null;
Expand Down Expand Up @@ -310,7 +295,7 @@ export class DataMatrix extends LitElement {
<td class='axis-title' colspan=${colsLabelSpan}>
${this.colTitle}
</td>
<th class="delete-cell">${this.renderDeleteButton()}</th>
<th></th>
</tr>
<tr>
<td class='axis-title label-vertical' rowspan=${rowsLabelSpan}>
Expand Down
66 changes: 59 additions & 7 deletions lit_nlp/client/modules/confusion_matrix_module.css
Original file line number Diff line number Diff line change
@@ -1,29 +1,81 @@
.dropdown {
max-width: fit-content;
.module-results-area {
display: flex;
flex-direction: column;
}

.matrices-holder {
.non-faceted-matrix {
width: calc(100% - 16px);
padding: 8px;
display: flex;
flex-flow: wrap;
padding: 4px;
flex-direction: row;
flex-wrap: wrap;
align-content: start;
}

.faceted-matrices {
flex: 1 1 0;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-content: start;
padding: 8px;
}

.matrix {
margin-right: 16px;
margin-bottom: 16px;
}

.flex {
.matrix-update-checkboxes {
display: flex;
flex-direction: row;
}

.module-toolbar {
border-bottom: 1px solid var(--lit-neutral-300);
}

.matrix-options {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: start;
padding: 4px 0;
}

#create-button {
margin-top: 28px;
pointer-events: auto;
}

.dropdown-holder {
display: flex;
flex-direction: row;
align-items: center;
}

/* Make the "Rows" and "Columns" labels the same width */
.dropdown-label {
display: inline-block;
width: 55px;
margin: 0 8px;
}

.spacer {
width: 16px;
}

.confusion-matrix {
display: flex;
flex-direction: column;
align-items: center;
padding: 4px;
padding-bottom: 0;
margin-bottom: 4px;
max-width: max-content;
}

.matrix-label {
margin-bottom: 4px;
}

0 comments on commit 8993f9b

Please sign in to comment.