Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions biz.ganttproject.mxgraph/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ dependencies {
implementation 'com.github.vlsi.mxgraph:jgraphx:4.2.+'
direct 'com.github.vlsi.mxgraph:jgraphx:4.2.+'

implementation 'com.fasterxml.jackson.core:jackson-databind:2.22.1'

testImplementation(
'org.junit.jupiter:junit-jupiter-api:5.+',
)
testRuntimeOnly(
'org.junit.jupiter:junit-jupiter-engine:5.13.2',
'org.junit.platform:junit-platform-launcher:1.13.2',
)
}

test {
useJUnitPlatform()
}

compileTestKotlin {
kotlinOptions {
jvmTarget = rootProject.java_version
}
}

jar {
Expand All @@ -39,6 +58,7 @@ compileKotlin {
}

addPublishing(project)
version = "2026-dev-json"
publishing {
publications {
core(MavenPublication) {
Expand Down
148 changes: 148 additions & 0 deletions biz.ganttproject.mxgraph/src/generated/ts/chart_model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
* TypeScript interfaces reflecting the JSON model produced by MxGraphPainter backed by
* JsonPainterImpl (biz.ganttproject.mxgraph) on the server side.
*
* The JSON model is a single flat list of chart primitives, kept in the order in which the
* canvas emits them to the painter, so that the client reproduces the overlapping of the
* shapes by painting them in the same order. The kind of a primitive is told by its `type`
* property. Every primitive carries its geometry at the top level, a nested `style` object
* with the visual properties, and a free-form `attributes` map that binds the primitive to a
* model object.
*
* The `style` object holds mxGraph style keys (see com.mxgraph.util.mxConstants), because the
* painting logic resolves the styles identically for both the mxGraph and the JSON backends.
* The commonly used keys are listed below as optional properties, but the map may contain any
* mxGraph style key, hence the index signature.
*
* Only rectangles, lines, rhombuses and texts are currently emitted. Text groups are not
* implemented yet.
*/

/** Free-form attributes attached to a primitive (e.g. task/resource identifiers). */
export type Attributes = Record<string, string>;

/** A style value as emitted into the JSON model. */
export type StyleValue = string | number;

/** Root object serialized by JsonPainterImpl.toJson(). */
export interface ChartModel {
/** The primitives in the order they were emitted by the canvas: paint them in this order. */
primitives: ChartPrimitive[];
}

/** A chart primitive, discriminated by its `type` property. */
export type ChartPrimitive =
| RectanglePrimitive
| LinePrimitive
| RhombusPrimitive
| TextPrimitive;

/** Visual style shared by filled shapes (rectangles and rhombuses). Keyed by mxGraph style names. */
export interface ShapeStyle {
/** Fill color as a `#rrggbb` string, or the mxGraph `none` sentinel. */
fillColor?: string;
/** Stroke/border color as a `#rrggbb` string, or the mxGraph `none` sentinel. */
strokeColor?: string;
/** Opacity as a percentage in the [0, 100] range. */
opacity?: number;
/** Border width in pixels, as prescribed by the chart style. */
strokeWidth?: number;
[key: string]: StyleValue | undefined;
}

/** Visual style for lines. Keyed by mxGraph style names. */
export interface LineStyle {
/** Arrow head at the end of the line: the mxGraph `classic` shape or the `none` sentinel. */
endArrow?: string;
/** Stroke color as a `#rrggbb` string. Falls back to `#000000`. */
strokeColor?: string;
/** Opacity as a percentage in the [0, 100] range. */
opacity?: number;
/** Whether the line is dashed: 1 for dashed, 0 otherwise. */
dashed?: number;
/**
* The dashes of a dashed line: the lengths of the dashes and the gaps in pixels, separated by
* spaces, repeated along the line. Absent if the chart style prescribes no particular dashes.
*/
dashPattern?: string;
/** Line width in pixels, as prescribed by the chart style. */
strokeWidth?: number;
[key: string]: StyleValue | undefined;
}

/**
* Visual style for texts. Keyed by mxGraph style names.
*
* The alignment values tell how the label is placed relative to the (x, y) anchor point of the
* primitive, and the spacings are the paddings of the chart style which the alignment formulas
* take into account (see TextPainter in biz.ganttproject.core, the desktop reference renderer).
*/
export interface TextStyle {
/** Horizontal alignment: the mxGraph `left`, `center` or `right` value. */
align?: string;
/** Vertical alignment: the mxGraph `top`, `middle` or `bottom` value. */
verticalAlign?: string;
/** Font color as a `#rrggbb` string. Falls back to `#000000`. */
fontColor?: string;
/** Font size in pixels. */
fontSize?: number;
/** Font family name. */
fontFamily?: string;
/** Text opacity as a percentage in the [0, 100] range. */
textOpacity?: number;
/** Padding above the label, in pixels. */
spacingTop?: number;
/** Padding below the label, in pixels. */
spacingBottom?: number;
/** Padding on the left of the label, in pixels. */
spacingLeft?: number;
/** Padding on the right of the label, in pixels. */
spacingRight?: number;
[key: string]: StyleValue | undefined;
}

export interface RectanglePrimitive {
type: 'rectangle';
/** X coordinate of the top-left corner. */
x: number;
/** Y coordinate of the top-left corner. */
y: number;
width: number;
height: number;
style: ShapeStyle;
attributes: Attributes;
}

export interface RhombusPrimitive {
type: 'rhombus';
/** X coordinate of the bounding box top-left corner. */
x: number;
/** Y coordinate of the bounding box top-left corner. */
y: number;
width: number;
height: number;
style: ShapeStyle;
attributes: Attributes;
}

export interface LinePrimitive {
type: 'line';
startX: number;
startY: number;
finishX: number;
finishY: number;
style: LineStyle;
attributes: Attributes;
}

export interface TextPrimitive {
type: 'text';
/** X coordinate of the anchor point, interpreted according to `style.align`. */
x: number;
/** Y coordinate of the anchor point, interpreted according to `style.verticalAlign`. */
y: number;
/** The label which fits into the space available for this text. */
text: string;
style: TextStyle;
attributes: Attributes;
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ import com.mxgraph.util.mxConstants
import java.awt.Color

internal class ColouredRectanglePainter(
private val mxPainterImpl: MxPainterImpl,
private val painter: PainterImpl,
private val color: Color
) : MxGraphPainter.RectanglePainter {
override fun paint(rectangle: Canvas.Rectangle) {
mxPainterImpl.paintRectangle(
painter.paintRectangle(
rectangle.leftX, rectangle.topY,
rectangle.width, rectangle.height,
mapOf(mxConstants.STYLE_FILLCOLOR to color), rectangle.attributes
mapOf(mxConstants.STYLE_FILLCOLOR to color.toHexString()), rectangle.attributes
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import net.sourceforge.ganttproject.chart.ChartUIConfiguration
import java.awt.Color

internal class DayoffPainter(
private val mxPainterImpl: MxPainterImpl,
private val painter: PainterImpl,
private val uiConfig: ChartUIConfiguration
) : MxGraphPainter.RectanglePainter {

Expand All @@ -36,7 +36,7 @@ internal class DayoffPainter(
mxConstants.STYLE_STROKECOLOR to Color.BLACK.toHexString(),
mxConstants.STYLE_OPACITY to 40
)
mxPainterImpl.paintRectangle(
painter.paintRectangle(
rectangle.leftX, rectangle.topY + margin, rectangle.width, rectangle.height - 2 * margin,
mxStyle, rectangle.attributes
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
Copyright 2026 BarD Software s.r.o

This file is part of GanttProject, an open-source project management tool.

GanttProject 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.

GanttProject 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.

You should have received a copy of the GNU General Public License
along with GanttProject. If not, see <http://www.gnu.org/licenses/>.
*/
package biz.ganttproject.mxgraph

import com.fasterxml.jackson.databind.ObjectMapper

/**
* This is the low-level counterpart of [MxPainterImpl]. Instead of building an mxGraph model,
* it accumulates the chart primitives as plain maps and serializes them to JSON. The resulting
* JSON model is intended to be rendered on the client side.
*
* All primitives go into a single list, in the order in which the canvas emits them, so that
* the client can paint them in the same order and reproduce the overlapping of the shapes.
* The kind of a primitive is told by its "type" property.
*/
class JsonPainterImpl : PainterImpl {
private val primitives = mutableListOf<Map<String, Any?>>()
private val mapper = ObjectMapper()

fun toJson(): String {
return mapper.writeValueAsString(linkedMapOf("primitives" to primitives))
}

override fun paintRectangle(leftX: Int, topY: Int, width: Int, height: Int, style: Map<String, Any?>, attributes: Map<String, String>) {
primitives.add(linkedMapOf(
"type" to TYPE_RECTANGLE,
"x" to leftX,
"y" to topY,
"width" to width,
"height" to height,
"style" to style,
"attributes" to attributes
))
}

override fun paintLine(startX: Int, startY: Int, finishX: Int, finishY: Int, style: Map<String, Any?>, attributes: Map<String, String>) {
primitives.add(linkedMapOf(
"type" to TYPE_LINE,
"startX" to startX,
"startY" to startY,
"finishX" to finishX,
"finishY" to finishY,
"style" to style,
"attributes" to attributes
))
}

/**
* The text itself is passed in the "text" attribute by [MxTextPainter], which has already
* chosen the label which fits into the available space.
*/
override fun paintText(leftX: Int, bottomY: Int, attributes: Map<String, String>, style: Map<String, Any?>) {
primitives.add(linkedMapOf(
"type" to TYPE_TEXT,
"x" to leftX,
"y" to bottomY,
"text" to attributes["text"],
"style" to style,
"attributes" to attributes
))
}

override fun paintRhombus(leftX: Int, topY: Int, width: Int, height: Int, style: Map<String, Any?>, attributes: Map<String, String>) {
primitives.add(linkedMapOf(
"type" to TYPE_RHOMBUS,
"x" to leftX,
"y" to topY,
"width" to width,
"height" to height,
"style" to style,
"attributes" to attributes
))
}

override fun clear() {
primitives.clear()
}

override fun beginUpdate() {}

override fun endUpdate() {}
}

/** Values of the "type" property which discriminates the primitives in the JSON model. */
const val TYPE_RECTANGLE = "rectangle"
const val TYPE_LINE = "line"
const val TYPE_RHOMBUS = "rhombus"
const val TYPE_TEXT = "text"
Loading
Loading