Skip to content

Commit

Permalink
Merge branch 'sgratzl/color' into sgratzl/sort
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/model/BooleansColumn.ts
#	src/model/CategoricalColumn.ts
#	src/model/CategoricalsColumn.ts
#	src/model/OrdinalColumn.ts
#	src/model/SetColumn.ts
#	src/model/index.ts
#	src/provider/utils.ts
  • Loading branch information
sgratzl committed Nov 23, 2018
2 parents 0015024 + 31bade4 commit 7e24a4f
Show file tree
Hide file tree
Showing 18 changed files with 604 additions and 44 deletions.
29 changes: 27 additions & 2 deletions src/model/BooleanColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ValueColumn, {IValueColumnDesc, dataLoaded} from './ValueColumn';
import {ICategoricalColumn, ICategory} from './ICategoricalColumn';
import {IDataRow} from './interfaces';
import {IEventListener} from '../internal/AEventDispatcher';
import {ICategoricalColorMappingFunction, restoreColorMapping, DEFAULT_COLOR_FUNCTION} from './CategoricalColorMappingFunction';

export interface IBooleanDesc {
/**
Expand All @@ -21,6 +22,12 @@ export interface IBooleanDesc {

export declare type IBooleanColumnDesc = IValueColumnDesc<boolean> & IBooleanDesc;

/**
* emitted when the color mapping property changes
* @asMemberOf BooleanColumn
* @event
*/
export declare function colorMappingChanged(previous: ICategoricalColorMappingFunction, current: ICategoricalColorMappingFunction): void;

/**
* emitted when the filter property changes
Expand All @@ -32,15 +39,18 @@ export declare function filterChanged(previous: boolean | null, current: boolean
/**
* a string column with optional alignment
*/
@toolbar('group', 'groupBy', 'filterBoolean')
@toolbar('group', 'groupBy', 'filterBoolean', 'colorMappedCategorical')
@Category('categorical')
export default class BooleanColumn extends ValueColumn<boolean> implements ICategoricalColumn {
static readonly EVENT_FILTER_CHANGED = 'filterChanged';
static readonly EVENT_COLOR_MAPPING_CHANGED = 'colorMappingChanged';

static readonly GROUP_TRUE = {name: 'True', color: 'black'};
static readonly GROUP_FALSE = {name: 'False', color: 'white'};

private currentFilter: boolean | null = null;

private colorMapping: ICategoricalColorMappingFunction;
readonly categories: ICategory[];

constructor(id: string, desc: Readonly<IBooleanColumnDesc>) {
Expand All @@ -60,13 +70,15 @@ export default class BooleanColumn extends ValueColumn<boolean> implements ICate
value: 1
}
];
this.colorMapping = DEFAULT_COLOR_FUNCTION;
}

protected createEventList() {
return super.createEventList().concat([BooleanColumn.EVENT_FILTER_CHANGED]);
return super.createEventList().concat([BooleanColumn.EVENT_COLOR_MAPPING_CHANGED, BooleanColumn.EVENT_FILTER_CHANGED]);
}

on(type: typeof BooleanColumn.EVENT_FILTER_CHANGED, listener: typeof filterChanged | null): this;
on(type: typeof BooleanColumn.EVENT_COLOR_MAPPING_CHANGED, listener: typeof colorMappingChanged | null): this;
on(type: typeof ValueColumn.EVENT_DATA_LOADED, listener: typeof dataLoaded | null): this;
on(type: typeof Column.EVENT_WIDTH_CHANGED, listener: typeof widthChanged | null): this;
on(type: typeof Column.EVENT_LABEL_CHANGED, listener: typeof labelChanged | null): this;
Expand Down Expand Up @@ -137,6 +149,7 @@ export default class BooleanColumn extends ValueColumn<boolean> implements ICate

dump(toDescRef: (desc: any) => any): any {
const r = super.dump(toDescRef);
r.colorMapping = this.colorMapping.dump();
if (this.currentFilter != null) {
r.filter = this.currentFilter;
}
Expand All @@ -145,11 +158,23 @@ export default class BooleanColumn extends ValueColumn<boolean> implements ICate

restore(dump: any, factory: (dump: any) => Column | null) {
super.restore(dump, factory);
this.colorMapping = restoreColorMapping(dump.colorMapping, this.categories);
if (typeof dump.filter !== 'undefined') {
this.currentFilter = dump.filter;
}
}

getColorMapping() {
return this.colorMapping.clone();
}

setColorMapping(mapping: ICategoricalColorMappingFunction) {
if (this.colorMapping.eq(mapping)) {
return;
}
this.fire([CategoricalColumn.EVENT_COLOR_MAPPING_CHANGED, Column.EVENT_DIRTY_VALUES, Column.EVENT_DIRTY_HEADER, Column.EVENT_DIRTY], this.colorMapping.clone(), this.colorMapping = mapping);
}

isFiltered() {
return this.currentFilter != null;
}
Expand Down
79 changes: 78 additions & 1 deletion src/model/BooleansColumn.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
import ArrayColumn, {IArrayColumnDesc} from './ArrayColumn';
import ArrayColumn, {IArrayColumnDesc, spliceChanged} from './ArrayColumn';
import {ISetColumn, toCategory} from './ICategoricalColumn';
import {IDataRow} from './interfaces';
import CategoricalColumn from './CategoricalColumn';
import {ICategoricalColorMappingFunction, DEFAULT_COLOR_FUNCTION, restoreColorMapping} from './CategoricalColorMappingFunction';
import ValueColumn, {dataLoaded} from './ValueColumn';
import Column, {labelChanged, metaDataChanged, dirty, dirtyHeader, dirtyValues, rendererTypeChanged, groupRendererChanged, summaryRendererChanged, visibilityChanged, widthChanged} from './Column';
import {IEventListener} from '../internal/AEventDispatcher';


export declare type IBooleansColumnDesc = IArrayColumnDesc<boolean>;

/**
* emitted when the color mapping property changes
* @asMemberOf BooleansColumn
* @event
*/
export declare function colorMappingChanged(previous: ICategoricalColorMappingFunction, current: ICategoricalColorMappingFunction): void;


export default class BooleansColumn extends ArrayColumn<boolean> implements ISetColumn {
static readonly EVENT_COLOR_MAPPING_CHANGED = CategoricalColumn.EVENT_COLOR_MAPPING_CHANGED;

private colorMapping: ICategoricalColorMappingFunction;

constructor(id: string, desc: Readonly<IBooleansColumnDesc>) {
super(id, desc);
this.setDefaultRenderer('upset');
this.colorMapping = DEFAULT_COLOR_FUNCTION;
}

get categories() {
Expand All @@ -24,6 +43,64 @@ export default class BooleansColumn extends ArrayColumn<boolean> implements ISet
if (v == null) {
return NaN;
}
getCategories(row: IDataRow) {
const categories = this.categories;
return super.getValues(row).map((v, i) => {
return v ? categories[i]! : null;
});
}

getColors(row: IDataRow) {
return this.getCategories(row).map((d) => d ? this.colorMapping.apply(d): Column.DEFAULT_COLOR);
}


protected createEventList() {
return super.createEventList().concat([BooleansColumn.EVENT_COLOR_MAPPING_CHANGED]);
}

on(type: typeof BooleansColumn.EVENT_COLOR_MAPPING_CHANGED, listener: typeof colorMappingChanged | null): this;
on(type: typeof ArrayColumn.EVENT_SPLICE_CHANGED, listener: typeof spliceChanged | null): this;
on(type: typeof ValueColumn.EVENT_DATA_LOADED, listener: typeof dataLoaded | null): this;
on(type: typeof Column.EVENT_WIDTH_CHANGED, listener: typeof widthChanged | null): this;
on(type: typeof Column.EVENT_LABEL_CHANGED, listener: typeof labelChanged | null): this;
on(type: typeof Column.EVENT_METADATA_CHANGED, listener: typeof metaDataChanged | null): this;
on(type: typeof Column.EVENT_DIRTY, listener: typeof dirty | null): this;
on(type: typeof Column.EVENT_DIRTY_HEADER, listener: typeof dirtyHeader | null): this;
on(type: typeof Column.EVENT_DIRTY_VALUES, listener: typeof dirtyValues | null): this;
on(type: typeof Column.EVENT_RENDERER_TYPE_CHANGED, listener: typeof rendererTypeChanged | null): this;
on(type: typeof Column.EVENT_GROUP_RENDERER_TYPE_CHANGED, listener: typeof groupRendererChanged | null): this;
on(type: typeof Column.EVENT_SUMMARY_RENDERER_TYPE_CHANGED, listener: typeof summaryRendererChanged | null): this;
on(type: typeof Column.EVENT_VISIBILITY_CHANGED, listener: typeof visibilityChanged | null): this;
on(type: string | string[], listener: IEventListener | null): this {
return super.on(<any>type, listener);
}

getColorMapping() {
return this.colorMapping.clone();
}

setColorMapping(mapping: ICategoricalColorMappingFunction) {
return CategoricalColumn.prototype.setColorMapping.call(this, mapping);
}

dump(toDescRef: (desc: any) => any): any {
const r = super.dump(toDescRef);
r.colorMapping = this.colorMapping.dump();
return r;
}

restore(dump: any, factory: (dump: any) => Column | null) {
super.restore(dump, factory);
this.colorMapping = restoreColorMapping(dump.colorMapping, this.categories);
}

compare(a: IDataRow, b: IDataRow) {
const aVal = this.getValue(a);
const bVal = this.getValue(b);
if (aVal == null) {
return bVal == null ? 0 : FIRST_IS_MISSING;
}
return v.reduce((a, b) => a + (b ? 1 : 0), 0);
}
}
75 changes: 75 additions & 0 deletions src/model/CategoricalColorMappingFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import {ICategory} from './ICategoricalColumn';
import Column from './Column';


export interface ICategoricalColorMappingFunction {
apply(v: ICategory): string;

dump(): any;

clone(): ICategoricalColorMappingFunction;

eq(other: ICategoricalColorMappingFunction): boolean;
}

/**
* @internal
*/
export const DEFAULT_COLOR_FUNCTION: ICategoricalColorMappingFunction = {
apply: (v) => v ? v.color : Column.DEFAULT_COLOR,
dump: () => null,
clone: () => DEFAULT_COLOR_FUNCTION,
eq: (other) => other === DEFAULT_COLOR_FUNCTION
};

export class ReplacmentColorMappingFunction implements ICategoricalColorMappingFunction {
public readonly map: ReadonlyMap<string, string>;
constructor(map: Map<ICategory | string, string>) {
this.map = new Map(Array.from(map.entries()).map(([k, v]) => <[string, string]>[typeof k === 'string' ? k : k.name, v]));
}

apply(v: ICategory) {
return this.map.has(v.name) ? this.map.get(v.name)! : DEFAULT_COLOR_FUNCTION.apply(v);
}

dump() {
const r: any = {};
this.map.forEach((v, k) => r[k] = v);
return r;
}

clone() {
return new ReplacmentColorMappingFunction(new Map(this.map.entries()));
}

eq(other: ICategoricalColorMappingFunction): boolean {
if (!(other instanceof ReplacmentColorMappingFunction)) {
return false;
}
if (other.map.size !== this.map.size) {
return false;
}
return Array.from(this.map.keys()).every((k) => this.map.get(k) === other.map.get(k));
}

static restore(dump: any, categories: ICategory[]) {
const lookup = new Map(categories.map((d) => <[string, ICategory]>[d.name, d]));
const r = new Map<ICategory, string>();
for (const key of Object.keys(dump)) {
if (lookup.has(key)) {
r.set(lookup.get(key)!, dump[key]);
}
}
return new ReplacmentColorMappingFunction(r);
}
}

/**
* @internal
*/
export function restoreColorMapping(dump: any, categories: ICategory[]): ICategoricalColorMappingFunction {
if (!dump) {
return DEFAULT_COLOR_FUNCTION;
}
return ReplacmentColorMappingFunction.restore(dump, categories);
}
46 changes: 38 additions & 8 deletions src/model/CategoricalColumn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import {
import {IDataRow, IGroup, IGroupData} from './interfaces';
import {missingGroup} from './missing';
import {IEventListener} from '../internal/AEventDispatcher';
import {ICategoricalColorMappingFunction, DEFAULT_COLOR_FUNCTION, restoreColorMapping} from './CategoricalColorMappingFunction';


/**
* emitted when the color mapping property changes
* @asMemberOf CategoricalColumn
* @event
*/
export declare function colorMappingChanged(previous: ICategoricalColorMappingFunction, current: ICategoricalColorMappingFunction): void;


/**
* emitted when the filter property changes
Expand All @@ -20,13 +30,16 @@ export declare function filterChanged(previous: ICategoricalFilter | null, curre
/**
* column for categorical values
*/
@toolbar('group', 'groupBy', 'sortGroupBy', 'filterCategorical')
@toolbar('group', 'groupBy', 'sortGroupBy', 'filterCategorical', 'colorMappedCategorical')
@Category('categorical')
export default class CategoricalColumn extends ValueColumn<string> implements ICategoricalColumn {
static readonly EVENT_FILTER_CHANGED = 'filterChanged';
static readonly EVENT_COLOR_MAPPING_CHANGED = 'colorMappingChanged';

readonly categories: ICategory[];

private colorMapping: ICategoricalColorMappingFunction;

private readonly lookup = new Map<string, Readonly<ICategory>>();
/**
* set of categories to show
Expand All @@ -39,13 +52,15 @@ export default class CategoricalColumn extends ValueColumn<string> implements IC
super(id, desc);
this.categories = toCategories(desc);
this.categories.forEach((d) => this.lookup.set(d.name, d));
this.colorMapping = DEFAULT_COLOR_FUNCTION;
}

protected createEventList() {
return super.createEventList().concat([CategoricalColumn.EVENT_FILTER_CHANGED]);
return super.createEventList().concat([CategoricalColumn.EVENT_FILTER_CHANGED, CategoricalColumn.EVENT_COLOR_MAPPING_CHANGED]);
}

on(type: typeof CategoricalColumn.EVENT_FILTER_CHANGED, listener: typeof filterChanged | null): this;
on(type: typeof CategoricalColumn.EVENT_COLOR_MAPPING_CHANGED, listener: typeof colorMappingChanged | null): this;
on(type: typeof ValueColumn.EVENT_DATA_LOADED, listener: typeof dataLoaded | null): this;
on(type: typeof Column.EVENT_WIDTH_CHANGED, listener: typeof widthChanged | null): this;
on(type: typeof Column.EVENT_LABEL_CHANGED, listener: typeof labelChanged | null): this;
Expand Down Expand Up @@ -88,11 +103,6 @@ export default class CategoricalColumn extends ValueColumn<string> implements IC
return v ? v.label : '';
}

getColor(row: IDataRow) {
const v = this.getCategory(row);
return v ? v.color : Column.DEFAULT_COLOR;
}

getValues(row: IDataRow) {
const v = this.getCategory(row);
return this.categories.map((d) => d === v);
Expand Down Expand Up @@ -124,12 +134,16 @@ export default class CategoricalColumn extends ValueColumn<string> implements IC
dump(toDescRef: (desc: any) => any): any {
const r = super.dump(toDescRef);
r.filter = this.currentFilter;
r.colorMapping = this.colorMapping.dump();
return r;
}

restore(dump: any, factory: (dump: any) => Column | null) {
super.restore(dump, factory);
if (!('filter' in dump)) {

this.colorMapping = restoreColorMapping(dump.colorMapping, this.categories);

if ('filter' in dump) {
this.currentFilter = null;
return;
}
Expand All @@ -141,6 +155,22 @@ export default class CategoricalColumn extends ValueColumn<string> implements IC
}
}

getColor(row: IDataRow) {
const v = this.getCategory(row);
return v ? this.colorMapping.apply(v) : Column.DEFAULT_COLOR;
}

getColorMapping() {
return this.colorMapping.clone();
}

setColorMapping(mapping: ICategoricalColorMappingFunction) {
if (this.colorMapping.eq(mapping)) {
return;
}
this.fire([CategoricalColumn.EVENT_COLOR_MAPPING_CHANGED, Column.EVENT_DIRTY_VALUES, Column.EVENT_DIRTY_HEADER, Column.EVENT_DIRTY], this.colorMapping.clone(), this.colorMapping = mapping);
}

isFiltered() {
return this.currentFilter != null;
}
Expand Down
Loading

0 comments on commit 7e24a4f

Please sign in to comment.