Diagram Component fails to load Diagram js file.

Hi,

In my project i am trying to use the diagram component to draw. Currently I have this all working being able to drag from the palette and save, zoom in and out etc. My issue is trying to load a diagram onto the diagram component. 

When I leave the LoadDiagram method the diagram disappears and can no longer be seen on the diagram component itself.

I have no idea why this is happening.


@page "/machinemonitoring"


@using Fitfactory.MES.Web.Components.Controls

@using Fitfactory.MES.Web.DataServices

@using Fitfactory.MES.Web.Models

@inject ISensorDataService SensorDataService

@inject ISensorTransactionDataService SensorTransactionDataService

@rendermode InteractiveServer

@using System.IO

@using Syncfusion.Blazor.Diagram.SymbolPalette

@using shapes = Syncfusion.Blazor.Diagram.NodeShapes

@using SymbolExpandMode = Syncfusion.Blazor.Navigations.ExpandMode

@using Syncfusion.Blazor.Navigations


<div class="toolbar-container">

    <div class="custom-toolbar">


        <div class="tooltip-container">

            <label class="switch">

                <input type="checkbox" @onchange="OnModeChanged" />

                <span class="slider"></span>

            </label>

            <div class="tooltip">@modeLabel</div>

        </div>


        <button class="toolbar-btn" @onclick="LoadDiagram">

            <img src="img/save.png" alt="Save" class="icon" />

        </button>


        <button class="toolbar-btn" @onclick="ZoomIn">

            <img src="img/zoomin.png" alt="Zoom In" class="icon" />

        </button>


        <button class="toolbar-btn" @onclick="ZoomOut">

            <img src="img/zoomout.png" alt="Zoom Out" class="icon" />

        </button>


        <div class="current-datetime">

            @currentDateTime

        </div>


    </div>

</div>


<div class="diagram-container">


    <div class="palette-component @(isViewMode ? "hidden" : "")">

        <SfSymbolPaletteComponent @ref="paletteComponent"

        Height="800px"

        Width="15vw"

        Palettes="@palettes"

        GetSymbolInfo="GetSymbolInfo"

        SymbolHeight="100"

        SymbolWidth="100"

        PaletteExpandMode="SymbolExpandMode.Multiple"

        EnableAnimation="true"

        SymbolDragPreviewSize="@symbolDragPreviewSize"

        AllowDrag="@isEditMode">

        </SfSymbolPaletteComponent>

    </div>


    <SfDiagramComponent @ref="@DiagramComponent" Width="100%" Height="818px" Nodes="@nodes" >

    </SfDiagramComponent>


</div>


@code {


    #region Initializers & Variables

    DiagramSize symbolDragPreviewSize;

    SymbolExpandMode paletteExpandMode = SymbolExpandMode.Multiple;

    public DiagramObjectCollection<Palette> palettes;

    public DiagramObjectCollection<NodeBase> basicShapes = new DiagramObjectCollection<NodeBase>();

    public static SfDiagramComponent DiagramComponent = new SfDiagramComponent();

    SfSymbolPaletteComponent paletteComponent;

    private DiagramObjectCollection<Node> nodes = new DiagramObjectCollection<Node>();

    private bool isEditMode = false;

    private string modeLabel = "View Mode";

    private string currentDateTime;

    private bool isDataLoaded = false;

    private string DiagramContainerClass => isEditMode ? "diagram-container" : "diagram-container full-width";

    private List<Sensor> Sensors;

    private System.Threading.Timer _timer;


    [Inject]

    protected IJSRuntime jsRuntime { get; set; }


    protected override async Task OnInitializedAsync()

    {

        await Task.Delay(2000);

        await InitializeSensorNodes();

    }


    protected override async Task OnAfterRenderAsync(bool firstRender)

    {

        if (firstRender)

        {

            // Ensure that the components are initialized before setting Targets

            if (paletteComponent != null && DiagramComponent != null)

            {

                paletteComponent.Targets = new DiagramObjectCollection<SfDiagramComponent>

                {

                    DiagramComponent

                };

            }

        }

    }

        private async Task InitializeSensorNodes()

        {

            palettes = new DiagramObjectCollection<Palette>();

            Sensors = await SensorDataService.GetByCompany("*CompanyID*");


            var sensorNodes = SensorNodesHelper.CreateSensorNodes(Sensors);


            palettes.Add(new Palette()

            {

                ID = "Sensors",

                Title = "Machines",

                Symbols = new DiagramObjectCollection<NodeBase>(sensorNodes.Cast<NodeBase>().ToList()),

                IsExpanded = true,

            });


            CreatePaletteNode(NodeBasicShapes.Rectangle, "Rectangle");

            CreatePaletteNode(NodeBasicShapes.RightTriangle, "Right Triangle");

            CreatePaletteNode(NodeBasicShapes.Ellipse, "Cirlce");

            CreatePaletteNode(NodeBasicShapes.Triangle, "Triangle");


            palettes.Add(new Palette()

            {

                Symbols = new DiagramObjectCollection<NodeBase>(basicShapes.Cast<NodeBase>().ToList()),

                Title = "Shop Floor Items",

                ID = "Basic Shapes",

                IsExpanded = false,

            });


            StateHasChanged();

        }

        #endregion


        #region Misc

        private void CreatePaletteNode(NodeBasicShapes basicShape, string id)

        {

            Node node = new Node()

            {

                ID = id,

                Shape = new BasicShape() { Type = NodeShapes.Basic, Shape = basicShape },

                Style = new ShapeStyle() { Fill = "#C8C8C8" },

                Annotations = new DiagramObjectCollection<ShapeAnnotation>

                {

                    new ShapeAnnotation

                    {

                        Style = new TextStyle()

                        {

                            Color = "White",

                        }

                    }

                },

            };

            basicShapes.Add(node);

        }


        private Node CreateBasicNode(string id, NodeBasicShapes type)

        {

            return new Node()

            {

                ID = id,

                Shape = new BasicShape()

                {

                    Type = shapes.Basic,

                    Shape = type

                },

                Annotations = new DiagramObjectCollection<ShapeAnnotation>

            {

                new ShapeAnnotation

                {

                    Style = new TextStyle()

                    {

                        Color = "Black",

                    }

                }

            }

            };

        }


        public void UpdatePaletteConstraints()

        {

            if (paletteComponent != null)

            {

                foreach (var palette in palettes)

                {

                    foreach (var symbol in palette.Symbols)

                    {

                        if (symbol is Node node)

                        {

                            node.Constraints = NodeConstraints.Default;


                        }

                    }

                }

            }

        }


        private SymbolInfo GetSymbolInfo(IDiagramObject symbol)

        {

            var symbolInfo = new SymbolInfo();

            if (symbol is Node node)

            {

                symbolInfo.Width = 75;

                symbolInfo.Height = 75;

                symbolInfo.Description = new SymbolDescription()

                {

                    Text = node.Annotations.FirstOrDefault()?.Content ?? node.ID,

                    Style = new TextStyle()

                    {

                        FontSize = 11,

                        TextOverflow = TextOverflow.Ellipsis,

                    },

                    Margin = new DiagramThickness() { Top = 10, Bottom = 10 }

                };

            }

            return symbolInfo;

        }



    #endregion


    #region Misc


    private async Task OnNodeCreated(object args)

    {

        var node = args as Node;


        if (node != null)

        {

            var sensor = node.Data as Sensor;


            if (sensor != null)

            {

                node.Annotations.Add(new ShapeAnnotation() { Content = sensor.Name });


                node.Width = 120;

                node.Height = 120;

            }

        }

    }


    public async Task Download(string fileName)

    {

        string diagramData = DiagramComponent.SaveDiagram();


        var base64Data = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(diagramData));


        await jsRuntime.InvokeVoidAsync("eval", $@"

        (function() {{

            const data = atob('{base64Data}');

            const blob = new Blob([data], {{ type: 'application/json' }});

            const url = URL.createObjectURL(blob);

            const anchor = document.createElement('a');

            anchor.rel='nofollow' href = url;

            anchor.download = '{fileName}';

            anchor.click();

            URL.revokeObjectURL(url);

        }})();

    ");

    }


    [Inject]

    public HttpClient HttpClient { get; set; }

    public async Task LoadDiagram()

    {

        DiagramComponent.BeginUpdate();


        try

        {

            string filePath = @"C:\Users\AlessandroCirignaco\Downloads\ShopFloorLayout.json";


            if (File.Exists(filePath))

            {

                string diagramData = await File.ReadAllTextAsync(filePath);


                if (!string.IsNullOrEmpty(diagramData))

                {

                    await InvokeAsync(async () =>

                    {

                        await DiagramComponent.LoadDiagramAsync(diagramData);

                    });

                    //await UpdateNodeColors();

                }

            }

        }

        catch (Exception ex)

        {

            // Handle exceptions, e.g., file not found or IO errors

            Console.WriteLine($"An error occurred: {ex.Message}");

        }


        await DiagramComponent.EndUpdateAsync();


    }  //AFTER THIS LINE THE DIAGRAM DISAPPEARS


    private async Task ApplyNodeConstraints()

    {

        modeLabel = isEditMode ? "Edit Mode" : "View Mode";


        DiagramComponent.BeginUpdate();

        foreach (var node in DiagramComponent.Nodes)

        {

            if (node != null)

            {

                node.Constraints = isEditMode

                    ? NodeConstraints.Default

                    : NodeConstraints.Default & ~NodeConstraints.Select;

            }

        }


        await DiagramComponent.EndUpdateAsync();

    }



    private async Task UpdateNodeColors()

    {

        if (DiagramComponent == null || DiagramComponent.Nodes == null)

        {

            return;

        }


        string endTime = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss");

        string startTime = DateTime.UtcNow.AddHours(-8).ToString("yyyy-MM-dd HH:mm:ss");


        DiagramComponent.BeginUpdate();


        try

        {

            foreach (var node in DiagramComponent.Nodes)

            {

                if (node.Data is Sensor sensor)

                {

                    List<SensorTransaction> transactions = await SensorTransactionDataService.GetSensorTransactionsForSelectedDateRange(sensor.SensorId, startTime, endTime);


                    if (transactions != null)

                    {

                        var lastTransaction = transactions.OrderByDescending(t => t.CreatedDate).FirstOrDefault();


                        if (lastTransaction != null)

                        {

                            var backgroundColor = lastTransaction.State == 1 ? "Green" : "Red";


                            node.Style.Fill = backgroundColor;


                            StateHasChanged();

                        }

                    }

                }

            }

        }

        catch (Exception ex)

        {

            Console.WriteLine($"An error occurred while updating node colors: {ex.Message}");

        }

        finally

        {

            await DiagramComponent.EndUpdateAsync();

        }


        StateHasChanged();

    }

    #endregion


    #region Events

    private async Task OnModeChanged(Microsoft.AspNetCore.Components.ChangeEventArgs args)

    {

        isEditMode = (bool)args.Value;

        modeLabel = isEditMode ? "Edit Mode" : "View Mode";

        UpdatePaletteConstraints();

        ToggleViewMode();

        await ApplyNodeConstraints();

        StateHasChanged();

    }


    private async Task SaveDiagram()

    {

        string fileName = "ShopFloorLayout";

        await Download(fileName);

    }


    private void ZoomIn()

    {

        Console.WriteLine("Zooming In");

        DiagramComponent.Zoom(1.2, new DiagramPoint() { X = 100, Y = 100 });

    }


    private void ZoomOut()

    {

        Console.WriteLine("Zooming Out");

        DiagramComponent.Zoom(1 / 1.2, new DiagramPoint() { X = 100, Y = 100 });

    }


    private bool isViewMode = true;


    private string DiagramClass => isViewMode ? "diagram-fullscreen" : "diagram-with-palette";


    private void ToggleViewMode()

    {

        isViewMode = !isViewMode;


        StateHasChanged();

    }


    #endregion

}

Above is my main class where all functionality is stored

using Fitfactory.MES.Web.Models;

using Syncfusion.Blazor.Diagram;


namespace Fitfactory.MES.Web.Components

{

    public class SensorNodesHelper

    {

        public static DiagramObjectCollection<NodeBase> CreateSensorNodes(IEnumerable<Sensor> sensors)

        {

            var nodes = sensors.Select(sensor => new Node

            {

                ID = sensor.Id.ToString(),

                Shape = new BasicShape

                {

                    Type = NodeShapes.Basic,

                    Shape = NodeBasicShapes.Rectangle

                },

                Style = new ShapeStyle

                {

                    Fill = "#919191"

                },

                Annotations = new DiagramObjectCollection<ShapeAnnotation>

                {

                    new ShapeAnnotation

                    {

                        Content = sensor.Name,

                        HorizontalAlignment = HorizontalAlignment.Center,

                        VerticalAlignment = VerticalAlignment.Center,

                        Style = new TextStyle

                        {

                            Color = "White",

                        }

                    }

                },

                Tooltip = new DiagramTooltip

                {

                    Content = sensor.Name,

                    ShowTipPointer = true,

                },

                Constraints = NodeConstraints.Drag | NodeConstraints.Tooltip,

                Data = sensor,

            }).Cast<NodeBase>().ToList();


            return new DiagramObjectCollection<NodeBase>(nodes);

        }

    }

}

Helper class to add Sensors to the palette


3 Replies

BR Balavignesh RaviChandran Syncfusion Team December 3, 2024 08:18 AM UTC

Hi Alessandro Cirignaco,

Based on the information provided and the behavior you’re describing, it appears that the issue might be related to missing two-way data binding for the nodes and connectors of the Syncfusion Diagram component.

The Syncfusion Diagram component requires proper two-way data binding (@bind-Nodes and @bind-Connectors) to track and manage the state of the diagram. Without two-way binding, the diagram's state might not be updated correctly when loading saved data, leading to issues such as the diagram disappearing or behaving unexpectedly.

How to Resolve

Please ensure that you are using the @bind- directive for both Nodes and Connectors in your Razor component. Here's an example of how you can implement this:

<SfDiagramComponent Height="700px" @ref="@diagram" @bind-nodes="@nodes"></SfDiagramComponent>

<span id='diagramName' style="display:none">DiagramComponent</span>

</div>

<SfButton OnClick="@SaveDiagram" >Save</SfButton>

<SfButton OnClick="@LoadDiagram">Load</SfButton>

<SfUploader @ref="@uploadFiles" ID="UploadFiles" ShowFileList="false" AllowedExtensions="@ExtensionType">

                <UploaderEvents OnUploadStart="@OnUploadFileSelected"></UploaderEvents>

                <UploaderAsyncSettings SaveUrl="https://aspnetmvc.syncfusion.com/services/api/uploadbox/Save" RemoveUrl="https://aspnetmvc.syncfusion.com/services/api/uploadbox/Remove"></UploaderAsyncSettings>

            </SfUploader> 

@code{

    //Reference to uploder

    SfUploader uploadFiles;

    SfDiagramComponent diagram;

    private string fileName;

    DiagramObjectCollection<Node> nodes = new DiagramObjectCollection<Node>();

    protected override void OnInitialized()

    {

        Node node = new Node()

            {

                ID = "node",

                OffsetX = 200,

                OffsetY = 200,

                Width = 100,

                Height = 200

            };

            nodes.Add(node);

    }

    private string ExtensionType = ".json";

    //Method to save the diagram

    public async Task SaveDiagram()

    {

        fileName = await jsRuntime.InvokeAsync<string>("getDiagramFileName", "");

        await DownloadDiagram(fileName);

    }

 

    //Method to download the diagram

    public async Task DownloadDiagram(string fileName)

    {

        string data = diagram.SaveDiagram();

        await FileUtil.SaveAs(jsRuntime, data, fileName);

    }

 

    //Method to load the diagram

    public async Task LoadDiagram()

    {

        diagram.BeginUpdate();

        ExtensionType = ".json";

        await FileUtil.Click(jsRuntime);

        await diagram.EndUpdateAsync();

    }

 

    public async Task OnUploadFileSelected(UploadingEventArgs args)

    {

        if (args.FileData.Type == "json")

        {

            string json = await FileUtil.LoadFile(jsRuntime, args.FileData);

            json = json.Replace(System.Environment.NewLine, string.Empty);

            await diagram.LoadDiagram(json.ToString());

            await uploadFiles.ClearAllAsync();

        }

    }

 

}  


Since the code snippet provided includes several outside models and additional details, we have shared a simpler sample that replicates the issue. This sample will allow us to identify if the problem is related to the two-way data binding setup or if there are other contributing factors.

Please check the provided sample, and if the issue persists, feel free to share any error messages or logs, and we will assist further.



Attachment: SaveAndLoad_2701c74b.zip


AC Alessandro Cirignaco December 4, 2024 04:24 PM UTC

Hi,

This worked, thank you. I have a new issue.

Currently I am saving my diagrams as a json file. When testing, i stacked nodes on top of each other, saved the diagram and then loaded the saved file back into my diagram component. This caused the app to crash when applying constraints saying the object reference is not set to an instance of an object.

What could be causing the crash at this method?

    private async Task ApplyNodeConstraints()

    {

        modeLabel = isEditMode ? "Edit Mode" : "View Mode";


        DiagramComponent.BeginUpdate();

        foreach (var node in DiagramComponent.Nodes)

        {

            if (node != null)

            {

                node.Constraints = isEditMode

                    ? NodeConstraints.Default

                    : NodeConstraints.Default & ~NodeConstraints.Select; //crashes here

            }

        }


        await DiagramComponent.EndUpdateAsync();

    }

I will provide the json file below.

To add, the load works fine when the nodes are not stacked/touching each other. 


Attachment: ShopFloorLayout_4bf255a8.zip


BR Balavignesh RaviChandran Syncfusion Team December 5, 2024 12:01 PM UTC

Hi Alessandro Cirignaco,

We tested the issue using the JSON file and the method you provided but were unable to reproduce the crash. The diagram loaded successfully, and constraints were applied without any errors, even with stacked nodes.

We’ve attached a simple working application where your JSON file is loaded without issues. Please make any necessary changes to this application to help us reproduce the problem so we can provide a solution.

Looking forward to your response.

Best regards,

Balavignesh R


Attachment: SaveAndLoad_9e57ae4d.zip

Loader.
Up arrow icon