349 lines
12 KiB
C#
349 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Windows.Forms;
|
|
|
|
namespace mbn_stock
|
|
{
|
|
public partial class Form1 : Form
|
|
{
|
|
private readonly BindingList<SectorHeatMapItem> _items = new BindingList<SectorHeatMapItem>();
|
|
private readonly Random _random = new Random();
|
|
|
|
private DataGridView _grid;
|
|
private PictureBox _previewBox;
|
|
private Button _randomButton;
|
|
private Button _saveButton;
|
|
private Label _statusLabel;
|
|
private bool _loading;
|
|
|
|
public Form1()
|
|
{
|
|
InitializeComponent();
|
|
BuildUi();
|
|
LoadDefaultItems();
|
|
RandomizeValues();
|
|
}
|
|
|
|
protected override void OnFormClosed(FormClosedEventArgs e)
|
|
{
|
|
if (_previewBox != null && _previewBox.Image != null)
|
|
{
|
|
_previewBox.Image.Dispose();
|
|
_previewBox.Image = null;
|
|
}
|
|
|
|
base.OnFormClosed(e);
|
|
}
|
|
|
|
private void BuildUi()
|
|
{
|
|
Font = new Font("Malgun Gothic", 9.5f, FontStyle.Regular, GraphicsUnit.Point);
|
|
BackColor = Color.FromArgb(245, 246, 248);
|
|
MinimumSize = new Size(1100, 700);
|
|
|
|
SplitContainer splitContainer = new SplitContainer();
|
|
splitContainer.Dock = DockStyle.Fill;
|
|
splitContainer.FixedPanel = FixedPanel.Panel1;
|
|
splitContainer.SplitterDistance = 500;
|
|
splitContainer.Panel1MinSize = 420;
|
|
splitContainer.Panel2MinSize = 520;
|
|
splitContainer.BackColor = Color.FromArgb(210, 214, 220);
|
|
|
|
Panel leftPanel = new Panel();
|
|
leftPanel.Dock = DockStyle.Fill;
|
|
leftPanel.Padding = new Padding(14);
|
|
leftPanel.BackColor = Color.White;
|
|
|
|
FlowLayoutPanel toolbar = new FlowLayoutPanel();
|
|
toolbar.Dock = DockStyle.Top;
|
|
toolbar.Height = 46;
|
|
toolbar.FlowDirection = FlowDirection.LeftToRight;
|
|
toolbar.WrapContents = false;
|
|
|
|
_randomButton = new Button();
|
|
_randomButton.Text = "랜덤 값";
|
|
_randomButton.Width = 110;
|
|
_randomButton.Height = 32;
|
|
_randomButton.Click += RandomButton_Click;
|
|
|
|
_saveButton = new Button();
|
|
_saveButton.Text = "PNG 저장";
|
|
_saveButton.Width = 110;
|
|
_saveButton.Height = 32;
|
|
_saveButton.Click += SaveButton_Click;
|
|
|
|
toolbar.Controls.Add(_randomButton);
|
|
toolbar.Controls.Add(_saveButton);
|
|
|
|
_grid = new DataGridView();
|
|
_grid.Dock = DockStyle.Fill;
|
|
_grid.AutoGenerateColumns = false;
|
|
_grid.AllowUserToAddRows = false;
|
|
_grid.AllowUserToDeleteRows = false;
|
|
_grid.AllowUserToResizeRows = false;
|
|
_grid.RowHeadersVisible = false;
|
|
_grid.SelectionMode = DataGridViewSelectionMode.CellSelect;
|
|
_grid.MultiSelect = false;
|
|
_grid.BackgroundColor = Color.White;
|
|
_grid.BorderStyle = BorderStyle.FixedSingle;
|
|
_grid.EnableHeadersVisualStyles = false;
|
|
_grid.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(238, 241, 245);
|
|
_grid.ColumnHeadersDefaultCellStyle.ForeColor = Color.FromArgb(30, 35, 45);
|
|
_grid.ColumnHeadersDefaultCellStyle.Font = new Font(Font, FontStyle.Bold);
|
|
_grid.DefaultCellStyle.SelectionBackColor = Color.FromArgb(218, 232, 255);
|
|
_grid.DefaultCellStyle.SelectionForeColor = Color.FromArgb(20, 24, 32);
|
|
_grid.DataError += Grid_DataError;
|
|
_grid.CellEndEdit += Grid_CellEndEdit;
|
|
_grid.CellParsing += Grid_CellParsing;
|
|
_grid.CellValidating += Grid_CellValidating;
|
|
|
|
DataGridViewTextBoxColumn nameColumn = new DataGridViewTextBoxColumn();
|
|
nameColumn.DataPropertyName = "Name";
|
|
nameColumn.HeaderText = "업종";
|
|
nameColumn.ReadOnly = true;
|
|
nameColumn.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
|
nameColumn.FillWeight = 130f;
|
|
|
|
DataGridViewTextBoxColumn capColumn = new DataGridViewTextBoxColumn();
|
|
capColumn.DataPropertyName = "MarketCap";
|
|
capColumn.HeaderText = "시가총액";
|
|
capColumn.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
|
capColumn.FillWeight = 90f;
|
|
capColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
|
|
capColumn.DefaultCellStyle.Format = "N0";
|
|
|
|
DataGridViewTextBoxColumn rateColumn = new DataGridViewTextBoxColumn();
|
|
rateColumn.DataPropertyName = "ChangeRate";
|
|
rateColumn.HeaderText = "등락률(%)";
|
|
rateColumn.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
|
|
rateColumn.FillWeight = 90f;
|
|
rateColumn.DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
|
|
rateColumn.DefaultCellStyle.Format = "+0.00;-0.00;0.00";
|
|
|
|
_grid.Columns.Add(nameColumn);
|
|
_grid.Columns.Add(capColumn);
|
|
_grid.Columns.Add(rateColumn);
|
|
_grid.DataSource = _items;
|
|
|
|
_statusLabel = new Label();
|
|
_statusLabel.Dock = DockStyle.Bottom;
|
|
_statusLabel.Height = 30;
|
|
_statusLabel.TextAlign = ContentAlignment.MiddleLeft;
|
|
_statusLabel.ForeColor = Color.FromArgb(80, 86, 96);
|
|
|
|
leftPanel.Controls.Add(_grid);
|
|
leftPanel.Controls.Add(toolbar);
|
|
leftPanel.Controls.Add(_statusLabel);
|
|
|
|
Panel previewPanel = new Panel();
|
|
previewPanel.Dock = DockStyle.Fill;
|
|
previewPanel.Padding = new Padding(14);
|
|
previewPanel.BackColor = Color.FromArgb(29, 32, 39);
|
|
|
|
_previewBox = new PictureBox();
|
|
_previewBox.Dock = DockStyle.Fill;
|
|
_previewBox.BackColor = Color.FromArgb(20, 22, 28);
|
|
_previewBox.SizeMode = PictureBoxSizeMode.Zoom;
|
|
|
|
previewPanel.Controls.Add(_previewBox);
|
|
splitContainer.Panel1.Controls.Add(leftPanel);
|
|
splitContainer.Panel2.Controls.Add(previewPanel);
|
|
|
|
Controls.Add(splitContainer);
|
|
}
|
|
|
|
private void LoadDefaultItems()
|
|
{
|
|
_loading = true;
|
|
_items.Clear();
|
|
|
|
string[] sectors =
|
|
{
|
|
"전기전자",
|
|
"금융업",
|
|
"서비스업",
|
|
"운수장비",
|
|
"화학",
|
|
"통신업",
|
|
"의약품",
|
|
"철강금속",
|
|
"유통업",
|
|
"건설업",
|
|
"음식료품",
|
|
"기계",
|
|
"운수창고",
|
|
"전기가스업",
|
|
"보험",
|
|
"증권"
|
|
};
|
|
|
|
foreach (string sector in sectors)
|
|
{
|
|
_items.Add(new SectorHeatMapItem
|
|
{
|
|
Name = sector,
|
|
MarketCap = 100m,
|
|
ChangeRate = 0m
|
|
});
|
|
}
|
|
|
|
_loading = false;
|
|
}
|
|
|
|
private void RandomizeValues()
|
|
{
|
|
_loading = true;
|
|
|
|
foreach (SectorHeatMapItem item in _items)
|
|
{
|
|
item.MarketCap = _random.Next(25, 260);
|
|
item.ChangeRate = Math.Round((decimal)(_random.NextDouble() * 60d - 30d), 2);
|
|
}
|
|
|
|
_loading = false;
|
|
_grid.Refresh();
|
|
RefreshPreview();
|
|
}
|
|
|
|
private void RefreshPreview()
|
|
{
|
|
if (_loading || _previewBox == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<SectorHeatMapItem> snapshot = _items
|
|
.Select(item => new SectorHeatMapItem
|
|
{
|
|
Name = item.Name,
|
|
MarketCap = item.MarketCap,
|
|
ChangeRate = item.ChangeRate
|
|
})
|
|
.ToList();
|
|
|
|
Bitmap newImage = HeatMapRenderer.Render(snapshot);
|
|
Image oldImage = _previewBox.Image;
|
|
_previewBox.Image = newImage;
|
|
|
|
if (oldImage != null)
|
|
{
|
|
oldImage.Dispose();
|
|
}
|
|
|
|
_statusLabel.Text = string.Format(
|
|
CultureInfo.CurrentCulture,
|
|
"출력 {0:N0} x {1:N0}px / 색상 기준 ±{2:N0}%",
|
|
HeatMapRenderer.ExportWidth,
|
|
HeatMapRenderer.ExportHeight,
|
|
HeatMapRenderer.MaxRatePercent);
|
|
}
|
|
|
|
private void RandomButton_Click(object sender, EventArgs e)
|
|
{
|
|
RandomizeValues();
|
|
}
|
|
|
|
private void SaveButton_Click(object sender, EventArgs e)
|
|
{
|
|
if (!_grid.EndEdit())
|
|
{
|
|
_statusLabel.Text = "입력값을 확인해 주세요.";
|
|
return;
|
|
}
|
|
|
|
using (SaveFileDialog dialog = new SaveFileDialog())
|
|
{
|
|
dialog.Filter = "PNG 이미지 (*.png)|*.png";
|
|
dialog.FileName = "sector_heatmap_" + DateTime.Now.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture) + ".png";
|
|
dialog.Title = "업종별 증시 이미지 저장";
|
|
|
|
if (dialog.ShowDialog(this) != DialogResult.OK)
|
|
{
|
|
return;
|
|
}
|
|
|
|
using (Bitmap bitmap = HeatMapRenderer.Render(_items))
|
|
{
|
|
bitmap.Save(dialog.FileName, ImageFormat.Png);
|
|
}
|
|
|
|
_statusLabel.Text = "저장 완료: " + dialog.FileName;
|
|
}
|
|
}
|
|
|
|
private void Grid_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
|
{
|
|
RefreshPreview();
|
|
}
|
|
|
|
private void Grid_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
|
|
{
|
|
if (_grid.Columns[e.ColumnIndex].DataPropertyName == "Name")
|
|
{
|
|
return;
|
|
}
|
|
|
|
decimal value;
|
|
string text = Convert.ToString(e.Value, CultureInfo.CurrentCulture);
|
|
|
|
if (TryParseDecimal(text, out value))
|
|
{
|
|
e.Value = value;
|
|
e.ParsingApplied = true;
|
|
}
|
|
}
|
|
|
|
private void Grid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
|
|
{
|
|
if (_grid.Columns[e.ColumnIndex].DataPropertyName == "Name")
|
|
{
|
|
return;
|
|
}
|
|
|
|
decimal value;
|
|
string text = Convert.ToString(e.FormattedValue, CultureInfo.CurrentCulture);
|
|
|
|
if (!TryParseDecimal(text, out value))
|
|
{
|
|
e.Cancel = true;
|
|
_grid.Rows[e.RowIndex].ErrorText = "숫자를 입력하세요.";
|
|
return;
|
|
}
|
|
|
|
if (_grid.Columns[e.ColumnIndex].DataPropertyName == "MarketCap" && value <= 0m)
|
|
{
|
|
e.Cancel = true;
|
|
_grid.Rows[e.RowIndex].ErrorText = "시가총액은 0보다 커야 합니다.";
|
|
return;
|
|
}
|
|
|
|
_grid.Rows[e.RowIndex].ErrorText = string.Empty;
|
|
}
|
|
|
|
private void Grid_DataError(object sender, DataGridViewDataErrorEventArgs e)
|
|
{
|
|
e.ThrowException = false;
|
|
_statusLabel.Text = "입력값을 확인해 주세요.";
|
|
}
|
|
|
|
private static bool TryParseDecimal(string text, out decimal value)
|
|
{
|
|
value = 0m;
|
|
|
|
if (text == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string normalized = text.Trim().Replace("%", string.Empty);
|
|
return decimal.TryParse(normalized, NumberStyles.Number, CultureInfo.CurrentCulture, out value)
|
|
|| decimal.TryParse(normalized, NumberStyles.Number, CultureInfo.InvariantCulture, out value);
|
|
}
|
|
}
|
|
}
|