Change Color of a Diagram Shape without recalculating all shape positions?

1 Answer 32 Views
Diagram
Scott
Top achievements
Rank 1
Scott asked on 13 Nov 2025, 07:42 PM | edited on 13 Nov 2025, 08:08 PM

I'm writing an OnShapeClick method that (among other things) should change the Color of the selected shape. I am defining shapes as such: 

<DiagramShapes>
    @foreach (Node node in Nodes)
    {
        <DiagramShape Id="@node.LinkID" >
            <DiagramShapeContent Text="@node.GetDisplayName()" />
            <DiagramShapeFill Color="@node.GetColor()"/>
        </DiagramShape>
    }
</DiagramShapes>

In OnShapeClick, I am updating the Color by changing a field for the specified node that results in node.GetColor() returning a new Value, and this works. 

async Task OnPersonClick(DiagramShapeClickEventArgs Args)
{
    foreach (Node node in Nodes)
    {
        if (node.LinkID.Equals(Args.Id))
            node.State = SelectedState.True;
        else
            node.State = SelectedState.False;
    }
}

The issue is that when the Diagram is re-rendered, the positions of all the shapes are recalculated. resulting in any Shapes that the user has dragged out of position to revert to their original position, or in the case of the Force Diagram, a complete reshuffle of the Shapes in the Diagram. This is undesired behavior. 

In short, how do I update the Color (or any property) of a Diagram Shape while maintaining the current positions of all shapes within the Diagram?

1 Answer, 1 is accepted

Sort by
0
Dimo
Telerik team
answered on 18 Nov 2025, 11:08 AM | edited on 18 Nov 2025, 11:14 AM

Hello Scott,

In this scenario, you need to use the Diagram state and persist the shape X and Y property values. Use the following example as a reference. Note that the serialized Diagram state contains a lot more properties, but you can persist only the ones you need. You can also directly configure the initial Diagram state from JSON if you like.

https://blazorrepl.telerik.com/GflvvMvl11Wn8WXY57 

@using System.Text.Json
@using System.Text.Json.Serialization

<TelerikDiagram @ref="@DiagramRef" OnShapeClick="@OnDiagramShapeClick">
    <DiagramLayout Type="@DiagramLayoutType.Tree" />

    <DiagramShapes>
        <DiagramShape Id="shape1">
            <DiagramShapeContent Text="Shape 1" />
        </DiagramShape>
        <DiagramShape Id="shape2">
            <DiagramShapeContent Text="Shape 2" />
        </DiagramShape>
        <DiagramShape Id="shape3">
            <DiagramShapeContent Text="Shape 3" />
        </DiagramShape>
        <DiagramShape Id="shape4">
            <DiagramShapeContent Text="Shape 4" />
        </DiagramShape>
        <DiagramShape Id="shape5">
            <DiagramShapeContent Text="Shape 5" />
        </DiagramShape>
        <DiagramShape Id="shape6">
            <DiagramShapeContent Text="Shape 6" />
        </DiagramShape>
    </DiagramShapes>

    <DiagramConnections>
        <DiagramConnection FromId="shape1" ToId="shape2" />
        <DiagramConnection FromId="shape1" ToId="shape3" />
        <DiagramConnection FromId="shape2" ToId="shape4" />
        <DiagramConnection FromId="shape2" ToId="shape5" />
        <DiagramConnection FromId="shape3" ToId="shape6" />
    </DiagramConnections>
</TelerikDiagram>

@code {
    #nullable enable

    private TelerikDiagram? DiagramRef { get; set; }

    private async Task OnDiagramShapeClick(DiagramShapeClickEventArgs args)
    {
        DiagramJson = await DiagramRef!.SaveAsJsonAsync();
        JsonSerializerOptions jsonOptions = new JsonSerializerOptions()
        {
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        };

        DiagramState? diagramStateObject = JsonSerializer.Deserialize<DiagramState>(DiagramJson, jsonOptions);

        if (diagramStateObject is not null && diagramStateObject.Shapes is not null)
        {
            var rnd = Random.Shared;
            string newFillColor = $"rgb({rnd.Next(0, 127)},{rnd.Next(0, 127)},{rnd.Next(0, 127)})";

            diagramStateObject.Shapes.First(x => x.Id == args.Id).Fill.Color = newFillColor;

            DiagramJson = JsonSerializer.Serialize(diagramStateObject, jsonOptions);
            await DiagramRef!.LoadFromJsonAsync(DiagramJson);
        }
    }

    private string DiagramJson { get; set; } = string.Empty;

    public class DiagramState
    {
        public IEnumerable<ShapeDescriptor>? Shapes { get; set; }
        public IEnumerable<ConnectionDescriptor>? Connections { get; set; }
    }

    public class ShapeDescriptor
    {
        public string Id { get; set; } = Guid.NewGuid().ToString();

        // Needs a custom converter, because the serialized value is a string
        //public DiagramShapeType Type { get; set; } = DiagramShapeType.Rectangle;
        public double? X { get; set; }
        public double? Y { get; set; }

        public double? Height { get; set; } = 100;
        public double? Width { get; set; } = 100;

        public ShapeDescriptorContentFill Fill { get; set; } = new();

        public ShapeDescriptorContent Content { get; set; } = new();
    }

    public class ShapeDescriptorContent
    {
        public string Text { get; set; } = string.Empty;
    }

    public class ShapeDescriptorContentFill
    {
        public string? Color { get; set; }
    }

    public class ConnectionDescriptor
    {
        public string Id { get; set; } = Guid.NewGuid().ToString();

        public ConnectionDescriptorFromTo From { get; set; } = new();
        public ConnectionDescriptorFromTo To { get; set; } = new();
    }

    public class ConnectionDescriptorFromTo
    {
        public string ShapeId { get; set; } = string.Empty;
    }
}

Regards,
Dimo
Progress Telerik

Love the Telerik and Kendo UI products and believe more people should try them? Invite a fellow developer to become a Progress customer and each of you can get a $50 Amazon gift voucher.

Scott
Top achievements
Rank 1
commented on 09 Dec 2025, 07:28 PM

This now appears to cause the opposite issue, where if I alter any of the diagram's settings, such as changing the type from Tree to Force, the newly redrawn diagram loses any properties set by the OnClick function. 
Dimo
Telerik team
commented on 10 Dec 2025, 07:56 AM | edited

Hi Scott,

(Post updated)

The Diagram uses client-side rendering and represents an SVG image. When a component parameter value changes, the Blazor component life cycle kicks in. The Diagram detects the configuration change and sends its new server-side state to the client for a UI refresh. However, the shape colors in my previous example are not part of the server-side configuration, so the default colors are sent and the custom ones are lost.

To persist the shape background colors in the discussed scenario, you need to define the shapes entirely in JSON and not use any Razor markup for them. In this case, the server-side state will not hold any shape information and won't override the client-side shape state:

https://blazorrepl.telerik.com/mzFGvkbF211yP8An55

Out of curiosity, can you describe what is the real-world need for a Diagram to change its predefined Layout at runtime, especially if user has already customized the shape positions?

Tags
Diagram
Asked by
Scott
Top achievements
Rank 1
Answers by
Dimo
Telerik team
Share this question
or