Add explanatory comments to heatmap code
This commit is contained in:
@@ -5,6 +5,7 @@ using System.Globalization;
|
|||||||
|
|
||||||
namespace mbn_stock
|
namespace mbn_stock
|
||||||
{
|
{
|
||||||
|
// 등락률 구간과 해당 구간에 사용할 색상 값을 보관하는 바인딩용 모델입니다.
|
||||||
public class ColorRangeRule : INotifyPropertyChanged
|
public class ColorRangeRule : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private decimal _fromRate;
|
private decimal _fromRate;
|
||||||
@@ -78,11 +79,13 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 입력한 등락률이 이 색상 구간에 포함되는지 확인합니다.
|
||||||
public bool Contains(decimal value)
|
public bool Contains(decimal value)
|
||||||
{
|
{
|
||||||
return value >= MinRate && value < MaxRate;
|
return value >= MinRate && value < MaxRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #RRGGBB 문자열을 실제 Drawing Color로 변환할 수 있는지 확인합니다.
|
||||||
public static bool TryParseColor(string text, out Color color)
|
public static bool TryParseColor(string text, out Color color)
|
||||||
{
|
{
|
||||||
color = Color.Empty;
|
color = Color.Empty;
|
||||||
@@ -104,6 +107,7 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 사용자가 입력한 색상 문자열을 #RRGGBB 형태에 가깝게 정리합니다.
|
||||||
public static string NormalizeColorHex(string text)
|
public static string NormalizeColorHex(string text)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
@@ -129,6 +133,7 @@ namespace mbn_stock
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DataGridView 바인딩에 변경 사실을 알려 화면을 갱신합니다.
|
||||||
private void OnPropertyChanged(string propertyName)
|
private void OnPropertyChanged(string propertyName)
|
||||||
{
|
{
|
||||||
PropertyChangedEventHandler handler = PropertyChanged;
|
PropertyChangedEventHandler handler = PropertyChanged;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Windows.Forms;
|
|||||||
|
|
||||||
namespace mbn_stock
|
namespace mbn_stock
|
||||||
{
|
{
|
||||||
|
// 업종 데이터와 색상 규칙을 편집하고 히트맵 이미지를 미리보기/저장하는 테스트 폼입니다.
|
||||||
public partial class Form1 : Form
|
public partial class Form1 : Form
|
||||||
{
|
{
|
||||||
private readonly BindingList<SectorHeatMapItem> _items = new BindingList<SectorHeatMapItem>();
|
private readonly BindingList<SectorHeatMapItem> _items = new BindingList<SectorHeatMapItem>();
|
||||||
@@ -16,6 +17,7 @@ namespace mbn_stock
|
|||||||
private readonly Random _random = new Random();
|
private readonly Random _random = new Random();
|
||||||
private bool _loading;
|
private bool _loading;
|
||||||
|
|
||||||
|
// 디자이너 UI를 초기화하고 런타임에서는 기본 데이터와 색상 규칙을 연결합니다.
|
||||||
public Form1()
|
public Form1()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -32,6 +34,7 @@ namespace mbn_stock
|
|||||||
RandomizeValues();
|
RandomizeValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 미리보기 이미지 리소스를 해제해 Bitmap 핸들이 남지 않게 정리합니다.
|
||||||
protected override void OnFormClosed(FormClosedEventArgs e)
|
protected override void OnFormClosed(FormClosedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_previewBox != null && _previewBox.Image != null)
|
if (_previewBox != null && _previewBox.Image != null)
|
||||||
@@ -43,6 +46,7 @@ namespace mbn_stock
|
|||||||
base.OnFormClosed(e);
|
base.OnFormClosed(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 국내 증시 업종 테스트용 기본 행을 그리드에 채웁니다.
|
||||||
private void LoadDefaultItems()
|
private void LoadDefaultItems()
|
||||||
{
|
{
|
||||||
_loading = true;
|
_loading = true;
|
||||||
@@ -81,6 +85,7 @@ namespace mbn_stock
|
|||||||
_loading = false;
|
_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 렌더러가 제공하는 기본 등락률 색상 구간을 그리드에 채웁니다.
|
||||||
private void LoadDefaultColorRules()
|
private void LoadDefaultColorRules()
|
||||||
{
|
{
|
||||||
_loading = true;
|
_loading = true;
|
||||||
@@ -95,6 +100,7 @@ namespace mbn_stock
|
|||||||
_colorRuleGrid.Refresh();
|
_colorRuleGrid.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 각 업종의 시가총액과 등락률을 테스트용 임의 값으로 갱신합니다.
|
||||||
private void RandomizeValues()
|
private void RandomizeValues()
|
||||||
{
|
{
|
||||||
_loading = true;
|
_loading = true;
|
||||||
@@ -110,6 +116,7 @@ namespace mbn_stock
|
|||||||
RefreshPreview();
|
RefreshPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 현재 업종 데이터와 색상 규칙으로 1920x1080 히트맵 미리보기를 다시 그립니다.
|
||||||
private void RefreshPreview()
|
private void RefreshPreview()
|
||||||
{
|
{
|
||||||
if (_loading || _previewBox == null)
|
if (_loading || _previewBox == null)
|
||||||
@@ -143,11 +150,13 @@ namespace mbn_stock
|
|||||||
_colorRules.Count);
|
_colorRules.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 랜덤 값 버튼 클릭 시 테스트 데이터를 다시 생성합니다.
|
||||||
private void RandomButton_Click(object sender, EventArgs e)
|
private void RandomButton_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
RandomizeValues();
|
RandomizeValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PNG 저장 버튼 클릭 시 현재 설정으로 이미지를 렌더링해 파일로 저장합니다.
|
||||||
private void SaveButton_Click(object sender, EventArgs e)
|
private void SaveButton_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (!_grid.EndEdit())
|
if (!_grid.EndEdit())
|
||||||
@@ -172,21 +181,24 @@ namespace mbn_stock
|
|||||||
bitmap.Save(dialog.FileName, ImageFormat.Png);
|
bitmap.Save(dialog.FileName, ImageFormat.Png);
|
||||||
}
|
}
|
||||||
|
|
||||||
_statusLabel.Text = "저장 완료: " + dialog.FileName;
|
_statusLabel.Text = "저장 완료: " + dialog.FileName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 기본 색상 버튼 클릭 시 등락률 색상 규칙을 초기값으로 되돌립니다.
|
||||||
private void ResetColorRulesButton_Click(object sender, EventArgs e)
|
private void ResetColorRulesButton_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
LoadDefaultColorRules();
|
LoadDefaultColorRules();
|
||||||
RefreshPreview();
|
RefreshPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 업종 그리드 편집이 끝나면 미리보기를 갱신합니다.
|
||||||
private void Grid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
private void Grid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
||||||
{
|
{
|
||||||
RefreshPreview();
|
RefreshPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 업종 그리드의 숫자 입력값을 decimal로 변환합니다.
|
||||||
private void Grid_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
|
private void Grid_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
|
||||||
{
|
{
|
||||||
if (_grid.Columns[e.ColumnIndex].DataPropertyName == "Name")
|
if (_grid.Columns[e.ColumnIndex].DataPropertyName == "Name")
|
||||||
@@ -204,6 +216,7 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 업종 그리드에 잘못된 숫자나 0 이하 시가총액이 들어오지 않게 검증합니다.
|
||||||
private void Grid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
|
private void Grid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
|
||||||
{
|
{
|
||||||
if (_grid.Columns[e.ColumnIndex].DataPropertyName == "Name")
|
if (_grid.Columns[e.ColumnIndex].DataPropertyName == "Name")
|
||||||
@@ -231,12 +244,14 @@ namespace mbn_stock
|
|||||||
_grid.Rows[e.RowIndex].ErrorText = string.Empty;
|
_grid.Rows[e.RowIndex].ErrorText = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 업종 그리드 바인딩 오류가 앱 예외로 번지지 않도록 상태 메시지로 처리합니다.
|
||||||
private void Grid_DataError(object sender, DataGridViewDataErrorEventArgs e)
|
private void Grid_DataError(object sender, DataGridViewDataErrorEventArgs e)
|
||||||
{
|
{
|
||||||
e.ThrowException = false;
|
e.ThrowException = false;
|
||||||
_statusLabel.Text = "입력값을 확인해 주세요.";
|
_statusLabel.Text = "입력값을 확인해 주세요.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 값 또는 미리보기 칸을 더블클릭하면 색상 선택 창을 엽니다.
|
||||||
private void ColorRuleGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
|
private void ColorRuleGrid_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.RowIndex < 0 ||
|
if (e.RowIndex < 0 ||
|
||||||
@@ -268,12 +283,14 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 규칙 편집이 끝나면 표 색상과 미리보기를 갱신합니다.
|
||||||
private void ColorRuleGrid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
private void ColorRuleGrid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
||||||
{
|
{
|
||||||
_colorRuleGrid.Invalidate();
|
_colorRuleGrid.Invalidate();
|
||||||
RefreshPreview();
|
RefreshPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 미리보기 칸을 실제 색상 배경으로 표시합니다.
|
||||||
private void ColorRuleGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
private void ColorRuleGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.RowIndex < 0 || _colorRuleGrid.Columns[e.ColumnIndex] != _colorPreviewColumn)
|
if (e.RowIndex < 0 || _colorRuleGrid.Columns[e.ColumnIndex] != _colorPreviewColumn)
|
||||||
@@ -293,6 +310,7 @@ namespace mbn_stock
|
|||||||
e.FormattingApplied = true;
|
e.FormattingApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 규칙 그리드의 숫자와 색상 문자열을 모델 값으로 변환합니다.
|
||||||
private void ColorRuleGrid_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
|
private void ColorRuleGrid_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
|
||||||
{
|
{
|
||||||
string propertyName = _colorRuleGrid.Columns[e.ColumnIndex].DataPropertyName;
|
string propertyName = _colorRuleGrid.Columns[e.ColumnIndex].DataPropertyName;
|
||||||
@@ -315,6 +333,7 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 규칙 구간값과 #RRGGBB 색상 형식을 검증합니다.
|
||||||
private void ColorRuleGrid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
|
private void ColorRuleGrid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
|
||||||
{
|
{
|
||||||
string propertyName = _colorRuleGrid.Columns[e.ColumnIndex].DataPropertyName;
|
string propertyName = _colorRuleGrid.Columns[e.ColumnIndex].DataPropertyName;
|
||||||
@@ -344,12 +363,14 @@ namespace mbn_stock
|
|||||||
_colorRuleGrid.Rows[e.RowIndex].ErrorText = string.Empty;
|
_colorRuleGrid.Rows[e.RowIndex].ErrorText = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 규칙 바인딩 오류가 앱 예외로 번지지 않도록 상태 메시지로 처리합니다.
|
||||||
private void ColorRuleGrid_DataError(object sender, DataGridViewDataErrorEventArgs e)
|
private void ColorRuleGrid_DataError(object sender, DataGridViewDataErrorEventArgs e)
|
||||||
{
|
{
|
||||||
e.ThrowException = false;
|
e.ThrowException = false;
|
||||||
_statusLabel.Text = "색상 규칙 입력값을 확인해 주세요.";
|
_statusLabel.Text = "색상 규칙 입력값을 확인해 주세요.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 새 색상 규칙 행을 추가할 때 기본값을 넣습니다.
|
||||||
private void ColorRuleGrid_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
private void ColorRuleGrid_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e)
|
||||||
{
|
{
|
||||||
e.Row.Cells[_fromRateColumn.Index].Value = 0m;
|
e.Row.Cells[_fromRateColumn.Index].Value = 0m;
|
||||||
@@ -357,11 +378,13 @@ namespace mbn_stock
|
|||||||
e.Row.Cells[_colorHexColumn.Index].Value = "#808080";
|
e.Row.Cells[_colorHexColumn.Index].Value = "#808080";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 규칙 행이 삭제되면 남은 규칙으로 미리보기를 다시 그립니다.
|
||||||
private void ColorRuleGrid_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
|
private void ColorRuleGrid_RowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
|
||||||
{
|
{
|
||||||
RefreshPreview();
|
RefreshPreview();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 퍼센트 기호가 붙은 문자열도 decimal 숫자로 읽을 수 있게 변환합니다.
|
||||||
private static bool TryParseDecimal(string text, out decimal value)
|
private static bool TryParseDecimal(string text, out decimal value)
|
||||||
{
|
{
|
||||||
value = 0m;
|
value = 0m;
|
||||||
@@ -376,6 +399,7 @@ namespace mbn_stock
|
|||||||
|| decimal.TryParse(normalized, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
|
|| decimal.TryParse(normalized, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ColorDialog에서 선택한 색상을 #RRGGBB 문자열로 변환합니다.
|
||||||
private static string ToHex(Color color)
|
private static string ToHex(Color color)
|
||||||
{
|
{
|
||||||
return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2}", color.R, color.G, color.B);
|
return string.Format(CultureInfo.InvariantCulture, "#{0:X2}{1:X2}{2:X2}", color.R, color.G, color.B);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace mbn_stock
|
namespace mbn_stock
|
||||||
{
|
{
|
||||||
|
// 업종별 데이터를 시가총액 면적 기반 트리맵 이미지로 렌더링하는 정적 유틸리티입니다.
|
||||||
public static class HeatMapRenderer
|
public static class HeatMapRenderer
|
||||||
{
|
{
|
||||||
public const int ExportWidth = 1920;
|
public const int ExportWidth = 1920;
|
||||||
@@ -18,21 +19,25 @@ namespace mbn_stock
|
|||||||
private const float TileGap = 6f;
|
private const float TileGap = 6f;
|
||||||
private static readonly float TileRadius = 0f;
|
private static readonly float TileRadius = 0f;
|
||||||
|
|
||||||
|
// 기본 이미지 크기와 기본 색상 규칙으로 히트맵을 렌더링합니다.
|
||||||
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems)
|
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems)
|
||||||
{
|
{
|
||||||
return Render(sourceItems, new Size(ExportWidth, ExportHeight), CreateDefaultColorRules());
|
return Render(sourceItems, new Size(ExportWidth, ExportHeight), CreateDefaultColorRules());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 기본 이미지 크기에 사용자가 넘긴 색상 규칙을 적용해 히트맵을 렌더링합니다.
|
||||||
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems, IEnumerable<ColorRangeRule> colorRules)
|
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems, IEnumerable<ColorRangeRule> colorRules)
|
||||||
{
|
{
|
||||||
return Render(sourceItems, new Size(ExportWidth, ExportHeight), colorRules);
|
return Render(sourceItems, new Size(ExportWidth, ExportHeight), colorRules);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 지정한 이미지 크기와 기본 색상 규칙으로 히트맵을 렌더링합니다.
|
||||||
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems, Size size)
|
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems, Size size)
|
||||||
{
|
{
|
||||||
return Render(sourceItems, size, CreateDefaultColorRules());
|
return Render(sourceItems, size, CreateDefaultColorRules());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 지정한 이미지 크기와 색상 규칙으로 전체 히트맵 Bitmap을 생성합니다.
|
||||||
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems, Size size, IEnumerable<ColorRangeRule> colorRules)
|
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems, Size size, IEnumerable<ColorRangeRule> colorRules)
|
||||||
{
|
{
|
||||||
Bitmap bitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb);
|
Bitmap bitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb);
|
||||||
@@ -65,6 +70,7 @@ namespace mbn_stock
|
|||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 초기 화면에서 사용할 기본 등락률 구간 색상 규칙을 만듭니다.
|
||||||
public static List<ColorRangeRule> CreateDefaultColorRules()
|
public static List<ColorRangeRule> CreateDefaultColorRules()
|
||||||
{
|
{
|
||||||
return new List<ColorRangeRule>
|
return new List<ColorRangeRule>
|
||||||
@@ -78,6 +84,7 @@ namespace mbn_stock
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 기본 색상 규칙 목록에 넣을 한 구간을 생성합니다.
|
||||||
private static ColorRangeRule CreateColorRule(decimal fromRate, decimal toRate, string colorHex)
|
private static ColorRangeRule CreateColorRule(decimal fromRate, decimal toRate, string colorHex)
|
||||||
{
|
{
|
||||||
return new ColorRangeRule
|
return new ColorRangeRule
|
||||||
@@ -88,6 +95,7 @@ namespace mbn_stock
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 시가총액 비중을 전체 캔버스 면적으로 환산해 배치 노드를 만듭니다.
|
||||||
private static List<TreeMapNode> BuildNodes(IList<SectorHeatMapItem> items, RectangleF bounds)
|
private static List<TreeMapNode> BuildNodes(IList<SectorHeatMapItem> items, RectangleF bounds)
|
||||||
{
|
{
|
||||||
decimal total = items.Sum(item => item.MarketCap);
|
decimal total = items.Sum(item => item.MarketCap);
|
||||||
@@ -105,6 +113,7 @@ namespace mbn_stock
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 노드 목록을 면적 균형이 맞도록 좌우 또는 상하로 재귀 분할합니다.
|
||||||
private static void LayoutBinary(IList<TreeMapNode> nodes, RectangleF bounds, List<TreeMapNode> result)
|
private static void LayoutBinary(IList<TreeMapNode> nodes, RectangleF bounds, List<TreeMapNode> result)
|
||||||
{
|
{
|
||||||
if (nodes.Count == 0)
|
if (nodes.Count == 0)
|
||||||
@@ -145,6 +154,7 @@ namespace mbn_stock
|
|||||||
LayoutBinary(secondGroup, bottomBounds, result);
|
LayoutBinary(secondGroup, bottomBounds, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 현재 노드 목록을 두 그룹으로 나눌 가장 균형 잡힌 지점을 찾습니다.
|
||||||
private static int FindBalancedSplit(IList<TreeMapNode> nodes, double totalArea)
|
private static int FindBalancedSplit(IList<TreeMapNode> nodes, double totalArea)
|
||||||
{
|
{
|
||||||
double target = totalArea / 2d;
|
double target = totalArea / 2d;
|
||||||
@@ -167,6 +177,7 @@ namespace mbn_stock
|
|||||||
return splitIndex;
|
return splitIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 계산된 각 사각형 영역을 색상으로 채우고 텍스트를 그립니다.
|
||||||
private static void DrawTiles(Graphics graphics, IList<TreeMapNode> nodes, IList<ColorRangeRule> colorRules)
|
private static void DrawTiles(Graphics graphics, IList<TreeMapNode> nodes, IList<ColorRangeRule> colorRules)
|
||||||
{
|
{
|
||||||
foreach (TreeMapNode node in nodes)
|
foreach (TreeMapNode node in nodes)
|
||||||
@@ -200,6 +211,7 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 업종명과 등락률을 사각형 중앙에 흰색 텍스트로 그립니다.
|
||||||
private static void DrawTileText(Graphics graphics, SectorHeatMapItem item, RectangleF tileBounds, Color fill)
|
private static void DrawTileText(Graphics graphics, SectorHeatMapItem item, RectangleF tileBounds, Color fill)
|
||||||
{
|
{
|
||||||
RectangleF textBounds = Inset(tileBounds, Math.Max(10f, Math.Min(tileBounds.Width, tileBounds.Height) * 0.07f));
|
RectangleF textBounds = Inset(tileBounds, Math.Max(10f, Math.Min(tileBounds.Width, tileBounds.Height) * 0.07f));
|
||||||
@@ -235,6 +247,7 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 업종명과 등락률이 사각형 안에 들어가도록 최대 폰트 크기를 찾습니다.
|
||||||
private static float FindFittingFontSize(Graphics graphics, string name, string rateText, RectangleF bounds)
|
private static float FindFittingFontSize(Graphics graphics, string name, string rateText, RectangleF bounds)
|
||||||
{
|
{
|
||||||
float maxSize = Math.Min(96f, Math.Max(12f, Math.Min(bounds.Width * 0.18f, bounds.Height * 0.34f)));
|
float maxSize = Math.Min(96f, Math.Max(12f, Math.Min(bounds.Width * 0.18f, bounds.Height * 0.34f)));
|
||||||
@@ -260,6 +273,7 @@ namespace mbn_stock
|
|||||||
return 8f;
|
return 8f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 규칙에 맞는 채움색을 찾고, 없으면 기본 빨강/파랑 계산으로 대체합니다.
|
||||||
private static Color GetFillColor(decimal changeRate, IList<ColorRangeRule> colorRules)
|
private static Color GetFillColor(decimal changeRate, IList<ColorRangeRule> colorRules)
|
||||||
{
|
{
|
||||||
if (colorRules != null && colorRules.Count > 0)
|
if (colorRules != null && colorRules.Count > 0)
|
||||||
@@ -274,6 +288,7 @@ namespace mbn_stock
|
|||||||
return GetDefaultFillColor(changeRate);
|
return GetDefaultFillColor(changeRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 등락률이 속한 색상 규칙을 찾고 범위를 벗어나면 양 끝 색상으로 보정합니다.
|
||||||
private static bool TryGetRuleColor(decimal changeRate, IList<ColorRangeRule> colorRules, out Color color)
|
private static bool TryGetRuleColor(decimal changeRate, IList<ColorRangeRule> colorRules, out Color color)
|
||||||
{
|
{
|
||||||
color = Color.Empty;
|
color = Color.Empty;
|
||||||
@@ -306,6 +321,7 @@ namespace mbn_stock
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 규칙이 없을 때 ±30% 기준으로 빨강/파랑 농도를 계산합니다.
|
||||||
private static Color GetDefaultFillColor(decimal changeRate)
|
private static Color GetDefaultFillColor(decimal changeRate)
|
||||||
{
|
{
|
||||||
double intensity = Math.Min(1d, Math.Abs(decimal.ToDouble(changeRate)) / decimal.ToDouble(MaxRatePercent));
|
double intensity = Math.Min(1d, Math.Abs(decimal.ToDouble(changeRate)) / decimal.ToDouble(MaxRatePercent));
|
||||||
@@ -323,6 +339,7 @@ namespace mbn_stock
|
|||||||
return Color.FromArgb(226, 228, 232);
|
return Color.FromArgb(226, 228, 232);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 비어 있거나 잘못된 색상 규칙을 제거하고 등락률 시작값 기준으로 정렬합니다.
|
||||||
private static List<ColorRangeRule> NormalizeColorRules(IEnumerable<ColorRangeRule> colorRules)
|
private static List<ColorRangeRule> NormalizeColorRules(IEnumerable<ColorRangeRule> colorRules)
|
||||||
{
|
{
|
||||||
List<ColorRangeRule> rules = colorRules == null
|
List<ColorRangeRule> rules = colorRules == null
|
||||||
@@ -336,12 +353,14 @@ namespace mbn_stock
|
|||||||
return rules.Count == 0 ? CreateDefaultColorRules() : rules;
|
return rules.Count == 0 ? CreateDefaultColorRules() : rules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 색상 규칙이 실제 렌더링에 사용할 수 있는 색상인지 확인합니다.
|
||||||
private static bool IsValidColorRule(ColorRangeRule rule)
|
private static bool IsValidColorRule(ColorRangeRule rule)
|
||||||
{
|
{
|
||||||
Color color;
|
Color color;
|
||||||
return ColorRangeRule.TryParseColor(rule.ColorHex, out color);
|
return ColorRangeRule.TryParseColor(rule.ColorHex, out color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 두 색상 사이를 amount 비율로 보간해 중간 색상을 만듭니다.
|
||||||
private static Color Interpolate(Color from, Color to, double amount)
|
private static Color Interpolate(Color from, Color to, double amount)
|
||||||
{
|
{
|
||||||
amount = Math.Max(0d, Math.Min(1d, amount));
|
amount = Math.Max(0d, Math.Min(1d, amount));
|
||||||
@@ -351,6 +370,7 @@ namespace mbn_stock
|
|||||||
return Color.FromArgb(r, g, b);
|
return Color.FromArgb(r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 등락률 숫자를 +1.23% 또는 -1.23% 형태의 문자열로 바꿉니다.
|
||||||
private static string FormatRate(decimal value)
|
private static string FormatRate(decimal value)
|
||||||
{
|
{
|
||||||
string format = value > 0m ? "+0.00'%'"
|
string format = value > 0m ? "+0.00'%'"
|
||||||
@@ -360,6 +380,7 @@ namespace mbn_stock
|
|||||||
return value.ToString(format, CultureInfo.InvariantCulture);
|
return value.ToString(format, CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 사각형 안쪽에 여백을 적용한 새 영역을 계산합니다.
|
||||||
private static RectangleF Inset(RectangleF rectangle, float padding)
|
private static RectangleF Inset(RectangleF rectangle, float padding)
|
||||||
{
|
{
|
||||||
float horizontal = Math.Min(padding, rectangle.Width / 2f - 0.5f);
|
float horizontal = Math.Min(padding, rectangle.Width / 2f - 0.5f);
|
||||||
@@ -382,6 +403,7 @@ namespace mbn_stock
|
|||||||
Math.Max(1f, rectangle.Height - vertical * 2f));
|
Math.Max(1f, rectangle.Height - vertical * 2f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 둥근 모서리 옵션을 사용할 때 필요한 사각형 경로를 만듭니다.
|
||||||
private static GraphicsPath CreateRoundedRectangle(RectangleF bounds, float radius)
|
private static GraphicsPath CreateRoundedRectangle(RectangleF bounds, float radius)
|
||||||
{
|
{
|
||||||
float diameter = radius * 2f;
|
float diameter = radius * 2f;
|
||||||
@@ -394,6 +416,7 @@ namespace mbn_stock
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 렌더링 가능한 업종 데이터가 없을 때 안내 문구를 그립니다.
|
||||||
private static void DrawEmptyMessage(Graphics graphics, RectangleF bounds)
|
private static void DrawEmptyMessage(Graphics graphics, RectangleF bounds)
|
||||||
{
|
{
|
||||||
using (Font font = new Font("Malgun Gothic", 40f, FontStyle.Bold, GraphicsUnit.Pixel))
|
using (Font font = new Font("Malgun Gothic", 40f, FontStyle.Bold, GraphicsUnit.Pixel))
|
||||||
@@ -406,8 +429,10 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 트리맵 배치 중 한 업종의 면적과 최종 사각형 영역을 함께 보관합니다.
|
||||||
private sealed class TreeMapNode
|
private sealed class TreeMapNode
|
||||||
{
|
{
|
||||||
|
// 업종 데이터와 비율 기반 면적을 묶어 배치 노드를 만듭니다.
|
||||||
public TreeMapNode(SectorHeatMapItem item, double area)
|
public TreeMapNode(SectorHeatMapItem item, double area)
|
||||||
{
|
{
|
||||||
Item = item;
|
Item = item;
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ using System.Windows.Forms;
|
|||||||
|
|
||||||
namespace mbn_stock
|
namespace mbn_stock
|
||||||
{
|
{
|
||||||
|
// WinForms 애플리케이션을 시작하고 메인 폼을 여는 진입점입니다.
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
/// <summary>
|
// 애플리케이션 기본 스타일을 설정한 뒤 Form1을 실행합니다.
|
||||||
/// 해당 애플리케이션의 주 진입점입니다.
|
|
||||||
/// </summary>
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.ComponentModel;
|
|||||||
|
|
||||||
namespace mbn_stock
|
namespace mbn_stock
|
||||||
{
|
{
|
||||||
|
// 업종명, 시가총액, 등락률을 한 행으로 보관하는 히트맵 입력 모델입니다.
|
||||||
public class SectorHeatMapItem : INotifyPropertyChanged
|
public class SectorHeatMapItem : INotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private string _name;
|
private string _name;
|
||||||
@@ -55,6 +56,7 @@ namespace mbn_stock
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DataGridView 바인딩에 변경 사실을 알려 화면을 갱신합니다.
|
||||||
private void OnPropertyChanged(string propertyName)
|
private void OnPropertyChanged(string propertyName)
|
||||||
{
|
{
|
||||||
PropertyChangedEventHandler handler = PropertyChanged;
|
PropertyChangedEventHandler handler = PropertyChanged;
|
||||||
|
|||||||
Reference in New Issue
Block a user