Skip to content

Commit

Permalink
implement date summary filter
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed Apr 14, 2020
1 parent f9d9471 commit 5298af3
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 9 deletions.
107 changes: 99 additions & 8 deletions src/renderer/DateHistogramCellRenderer.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {dateStatsBuilder, IDateStatistics} from '../internal';
import {Column, IDataRow, IDateColumn, IDatesColumn, IOrderedGroup, isDateColumn, isDatesColumn} from '../model';
import {cssClass} from '../styles';
import {ERenderMode, ICellRendererFactory, IRenderContext, ICellRenderer, IGroupCellRenderer, ISummaryRenderer} from './interfaces';
import {Column, IDataRow, IDateColumn, IDatesColumn, IOrderedGroup, isDateColumn, isDatesColumn, Ranking} from '../model';
import {cssClass, engineCssClass} from '../styles';
import {ERenderMode, ICellRendererFactory, IRenderContext, ICellRenderer, IGroupCellRenderer, ISummaryRenderer, IRenderTasks} from './interfaces';
import {renderMissingDOM} from './missing';
import {colorOf} from './utils';
import {histogramUpdate, histogramTemplate, mappingHintTemplate, mappingHintUpdate, IFilterInfo, IFilterContext, filteredHistTemplate, initFilter} from './histogram';
import InputDateDialog from '../ui/dialogs/InputDateDialog';
import {shiftFilterDateDay} from '../model/internalDate';
import {shiftFilterDateDay, noDateFilter} from '../model/internalDate';
import DialogManager from '../ui/dialogs/DialogManager';

/** @internal */
export default class DateHistogramCellRenderer implements ICellRendererFactory {
Expand Down Expand Up @@ -102,7 +103,8 @@ function interactiveSummary(col: IDateColumn, context: IRenderContext, template:
}
const {summary, data} = r;
if (!updateFilter) {
fContext = createFilterContext(col, context, [data.min ? data.min.getTime() : Date.now(), data.max ? data.max.getTime() : Date.now()]);
const domain: [number, number] = [data.min ? data.min.getTime() : Date.now(), data.max ? data.max.getTime() : Date.now()];
fContext = createFilterContext(col, context, domain);
updateFilter = initFilter(node, fContext);
}

Expand All @@ -119,6 +121,96 @@ function interactiveSummary(col: IDateColumn, context: IRenderContext, template:
};
}


/** @internal */
export function createDateFilter(col: IDateColumn, parent: HTMLElement, context: {idPrefix: string, dialogManager: DialogManager, tasks: IRenderTasks}, livePreviews: boolean) {
const renderer = getHistDOMRenderer(col);
const filter = col.getFilter();

let domain: [number, number] = [isFinite(filter.min) ? filter.min : 0, isFinite(filter.max) ? filter.max : 100];

let fContext = createFilterContext(col, context, domain);
const applyFilter = fContext.setFilter;
let currentFilter = createFilterInfo(col, domain);
fContext.setFilter = (filterMissing, min, max) => {
currentFilter = {filterMissing, filterMin: min, filterMax: max};
if (livePreviews) {
applyFilter(filterMissing, min, max);
}
};
parent.innerHTML = `${renderer.template}${filteredHistTemplate(fContext, createFilterInfo(col, domain))}</div>`;
const summaryNode = <HTMLElement>parent.firstElementChild!;
summaryNode.classList.add(cssClass('summary'), cssClass('renderer'));
summaryNode.dataset.renderer = 'histogram';
summaryNode.dataset.interactive = '';
summaryNode.classList.add(cssClass('histogram-i'));


let updateFilter: null | ((missing: number, filter: IFilterInfo<number>) => void) = null;
const prepareRender = (min: Date | null, max: Date | null) => {
// reinit with proper domain
domain = [min ? min.getTime() : Date.now(), max ? max.getTime() : Date.now()];
fContext = createFilterContext(col, context, domain);
const applyFilter = fContext.setFilter;
currentFilter = createFilterInfo(col, domain);
fContext.setFilter = (filterMissing, min, max) => {
currentFilter = {filterMissing, filterMin: min, filterMax: max};
if (livePreviews) {
applyFilter(filterMissing, min, max);
}
};
return initFilter(summaryNode, fContext);
};

const rerender = () => {
const ready = context.tasks.summaryDateStats(col).then((r) => {
if (typeof r === 'symbol') {
return;
}
const {summary, data} = r;
if (!updateFilter) {
updateFilter = prepareRender(data.min, data.max);
}
updateFilter(data ? data.missing : (summary ? summary.missing : 0), currentFilter);
summaryNode.classList.toggle(cssClass('missing'), !summary);
if (!summary) {
return;
}
renderer.render(summaryNode, summary, data);
});
if (!ready) {
return;
}
summaryNode.classList.add(engineCssClass('loading'));
ready.then(() => {
summaryNode.classList.remove(engineCssClass('loading'));
});
};

const ranking = col.findMyRanker()!;

if (ranking) {
ranking.on(`${Ranking.EVENT_ORDER_CHANGED}.numberFilter`, () => rerender());
}
rerender();

return {
cleanUp() {
if (ranking) {
ranking.on(`${Ranking.EVENT_ORDER_CHANGED}.numberFilter`, null);
}
},
reset() {
currentFilter = createFilterInfo(col, domain, noDateFilter());
rerender();
},
submit() {
applyFilter(currentFilter.filterMissing, currentFilter.filterMin, currentFilter.filterMax);
}
};
}


function getHistDOMRenderer(col: IDateColumn) {
const guessedBins = 10;

Expand All @@ -135,8 +227,7 @@ function getHistDOMRenderer(col: IDateColumn) {
};
}

function createFilterInfo(col: IDateColumn, domain: [number, number]): IFilterInfo<number> {
const filter = col.getFilter();
function createFilterInfo(col: IDateColumn, domain: [number, number], filter = col.getFilter()): IFilterInfo<number> {
const filterMin = isFinite(filter.min) ? filter.min : domain[0];
const filterMax = isFinite(filter.max) ? filter.max : domain[1];
return {
Expand All @@ -146,7 +237,7 @@ function createFilterInfo(col: IDateColumn, domain: [number, number]): IFilterIn
};
}

function createFilterContext(col: IDateColumn, context: IRenderContext, domain: [number, number]): IFilterContext<number> {
function createFilterContext(col: IDateColumn, context: {idPrefix: string, dialogManager: DialogManager}, domain: [number, number]): IFilterContext<number> {
const percent = (v: number) => Math.round(100 * (v - domain[0]) / (domain[1] - domain[0]));
const unpercent = (v: number) => ((v / 100) * (domain[1] - domain[0]) + domain[0]);
return {
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/histogram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export function initFilter<T>(node: HTMLElement, context: IFilterContext<T>) {
maxHint.style.width = `${100 - context.percent(newValue)}%`;
max.dataset.value = context.format(newValue);
max.style.right = `${100 - context.percent(newValue)}%`;
min.classList.toggle(cssClass('swap-hint'), context.percent(newValue) < 85);
max.classList.toggle(cssClass('swap-hint'), context.percent(newValue) < 85);
setFilter();
});
};
Expand Down Expand Up @@ -209,6 +209,8 @@ export function initFilter<T>(node: HTMLElement, context: IFilterContext<T>) {
max.dataset.value = context.format(f.filterMax);
min.style.left = `${context.percent(f.filterMin)}%`;
max.style.right = `${100 - context.percent(f.filterMax)}%`;
min.classList.toggle(cssClass('swap-hint'), context.percent(f.filterMin) > 15);
max.classList.toggle(cssClass('swap-hint'), context.percent(f.filterMax) < 85);
filterMissing.checked = f.filterMissing;
updateFilterMissingNumberMarkup(<HTMLElement>filterMissing.parentElement, missing);
};
Expand Down
1 change: 1 addition & 0 deletions src/ui/dialogs/DateFilterDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {noDateFilter} from '../../model/internalDate';
import {createDateFilter} from '../../renderer/DateHistogramCellRenderer';
import {cssClass} from '../../styles';
import ADialog, {IDialogContext} from './ADialog';
import {IRankingHeaderContext} from '../interfaces';

/** @internal */
export default class DateFilterDialog extends ADialog {
Expand Down

0 comments on commit 5298af3

Please sign in to comment.