Diagram attached to Link

I have kind of a unique scenario that I don’t know how to approach.
I’m using GoXam to build cable assemblies and the ‘Links’ are representing wires that connect my nodes together.

I need to include a wiring diagram for each of the Links and my initial thought was to use a ‘Diagram’ that gets associated with the link. If you look at the screen capture below, my intent is to have the user click on the orange cable (Link) and over on the right, they can ‘Add Wiring Diagram’ and this would show a GoXam Diagram where they could drag links between the connectors to define how the wires inside of the cable should be ‘wired’.

I’m using this as my Link definition:


    public class MyLink : GraphLinksModelLinkData<String, String>
    {
        public string HOSE_LENGTH { get; set; }
        public string HOSE_WIRE_COUNT { get; set; }
        public string HOSE_AWG { get; set; }

        public override XElement MakeXElement(XName n)
        {
            XElement e = base.MakeXElement(n);
            e.Add(XHelper.Attribute("HOSE_LENGTH", this.HOSE_LENGTH, ""));
            e.Add(XHelper.Attribute("HOSE_WIRE_COUNT", this.HOSE_WIRE_COUNT, ""));
            e.Add(XHelper.Attribute("HOSE_AWG", this.HOSE_AWG, ""));
            return e;
        }
        
        public override void LoadFromXElement(XElement e)
        {
            base.LoadFromXElement(e);
            this.HOSE_LENGTH = XHelper.Read("HOSE_LENGTH", e, "");
            this.HOSE_WIRE_COUNT = XHelper.Read("HOSE_WIRE_COUNT", e, "");
            this.HOSE_AWG = XHelper.Read("HOSE_AWG", e, "");
        }
    }

The problem is that I need to be able to save this entire assembly to a database - so I need someway to translate this to and from XML.

I thought that maybe I could do something like this:


    public class MyLink : GraphLinksModelLinkData<String, String>
    {
        public string HOSE_LENGTH { get; set; }
        public string HOSE_WIRE_COUNT { get; set; }
        public string HOSE_AWG { get; set; }
<b>        public GraphLinksModelNodeData<WiringDiagram> WIRING_DIAGRAM { get; set; }</b>

        public override XElement MakeXElement(XName n)
        {
            XElement e = base.MakeXElement(n);
            e.Add(XHelper.Attribute("HOSE_LENGTH", this.HOSE_LENGTH, ""));
            e.Add(XHelper.Attribute("HOSE_WIRE_COUNT", this.HOSE_WIRE_COUNT, ""));
            e.Add(XHelper.Attribute("HOSE_AWG", this.HOSE_AWG, ""));
            return e;
        }
        
        public override void LoadFromXElement(XElement e)
        {
            base.LoadFromXElement(e);
            this.HOSE_LENGTH = XHelper.Read("HOSE_LENGTH", e, "");
            this.HOSE_WIRE_COUNT = XHelper.Read("HOSE_WIRE_COUNT", e, "");
            this.HOSE_AWG = XHelper.Read("HOSE_AWG", e, "");
        }
    }

    public class WiringDiagram : GraphLinksModelNodeData<String>
    {
    }
    public class WiringDiagramLinks : GraphLinksModelLinkData<String, String>
    {
    }

…but even if I do get this working, how would I even convert this to XML for saving.

I’m wondering if you’ve run into this before and maybe have some other suggestions or maybe a different direction that I should be going in.

Thanks!

OK, so for each MyLink data (cable?) in the diagram on the left you want to have an associated wiring diagram on the right describing the wires in the cable.

What you’ve done so far in extending the MyLink class is OK, but I would do it slightly differently. I wouldn’t bother with a WiringDiagram class or any additional node data.

Instead I would define it something like:

public class MyLink . . . {
. . .
public List WIRING_DIAGRAM { get; set; }

public override XElement MakeXElement(XName n) {
  . . .
  e.Add(XHelper.Elements("WIRING_DIAGRAM", "Wire", this.WIRING_DIAGRAM,
      w => XmlConvert.ToString(w.From) + " " + XmlConvert.ToString(w.To));
  return e;
}

public override void LoadFromXElement(XElement e) {
  . . .
  this.WIRING_DIAGRAM = XHelper.ReadElements<Wire>(e.Element("WIRING_DIAGRAM"), "Wire",
      new List<Wire>(),
      s => new Wire(s));
}

}

public class Wire : GraphLinksModelLinkData<String, String> {
public Wire(String s) {
char[] separators = { ’ ’ };
String[] names = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
if (names.Length > 0) this.From = names[0];
if (names.Length > 1) this.To = names[1];
}
}

Basically this stores the wiring connections as a collection of Wire elements inside each XML element representing a MyLink.

Now when a Link bound to a MyLink data is selected, you can initialize the right diagram’s GraphLinksModel.LinksSource to the MyLink.WIRING_DIAGRAM list of Wire data. This model would be a GraphLinksModel<String, String>. That model doesn’t need its NodesSource initialized because you can have the model do that for you by setting the GraphLinksModel.NodeKeyIsNodeData to true and GraphLinksModel.NodeKeyReferenceAutoInserts to true. (The BeatPaths sample does the same thing.)

Then all you need to do is manually lay out the positions of the left nodes and the right nodes.

Oh, and customize the right diagram’s LinkTemplate to be a thick line.
I guess you want its Link.Route.RelinkableFrom and RelinkableTo to be true too.

I have some additional comments, not directly bearing on your question. I hope you don’t take offense.

In case you didn’t notice, I renamed “WiringDiagramLinks” to “Wire”. It really isn’t a collection so it shouldn’t be plural. And simpler/shorter is better.

Also I would suggest renaming “MyLink” to “Cable” or something more descriptive. Having model data named “MyLink” might be confused with “Link”, which is a FrameworkElement class.

And I suspect it would be more natural for HOSE_LENGTH and HOSE_WIRE_COUNT to be integers rather than strings.

Walter- thank you so much for your help with this!
I’m almost there but I’m having issues implementing your suggestion…

What I have is something like this:

//This is where I'm capturing the wire click and initializing the model for my wiring diagram.

private void Left_Click_Node_ButtonDown(object sender, MouseButtonEventArgs e)
{
   switch(sender.ToString()) 
      {
         //Clicked on a link (hose). 
         case "Northwoods.GoXam.LinkPanel":
         //myDiagram is the Right-side diagram
         var elt = myDiagram.Panel.FindElementAt<UIElement> 
                  (this.myDiagram.LastMousePointInModel, c => c, null, SearchLayers.Links);
         if (elt == null) return;
         Link link = Part.FindAncestor<Link>(elt);
         if (link != null)
         {
            MyLink mylink = link.Data as MyLink;
<span ="Apple-tab-span" style="white-space:pre">			</span>
            var wdmodel = new GraphLinksModel<String, String, String, MyLink>();
            //Wire_Wiring_Diagram is the Left-side diagram
            Wire_Wiring_Diagram.LinksSource = MyLink.WIRING_DIAGRAM;
            Wire_Wiring_Diagram.Model = wdmodel;
          }
      }
      break;
}

.......


    public class MyLink : GraphLinksModelLinkData<String, String>
    {
        public string HOSE_LENGTH { get; set; }
        public string HOSE_WIRE_COUNT { get; set; }
        public string HOSE_AWG { get; set; }
        public List<Wire> WIRING_DIAGRAM { get; set; }

        public override XElement MakeXElement(XName n)
        {
            XElement e = base.MakeXElement(n);
            e.Add(XHelper.Attribute("HOSE_LENGTH", this.HOSE_LENGTH, ""));
            e.Add(XHelper.Attribute("HOSE_WIRE_COUNT", this.HOSE_WIRE_COUNT, ""));
            e.Add(XHelper.Attribute("HOSE_AWG", this.HOSE_AWG, ""));
            e.Add(XHelper.Elements("WIRING_DIAGRAM", "Wire", this.Wire_Wiring_Diagram, w => XmlConvert.ToString(w.From) + " " + XmlConvert.ToString(w.To)));  //I'm getting:  MyLink does not contain a definition for Wire_Wiring_Diagram
            return e;
        }
        
        public override void LoadFromXElement(XElement e)
        {
            base.LoadFromXElement(e);
            this.HOSE_LENGTH = XHelper.Read("HOSE_LENGTH", e, "");
            this.HOSE_WIRE_COUNT = XHelper.Read("HOSE_WIRE_COUNT", e, "");
            this.HOSE_AWG = XHelper.Read("HOSE_AWG", e, "");
            this.WIRING_DIAGRAM = XHelper.ReadElements(e.Element("WIRING_DIAGRAM"), "Wire", e, new List<Wire>(), s => new Wire(s));  //I'm getting:  No overload for ReadElements takes 5 arguments
        }
    }


    public class Wire : GraphLinksModelLinkData<String, String>
    {
        public Wire(String s)
        {
            char[] separators = { ' ' };
            String[] names = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
            if (names.Length > 0) this.From = names[0];
            if (names.Length > 1) this.To = names[1];
        }
    }

Sorry if I’m not grasping this… I feel like I’m close…

Oops, I forgot to specify the parameterized type in the call to ReadElements:

this.WIRING_DIAGRAM = XHelper.ReadElements<Wire>(e.Element("WIRING_DIAGRAM"), "Wire", e, ...);

Another stylistic comment: you probably want to depend on the Diagram.SelectionChanged event instead of the left-mouse-button-click event. If the primary selection is a Link, you can get it as the Diagram.SelectedLink property (otherwise it’s null).

Can you help me with this line?

this.WIRING_DIAGRAM = XHelper.ReadElements<Wire>(e.Element("WIRING_DIAGRAM"), "Wire", e, new List<Wire>(), s => new Wire(s));

It’s saying that ReadElements can’t handle 5 arguments.
It seemed to me that I should take out the ‘e’ so that it’s:

this.WIRING_DIAGRAM = XHelper.ReadElements<Wire>(e.Element("WIRING_DIAGRAM"), "Wire", new List<Wire>(), s => new Wire(s));

… but that still isn’t working. Thanks again for your help!

Yes, you’re right, that “e” argument is superfluous.

I assume you’re not getting any compiler errrors.
What behavior are you getting?
Is the XML generated right?

I’m sorry that I didn’t have the ability to actually try compiling and running the code.

Thanks Walter.
I overcame a compiler issue by changing the ReadElements line to this:


            this.WIRING_DIAGRAM = (List<Wire>)XHelper.ReadElements<Wire>(e.Element("WIRING_DIAGRAM"), "Wire", new List<Wire>(), s => new Wire(s));

Now I’m struggling with how you were intending that I actually show the nodes inside of the diagram.

So, if a user specifies that the ‘Cable’ has 3 wires inside of it and I want the diagram to display:

1a               1b
2a               2b
3a               3b

…so that they can drag links between these - how do I do this?

I started going down this road:


                        var wdmodel = new GraphLinksModel<String, String, String, Cable>();

                        if (mylink.WIRING_DIAGRAM == null)
                        {
                            wdmodel.LinksSource = new List<Cable>() {
                                new Cable() {
                                    To="1a", From="1b"
                                },
                                new Cable() {
                                    To="2a", From="2b"
                                },
                                new Cable() {
                                    To="3a", From="3b"
                                }
                            };
                        }
                        else
                        {
                            wdmodel.LinksSource = mylink.WIRING_DIAGRAM;
                        }
                        wdmodel.NodeKeyIsNodeData = true;
                        wdmodel.NodeKeyReferenceAutoInserts = true;
                        wdmodel.Modifiable = true;
                        Wire_Wiring_Diagram.Model = wdmodel;

but I can’t figure out how to specify location information (in this case I would want 3 nodes on the left and 3 nodes on the right). I’m also not sure if I’m instantiating these nodes correctly.

I guess my question is, how do I initially set up the diagram with the specified number of nodes based on the txtNumberofWires textbox?

If you are going to support unconnected terminals, such as no wire going to “2b”, then you can’t use the NodeKeyReferenceAutoInserts property. Instead you’ll need to supply the model.NodesSource collection, because a node [data] can exist without any link [data] connecting to it.

I was assuming that you could write trivial code to do the layout. All you need to do is distinguish between nodes that should go on the left side and ones that should go on the right side. Can you do that just by looking at the string? That seems to be the case in your examples, but I don’t know if that’s true in general.

If it’s not true, i.e. that you need an additional property to distinguish left from right side, then you won’t be able to use String as the node data type. You’ll need to define a node data class. You might as well define something inheriting from the predefined GraphLinksModelNodeData generic class. Of course references to such nodes can continue being Strings, but then you can’t set NodeKeyIsNodeData to true any more either.