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

1 Answer 53 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?

Scott
Top achievements
Rank 1
commented on 08 Jan 2026, 05:47 PM | edited

Basically, we are using this to allow the user to visualize how different arbitrarily connected elements, allowing the users these options allow them to explore the connected data and visually move them to make sense of it.  

The issue I ran into with defining the diagram solely in JSON is that I would have to manually define the positions of the Shapes, where I would like to utilize the capabilities of Telerik to calculate those. Though I was able to fix this issue by only usings the Razor markup settings for Shape Fill and Stroke when Settings have been changed more recently that a Shape has been selected. 

Scott
Top achievements
Rank 1
commented on 08 Jan 2026, 06:25 PM

Now I am attempting to use a JS Visual function to Visualize more Information, but even though the using the JSON to preserve location (and other attributes) does run and work, but it does so without using the Visual function. Then the Diagram is Immediately Re-rendered utilizing the Visual function but is back to recalculating all Shape positions. 
Dimo
Telerik team
commented on 09 Jan 2026, 07:23 AM

Indeed, currently you need to specify X and Y explicitly in the JSON, but we will remove this requirement in a future enhancement.

With regard to the second question, it's good to show examples of what you are trying to do, so that we eliminate the guesswork on our part.

I combined my previous example with the one in our Shape Visual Function documentation and here is the result:

https://blazorrepl.telerik.com/cquvktaB17tMyMoE00

You can still click on shapes to change their background color. A Visual function is used at all times while preserving the shape positions (until you toggle the layout).

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