Skip to content

Commit

Permalink
Bug/3653/improve steetmap and treemapstreet layout corrected version (#…
Browse files Browse the repository at this point in the history
…3659)

* added thicker root street

* modified height scaling

* adapt height scaling

* adapted the height scaling again

* Added that single boxes in a file will show at the front of the street and files after that, so that it's organized #3653

* directories which contain less then 5 single files are excluded and the layout algorith got a bit revised #3653

* small adjustments for excluding directories with less than 5 single files #3653

* dynamic calculated street thickness #3653

* small adjustments for excluding directories with fewer than 5 single files #3653

* fixed test #3653

* fixed tests #3653

---------

Co-authored-by: Nadine Schatz <[email protected]>
Co-authored-by: SebastianW <[email protected]>
  • Loading branch information
3 people authored Jul 18, 2024
1 parent 9eda94a commit 9c1acef
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ import Rectangle from "./rectangle"
export default abstract class BoundingBox {
height = 0
width = 0
protected node: CodeMapNode
node: CodeMapNode
protected metricValue: number
protected FIXED_MARGIN = 0.5

constructor(node: CodeMapNode) {
this.node = node
}

getNode() {
return this.node
}

abstract calculateDimension(metricName: string): void
abstract layout(margin: number, origin: Vector2): CodeMapNode[]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("horizontalStreet", () => {
codeMapNode.path = "somePath"
house = new House(codeMapNode)
children = [house]
horizontalStreet = new HorizontalStreet(codeMapNode, children, 1, HorizontalOrientation.RIGHT)
horizontalStreet = new HorizontalStreet(codeMapNode, children, HorizontalOrientation.RIGHT)
})
describe("calculateDimension", () => {
it("should calculate street width and height", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,16 @@ export enum HorizontalOrientation {
}

export default class HorizontalStreet extends Street {
private children: BoundingBox[] = []
protected node: CodeMapNode
protected children: BoundingBox[] = []
node: CodeMapNode
protected topRow: BoundingBox[] = []
protected bottomRow: BoundingBox[] = []
orientation: HorizontalOrientation
protected depth: number

constructor(
node: CodeMapNode,
children: BoundingBox[],
depth: number,
orientation: HorizontalOrientation = HorizontalOrientation.RIGHT
) {
constructor(node: CodeMapNode, children: BoundingBox[], orientation: HorizontalOrientation = HorizontalOrientation.RIGHT) {
super(node)
this.children = children
this.depth = depth
this.orientation = orientation
}

Expand All @@ -46,7 +40,7 @@ export default class HorizontalStreet extends Street {
// TODO Add a comment what the following calculations are doing.
this.metricValue = StreetViewHelper.calculateSize(this.node, metricName)
this.width = Math.max(this.getLength(this.topRow), this.getLength(this.bottomRow))
this.height = this.getMaxHeight(this.topRow) + this.getStreetThickness() + this.getMaxHeight(this.bottomRow) + 2 * this.spacer
this.height = this.getMaxHeight(this.topRow) + this.getStreetThickness() + this.getMaxHeight(this.bottomRow) + this.spacer
}

layout(margin: number, origin: Vector2): CodeMapNode[] {
Expand Down Expand Up @@ -132,7 +126,7 @@ export default class HorizontalStreet extends Street {
/**
* Calculates y-coordinate of street.
* @param origin origin of local coordinate system
* @param maxLeftWidth highest node in topRow
* @param maxTopHeight highest node in topRow
*/
private calculateStreetOffsetY(origin: Vector2, maxTopHeight: number): number {
return origin.y + this.spacer + maxTopHeight
Expand Down Expand Up @@ -165,11 +159,15 @@ export default class HorizontalStreet extends Street {
* @param children children of the current node
*/
protected splitChildrenToRows(children: BoundingBox[]): void {
const totalLength = this.getLength(children)
let totalLength = 0
let sum = 0

for (const child of children) {
if (sum < totalLength / 2) {
totalLength += child.width
}

for (const child of children) {
if (sum <= totalLength / 2) {
this.topRow.push(child)
sum += child.width
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default class Rectangle {
topLeft: Vector2
width: number
height: number
private bottomRight: Vector2
protected bottomRight: Vector2

constructor(topLeft: Vector2, width: number, height: number) {
this.topLeft = topLeft
Expand Down
23 changes: 20 additions & 3 deletions visualization/app/codeCharta/util/algorithm/streetLayout/street.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,32 @@ export enum StreetOrientation {
export default abstract class Street extends BoundingBox {
streetRect: Rectangle | undefined
protected spacer = 2
private maxStreetThickness = 10
protected abstract depth: number

protected abstract layoutStreet(origin: Vector2, maxNodeSideLength: number): CodeMapNode
protected abstract splitChildrenToRows(children: BoundingBox[]): void
protected abstract calculateStreetOverhang(streetOrigin: Vector2): number
protected abstract rearrangeRows(): void

protected getStreetThickness(): number {
return this.maxStreetThickness / (this.depth + 1)
const pathParts = this.node.path.split("/")
const isDirectChildOfRoot = this.node.path.startsWith("/root/") && pathParts.length === 3 && pathParts[2] !== ""

return this.node.path === "/root" || isDirectChildOfRoot
? this.calculateRootStreetThickness(this.node)
: this.calculateNonRootThickness(this.node)
}

protected calculateNonRootThickness(node: CodeMapNode): number {
const baseThickness = 2
const sizeFactor = 0.0005
const fileSize = node.attributes.unary
return baseThickness + fileSize * sizeFactor
}

protected calculateRootStreetThickness(node: CodeMapNode): number {
const baseThickness = 8
const sizeFactor = 0.001
const fileSize = node.attributes.unary
return baseThickness + fileSize * sizeFactor
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
import { CodeMapNode, Node, CcState, NodeMetricData, LayoutAlgorithm } from "../../../codeCharta.model"
import { CcState, CodeMapNode, LayoutAlgorithm, Node, NodeMetricData } from "../../../codeCharta.model"
import BoundingBox from "./boundingBox"
import VerticalStreet from "./verticalStreet"
import HorizontalStreet from "./horizontalStreet"
import House from "./house"
import TreeMap from "./treeMap"
import { Vector2 } from "three"
import { StreetOrientation } from "./street"
import { getMapResolutionScaleFactor, isPathBlacklisted, isLeaf } from "../../codeMapHelper"
import { getMapResolutionScaleFactor, isLeaf, isPathBlacklisted } from "../../codeMapHelper"
import { StreetViewHelper } from "./streetViewHelper"
import SquarifiedTreeMap from "./squarifiedTreeMap"
import { treeMapSize } from "../treeMapLayout/treeMapHelper"

const MARGIN_SCALING_FACTOR = 0.02
const HEIGHT_SCALING_FACTOR = 0.1
export class StreetLayoutGenerator {
static createStreetLayoutNodes(map: CodeMapNode, state: CcState, metricData: NodeMetricData[], isDeltaState: boolean): Node[] {
const mapSizeResolutionScaling = getMapResolutionScaleFactor(state.files)
const maxHeight = metricData.find(x => x.name === state.dynamicSettings.heightMetric).maxValue * mapSizeResolutionScaling
const heightScale = ((treeMapSize * 2) / maxHeight) * HEIGHT_SCALING_FACTOR

const metricName = state.dynamicSettings.areaMetric
const mergedMap = StreetViewHelper.mergeDirectories(map, metricName)
const maxTreeMapFiles = state.appSettings.maxTreeMapFiles
const childBoxes = this.createBoxes(mergedMap, metricName, state, StreetOrientation.Vertical, 0, maxTreeMapFiles)
const childBoxes = this.createBoxes(mergedMap, metricName, state, StreetOrientation.Vertical, 1, maxTreeMapFiles)
const rootStreet = new HorizontalStreet(mergedMap, childBoxes, 0)
rootStreet.calculateDimension(metricName)
const margin = state.dynamicSettings.margin * MARGIN_SCALING_FACTOR
const layoutNodes = rootStreet.layout(margin, new Vector2(0, 0))

return layoutNodes.map(streetLayoutNode => {
return StreetViewHelper.buildNodeFrom(streetLayoutNode as CodeMapNode, heightScale, maxHeight, state, isDeltaState)
return StreetViewHelper.buildNodeFrom(
streetLayoutNode as CodeMapNode,
this.calculateHeightScale(map, treeMapSize, maxHeight),
maxHeight,
state,
isDeltaState
)
})
}

Expand All @@ -44,13 +48,13 @@ export class StreetLayoutGenerator {
const children: BoundingBox[] = []
const areaMetric = state.dynamicSettings.areaMetric
for (let child of node.children) {
if (isPathBlacklisted(child.path, state.fileSettings.blacklist, "exclude")) {
continue
}
if (isLeaf(child)) {
children.push(new House(child))
continue
}
if (isPathBlacklisted(child.path, state.fileSettings.blacklist, "exclude")) {
continue
}

const layoutAlgorithm = state.appSettings.layoutAlgorithm
const fileDescendants = StreetLayoutGenerator.countFileDescendants(child)
Expand Down Expand Up @@ -91,4 +95,17 @@ export class StreetLayoutGenerator {
}
return totalFileNodes
}

private static calculateHeightScale(map: CodeMapNode, treeMapSize: number, maxHeight: number): number {
// Constants to control the curve and scaling
const linearCoefficient = 0.0001
const rootCoefficient = 0.005

// Calculate linear and square root components
const linearComponent = linearCoefficient * map.attributes.unary
const rootComponent = Math.sqrt(map.attributes.unary) * rootCoefficient

// Combine both components for the height scale calculation
return ((treeMapSize * 2) / maxHeight) * (linearComponent + rootComponent)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ function mergeDirectories(node: CodeMapNode, metricName: string): CodeMapNode {
return mergedNode
}

function buildNodeFrom(layoutNode: CodeMapNode, heightScale: number, maxHeight: number, s: CcState, isDeltaState: boolean): Node {
function buildNodeFrom(layoutNode: CodeMapNode, heightScale: number, maxHeight: number, state: CcState, isDeltaState: boolean): Node {
const isNodeLeaf = !(layoutNode.children && layoutNode.children.length > 0)
const flattened: boolean = isNodeFlat(layoutNode, s)
const heightValue: number = TreeMapHelper.getHeightValue(s, layoutNode, maxHeight, flattened)
const flattened: boolean = isNodeFlat(layoutNode, state)
const heightValue: number = TreeMapHelper.getHeightValue(state, layoutNode, maxHeight, flattened)
const height = Math.abs(
isNodeLeaf ? Math.max(heightScale * heightValue, TreeMapHelper.MIN_BUILDING_HEIGHT) : TreeMapHelper.FOLDER_HEIGHT
)
Expand All @@ -64,15 +64,15 @@ function buildNodeFrom(layoutNode: CodeMapNode, heightScale: number, maxHeight:
attributes: layoutNode.attributes,
edgeAttributes: layoutNode.edgeAttributes,
deltas: layoutNode.deltas,
heightDelta: layoutNode.deltas?.[s.dynamicSettings.heightMetric]
? heightScale * layoutNode.deltas[s.dynamicSettings.heightMetric]
heightDelta: layoutNode.deltas?.[state.dynamicSettings.heightMetric]
? heightScale * layoutNode.deltas[state.dynamicSettings.heightMetric]
: 0,
visible: isVisible(layoutNode, isNodeLeaf, s, flattened),
visible: isVisible(layoutNode, isNodeLeaf, state, flattened),
path: layoutNode.path,
link: layoutNode.link,
markingColor: getMarkingColor(layoutNode, s.fileSettings.markedPackages),
markingColor: getMarkingColor(layoutNode, state.fileSettings.markedPackages),
flat: flattened,
color: getBuildingColor(layoutNode, s, selectedColorMetricDataSelector(s), isDeltaState, flattened),
color: getBuildingColor(layoutNode, state, selectedColorMetricDataSelector(state), isDeltaState, flattened),
incomingEdgePoint: getIncomingEdgePoint(layoutNode.rect.width, height, length, new Vector3(x0, z0, y0), treeMapSize),
outgoingEdgePoint: getIncomingEdgePoint(layoutNode.rect.width, height, length, new Vector3(x0, z0, y0), treeMapSize)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default abstract class Treemap extends BoundingBox {
protected treeMapNodes: CodeMapNode[] = []
protected metricName: string

constructor(rootNode: CodeMapNode) {
protected constructor(rootNode: CodeMapNode) {
super(rootNode)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import BoundingBox from "./boundingBox"
import Rectangle from "./rectangle"
import HorizontalStreet, { HorizontalOrientation } from "./horizontalStreet"
import Street from "./street"
import { CodeMapNode } from "../../../codeCharta.model"
import { CodeMapNode, NodeType } from "../../../codeCharta.model"
import { StreetViewHelper } from "./streetViewHelper"
import { Vector2 } from "three"

Expand All @@ -12,16 +12,15 @@ export enum VerticalOrientation {
}

export default class VerticalStreet extends Street {
private children: BoundingBox[] = []
protected children: BoundingBox[] = []
protected leftRow: BoundingBox[] = []
protected rightRow: BoundingBox[] = []
orientation: VerticalOrientation
protected depth: number
private _origin: Vector2

constructor(node: CodeMapNode, children: BoundingBox[], depth: number, orientation: VerticalOrientation = VerticalOrientation.UP) {
super(node)
this.children = children
this.depth = depth
this.orientation = orientation
}

Expand Down Expand Up @@ -80,7 +79,7 @@ export default class VerticalStreet extends Street {
value: metricValue,
rect: this.streetRect,
zOffset: 0
} as CodeMapNode
}
}

private layoutRightRow(origin: Vector2, maxLeftWidth: number, margin: number): CodeMapNode[] {
Expand Down Expand Up @@ -121,12 +120,29 @@ export default class VerticalStreet extends Street {
return sum
}

protected splitChildrenToRows(children: BoundingBox[]) {
const totalLength = this.getLength(children)
protected sortChildrenByType(children: BoundingBox[]): void {
children.sort((a, b) => {
if (a.node.type === b.node.type) {
return 0
}
if (a.node.type === NodeType.FILE) {
return -1
}
return 1
})
}
protected splitChildrenToRows(children: BoundingBox[]): void {
this.sortChildrenByType(children)

let totalLength = 0
let sum = 0

for (const child of children) {
if (sum < totalLength / 2) {
totalLength += child.height
}

for (const child of children) {
if (sum <= totalLength / 2) {
if (child instanceof HorizontalStreet) {
child.orientation = HorizontalOrientation.LEFT
}
Expand Down

0 comments on commit 9c1acef

Please sign in to comment.