Last updated

Layouts

In GeoToolkitJS, any shape or node is located in the model coordinates of its parent node. Use one of two strategies to place shapes on the canvas. Firstly, Transformations can be set directly on the Groups in the scene graph. See the Transforms tutorial for more information. Secondly, use a Layout. Using a Layout is probably easier.

The Layout class provides the base class for all layouts in the GeoToolkitJS. This class defines an API for placing children groups: the method layout. This method computes the layouts for the children groups (sets the bounds on the groups). The 'setLayout' method in geotoolit/scene/Group informs the group to use the given layout to place the group's children.

# Box Layout

Box layout lines up child nodes horizontally or vertically. It has two implementations: HorizontalBoxLayout and VerticalBoxLayout.

# Horizontal

The canvas below demonstrates the horizontal layout.

import { HorizontalBoxLayout } from "@int/geotoolkit/layout/HorizontalBoxLayout.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { addRectToGroup, colors } from "/src/code/Carnac/Groups/Layouts/utils.ts";
function createScene(canvas) {
  const group1 = new Group().setModelLimits(new Rect(0, 0, 100, 100)).setDesiredWidth("50%");
  const group2 = new Group().setModelLimits(new Rect(0, 0, 100, 100)).setDesiredWidth("80");
  const group3 = new Group().setModelLimits(new Rect(0, 0, 100, 100));
  const parentGroup = new Group().addChild([
    group1,
    group2,
    group3
  ]);
  new HorizontalBoxLayout(new Rect(20, 20, 330, 230)).add([group1, group2, group3]).layout();
  for (let i = 0; i < parentGroup.getChildrenCount(); i++) {
    addRectToGroup(parentGroup.getChild(i), colors[i], i);
  }
  return new Plot({
    "canvaselement": canvas,
    "root": parentGroup
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Vertical

The canvas below demonstrates the vertical layout.

import { VerticalBoxLayout } from "@int/geotoolkit/layout/VerticalBoxLayout.ts";
import { Alignment } from "@int/geotoolkit/layout/BoxLayout.ts";
import { Rectangle } from "@int/geotoolkit/scene/shapes/Rectangle.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { addRectToGroup, colors } from "/src/code/Carnac/Groups/Layouts/utils.ts";
function setupGroupsWithLayout(numGroups, layout, parentGroup) {
  const groups = [];
  for (let i = 0; i < numGroups; i++) {
    const group1 = new Group().setModelLimits(new Rect(0, 0, 100, 100));
    groups.push(group1);
  }
  parentGroup.addChild(groups);
  layout.add(groups);
  return groups;
}
function createScene(canvas) {
  const numberOfGroups = 5;
  const all = new Group().setBounds(new Rect(0, 0, 350, 250)).setModelLimits(new Rect(0, 0, 350, 250));
  const layout = new VerticalBoxLayout(new Rect(30, 30, 330, 230), Alignment.Center);
  const groups = setupGroupsWithLayout(numberOfGroups, layout, all);
  all.addChild(new Rectangle({
    "left": 30,
    "top": 30,
    "right": 330,
    "bottom": 230,
    "linestyle": { color: "black", width: 1 }
  }));
  groups[1].setLayoutStyle({ left: 10 });
  groups[2].setLayoutStyle({ left: "70%" });
  for (let i = 0; i < numberOfGroups; i++) {
    groups[i].setDesiredWidth(i * 10 + "%");
    addRectToGroup(groups[i], colors[i], i);
  }
  layout.layout();
  return new Plot({
    "canvaselement": canvas,
    "root": all
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Regular Grid Layout

Grid layout lines up child nodes in the simple NxM grid, whereN is a count of rows and M is a count of columns. It has two implementations: Vertical direction and Horizontal direction.

# Horizontal

The canvas below demonstrates the horizontal direction of Grid layout.

import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { ContainerDirection, RegularGridLayout } from "@int/geotoolkit/layout/RegularGridLayout.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { addRectToGroup, colors } from "/src/code/Carnac/Groups/Layouts/utils.ts";
function createScene(canvas) {
  const numberOfGroups = 6;
  const all = new Group().setBounds(new Rect(20, 20, 330, 230)).setModelLimits(new Rect(0, 0, 350, 250));
  const layout = new RegularGridLayout({
    "rowcount": 3,
    "columncount": 2,
    "direction": ContainerDirection.Horizontal,
    "rect": new Rect(20, 20, 330, 230)
  });
  const groups = [];
  for (let i = 0; i < numberOfGroups; i++) {
    const newGroup = new Group().setModelLimits(new Rect(0, 0, 100, 100));
    groups.push(newGroup);
    layout.add(newGroup);
  }
  all.addChild(groups);
  layout.layout();
  for (let j = 0; j < numberOfGroups; j++) {
    addRectToGroup(groups[j], colors[j], j);
  }
  return new Plot({
    "canvaselement": canvas,
    "root": all
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Vertical

The canvas below demonstrates a vertical direction implementation of Grid layout.

import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { ContainerDirection, RegularGridLayout } from "@int/geotoolkit/layout/RegularGridLayout.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { addRectToGroup, colors } from "/src/code/Carnac/Groups/Layouts/utils.ts";
function createScene(canvas) {
  const numberOfGroups = 6;
  const all = new Group().setBounds(new Rect(20, 20, 330, 230)).setModelLimits(new Rect(0, 0, 350, 250));
  const layout = new RegularGridLayout({
    "rowcount": 3,
    "columncount": 2,
    "direction": ContainerDirection.Vertical,
    "rect": new Rect(20, 20, 330, 230)
  });
  const groups = [];
  for (let i = 0; i < numberOfGroups; i++) {
    let newGroup = null;
    newGroup = new Group().setModelLimits(new Rect(0, 0, 100, 100));
    layout.add(newGroup);
    groups.push(newGroup);
  }
  all.addChild(groups);
  layout.layout();
  for (let j = 0; j < numberOfGroups; j++) {
    addRectToGroup(groups[j], colors[j], j);
  }
  return new Plot({
    "canvaselement": canvas,
    "root": all
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# CSS Layout

CSS layout allows positioning and sizing of child nodes using left, right, top, bottom, width and height attributes.

import { TextStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { Text } from "@int/geotoolkit/scene/shapes/Text.ts";
import { AnchorType } from "@int/geotoolkit/util/AnchorType.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { CssLayout } from "@int/geotoolkit/layout/CssLayout.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
function createScene(canvas) {
  const textStyle = new TextStyle({ "color": KnownColors.Black, "font": "12px Arial" });
  const t1 = new Text({
    text: "left: 10",
    ax: 0.05,
    ay: 0.5,
    textstyle: textStyle,
    alignment: AnchorType.Center
  }).rotate(-Math.PI / 2, 0.05, 0.5);
  const t2 = new Text({
    text: "top: 10",
    ax: 0.5,
    ay: 0.05,
    textstyle: textStyle,
    alignment: AnchorType.Center
  });
  const t3 = new Text({
    text: "right: 200",
    ax: 0.95,
    ay: 0.5,
    textstyle: textStyle,
    alignment: AnchorType.Center
  }).rotate(-Math.PI / 2, 0.95, 0.5);
  const t4 = new Text({
    text: "bottom: 100",
    ax: 0.5,
    ay: 0.95,
    textstyle: textStyle,
    alignment: AnchorType.Center
  });
  const blueGroup = new Group().setFillStyle(KnownColors.Blue).setLayoutStyle({ left: 10, top: 10, right: 200, bottom: 100 }).setModelLimits(new Rect(0, 0, 1, 1)).addChild([t1, t2, t3, t4]);
  const t5 = new Text({
    text: "left: 180",
    ax: 0.05,
    ay: 0.5,
    textstyle: textStyle,
    alignment: AnchorType.Center
  });
  t5.rotate(-Math.PI / 2, 0.05, 0.5);
  const t6 = new Text({
    text: "top: 40",
    ax: 0.5,
    ay: 0.05,
    textstyle: textStyle,
    alignment: AnchorType.Center
  });
  const t7 = new Text({
    text: "width: 150",
    ax: 0.5,
    ay: 0.45,
    textstyle: textStyle,
    alignment: AnchorType.Center
  });
  const t8 = new Text({
    text: "height: 200",
    ax: 0.5,
    ay: 0.55,
    textstyle: textStyle,
    alignment: AnchorType.Center
  });
  const redGroup = new Group().setFillStyle(KnownColors.Red).setLayoutStyle({ left: 180, top: 40, width: 150, height: 200 }).setModelLimits(new Rect(0, 0, 1, 1)).addChild([t5, t6, t7, t8]);
  const rootGroup = new Group().setLayout(new CssLayout()).setFillStyle(KnownColors.LightGray).addChild([
    blueGroup,
    redGroup
  ]);
  return new Plot({
    "canvaselement": canvas,
    "root": rootGroup
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));

# Flexbox Layout

The Flexible Box Layout, makes it easier to design flexible responsive layout structure without using explicit position of an element. For more details, see CSS Flexbox Layout module.

# Playground

The playground below demonstrates the main functionality of Flexible Box Layout. Use +/- buttons to add/remove flex items. Click flex item to edit its layout style.

import { TextStyle } from "@int/geotoolkit/attributes/TextStyle.ts";
import { Text } from "@int/geotoolkit/scene/shapes/Text.ts";
import { AnchorType } from "@int/geotoolkit/util/AnchorType.ts";
import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { ColorUtil, KnownColors } from "@int/geotoolkit/util/ColorUtil.ts";
import { FlexboxLayout, FlexDirection, FlexWrap } from "@int/geotoolkit/layout/FlexboxLayout.ts";
import { LayoutableItem } from "/src/code/Carnac/Groups/Layouts/ts/LayoutableItem.ts";
import { init } from "@int/geotoolkit/base.js";
import { Tools } from "@int/geotoolkit/controls/tools/Tools.ts";
import { Selection } from "@int/geotoolkit/controls/tools/Selection.ts";
init({ "imports": [Tools] });
export function createLayoutableItem(width, height, name, fillColor, highlightMargins, marginsStyle) {
  marginsStyle = marginsStyle ?? {
    left: 5,
    top: 5,
    right: 5,
    bottom: 5
  };
  highlightMargins = highlightMargins !== void 0 ? highlightMargins : true;
  const textStyle = new TextStyle({ "color": KnownColors.Black, "font": "12px Arial" });
  return new LayoutableItem().setId("flexItem").setName(name).setFillStyle(fillColor).setLayoutStyle({ width, minheight: height }).setModelLimits(new Rect(0, 0, 1, 1)).addChild(new Text({
    text: name,
    ax: 0.5,
    ay: 0.5,
    textstyle: textStyle,
    alignment: AnchorType.Center
  })).setMarginsStyle(marginsStyle).setHighlightMargins(highlightMargins);
}
export function createScene(canvas) {
  const pseudo = 20;
  const rootGroup = new LayoutableItem().setLayout(new FlexboxLayout({
    "flexdirection": FlexDirection.Row,
    "flexwrap": FlexWrap.NoWrap
  })).setPaddingStyle({
    left: 5,
    top: 5,
    right: 5,
    bottom: 5
  }).setFillStyle(KnownColors.LightGray).addChild([
    createLayoutableItem(100, 150, "1", ColorUtil.getRandomColorRgb(pseudo)),
    createLayoutableItem(125, 75, "2", ColorUtil.getRandomColorRgb(pseudo)),
    createLayoutableItem(75, 125, "3", ColorUtil.getRandomColorRgb(pseudo))
  ]);
  const selection = new Selection({
    "name": "SelectionTool"
  });
  const plot = new Plot({
    "canvaselement": canvas,
    "root": rootGroup
  });
  plot.getTool().add(selection);
  return plot;
}

createScene(document.querySelector('[ref="plot"]'));

# Nesting

The canvas below demonstrates how the nested Flexible Box Layout can be used to design complicated layout structure.

import { Rect } from "@int/geotoolkit/util/Rect.ts";
import { Plot } from "@int/geotoolkit/plot/Plot.ts";
import { Group } from "@int/geotoolkit/scene/Group.ts";
import { addRectToGroup } from "/src/code/Carnac/Groups/Layouts/utils.ts";
import { AlignItems, FlexboxLayout, FlexDirection, FlexWrap, JustifyContent } from "@int/geotoolkit/layout/FlexboxLayout.ts";
import { ColorUtil } from "@int/geotoolkit/util/ColorUtil.ts";
const pseudo = 25;
const dim = 3;
const depth = 2;
function createScene(canvas) {
  const root = new Group().setModelLimits(new Rect(0, 0, 500, 500));
  const nestedContainer = createNestedContainer().setBounds(new Rect(50, 50, 450, 450));
  root.addChild(nestedContainer);
  return new Plot({
    "canvaselement": canvas,
    "root": root
  });
}
function createNestedContainer(currLevel, containerIdx) {
  if (currLevel == null) {
    currLevel = 1;
  }
  if (containerIdx == null) {
    containerIdx = 0;
  }
  if (currLevel > 2 || dim > 3) {
    containerIdx = -1;
  }
  const vertContainer = createVerticalContainer(containerIdx, currLevel > 1 ? 0 : null);
  for (let i = 0; i < dim; i++) {
    const horContainer = createHorizontalContainer();
    for (let j = 0; j < dim; j++) {
      const nItem = i * dim + j;
      const child = currLevel < depth ? createNestedContainer(currLevel + 1, nItem) : createVerticalContainer(containerIdx, nItem + 1);
      horContainer.addChild(child);
    }
    vertContainer.addChild(horContainer);
  }
  return vertContainer;
}
function createVerticalContainer(containerIdx, nItem) {
  const verticalBoxLayout = new FlexboxLayout({
    "flexwrap": FlexWrap.NoWrap,
    "flexdirection": FlexDirection.Column,
    "justifycontent": JustifyContent.SpaceAround,
    "alignitems": AlignItems.Stretch
  });
  const container = new Group().setLayout(verticalBoxLayout).setModelLimits(new Rect(0, 0, 100, 100)).setLayoutStyle({
    "width": "25%",
    "height": "100%"
  });
  const n = containerIdx < 0 || nItem == null ? NaN : parseFloat(containerIdx + "." + nItem);
  addRectToGroup(container, ColorUtil.getRandomColorRgb(pseudo), n);
  return container;
}
function createHorizontalContainer() {
  const horizontalBoxLayout = new FlexboxLayout({
    "flexwrap": FlexWrap.NoWrap,
    "flexdirection": FlexDirection.Row,
    "justifycontent": JustifyContent.SpaceAround,
    "alignitems": AlignItems.Stretch
  });
  return new Group().setLayout(horizontalBoxLayout).setLayoutStyle({
    "width": "100%",
    "height": "25%"
  });
}
export { createScene };

createScene(document.querySelector('[ref="plot"]'));