Skip to content

Commit

Permalink
implemented feedback from Jonathan in PR
Browse files Browse the repository at this point in the history
  • Loading branch information
NanaKwame committed Apr 4, 2021
1 parent fdf2c63 commit 82b2fa4
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 180 deletions.
17 changes: 0 additions & 17 deletions src/js/components/HistoryToolbar.tsx

This file was deleted.

68 changes: 22 additions & 46 deletions src/js/components/history/HistoryItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import {connect} from 'react-redux';
import {View, parse, Spec} from 'vega';
import {HistoryRecord, HistoryState} from '../../store/factory/History';
import {cleanSpecForPreview} from '../../ctrl/demonstrations';
import {cleanHistorySpec} from './HistoryList';
import {startDragging, stopDragging} from '../../actions/inspectorActions';
import {DraggingStateRecord, HistoryDraggingState} from '../../store/factory/Inspector';
import sg from '../../ctrl/signals';
Expand All @@ -16,61 +16,42 @@ import {ColumnRecord, Schema} from '../../store/factory/Dataset';
import { AnyAction } from 'redux';
import {ThunkDispatch} from 'redux-thunk';
import {State} from '../../store';
import {setSignal} from '../../actions/signalActions';
import {SignalValue} from 'vega-typings/types';
import {batchGroupBy} from '../../reducers/historyOptions';

const ctrl = require('../../ctrl');


interface OwnProps {
id: string,
history: HistoryRecord // TODO: use History.ts for you just have to pass the id

groupNames: any[];
id: number,
history: HistoryRecord // TODO(ej): use History.ts so you just have to pass the id
groupNames: string[];
width: number;
height: number;
}
interface DispatchProps {
startDragging: (d: DraggingStateRecord) => void;
stopDragging: () => void;
startDragging: (d: DraggingStateRecord) => void; // TODO(ej): use this for custom drop zones later
stopDragging: () => void; // TODO(ej): use this for custom drop zones later
setMarkVisual: (payload: {property: string, def: NumericValueRef | StringValueRef}, markId: number) => void;

bindChannel: (dsId: number, field: ColumnRecord, markId: number, property: string) => void;

setSignal: (value: SignalValue, signal: string) => void;
}

function mapDispatchToProps(dispatch: ThunkDispatch<State, null, AnyAction>, ownProps: OwnProps): DispatchProps {
return {
startDragging: (d: DraggingStateRecord) => {
dispatch(startDragging(d)); },
stopDragging: () => {
dispatch(stopDragging()); },
setMarkVisual: (p, markId) => {
dispatch(setMarkVisual(p, markId));
},
bindChannel: (dsId: number, field: ColumnRecord, markId: number, property: string) => {
dispatch(bindChannel(dsId, field, markId, property));
},
setSignal: (value: SignalValue, signal: string) => {
dispatch(setSignal(value, signal));
}
};
}
const actionCreators: DispatchProps = {startDragging, stopDragging, setMarkVisual, bindChannel};

export class HistoryItemInspector extends React.Component<OwnProps & DispatchProps> {

constructor(props) {
super(props);
}

private width = 100; // these should match baseSignals in demonstrations.ts
private height = 100; //


public handleClick = (historyId: number) => {

}
public handleDragStart = (historyId: number) => {
this.props.startDragging(HistoryDraggingState({historyId}));
this.props.startDragging(HistoryDraggingState({historyId})); // TODO(ej) use this for custom drop zones

sg.set(MODE, 'channels');
ctrl.update();
Expand All @@ -88,7 +69,7 @@ export class HistoryItemInspector extends React.Component<OwnProps & DispatchPro
if (dropped) {
const channel = channelName(cell.key);
let fieldName, dsId;
if (channel === 'x' || channel === 'y' || channel === 'color' || channel === 'size') {
if (channel === 'x' || channel === 'y' || channel === 'color' || channel === 'size') { // TODO(ej): Adapt this to work with other mark types. channels won't always be color and size though x and y are consistent
// set scale
let channelScaleIds = this.props.history.getIn(["guides"])
.map((g) => {
Expand Down Expand Up @@ -116,21 +97,19 @@ export class HistoryItemInspector extends React.Component<OwnProps & DispatchPro
vega.extend(bindField, opts); // Aggregate or Bin passed in opts.
props.bindChannel(dsId, bindField, lyraId, cell.key);
} else {
// TODO(ej): need to introduce custom drop zones for aesthetic/non-scale merges instead of using non bubble cursor drops
// update the whole symbol/mark: shape, size
// the history has the signals
let relevantProps = ["size", "shape", "fill", "fillOpacity", "stroke", "strokeWidth"];
let historyMark = this.props.history.getIn(["marks", String(lyraId)]); // assume mark is the same as before. wanna revert to old settings of old mark

batchGroupBy.start();
relevantProps.forEach((prop) => {
let historyProp = historyMark.getIn(["encode", "update", prop]);
if (historyProp.signal) {
let historySigVal = this.props.history.getIn(["signals", historyProp.signal]).value;
this.props.setSignal(historySigVal, historyProp.signal); // update Store
sg.set(historyProp.signal, historySigVal, false); // update Vega
sg.set(historyProp.signal, historySigVal, false);
}
});
batchGroupBy.end();
}
} catch (e) {
console.error('Unable to bind primitive');
Expand All @@ -145,10 +124,10 @@ export class HistoryItemInspector extends React.Component<OwnProps & DispatchPro

}

private historyToSpec(preview: HistoryRecord): Spec {
private getHistorySpec(): Spec {
let historySpec = ctrl.export(false, this.props.history);
if (historySpec.marks) {
historySpec = cleanSpecForPreview(historySpec, this.width, this.height, null, parseInt(this.props.id), true);
historySpec = cleanHistorySpec(historySpec, this.props.width, this.props.height);
}

return historySpec;
Expand All @@ -158,15 +137,13 @@ export class HistoryItemInspector extends React.Component<OwnProps & DispatchPro


public componentDidMount() {
const spec = this.historyToSpec(this.props.history);
const spec = this.getHistorySpec();
this.view = new View(parse(spec), {
renderer: 'svg', // renderer (canvas or svg)
container: `#history-test-${this.props.id}` // parent DOM container
});
this.view.width(this.width);
// this.view.signal("width", this.width);
this.view.height(this.height);
// this.view.signal("height", this.height);
this.view.width(this.props.width);
this.view.height(this.props.height);
this.view.runAsync();
}

Expand All @@ -177,16 +154,15 @@ export class HistoryItemInspector extends React.Component<OwnProps & DispatchPro
className={"history-preview"}
draggable={true}
key={this.props.id}
onClick={() => this.handleClick(parseInt(this.props.id))}
onDragStart={() => this.handleDragStart(parseInt(this.props.id))}
onClick={() => this.handleClick(this.props.id)}
onDragStart={() => this.handleDragStart(this.props.id)}
onDragEnd={this.handleDragEnd}></div>
);

}

}

export const HistoryItem = connect(
null,
mapDispatchToProps
actionCreators
)(HistoryItemInspector);
169 changes: 159 additions & 10 deletions src/js/components/history/HistoryList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ import {updateHistoryProperty} from '../../actions/historyActions';
import {State} from '../../store';
import {HistoryItem} from './HistoryItem';
import {MarkRecord} from '../../store/factory/Mark';
import exportName from '../../util/exportName';
import {mapNestedMarksOfGroup, editSignals} from '../../ctrl/demonstrations';
import duplicate from "../../util/duplicate";
import {Spec} from "vega";
import {marksReducer} from '../../reducers/marksReducer';

const getIn = require('../../util/immutable-utils').getIn;
const HEIGHT = 100;
const WIDTH = 100;

interface OwnProps {
}
Expand All @@ -19,12 +26,12 @@ interface DispatchProps {
updateHistoryProperty: (payload: {property: string, value: any}, id: number) => void;
}

function mapState(state: State): StateProps {
function mapStateToProps(state: State): StateProps {
const marks: Map<string, MarkRecord> = state.getIn(['vis', 'present', 'marks']);
const groupNames = marks.filter((mark: MarkRecord) => {
return mark.type === 'group';
}).map((v) => {
return v.name.replace(" ", "_");
return exportName(v.name);
}).toList().toJSON();
let history = [...getIn(state, 'vis').past];
history.push(getIn(state, 'vis').present);
Expand All @@ -34,23 +41,165 @@ function mapState(state: State): StateProps {
};
}

const mapDispatch: DispatchProps = {
const actionCreators: DispatchProps = {
updateHistoryProperty
};

class BaseHistoryList extends React.Component<OwnProps & StateProps & DispatchProps> {

public render() {

return (
<div id='history-list' >
{this.props.history.map(
(item, idx) => {
return <HistoryItem id={idx+''} key={idx+''} history={item} groupNames={this.props.groupNames} />
}
)}
<div id='history-toolbar'>
<h2>History</h2>
<div id='history-list' >
{this.props.history.map(
(item, idx) => {
return <HistoryItem id={idx} key={idx+''} history={item} groupNames={this.props.groupNames} width={WIDTH} height={HEIGHT} />
}
)}
</div>
</div>
);
}
}

export const HistoryList = connect(mapState, mapDispatch)(BaseHistoryList);
export function cleanHistorySpec(sceneSpec, width: number, height: number): Spec {
let interactionSignals = ["brush_", "point_", "points_", "mouse_", "grid_", "unit", "key_modifier"];
const sceneUpdated = duplicate(sceneSpec);
sceneUpdated.autosize = "none";

sceneUpdated.marks = sceneUpdated.marks.map(markSpec => {
if (markSpec.name && markSpec.type === 'group') { // don't touch manipulators, which don't have names
const oldWidth = markSpec.encode?.update?.width?.value || 640;
const oldHeight = markSpec.encode?.update?.height?.value || 360;
const wScale = width / oldWidth; // preview width / main view width
const hScale = height / oldHeight; // preview height / main view height
let scale = (wScale + hScale) / 2;

// remove interaction
markSpec.marks = markSpec.marks.filter((subMarkSpec) => {
return !subMarkSpec.clip;
});
markSpec.signals = markSpec.signals.filter((sig) => {
return !interactionSignals.some((interationSig) => sig.name.includes(interationSig));
});

markSpec.axes = markSpec.axes.map((axis) => {
axis.encode.labels.update.fontSize.value *= scale; // labels
axis.encode.grid.update.strokeWidth.value *=scale; // grid
axis.encode.ticks.update.strokeWidth.value *=scale; // ticks
axis.encode.title.update.fontSize.value *=scale; // title
return axis;

});

markSpec.legends = markSpec.legends.map((legend) => {
legend.titleFontSize *=scale; // title
legend.encode.title.update.fontSize.value *=scale; // title
legend.encode.labels.update.fontSize.value *=scale; // labels
legend.encode.legend.update.strokeWidth.value *=scale; // strokewidth
legend.encode.symbols.update.strokeWidth.value *=scale; // symbols
legend.encode.symbols.update.size.value *=scale; // symbols
return legend;
});


markSpec.encode.update.x = {"value": 0};
markSpec.encode.update.y = {"value": 0};
markSpec.encode.update.width = {"signal": "width"};
markSpec.encode.update.height = {"signal": "height"};

markSpec = mapNestedMarksOfGroup(markSpec, mark => {
if (mark.type === 'symbol' && mark.encode?.update?.size) {
if (Array.isArray(mark.encode.update.size) && mark.encode?.update?.size.length == 2) {
mark.encode.update.size = mark.encode.update.size.map(def => {
if (def.value) {
return {
...def,
value: def.value * (wScale + hScale) / 2
}
}
return def;
})
}
else if (mark.encode.update.size.value) {
mark.encode.update.size.value *= (wScale + hScale) / 2;
}
}
if (mark.type === 'text') {
if (mark.encode?.update?.fontSize?.value) {
mark.encode.update.fontSize.value /= 2;
}
if (mark.encode?.update?.dx?.value) {
mark.encode.update.dx.value *= wScale;
}
if (mark.encode?.update?.dy?.value) {
mark.encode.update.dy.value *= hScale;
}
if (mark.encode?.update?.x?.value) {
mark.encode.update.x.value *= wScale;
}
if (mark.encode?.update?.y?.value) {
mark.encode.update.y.value *= hScale;
}
}
if (mark.type === 'line' && mark.encode?.update?.strokeWidth?.value) {
mark.encode.update.strokeWidth.value /= 2;
}
if (mark.encode?.update?.x?.value != null) {
mark.encode.update.x.value *= scale;
}
if (mark.encode?.update?.y?.value != null) {
mark.encode.update.y.value *= scale;
}
return mark;
});

markSpec.scales = markSpec.scales.map(scale => {
if (scale.range) {
const range = scale.range;
if (Array.isArray(range) && range.length == 2 && !range.some(isNaN)) {
scale.range = range.map(n => n / 10);
}
if (range.step && range.step.signal) {
markSpec.signals = markSpec.signals.map(signal => {
if (signal.name === range.step.signal) {
signal.value /= 2;
}
return signal;
});
}
}
return scale;
})
}
return markSpec;
});

return addBaseSignals(sceneUpdated);
}

function addBaseSignals(sceneSpec) {
const sceneUpdated = duplicate(sceneSpec);
sceneUpdated.marks = sceneUpdated.marks.map(markSpec => {
if (markSpec.name && markSpec.type === 'group') {
markSpec.signals = editSignals(markSpec.signals, baseSignals);
}
return markSpec;
});
return sceneUpdated;
}

const baseSignals = [
{
name: "width",
init: String(WIDTH)
},
{
name: "height",
init: String(HEIGHT)
}
];

export const HistoryList = connect(mapStateToProps, actionCreators)(BaseHistoryList);
Loading

0 comments on commit 82b2fa4

Please sign in to comment.