diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-07 21:52:16 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-07-07 21:52:16 +0200 |
| commit | 129bca332b68bd6106079f58bd0de77baf21ec68 (patch) | |
| tree | e77fc44ca4a5a9a0cb9b653859d7546154472911 | |
| parent | 9cbc1f2bf737e862a01c6859029c3683d6067def (diff) | |
Take Notes into account in smart labels
| -rw-r--r-- | Map/Business/SmartLabelPositioning/SmartLabelPositioning.swift | 376 | ||||
| -rw-r--r-- | Map/Localizable.xcstrings | 625 | ||||
| -rw-r--r-- | Map/MapApp.swift | 67 | ||||
| -rw-r--r-- | Map/Presentation/Base Components/MapRender/MapMask.swift | 9 | ||||
| -rw-r--r-- | Map/Presentation/Base Components/MapRender/MapVertices.swift | 9 | ||||
| -rw-r--r-- | Map/Presentation/Complex Components/MapRender/MapRenderView.swift | 77 |
6 files changed, 843 insertions, 320 deletions
diff --git a/Map/Business/SmartLabelPositioning/SmartLabelPositioning.swift b/Map/Business/SmartLabelPositioning/SmartLabelPositioning.swift new file mode 100644 index 0000000..f765108 --- /dev/null +++ b/Map/Business/SmartLabelPositioning/SmartLabelPositioning.swift @@ -0,0 +1,376 @@ +// Copyright (C) 2024 Rubén Beltrán del Río + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +import AppKit +// You should have received a copy of the GNU General Public License +// along with this program. If not, see https://map.tranquil.systems. +import CoreGraphics + +struct SmartLabelPositioning { + + static func calculateOptimalLabelPosition( + for vertex: Vertex, + mapSize: CGSize, + vertexSize: CGSize, + edges: [MapEdge], + opportunities: [Opportunity], + inertias: [Inertia], + axisLines: [Line], + allVertices: [Vertex] = [], + notes: [Note] = [] + ) -> CGPoint { + let vertexPixelPos = CGPoint( + x: w(vertex.position.x, mapSize: mapSize), + y: h(vertex.position.y, mapSize: mapSize) + ) + + let actualLabelSize = calculateLabelSize(for: vertex.label) + + let candidatePositions = generateCandidatePositions( + vertexPixelPos: vertexPixelPos, + vertexSize: vertexSize, + labelSize: actualLabelSize + ) + + let allLines = collectAllLines( + edges: edges, + opportunities: opportunities, + inertias: inertias, + axisLines: axisLines, + mapSize: mapSize, + vertexSize: vertexSize + ) + + let noteRects = collectNoteRects( + notes: notes, + mapSize: mapSize + ) + + let defaultPosition = candidatePositions[0] + + let defaultRect = CGRect(origin: defaultPosition, size: actualLabelSize) + if countCollisions(labelRect: defaultRect, lines: allLines) == 0 + && countNoteCollisions(labelRect: defaultRect, noteRects: noteRects) == 0 + { + return defaultPosition + } + + var bestPosition = defaultPosition + var bestScore = Double.infinity + + for (index, position) in candidatePositions.enumerated() { + let labelRect = CGRect( + origin: position, + size: actualLabelSize + ) + + let collisionCount = countCollisions(labelRect: labelRect, lines: allLines) + let noteCollisionCount = countNoteCollisions(labelRect: labelRect, noteRects: noteRects) + let distance = sqrt( + pow(position.x - (vertexPixelPos.x + vertexSize.width / 2), 2) + + pow(position.y - (vertexPixelPos.y + vertexSize.height / 2), 2)) + + let ownershipPenalty = calculateOwnershipPenalty( + labelRect: labelRect, + ownVertex: vertex, + allVertices: allVertices, + mapSize: mapSize, + vertexSize: vertexSize + ) + + let score = + Double(collisionCount * 100) + Double(noteCollisionCount * 500) + distance * 0.5 + Double( + index) * 0.1 + ownershipPenalty + + if score < bestScore { + bestScore = score + bestPosition = position + } + } + + return bestPosition + } + + private static func calculateLabelSize(for text: String) -> CGSize { + let cleanText = text.replacingOccurrences(of: "\\n", with: "\n") + let font = NSFont.systemFont(ofSize: 11) + let attributes: [NSAttributedString.Key: Any] = [.font: font] + let size = cleanText.boundingRect( + with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), + options: [.usesLineFragmentOrigin, .usesFontLeading], + attributes: attributes, + context: nil + ).size + + return CGSize(width: ceil(size.width) + 4, height: ceil(size.height) + 2) + } + + private static func calculateOwnershipPenalty( + labelRect: CGRect, + ownVertex: Vertex, + allVertices: [Vertex], + mapSize: CGSize, + vertexSize: CGSize + ) -> Double { + guard !allVertices.isEmpty else { return 0 } + + let labelCenter = CGPoint( + x: labelRect.midX, + y: labelRect.midY + ) + + let ownVertexPixelPos = CGPoint( + x: w(ownVertex.position.x, mapSize: mapSize), + y: h(ownVertex.position.y, mapSize: mapSize) + ) + let ownVertexCenter = CGPoint( + x: ownVertexPixelPos.x + vertexSize.width / 2, + y: ownVertexPixelPos.y + vertexSize.height / 2 + ) + + let distanceToOwnVertex = sqrt( + pow(labelCenter.x - ownVertexCenter.x, 2) + pow(labelCenter.y - ownVertexCenter.y, 2) + ) + + for otherVertex in allVertices { + if otherVertex.id == ownVertex.id { continue } + + let otherVertexPixelPos = CGPoint( + x: w(otherVertex.position.x, mapSize: mapSize), + y: h(otherVertex.position.y, mapSize: mapSize) + ) + let otherVertexCenter = CGPoint( + x: otherVertexPixelPos.x + vertexSize.width / 2, + y: otherVertexPixelPos.y + vertexSize.height / 2 + ) + + let distanceToOtherVertex = sqrt( + pow(labelCenter.x - otherVertexCenter.x, 2) + pow(labelCenter.y - otherVertexCenter.y, 2) + ) + + if distanceToOtherVertex < distanceToOwnVertex { + return 50.0 + } + } + + return 0 + } + + private static func generateCandidatePositions( + vertexPixelPos: CGPoint, + vertexSize: CGSize, + labelSize: CGSize + ) -> [CGPoint] { + let padding = CGFloat(5.0) + + return [ + CGPoint( + x: vertexPixelPos.x + vertexSize.width + padding, + y: vertexPixelPos.y + (vertexSize.height - labelSize.height) / 2 + ), + CGPoint( + x: vertexPixelPos.x - labelSize.width - padding, + y: vertexPixelPos.y + (vertexSize.height - labelSize.height) / 2 + ), + CGPoint( + x: vertexPixelPos.x + (vertexSize.width - labelSize.width) / 2, + y: vertexPixelPos.y - labelSize.height - padding + ), + CGPoint( + x: vertexPixelPos.x + (vertexSize.width - labelSize.width) / 2, + y: vertexPixelPos.y + vertexSize.height + padding + ), + CGPoint( + x: vertexPixelPos.x + vertexSize.width + padding, + y: vertexPixelPos.y - labelSize.height - padding + ), + CGPoint( + x: vertexPixelPos.x - labelSize.width - padding, + y: vertexPixelPos.y - labelSize.height - padding + ), + CGPoint( + x: vertexPixelPos.x + vertexSize.width + padding, + y: vertexPixelPos.y + vertexSize.height + padding + ), + CGPoint( + x: vertexPixelPos.x - labelSize.width - padding, + y: vertexPixelPos.y + vertexSize.height + padding + ), + ] + } + + private static func collectAllLines( + edges: [MapEdge], + opportunities: [Opportunity], + inertias: [Inertia], + axisLines: [Line], + mapSize: CGSize, + vertexSize: CGSize + ) -> [Line] { + var lines: [Line] = [] + + for edge in edges { + let origin = CGPoint( + x: w(edge.origin.x, mapSize: mapSize), y: h(edge.origin.y, mapSize: mapSize)) + let destination = CGPoint( + x: w(edge.destination.x, mapSize: mapSize), y: h(edge.destination.y, mapSize: mapSize)) + + let slope = (destination.y - origin.y) / (destination.x - origin.x) + let angle = atan(slope) + let multiplier = CGFloat(slope < 0 ? -1.0 : 1.0) + + let offsetOrigin = CGPoint( + x: origin.x + multiplier * (vertexSize.width / 2.0) * cos(angle), + y: origin.y + multiplier * (vertexSize.height / 2.0) * sin(angle) + ) + let offsetDestination = CGPoint( + x: destination.x - multiplier * (vertexSize.width / 2.0) * cos(angle), + y: destination.y - multiplier * (vertexSize.height / 2.0) * sin(angle) + ) + + let adjustedOrigin = CGPoint( + x: offsetOrigin.x + vertexSize.width / 2.0, + y: offsetOrigin.y + vertexSize.height / 2.0 + ) + let adjustedDestination = CGPoint( + x: offsetDestination.x + vertexSize.width / 2.0, + y: offsetDestination.y + vertexSize.height / 2.0 + ) + + lines.append(Line(start: adjustedOrigin, end: adjustedDestination)) + } + + for opportunity in opportunities { + let origin = CGPoint( + x: w(opportunity.origin.x, mapSize: mapSize), y: h(opportunity.origin.y, mapSize: mapSize)) + let destination = CGPoint( + x: w(opportunity.destination.x, mapSize: mapSize), + y: h(opportunity.destination.y, mapSize: mapSize)) + + let multiplier = CGFloat(opportunity.destination.x > opportunity.origin.x ? 1.0 : -1.0) + + let offsetOrigin = CGPoint( + x: origin.x + multiplier * (vertexSize.width / 2.0), + y: origin.y + ) + let offsetDestination = CGPoint( + x: destination.x - multiplier * (vertexSize.width / 2.0), + y: destination.y + ) + + let adjustedOrigin = CGPoint( + x: offsetOrigin.x + vertexSize.width / 2.0, + y: offsetOrigin.y + vertexSize.height / 2.0 + ) + let adjustedDestination = CGPoint( + x: offsetDestination.x + vertexSize.width / 2.0, + y: offsetDestination.y + vertexSize.height / 2.0 + ) + + lines.append(Line(start: adjustedOrigin, end: adjustedDestination)) + } + + lines.append(contentsOf: axisLines) + + return lines + } + + private static func countCollisions(labelRect: CGRect, lines: [Line]) -> Int { + var collisions = 0 + + for line in lines { + if lineIntersectsRect(line: line, rect: labelRect) { + collisions += 1 + } + } + + return collisions + } + + private static func lineIntersectsRect(line: Line, rect: CGRect) -> Bool { + let rectLines = [ + Line(start: CGPoint(x: rect.minX, y: rect.minY), end: CGPoint(x: rect.maxX, y: rect.minY)), + Line(start: CGPoint(x: rect.maxX, y: rect.minY), end: CGPoint(x: rect.maxX, y: rect.maxY)), + Line(start: CGPoint(x: rect.maxX, y: rect.maxY), end: CGPoint(x: rect.minX, y: rect.maxY)), + Line(start: CGPoint(x: rect.minX, y: rect.maxY), end: CGPoint(x: rect.minX, y: rect.minY)), + ] + + for rectLine in rectLines { + if linesIntersect(line1: line, line2: rectLine) { + return true + } + } + + return rect.contains(line.start) || rect.contains(line.end) + } + + private static func linesIntersect(line1: Line, line2: Line) -> Bool { + let x1 = line1.start.x + let y1 = line1.start.y + let x2 = line1.end.x + let y2 = line1.end.y + let x3 = line2.start.x + let y3 = line2.start.y + let x4 = line2.end.x + let y4 = line2.end.y + + let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4) + + if abs(denom) < 1e-10 { + return false + } + + let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom + let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom + + return t >= 0 && t <= 1 && u >= 0 && u <= 1 + } + + private static func w(_ dimension: CGFloat, mapSize: CGSize) -> CGFloat { + max(0.0, min(mapSize.width, dimension * mapSize.width / 100.0)) + } + + private static func collectNoteRects( + notes: [Note], + mapSize: CGSize + ) -> [CGRect] { + return notes.map { note in + let notePixelPos = CGPoint( + x: w(note.position.x, mapSize: mapSize), + y: h(note.position.y, mapSize: mapSize) + ) + let noteSize = calculateLabelSize(for: note.text) + return CGRect(origin: notePixelPos, size: noteSize) + } + } + + private static func countNoteCollisions(labelRect: CGRect, noteRects: [CGRect]) -> Int { + var collisions = 0 + + for noteRect in noteRects { + if labelRect.intersects(noteRect) { + collisions += 1 + } + } + + return collisions + } + + private static func h(_ dimension: CGFloat, mapSize: CGSize) -> CGFloat { + max(0.0, min(mapSize.height, dimension * mapSize.height / 100.0)) + } +} + +struct Line { + let start: CGPoint + let end: CGPoint +} diff --git a/Map/Localizable.xcstrings b/Map/Localizable.xcstrings index fc9ef7c..f2c0b6c 100644 --- a/Map/Localizable.xcstrings +++ b/Map/Localizable.xcstrings @@ -147,6 +147,17 @@ } } }, + "document.inactive" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Document not active" + } + } + } + }, "evolution_picker.label" : { "extractionState" : "manual", "localizations" : { @@ -736,1324 +747,1324 @@ } } }, - "stage_type.certainty" : { + "stage_type.behavior.stage_i" : { + "comment" : "First behavior stage - uncertain about when and how to use the component", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Certainty" + "value" : "Uncertain when to use" } } } }, - "stage_type.comparison" : { + "stage_type.behavior.stage_ii" : { + "comment" : "Second behavior stage - learning when to use the component appropriately", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Comparison" + "value" : "Learning when to use" } } } }, - "stage_type.cynefin" : { + "stage_type.behavior.stage_iii" : { + "comment" : "Third behavior stage - learning through practical use and experience", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Cynefin" + "value" : "Learning through use" } } } }, - "stage_type.cynefin.i" : { + "stage_type.behavior.stage_iv" : { + "comment" : "Fourth behavior stage - known and common usage patterns that are well understood", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Chaotic" + "value" : "Known / common usage" } } } }, - "stage_type.cynefin.ii" : { + "stage_type.certainty" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Complex" + "value" : "Certainty" } } } }, - "stage_type.cynefin.iii" : { + "stage_type.certainty.stage_i" : { + "comment" : "First certainty stage - poorly understood components with high uncertainty", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Complicated" + "value" : "Poorly Understood / exploring the unknown" } } } }, - "stage_type.cynefin.iv" : { + "stage_type.certainty.stage_ii" : { + "comment" : "Second certainty stage - rapid learning and discovery leading to better understanding", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Clear" + "value" : "Rapid Increase In Learning / discovery becomes refining" } } } }, - "stage_type.data" : { + "stage_type.certainty.stage_iii" : { + "comment" : "Third certainty stage - increased practical use leading to better fit for purpose", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Data" + "value" : "Rapid increase in use / increasing fit for purpose" } } } }, - "stage_type.decision_drivers" : { + "stage_type.certainty.stage_iv" : { + "comment" : "Fourth certainty stage - commonly understood components with predictable behavior", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Decision Drivers" + "value" : "Commonly understood (in terms of use)" } } } }, - "stage_type.efficiency" : { + "stage_type.comparison" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Efficiency" + "value" : "Comparison" } } } }, - "stage_type.failure" : { + "stage_type.comparison.stage_i" : { + "comment" : "First comparison stage - constantly changing, differential, and unstable", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Failure" + "value" : "Constantly changing / a differential / unstable" } } } }, - "stage_type.focus_of_value" : { + "stage_type.comparison.stage_ii" : { + "comment" : "Second comparison stage - learning from others with some evidential support", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Focus Of Value" + "value" : "Learning from others / testing the water / some evidential support" } } } }, - "stage_type.general" : { + "stage_type.comparison.stage_iii" : { + "comment" : "Third comparison stage - competing models with feature differences and evidence", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Activities" + "value" : "Competing models / feature difference / evidential support" } } } }, - "stage_type.knowledge" : { + "stage_type.comparison.stage_iv" : { + "comment" : "Fourth comparison stage - essential components where advantage is operational", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Knowledge" + "value" : "Essential / any advantage is operational / accepted norm" } } } }, - "stage_type.knowledge_management" : { + "stage_type.cynefin" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Knowledge Management" + "value" : "Cynefin" } } } }, - "stage_type.market" : { + "stage_type.cynefin.i" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Market" + "value" : "Chaotic" } } } }, - "stage_type.market_action" : { + "stage_type.cynefin.ii" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Market Action" + "value" : "Complex" } } } }, - "stage_type.market_perception" : { + "stage_type.cynefin.iii" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Market Perception" + "value" : "Complicated" } } } }, - "stage_type.perception_in_industry" : { + "stage_type.cynefin.iv" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Perception In Industry" + "value" : "Clear" } } } }, - "stage_type.placeholder" : { + "stage_type.data" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Evolution Stage" + "value" : "Data" } } } }, - "stage_type.placeholder.i" : { + "stage_type.data.stage_i" : { + "comment" : "First evolution stage for data - unmodeled, raw data without clear structure or understanding", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Stage I" + "value" : "Unmodelled" } } } }, - "stage_type.placeholder.ii" : { + "stage_type.data.stage_ii" : { + "comment" : "Second evolution stage for data - divergent data models being explored and tested", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Stage II" + "value" : "Divergent" } } } }, - "stage_type.placeholder.iii" : { + "stage_type.data.stage_iii" : { + "comment" : "Third evolution stage for data - convergent data models that are becoming standardized", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Stage III" + "value" : "Convergent" } } } }, - "stage_type.placeholder.iv" : { + "stage_type.data.stage_iv" : { + "comment" : "Fourth evolution stage for data - fully modeled data with well-defined schemas and standards", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Stage IV" + "value" : "Modelled" } } } }, - "stage_type.practice" : { + "stage_type.decision_drivers" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Practice" + "value" : "Decision Drivers" } } } }, - "stage_type.publication_types" : { + "stage_type.decision_drivers.stage_i" : { + "comment" : "First decision drivers stage - decisions driven by heritage and culture", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Publication Types" + "value" : "Heritage / culture" } } } }, - "stage_type.section.characteristics" : { + "stage_type.decision_drivers.stage_ii" : { + "comment" : "Second decision drivers stage - decisions based on analysis and synthesis", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Characteristics" + "value" : "Analyses & synthesis" } } } }, - "stage_type.section.custom" : { + "stage_type.decision_drivers.stage_iii" : { + "comment" : "Third decision drivers stage - continued analysis and synthesis approaches", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Custom" + "value" : "Analyses & synthesis" } } } }, - "stage_type.section.general_properties" : { + "stage_type.decision_drivers.stage_iv" : { + "comment" : "Fourth decision drivers stage - decisions based on previous experience and patterns", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "General Properties" + "value" : "Previous Experience" } } } }, - "stage_type.section.types" : { + "stage_type.efficiency" : { + "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Types" + "value" : "Efficiency" } } } }, - "stage_type.ubiquity" : { + "stage_type.efficiency.stage_i" : { + "comment" : "First efficiency stage - reducing cost of change through experimentation", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Ubiquity" + "value" : "Reducing the cost of change (experimentation)" } } } }, - "stage_type.understanding" : { + "stage_type.efficiency.stage_ii" : { + "comment" : "Second efficiency stage - reducing cost of waste through learning", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Understanding" + "value" : "Reducing cost of waste (Learning)" } } } }, - "stage_type.user_perception" : { + "stage_type.efficiency.stage_iii" : { + "comment" : "Third efficiency stage - reducing cost of waste through continued learning", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "User Perception" + "value" : "Reducing cost of waste (Learning)" } } } }, - "stage_type.general.stage_i" : { - "comment" : "First evolution stage for general activities - novel, experimental components with high uncertainty", + "stage_type.efficiency.stage_iv" : { + "comment" : "Fourth efficiency stage - reducing cost of deviation through volume operations", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Genesis" + "value" : "Reducing cost of deviation (Volume)" } } } }, - "stage_type.general.stage_ii" : { - "comment" : "Second evolution stage for general activities - custom-built solutions emerging from genesis", + "stage_type.failure" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Custom" + "value" : "Failure" } } } }, - "stage_type.general.stage_iii" : { - "comment" : "Third evolution stage for general activities - standardized products available for purchase or rental", + "stage_type.failure.stage_i" : { + "comment" : "First failure stage - high failure rate that is tolerated and assumed", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Product (+rental)" + "value" : "High / tolerated / assumed to be wrong" } } } }, - "stage_type.general.stage_iv" : { - "comment" : "Fourth evolution stage for general activities - commoditized utilities with standard interfaces", + "stage_type.failure.stage_ii" : { + "comment" : "Second failure stage - moderate failure rate that is disappointing but not surprising", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Commodity (+utility)" + "value" : "Moderate / unsurprising if wrong but disappointed" } } } }, - "stage_type.practice.stage_i" : { - "comment" : "First evolution stage for practices - novel, experimental practices with uncertain outcomes", + "stage_type.failure.stage_iii" : { + "comment" : "Third failure stage - failure not tolerated with focus on constant improvement", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Novel" + "value" : "Not tolerated / focus on constant improvement / assumed to be in the right direction / resistance to changing the model" } } } }, - "stage_type.practice.stage_ii" : { - "comment" : "Second evolution stage for practices - emerging practices showing some promise", + "stage_type.failure.stage_iv" : { + "comment" : "Fourth failure stage - surprise at failure with focus on operational efficiency", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Emerging" + "value" : "Surprised by failure / focus on operational efficiency" } } } }, - "stage_type.practice.stage_iii" : { - "comment" : "Third evolution stage for practices - good practices that are proven and reliable", + "stage_type.focus_of_value" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Good" + "value" : "Focus Of Value" } } } }, - "stage_type.practice.stage_iv" : { - "comment" : "Fourth evolution stage for practices - best practices that are widely adopted and standardized", + "stage_type.focus_of_value.stage_i" : { + "comment" : "First value focus stage - high future worth requiring immediate investment", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Best" + "value" : "High future worth but immediate investment" } } } }, - "stage_type.data.stage_i" : { - "comment" : "First evolution stage for data - unmodeled, raw data without clear structure or understanding", + "stage_type.focus_of_value.stage_ii" : { + "comment" : "Second value focus stage - seeking ways to profit and confirm return on investment", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Unmodelled" + "value" : "Seeking ways to profit and a ROI / seeking confirmation of value" } } } }, - "stage_type.data.stage_ii" : { - "comment" : "Second evolution stage for data - divergent data models being explored and tested", + "stage_type.focus_of_value.stage_iii" : { + "comment" : "Third value focus stage - high profitability with focus on exploitation of valuable models", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Divergent" + "value" : "High profitability per unit / a valuable model / a feeling of understanding / focus on exploitation" } } } }, - "stage_type.data.stage_iii" : { - "comment" : "Third evolution stage for data - convergent data models that are becoming standardized", + "stage_type.focus_of_value.stage_iv" : { + "comment" : "Fourth value focus stage - high volume with reducing margins as essential infrastructure", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Convergent" + "value" : "High volume / reducing margin / important but invisible / an essential component of something more complex" } } } }, - "stage_type.data.stage_iv" : { - "comment" : "Fourth evolution stage for data - fully modeled data with well-defined schemas and standards", + "stage_type.general" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Modelled" + "value" : "Activities" } } } }, - "stage_type.knowledge.stage_i" : { - "comment" : "First evolution stage for knowledge - conceptual ideas without empirical validation", + "stage_type.general.stage_i" : { + "comment" : "First evolution stage for general activities - novel, experimental components with high uncertainty", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Concept" + "value" : "Genesis" } } } }, - "stage_type.knowledge.stage_ii" : { - "comment" : "Second evolution stage for knowledge - hypotheses that can be tested and validated", + "stage_type.general.stage_ii" : { + "comment" : "Second evolution stage for general activities - custom-built solutions emerging from genesis", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Hypothesis" + "value" : "Custom" } } } }, - "stage_type.knowledge.stage_iii" : { - "comment" : "Third evolution stage for knowledge - theoretical frameworks supported by evidence", + "stage_type.general.stage_iii" : { + "comment" : "Third evolution stage for general activities - standardized products available for purchase or rental", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Theory" + "value" : "Product (+rental)" } } } }, - "stage_type.knowledge.stage_iv" : { - "comment" : "Fourth evolution stage for knowledge - accepted knowledge that is widely understood and proven", + "stage_type.general.stage_iv" : { + "comment" : "Fourth evolution stage for general activities - commoditized utilities with standard interfaces", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Accepted" + "value" : "Commodity (+utility)" } } } }, - "stage_type.ubiquity.stage_i" : { - "comment" : "First ubiquity stage - rare components with limited adoption", + "stage_type.knowledge" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Rare" + "value" : "Knowledge" } } } }, - "stage_type.ubiquity.stage_ii" : { - "comment" : "Second ubiquity stage - slowly increasing adoption and awareness", + "stage_type.knowledge_management" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Slowly Increasing" + "value" : "Knowledge Management" } } } }, - "stage_type.ubiquity.stage_iii" : { - "comment" : "Third ubiquity stage - rapidly increasing adoption as value becomes clear", + "stage_type.knowledge_management.stage_i" : { + "comment" : "First knowledge management stage - uncertain knowledge with unclear application", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Rapidly Increasing" + "value" : "Uncertain" } } } }, - "stage_type.ubiquity.stage_iv" : { - "comment" : "Fourth ubiquity stage - widespread adoption throughout the applicable market or ecosystem", + "stage_type.knowledge_management.stage_ii" : { + "comment" : "Second knowledge management stage - learning through use and testing predictions", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Widespread in the applicable market / ecosystem" + "value" : "Learning on use / focused on testing prediction" } } } }, - "stage_type.certainty.stage_i" : { - "comment" : "First certainty stage - poorly understood components with high uncertainty", + "stage_type.knowledge_management.stage_iii" : { + "comment" : "Third knowledge management stage - operational learning with prediction and verification", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Poorly Understood / exploring the unknown" + "value" : "Learning on operation / using prediction / verification" } } } }, - "stage_type.certainty.stage_ii" : { - "comment" : "Second certainty stage - rapid learning and discovery leading to better understanding", + "stage_type.knowledge_management.stage_iv" : { + "comment" : "Fourth knowledge management stage - known and accepted knowledge that is well understood", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Rapid Increase In Learning / discovery becomes refining" + "value" : "Known / accepted" } } } }, - "stage_type.certainty.stage_iii" : { - "comment" : "Third certainty stage - increased practical use leading to better fit for purpose", + "stage_type.knowledge.stage_i" : { + "comment" : "First evolution stage for knowledge - conceptual ideas without empirical validation", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Rapid increase in use / increasing fit for purpose" + "value" : "Concept" } } } }, - "stage_type.certainty.stage_iv" : { - "comment" : "Fourth certainty stage - commonly understood components with predictable behavior", + "stage_type.knowledge.stage_ii" : { + "comment" : "Second evolution stage for knowledge - hypotheses that can be tested and validated", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Commonly understood (in terms of use)" + "value" : "Hypothesis" } } } }, - "stage_type.publication_types.stage_i" : { - "comment" : "First publication stage - describing novel discoveries and unknown frontiers", + "stage_type.knowledge.stage_iii" : { + "comment" : "Third evolution stage for knowledge - theoretical frameworks supported by evidence", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Describe the wonder of the thing / the discovery of some marvel / a new land / an unknown frontier" + "value" : "Theory" } } } }, - "stage_type.publication_types.stage_ii" : { - "comment" : "Second publication stage - focused on building and learning with multiple competing models", + "stage_type.knowledge.stage_iv" : { + "comment" : "Fourth evolution stage for knowledge - accepted knowledge that is widely understood and proven", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Focused on build / construct / awareness and learning / many models of explanation / no accepted forms / a wild west" + "value" : "Accepted" } } } }, - "stage_type.publication_types.stage_iii" : { - "comment" : "Third publication stage - maintenance and operational focus with feature comparisons", + "stage_type.market" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Maintenance / operations / installation / comparison between competing forms / feature analysis" + "value" : "Market" } } } }, - "stage_type.publication_types.stage_iv" : { - "comment" : "Fourth publication stage - focused on practical use of accepted, nearly invisible components", + "stage_type.market_action" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Focused on use / increasingly an accepted, almost invisible component" + "value" : "Market Action" } } } }, - "stage_type.market.stage_i" : { - "comment" : "First market stage - undefined market with unclear value proposition", + "stage_type.market_action.stage_i" : { + "comment" : "First market action stage - gambling and gut-driven decision making", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Undefined Market" + "value" : "Gambling / driven by gut" } } } }, - "stage_type.market.stage_ii" : { - "comment" : "Second market stage - forming market with competing approaches and models", + "stage_type.market_action.stage_ii" : { + "comment" : "Second market action stage - exploring discovered value opportunities", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Forming Market / an array of competing forms and models of understanding" + "value" : "Exploring a \"found\" value" } } } }, - "stage_type.market.stage_iii" : { - "comment" : "Third market stage - growing market with consolidation toward accepted forms", + "stage_type.market_action.stage_iii" : { + "comment" : "Third market action stage - market analysis and listening to customer feedback", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Growing Market / consolidation to a few competing but more accepted forms" + "value" : "Market analysis / listening to customers" } } } }, - "stage_type.market.stage_iv" : { - "comment" : "Fourth market stage - mature market with stabilized and accepted forms", + "stage_type.market_action.stage_iv" : { + "comment" : "Fourth market action stage - metric-driven approach building what is needed", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Mature Market / stabilised to an accepted form" + "value" : "Metric driven / build what is needed" } } } }, - "stage_type.knowledge_management.stage_i" : { - "comment" : "First knowledge management stage - uncertain knowledge with unclear application", + "stage_type.market_perception" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Uncertain" + "value" : "Market Perception" } } } }, - "stage_type.knowledge_management.stage_ii" : { - "comment" : "Second knowledge management stage - learning through use and testing predictions", + "stage_type.market_perception.stage_i" : { + "comment" : "First market perception stage - chaotic, non-linear domain of experimental 'crazy' ideas", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Learning on use / focused on testing prediction" + "value" : "Chaotic (non-linear) / domain of the \"crazy\"" } } } }, - "stage_type.knowledge_management.stage_iii" : { - "comment" : "Third knowledge management stage - operational learning with prediction and verification", + "stage_type.market_perception.stage_ii" : { + "comment" : "Second market perception stage - domain of experts with specialized knowledge", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Learning on operation / using prediction / verification" + "value" : "Domain of \"experts\"" } } } }, - "stage_type.knowledge_management.stage_iv" : { - "comment" : "Fourth knowledge management stage - known and accepted knowledge that is well understood", + "stage_type.market_perception.stage_iii" : { + "comment" : "Third market perception stage - increasing expectations of use by professionals", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Known / accepted" + "value" : "Increasing expectation of use / domain of \"professionals\"" } } } }, - "stage_type.market_perception.stage_i" : { - "comment" : "First market perception stage - chaotic, non-linear domain of experimental 'crazy' ideas", + "stage_type.market_perception.stage_iv" : { + "comment" : "Fourth market perception stage - ordered, linear domain with trivial, formulaic applications", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Chaotic (non-linear) / domain of the \"crazy\"" + "value" : "Ordered (appearance of being linear) / trivial / formula to be applied" } } } }, - "stage_type.market_perception.stage_ii" : { - "comment" : "Second market perception stage - domain of experts with specialized knowledge", + "stage_type.market.stage_i" : { + "comment" : "First market stage - undefined market with unclear value proposition", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Domain of \"experts\"" + "value" : "Undefined Market" } } } }, - "stage_type.market_perception.stage_iii" : { - "comment" : "Third market perception stage - increasing expectations of use by professionals", + "stage_type.market.stage_ii" : { + "comment" : "Second market stage - forming market with competing approaches and models", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Increasing expectation of use / domain of \"professionals\"" + "value" : "Forming Market / an array of competing forms and models of understanding" } } } }, - "stage_type.market_perception.stage_iv" : { - "comment" : "Fourth market perception stage - ordered, linear domain with trivial, formulaic applications", + "stage_type.market.stage_iii" : { + "comment" : "Third market stage - growing market with consolidation toward accepted forms", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Ordered (appearance of being linear) / trivial / formula to be applied" + "value" : "Growing Market / consolidation to a few competing but more accepted forms" } } } }, - "stage_type.user_perception.stage_i" : { - "comment" : "First user perception stage - different, confusing, exciting, and potentially dangerous", + "stage_type.market.stage_iv" : { + "comment" : "Fourth market stage - mature market with stabilized and accepted forms", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Different / confusing / exciting / surprising / dangerous" + "value" : "Mature Market / stabilised to an accepted form" } } } }, - "stage_type.user_perception.stage_ii" : { - "comment" : "Second user perception stage - leading edge technology with uncertain results", + "stage_type.perception_in_industry" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Leading edge / emerging / unceirtanty over results" + "value" : "Perception In Industry" } } } }, - "stage_type.user_perception.stage_iii" : { - "comment" : "Third user perception stage - increasingly common with disappointment if not available", + "stage_type.perception_in_industry.stage_i" : { + "comment" : "First industry perception stage - future competitive advantage that is unpredictable and unknown", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Increasingly common / disappointed if not used or available / feeling left behind" + "value" : "Future source of competitive advantage / unpredictable / unknown" } } } }, - "stage_type.user_perception.stage_iv" : { - "comment" : "Fourth user perception stage - standard and expected, with shock if not available", + "stage_type.perception_in_industry.stage_ii" : { + "comment" : "Second industry perception stage - seen as competitive advantage with ROI and case examples", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Standard / expected / feeling of shock if not used" + "value" : "Seen as a scompetitive advantage / a differential / ROI / case examples" } } } }, - "stage_type.perception_in_industry.stage_i" : { - "comment" : "First industry perception stage - future competitive advantage that is unpredictable and unknown", + "stage_type.perception_in_industry.stage_iii" : { + "comment" : "Third industry perception stage - advantage through implementation and feature differentiation", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Future source of competitive advantage / unpredictable / unknown" + "value" : "Advantage through implementation / features / this model is better than that" } } } }, - "stage_type.perception_in_industry.stage_ii" : { - "comment" : "Second industry perception stage - seen as competitive advantage with ROI and case examples", + "stage_type.perception_in_industry.stage_iv" : { + "comment" : "Fourth industry perception stage - cost of doing business with accepted, defined models", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Seen as a scompetitive advantage / a differential / ROI / case examples" + "value" : "Cost of doing business / accepted / specific defined models" } } } }, - "stage_type.perception_in_industry.stage_iii" : { - "comment" : "Third industry perception stage - advantage through implementation and feature differentiation", + "stage_type.placeholder" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Advantage through implementation / features / this model is better than that" + "value" : "Evolution Stage" } } } }, - "stage_type.perception_in_industry.stage_iv" : { - "comment" : "Fourth industry perception stage - cost of doing business with accepted, defined models", + "stage_type.placeholder.i" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Cost of doing business / accepted / specific defined models" + "value" : "Stage I" } } } }, - "stage_type.focus_of_value.stage_i" : { - "comment" : "First value focus stage - high future worth requiring immediate investment", + "stage_type.placeholder.ii" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "High future worth but immediate investment" + "value" : "Stage II" } } } }, - "stage_type.focus_of_value.stage_ii" : { - "comment" : "Second value focus stage - seeking ways to profit and confirm return on investment", + "stage_type.placeholder.iii" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Seeking ways to profit and a ROI / seeking confirmation of value" + "value" : "Stage III" } } } }, - "stage_type.focus_of_value.stage_iii" : { - "comment" : "Third value focus stage - high profitability with focus on exploitation of valuable models", + "stage_type.placeholder.iv" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "High profitability per unit / a valuable model / a feeling of understanding / focus on exploitation" + "value" : "Stage IV" } } } }, - "stage_type.focus_of_value.stage_iv" : { - "comment" : "Fourth value focus stage - high volume with reducing margins as essential infrastructure", + "stage_type.practice" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "High volume / reducing margin / important but invisible / an essential component of something more complex" + "value" : "Practice" } } } }, - "stage_type.understanding.stage_i" : { - "comment" : "First understanding stage - poorly understood and unpredictable components", + "stage_type.practice.stage_i" : { + "comment" : "First evolution stage for practices - novel, experimental practices with uncertain outcomes", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Poorly Understood / unpredictable" + "value" : "Novel" } } } }, - "stage_type.understanding.stage_ii" : { - "comment" : "Second understanding stage - increasing understanding with development of measures", + "stage_type.practice.stage_ii" : { + "comment" : "Second evolution stage for practices - emerging practices showing some promise", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Increasing understanding / development of measures" + "value" : "Emerging" } } } }, - "stage_type.understanding.stage_iii" : { - "comment" : "Third understanding stage - increasing education with constant refinement of needs", + "stage_type.practice.stage_iii" : { + "comment" : "Third evolution stage for practices - good practices that are proven and reliable", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Increasing education / constant refinement of needs / measures" + "value" : "Good" } } } }, - "stage_type.understanding.stage_iv" : { - "comment" : "Fourth understanding stage - well-defined, stable, and measurable components", + "stage_type.practice.stage_iv" : { + "comment" : "Fourth evolution stage for practices - best practices that are widely adopted and standardized", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Believed to be well defined / stable / measurable" + "value" : "Best" } } } }, - "stage_type.comparison.stage_i" : { - "comment" : "First comparison stage - constantly changing, differential, and unstable", + "stage_type.publication_types" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Constantly changing / a differential / unstable" + "value" : "Publication Types" } } } }, - "stage_type.comparison.stage_ii" : { - "comment" : "Second comparison stage - learning from others with some evidential support", + "stage_type.publication_types.stage_i" : { + "comment" : "First publication stage - describing novel discoveries and unknown frontiers", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Learning from others / testing the water / some evidential support" + "value" : "Describe the wonder of the thing / the discovery of some marvel / a new land / an unknown frontier" } } } }, - "stage_type.comparison.stage_iii" : { - "comment" : "Third comparison stage - competing models with feature differences and evidence", + "stage_type.publication_types.stage_ii" : { + "comment" : "Second publication stage - focused on building and learning with multiple competing models", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Competing models / feature difference / evidential support" + "value" : "Focused on build / construct / awareness and learning / many models of explanation / no accepted forms / a wild west" } } } }, - "stage_type.comparison.stage_iv" : { - "comment" : "Fourth comparison stage - essential components where advantage is operational", + "stage_type.publication_types.stage_iii" : { + "comment" : "Third publication stage - maintenance and operational focus with feature comparisons", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Essential / any advantage is operational / accepted norm" + "value" : "Maintenance / operations / installation / comparison between competing forms / feature analysis" } } } }, - "stage_type.failure.stage_i" : { - "comment" : "First failure stage - high failure rate that is tolerated and assumed", + "stage_type.publication_types.stage_iv" : { + "comment" : "Fourth publication stage - focused on practical use of accepted, nearly invisible components", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "High / tolerated / assumed to be wrong" + "value" : "Focused on use / increasingly an accepted, almost invisible component" } } } }, - "stage_type.failure.stage_ii" : { - "comment" : "Second failure stage - moderate failure rate that is disappointing but not surprising", + "stage_type.section.characteristics" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Moderate / unsurprising if wrong but disappointed" + "value" : "Characteristics" } } } }, - "stage_type.failure.stage_iii" : { - "comment" : "Third failure stage - failure not tolerated with focus on constant improvement", + "stage_type.section.custom" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Not tolerated / focus on constant improvement / assumed to be in the right direction / resistance to changing the model" + "value" : "Custom" } } } }, - "stage_type.failure.stage_iv" : { - "comment" : "Fourth failure stage - surprise at failure with focus on operational efficiency", + "stage_type.section.general_properties" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Surprised by failure / focus on operational efficiency" + "value" : "General Properties" } } } }, - "stage_type.market_action.stage_i" : { - "comment" : "First market action stage - gambling and gut-driven decision making", - "extractionState" : "manual", + "stage_type.section.types" : { "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Gambling / driven by gut" + "value" : "Types" } } } }, - "stage_type.market_action.stage_ii" : { - "comment" : "Second market action stage - exploring discovered value opportunities", + "stage_type.ubiquity" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Exploring a \"found\" value" + "value" : "Ubiquity" } } } }, - "stage_type.market_action.stage_iii" : { - "comment" : "Third market action stage - market analysis and listening to customer feedback", + "stage_type.ubiquity.stage_i" : { + "comment" : "First ubiquity stage - rare components with limited adoption", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Market analysis / listening to customers" + "value" : "Rare" } } } }, - "stage_type.market_action.stage_iv" : { - "comment" : "Fourth market action stage - metric-driven approach building what is needed", + "stage_type.ubiquity.stage_ii" : { + "comment" : "Second ubiquity stage - slowly increasing adoption and awareness", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Metric driven / build what is needed" + "value" : "Slowly Increasing" } } } }, - "stage_type.efficiency.stage_i" : { - "comment" : "First efficiency stage - reducing cost of change through experimentation", + "stage_type.ubiquity.stage_iii" : { + "comment" : "Third ubiquity stage - rapidly increasing adoption as value becomes clear", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Reducing the cost of change (experimentation)" + "value" : "Rapidly Increasing" } } } }, - "stage_type.efficiency.stage_ii" : { - "comment" : "Second efficiency stage - reducing cost of waste through learning", + "stage_type.ubiquity.stage_iv" : { + "comment" : "Fourth ubiquity stage - widespread adoption throughout the applicable market or ecosystem", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Reducing cost of waste (Learning)" + "value" : "Widespread in the applicable market / ecosystem" } } } }, - "stage_type.efficiency.stage_iii" : { - "comment" : "Third efficiency stage - reducing cost of waste through continued learning", + "stage_type.understanding" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Reducing cost of waste (Learning)" + "value" : "Understanding" } } } }, - "stage_type.efficiency.stage_iv" : { - "comment" : "Fourth efficiency stage - reducing cost of deviation through volume operations", + "stage_type.understanding.stage_i" : { + "comment" : "First understanding stage - poorly understood and unpredictable components", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Reducing cost of deviation (Volume)" + "value" : "Poorly Understood / unpredictable" } } } }, - "stage_type.decision_drivers.stage_i" : { - "comment" : "First decision drivers stage - decisions driven by heritage and culture", + "stage_type.understanding.stage_ii" : { + "comment" : "Second understanding stage - increasing understanding with development of measures", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Heritage / culture" + "value" : "Increasing understanding / development of measures" } } } }, - "stage_type.decision_drivers.stage_ii" : { - "comment" : "Second decision drivers stage - decisions based on analysis and synthesis", + "stage_type.understanding.stage_iii" : { + "comment" : "Third understanding stage - increasing education with constant refinement of needs", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Analyses & synthesis" + "value" : "Increasing education / constant refinement of needs / measures" } } } }, - "stage_type.decision_drivers.stage_iii" : { - "comment" : "Third decision drivers stage - continued analysis and synthesis approaches", + "stage_type.understanding.stage_iv" : { + "comment" : "Fourth understanding stage - well-defined, stable, and measurable components", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Analyses & synthesis" + "value" : "Believed to be well defined / stable / measurable" } } } }, - "stage_type.decision_drivers.stage_iv" : { - "comment" : "Fourth decision drivers stage - decisions based on previous experience and patterns", + "stage_type.user_perception" : { "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Previous Experience" + "value" : "User Perception" } } } }, - "stage_type.behavior.stage_i" : { - "comment" : "First behavior stage - uncertain about when and how to use the component", + "stage_type.user_perception.stage_i" : { + "comment" : "First user perception stage - different, confusing, exciting, and potentially dangerous", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Uncertain when to use" + "value" : "Different / confusing / exciting / surprising / dangerous" } } } }, - "stage_type.behavior.stage_ii" : { - "comment" : "Second behavior stage - learning when to use the component appropriately", + "stage_type.user_perception.stage_ii" : { + "comment" : "Second user perception stage - leading edge technology with uncertain results", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Learning when to use" + "value" : "Leading edge / emerging / unceirtanty over results" } } } }, - "stage_type.behavior.stage_iii" : { - "comment" : "Third behavior stage - learning through practical use and experience", + "stage_type.user_perception.stage_iii" : { + "comment" : "Third user perception stage - increasingly common with disappointment if not available", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Learning through use" + "value" : "Increasingly common / disappointed if not used or available / feeling left behind" } } } }, - "stage_type.behavior.stage_iv" : { - "comment" : "Fourth behavior stage - known and common usage patterns that are well understood", + "stage_type.user_perception.stage_iv" : { + "comment" : "Fourth user perception stage - standard and expected, with shock if not available", "extractionState" : "manual", "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Known / common usage" + "value" : "Standard / expected / feeling of shock if not used" } } } diff --git a/Map/MapApp.swift b/Map/MapApp.swift index 54eb9be..5061807 100644 --- a/Map/MapApp.swift +++ b/Map/MapApp.swift @@ -12,6 +12,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see https://map.tranquil.systems. +import AppKit import Sparkle import SwiftUI @@ -23,7 +24,7 @@ struct MapApp: App { var body: some Scene { DocumentGroup(newDocument: MapDocument(text: nil)) { file in - MapEditor(document: file.$document, url: file.fileURL) + LazyMapEditor(document: file.$document, url: file.fileURL) .focusedSceneValue(\.document, file.$document) .focusedSceneValue(\.fileURL, file.fileURL) } @@ -38,3 +39,67 @@ struct MapApp: App { } } } + +struct LazyMapEditor: View { + @Binding var document: MapDocument + var url: URL? + + @FocusedBinding(\.document) var focusedDocument: MapDocument? + @FocusedValue(\.fileURL) var focusedFileURL: URL? + @State private var shouldRender = false + @State private var renderTask: Task<Void, Never>? + + var isActiveDocument: Bool { + guard let focusedDocument else { return false } + // Use URL for identification if available, otherwise fall back to text comparison + if let documentURL = url, + let focusedURL = focusedFileURL + { + return documentURL == focusedURL + } + // For new documents without URLs, use text comparison as fallback + return focusedDocument.text == document.text + } + + var body: some View { + if isActiveDocument && shouldRender { + MapEditor(document: $document, url: url) + } else { + Rectangle() + .fill(Color(NSColor.windowBackgroundColor)) + .overlay( + VStack(spacing: 16) { + Text("document.inactive") + .font(.title2) + .foregroundColor(.secondary) + if let url = url { + Text(url.lastPathComponent) + .font(.caption) + .foregroundColor(.secondary) + } + } + ) + .onChange(of: isActiveDocument) { _, newValue in + renderTask?.cancel() + renderTask = nil + + if newValue { + renderTask = Task { + try? await Task.sleep(nanoseconds: 50_000_000) + if !Task.isCancelled { + await MainActor.run { + shouldRender = true + } + } + } + } else { + shouldRender = false + } + } + .onAppear { + // Initialize focus state properly - don't render by default + shouldRender = false + } + } + } +} diff --git a/Map/Presentation/Base Components/MapRender/MapMask.swift b/Map/Presentation/Base Components/MapRender/MapMask.swift index 2c064b3..b477c0a 100644 --- a/Map/Presentation/Base Components/MapRender/MapMask.swift +++ b/Map/Presentation/Base Components/MapRender/MapMask.swift @@ -19,6 +19,7 @@ struct MapMask: View { let mapSize: CGSize let vertexSize: CGSize let vertices: [Vertex] + let labelPositions: [Int: CGPoint] let padding = CGFloat(5.0) var body: some View { @@ -36,8 +37,9 @@ struct MapMask: View { .clipShape(Capsule()) .offset( CGSize( - width: w(vertex.position.x) + vertexSize.width + padding, - height: h(vertex.position.y) + 7.0) + width: labelPositions[vertex.id]?.x + ?? (w(vertex.position.x) + vertexSize.width + padding), + height: labelPositions[vertex.id]?.y ?? (h(vertex.position.y) + 7.0)) ) .blendMode(.destinationOut) }.zIndex(1) @@ -64,5 +66,6 @@ struct MapMask: View { Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), - ]) + ], + labelPositions: [:]) } diff --git a/Map/Presentation/Base Components/MapRender/MapVertices.swift b/Map/Presentation/Base Components/MapRender/MapVertices.swift index f03bb79..e6617b3 100644 --- a/Map/Presentation/Base Components/MapRender/MapVertices.swift +++ b/Map/Presentation/Base Components/MapRender/MapVertices.swift @@ -19,6 +19,7 @@ struct MapVertices: View { let mapSize: CGSize let vertexSize: CGSize let vertices: [Vertex] + let labelPositions: [Int: CGPoint] let padding = CGFloat(5.0) var onDragVertex: (Vertex, CGFloat, CGFloat) -> Void = { _, _, _ in } @@ -36,8 +37,9 @@ struct MapVertices: View { .foregroundColor(.Theme.Map.labelColor) .offset( CGSize( - width: w(vertex.position.x) + vertexSize.width + padding, - height: h(vertex.position.y) + 7.0)) + width: labelPositions[vertex.id]?.x + ?? (w(vertex.position.x) + vertexSize.width + padding), + height: labelPositions[vertex.id]?.y ?? (h(vertex.position.y) + 7.0))) }.gesture( DragGesture() .onChanged { value in @@ -113,5 +115,6 @@ struct MapVertices: View { Vertex(id: 1, label: "A Square", position: CGPoint(x: 10.0, y: 20.0), shape: .square), Vertex(id: 2, label: "A triangle", position: CGPoint(x: 25, y: 32.0), shape: .triangle), Vertex(id: 3, label: "An X", position: CGPoint(x: 70.0, y: 70.0), shape: .x), - ]) + ], + labelPositions: [:]) } diff --git a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift index 3b97945..896fbd0 100644 --- a/Map/Presentation/Complex Components/MapRender/MapRenderView.swift +++ b/Map/Presentation/Complex Components/MapRender/MapRenderView.swift @@ -24,14 +24,13 @@ struct MapRenderView: View { @AppStorage("showMapBackground") var showMapBackground: Bool = true @AppStorage("useCustomFont") var useCustomFont: Bool = false @AppStorage("customFontName") var customFontName: String = "Helvetica" - var stage: Stage { Stage.stages(evolution) } - var parsedMap: ParsedMap { - MapParser.parse(content: document.text) - } + @State private var parsedMap: ParsedMap = ParsedMap.empty + @State private var smartLabelPositions: [Int: CGPoint] = [:] + @State private var parseTask: Task<Void, Never>? let mapSize = Dimensions.Map.size let padding = Dimensions.Map.padding @@ -41,6 +40,54 @@ struct MapRenderView: View { var onDragVertex: (Vertex, CGFloat, CGFloat) -> Void = { _, _, _ in } + private func parseMapInBackground() { + parseTask?.cancel() + let currentText = document.text + + parseTask = Task { + // Parse on background thread + let parsed = await Task.detached(priority: .userInitiated) { + MapParser.parse(content: currentText) + }.value + + // Calculate smart positions on background thread + let positions = await Task.detached(priority: .userInitiated) { + var positions: [Int: CGPoint] = [:] + + let axisLines = [ + Line(start: CGPoint(x: 0, y: 0), end: CGPoint(x: 0, y: mapSize.height)), + Line( + start: CGPoint(x: 0, y: mapSize.height), + end: CGPoint(x: mapSize.width, y: mapSize.height)), + ] + + for vertex in parsed.vertices { + positions[vertex.id] = SmartLabelPositioning.calculateOptimalLabelPosition( + for: vertex, + mapSize: mapSize, + vertexSize: vertexSize, + edges: parsed.edges, + opportunities: parsed.opportunities, + inertias: parsed.inertias, + axisLines: axisLines, + allVertices: parsed.vertices, + notes: parsed.notes + ) + } + + return positions + }.value + + // Update UI on main thread + if !Task.isCancelled { + await MainActor.run { + parsedMap = parsed + smartLabelPositions = positions + } + } + } + } + var body: some View { ZStack(alignment: .topLeading) { @@ -68,11 +115,12 @@ struct MapRenderView: View { MapIntertias(mapSize: mapSize, vertexSize: vertexSize, inertias: parsedMap.inertias) }.mask { MapMask( - mapSize: mapSize, vertexSize: vertexSize, vertices: parsedMap.vertices) + mapSize: mapSize, vertexSize: vertexSize, vertices: parsedMap.vertices, + labelPositions: smartLabelPositions) } MapVertices( mapSize: mapSize, vertexSize: vertexSize, vertices: parsedMap.vertices, - onDragVertex: onDragVertex) + labelPositions: smartLabelPositions, onDragVertex: onDragVertex) MapGroups(mapSize: mapSize, vertexSize: vertexSize, groups: parsedMap.groups).drawingGroup( opaque: true ).opacity(0.1) @@ -84,6 +132,23 @@ struct MapRenderView: View { width: mapSize.width + 2 * padding, height: mapSize.height + 2 * padding, alignment: .topLeading ) + .onAppear { + parseMapInBackground() + } + .onChange(of: document.text) { _, _ in + parseMapInBackground() + } + .onDisappear { + parseTask?.cancel() + } + } + + func h(_ dimension: CGFloat) -> CGFloat { + max(0.0, min(mapSize.height, dimension * mapSize.height / 100.0)) + } + + func w(_ dimension: CGFloat) -> CGFloat { + max(0.0, min(mapSize.width, dimension * mapSize.width / 100.0)) } } |