Open justinlevi opened 7 years ago
Update: Here is a working example using layer selection.
import * as ui from 'waves-ui';
class CollisionSegmentBehavior extends ui.behaviors.BaseBehavior {
segmentsData = [];
leadingSegment = undefined;
train = [];
DIRECTION = {
LEFT: 'LEFT',
RIGHT: 'RIGHT'
};
segmentIndex = (search) => {return this.segmentsData.findIndex( obj => { return obj === search }); };
segmentValues = (renderingContext, shape, dataObj) => {
return ({
startX: renderingContext.timeToPixel(shape.x(dataObj)),
// y: renderingContext.valueToPixel(shape.y(dataObj)),
endX: renderingContext.timeToPixel(shape.x(dataObj) + shape.width(dataObj)),
// height: renderingContext.valueToPixel(shape.height(dataObj))
});
};
constructor(segmentsData) {
super();
// segmentsData is a reference to the data defining all your segments
this.segmentsData = segmentsData;
// binding
this.isTouchingSibling = this.isTouchingSibling.bind(this);
}
edit(renderingContext, shape, datum, dx, dy, target) {
const classList = target.classList;
let action = 'move';
if (classList.contains('handler') && classList.contains('left')) {
action = 'resizeLeft';
} else if (classList.contains('handler') && classList.contains('right')) {
action = 'resizeRight';
}
this[`_${action}`](renderingContext, shape, datum, dx, dy, target);
}
isTouchingSibling(renderingContext, shape, currentIndex, direction) {
// CONVENIENCE
const { segmentValues, DIRECTION } = this;
const { LEFT, RIGHT} = DIRECTION;
const currentSegmentObj = this.segmentsData[currentIndex];
const nextSegmentObj = this.segmentsData[(direction === LEFT) ? currentIndex - 1 : currentIndex + 1];
if (!nextSegmentObj) { return }
const currentSegmentValues = segmentValues(renderingContext, shape, currentSegmentObj);
const cSegStart = currentSegmentValues.startX;
const cSegEnd = currentSegmentValues.endX;
const nextSegmentValues = segmentValues(renderingContext, shape, nextSegmentObj);
const nSegStart = nextSegmentValues.startX;
const nSegEnd = nextSegmentValues.endX;
if (direction === LEFT) {
// Does the left edge of the current segment hit the right edge of the next segment
return (cSegStart <= nSegEnd) ? true : false;
}else if (direction === RIGHT) {
// Does the right edge of the current segment hit the left edge of the next segment
return (cSegEnd >= nSegStart) ? true : false;
}
return false;
}
_move(renderingContext, shape, dataObj, dx, dy, target) {
// convenience destructuring
const { segmentIndex, DIRECTION, isTouchingSibling } = this;
const { LEFT, RIGHT } = DIRECTION;
const currentIndex = segmentIndex(dataObj);
const direction = (dx < 0) ? LEFT : RIGHT;
// TODO: If changing direction of drag, all selected items should be deselected expect current
if (isTouchingSibling(renderingContext, shape, currentIndex, direction)) {
this.select(this._layer.items[(direction === LEFT)? currentIndex - 1 : currentIndex + 1]);
}
const x = renderingContext.timeToPixel(shape.x(dataObj))
var targetX = Math.max(x + dx, 0);
// START - OVERLAP NEIGHBOR CHECK
// TODO: This can definitely be refactored
const nextSegmentObj = this.segmentsData[(direction === LEFT) ? currentIndex - 1 : currentIndex + 1];
if(nextSegmentObj){
const currentSegmentValues = this.segmentValues(renderingContext, shape, dataObj);
const cSegStart = currentSegmentValues.startX;
const cSegEnd = currentSegmentValues.endX;
const cSegWidth = cSegEnd - cSegStart;
const nextSegmentValues = this.segmentValues(renderingContext, shape, nextSegmentObj);
const nSegStart = nextSegmentValues.startX;
const nSegEnd = nextSegmentValues.endX;
if (direction === LEFT && targetX < nSegEnd) {
targetX = nSegEnd + dx;
}
else if (direction === RIGHT && targetX + cSegWidth > nSegStart){
targetX = nSegStart - cSegWidth + dx;
}
// END - OVERLAP NEIGHBOR CHECK
}
shape.x(dataObj, renderingContext.timeToPixel.invert(targetX));
}
_resizeLeft(renderingContext, shape, datum, dx, dy, target) {
// current values
const x = renderingContext.timeToPixel(shape.x(datum));
const width = renderingContext.timeToPixel(shape.width(datum));
// target values
let maxTargetX = x + width;
let targetX = x + dx < maxTargetX ? Math.max(x + dx, 0) : x;
let targetWidth = targetX !== 0 ? Math.max(width - dx, 1) : width;
shape.x(datum, renderingContext.timeToPixel.invert(targetX));
shape.width(datum, renderingContext.timeToPixel.invert(targetWidth));
}
_resizeRight(renderingContext, shape, datum, dx, dy, target) {
// current values
const width = renderingContext.timeToPixel(shape.width(datum));
// target values
let targetWidth = Math.max(width + dx, 1);
shape.width(datum, renderingContext.timeToPixel.invert(targetWidth));
}
}
export default CollisionSegmentBehavior;
@b-ma Hoping you might be able to take a look at this custom behavior I've been working on for a few days and give some feedback on my approach.
Here is the gist https://gist.github.com/justinlevi/fa6afbe108620c9806c39b325c17bdef
Summary: I'm trying to recreate the shopping cart/ train car queue/ (trolly) metaphor. In other words, if you drag a segment left or right, it will respect the boundary of a neighbor/sibling and then push the the neighbor/sibling along the track as well.
I have this working, kind of. The redraw is jittery and slow, which makes me think there must be a better approach.
My first attempt was to create my own queue array, and during the drag, test to see if the current item's start/end (depending on direction) overlaps with a neighbor's start/end. Then, in the overridden
_move
method, I am looping through my queue array calling `shape.x(segment, renderingContext.timeToPixel.invert(targetX));After reading through the code closer, I'm noticing there is an internal mechanism to track
selectedItems
on the Layer class. I'm wondering if there is a built in mechanism that all selected items would receive an edit callback from the Layer?Ultimately I'm looking for a smooth UX when dragging any number of segments left/right.
As you can see, I also clearly got a bit overboard with my destructuring syntax. I very likely have some super inefficient code. Any feedback you might have would be greatly appreciated.
Full custom behavior below as well: