Skip to content

Commit

Permalink
Fixes for generated datapoints table:
Browse files Browse the repository at this point in the history
- Structured input rendering to match data table, (moving the formatForDisplay() method to types.ts so it can be used in both places)

- "table widths are weird" bug from the data table (currently, the generated datapoints table does not use table.ts, so this moves that logic into types.ts as well so it can be used in both places.)

- Combining "counterfactuals" and "counterfactual explanations" tabs (+ tweaks to both)

PiperOrigin-RevId: 342308747
  • Loading branch information
EmilyReif authored and LIT team committed Nov 13, 2020
1 parent 9ffd199 commit 8cd83e9
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 106 deletions.
6 changes: 2 additions & 4 deletions lit_nlp/client/default/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,7 @@ export const LAYOUTS: LitComponentLayouts = {
SalienceMapModule,
AttentionModule,
],
'Counterfactuals': [GeneratorModule],
'Counterfactual Explanation': [CounterfactualExplainerModule],
'Counterfactuals': [GeneratorModule, CounterfactualExplainerModule],
},
description: "A default LIT layout, which includes the data table and data point editor, the performance and metrics, predictions, explanations, and counterfactuals. Does not include the embedding projector."
},
Expand All @@ -190,8 +189,7 @@ export const LAYOUTS: LitComponentLayouts = {
SalienceMapModule,
AttentionModule,
],
'Counterfactuals': [GeneratorModule],
'Counterfactual Explanation': [CounterfactualExplainerModule],
'Counterfactuals': [GeneratorModule, CounterfactualExplainerModule],
},
description: "The default LIT layout, which includes the data table and data point editor, the performance and metrics, predictions, explanations, and counterfactuals."
},
Expand Down
24 changes: 2 additions & 22 deletions lit_nlp/client/elements/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {classMap} from 'lit-html/directives/class-map';
import {styleMap} from 'lit-html/directives/style-map';
import {computed, observable} from 'mobx';
import {ReactiveElement} from '../lib/elements';
import {range} from '../lib/utils';
import {chunkWords} from '../lib/utils';

import {styles} from './table.css';

Expand Down Expand Up @@ -508,23 +508,6 @@ export class DataTable extends ReactiveElement {
// clang-format on
}

/**
* Chunks a long word into shorter pieces so that the table won't stretch
* to fit the entire word length, as it normally would
* (https://www.w3schools.com/cssref/pr_tab_table-layout.asp).
* TODO(lit-dev): find a more long-term solution to this, since adding a
* NPWS will make copy/pasting from the table behave strangely.
*/
private chunkWord(word: string) {
const maxLen = 15;
const chunks: string[] = [];
for (let i=0; i<word.length; i+=maxLen) {
chunks.push(word.slice(i, i+maxLen));
}
// This is not an empty string, it is a non-printing space.
const zeroWidthSpace = '​';
return chunks.join(zeroWidthSpace);
}

renderRow(data: TableData, rowIndex: number) {
const dataIndex = this.rowIndexToDataIndex.get(rowIndex);
Expand All @@ -543,12 +526,9 @@ export class DataTable extends ReactiveElement {
this.handleRowClick(e, rowIndex);
};

const formatData = (d: string) =>
d.split(' ').map(word => this.chunkWord(word)).join(' ');

return html`
<tr class="${rowClass}" @mousedown=${mouseDown}>
${data.map(d => html`<td><div>${formatData(d.toString())}</div></td>`)}
${data.map(d => html`<td><div>${chunkWords(d.toString())}</div></td>`)}
</tr>
`;
}
Expand Down
48 changes: 47 additions & 1 deletion lit_nlp/client/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,4 +298,50 @@ export declare interface LayoutSettings {
/** Display name for the "no dataset" dataset in settings. */
export const NONE_DS_DISPLAY_NAME = 'none';
/** Key for thhe "no dataset" dataset in settings. */
export const NONE_DS_DICT_KEY = '_union_empty';
export const NONE_DS_DICT_KEY = '_union_empty';

/**
* Formats the following types for display in the data table:
* string, number, boolean, string[], number[], (string|number)[]
* TODO(lit-dev): allow passing custom HTML to table, not just strings.
*/
// tslint:disable-next-line:no-any
export function formatForDisplay(input: any, fieldSpec?: LitType): string {
if (input == null) return '';

// Handle SpanLabels, if field spec given.
// TODO(lit-dev): handle more fields this way.
if (fieldSpec != null && isLitSubtype(fieldSpec, 'SpanLabels')) {
const formattedTags = (input as SpanLabel[]).map(formatSpanLabel);
return formattedTags.join(', ');
}
// Handle EdgeLabels, if field spec given.
if (fieldSpec != null && isLitSubtype(fieldSpec, 'EdgeLabels')) {
const formattedTags = (input as EdgeLabel[]).map(formatEdgeLabel);
return formattedTags.join(', ');
}
const formatNumber = (item: number) =>
Number.isInteger(item) ? item.toString() : item.toFixed(4).toString();

// Generic data, based on type of input.
if (Array.isArray(input)) {
const strings = input.map((item) => {
if (typeof item === 'number') {
return formatNumber(item);
}
return `${item}`;
});
return `${strings.join(', ')}`;
}

if (typeof input === 'boolean') {
return input ? '✔' : ' ';
}

if (typeof input === 'number') {
return formatNumber(input);
}

// Fallback: just coerce to string.
return `${input}`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,41 +17,40 @@

import 'jasmine';

import {DataTableModule} from './data_table_module';
import {formatForDisplay} from './types';

describe('formatForDisplay test', () => {
const dataTableModule = new DataTableModule();

it('formats string for display', () => {
const testString = 'test';
const formatted = dataTableModule.formatForDisplay(testString);
const formatted = formatForDisplay(testString);
expect(formatted).toBe(testString);
});

it('formats number for display', () => {
const formatted = dataTableModule.formatForDisplay(1.23456789);
const formatted = formatForDisplay(1.23456789);
expect(formatted).toBe('1.2346');
});

it('formats boolean for display', () => {
let formatted = dataTableModule.formatForDisplay(true);
let formatted = formatForDisplay(true);
expect(formatted).toBe('✔');

formatted = dataTableModule.formatForDisplay(false);
formatted = formatForDisplay(false);
expect(formatted).toBe(' ');
});

it('formats array for display', () => {
// number array.
let formatted = dataTableModule.formatForDisplay([1.23456789, 2.3456789]);
let formatted = formatForDisplay([1.23456789, 2.3456789]);
expect(formatted).toBe('1.2346, 2.3457');

// number|string array.
formatted = dataTableModule.formatForDisplay(['a', 1.23456789, 2.3456789]);
formatted = formatForDisplay(['a', 1.23456789, 2.3456789]);
expect(formatted).toBe('a, 1.2346, 2.3457');

// string array
formatted = dataTableModule.formatForDisplay(['a', 'b', 'cd']);
formatted = formatForDisplay(['a', 'b', 'cd']);
expect(formatted).toBe('a, b, cd');
});
});
22 changes: 22 additions & 0 deletions lit_nlp/client/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,25 @@ export function copyToClipboard(value: string) {
document.execCommand("copy");
document.body.removeChild(tempInput);
}

/**
* Processes a sentence so that no word exceeds a certain length by
* chunking a long word into shorter pieces. This is useful when rendering
* a table-- normally a table will stretch to fit the entire word length
* (https://www.w3schools.com/cssref/pr_tab_table-layout.asp).
* TODO(lit-dev): find a more long-term solution to this, since adding a
* NPWS will make copy/pasting from the table behave strangely.
*/
export function chunkWords(sent: string) {
const chunkWord = (word: string) => {
const maxLen = 15;
const chunks: string[] = [];
for (let i=0; i<word.length; i+=maxLen) {
chunks.push(word.slice(i, i+maxLen));
}
// This is not an empty string, it is a non-printing space.
const zeroWidthSpace = '​';
return chunks.join(zeroWidthSpace);
};
return sent.split(' ').map(word => chunkWord(word)).join(' ');
}
34 changes: 18 additions & 16 deletions lit_nlp/client/modules/counterfactual_explainer_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export class SignedSalienceCmap extends SalienceCmap {
@customElement('counterfactual-explainer-module')
export class CounterfactualExplainerModule extends LitModule {
static title = 'Counterfactual Explanation';
static numCols = 6;
static numCols = 10;
static collapseByDefault = true;
static duplicateForExampleComparison = true;
static template = (model = '', selectionServiceIndex = 0) => {
return html`<counterfactual-explainer-module model=${
Expand Down Expand Up @@ -344,23 +345,24 @@ export class CounterfactualExplainerModule extends LitModule {

// clang-format off
return html`
<label class="dropdown-label">Class to explain:</label>
<select class="dropdown options-dropdown" @change=${classChanged}>
${classOptions}
</select>
<label class="dropdown-label">Model head:</label>
<select class="dropdown options-dropdown" @change=${predKeyChanged}>
${predKeyOptions}
</select>
<div class='config'>
<label class="dropdown-label">Class to explain:</label>
<select class="dropdown options-dropdown" @change=${classChanged}>
${classOptions}
</select>
<label class="dropdown-label">Model head:</label>
<select class="dropdown options-dropdown" @change=${predKeyChanged}>
${predKeyOptions}
</select>
<div class='group-label'>
<lit-checkbox label="autorun"
?checked=${this.state.autorun}
@change=${() => { this.state.autorun = !this.state.autorun;}}
></lit-checkbox>
</div>
</div>
<table>
<tr>
<th class='group-label'>
${COUNTERFACTUAL_INTERPRETER_NAME}
<lit-checkbox label="autorun"
?checked=${this.state.autorun}
@change=${() => { this.state.autorun = !this.state.autorun;}}
></lit-checkbox>
</th>
<td class=${classMap({'group-container': true,
'loading': this.state.isLoading})}>
${Object.keys(salience).map(gradKey =>
Expand Down
54 changes: 4 additions & 50 deletions lit_nlp/client/modules/data_table_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import {computed, observable} from 'mobx';
import {app} from '../core/lit_app';
import {LitModule} from '../core/lit_module';
import {TableData} from '../elements/table';
import {EdgeLabel, formatEdgeLabel, formatSpanLabel, IndexedInput, LitType, ModelsMap, SpanLabel, Spec} from '../lib/types';
import {compareArrays, findSpecKeys, isLitSubtype, shortenId} from '../lib/utils';
import {IndexedInput, ModelsMap, formatForDisplay, Spec} from '../lib/types';
import {compareArrays, findSpecKeys, shortenId} from '../lib/utils';
import {ClassificationInfo} from '../services/classification_service';
import {RegressionInfo} from '../services/regression_service';
import {ClassificationService, RegressionService, SelectionService} from '../services/services';
Expand Down Expand Up @@ -173,7 +173,7 @@ export class DataTableModule extends LitModule {
return [
index, displayId,
...this.keys.map(
(key) => this.formatForDisplay(d.data[key], this.dataSpec[key])),
(key) => formatForDisplay(d.data[key], this.dataSpec[key])),
...predictionInfoEntries
];
});
Expand Down Expand Up @@ -297,7 +297,7 @@ export class DataTableModule extends LitModule {
// tslint:disable-next-line:no-any
entries.forEach((entry: any, i: number) => {
const displayInfoName = displayNames[i];
const displayInfoValue = this.formatForDisplay(entry[1]);
const displayInfoValue = formatForDisplay(entry[1]);
if (displayInfoName == null) return;
keysToTableEntry.set(
this.getTableKey(
Expand All @@ -308,52 +308,6 @@ export class DataTableModule extends LitModule {
});
}

/**
* Formats the following types for display in the data table:
* string, number, boolean, string[], number[], (string|number)[]
* TODO(lit-dev): allow passing custom HTML to table, not just strings.
*/
// tslint:disable-next-line:no-any
formatForDisplay(input: any, fieldSpec?: LitType): string {
if (input == null) return '';

// Handle SpanLabels, if field spec given.
// TODO(lit-dev): handle more fields this way.
if (fieldSpec != null && isLitSubtype(fieldSpec, 'SpanLabels')) {
const formattedTags = (input as SpanLabel[]).map(formatSpanLabel);
return formattedTags.join(', ');
}
// Handle EdgeLabels, if field spec given.
if (fieldSpec != null && isLitSubtype(fieldSpec, 'EdgeLabels')) {
const formattedTags = (input as EdgeLabel[]).map(formatEdgeLabel);
return formattedTags.join(', ');
}
const formatNumber = (item: number) =>
Number.isInteger(item) ? item.toString() : item.toFixed(4).toString();

// Generic data, based on type of input.
if (Array.isArray(input)) {
const strings = input.map((item) => {
if (typeof item === 'number') {
return formatNumber(item);
}
return `${item}`;
});
return `${strings.join(', ')}`;
}

if (typeof input === 'boolean') {
return input ? '✔' : ' ';
}

if (typeof input === 'number') {
return formatNumber(input);
}

// Fallback: just coerce to string.
return `${input}`;
}

/**
* Returns the formatted column name for the keyToTableEntry Map.
*/
Expand Down
13 changes: 9 additions & 4 deletions lit_nlp/client/modules/generator_module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {computed, observable} from 'mobx';

import {app} from '../core/lit_app';
import {LitModule} from '../core/lit_module';
import {CallConfig, IndexedInput, Input, ModelsMap, Spec} from '../lib/types';
import {CallConfig, IndexedInput, Input, ModelsMap, Spec, formatForDisplay, LitName} from '../lib/types';
import {handleEnterKey, isLitSubtype} from '../lib/utils';
import {GroupService} from '../services/group_service';
import {SelectionService} from '../services/services';
Expand All @@ -37,7 +37,7 @@ import {styles as sharedStyles} from './shared_styles.css';
@customElement('generator-module')
export class GeneratorModule extends LitModule {
static title = 'Datapoint Generator';
static numCols = 12;
static numCols = 10;

static template = () => {
return html`<generator-module></generator-module>`;
Expand Down Expand Up @@ -361,11 +361,16 @@ export class GeneratorModule extends LitModule {
};

// For non-categorical outputs, render an editable textfield.
// TODO(lit-dev): Consolidate this logic with the datapoint editor,
// ideally as part of b/172597999.
const renderFreeformInput = () => {
const fieldSpec = this.appState.currentDatasetSpec[key];
const nonEditableSpecs: LitName[] = ['EdgeLabels', 'SpanLabels'];
editable = editable && !isLitSubtype(fieldSpec, nonEditableSpecs);
const formattedVal = formatForDisplay(value, fieldSpec);
return editable ? html`
<input type="text" class="input-box" @input=${handleInputChange}
.value="${value}" />` :
html`<div>${value}</div>`;
.value="${formattedVal}" />` : html`<div>${formattedVal}</div>`;
};

const onKeyUp = (e: KeyboardEvent) => {
Expand Down
5 changes: 5 additions & 0 deletions lit_nlp/client/modules/salience_map_module.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@
align-items: center;
justify-content: center;
}

.config {
display: flex;
align-items: center;
}

0 comments on commit 8cd83e9

Please sign in to comment.