implement xySwap in map

This commit is contained in:
gabrielburnworth 2018-04-13 02:54:00 -07:00
parent 9cf19193bd
commit b343d44192
17 changed files with 272 additions and 111 deletions

View file

@ -95,6 +95,33 @@ describe("<VirtualFarmBot/>", () => {
expect(maxLines.at(1).props()).toEqual({ "x1": 2998, "x2": 2900, "y1": 100, "y2": 100 });
});
it("renders max line in correct location", () => {
const p = fakeProps();
p.stopAtHome.x = false;
p.stopAtHome.y = false;
p.botSize = {
x: { value: 100, isDefault: false },
y: { value: 100, isDefault: true }
};
const wrapper = shallow(<BotExtents {...p} />);
const maxLines = wrapper.find("#max-lines").find("line");
expect(maxLines.at(0).props()).toEqual({ "x1": 100, "x2": 100, "y1": 2, "y2": 100 });
});
it("renders max line in correct location with swapped axes", () => {
const p = fakeProps();
p.stopAtHome.x = false;
p.stopAtHome.y = false;
p.mapTransformProps.xySwap = true;
p.botSize = {
x: { value: 100, isDefault: false },
y: { value: 100, isDefault: true }
};
const wrapper = shallow(<BotExtents {...p} />);
const maxLines = wrapper.find("#max-lines").find("line");
expect(maxLines.at(0).props()).toEqual({ "x1": 2, "x2": 100, "y1": 100, "y2": 100 });
});
it("renders no lines", () => {
const p = fakeProps();
p.stopAtHome.x = false;

View file

@ -13,31 +13,49 @@ describe("<ToolbaySlot />", () => {
x: 10,
y: 20,
pulloutDirection: 0,
quadrant: 2
quadrant: 2,
xySwap: false,
};
};
const checkSlotDirection =
(direction: number, quadrant: BotOriginQuadrant, expected: string) => {
it(`renders slot, pullout: ${direction} quad: ${quadrant}`, () => {
const p = fakeProps();
p.pulloutDirection = direction;
p.quadrant = quadrant;
const wrapper = mount(<ToolbaySlot {...p } />);
expect(wrapper.find("use").props().transform).toEqual(expected);
});
(direction: number,
quadrant: BotOriginQuadrant,
xySwap: boolean,
expected: string) => {
it(`renders slot, pullout: ${direction} quad: ${quadrant} yx: ${xySwap}`,
() => {
const p = fakeProps();
p.pulloutDirection = direction;
p.quadrant = quadrant;
p.xySwap = xySwap;
const wrapper = mount(<ToolbaySlot {...p} />);
expect(wrapper.find("use").props().transform).toEqual(expected);
});
};
checkSlotDirection(0, 2, "rotate(0, 10, 20)");
checkSlotDirection(1, 1, "rotate(180, 10, 20)");
checkSlotDirection(1, 2, "rotate(0, 10, 20)");
checkSlotDirection(1, 3, "rotate(0, 10, 20)");
checkSlotDirection(1, 4, "rotate(180, 10, 20)");
checkSlotDirection(2, 3, "rotate(180, 10, 20)");
checkSlotDirection(3, 1, "rotate(90, 10, 20)");
checkSlotDirection(3, 2, "rotate(90, 10, 20)");
checkSlotDirection(3, 3, "rotate(270, 10, 20)");
checkSlotDirection(3, 4, "rotate(270, 10, 20)");
checkSlotDirection(4, 3, "rotate(90, 10, 20)");
checkSlotDirection(0, 2, false, "rotate(0, 10, 20)");
checkSlotDirection(1, 1, false, "rotate(180, 10, 20)");
checkSlotDirection(1, 2, false, "rotate(0, 10, 20)");
checkSlotDirection(1, 3, false, "rotate(0, 10, 20)");
checkSlotDirection(1, 4, false, "rotate(180, 10, 20)");
checkSlotDirection(2, 3, false, "rotate(180, 10, 20)");
checkSlotDirection(3, 1, false, "rotate(90, 10, 20)");
checkSlotDirection(3, 2, false, "rotate(90, 10, 20)");
checkSlotDirection(3, 3, false, "rotate(270, 10, 20)");
checkSlotDirection(3, 4, false, "rotate(270, 10, 20)");
checkSlotDirection(4, 3, false, "rotate(90, 10, 20)");
checkSlotDirection(0, 2, true, "rotate(180, 10, 20)");
checkSlotDirection(1, 1, true, "rotate(90, 10, 20)");
checkSlotDirection(1, 2, true, "rotate(90, 10, 20)");
checkSlotDirection(1, 3, true, "rotate(270, 10, 20)");
checkSlotDirection(1, 4, true, "rotate(270, 10, 20)");
checkSlotDirection(2, 3, true, "rotate(90, 10, 20)");
checkSlotDirection(3, 1, true, "rotate(180, 10, 20)");
checkSlotDirection(3, 2, true, "rotate(0, 10, 20)");
checkSlotDirection(3, 3, true, "rotate(0, 10, 20)");
checkSlotDirection(3, 4, true, "rotate(180, 10, 20)");
checkSlotDirection(4, 3, true, "rotate(180, 10, 20)");
});
describe("<Tool/>", () => {
@ -58,7 +76,7 @@ describe("<Tool/>", () => {
};
it("renders standard tool styling", () => {
const wrapper = mount(<Tool {...fakeProps() } />);
const wrapper = mount(<Tool {...fakeProps()} />);
const props = wrapper.find("circle").last().props();
expect(props.r).toEqual(35);
expect(props.cx).toEqual(10);
@ -69,7 +87,7 @@ describe("<Tool/>", () => {
it("tool hover", () => {
const p = fakeProps();
p.toolProps.hovered = true;
const wrapper = mount(<Tool {...p } />);
const wrapper = mount(<Tool {...p} />);
const props = wrapper.find("circle").last().props();
expect(props.fill).toEqual(Color.darkGray);
});
@ -77,7 +95,7 @@ describe("<Tool/>", () => {
it("renders special tool styling: bin", () => {
const p = fakeProps();
p.tool = "seedBin";
const wrapper = mount(<Tool {...p } />);
const wrapper = mount(<Tool {...p} />);
const elements = wrapper.find("#seed-bin").find("circle");
expect(elements.length).toEqual(2);
expect(elements.last().props().fill).toEqual("url(#SeedBinGradient)");
@ -87,7 +105,7 @@ describe("<Tool/>", () => {
const p = fakeProps();
p.tool = "seedBin";
p.toolProps.hovered = true;
const wrapper = mount(<Tool {...p } />);
const wrapper = mount(<Tool {...p} />);
p.toolProps.hovered = true;
expect(wrapper.find("#seed-bin").find("circle").length).toEqual(3);
});
@ -95,7 +113,7 @@ describe("<Tool/>", () => {
it("renders special tool styling: tray", () => {
const p = fakeProps();
p.tool = "seedTray";
const wrapper = mount(<Tool {...p } />);
const wrapper = mount(<Tool {...p} />);
const elements = wrapper.find("#seed-tray");
expect(elements.find("circle").length).toEqual(2);
expect(elements.find("rect").length).toEqual(1);
@ -106,7 +124,7 @@ describe("<Tool/>", () => {
const p = fakeProps();
p.tool = "seedTray";
p.toolProps.hovered = true;
const wrapper = mount(<Tool {...p } />);
const wrapper = mount(<Tool {...p} />);
p.toolProps.hovered = true;
expect(wrapper.find("#seed-tray").find("circle").length).toEqual(3);
});

View file

@ -7,30 +7,46 @@ describe("textAnchorPosition()", () => {
const MIDDLE_BOTTOM = { anchor: "middle", x: 0, y: -40 };
it("returns correct label position: positive x", () => {
expect(textAnchorPosition(1, 1)).toEqual(END);
expect(textAnchorPosition(1, 2)).toEqual(START);
expect(textAnchorPosition(1, 3)).toEqual(START);
expect(textAnchorPosition(1, 4)).toEqual(END);
expect(textAnchorPosition(1, 1, false)).toEqual(END);
expect(textAnchorPosition(1, 2, false)).toEqual(START);
expect(textAnchorPosition(1, 3, false)).toEqual(START);
expect(textAnchorPosition(1, 4, false)).toEqual(END);
expect(textAnchorPosition(1, 1, true)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(1, 2, true)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(1, 3, true)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(1, 4, true)).toEqual(MIDDLE_BOTTOM);
});
it("returns correct label position: negative x", () => {
expect(textAnchorPosition(2, 1)).toEqual(START);
expect(textAnchorPosition(2, 2)).toEqual(END);
expect(textAnchorPosition(2, 3)).toEqual(END);
expect(textAnchorPosition(2, 4)).toEqual(START);
expect(textAnchorPosition(2, 1, false)).toEqual(START);
expect(textAnchorPosition(2, 2, false)).toEqual(END);
expect(textAnchorPosition(2, 3, false)).toEqual(END);
expect(textAnchorPosition(2, 4, false)).toEqual(START);
expect(textAnchorPosition(2, 1, true)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(2, 2, true)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(2, 3, true)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(2, 4, true)).toEqual(MIDDLE_TOP);
});
it("returns correct label position: positive y", () => {
expect(textAnchorPosition(3, 1)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(3, 2)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(3, 3)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(3, 4)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(3, 1, false)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(3, 2, false)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(3, 3, false)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(3, 4, false)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(3, 1, true)).toEqual(END);
expect(textAnchorPosition(3, 2, true)).toEqual(START);
expect(textAnchorPosition(3, 3, true)).toEqual(START);
expect(textAnchorPosition(3, 4, true)).toEqual(END);
});
it("returns correct label position: negative y", () => {
expect(textAnchorPosition(4, 1)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(4, 2)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(4, 3)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(4, 4)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(4, 1, false)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(4, 2, false)).toEqual(MIDDLE_BOTTOM);
expect(textAnchorPosition(4, 3, false)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(4, 4, false)).toEqual(MIDDLE_TOP);
expect(textAnchorPosition(4, 1, true)).toEqual(START);
expect(textAnchorPosition(4, 2, true)).toEqual(END);
expect(textAnchorPosition(4, 3, true)).toEqual(END);
expect(textAnchorPosition(4, 4, true)).toEqual(START);
});
});

View file

@ -170,9 +170,18 @@ describe("getbotSize()", () => {
describe("getMapSize()", () => {
it("calculates map size", () => {
const mapSize = getMapSize(
{ x: 2000, y: 1000 },
fakeMapTransformProps(),
{ x: 100, y: 50 });
expect(mapSize).toEqual({ x: 2200, y: 1100 });
expect(mapSize).toEqual({ h: 1600, w: 3200 });
});
it("calculates map size: X&Y Swapped", () => {
const fakeMPT = fakeMapTransformProps();
fakeMPT.xySwap = true;
const mapSize = getMapSize(
fakeMPT,
{ x: 100, y: 50 });
expect(mapSize).toEqual({ h: 3200, w: 1600 });
});
});
@ -184,10 +193,17 @@ describe("transformXY", () => {
const transformCheck =
(original: QXY, transformed: QXY, transformProps: MapTransformProps) => {
transformProps.xySwap = false;
expect(transformXY(original.qx, original.qy, transformProps))
.toEqual(transformed);
expect(transformXY(transformed.qx, transformed.qy, transformProps))
.toEqual(original);
transformProps.xySwap = true;
const transformedYX = { qx: transformed.qy, qy: transformed.qx };
expect(transformXY(original.qx, original.qy, transformProps))
.toEqual(transformedYX);
expect(transformXY(transformed.qx, transformed.qy, transformProps))
.toEqual({ qx: original.qy, qy: original.qx });
};
it("calculates transformed coordinate: quadrant 2", () => {

View file

@ -4,6 +4,7 @@ import { BotExtentsProps } from "./interfaces";
export function BotExtents(props: BotExtentsProps) {
const { stopAtHome, botSize, mapTransformProps } = props;
const { xySwap } = mapTransformProps;
const homeLength = transformXY(
botSize.x.value, botSize.y.value, mapTransformProps);
const homeZero = transformXY(2, 2, mapTransformProps);
@ -27,12 +28,12 @@ export function BotExtents(props: BotExtentsProps) {
}
</g>
<g id="max-lines">
{!botSize.x.isDefault &&
{(xySwap ? !botSize.y.isDefault : !botSize.x.isDefault) &&
<line
x1={homeLength.qx} y1={homeZero.qy}
x2={homeLength.qx} y2={homeLength.qy} />
}
{!botSize.y.isDefault &&
{(xySwap ? !botSize.x.isDefault : !botSize.y.isDefault) &&
<line
x1={homeZero.qx} y1={homeLength.qy}
x2={homeLength.qx} y2={homeLength.qy} />

View file

@ -55,7 +55,7 @@ export function DragHelpers(props: DragHelpersProps) {
const {
dragging, plant, zoomLvl, activeDragXY, mapTransformProps, plantAreaOffset
} = props;
const mapSize = getMapSize(mapTransformProps.gridSize, plantAreaOffset);
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
const { radius, x, y } = plant.body;
const scale = 1 + Math.round(15 * (1.8 - zoomLvl)) / 10; // scale factor
@ -71,8 +71,8 @@ export function DragHelpers(props: DragHelpersProps) {
</text>}
{dragging && // Active plant
<g id="long-crosshair">
<rect x={qx - 0.5} y={-plantAreaOffset.y} width={1} height={mapSize.y} />
<rect x={-plantAreaOffset.x} y={qy - 0.5} width={mapSize.x} height={1} />
<rect x={qx - 0.5} y={-plantAreaOffset.y} width={1} height={mapSize.h} />
<rect x={-plantAreaOffset.x} y={qy - 0.5} width={mapSize.w} height={1} />
</g>}
{dragging && // Active plant
<g id="short-crosshair">

View file

@ -263,16 +263,19 @@ export class GardenMap extends
const plant = this.getPlant();
const map = document.querySelector(".farm-designer-map");
const { gridSize } = this.props;
const { quadrant, xySwap } = this.mapTransformProps;
if (this.state.isDragging && plant && map) {
const zoomLvl = parseFloat(window.getComputedStyle(map).zoom || "1");
const { qx, qy } = transformXY(e.pageX, e.pageY, this.mapTransformProps);
const deltaX = Math.round((qx - (this.state.pageX || qx)) / zoomLvl);
const deltaY = Math.round((qy - (this.state.pageY || qy)) / zoomLvl);
const dX = xySwap && (quadrant % 2 === 1) ? -deltaX : deltaX;
const dY = xySwap && (quadrant % 2 === 1) ? -deltaY : deltaY;
this.setState({
pageX: qx, pageY: qy,
activeDragXY: { x: plant.body.x + deltaX, y: plant.body.y + deltaY, z: 0 }
activeDragXY: { x: plant.body.x + dX, y: plant.body.y + dY, z: 0 }
});
this.props.dispatch(movePlant({ deltaX, deltaY, plant, gridSize }));
this.props.dispatch(movePlant({ deltaX: dX, deltaY: dY, plant, gridSize }));
}
break;
case Mode.boxSelect:
@ -312,13 +315,14 @@ export class GardenMap extends
render() {
const { gridSize } = this.props;
const mapSize = getMapSize(gridSize, this.props.gridOffset);
const mapSize = getMapSize(this.mapTransformProps, this.props.gridOffset);
const mapTransformProps = this.mapTransformProps;
const { xySwap } = mapTransformProps;
return <div
className="drop-area"
style={{
height: mapSize.y + "px", maxHeight: mapSize.y + "px",
width: mapSize.x + "px", maxWidth: mapSize.x + "px"
height: mapSize.h + "px", maxHeight: mapSize.h + "px",
width: mapSize.w + "px", maxWidth: mapSize.w + "px"
}}
onDrop={this.handleDrop}
onDragEnter={this.handleDragEnter}
@ -334,7 +338,8 @@ export class GardenMap extends
<svg
id="drop-area-svg"
x={this.props.gridOffset.x} y={this.props.gridOffset.y}
width={gridSize.x} height={gridSize.y}
width={xySwap ? gridSize.y : gridSize.x}
height={xySwap ? gridSize.x : gridSize.y}
onMouseUp={this.endDrag}
onMouseDown={this.startDrag}
onMouseMove={this.drag}

View file

@ -6,7 +6,9 @@ import { Color } from "../../ui/index";
export function Grid(props: GridProps) {
const { mapTransformProps } = props;
const { gridSize } = mapTransformProps;
const { gridSize, xySwap } = mapTransformProps;
const gridSizeW = xySwap ? gridSize.y : gridSize.x;
const gridSizeH = xySwap ? gridSize.x : gridSize.y;
const origin = transformXY(0, 0, mapTransformProps);
const arrowEnd = transformXY(25, 25, mapTransformProps);
const xLabel = transformXY(15, -10, mapTransformProps);
@ -34,10 +36,10 @@ export function Grid(props: GridProps) {
<g id="grid">
<rect id="minor-grid"
width={gridSize.x} height={gridSize.y} fill="url(#minor_grid)" />
width={gridSizeW} height={gridSizeH} fill="url(#minor_grid)" />
<rect id="major-grid" transform={transformForQuadrant(mapTransformProps)}
width={gridSize.x} height={gridSize.y} fill="url(#major_grid)" />
<rect id="border" width={gridSize.x} height={gridSize.y} fill="none"
width={gridSizeW} height={gridSizeH} fill="url(#major_grid)" />
<rect id="border" width={gridSizeW} height={gridSizeH} fill="none"
stroke="rgba(0,0,0,0.3)" strokeWidth={2} />
</g>

View file

@ -1,13 +1,15 @@
import * as React from "react";
import { MapBackgroundProps } from "./interfaces";
import { Color } from "../../ui/index";
import { getMapSize } from "./util";
export function MapBackground(props: MapBackgroundProps) {
const { mapTransformProps, plantAreaOffset } = props;
const { gridSize } = mapTransformProps;
const { gridSize, xySwap } = mapTransformProps;
const gridSizeW = xySwap ? gridSize.y : gridSize.x;
const gridSizeH = xySwap ? gridSize.x : gridSize.y;
const boardWidth = 20;
const mapWidth = gridSize.x + plantAreaOffset.x * 2;
const mapHeight = gridSize.y + plantAreaOffset.y * 2;
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
return <g id="map-background">
<defs>
<pattern id="diagonalHatch"
@ -18,16 +20,16 @@ export function MapBackground(props: MapBackgroundProps) {
</defs>
<rect id="bed-border"
x={0} y={0} width={mapWidth} height={mapHeight}
x={0} y={0} width={mapSize.w} height={mapSize.h}
fill={Color.soilBackground} />
<rect id="bed-interior" x={boardWidth / 2} y={boardWidth / 2}
width={mapWidth - boardWidth} height={mapHeight - boardWidth}
width={mapSize.w - boardWidth} height={mapSize.h - boardWidth}
stroke="rgba(120,63,4,0.25)" strokeWidth={boardWidth}
fill={Color.soilBackground} />
<rect id="no-access-perimeter" x={boardWidth} y={boardWidth}
width={mapWidth - boardWidth * 2} height={mapHeight - boardWidth * 2}
width={mapSize.w - boardWidth * 2} height={mapSize.h - boardWidth * 2}
fill="url(#diagonalHatch)" />
<rect id="grid-fill" x={plantAreaOffset.x} y={plantAreaOffset.y}
width={gridSize.x} height={gridSize.y} fill={Color.gridSoil} />
width={gridSizeW} height={gridSizeH} fill={Color.gridSoil} />
</g>;
}

View file

@ -22,13 +22,16 @@ export interface ToolSlotGraphicProps {
y: number;
pulloutDirection: ToolPulloutDirection;
quadrant: BotOriginQuadrant;
xySwap: boolean;
}
const toolbaySlotAngle = (
pulloutDirection: ToolPulloutDirection,
quadrant: BotOriginQuadrant) => {
quadrant: BotOriginQuadrant,
xySwap: boolean) => {
const rawAngle = () => {
switch (pulloutDirection) {
const direction = pulloutDirection + (xySwap ? 2 : 0);
switch (direction > 4 ? direction % 4 : direction) {
case ToolPulloutDirection.POSITIVE_X: return 0;
case ToolPulloutDirection.NEGATIVE_X: return 180;
case ToolPulloutDirection.NEGATIVE_Y: return 90;
@ -56,8 +59,8 @@ export enum ToolNames {
}
export const ToolbaySlot = (props: ToolSlotGraphicProps) => {
const { id, x, y, pulloutDirection, quadrant } = props;
const angle = toolbaySlotAngle(pulloutDirection, quadrant);
const { id, x, y, pulloutDirection, quadrant, xySwap } = props;
const angle = toolbaySlotAngle(pulloutDirection, quadrant, xySwap);
return <g id={"toolbay-slot"}>
<defs>
<g id={"toolbay-slot-" + id}

View file

@ -12,9 +12,11 @@ enum Anchor {
export const textAnchorPosition = (
pulloutDirection: ToolPulloutDirection,
quadrant: BotOriginQuadrant): { x: number, y: number, anchor: string } => {
quadrant: BotOriginQuadrant,
xySwap: boolean): { x: number, y: number, anchor: string } => {
const rawAnchor = () => {
switch (pulloutDirection) {
const direction = pulloutDirection + (xySwap ? 2 : 0);
switch (direction > 4 ? direction % 4 : direction) {
case ToolPulloutDirection.POSITIVE_X: return Anchor.start;
case ToolPulloutDirection.NEGATIVE_X: return Anchor.end;
case ToolPulloutDirection.NEGATIVE_Y: return Anchor.middleTop;
@ -48,11 +50,12 @@ interface ToolLabelProps {
y: number;
pulloutDirection: ToolPulloutDirection;
quadrant: BotOriginQuadrant;
xySwap: boolean;
}
export const ToolLabel = (props: ToolLabelProps) => {
const { toolName, hovered, x, y, pulloutDirection, quadrant } = props;
const labelAnchor = textAnchorPosition(pulloutDirection, quadrant);
const { toolName, hovered, x, y, pulloutDirection, quadrant, xySwap } = props;
const labelAnchor = textAnchorPosition(pulloutDirection, quadrant, xySwap);
return <text textAnchor={labelAnchor.anchor}
visibility={hovered ? "visible" : "hidden"}

View file

@ -36,7 +36,7 @@ export class ToolSlotPoint extends
render() {
const { id, x, y, pullout_direction } = this.slot.toolSlot.body;
const { mapTransformProps } = this.props;
const { quadrant } = mapTransformProps;
const { quadrant, xySwap } = mapTransformProps;
const { qx, qy } = transformXY(x, y, this.props.mapTransformProps);
const toolName = this.slot.tool ? this.slot.tool.body.name : "no tool";
const toolProps = {
@ -52,7 +52,8 @@ export class ToolSlotPoint extends
x={qx}
y={qy}
pulloutDirection={pullout_direction}
quadrant={quadrant} />}
quadrant={quadrant}
xySwap={xySwap} />}
{(this.slot.tool || !pullout_direction) &&
<Tool
@ -65,7 +66,8 @@ export class ToolSlotPoint extends
x={qx}
y={qy}
pulloutDirection={pullout_direction}
quadrant={quadrant} />
quadrant={quadrant}
xySwap={xySwap} />
</g>;
}
}

View file

@ -44,7 +44,7 @@ export function round(num: number) {
* zoom level
* quadrant
* grid size
* XY swap (coming soon)
* XY swap
*
*/
@ -69,6 +69,7 @@ export function translateScreenToGarden(
params: ScreenToGardenParams
): XYCoordinate {
const { page, scroll, zoomLvl, mapTransformProps, gridOffset } = params;
const { xySwap } = mapTransformProps;
const screenXY = page;
const mapXY = ["x", "y"].reduce<XYCoordinate>(
(result: XYCoordinate, axis: "x" | "y") => {
@ -79,8 +80,11 @@ export function translateScreenToGarden(
result[axis] = unscaled;
return result;
}, { x: 0, y: 0 });
const gardenXY = quadTransform({ coordinate: mapXY, mapTransformProps });
return gardenXY;
const coordinate = xySwap ? { x: mapXY.y, y: mapXY.x } : mapXY;
const gardenXY = transformXY(coordinate.x, coordinate.y, mapTransformProps);
return xySwap
? { x: gardenXY.qy, y: gardenXY.qx }
: { x: gardenXY.qx, y: gardenXY.qy };
}
/* BotOriginQuadrant diagram
@ -112,7 +116,7 @@ interface QuadTransformParams {
/** Quadrant coordinate transformation */
function quadTransform(params: QuadTransformParams): XYCoordinate {
const { coordinate, mapTransformProps } = params;
const { quadrant, gridSize } = mapTransformProps;
const { gridSize, quadrant } = mapTransformProps;
if (isBotOriginQuadrant(quadrant)) {
return ["x", "y"].reduce<XYCoordinate>(
(result: XYCoordinate, axis: "x" | "y") => {
@ -146,9 +150,24 @@ function quadTransform(params: QuadTransformParams): XYCoordinate {
export function transformXY(
x: number,
y: number,
mapTransformProps: MapTransformProps
rawMapTransformProps: MapTransformProps
): { qx: number, qy: number } {
const transformed = quadTransform({ coordinate: { x, y }, mapTransformProps });
const { quadrant, gridSize, xySwap } = rawMapTransformProps;
const coordinate = {
x: xySwap ? y : x,
y: xySwap ? x : y,
};
const transformed = quadTransform({
coordinate,
mapTransformProps: {
quadrant,
gridSize: {
x: xySwap ? gridSize.y : gridSize.x,
y: xySwap ? gridSize.x : gridSize.y,
},
xySwap
}
});
return {
qx: transformed.x,
qy: transformed.y
@ -184,13 +203,18 @@ export function getBotSize(
/** Calculate map dimensions */
export function getMapSize(
gridSize: AxisNumberProperty,
mapTransformProps: MapTransformProps,
gridOffset: AxisNumberProperty
): AxisNumberProperty {
return {
): { w: number, h: number } {
const { gridSize, xySwap } = mapTransformProps;
const mapSize = {
x: gridSize.x + gridOffset.x * 2,
y: gridSize.y + gridOffset.y * 2
};
return {
w: xySwap ? mapSize.y : mapSize.x,
h: xySwap ? mapSize.x : mapSize.y
};
}
/** Transform object based on selected map quadrant and grid size. */

View file

@ -17,21 +17,23 @@ describe("<BotFigure/>", () => {
function checkPositionForQuadrant(
quadrant: BotOriginQuadrant,
xySwap: boolean,
expected: { x: number, y: number },
name: string,
opacity: number) {
it(`shows ${name} in correct location for quadrant ${quadrant}`, () => {
const p = fakeProps();
p.mapTransformProps.quadrant = quadrant;
p.mapTransformProps.xySwap = xySwap;
p.name = name;
const result = shallow(<BotFigure {...p} />);
const expectedGantryProps = expect.objectContaining({
id: "gantry",
x: expected.x - 10,
y: -100,
width: 20,
height: 1700,
x: xySwap ? -100 : expected.x - 10,
y: xySwap ? expected.x - 10 : -100,
width: xySwap ? 1700 : 20,
height: xySwap ? 20 : 1700,
fill: Color.darkGray,
fillOpacity: opacity
});
@ -40,8 +42,8 @@ describe("<BotFigure/>", () => {
const expectedUTMProps = expect.objectContaining({
id: "UTM",
cx: expected.x,
cy: expected.y,
cx: xySwap ? expected.y : expected.x,
cy: xySwap ? expected.x : expected.y,
r: 35,
fill: Color.darkGray,
fillOpacity: opacity
@ -51,11 +53,15 @@ describe("<BotFigure/>", () => {
});
}
checkPositionForQuadrant(1, { x: 3000, y: 0 }, "motors", 0.75);
checkPositionForQuadrant(2, { x: 0, y: 0 }, "motors", 0.75);
checkPositionForQuadrant(3, { x: 0, y: 1500 }, "motors", 0.75);
checkPositionForQuadrant(4, { x: 3000, y: 1500 }, "motors", 0.75);
checkPositionForQuadrant(2, { x: 0, y: 0 }, "encoders", 0.25);
checkPositionForQuadrant(1, false, { x: 3000, y: 0 }, "motors", 0.75);
checkPositionForQuadrant(2, false, { x: 0, y: 0 }, "motors", 0.75);
checkPositionForQuadrant(3, false, { x: 0, y: 1500 }, "motors", 0.75);
checkPositionForQuadrant(4, false, { x: 3000, y: 1500 }, "motors", 0.75);
checkPositionForQuadrant(1, true, { x: 0, y: 1500 }, "motors", 0.75);
checkPositionForQuadrant(2, true, { x: 0, y: 0 }, "motors", 0.75);
checkPositionForQuadrant(3, true, { x: 3000, y: 0 }, "motors", 0.75);
checkPositionForQuadrant(4, true, { x: 3000, y: 1500 }, "motors", 0.75);
checkPositionForQuadrant(2, false, { x: 0, y: 0 }, "encoders", 0.25);
it("changes location", () => {
const p = fakeProps();

View file

@ -59,6 +59,35 @@ describe("<BotPeripherals/>", () => {
fill: "url(#LightingGradient)",
height: 1700, width: 400, x: 0, y: -100
});
expect(wrapper.find("use").first().props()).toEqual({
xlinkHref: "#light-half",
transform: "rotate(0, 0, 750)"
});
expect(wrapper.find("use").last().props()).toEqual({
xlinkHref: "#light-half",
transform: "rotate(180, 0, 750)"
});
});
it("displays light: X&Y swapped", () => {
const p = fakeProps();
p.peripherals[0].label = "lights";
p.peripherals[0].value = true;
p.mapTransformProps.xySwap = true;
const wrapper = shallow(<BotPeripherals {...p} />);
expect(wrapper.find("#lights").length).toEqual(1);
expect(wrapper.find("rect").last().props()).toEqual({
fill: "url(#LightingGradient)",
height: 1700, width: 400, x: -100, y: 0
});
expect(wrapper.find("use").first().props()).toEqual({
xlinkHref: "#light-half",
transform: "rotate(90, 750, 850)"
});
expect(wrapper.find("use").last().props()).toEqual({
xlinkHref: "#light-half",
transform: "rotate(270, -100, 0)"
});
});
it("displays water", () => {

View file

@ -26,18 +26,18 @@ export class BotFigure extends
render() {
const { name, position, plantAreaOffset, eStopStatus, mapTransformProps
} = this.props;
const mapSize = getMapSize(mapTransformProps.gridSize, plantAreaOffset);
const { xySwap } = mapTransformProps;
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
const positionQ = transformXY(
(position.x || 0), (position.y || 0), mapTransformProps);
const color = eStopStatus ? Color.virtualRed : Color.darkGray;
const opacity = name.includes("encoder") ? 0.25 : 0.75;
return <g id={name}>
<rect id="gantry"
x={positionQ.qx - 10}
y={-plantAreaOffset.y
}
width={20}
height={mapSize.y}
x={xySwap ? -plantAreaOffset.x : positionQ.qx - 10}
y={xySwap ? positionQ.qy - 10 : -plantAreaOffset.y}
width={xySwap ? mapSize.w : 20}
height={xySwap ? 20 : mapSize.h}
fillOpacity={opacity}
fill={color} />
<circle id="UTM"

View file

@ -5,6 +5,7 @@ import { BotPosition } from "../../../devices/interfaces";
import * as _ from "lodash";
import { Session } from "../../../session";
import { BooleanSetting } from "../../../session_keys";
import { trim } from "../../../util";
export interface BotPeripheralsProps {
position: BotPosition;
@ -14,8 +15,8 @@ export interface BotPeripheralsProps {
}
function lightsFigure(
props: { i: number, x: number, y: number, height: number }) {
const { i, x, y, height } = props;
props: { i: number, x: number, y: number, height: number, xySwap: boolean }) {
const { i, x, y, height, xySwap } = props;
const mapHeightMid = height / 2 + y;
return <g id="lights" key={`peripheral_${i}`}>
<defs>
@ -34,9 +35,13 @@ function lightsFigure(
</defs>
<use xlinkHref="#light-half"
transform={`rotate(0, ${x}, ${mapHeightMid})`} />
transform={trim(`rotate(${xySwap ? 90 : 0},
${xySwap ? height / 2 + x : x},
${mapHeightMid})`)} />
<use xlinkHref="#light-half"
transform={`rotate(180, ${x}, ${mapHeightMid})`} />
transform={trim(`rotate(${xySwap ? 270 : 180},
${x},
${xySwap ? y : mapHeightMid})`)} />
</g>;
}
@ -117,7 +122,8 @@ function vacuumFigure(
export function BotPeripherals(props: BotPeripheralsProps) {
const { peripherals, position, plantAreaOffset, mapTransformProps } = props;
const mapSize = getMapSize(mapTransformProps.gridSize, plantAreaOffset);
const { xySwap } = mapTransformProps;
const mapSize = getMapSize(mapTransformProps, plantAreaOffset);
const positionQ = transformXY(
(position.x || 0), (position.y || 0), mapTransformProps);
@ -126,9 +132,10 @@ export function BotPeripherals(props: BotPeripheralsProps) {
if (x.label.toLowerCase().includes("light") && x.value) {
return lightsFigure({
i,
x: positionQ.qx,
y: -plantAreaOffset.y,
height: mapSize.y
x: xySwap ? -plantAreaOffset.y : positionQ.qx,
y: xySwap ? positionQ.qy : -plantAreaOffset.y,
height: xySwap ? mapSize.w : mapSize.h,
xySwap,
});
} else if (x.label.toLowerCase().includes("water") && x.value) {
return waterFigure({