1 // Copyright 2001-2019 Crytek GmbH / Crytek Group. All rights reserved.
4 using System
.Collections
.Generic
;
9 using System
.Windows
.Forms
;
17 get { return m_size; }
18 set { m_size = value; }
24 set { m_tag = value; }
29 get { return m_name; }
30 set { m_name = value; }
33 public GLTexture Texture
35 get { return m_texture; }
36 set { m_texture = value; }
39 internal RectD Bounds
;
40 internal List
<TreemapNode
> Children
= new List
<TreemapNode
>();
46 private double m_size
;
48 private string m_name
= string.Empty
;
49 private GLTexture m_texture
= null;
52 class TreemapControl
: CanvasControl
54 private double m_pxNodeMargin
= 1.5;
56 private ToolTip m_tooltip
;
58 private TreemapNode m_hoverNode
;
59 private TreemapNode m_selectNode
;
60 private Point m_lastPoint
;
62 public TreemapNode Tree
64 get { return m_tree; }
67 public TreemapNode SelectedNode
69 get { return m_selectNode; }
72 public double NodeMarginPx
74 get { return m_pxNodeMargin; }
75 set { m_pxNodeMargin = value; }
78 public EventHandler SelectionChanged
;
80 public TreemapControl()
82 m_tooltip
= new ToolTip();
83 m_tooltip
.AutoPopDelay
= 5000;
84 m_tooltip
.InitialDelay
= 1000;
85 m_tooltip
.ReshowDelay
= 500;
88 public TreemapNode
CreateTree()
90 m_tree
= new TreemapNode();
91 m_tree
.Bounds
= new RectD(0, 0, 1, 1);
94 VirtualBounds
= m_tree
.Bounds
;
98 public TreemapNode
CreateChild(TreemapNode parent
)
100 TreemapNode n
= new TreemapNode();
101 parent
.Children
.Add(n
);
106 public void InvalidateLayout()
112 private TreemapNode
HitTest(TreemapNode n
, double x
, double y
)
114 if (x
>= n
.Bounds
.Left
&& x
< n
.Bounds
.Right
)
116 if (y
>= n
.Bounds
.Top
&& y
< n
.Bounds
.Bottom
)
118 foreach (TreemapNode c
in n
.Children
)
120 TreemapNode hn
= HitTest(c
, x
, y
);
132 public TreemapNode
HitTest(Point clientPt
)
134 double smpX
= SampleXFromClientX(clientPt
.X
);
135 double smpY
= SampleYFromClientY(clientPt
.Y
);
139 TreemapNode hit
= null;
141 m_tree
, new RectD(0, 0, 1, 1), smpX
, smpY
, 0,
142 (TreemapNode n
, RectD nBounds
, int depth
) =>
144 if (smpX
< nBounds
.Left
) return false;
145 if (smpX
>= nBounds
.Right
) return false;
146 if (smpY
< nBounds
.Top
) return false;
147 if (smpY
>= nBounds
.Bottom
) return false;
159 protected override void glDraw()
174 RectD vBounds
= View
;
176 double pxUnitsX
= GetSamplesPerPixelX();
177 double pxUnitsY
= GetSamplesPerPixelY();
179 OpenGL
.glBegin(OpenGL
.GL_QUADS
);
180 FillNode(m_tree
, new RectD(0, 0, 1, 1), vBounds
, pxUnitsX
, pxUnitsY
, 0);
183 float windowAspect
= ClientSize
.Width
/ (float)ClientSize
.Height
;
184 float viewportAspect
= (float)(View
.Width
/ View
.Height
);
187 m_tree
, new RectD(0, 0, 1, 1), pxUnitsX
, pxUnitsY
, 0,
188 (TreemapNode n
, RectD nBounds
, int depth
) =>
190 if (Overlaps(nBounds
, vBounds
))
192 RectD titleBounds
= new RectD(
196 nBounds
.Top
+ nBounds
.Height
* 0.03f
);
197 if (titleBounds
.Height
> pxUnitsY
* 3)
199 float yTextScale
= (float)(titleBounds
.Height
/ m_titleFont
.Height
);
200 float xTextScale
= yTextScale
/ (windowAspect
/ viewportAspect
);
201 m_titleFont
.Draw((float)titleBounds
.Left
, (float)titleBounds
.Top
, Color
.Black
, n
.Name
, xTextScale
, yTextScale
);
209 OpenGL
.glBegin(OpenGL
.GL_LINES
);
210 StrokeNode(m_tree
, new RectD(0, 0, 1, 1), vBounds
, pxUnitsX
, pxUnitsY
, 0);
215 protected virtual string FormatTooltip(TreemapNode node
)
220 protected override void OnMouseMove(MouseEventArgs e
)
222 if (e
.Button
== MouseButtons
.None
)
224 if (e
.X
!= m_lastPoint
.X
|| e
.Y
!= m_lastPoint
.Y
)
226 TreemapNode selectedNode
= HitTest(e
.Location
);
228 if (selectedNode
!= m_hoverNode
)
230 string tt
= string.Empty
;
232 m_hoverNode
= selectedNode
;
233 if (selectedNode
!= null)
234 tt
= FormatTooltip(selectedNode
);
236 m_tooltip
.SetToolTip(this, tt
);
240 m_lastPoint
= new Point(e
.X
, e
.Y
);
246 protected virtual void OnSelectedNode()
248 if (SelectionChanged
!= null)
249 SelectionChanged(this, EventArgs
.Empty
);
252 protected override void OnLClick(MouseEventArgs e
)
256 TreemapNode node
= HitTest(e
.Location
);
258 if (m_selectNode
!= null)
262 private void InitResources()
264 if (m_titleFont
== null)
266 Font font
= new Font(FontFamily
.GenericSansSerif
, 64.0f
, GraphicsUnit
.Pixel
);
267 m_titleFont
= new GLFont(font
);
271 private delegate bool EnumTreeHandler(TreemapNode n
, RectD nBounds
, int depth
);
273 private void EnumTree(TreemapNode n
, RectD nBounds
, double pxX
, double pxY
, int depth
, EnumTreeHandler h
)
275 if (h(n
, nBounds
, depth
))
277 RectD conBounds
= new RectD(
278 nBounds
.Left
+ nBounds
.Width
* 0.01f
,
279 nBounds
.Top
+ nBounds
.Height
* 0.03f
,
280 nBounds
.Right
- nBounds
.Width
* 0.01f
,
281 nBounds
.Bottom
- nBounds
.Height
* 0.01f
);
282 for (int i
= 0, c
= n
.Children
.Count
; i
!= c
; ++i
)
284 TreemapNode cn
= n
.Children
[i
];
286 RectD cBounds
= new RectD(
287 cn
.Bounds
.Left
* conBounds
.Width
+ conBounds
.Left
,
288 cn
.Bounds
.Top
* conBounds
.Height
+ conBounds
.Top
,
289 cn
.Bounds
.Right
* conBounds
.Width
+ conBounds
.Left
,
290 cn
.Bounds
.Bottom
* conBounds
.Height
+ conBounds
.Top
);
292 if (cBounds
.Width
> pxX
* 5)
294 cBounds
.Left
+= pxX
* m_pxNodeMargin
;
295 cBounds
.Right
-= pxX
* m_pxNodeMargin
;
298 if (cBounds
.Height
> pxY
* 5)
300 cBounds
.Top
+= pxY
* m_pxNodeMargin
;
301 cBounds
.Bottom
-= pxY
* m_pxNodeMargin
;
304 EnumTree(cn
, cBounds
, pxX
, pxY
, depth
+ 1, h
);
309 private static bool Overlaps(RectD a
, RectD b
)
311 if (a
.Right
<= b
.Left
) return false;
312 if (b
.Right
<= a
.Left
) return false;
313 if (a
.Bottom
<= b
.Top
) return false;
314 if (b
.Bottom
<= a
.Top
) return false;
318 private void FillNode(TreemapNode n
, RectD nBounds
, RectD vBounds
, double pxX
, double pxY
, int depth
)
320 if (Overlaps(nBounds
, vBounds
))
322 GLTexture tex
= n
.Texture
;
323 if (tex
!= null && tex
.Valid
)
326 OpenGL
.glEnable(OpenGL
.GL_TEXTURE_2D
);
327 OpenGL
.glDisable(OpenGL
.GL_BLEND
);
330 OpenGL
.glColor3f(1.0f
, 1.0f
, 1.0f
);
331 OpenGL
.glBegin(OpenGL
.GL_QUADS
);
335 RGB col
= new RGB(new HSV(depth
* (1.0f
/ 6.0f
), 0.5f
- depth
/ 256.0f
, 1));
336 OpenGL
.glColor3f(col
.r
, col
.g
, col
.b
);
340 OpenGL
.glTexCoord2f(0.0f
, 0.0f
);
341 OpenGL
.glVertex2f((float)nBounds
.Left
, (float)nBounds
.Top
);
343 OpenGL
.glTexCoord2f(0.0f
, 1.0f
);
344 OpenGL
.glVertex2f((float)nBounds
.Left
, (float)nBounds
.Bottom
);
346 OpenGL
.glTexCoord2f(1.0f
, 1.0f
);
347 OpenGL
.glVertex2f((float)nBounds
.Right
, (float)nBounds
.Bottom
);
349 OpenGL
.glTexCoord2f(1.0f
, 0.0f
);
350 OpenGL
.glVertex2f((float)nBounds
.Right
, (float)nBounds
.Top
);
353 if (tex
!= null && tex
.Valid
)
357 OpenGL
.glDisable(OpenGL
.GL_TEXTURE_2D
);
358 OpenGL
.glEnable(OpenGL
.GL_BLEND
);
359 OpenGL
.glBegin(OpenGL
.GL_QUADS
);
362 RectD conBounds
= new RectD(
363 nBounds
.Left
+ nBounds
.Width
* 0.01f
,
364 nBounds
.Top
+ nBounds
.Height
* 0.03f
,
365 nBounds
.Right
- nBounds
.Width
* 0.01f
,
366 nBounds
.Bottom
- nBounds
.Height
* 0.01f
);
368 for (int i
= 0, c
= n
.Children
.Count
; i
!= c
; ++i
)
370 TreemapNode cn
= n
.Children
[i
];
372 RectD cBounds
= new RectD(
373 cn
.Bounds
.Left
* conBounds
.Width
+ conBounds
.Left
,
374 cn
.Bounds
.Top
* conBounds
.Height
+ conBounds
.Top
,
375 cn
.Bounds
.Right
* conBounds
.Width
+ conBounds
.Left
,
376 cn
.Bounds
.Bottom
* conBounds
.Height
+ conBounds
.Top
);
378 if (cBounds
.Width
> pxX
* 5)
380 cBounds
.Left
+= pxX
* m_pxNodeMargin
;
381 cBounds
.Right
-= pxX
* m_pxNodeMargin
;
384 if (cBounds
.Height
> pxY
* 5)
386 cBounds
.Top
+= pxY
* m_pxNodeMargin
;
387 cBounds
.Bottom
-= pxY
* m_pxNodeMargin
;
390 FillNode(cn
, cBounds
, vBounds
, pxX
, pxY
, depth
+ 1);
395 private void StrokeNode(TreemapNode n
, RectD nBounds
, RectD vBounds
, double pxX
, double pxY
, int depth
)
397 if (Overlaps(nBounds
, vBounds
))
399 OpenGL
.glColor3f(0, 0, 0);
402 OpenGL
.glVertex2f((float)nBounds
.Left
, (float)nBounds
.Top
);
403 OpenGL
.glVertex2f((float)nBounds
.Left
, (float)nBounds
.Bottom
);
405 OpenGL
.glVertex2f((float)nBounds
.Left
, (float)nBounds
.Bottom
);
406 OpenGL
.glVertex2f((float)nBounds
.Right
, (float)nBounds
.Bottom
);
408 OpenGL
.glVertex2f((float)nBounds
.Right
, (float)nBounds
.Bottom
);
409 OpenGL
.glVertex2f((float)nBounds
.Right
, (float)nBounds
.Top
);
411 OpenGL
.glVertex2f((float)nBounds
.Right
, (float)nBounds
.Top
);
412 OpenGL
.glVertex2f((float)nBounds
.Left
, (float)nBounds
.Top
);
415 RectD conBounds
= new RectD(
416 nBounds
.Left
+ nBounds
.Width
* 0.01f
,
417 nBounds
.Top
+ nBounds
.Height
* 0.03f
,
418 nBounds
.Right
- nBounds
.Width
* 0.01f
,
419 nBounds
.Bottom
- nBounds
.Height
* 0.01f
);
421 for (int i
= 0, c
= n
.Children
.Count
; i
!= c
; ++i
)
423 TreemapNode cn
= n
.Children
[i
];
425 RectD cBounds
= new RectD(
426 cn
.Bounds
.Left
* conBounds
.Width
+ conBounds
.Left
,
427 cn
.Bounds
.Top
* conBounds
.Height
+ conBounds
.Top
,
428 cn
.Bounds
.Right
* conBounds
.Width
+ conBounds
.Left
,
429 cn
.Bounds
.Bottom
* conBounds
.Height
+ conBounds
.Top
);
431 if (cBounds
.Width
> pxX
* 5)
433 cBounds
.Left
+= pxX
* m_pxNodeMargin
;
434 cBounds
.Right
-= pxX
* m_pxNodeMargin
;
437 if (cBounds
.Height
> pxY
* 5)
439 cBounds
.Top
+= pxY
* m_pxNodeMargin
;
440 cBounds
.Bottom
-= pxY
* m_pxNodeMargin
;
443 StrokeNode(cn
, cBounds
, vBounds
, pxX
, pxY
, depth
+ 1);
448 static private bool CompareRatio(List
<TreemapNode
> row
, TreemapNode next
, double w
)
450 double mn
= float.MaxValue
;
454 for (int i
= 0, c
= row
.Count
; i
!= c
; ++ i
)
456 TreemapNode n
= row
[i
];
457 mx
= Math
.Max(mx
, n
.Size
);
458 mn
= Math
.Min(mn
, n
.Size
);
462 double sumSq
= sum
* sum
;
464 double ratio
= Math
.Max((wSq
* mx
) / sumSq
, sumSq
/ (wSq
* mn
));
466 double nextMx
= Math
.Max(mx
, next
.Size
);
467 double nextMn
= Math
.Min(mn
, next
.Size
);
468 double nextSum
= sum
+ next
.Size
;
470 double nextSumSq
= nextSum
* nextSum
;
471 double nextRatio
= Math
.Max((wSq
* nextMx
) / nextSumSq
, nextSumSq
/ (wSq
* nextMn
));
473 return ratio
<= nextRatio
;
476 static private void LayoutRow(ref RectD bounds
, List
<TreemapNode
> nodes
, out RectD remainder
)
480 for (int i
= 0, c
= nodes
.Count
; i
!= c
; ++ i
)
482 TreemapNode n
= nodes
[i
];
486 double boundsWidth
= bounds
.Width
;
487 double boundsHeight
= bounds
.Height
;
488 double stride
= sum
/ Math
.Min(boundsWidth
, boundsHeight
);
489 bool horz
= bounds
.Width
< bounds
.Height
;
491 double x
= bounds
.Left
;
492 double y
= bounds
.Top
;
494 for (int i
= 0, c
= nodes
.Count
; i
!= c
; ++i
)
496 TreemapNode n
= nodes
[i
];
497 double d
= n
.Size
/ stride
;
501 n
.Bounds
= new RectD(x
, y
, x
+ d
, y
+ stride
);
506 n
.Bounds
= new RectD(x
, y
, x
+ stride
, y
+ d
);
512 remainder
= new RectD(bounds
.Left
, bounds
.Top
+ stride
, bounds
.Right
, bounds
.Bottom
);
514 remainder
= new RectD(bounds
.Left
+ stride
, bounds
.Top
, bounds
.Right
, bounds
.Bottom
);
517 static private void Squarify(List
<TreemapNode
> nodes
)
519 RectD bounds
= new RectD(0, 0, 1, 1);
522 List
<TreemapNode
> row
= new List
<TreemapNode
>();
527 double w
= Math
.Min(bounds
.Width
, bounds
.Height
);
529 for (int c
= nodes
.Count
; ni
!= c
; ++ni
)
531 TreemapNode n
= nodes
[ni
];
532 if (CompareRatio(row
, n
, w
))
538 LayoutRow(ref bounds
, row
, out remainder
);
541 while (ni
< nodes
.Count
);
544 static private void LayoutTree(TreemapNode tree
)
546 Squarify(tree
.Children
);
548 for (int i
= 0, c
= tree
.Children
.Count
; i
!= c
; ++i
)
549 LayoutTree(tree
.Children
[i
]);
552 private GLFont m_titleFont
;
553 private TreemapNode m_tree
;
554 private bool m_dirty
;