예제 #1
0
        // return right bottom of area used from curpos
        Point CreatePlanetTree(List <ExtPictureBox.ImageElement> pc, StarScan.ScanNode planetnode,
                               List <MaterialCommodityMicroResource> historicmats, List <MaterialCommodityMicroResource> curmats,
                               Point leftmiddle, string[] filter, bool habzone, out int planetcentre)
        {
            Color?backwash = null;

            if (habzone)
            {
                backwash = Color.FromArgb(64, 0, 128, 0);       // transparent in case we have a non black background
            }
            Image barycentre = BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Barycentre");

            Point maxtreepos = DrawNode(pc, planetnode, historicmats, curmats,
                                        (planetnode.NodeType == StarScan.ScanNodeType.barycentre) ? barycentre : JournalScan.GetPlanetImageNotScanned(),
                                        leftmiddle, false, out Rectangle planetpos, planetsize, DrawLevel.PlanetLevel, backwash: backwash); // offset passes in the suggested offset, returns the centre offset

            planetcentre = planetpos.X + planetpos.Width / 2;

            if (planetnode.Children != null && ShowMoons)
            {
                Point moonposcentremid = new Point(planetcentre, maxtreepos.Y + moonspacery + moonsize.Height / 2);    // moon pos, below planet, centre x coord

                var moonnodes   = planetnode.Children.Values.Where(n => n.NodeType != StarScan.ScanNodeType.barycentre).ToList();
                var mooncentres = new Dictionary <StarScan.ScanNode, Point>();

                for (int mn = 0; mn < moonnodes.Count; mn++)
                {
                    StarScan.ScanNode moonnode = moonnodes[mn];

                    if (filter != null && moonnode.IsBodyInFilter(filter, true) == false)       // if filter active, but no body or children in filter
                    {
                        continue;
                    }

                    bool nonedsmscans = moonnode.DoesNodeHaveNonEDSMScansBelow();     // is there any scans here, either at this node or below?

                    if (nonedsmscans || ShowEDSMBodies)
                    {
                        Point mmax       = DrawNode(pc, moonnode, historicmats, curmats, (moonnode.NodeType == StarScan.ScanNodeType.barycentre) ? barycentre : JournalScan.GetMoonImageNotScanned(), moonposcentremid, true, out Rectangle moonimagepos, moonsize, DrawLevel.MoonLevel);
                        int   mooncentre = moonimagepos.X + moonimagepos.Width / 2;

                        maxtreepos = new Point(Math.Max(maxtreepos.X, mmax.X), Math.Max(maxtreepos.Y, mmax.Y));

                        if (moonnode.Children != null)
                        {
                            Point submoonpos = new Point(mmax.X + moonspacerx, moonposcentremid.Y);     // first its left mid
                            bool  xiscentre  = false;

                            foreach (StarScan.ScanNode submoonnode in moonnode.Children.Values)
                            {
                                if (filter != null && submoonnode.IsBodyInFilter(filter, true) == false)       // if filter active, but no body or children in filter
                                {
                                    continue;
                                }

                                bool nonedsmsubmoonscans = submoonnode.DoesNodeHaveNonEDSMScansBelow();     // is there any scans here, either at this node or below?

                                if (nonedsmsubmoonscans || ShowEDSMBodies)
                                {
                                    Point sbmax = DrawNode(pc, submoonnode, historicmats, curmats, (moonnode.NodeType == StarScan.ScanNodeType.barycentre) ? barycentre : JournalScan.GetMoonImageNotScanned(), submoonpos, xiscentre, out Rectangle submoonimagepos, moonsize, DrawLevel.MoonLevel);

                                    if (xiscentre)
                                    {
                                        submoonpos = new Point(submoonpos.X, sbmax.Y + moonspacery + moonsize.Height / 2);
                                    }
                                    else
                                    {
                                        int xsubmooncentre = submoonimagepos.X + submoonimagepos.Width / 2;
                                        submoonpos = new Point(xsubmooncentre, sbmax.Y + moonspacery + moonsize.Height / 2);
                                        xiscentre  = true;      // now go to centre placing
                                    }

                                    maxtreepos = new Point(Math.Max(maxtreepos.X, sbmax.X), Math.Max(maxtreepos.Y, sbmax.Y));
                                }
                            }
                        }

                        mooncentres[moonnode] = new Point(mooncentre, moonposcentremid.Y);

                        moonposcentremid = new Point(moonposcentremid.X, maxtreepos.Y + moonspacery + moonsize.Height / 2);

                        //System.Diagnostics.Debug.WriteLine("Next moon centre at " + moonposcentremid );
                    }
                }

                //// now, taking the moon modes, create a barycentre tree with those inserted in
                var barynodes = StarScan.ScanNode.PopulateBarycentres(moonnodes); // children always made, barynode tree

                foreach (var k in barynodes.Children)                             // for all barynodes.. display
                {
                    DisplayBarynode(k.Value, 0, mooncentres, moonnodes, pc, moonsize.Width * 5 / 4, true);
                }
            }

            return(maxtreepos);
        }
        Dictionary <Bitmap, float> imageintensities = new Dictionary <Bitmap, float>();       // cached

        // return right bottom of area used from curpos

        Point DrawNode(List <ExtPictureBox.ImageElement> pc,
                       StarScan.ScanNode sn,
                       List <MaterialCommodityMicroResource> historicmats, // curmats may be null
                       List <MaterialCommodityMicroResource> curmats,      // curmats may be null
                       Image notscanned,                                   // image if sn is not known
                       Point position,                                     // position is normally left/middle, unless xiscentre is set.
                       bool xiscentre,
                       out Rectangle imagepos,
                       Size size,                        // nominal size
                       DrawLevel drawtype,               // drawing..
                       Color?backwash         = null,    // optional back wash on image
                       string appendlabeltext = ""       // any label text to append
                       )
        {
            string tip;
            Point  endpoint = position;

            imagepos = Rectangle.Empty;

            JournalScan sc = sn.ScanData;

            if (sc != null && (!sc.IsEDSMBody || ShowEDSMBodies))    // has a scan and its our scan, or we are showing EDSM
            {
                if (sn.NodeType != StarScan.ScanNodeType.ring)       // not rings
                {
                    tip = sc.DisplayString(0, historicmats, curmats);
                    if (sn.Signals != null)
                    {
                        tip += "\n" + "Signals".T(EDTx.ScanDisplayUserControl_Signals) + ":\n" + JournalSAASignalsFound.SignalList(sn.Signals, 4, "\n");
                    }

                    Bitmap nodeimage = (Bitmap)BaseUtils.Icons.IconSet.GetIcon(sc.GetStarPlanetTypeImageName());

                    string overlaytext = "";
                    var    nodelabels  = new string[2] {
                        "", ""
                    };

                    nodelabels[0] = sn.CustomNameOrOwnname;
                    if (sc.IsEDSMBody)
                    {
                        nodelabels[0] = "_" + nodelabels[0];
                    }

                    if (sc.IsStar)
                    {
                        if (ShowStarClasses)
                        {
                            overlaytext = sc.StarClassificationAbv;
                        }

                        if (sc.nStellarMass.HasValue)
                        {
                            nodelabels[1] = nodelabels[1].AppendPrePad($"{sc.nStellarMass.Value:N2} SM", Environment.NewLine);
                        }

                        if (drawtype == DrawLevel.TopLevelStar)
                        {
                            if (sc.nAge.HasValue)
                            {
                                nodelabels[1] = nodelabels[1].AppendPrePad($"{sc.nAge.Value:N0} MY", Environment.NewLine);
                            }

                            if (ShowHabZone)
                            {
                                var habZone = sc.GetHabZoneStringLs();
                                if (habZone.HasChars())
                                {
                                    nodelabels[1] = nodelabels[1].AppendPrePad($"{habZone}", Environment.NewLine);
                                }
                            }
                        }
                    }
                    else
                    {
                        if (ShowPlanetClasses)
                        {
                            overlaytext = Bodies.PlanetAbv(sc.PlanetTypeID);
                        }

                        if ((sn.ScanData.IsLandable || ShowAllG) && sn.ScanData.nSurfaceGravity != null)
                        {
                            nodelabels[1] = nodelabels[1].AppendPrePad($"{(sn.ScanData.nSurfaceGravity / JournalScan.oneGee_m_s2):N2}g", Environment.NewLine);
                        }
                    }

                    if (ShowDist)
                    {
                        if (drawtype != DrawLevel.MoonLevel)       // other than moons
                        {
                            if (sn.ScanData.IsOrbitingBaryCentre)  // if in orbit of barycentre
                            {
                                string s = $"{(sn.ScanData.DistanceFromArrivalLS):N1}ls";
                                if (sn.ScanData.nSemiMajorAxis.HasValue)
                                {
                                    s += "/" + sn.ScanData.SemiMajorAxisLSKM;
                                }
                                nodelabels[1] = nodelabels[1].AppendPrePad(s, Environment.NewLine);
                            }
                            else
                            {
                                //System.Diagnostics.Debug.WriteLine(sn.ScanData.BodyName + " SMA " + sn.ScanData.nSemiMajorAxis + " " + sn.ScanData.DistanceFromArrivalm);
                                string s = sn.ScanData.nSemiMajorAxis.HasValue && Math.Abs(sn.ScanData.nSemiMajorAxis.Value - sn.ScanData.DistanceFromArrivalm) > JournalScan.oneAU_m ? (" / " + sn.ScanData.SemiMajorAxisLSKM) : "";
                                nodelabels[1] = nodelabels[1].AppendPrePad($"{sn.ScanData.DistanceFromArrivalLS:N1}ls" + s, Environment.NewLine);
                            }
                        }
                        else
                        {
                            if (!sn.ScanData.IsOrbitingBaryCentre && sn.ScanData.nSemiMajorAxis.HasValue)          // if not in orbit of barycentre
                            {
                                nodelabels[1] = nodelabels[1].AppendPrePad($"{(sn.ScanData.nSemiMajorAxis / JournalScan.oneLS_m):N1}ls", Environment.NewLine);
                            }
                        }
                    }

                    nodelabels[1] = nodelabels[1].AppendPrePad(appendlabeltext, Environment.NewLine);

//  nodelabels[1] = nodelabels[1].AppendPrePad("" + sn.ScanData?.BodyID, Environment.NewLine);

                    bool valuable     = sc.GetEstimatedValues().EstimatedValue(sc.WasDiscovered, sc.WasMapped, true, true) >= ValueLimit;
                    bool isdiscovered = sc.IsPreviouslyDiscovered && sc.IsPlanet;
                    int  iconoverlays = ShowOverlays ? ((sc.Terraformable ? 1 : 0) + (sc.HasMeaningfulVolcanism ? 1 : 0) +
                                                        (valuable ? 1 : 0) + (sc.Mapped ? 1 : 0) + (isdiscovered ? 1 : 0) + (sc.IsPreviouslyMapped ? 1 : 0) +
                                                        (sn.Signals != null ? 1 : 0)) : 0;

                    //   if (sc.BodyName.Contains("4 b"))  iconoverlays = 0;

                    bool materialsicon = sc.HasMaterials && !ShowMaterials;
                    bool imageoverlays = sc.IsLandable || (sc.HasRings && drawtype != DrawLevel.TopLevelStar) || materialsicon;

                    int bitmapheight   = size.Height * nodeheightratio / noderatiodivider;
                    int overlaywidth   = bitmapheight / 6;
                    int imagewidtharea = (imageoverlays ? 2 : 1) * size.Width;             // area used by image+overlay if any
                    int iconwidtharea  = (iconoverlays > 0 ? overlaywidth : 0);            // area used by icon width area on left

                    int bitmapwidth = iconwidtharea + imagewidtharea;                      // total width
                    int imageleft   = iconwidtharea + imagewidtharea / 2 - size.Width / 2; // calculate where the left of the image is
                    int imagetop    = bitmapheight / 2 - size.Height / 2;                  // and the top

                    Bitmap bmp = new Bitmap(bitmapwidth, bitmapheight);

                    using (Graphics g = Graphics.FromImage(bmp))
                    {
                        //  backwash = Color.FromArgb(128, 40, 40, 40); // debug

                        if (backwash.HasValue)
                        {
                            using (Brush b = new SolidBrush(backwash.Value))
                            {
                                g.FillRectangle(b, new Rectangle(iconwidtharea, 0, imagewidtharea, bitmapheight));
                            }
                        }

                        g.DrawImage(nodeimage, imageleft, imagetop, size.Width, size.Height);

                        if (sc.IsLandable)
                        {
                            int offset = size.Height * 4 / 16;
                            int scale  = 5;
                            g.DrawImage(BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Landable"), new Rectangle(imageleft + size.Width / 2 - offset * scale / 2,
                                                                                                                        imagetop + size.Height / 2 - offset * scale / 2, offset * scale, offset * scale));
                        }

                        if (sc.HasRings && drawtype != DrawLevel.TopLevelStar)
                        {
                            g.DrawImage(sc.Rings.Count() > 1 ? BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.RingGap") : BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.RingOnly"),
                                        new Rectangle(imageleft - size.Width / 2, imagetop, size.Width * 2, size.Height));
                        }

                        if (iconoverlays > 0)
                        {
                            int ovsize = bmp.Height / 6;
                            int pos    = 4;

                            if (sc.Terraformable)
                            {
                                g.DrawImage(BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Terraformable"), new Rectangle(0, pos, ovsize, ovsize));
                                pos += ovsize + 1;
                            }

                            if (sc.HasMeaningfulVolcanism) //this renders below the terraformable icon if present
                            {
                                g.DrawImage(BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Volcanism"), new Rectangle(0, pos, ovsize, ovsize));
                                pos += ovsize + 1;
                            }

                            if (valuable)
                            {
                                g.DrawImage(BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.HighValue"), new Rectangle(0, pos, ovsize, ovsize));
                                pos += ovsize + 1;
                            }

                            if (sc.Mapped)
                            {
                                g.DrawImage(BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Mapped"), new Rectangle(0, pos, ovsize, ovsize));
                                pos += ovsize + 1;
                            }

                            if (sc.IsPreviouslyMapped)
                            {
                                g.DrawImage(BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.MappedByOthers"), new Rectangle(0, pos, ovsize, ovsize));
                                pos += ovsize + 1;
                            }

                            if (isdiscovered)
                            {
                                g.DrawImage(BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.DiscoveredByOthers"), new Rectangle(0, pos, ovsize, ovsize));
                                pos += ovsize + 1;
                            }

                            if (sn.Signals != null)
                            {
                                g.DrawImage(BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Signals"), new Rectangle(0, pos, ovsize, ovsize));
                            }
                        }

                        if (materialsicon)
                        {
                            Image mm = BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.MaterialMore");
                            g.DrawImage(mm, new Rectangle(bmp.Width - mm.Width, bmp.Height - mm.Height, mm.Width, mm.Height));
                        }

                        if (overlaytext.HasChars())
                        {
                            float ii;
                            if (imageintensities.ContainsKey(nodeimage))        // find cache
                            {
                                ii = imageintensities[nodeimage];
                                //System.Diagnostics.Debug.WriteLine("Cached Image intensity of " + sn.fullname + " " + ii);
                            }
                            else
                            {
                                var imageintensity = nodeimage.Function(BitMapHelpers.BitmapFunction.Brightness, nodeimage.Width * 3 / 8, nodeimage.Height * 3 / 8, nodeimage.Width * 2 / 8, nodeimage.Height * 2 / 8);
                                ii = imageintensity.Item2;
                                imageintensities[nodeimage] = ii;
                                //System.Diagnostics.Debug.WriteLine("Calculated Image intensity of " + sn.fullname + " " + ii);
                            }

                            Color text = ii > 0.3f ? Color.Black : Color.FromArgb(255, 200, 200, 200);

                            using (Font f = new Font(Font.Name, size.Width / 5.0f))
                            {
                                using (Brush b = new SolidBrush(text))
                                {
                                    g.DrawString(overlaytext, f, b, new Rectangle(iconwidtharea, 0, bitmapwidth - iconwidtharea, bitmapheight), new StringFormat()
                                    {
                                        Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center
                                    });
                                }
                            }
                        }
                    }

                    // need left middle, if xiscentre, translate to it
                    Point postoplot = xiscentre ? new Point(position.X - bmp.Width / 2, position.Y) : position;

                    //System.Diagnostics.Debug.WriteLine("Body " + sc.BodyName + " plot at "  + postoplot + " " + bmp.Size + " " + (postoplot.X+imageleft) + "," + (postoplot.Y-bmp.Height/2+imagetop));
                    endpoint = CreateImageAndLabel(pc, bmp, postoplot, bmp.Size, out imagepos, nodelabels, tip);
                    //System.Diagnostics.Debug.WriteLine("Draw {0} at {1} {2} out {3}", nodelabels[0], postoplot, bmp.Size, imagepos);

                    if (sc.HasMaterials && ShowMaterials)
                    {
                        Point matpos = new Point(endpoint.X + 4, position.Y);
                        Point endmat = CreateMaterialNodes(pc, sc, historicmats, curmats, matpos, materialsize);
                        endpoint = new Point(Math.Max(endpoint.X, endmat.X), Math.Max(endpoint.Y, endmat.Y)); // record new right point..
                    }
                }
            }
            else if (sn.NodeType == StarScan.ScanNodeType.belt)
            {
                if (sn.BeltData != null)
                {
                    tip = sn.BeltData.RingInformationMoons(true, "");
                }
                else
                {
                    tip = sn.OwnName + Environment.NewLine + Environment.NewLine + "No scan data available".T(EDTx.ScanDisplayUserControl_NSD);
                }

                if (sn.Children != null && sn.Children.Count != 0)
                {
                    foreach (StarScan.ScanNode snc in sn.Children.Values)
                    {
                        if (snc.ScanData != null)
                        {
                            string sd = snc.ScanData.DisplayString() + "\n";
                            tip += "\n" + sd;
                        }
                    }
                }

                Size bmpsize = new Size(size.Width, planetsize.Height * nodeheightratio / noderatiodivider);

                endpoint = CreateImageAndLabel(pc, BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Belt"), position, bmpsize, out imagepos, new string[] { sn.OwnName.AppendPrePad(appendlabeltext, Environment.NewLine) }, tip, false);
            }
            else
            {
                if (sn.NodeType == StarScan.ScanNodeType.barycentre)
                {
                    tip = string.Format("Barycentre of {0}".T(EDTx.ScanDisplayUserControl_BC), sn.OwnName);
                }
                else
                {
                    tip = sn.OwnName + Environment.NewLine + Environment.NewLine + "No scan data available".T(EDTx.ScanDisplayUserControl_NSD);
                }

                string nodelabel = sn.CustomName ?? sn.OwnName;
                nodelabel = nodelabel.AppendPrePad(appendlabeltext, Environment.NewLine);

                endpoint = CreateImageAndLabel(pc, notscanned, position, size, out imagepos, new string[] { nodelabel }, tip, false);
            }

            //    System.Diagnostics.Debug.WriteLine("Node " + sn.ownname + " " + position + " " + size + " -> "+ endpoint);
            return(endpoint);
        }
예제 #3
0
        // draw scannode into an imagebox in widthavailable..
        // curmats may be null
        public void DrawSystem(ExtendedControls.ExtPictureBox imagebox, int widthavailable,
                               StarScan.SystemNode systemnode, List <MaterialCommodityMicroResource> historicmats, List <MaterialCommodityMicroResource> curmats, string opttext = null, string[] filter = null)
        {
            imagebox.ClearImageList();  // does not clear the image, render will do that

            if (systemnode != null)
            {
                var notscannedbitmap = (Bitmap)BaseUtils.Icons.IconSet.GetIcon("Bodies.Unknown");

                Point leftmiddle = new Point(leftmargin, topmargin + StarSize.Height * nodeheightratio / 2 / noderatiodivider);  // half down (h/2 * ratio)

                if (opttext != null)
                {
                    ExtPictureBox.ImageElement lab = new ExtPictureBox.ImageElement();
                    lab.TextAutosize(new Point(leftmargin, 0), new Size(500, 30), opttext, LargerFont, LabelColor, BackColor);
                    imagebox.Add(lab);
                    leftmiddle.Y += lab.Image.Height + 8;
                }

                DisplayAreaUsed = leftmiddle;
                List <ExtPictureBox.ImageElement> starcontrols = new List <ExtPictureBox.ImageElement>();

                bool displaybelts = filter == null || (filter.Contains("belt") || filter.Contains("All"));

                Point maxitemspos = new Point(0, 0);

                bool drawnsignals = false;                                                // set if we drawn signals against any of the stars

                foreach (StarScan.ScanNode starnode in systemnode.StarNodes.Values)       // always has scan nodes
                {
                    if (filter != null && starnode.IsBodyInFilter(filter, true) == false) // if filter active, but no body or children in filter
                    {
                        // System.Diagnostics.Debug.WriteLine("SDUC Rejected " + starnode.fullname);
                        continue;
                    }

                    if (!starnode.DoesNodeHaveNonEDSMScansBelow() && !ShowEDSMBodies)      // if we don't have any non edsm bodies at or under the node, and we are not showing edsm bodies, ignore
                    {
                        continue;
                    }

                    {  // Draw star
                        Image barycentre = BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Barycentre");

                        Point maxpos = DrawNode(starcontrols, starnode, historicmats, curmats,
                                                (starnode.NodeType == StarScan.ScanNodeType.barycentre) ? barycentre: notscannedbitmap,
                                                leftmiddle, false, out Rectangle starimagepos, StarSize, DrawLevel.TopLevelStar); // the last part nerfs the label down to the right position

                        maxitemspos = new Point(Math.Max(maxitemspos.X, maxpos.X), Math.Max(maxitemspos.Y, maxpos.Y));

                        if (!drawnsignals && systemnode.FSSSignalList.Count > 0)           // Draw signals, if not drawn
                        {
                            drawnsignals = true;
                            Point maxsignalpos = DrawSignals(starcontrols, new Point(starimagepos.Right + moonspacerx, leftmiddle.Y), systemnode.FSSSignalList, StarSize.Height * 6 / 4, 16);
                            maxitemspos = new Point(Math.Max(maxitemspos.X, maxsignalpos.X), Math.Max(maxitemspos.Y, maxsignalpos.Y));
                        }

                        leftmiddle = new Point(maxitemspos.X + starfirstplanetspacerx, leftmiddle.Y);       // move the cursor on to the right of the box, no spacing
                    }

                    if (starnode.Children != null)
                    {
                        Point firstcolumn = leftmiddle;

                        Queue <StarScan.ScanNode> belts;
                        if (starnode.ScanData != null && (!starnode.ScanData.IsEDSMBody || ShowEDSMBodies))                                       // have scandata on star, and its not edsm or allowed edsm
                        {
                            belts = new Queue <StarScan.ScanNode>(starnode.Children.Values.Where(s => s.NodeType == StarScan.ScanNodeType.belt)); // find belts in children of star
                        }
                        else
                        {
                            belts = new Queue <StarScan.ScanNode>(); // empty array
                        }

                        StarScan.ScanNode lastbelt = belts.Count != 0 ? belts.Dequeue() : null;

                        EliteDangerousCore.JournalEvents.JournalScan.HabZones hz = starnode.ScanData?.GetHabZones();

                        double habzonestartls = hz != null ? hz.HabitableZoneInner : 0;
                        double habzoneendls   = hz != null ? hz.HabitableZoneOuter : 0;

                        Image beltsi = BaseUtils.Icons.IconSet.GetIcon("Controls.Scan.Bodies.Belt");

                        // process body and stars only

                        List <StarScan.ScanNode> planetsinorder = starnode.Children.Values.Where(s => s.NodeType == StarScan.ScanNodeType.body || s.NodeType == StarScan.ScanNodeType.star).ToList();
                        var planetcentres = new Dictionary <StarScan.ScanNode, Point>();

                        for (int pn = 0; pn < planetsinorder.Count; pn++)
                        {
                            StarScan.ScanNode planetnode = planetsinorder[pn];

                            if (filter != null && planetnode.IsBodyInFilter(filter, true) == false)       // if filter active, but no body or children in filter
                            {
                                //System.Diagnostics.Debug.WriteLine("SDUC Rejected " + planetnode.fullname);
                                continue;
                            }

                            //System.Diagnostics.Debug.WriteLine("Draw " + planetnode.ownname + ":" + planetnode.type);

                            // if belt is before this, display belts here

                            while (displaybelts && lastbelt != null && planetnode.ScanData != null && (lastbelt.BeltData == null || planetnode.ScanData.IsOrbitingBaryCentre || lastbelt.BeltData.OuterRad < planetnode.ScanData.nSemiMajorAxis))
                            {
                                // if too far across, go back to star
                                if (leftmiddle.X + planetsize.Width > widthavailable)                                             // if too far across..
                                {
                                    leftmiddle = new Point(firstcolumn.X, maxitemspos.Y + planetspacery + planetsize.Height / 2); // move to left at maxy+space+h/2
                                }

                                string appendlabel = "";

                                if (lastbelt.BeltData != null)
                                {
                                    appendlabel = appendlabel.AppendPrePad($"{lastbelt.BeltData.OuterRad / JournalScan.oneLS_m:N1}ls", Environment.NewLine);
                                }

                                appendlabel = appendlabel.AppendPrePad("" + lastbelt.ScanData?.BodyID, Environment.NewLine);


                                Point maxbeltpos = DrawNode(starcontrols, lastbelt, historicmats, curmats, beltsi, leftmiddle, false, out Rectangle unusedbeltcentre, beltsize, DrawLevel.PlanetLevel, appendlabeltext: appendlabel);

                                leftmiddle = new Point(maxbeltpos.X + planetspacerx, leftmiddle.Y);
                                lastbelt   = belts.Count != 0 ? belts.Dequeue() : null;

                                maxitemspos = new Point(Math.Max(maxitemspos.X, maxbeltpos.X), Math.Max(maxitemspos.Y, maxbeltpos.Y));
                            }

                            //System.Diagnostics.Debug.WriteLine("Planet Node " + planetnode.ownname + " has scans " + nonedsmscans);

                            if (planetnode.DoesNodeHaveNonEDSMScansBelow() || ShowEDSMBodies)
                            {
                                List <ExtPictureBox.ImageElement> pc = new List <ExtPictureBox.ImageElement>();

                                bool habzone = false;

                                if (ShowHabZone && planetnode.ScanData != null && !planetnode.ScanData.IsOrbitingBaryCentre && planetnode.ScanData.nSemiMajorAxis.HasValue)
                                {
                                    double dist = planetnode.ScanData.nSemiMajorAxis.Value / JournalScan.oneLS_m;  // m , converted to LS
                                    habzone = dist >= habzonestartls && dist <= habzoneendls;
                                }

                                Point maxplanetpos = CreatePlanetTree(pc, planetnode, historicmats, curmats, leftmiddle, filter, habzone, out int centreplanet);

                                Point pcnt = new Point(centreplanet, leftmiddle.Y);

                                if (maxplanetpos.X > widthavailable)          // uh oh too wide..
                                {
                                    int xoff = firstcolumn.X - leftmiddle.X;  // shift to firstcolumn.x, maxitemspos.Y+planetspacer
                                    int yoff = (maxitemspos.Y + planetspacery) - (leftmiddle.Y - planetsize.Height / 2);

                                    RepositionTree(pc, xoff, yoff);                                              // shift co-ords of all you've drawn - this will include any bary points drawn in moons

                                    pcnt.X += xoff; pcnt.Y += yoff;                                              // need to account for planet centre

                                    maxplanetpos = new Point(maxplanetpos.X + xoff, maxplanetpos.Y + yoff);      // remove the shift from maxpos

                                    leftmiddle = new Point(maxplanetpos.X + planetspacerx, leftmiddle.Y + yoff); // and set the curpos to maxpos.x + spacer, remove the shift from curpos.y
                                }
                                else
                                {
                                    leftmiddle = new Point(maxplanetpos.X + planetspacerx, leftmiddle.Y);     // shift current pos right, plus a spacer
                                }
                                maxitemspos = new Point(Math.Max(maxitemspos.X, maxplanetpos.X), Math.Max(maxitemspos.Y, maxplanetpos.Y));

                                starcontrols.AddRange(pc.ToArray());

                                planetcentres[planetnode] = pcnt;
                            }
                        }

                        // do any futher belts after all planets

                        while (displaybelts && lastbelt != null)
                        {
                            if (leftmiddle.X + planetsize.Width > widthavailable)                                             // if too far across..
                            {
                                leftmiddle = new Point(firstcolumn.X, maxitemspos.Y + planetspacery + planetsize.Height / 2); // move to left at maxy+space+h/2
                            }

                            string appendlabel = "";

                            if (lastbelt.BeltData != null)
                            {
                                appendlabel = appendlabel.AppendPrePad($"{lastbelt.BeltData.OuterRad / JournalScan.oneLS_m:N1}ls", Environment.NewLine);
                            }

                            appendlabel = appendlabel.AppendPrePad("" + lastbelt.ScanData?.BodyID, Environment.NewLine);

                            Point maxbeltpos = DrawNode(starcontrols, lastbelt, historicmats, curmats, beltsi, leftmiddle, false, out Rectangle unusedbelt2centre, beltsize, DrawLevel.PlanetLevel, appendlabeltext: appendlabel);

                            leftmiddle = new Point(maxbeltpos.X + planetspacerx, leftmiddle.Y);
                            lastbelt   = belts.Count != 0 ? belts.Dequeue() : null;

                            maxitemspos = new Point(Math.Max(maxitemspos.X, maxbeltpos.X), Math.Max(maxitemspos.Y, maxbeltpos.Y));
                        }

                        maxitemspos = leftmiddle = new Point(leftmargin, maxitemspos.Y + starplanetgroupspacery + StarSize.Height / 2);     // move back to left margin and move down to next position of star, allowing gap

                        // make a tree of the planets with their barycentres from the Parents information
                        var barynodes = StarScan.ScanNode.PopulateBarycentres(planetsinorder);  // children always made, barynode tree

                        //StarScan.ScanNode.DumpTree(barynodes, "TOP", 0);

                        List <ExtPictureBox.ImageElement> pcb = new List <ExtPictureBox.ImageElement>();

                        foreach (var k in barynodes.Children)                                                       // for all barynodes.. display
                        {
                            DisplayBarynode(k.Value, 0, planetcentres, planetsinorder, pcb, planetsize.Height / 2); // done after the reposition so true positions set up.
                        }

                        starcontrols.InsertRange(0, pcb); // insert at start so drawn under
                    }
                    else
                    {               // no planets, so just move across and plot another one
                        leftmiddle = new Point(maxitemspos.X + starfirstplanetspacerx, leftmiddle.Y);

                        if (leftmiddle.X + StarSize.Width > widthavailable)                                                                 // if too far across..
                        {
                            maxitemspos = leftmiddle = new Point(leftmargin, maxitemspos.Y + starplanetgroupspacery + StarSize.Height / 2); // move to left at maxy+space+h/2
                        }
                    }

                    DisplayAreaUsed = new Point(Math.Max(DisplayAreaUsed.X, maxitemspos.X), Math.Max(DisplayAreaUsed.Y, maxitemspos.Y));
                }

                if (!drawnsignals && systemnode.FSSSignalList.Count > 0)  // if no stars were drawn, but signals..
                {
                    Point maxpos = CreateImageAndLabel(starcontrols, notscannedbitmap, leftmiddle, StarSize, out Rectangle starpos, new string[] { "" }, "", false);
                    DrawSignals(starcontrols, new Point(starpos.Right + moonspacerx, leftmiddle.Y), systemnode.FSSSignalList, StarSize.Height * 6 / 4, 16);       // draw them, nothing else to follow
                }

                imagebox.AddRange(starcontrols);
            }

            imagebox.Render();      // replaces image..
        }
        const int baryspacingmargin = baryspacing * 4;      // up to 4 bary lines..

        Tuple <Point, int, int> DisplayBarynode(StarScan.ScanNode sn, int level, Dictionary <StarScan.ScanNode, Point> nodecentres, List <StarScan.ScanNode> nodes, List <ExtPictureBox.ImageElement> pc, int imagesize, bool horz = false)
        {
            if (sn.Children == null)
            {
                return(new Tuple <Point, int, int>(Point.Empty, level, 0));
            }

            var tojoin = new List <BaryPointInfo>();

            int orderpos = 0;   // this records the last planet order pos for use to pass back up the tree - helps in ordering

            foreach (var c in sn.Children)
            {
                if (c.Value.NodeType == StarScan.ScanNodeType.barycentre)       // if a barycentre, draw it
                {
                    var x = DisplayBarynode(c.Value, level + 1, nodecentres, nodes, pc, imagesize, horz);
                    //System.Diagnostics.Debug.WriteLine("                        ".Substring(0, level * 3) + level + " Draw bary " + c.Value.fullname + " " + x.Item1 + " " + level);
                    if (horz)
                    {
                        tojoin.Add(new BaryPointInfo()
                        {
                            point = x.Item1, toppos = x.Item1, orderpos = x.Item3
                        });                                                                                        // make a join point for a barynode
                    }
                    else
                    {
                        tojoin.Add(new BaryPointInfo()
                        {
                            point = x.Item1, toppos = x.Item1, orderpos = x.Item3
                        });                                                                                        // make a join point for a barynode. Leave the toppos for later
                    }
                }
                else
                {
                    // Note if EDSM draw is turned off, but it was in the data from another source, we would not have drawn it so we need to check that by
                    // seeing if the node has a nodecentre. No nodecentre, did not draw

                    if (nodecentres.ContainsKey(c.Value))
                    {
                        orderpos = nodes.IndexOf(c.Value);
                        //System.Diagnostics.Debug.WriteLine("                        ".Substring(0, level * 3) + level + " Draw Body " + c.Value.fullname + " " + orderpos + " " + nodecentres[c.Value]);
                        if (horz)
                        {
                            tojoin.Add(new BaryPointInfo()
                            {
                                point = nodecentres[c.Value], toppos = new Point(nodecentres[c.Value].X - imagesize / 2, nodecentres[c.Value].Y), orderpos = orderpos
                            });
                        }
                        else
                        {
                            tojoin.Add(new BaryPointInfo()
                            {
                                point = nodecentres[c.Value], toppos = new Point(nodecentres[c.Value].X, nodecentres[c.Value].Y - 10 * imagesize / 8), orderpos = orderpos
                            });
                        }
                    }
                }
            }

            if (tojoin.Count > 1)                                                                                      // we need two or more to make a barynode line
            {
                tojoin.Sort(delegate(BaryPointInfo l, BaryPointInfo r) { return(l.orderpos.CompareTo(r.orderpos)); }); // make sure in order

                if (horz)
                {
                    foreach (var c in tojoin)                                                                                              // find min y of cohort (same line) and set estimate pos to it
                    {
                        int minx = tojoin.Where(x => Math.Abs(x.toppos.X - c.toppos.X) < baryspacingmargin).Select(x => x.toppos.X).Min(); // find minx in cohort
                        c.est = minx - baryspacing;                                                                                        // don't change toppos, we need it for other estimations. hold in another var
                    }

                    foreach (var c in tojoin)
                    {
                        c.toppos = new Point(c.est, c.toppos.Y);    // apply calculated pos to toppos to align all barycentres to it
                    }
                }
                else
                {
                    foreach (var c in tojoin)                                                                                              // find min y of cohort (same line) and set estimate pos to it
                    {
                        int miny = tojoin.Where(y => Math.Abs(y.toppos.Y - c.toppos.Y) < baryspacingmargin).Select(x => x.toppos.Y).Min(); // find miny in cohort
                        c.est = miny - baryspacing;                                                                                        // don't change toppos, we need it for other estimations. hold in another var
                    }

                    foreach (var c in tojoin)
                    {
                        c.toppos = new Point(c.toppos.X, c.est);    // apply calculated pos to toppos to align all barycentres to it
                    }
                }

                ExtPictureBox.ImageElement ie = new ExtPictureBox.ImageElement();
                ie.OwnerDraw(DrawBaryTree, new Rectangle(0, 0, horz ? 1 : 0, 0), tojoin); // use Width, which does not get affected by repositiontree, to record if horz
                pc.Insert(0, ie);                                                         // insert first so drawn under

                //System.Diagnostics.Debug.Write("                        ".Substring(0, level * 3) + level + " Join co-ords");
                //for (int i = 0; i < tojoin.Count; i++) System.Diagnostics.Debug.Write(" " + tojoin[i].point + ":" + tojoin[i].toppos);

                Point pi;       // calculate and return the barycentre position
                if (horz)
                {
                    if (tojoin.Average(x => x.toppos.X) == tojoin[0].toppos.X)      // if all on same line
                    {
                        pi = new Point(tojoin[0].toppos.X, (int)tojoin.Select(y => y.point.Y).Average());
                        //System.Diagnostics.Debug.Write(" same xline");
                    }
                    else
                    {
                        pi = new Point(tojoin[0].toppos.X, tojoin[0].toppos.Y + imagesize);
                        //System.Diagnostics.Debug.Write(" not same xline");
                    }
                }
                else
                {
                    if (tojoin.Average(x => x.toppos.Y) == tojoin[0].toppos.Y)      // if all on same line
                    {
                        pi = new Point((int)tojoin.Select(x => x.point.X).Average(), tojoin[0].toppos.Y);
                        //System.Diagnostics.Debug.Write(" same yline");
                    }
                    else
                    {
                        pi = new Point(tojoin[0].point.X + imagesize, tojoin[0].toppos.Y); // objects are not on same line, so we set the barypoint off to side a bit of the first
                        //System.Diagnostics.Debug.Write(" not same yline");
                    }
                }

                //System.Diagnostics.Debug.WriteLine(" Pass back " + pi);
                //System.Diagnostics.Debug.WriteLine("");
                return(new Tuple <Point, int, int>(pi, level, orderpos));
            }
            else
            {
                return(new Tuple <Point, int, int>(Point.Empty, level, 0));
            }
        }