Skip to content

Make Graph.createEdgeHandler and Graph.isOrthogonal configurable and independent from EdgeStyle implementation #767

@tbouffard

Description

@tbouffard

Warning

The following issues may be implemented before the one described here (see below for details):

Is your feature request related to a problem? Please describe.

In version 0.17.0, the methods Graph.isOrthogonal and Graph.createEdgeHandler contain hardcoded references to several EdgeStyle implementations. This tight coupling creates multiple issues:

  1. Poor maintainability:

    • When adding a new EdgeStyle to maxGraph, these methods must be updated manually to ensure compatibility.
    • If this step is forgotten (as it was when ManhattanConnector was introduced), bugs or inconsistencies can occur. See #707 for an example.
    • This design forces contributors and users to be aware of internal dependencies.
  2. Limited extensibility:

    • Applications using custom EdgeStyle implementations must override these two methods to ensure correct behavior (this is what has to be done in the Wires story which introduces the WireConnector).
    • Inconsistent overrides (for example, missing the isOrthogonal override) lead to incorrect rendering or behavior.
  3. Broken tree-shaking:

    • Because the methods reference all EdgeStyle types directly, all of them are bundled into the final build, regardless of actual usage.
    • For example, even if the application does not use TopToBottom or ManhattanConnector, they are still included.
    • This is especially problematic for large edge styles like ManhattanConnector (several KB minified).

Reminder: what these methods do

  • isOrthogonal: Returns true if edge points should be computed such that the resulting edge has only horizontal or vertical segments.
  • createEdgeHandler: Creates a new EdgeHandler instance for the given CellState, depending on the EdgeStyle used to render the edge. The handler manages edge reconnections, control points, and label positioning.

Warning

These methods will be relocated in a future version (#762):

  • Graph.isOrthogonalGraphView
  • Graph.createEdgeHandlerSelectionCellsHandler

Note

Be aware that in version 0.17.0, the definition of EdgeStyle makes tree shaking impossible.
Importing a single EdgeStyle function imports all of them due to how they are packaged together in the EdgeStyle class.
This must be addressed first to make the tree-shaking benefits of this proposal fully effective. See #759

Describe the solution you'd like

We want to provide a mechanism that offers the same behavior in createEdgeHandler and isOrthogonal as today, but without hardcoding references to specific EdgeStyle implementations.

Ideally, these methods should rely only on the EdgeStyle functions that are globally registered for rendering. This would solve both the maintainability issue and the tree-shaking limitation.

Categorizing EdgeStyle metadata at registration

Each EdgeStyle function should be registered with two distinct metadata fields, each serving one of the two internal behaviors we want to preserve:

  • createEdgeHandler needs to determine what type of handler to instantiate
  • isOrthogonal needs to determine whether the edge should follow orthogonal routing

Since these two concerns are independent, it is clearer and safer to define two explicit fields in the registration metadata.

For createEdgeHandler, we define a category field:

This field determines which EdgeHandler implementation to use:

  • 'elbow'
  • 'segment'
  • 'default' (used when no specific category is matched)

For isOrthogonal, we use a simple boolean:

  • true for styles that result in orthogonal routing (typically associated with 'elbow' or 'segment' handlers)
  • false otherwise

Note

Loop styles (for example, Loop) technically use the 'elbow' handler, but they should not be considered orthogonal in the context of isOrthogonal.

This registration mechanism gives us flexibility while maintaining clarity, and avoids reliance on naming conventions or type-checking against known functions.

Proposal

To implement this, we propose introducing a dedicated EdgeStyleRegistry. This new registry would replace hardcoded logic with a flexible metadata-based approach. It would serve as the single source of truth for determining how a given EdgeStyle should be handled, both in terms of which EdgeHandler to use and whether it should be considered orthogonal.

The registry would store two key metadata fields for each registered EdgeStyle function:

  • isOrthogonal: A boolean that indicates whether the style should be treated as orthogonal
  • edgeHandlerCategory: A string category that identifies the appropriate handler type (for example, 'elbow', 'segment', 'default'). If the category is not provided, a fallback handler (such as defaultEdgeHandler) would be used

The StyleRegistry would remain, but its scope would be limited to Perimeter functions only, making its role more explicit and reducing the chances of confusion between style types.

Example registration API

To register a new EdgeStyle function, the API could look like this:

EdgeStyleRegistry.register(myEdgeStyleFunction, {
  isOrthogonal: true,
  edgeHandlerCategory: 'segment'
});

This approach is simple, declarative, and scales well. If a style is not explicitly registered:

  • createEdgeHandler will fall back to a default handler
  • isOrthogonal will return false

If needed, naming can be adjusted (for example, edgeHandlerKind, handlerKind, isOrthogonalRouting), but the structure should remain straightforward.

Tip

The type of property categorizing the associated handler will be a string including the 3 builtin use cases (for type guidance) and will also any string to allow extension

Benefits

  • Easier maintenance: no need to update core methods when adding or changing an EdgeStyle, just register it with the right metadata.
  • Better tree-shaking: only registered and used EdgeStyle functions are included in the final bundle.
  • Improved extensibility: custom or third-party EdgeStyle functions can integrate cleanly, without needing to override internal methods.
  • Cleaner architecture: separating rendering logic from metadata makes the system easier to understand and maintain.

Drawbacks and considerations

This approach introduces flexibility, but it also adds some constraints. This mainly applies to applications that won't use the default builtins registration (#760):

  • Registration becomes mandatory for default builtins provided by maxGraph: unregistered edge styles assigned to CellStateStyle.edgeStyle will no longer work correctly with createEdgeHandler and isOrthogonal.
    In the prior implementation, using a builtin EdgeStyle without registration worked because it was hard coded in the 2 Graph methods.
  • Risk of misconfiguration: forgetting to register a custom edge style, or registering a builtin edge style with incorrect metadata, may cause rendering issues.
    Not registering a custom EdgeStyle will behave as in the past: it is considered as not orthogonal and the default EdgeHandler is used.

To reduce friction:

  • The JSDoc for CellStateStyle.edgeStyle will be updated to highlight the registration requirement.
  • The official documentation will clearly describe this behavior with examples.

Tip

Libraries or teams can provide a helper function to register their custom EdgeStyle functions with the appropriate metadata. This helps ensure consistency and reduces boilerplate.

Describe alternatives you've considered

We considered keeping StyleRegistry and introducing a second metadata registry. However, this would:

  • Increase complexity
  • Introduce a risk of mismatched registrations between style logic and metadata

Instead, we propose:

  • Making StyleRegistry exclusive to Perimeter functions
  • Introducing EdgeStyleRegistry as the single registry for EdgeStyle functions and their metadata

Tasks

  • Introduce EdgeStyleRegistry and use it in GraphView to resolve the actual EdgeStyle function from the cellStyle.edgeStyle value
  • Use it in Graph.isOrthogonal to retrieve the category of the current EdgeStyle
  • Use it in Graph.createEdgeHandler to retrieve the category of the current EdgeStyle
  • Update the JSDoc of cellStyle.edgeStyle and the list of default edge styles; remove references to StyleRegistry
  • Introduce PerimeterRegistry to accept only Perimeter functions as values (breaking change)
  • Remove StyleRegistry (breaking change)
  • Add EdgeStyleRegistry to the global configuration documentation
  • Add breaking changes in changelog

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions