Skip to content

Commit

Permalink
Merge branch 'sgratzl/fix_data_mapping' into sgratzl/filter_tuning
Browse files Browse the repository at this point in the history
  • Loading branch information
sgratzl committed Apr 8, 2020
2 parents abeffd7 + 094e7b3 commit c545a04
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 46 deletions.
34 changes: 34 additions & 0 deletions cypress/integration/pr292_data_mapping.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {withLineUp, waitReady, LineUpJSType, LineUp} from './utils/lineup';
import {generateData} from './utils/data';
import {openMoreDialog, closeDialog, resetDialog} from './utils/ui';

describe('pr292_data_mapping', () => {
let lineup: LineUp;
let lineUpJS: LineUpJSType;
before(withLineUp((l, document) => {
lineUpJS = l;
const arr = generateData({
number: 1,
string: 0,
date: 0,
cat: 0
});

lineup = lineUpJS.asLineUp(document.body, arr);
waitReady(lineup);
}));

function openDataMappingDialog() {
// open more menu
return openMoreDialog('[data-type=number]', 'data-mapping');
}

it('reset domain value', () => {
openDataMappingDialog().within(() => {
cy.get('input[type=number]:first').invoke('val', '0.2');
resetDialog();
cy.get('input[type=number]:first').should('not.have.value', '0.2');
});
closeDialog('cancel');
});
});
36 changes: 25 additions & 11 deletions src/styles/_mappingeditor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
}

div.#{$lu_css_prefix}-dialog-mapper-script {
margin-top: 1em;
display: none;

textarea {
Expand All @@ -30,37 +29,50 @@ div.#{$lu_css_prefix}-dialog-mapper-script {
}
}

.#{$lu_css_prefix}-dialog-mapper-domain,
.#{$lu_css_prefix}-dialog-mapper-range {
transform: translate(0, 10px);
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 14px;
border: 3px solid $lu_mapping_box;
padding: 0.4em 2px;
text-align: center;
}

.#{$lu_css_prefix}-dialog-mapper-range {
transform: translate(0, -10px);
justify-content: space-around;
}

svg.#{$lu_css_prefix}-dialog-mapper-details {
shape-rendering: geometricprecision;
z-index: 1;
height: 25em * 6 / 10;
background: transparent;

rect {
fill: transparent;
stroke: none;
}

line {
stroke: $lu_mapping_bg;
stroke: $lu_mapping_handle;
stroke-width: 1;
stroke-linejoin: round;
stroke-linecap: round;

&:not([x1]) {

// the horizontal lines
stroke-width: 1.5;
}

&[data-v] {
stroke-opacity: 0.5;
stroke-width: 0.5;
stroke: $lu_toolbar_color_base2;
stroke: $lu_mapping_line;
pointer-events: none;
}
}

circle {
fill: $lu_mapping_circle;
fill: $lu_mapping_handle;
}
}

Expand All @@ -74,9 +86,11 @@ g.#{$lu_css_prefix}-dialog-mapper-mapping {

&.#{$lu_css_prefix}-frozen circle:first-of-type {
cursor: not-allowed;
fill: $lu_mapping_circle !important;
fill: $lu_mapping_handle !important;
transform: scale(0.4);
}

&.#{$lu_css_prefix}-mapping-line-selected,
&:hover {
circle {
fill: $lu_mapping_hover;
Expand Down
7 changes: 4 additions & 3 deletions src/styles/_vars.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ $lu_missing_dash_color: #c1c1c1 !default;

$lu_axis_color: #c1c1c1 !default;

$lu_mapping_circle: $lu_toolbar_color_base2;
$lu_mapping_hover: $lu_selected_color;
$lu_mapping_bg: $lu_toolbar_color_hover;
$lu_mapping_box: $lu_toolbar_color_base !default;
$lu_mapping_line: $lu_hover_color !default;
$lu_mapping_handle: $lu_toolbar_color_hover !default;
$lu_mapping_hover: $lu_selected_color !default;

$lu_side_panel_bg_color: #f0f0f0 !default;
$lu_side_panel_toolbar_bg: #6d6c6c !default;
Expand Down
1 change: 1 addition & 0 deletions src/styles/engine/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
}

.#{$engine_css_prefix}-footer {

// extra space to the right using a width for easier last column resizing
width: $lu_engine_resize_space;
}
Expand Down
35 changes: 26 additions & 9 deletions src/ui/dialogs/MappingDialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,24 @@ export default class MappingDialog extends ADialog {
${others.length > 0 ? `<optgroup label="Copy From">${others.map((d) => `<option value="copy_${d.id}">${d.label}</option>`).join('')}</optgroup>`: ''}
</select>
</div>
<div><strong>Domain (min - max): </strong><input id="${this.idPrefix}min" required type="number" value="${round(this.rawDomain[0], 3)}" step="any"> - <input id="${this.idPrefix}max" required type="number" value="${round(this.rawDomain[1], 3)}" step="any"></div>
<strong style="text-align: center">Input Domain (min - max)</strong>
<div class=${cssClass('dialog-mapper-domain')}>
<input id="${this.idPrefix}min" required type="number" value="${round(this.rawDomain[0], 3)}" step="any">
<span>Input Domain (min - max)</span>
<input id="${this.idPrefix}max" required type="number" value="${round(this.rawDomain[1], 3)}" step="any">
</div>
<svg class="${cssClass('dialog-mapper-details')}" viewBox="0 0 106 66">
<g transform="translate(3,3)">
<line x2="100"></line>
<rect y="-3" width="100" height="10"></rect>
<line y1="60" x2="100" y2="60"></line>
<rect y="36" width="100" height="10"></rect>
<rect y="-3" width="100" height="10">
<title>Click to create a new mapping line</title>
</rect>
<rect y="53" width="100" height="10">
<title>Click to create a new mapping line</title>
</rect>
</g>
</svg>
<strong style="text-align: center; margin-top: 0">Output Normalized Domain (0 - 1)</strong>
<div class=${cssClass('dialog-mapper-range')}>
<span>Output Normalized Domain (0 - 1)</span>
</div>
<div class="${cssClass('dialog-mapper-script')}">
<strong>Custom Normalization Script</strong>
<textarea class="${cssClass('textarea')}"></textarea>
Expand Down Expand Up @@ -171,7 +178,12 @@ export default class MappingDialog extends ADialog {
}
d.setCustomValidity('');
this.rawDomain[i] = v;
this.scale.domain = this.rawDomain.slice();
this.scale = this.computeScale();
const patchedDomain = this.scale.domain.slice();
patchedDomain[0] = this.rawDomain[0];
patchedDomain[patchedDomain.length - 1] = this.rawDomain[1];
this.scale.domain = patchedDomain;
this.createMappings();
this.updateLines();
if (this.showLivePreviews()) {
this.column.setMapping(this.scale);
Expand All @@ -186,6 +198,11 @@ export default class MappingDialog extends ADialog {
}
});
});

this.node.addEventListener('click', () => {
// any open dialog line editors
this.dialog.manager.removeAboveLevel(this.dialog.level + 1);
});
}

private createMappings() {
Expand All @@ -209,7 +226,7 @@ export default class MappingDialog extends ADialog {
(<HTMLTextAreaElement>this.find('textarea')).value = (<ScriptMappingFunction>this.scale).code;
}
const domain = this.scale.domain;
this.forEach(`.${cssClass('dialog-mapper-details')} input[type=number]`, (d: HTMLInputElement, i) => {
this.forEach(`input[type=number]`, (d: HTMLInputElement, i) => {
d.value = String(domain[i]);
});
}
Expand Down
80 changes: 57 additions & 23 deletions src/ui/dialogs/MappingLineDialog.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {round, similar, dragHandle, IDragHandleOptions} from '../../internal';
import {IDialogContext} from './ADialog';
import ADialog, {IDialogContext} from './ADialog';
import {cssClass} from '../../styles';
import APopup from './APopup';

function clamp(v: number) {
return Math.max(Math.min(v, 100), 0);
Expand All @@ -23,28 +22,51 @@ export interface IMappingAdapter {
}

/** @internal */
export default class MappingLineDialog extends APopup {
constructor(private readonly line: {destroy(): void, domain: number, range: number, frozen: boolean, update(domain: number, range: number): void}, dialog: IDialogContext, private readonly adapter: IMappingAdapter) {
super(dialog);
export default class MappingLineDialog extends ADialog {
private readonly before: {domain: number, range: number};

constructor(private readonly line: {destroy(): void, domain: number, range: number, frozen: boolean, update(domain: number, range: number, trigger: boolean): void}, dialog: IDialogContext, private readonly adapter: IMappingAdapter) {
super(dialog, {
livePreview: 'dataMapping'
});
this.dialog.attachment.classList.add(cssClass('mapping-line-selected'));
this.before = {
domain: this.line.domain,
range: this.line.range
};
}

build(node: HTMLElement) {
const domain = this.adapter.domain();
node.insertAdjacentHTML('beforeend', `
<button class="${cssClass('dialog-button')} lu-action-remove" type="button" ${this.line.frozen ? 'style="display: none"' : ''} ><span style="margin-left: 3px">Remove Mapping Line</span></button>
<strong>Input Domain Value (min ... max)</strong>
<input type="number" value="${round(this.adapter.unnormalizeRaw(this.line.domain), 3)}" ${this.line.frozen ? 'readonly' : ''} autofocus required min="${domain[0]}" max="${domain[1]}" step="any">
<input type="number" value="${round(this.adapter.unnormalizeRaw(this.line.domain), 3)}" ${this.line.frozen ? 'readonly disabled' : ''} autofocus required min="${domain[0]}" max="${domain[1]}" step="any">
<strong>Output Normalized Value (0 ... 1)</strong>
<input type="number" value="${round(this.line.range / 100, 3)}" required min="0" max="1" step="any">
<button type="button" ${this.line.frozen ? 'disabled' : ''} >Remove Mapping Line</button>
`);

this.forEach('input', (d: HTMLInputElement) => d.onchange = () => this.submit());
this.find('button').addEventListener('click', () => {
this.destroy('confirm');
this.line.destroy();
}, {
passive: true
});
});
this.enableLivePreviews('input');
}

cleanUp(action: 'cancel' | 'confirm' | 'handled') {
super.cleanUp(action);

this.dialog.attachment.classList.remove(cssClass('mapping-line-selected'));
}

protected cancel() {
this.line.update(this.before.domain, this.before.range, true);
}

protected reset() {
this.findInput('input[type=number]').value = round(this.adapter.unnormalizeRaw(this.before.domain), 3).toString();
this.findInput('input[type=number]:last-of-type').value = round(this.before.range / 100, 3).toString();
}

protected submit() {
Expand All @@ -53,7 +75,7 @@ export default class MappingLineDialog extends APopup {
}
const domain = this.adapter.normalizeRaw(this.findInput('input[type=number]').valueAsNumber);
const range = this.findInput('input[type=number]:last-of-type').valueAsNumber * 100;
this.line.update(domain, range);
this.line.update(domain, range, true);
return true;
}
}
Expand All @@ -66,9 +88,9 @@ export class MappingLine {
g.insertAdjacentHTML('beforeend', `<g class="${cssClass('dialog-mapper-mapping')}" transform="translate(${domain},0)">
<line x1="0" x2="${range - domain}" y2="60"></line>
<line x1="0" x2="${range - domain}" y2="60"></line>
<circle r="3"></circle>
<circle cx="${range - domain}" cy="60" r="3"></circle>
<title>Drag the anchor circle to change the mapping, shift click to edit</title>
<circle r="2.5"></circle>
<circle cx="${range - domain}" cy="60" r="2.5"></circle>
<title>Drag the anchor circle to change the mapping, double click to edit</title>
</g>`);
this.node = <SVGGElement>g.lastElementChild!;

Expand Down Expand Up @@ -130,17 +152,25 @@ export class MappingLine {
if (!evt.shiftKey) {
return;
}
const ctx = {
manager: this.adapter.dialog.manager,
level: this.adapter.dialog.level + 1,
attachment: <any>this.node,
idPrefix: this.adapter.dialog.idPrefix
};
const dialog = new MappingLineDialog(this, ctx, this.adapter);
dialog.open();
this.openDialog();
};

this.node.ondblclick = () => {
this.openDialog();
};
}

private openDialog() {
const ctx = {
manager: this.adapter.dialog.manager,
level: this.adapter.dialog.level + 1,
attachment: <any>this.node,
idPrefix: this.adapter.dialog.idPrefix
};
const dialog = new MappingLineDialog(this, ctx, this.adapter);
dialog.open();
}

get frozen() {
return this.node.classList.contains(cssClass('frozen'));
}
Expand All @@ -150,7 +180,7 @@ export class MappingLine {
this.adapter.destroyed(this);
}

update(domain: number, range: number) {
update(domain: number, range: number, trigger = false) {
if (similar(domain, this.domain) && similar(range, this.range)) {
return;
}
Expand All @@ -163,5 +193,9 @@ export class MappingLine {
const shift = range - domain;
Array.from(this.node.querySelectorAll<SVGLineElement>('line')).forEach((d) => d.setAttribute('x2', String(shift)));
this.node.querySelector<SVGCircleElement>('circle[cx]')!.setAttribute('cx', String(shift));

if (trigger) {
this.adapter.updated(this);
}
}
}

0 comments on commit c545a04

Please sign in to comment.