Skip to content

Commit

Permalink
recommendSplit for scatterplot frontend done
Browse files Browse the repository at this point in the history
  • Loading branch information
richardwolfmayr committed Sep 17, 2023
1 parent c5c1057 commit 89a6b4d
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 63 deletions.
142 changes: 88 additions & 54 deletions src/Taskview/visualizations/AVegaVisualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { DATA_LABEL } from './constants';
import { IVisualization } from './IVisualization';
import { IAttribute, IdValuePair } from '../../data/IAttribute';
import {
ICohortDBDataParams,
ICohortDBDataParams, ICohortDBDataRecommendSplitParams,
ICohortDBWithNumFilterParams, ICohortMultiAttrDBDataParams,
IEqualsList,
INumRange,
Expand Down Expand Up @@ -257,6 +257,7 @@ export abstract class AVegaVisualization implements IVegaVisualization {
abstract filter(): void;
abstract split(): void;
abstract createAutomatically?(): void;
abstract recommendSplit?(useBinCount: boolean = false): void;
abstract showImpl(chart: HTMLDivElement, data: Array<IdValuePair>); // probably the method impl from SingleAttributeVisualization can be moved here

destroy() {
Expand Down Expand Up @@ -460,19 +461,29 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {
}


/** Calls the recommendSplit webservice and sets the bins accoding to the returned results */
async recommendSplit() {
/** Calls the recommendSplit webservice and sets the bins according to the returned results */
async recommendSplit(useBinCount: boolean = false) {
console.log("recommendSplit");

const bins = this.getSelectedData();
let binsCount = 0;
if (useBinCount) {
// select the bins field
binsCount = (this.controls.querySelector('#split input.bins') as HTMLInputElement).valueAsNumber;
}

let cohorts = this.cohorts;

let filterDesc: IFilterDesc[] = [];
if (bins.length === 1) {
if (cohorts.length === 1) {
// it does not make sense to do a recommendSplit on multiple cohorts at once
// createAutomatically looks at the data of one cohort and creates new cohorts based on that
// recommendSplit recommends splits that are used for ALL cohorts, so it would not make sense to use it on multiple cohorts

// 1 cohort, 1 category
let filter: INumRange[] | IEqualsList = [];

filterDesc.push({
cohort: bins[0].cohort,
cohort: cohorts[0],
filter: [
{
attr: this.attribute,
Expand All @@ -481,9 +492,10 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {
],
});

const params: ICohortDBDataParams = {
const params: ICohortDBDataRecommendSplitParams = {
cohortId: filterDesc[0].cohort.dbId,
attribute: this.attribute.dataKey
attribute0: this.attribute.dataKey,
binsCount0: binsCount,
};
const data = await recommendSplit(params);
console.log("recommendSplit", data);
Expand All @@ -496,45 +508,6 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {
}
this.vegaView.data(AVegaVisualization.SPLITVALUE_DATA_STORE, cloneDeep(this.splitValues)); // set a defensive copy
}


// this.vegaView.removeDataListener(AVegaVisualization.SPLITVALUE_DATA_STORE, this.vegaSplitListener); // remove listener temporarily
//
// const binCount = parseFloat(event.value);
// const splitValCount = binCount - 1;
// const [min, max] = this.vegaView.scale('x').domain();
// const extent = max - min;
// const binWidth = extent / binCount;
//
// const binType = this.controls.querySelector('#split select.binType') as HTMLSelectElement;
//
// if (binType.selectedIndex === 0) {
// // equal width
// this.splitValues = [];
// for (let i = 1; i <= splitValCount; i++) {
// const binBorder = min + binWidth * i;
// this.splitValues.push({ x: binBorder });
// }
// } else if (binType.selectedIndex === 1) {
// // custom width
// if (this.splitValues.length > splitValCount) {
// this.splitValues = this.splitValues.sort((a, b) => a.x - b.x); // sort em
// this.splitValues.length = splitValCount; // drop largest split values
// } else {
// while (this.splitValues.length < splitValCount) {
// this.splitValues.push({ x: max }); // add maximum until there are enough rulers
// }
// }
// } else {
// this.splitValues = new Array(5).fill({ x: 0 }).map((x) => ({ x }));
// }
//
// this.vegaView.data(AVegaVisualization.SPLITVALUE_DATA_STORE, cloneDeep(this.splitValues)); // set a defensive copy
// this.vegaView.runAsync().then(
// (
// vegaView, // defer adding signallistener until the new data is set internally
// ) => vegaView.addDataListener(AVegaVisualization.SPLITVALUE_DATA_STORE, this.vegaSplitListener), // add listener again
// );
}


Expand Down Expand Up @@ -728,9 +701,10 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {
addIntervalControls() {
const [min, max] = this.vegaView.scale('x').domain();

this.controls.insertAdjacentHTML(
'afterbegin',
`
if(this.cohorts.length == 1){ // only show recommendButton when there is only one cohort
this.controls.insertAdjacentHTML(
'afterbegin',
`
<div>
<!-- Nav tabs -->
<ul class="nav nav-tabs nav-justified" role="tablist">
Expand All @@ -755,7 +729,9 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {
</div>
<div role="tabpanel" class="tab-pane" id="split">
<div class="flex-wrapper" data-attr="${this.attribute.dataKey}">
<button type="button" class="btn recommendSplitBtn btn-coral-prime" title="Calculate meaningful splits.">Recommend split</button>
<button type="button" class="btn recommendSplitBtn btn-coral-prime" title="Calculate meaningful splits by choosing a useful bin number automatically.">Recommend split: Automatic bin number</button>
<button type="button" class="btn recommendSplitWithBinCountBtn btn-coral-prime" title="Calculate meaningful splits according to the number of bins selected.">Recommend split: Use selected bin count</button>
<button type="button" class="btn createAutomaticallyBtn btn-coral-prime" title="Calculate meaningful splits.">Create cohorts automatically</button>
<label>Split into</label>
<input type="number" class="bins" step="any" min="1" max="99" value="2"/>
<label >bins of</label>
Expand All @@ -773,10 +749,61 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {
</div>
<div class="d-grid gap-2">
<button type="button" class="btn applyBtn btn-coral-prime" title="Apply to get a preview of the output cohorts.">Apply</button>
<button type="button" class="btn createAutomaticallyBtn btn-coral-prime" title="Calculate meaningful splits.">Create cohorts automatically</button>
</div>
`,
);
);
} else {
this.controls.insertAdjacentHTML(
'afterbegin',
`
<div>
<!-- Nav tabs -->
<ul class="nav nav-tabs nav-justified" role="tablist">
<li role="presentation" class="nav-item"><a class="nav-link active" href="#filter" aria-controls="filter" role="tab" data-bs-toggle="tab"><i class="fas fa-filter" aria-hidden="true"></i> Filter</a></li>
<li role="presentation" class="nav-item"><a class="nav-link" href="#split" aria-controls="split" role="tab" data-bs-toggle="tab"><i class="fas fa-share-alt" aria-hidden="true"></i> Split</a></li>
<!-- <li role="presentation" class="nav-item"><a class="nav-link" href="#autosplit" aria-controls="autosplit" role="tab" data-bs-toggle="tab"><i class="fas fa-share-alt" aria-hidden="true"></i> Autosplit</a></li> -->
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="filter">
<p>Click and drag in the visualization or set the range below AVegaViz:</p>
<div class="flex-wrapper" data-attr="${this.attribute.dataKey}">
<label>Filter from</label>
<input type="number" class="interval minimum" step="any" min="${min}" max="${max}" data-axis="x"/>
<label>to</label>
<input type="number" class="interval maximum" step="any" min="${min}" max="${max}" data-axis="x"/>
<div class="null-value-container form-check">
<input type="checkbox" class="null-value-checkbox form-check-input" id="null_value_checkbox_1">
<label class="form-check-label" for="null_value_checkbox_1">Include <span class="hint">missing values</span></label>
</div>
</div>
</div>
<div role="tabpanel" class="tab-pane" id="split">
<div class="flex-wrapper" data-attr="${this.attribute.dataKey}">
<button type="button" class="btn createAutomaticallyBtn btn-coral-prime" title="Calculate meaningful splits.">Create cohorts automatically</button>
<label>Split into</label>
<input type="number" class="bins" step="any" min="1" max="99" value="2"/>
<label >bins of</label>
<select class="binType">
<option>equal width</option>
<option>custom width</option>
</select>
<div class="null-value-container form-check">
<input type="checkbox" class="null-value-checkbox form-check-input" id="null_value_checkbox_2">
<label class="form-check-label" for="null_value_checkbox_2">Add a <span class="hint">missing values</span> bin</label>
</div>
</div>
</div>
</div>
</div>
<div class="d-grid gap-2">
<button type="button" class="btn applyBtn btn-coral-prime" title="Apply to get a preview of the output cohorts.">Apply</button>
</div>
`,
);
}

let nullValueTooltip = ``;
for (const [cht, nullValues] of this.nullValueMap.get(this.attribute.dataKey)) {
nullValueTooltip += `<i class="fas fa-square" aria-hidden="true" style="color: ${cht.colorTaskView} "></i>&nbsp;${nullValues}<br>`;
Expand Down Expand Up @@ -804,7 +831,14 @@ export abstract class SingleAttributeVisualization extends AVegaVisualization {
.select('button.recommendSplitBtn')
.on('click', () => {
console.log("recommendSplitBtn clicked");
this.recommendSplit();
this.recommendSplit(false);
});

select(this.controls)
.select('button.recommendSplitWithBinCountBtn')
.on('click', () => {
console.log("recommendSplitWithBinCountBtn clicked");
this.recommendSplit(true);
});

select(this.controls)
Expand Down
56 changes: 51 additions & 5 deletions src/Taskview/visualizations/MultiAttributeVisualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,10 @@ export abstract class MultiAttributeVisualization extends AVegaVisualization {

// may be overwritten (e.g. for tsne plot where the attribtues are different)
protected addControls() {
this.controls.insertAdjacentHTML(
'afterbegin',
`
if(this.cohorts.length == 1) { // only show recommendButton when there is only one cohort
this.controls.insertAdjacentHTML(
'afterbegin',
`
<div>
<!-- Nav tabs -->
<ul class="nav nav-tabs nav-justified" role="tablist">
Expand All @@ -132,15 +133,46 @@ export abstract class MultiAttributeVisualization extends AVegaVisualization {
</div>
<div role="tabpanel" class="tab-pane" id="split">
<!-- INSERT SPLIT CONTROLS HERE -->
<button type="button" class="btn recommendSplitBtn btn-coral-prime" title="Calculate meaningful splits by choosing a useful bin number automatically.">Recommend split: Automatic bin number</button>
<button type="button" class="btn recommendSplitWithBinCountBtn btn-coral-prime" title="Calculate meaningful splits according to the number of bins selected.">Recommend split: Use selected bin count</button>
<button type="button" class="btn createAutomaticallyBtn btn-coral-prime" title="Calculate meaningful splits.">Create cohorts automatically</button>
</div>
</div>
</div>
<div class="d-grid gap-2">
<button type="button" class="btn btn-coral-prime btn-block applyBtn">Apply</button>
<button type="button" class="btn createAutomaticallyBtn btn-coral-prime" title="Calculate meaningful splits.">Create cohorts automatically</button>
</div>
`,
);
);
} else {
this.controls.insertAdjacentHTML(
'afterbegin',
`
<div>
<!-- Nav tabs -->
<ul class="nav nav-tabs nav-justified" role="tablist">
<li role="presentation" class="nav-item"><a class="nav-link active" href="#filter" aria-controls="filter" role="tab" data-bs-toggle="tab"><i class="fas fa-filter" aria-hidden="true"></i> Filter</a></li>
<li role="presentation" class="nav-item"><a class="nav-link" href="#split" aria-controls="split" role="tab" data-bs-toggle="tab"><i class="fas fa-share-alt" aria-hidden="true"></i> Split</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="filter">
<p>Click and drag in the visualization or set the range below:</p>
<!-- INSERT FILTER CONTROLS HERE -->
</div>
<div role="tabpanel" class="tab-pane" id="split">
<!-- INSERT SPLIT CONTROLS HERE -->
<button type="button" class="btn createAutomaticallyBtn btn-coral-prime" title="Calculate meaningful splits.">Create cohorts automatically</button>
</div>
</div>
</div>
<div class="d-grid gap-2">
<button type="button" class="btn btn-coral-prime btn-block applyBtn">Apply</button>
</div>
`,
);
}

// for each attribute type, add the respective controls:
for (const [i, attr] of this.attributes.entries()) {
if (attr.type === 'number') {
Expand All @@ -156,6 +188,20 @@ export abstract class MultiAttributeVisualization extends AVegaVisualization {
this.createAutomatically();
});

select(this.controls)
.select('button.recommendSplitBtn')
.on('click', () => {
console.log("recommendSplitBtn clicked");
this.recommendSplit(false);
});

select(this.controls)
.select('button.recommendSplitWithBinCountBtn')
.on('click', () => {
console.log("recommendSplitWithBinCountBtn clicked");
this.recommendSplit(true);
});


select(this.controls)
.select('button.applyBtn')
Expand Down
70 changes: 68 additions & 2 deletions src/Taskview/visualizations/Scatterplot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import { Field } from 'vega-lite/build/src/channeldef';
import { ICohort } from '../../app/interfaces';
import { AttributeType, IAttribute, IdValuePair, ServerColumnAttribute } from '../../data';
import {
ICohortDBDataParams,
ICohortMultiAttrDBDataParams,
ICohortDBDataParams, ICohortDBDataRecommendSplitParams,
ICohortMultiAttrDBDataParams, ICohortMultiAttrDBDataRecommendSplitParams, IEqualsList, INumRange,
NumRangeOperators
} from '../../base';
import {IFilterDesc, INewCohortDesc, inRange} from '../../util';
Expand Down Expand Up @@ -877,6 +877,72 @@ export class Scatterplot extends MultiAttributeVisualization {
this.container.dispatchEvent(new FilterEvent(filterDescs));
}


/** Calls the recommendSplit webservice and sets the bins according to the returned results */
async recommendSplit(useBinCount: boolean = false) {
console.log("recommendSplit scatterplot");

let binsCount0 = 0;
let binsCount1 = 0;
if (useBinCount) {
// select the bins field
binsCount0 = (this.controls.querySelector(`#split input.bins[data-axis=x]`) as HTMLInputElement).valueAsNumber;
console.log("binsCountX", binsCount0);
binsCount1 = (this.controls.querySelector(`#split input.bins[data-axis=y]`) as HTMLInputElement).valueAsNumber;
console.log("binsCountY", binsCount1);
}

const cohorts = this.cohorts;

let filterDesc: IFilterDesc[] = [];
if (cohorts.length === 1) {
// it does not make sense to do a recommendSplit on multiple cohorts at once
// createAutomatically looks at the data of one cohort and creates new cohorts based on that
// recommendSplit recommends splits that are used for ALL cohorts, so it would not make sense to use it on multiple cohorts

// 1 cohort, 1 category
let filter: INumRange[] | IEqualsList = [];

filterDesc.push({
cohort: cohorts[0],
filter: [
{
attr: this.attributes[0],
range: filter,
},
],
});

filterDesc.push({
cohort: cohorts[0],
filter: [
{
attr: this.attributes[1],
range: filter,
},
],
});

const params: ICohortMultiAttrDBDataRecommendSplitParams = {
cohortId: filterDesc[0].cohort.dbId,
attribute0: this.attributes[0].dataKey,
attribute1: this.attributes[1].dataKey,
binsCount0: binsCount0,
binsCount1: binsCount1,
};
// const data = await recommendSplit(params);
// console.log("recommendSplit", data);
//
// this.splitValues = [];
// for (let i = 0; i < data.length; i++) {
// // get the int val of the data[i]
// const binBorder = Number(data[i]);
// this.splitValues.push({x: binBorder});
// }
// this.vegaView.data(AVegaVisualization.SPLITVALUE_DATA_STORE, cloneDeep(this.splitValues)); // set a defensive copy
}
}

async createAutomatically() {
console.log("createAutomatically scatterplot");

Expand Down
Loading

0 comments on commit 89a6b4d

Please sign in to comment.