Merged PR 175: Implement drag drop

- [x] Implement drag drop to create an element at a specific index
- Add Swap behavrior
- Implement max contraints with simplex
~~- [ ] Implement drag drop to swap two container that flex~~

- Fixes tries number for simplex it can now go up to 2 * number of containers
- Fixes flex calling another flex behavior when not needed (remember that flex behavior is the only behavior that needs to communicate with siblings)
- Fix max width being ignored in input group
This commit is contained in:
Eric Nguyen 2022-09-05 07:56:45 +00:00
parent 4d4ecd67d0
commit 353f461f4b
13 changed files with 220 additions and 59 deletions

View file

@ -119,6 +119,7 @@ export function ContainerForm(props: IContainerFormProps): JSX.Element {
inputClassName='' inputClassName=''
type='number' type='number'
min={props.properties.minWidth} min={props.properties.minWidth}
max={props.properties.maxWidth}
value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()} value={(RemoveWidthMargin(props.properties.width, props.properties.margin.left, props.properties.margin.right)).toString()}
onChange={(event) => props.onChange('width', ApplyWidthMargin(Number(event.target.value), props.properties.margin.left, props.properties.margin.right))} onChange={(event) => props.onChange('width', ApplyWidthMargin(Number(event.target.value), props.properties.margin.left, props.properties.margin.right))}
isDisabled={props.properties.isFlex} /> isDisabled={props.properties.isFlex} />

View file

@ -6,7 +6,7 @@ import { GetCurrentHistory, UpdateCounters } from '../Editor';
import { AddMethod } from '../../../Enums/AddMethod'; import { AddMethod } from '../../../Enums/AddMethod';
import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer'; import { IAvailableContainer } from '../../../Interfaces/IAvailableContainer';
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../../utils/default'; import { GetDefaultContainerProps, DEFAULTCHILDTYPE_ALLOW_CYCLIC, DEFAULTCHILDTYPE_MAX_DEPTH } from '../../../utils/default';
import { ApplyBehaviors, ApplyBehaviorsOnSiblings } from '../Behaviors/Behaviors'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel'; import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import Swal from 'sweetalert2'; import Swal from 'sweetalert2';
import { ApplyMargin, TransformX } from '../../../utils/svg'; import { ApplyMargin, TransformX } from '../../../utils/svg';
@ -83,7 +83,7 @@ export function DeleteContainer(
throw new Error('[DeleteContainer] Could not find container among parent\'s children'); throw new Error('[DeleteContainer] Could not find container among parent\'s children');
} }
ApplyBehaviorsOnSiblings(container, current.symbols); ApplyBehaviorsOnSiblingsChildren(container, current.symbols);
// Select the previous container // Select the previous container
// or select the one above // or select the one above
@ -285,7 +285,7 @@ export function AddContainers(
ApplyBehaviors(newContainer, current.symbols); ApplyBehaviors(newContainer, current.symbols);
// Then, apply the behaviors on its siblings (mostly for flex) // Then, apply the behaviors on its siblings (mostly for flex)
ApplyBehaviorsOnSiblings(newContainer, current.symbols); ApplyBehaviorsOnSiblingsChildren(newContainer, current.symbols);
// Sort the parent children by x // Sort the parent children by x
UpdateParentChildrenList(parentClone); UpdateParentChildrenList(parentClone);
@ -547,7 +547,7 @@ function SetContainer(
ApplyBehaviors(container, symbols); ApplyBehaviors(container, symbols);
// Apply special behaviors on siblings // Apply special behaviors on siblings
ApplyBehaviorsOnSiblings(container, symbols); ApplyBehaviorsOnSiblingsChildren(container, symbols);
// sort the children list by their position // sort the children list by their position
UpdateParentChildrenList(container.parent); UpdateParentChildrenList(container.parent);

View file

@ -5,7 +5,7 @@ import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { GetDefaultSymbolModel } from '../../../utils/default'; import { GetDefaultSymbolModel } from '../../../utils/default';
import { FindContainerById } from '../../../utils/itertools'; import { FindContainerById } from '../../../utils/itertools';
import { RestoreX } from '../../../utils/svg'; import { RestoreX } from '../../../utils/svg';
import { ApplyBehaviors, ApplyBehaviorsOnSiblings } from '../Behaviors/Behaviors'; import { ApplyBehaviors, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
import { GetCurrentHistory, UpdateCounters } from '../Editor'; import { GetCurrentHistory, UpdateCounters } from '../Editor';
export function AddSymbol( export function AddSymbol(
@ -150,7 +150,7 @@ export function OnPropertyChange(
ApplyBehaviors(container, newSymbols); ApplyBehaviors(container, newSymbols);
ApplyBehaviorsOnSiblings(container, newSymbols); ApplyBehaviorsOnSiblingsChildren(container, newSymbols);
}); });
history.push({ history.push({

View file

@ -44,7 +44,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
* @param containers A list of containers * @param containers A list of containers
* @returns A list of overlapping containers * @returns A list of overlapping containers
*/ */
function GetOverlappingContainers( export function GetOverlappingContainers(
container: IContainerModel, container: IContainerModel,
containers: IContainerModel[] containers: IContainerModel[]
): IContainerModel[] { ): IContainerModel[] {

View file

@ -4,6 +4,7 @@ import { APPLY_BEHAVIORS_ON_CHILDREN } from '../../../utils/default';
import { ApplyAnchor } from './AnchorBehaviors'; import { ApplyAnchor } from './AnchorBehaviors';
import { Flex } from './FlexBehaviors'; import { Flex } from './FlexBehaviors';
import { ApplyRigidBody } from './RigidBodyBehaviors'; import { ApplyRigidBody } from './RigidBodyBehaviors';
import { ApplySwap } from './SwapBehaviors';
import { ApplySymbol } from './SymbolBehaviors'; import { ApplySymbol } from './SymbolBehaviors';
/** /**
@ -13,28 +14,26 @@ import { ApplySymbol } from './SymbolBehaviors';
* @returns Updated container * @returns Updated container
*/ */
export function ApplyBehaviors(container: IContainerModel, symbols: Map<string, ISymbolModel>): IContainerModel { export function ApplyBehaviors(container: IContainerModel, symbols: Map<string, ISymbolModel>): IContainerModel {
try { const symbol = symbols.get(container.properties.linkedSymbolId);
const symbol = symbols.get(container.properties.linkedSymbolId); if (container.properties.linkedSymbolId !== '' && symbol !== undefined) {
if (container.properties.linkedSymbolId !== '' && symbol !== undefined) { ApplySymbol(container, symbol);
ApplySymbol(container, symbol); }
if (container.properties.isAnchor) {
ApplyAnchor(container);
}
ApplySwap(container);
Flex(container);
ApplyRigidBody(container);
if (APPLY_BEHAVIORS_ON_CHILDREN) {
// Apply DFS by recursion
for (const child of container.children) {
ApplyBehaviors(child, symbols);
} }
if (container.properties.isAnchor) {
ApplyAnchor(container);
}
Flex(container);
ApplyRigidBody(container);
if (APPLY_BEHAVIORS_ON_CHILDREN) {
// Apply DFS by recursion
for (const child of container.children) {
ApplyBehaviors(child, symbols);
}
}
} catch (error) {
console.debug(error);
} }
return container; return container;
@ -46,7 +45,7 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
* @param symbols * @param symbols
* @returns * @returns
*/ */
export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols: Map<string, ISymbolModel>): void { export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel, symbols: Map<string, ISymbolModel>): void {
if (newContainer.parent === null || newContainer.parent === undefined) { if (newContainer.parent === null || newContainer.parent === undefined) {
return; return;
} }
@ -57,6 +56,8 @@ export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols:
return; return;
} }
ApplyBehaviors(container, symbols); for (const child of container.children) {
ApplyBehaviors(child, symbols);
}
}); });
} }

View file

@ -1,3 +1,4 @@
import Swal from 'sweetalert2';
import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Simplex } from '../../../utils/simplex'; import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg'; import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
@ -49,16 +50,16 @@ function FlexGroup(flexibleGroup: IFlexibleGroup): void {
const checkSumMinWidthsIsFitting = minimumPossibleWidth > requiredMaxWidth; const checkSumMinWidthsIsFitting = minimumPossibleWidth > requiredMaxWidth;
if (checkSumMinWidthsIsFitting) { if (checkSumMinWidthsIsFitting) {
// Swal.fire({ Swal.fire({
// icon: 'error', icon: 'error',
// title: 'Cannot fit!', title: 'Cannot fit!',
// text: 'Cannot fit at all even when squeezing all flex containers to the minimum.' text: 'Cannot fit at all even when squeezing all flex containers to the minimum.'
// }); });
throw new Error('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.'); throw new Error('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.');
} }
const maxMinWidths = Math.max(...minWidths); const maxMinWidths = Math.max(...minWidths);
if (maxMinWidths * minWidths.length < requiredMaxWidth) { if (maxMinWidths * minWidths.length <= requiredMaxWidth) {
const wantedWidth = requiredMaxWidth / minWidths.length; const wantedWidth = requiredMaxWidth / minWidths.length;
// it fits, flex with maxMinWidths and fixed width // it fits, flex with maxMinWidths and fixed width
let right = flexibleGroup.offset; let right = flexibleGroup.offset;
@ -79,7 +80,9 @@ function FlexGroup(flexibleGroup: IFlexibleGroup): void {
// does not fit // does not fit
/// SIMPLEX /// /// SIMPLEX ///
const solutions: number[] = Simplex(minWidths, requiredMaxWidth); const maxWidths = flexibleContainers
.map(sibling => sibling.properties.maxWidth);
const solutions: number[] = Simplex(minWidths, maxWidths, requiredMaxWidth);
// apply the solutions // apply the solutions
for (let i = 0; i < flexibleContainers.length; i++) { for (let i = 0; i < flexibleContainers.length; i++) {

View file

@ -0,0 +1,31 @@
/**
* Swap two flex container when one is overlapping another
*/
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { GetOverlappingContainers } from './AnchorBehaviors';
export function ApplySwap(container: IContainerModel): void {
if (container.parent === null || container.parent === undefined) {
return;
}
const children = container.parent.children;
const overlappingContainers = GetOverlappingContainers(container, children);
if (overlappingContainers.length > 1 || overlappingContainers.length === 0) {
return;
}
const overlappingContainer = overlappingContainers.pop();
if (overlappingContainer === null || overlappingContainer === undefined) {
return;
}
// swap positions
[overlappingContainer.properties.x, container.properties.x] = [container.properties.x, overlappingContainer.properties.x];
const indexContainer = children.indexOf(container);
const indexOverlapping = children.indexOf(overlappingContainer);
[children[indexContainer], children[indexOverlapping]] = [children[indexOverlapping], children[indexContainer]];
}

View file

@ -4,7 +4,7 @@ import { IConfiguration } from '../../Interfaces/IConfiguration';
import { SVG } from '../SVG/SVG'; import { SVG } from '../SVG/SVG';
import { IHistoryState } from '../../Interfaces/IHistoryState'; import { IHistoryState } from '../../Interfaces/IHistoryState';
import { UI } from '../UI/UI'; import { UI } from '../UI/UI';
import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange, AddContainers } from './Actions/ContainerOperations'; import { SelectContainer, DeleteContainer, AddContainerToSelectedContainer, OnPropertyChange, AddContainer } from './Actions/ContainerOperations';
import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save'; import { SaveEditorAsJSON, SaveEditorAsSVG } from './Actions/Save';
import { OnKey } from './Actions/Shortcuts'; import { OnKey } from './Actions/Shortcuts';
import { events as EVENTS } from '../../Events/EditorEvents'; import { events as EVENTS } from '../../Events/EditorEvents';
@ -240,6 +240,16 @@ export function Editor(props: IEditorProps): JSX.Element {
setNewHistory(newHistory); setNewHistory(newHistory);
} }
}} }}
addContainerAt={(index, type, parent) => setNewHistory(
AddContainer(
index,
type,
parent,
configuration,
history,
historyCurrentStep
)
)}
addSymbol={(type) => setNewHistory( addSymbol={(type) => setNewHistory(
AddSymbol( AddSymbol(
type, type,

View file

@ -22,6 +22,7 @@ describe.concurrent('Elements sidebar', () => {
selectedContainer={undefined} selectedContainer={undefined}
onPropertyChange={() => {}} onPropertyChange={() => {}}
selectContainer={() => {}} selectContainer={() => {}}
addContainer={() => {}}
/>); />);
expect(screen.getByText(/Elements/i)); expect(screen.getByText(/Elements/i));
@ -45,6 +46,7 @@ describe.concurrent('Elements sidebar', () => {
selectedContainer={mainContainer} selectedContainer={mainContainer}
onPropertyChange={() => {}} onPropertyChange={() => {}}
selectContainer={() => {}} selectContainer={() => {}}
addContainer={() => {}}
/>); />);
expect(screen.getByText(/Elements/i)); expect(screen.getByText(/Elements/i));
@ -149,6 +151,7 @@ describe.concurrent('Elements sidebar', () => {
selectedContainer={mainContainer} selectedContainer={mainContainer}
onPropertyChange={() => {}} onPropertyChange={() => {}}
selectContainer={() => {}} selectContainer={() => {}}
addContainer={() => {}}
/>); />);
expect(screen.getByText(/Elements/i)); expect(screen.getByText(/Elements/i));
@ -208,6 +211,7 @@ describe.concurrent('Elements sidebar', () => {
selectedContainer={selectedContainer} selectedContainer={selectedContainer}
onPropertyChange={() => {}} onPropertyChange={() => {}}
selectContainer={selectContainer} selectContainer={selectContainer}
addContainer={() => {}}
/>); />);
expect(screen.getByText(/Elements/i)); expect(screen.getByText(/Elements/i));
@ -230,6 +234,7 @@ describe.concurrent('Elements sidebar', () => {
selectedContainer={selectedContainer} selectedContainer={selectedContainer}
onPropertyChange={() => {}} onPropertyChange={() => {}}
selectContainer={selectContainer} selectContainer={selectContainer}
addContainer={() => {}}
/>); />);
expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy(); expect((propertyId as HTMLInputElement).value === 'main').toBeFalsy();

View file

@ -2,7 +2,7 @@ import * as React from 'react';
import { FixedSizeList as List } from 'react-window'; import { FixedSizeList as List } from 'react-window';
import { Properties } from '../ContainerProperties/ContainerProperties'; import { Properties } from '../ContainerProperties/ContainerProperties';
import { IContainerModel } from '../../Interfaces/IContainerModel'; import { IContainerModel } from '../../Interfaces/IContainerModel';
import { GetDepth, MakeIterator } from '../../utils/itertools'; import { FindContainerById, GetDepth, MakeIterator } from '../../utils/itertools';
import { ISymbolModel } from '../../Interfaces/ISymbolModel'; import { ISymbolModel } from '../../Interfaces/ISymbolModel';
import { PropertyType } from '../../Enums/PropertyType'; import { PropertyType } from '../../Enums/PropertyType';
@ -18,6 +18,102 @@ interface IElementsSidebarProps {
type?: PropertyType type?: PropertyType
) => void ) => void
selectContainer: (containerId: string) => void selectContainer: (containerId: string) => void
addContainer: (index: number, type: string, parent: string) => void
}
function RemoveBorderClasses(target: HTMLButtonElement, exception: string = ''): void {
const bordersClasses = ['border-t-8', 'border-8', 'border-b-8'].filter(className => className !== exception);
target.classList.remove(...bordersClasses);
}
function HandleDragLeave(event: React.DragEvent): void {
const target: HTMLButtonElement = event.target as HTMLButtonElement;
RemoveBorderClasses(target);
}
function HandleDragOver(
event: React.DragEvent,
mainContainer: IContainerModel
): void {
event.preventDefault();
const target: HTMLButtonElement = event.target as HTMLButtonElement;
const rect = target.getBoundingClientRect();
const y = event.clientY - rect.top; // y position within the element.
if (target.id === mainContainer.properties.id) {
target.classList.add('border-8');
return;
}
if (y < 12) {
RemoveBorderClasses(target, 'border-t-8');
target.classList.add('border-t-8');
} else if (y < 24) {
RemoveBorderClasses(target, 'border-8');
target.classList.add('border-8');
} else {
RemoveBorderClasses(target, 'border-b-8');
target.classList.add('border-b-8');
}
}
function HandleOnDrop(
event: React.DragEvent,
mainContainer: IContainerModel,
addContainer: (index: number, type: string, parent: string) => void
): void {
event.preventDefault();
const type = event.dataTransfer.getData('type');
const target: HTMLButtonElement = event.target as HTMLButtonElement;
RemoveBorderClasses(target);
const targetContainer: IContainerModel | undefined = FindContainerById(
mainContainer,
target.id
);
if (targetContainer === undefined) {
throw new Error('[handleOnDrop] Tried to drop onto a unknown container!');
}
if (targetContainer === mainContainer) {
// if the container is the root, only add type as child
addContainer(
targetContainer.children.length,
type,
targetContainer.properties.id);
return;
}
if (targetContainer.parent === null ||
targetContainer.parent === undefined) {
throw new Error('[handleDrop] Tried to drop into a child container without a parent!');
}
const rect = target.getBoundingClientRect();
const y = event.clientY - rect.top; // y position within the element.
// locate the hitboxes
if (y < 12) {
const index = targetContainer.parent.children.indexOf(targetContainer);
addContainer(
index,
type,
targetContainer.parent.properties.id
);
} else if (y < 24) {
addContainer(
targetContainer.children.length,
type,
targetContainer.properties.id);
} else {
const index = targetContainer.parent.children.indexOf(targetContainer);
addContainer(
index + 1,
type,
targetContainer.parent.properties.id
);
}
} }
export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element { export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
@ -55,6 +151,9 @@ export function ElementsSidebar(props: IElementsSidebarProps): JSX.Element {
key={key} key={key}
style={style} style={style}
onClick={() => props.selectContainer(container.properties.id)} onClick={() => props.selectContainer(container.properties.id)}
onDrop={(event) => HandleOnDrop(event, props.mainContainer, props.addContainer)}
onDragOver={(event) => HandleDragOver(event, props.mainContainer)}
onDragLeave={(event) => HandleDragLeave(event)}
> >
{text} {text}
</button> </button>

View file

@ -12,6 +12,7 @@ interface IInputGroupProps {
defaultValue?: string defaultValue?: string
defaultChecked?: boolean defaultChecked?: boolean
min?: number min?: number
max?: number
isDisabled?: boolean isDisabled?: boolean
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
} }
@ -44,6 +45,7 @@ export function InputGroup(props: IInputGroupProps): JSX.Element {
defaultChecked={props.defaultChecked} defaultChecked={props.defaultChecked}
onChange={props.onChange} onChange={props.onChange}
min={props.min} min={props.min}
max={props.max}
disabled={props.isDisabled} /> disabled={props.isDisabled} />
</>; </>;
} }

View file

@ -24,6 +24,7 @@ interface IUIProps {
deleteContainer: (containerId: string) => void deleteContainer: (containerId: string) => void
onPropertyChange: (key: string, value: string | number | boolean, type?: PropertyType) => void onPropertyChange: (key: string, value: string | number | boolean, type?: PropertyType) => void
addContainer: (type: string) => void addContainer: (type: string) => void
addContainerAt: (index: number, type: string, parent: string) => void
addSymbol: (type: string) => void addSymbol: (type: string) => void
onSymbolPropertyChange: (key: string, value: string | number | boolean) => void onSymbolPropertyChange: (key: string, value: string | number | boolean) => void
selectSymbol: (symbolId: string) => void selectSymbol: (symbolId: string) => void
@ -87,6 +88,7 @@ export function UI(props: IUIProps): JSX.Element {
isHistoryOpen={isHistoryOpen} isHistoryOpen={isHistoryOpen}
onPropertyChange={props.onPropertyChange} onPropertyChange={props.onPropertyChange}
selectContainer={props.selectContainer} selectContainer={props.selectContainer}
addContainer={props.addContainerAt}
/> />
<SymbolsSidebar <SymbolsSidebar
selectedSymbolId={props.current.selectedSymbolId} selectedSymbolId={props.current.selectedSymbolId}

View file

@ -17,20 +17,22 @@
* @param requiredMaxWidth * @param requiredMaxWidth
* @returns * @returns
*/ */
export function Simplex(minWidths: number[], requiredMaxWidth: number): number[] { export function Simplex(minWidths: number[], maxWidths: number[], requiredMaxWidth: number): number[] {
/// 1) standardized the equations /// 1) standardized the equations
// add the min widths constraints // add the min widths constraints
const constraints = minWidths.map(minWidth => minWidth * -1); const constraints = minWidths.map(minWidth => minWidth * -1);
// add the max widths constraint
constraints.push(requiredMaxWidth);
/// 2) Create the initial matrix /// 2) Create the initial matrix
// get row length (nVariables + nConstraints + 1 (z) + 1 (b)) // get row length (nVariables + nConstraints + 1 (z) + 1 (b))
const nVariables = minWidths.length; const nVariables = minWidths.length;
const nConstraints = constraints.length; const nConstraints = constraints.length;
const rowlength = nVariables + nConstraints + 2; const rowlength =
const matrix = GetInitialMatrix(constraints, rowlength, nVariables); minWidths.length + // min constraints
maxWidths.length + // max constraints
nConstraints + 1 + // slack variables
1 + // z
1; // b
const matrix = GetInitialMatrix(constraints, maxWidths, requiredMaxWidth, rowlength);
/// Apply the algorithm /// Apply the algorithm
const finalMatrix = ApplyMainLoop(matrix, rowlength); const finalMatrix = ApplyMainLoop(matrix, rowlength);
@ -40,32 +42,35 @@ export function Simplex(minWidths: number[], requiredMaxWidth: number): number[]
return solutions; return solutions;
} }
const MAX_TRIES = 10;
/** /**
* Specific to min widths algorithm * Specific to min widths algorithm
* Get the initial matrix from the maximum constraints * Get the initial matrix from the maximum constraints
* and the number of variables * and the number of variables
* @param maximumConstraints * @param minConstraints
* @param rowlength * @param rowlength
* @param nVariables * @param nVariables
* @returns * @returns
*/ */
function GetInitialMatrix( function GetInitialMatrix(
maximumConstraints: number[], minConstraints: number[],
rowlength: number, maxConstraints: number[],
nVariables: number objectiveConstraint: number,
rowlength: number
): number[][] { ): number[][] {
const nConstraints = maximumConstraints.length; const nVariables = maxConstraints.length;
const matrix = maximumConstraints.map((maximumConstraint, index) => { const constraints = minConstraints.concat(maxConstraints);
constraints.push(objectiveConstraint);
const matrix = constraints.map((constraint, index) => {
const row: number[] = Array(rowlength).fill(0); const row: number[] = Array(rowlength).fill(0);
// insert the variable coefficient a of a*x // insert the variable coefficient a of a*x
if (index <= nConstraints - 2) { if (index < nVariables) {
// insert the the variable coefficient of the minimum widths constraints (negative identity matrix) // insert the the variable coefficient of the minimum/maximum widths constraints (negative identity matrix)
row[index] = -1; row[index] = -1;
} else if (index < (2 * nVariables)) {
row[index - (nVariables)] = 1;
} else { } else {
// insert the the variable coefficient of the maximum width constraint // insert the the variable coefficient of the maximum desired width constraint
row.fill(1, 0, nVariables); row.fill(1, 0, nVariables);
} }
@ -73,7 +78,7 @@ function GetInitialMatrix(
row[index + nVariables] = 1; row[index + nVariables] = 1;
// insert the constraint coefficient (b) // insert the constraint coefficient (b)
row[rowlength - 1] = maximumConstraint; row[rowlength - 1] = constraint;
return row; return row;
}); });
@ -119,7 +124,8 @@ function GetAllIndexes(arr: number[], val: number): number[] {
*/ */
function ApplyMainLoop(oldMatrix: number[][], rowlength: number): number[][] { function ApplyMainLoop(oldMatrix: number[][], rowlength: number): number[][] {
let matrix = oldMatrix; let matrix = oldMatrix;
let tries = MAX_TRIES; const maxTries = oldMatrix.length * 2;
let tries = maxTries;
const indexesTried: Record<number, number> = {}; const indexesTried: Record<number, number> = {};
while (matrix[matrix.length - 1].some((v: number) => v < 0) && tries > 0) { while (matrix[matrix.length - 1].some((v: number) => v < 0) && tries > 0) {
// 1) find the index with smallest coefficient (O(n)+) // 1) find the index with smallest coefficient (O(n)+)
@ -183,9 +189,10 @@ function ApplyMainLoop(oldMatrix: number[][], rowlength: number): number[][] {
} }
if (tries === 0) { if (tries === 0) {
console.table(matrix);
throw new Error('[Flex] Simplexe: Could not find a solution'); throw new Error('[Flex] Simplexe: Could not find a solution');
} }
console.debug(`Simplex was solved in ${maxTries - tries} tries`);
return matrix; return matrix;
} }