Initial sector heatmap generator
This commit is contained in:
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
.vs/
|
||||||
|
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
TestResult*/
|
||||||
|
*.log
|
||||||
25
mbn_stock.sln
Normal file
25
mbn_stock.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 16.0.35731.53
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mbn_stock", "mbn_stock\mbn_stock.csproj", "{C3638A86-3DDE-40D7-B896-A58DFC53E372}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{C3638A86-3DDE-40D7-B896-A58DFC53E372}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C3638A86-3DDE-40D7-B896-A58DFC53E372}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C3638A86-3DDE-40D7-B896-A58DFC53E372}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C3638A86-3DDE-40D7-B896-A58DFC53E372}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {C7AE5942-0C77-477D-A577-5D5787EDFC50}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
6
mbn_stock/App.config
Normal file
6
mbn_stock/App.config
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||||
|
</startup>
|
||||||
|
</configuration>
|
||||||
41
mbn_stock/Form1.Designer.cs
generated
Normal file
41
mbn_stock/Form1.Designer.cs
generated
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
namespace mbn_stock
|
||||||
|
{
|
||||||
|
partial class Form1
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 필수 디자이너 변수입니다.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 사용 중인 모든 리소스를 정리합니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">관리되는 리소스를 삭제해야 하면 true이고, 그렇지 않으면 false입니다.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form 디자이너에서 생성한 코드
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 디자이너 지원에 필요한 메서드입니다.
|
||||||
|
/// 이 메서드의 내용을 코드 편집기로 수정하지 마세요.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
this.components = new System.ComponentModel.Container();
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(1280, 760);
|
||||||
|
this.Text = "업종별 증시 이미지 생성기";
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
348
mbn_stock/Form1.cs
Normal file
348
mbn_stock/Form1.cs
Normal file
@@ -0,0 +1,348 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
332
mbn_stock/HeatMapRenderer.cs
Normal file
332
mbn_stock/HeatMapRenderer.cs
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.Drawing.Text;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace mbn_stock
|
||||||
|
{
|
||||||
|
public static class HeatMapRenderer
|
||||||
|
{
|
||||||
|
public const int ExportWidth = 1920;
|
||||||
|
public const int ExportHeight = 1080;
|
||||||
|
public const decimal MaxRatePercent = 30m;
|
||||||
|
|
||||||
|
private const float TileGap = 6f;
|
||||||
|
private static readonly float TileRadius = 0f;
|
||||||
|
|
||||||
|
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems)
|
||||||
|
{
|
||||||
|
return Render(sourceItems, new Size(ExportWidth, ExportHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bitmap Render(IEnumerable<SectorHeatMapItem> sourceItems, Size size)
|
||||||
|
{
|
||||||
|
Bitmap bitmap = new Bitmap(size.Width, size.Height, PixelFormat.Format32bppArgb);
|
||||||
|
|
||||||
|
using (Graphics graphics = Graphics.FromImage(bitmap))
|
||||||
|
{
|
||||||
|
graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
|
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
|
||||||
|
graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
|
||||||
|
graphics.Clear(Color.FromArgb(20, 22, 28));
|
||||||
|
|
||||||
|
List<SectorHeatMapItem> items = sourceItems
|
||||||
|
.Where(item => item != null && item.MarketCap > 0m)
|
||||||
|
.OrderByDescending(item => item.MarketCap)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
DrawEmptyMessage(graphics, new RectangleF(0, 0, size.Width, size.Height));
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
RectangleF canvas = new RectangleF(0, 0, size.Width, size.Height);
|
||||||
|
List<TreeMapNode> nodes = BuildNodes(items, canvas);
|
||||||
|
DrawTiles(graphics, nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<TreeMapNode> BuildNodes(IList<SectorHeatMapItem> items, RectangleF bounds)
|
||||||
|
{
|
||||||
|
decimal total = items.Sum(item => item.MarketCap);
|
||||||
|
double totalArea = bounds.Width * bounds.Height;
|
||||||
|
List<TreeMapNode> nodes = new List<TreeMapNode>();
|
||||||
|
|
||||||
|
foreach (SectorHeatMapItem item in items)
|
||||||
|
{
|
||||||
|
double area = decimal.ToDouble(item.MarketCap / total) * totalArea;
|
||||||
|
nodes.Add(new TreeMapNode(item, area));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<TreeMapNode> result = new List<TreeMapNode>();
|
||||||
|
LayoutBinary(nodes, bounds, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LayoutBinary(IList<TreeMapNode> nodes, RectangleF bounds, List<TreeMapNode> result)
|
||||||
|
{
|
||||||
|
if (nodes.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.Count == 1)
|
||||||
|
{
|
||||||
|
nodes[0].Bounds = bounds;
|
||||||
|
result.Add(nodes[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double totalArea = nodes.Sum(node => node.Area);
|
||||||
|
int splitIndex = FindBalancedSplit(nodes, totalArea);
|
||||||
|
List<TreeMapNode> firstGroup = nodes.Take(splitIndex).ToList();
|
||||||
|
List<TreeMapNode> secondGroup = nodes.Skip(splitIndex).ToList();
|
||||||
|
double firstArea = firstGroup.Sum(node => node.Area);
|
||||||
|
float firstRatio = (float)(firstArea / totalArea);
|
||||||
|
|
||||||
|
if (bounds.Width >= bounds.Height)
|
||||||
|
{
|
||||||
|
float firstWidth = bounds.Width * firstRatio;
|
||||||
|
RectangleF firstBounds = new RectangleF(bounds.X, bounds.Y, firstWidth, bounds.Height);
|
||||||
|
RectangleF secondBounds = new RectangleF(bounds.X + firstWidth, bounds.Y, bounds.Width - firstWidth, bounds.Height);
|
||||||
|
|
||||||
|
LayoutBinary(firstGroup, firstBounds, result);
|
||||||
|
LayoutBinary(secondGroup, secondBounds, result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float firstHeight = bounds.Height * firstRatio;
|
||||||
|
RectangleF topBounds = new RectangleF(bounds.X, bounds.Y, bounds.Width, firstHeight);
|
||||||
|
RectangleF bottomBounds = new RectangleF(bounds.X, bounds.Y + firstHeight, bounds.Width, bounds.Height - firstHeight);
|
||||||
|
|
||||||
|
LayoutBinary(firstGroup, topBounds, result);
|
||||||
|
LayoutBinary(secondGroup, bottomBounds, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int FindBalancedSplit(IList<TreeMapNode> nodes, double totalArea)
|
||||||
|
{
|
||||||
|
double target = totalArea / 2d;
|
||||||
|
double running = 0d;
|
||||||
|
double bestDifference = double.MaxValue;
|
||||||
|
int splitIndex = 1;
|
||||||
|
|
||||||
|
for (int index = 0; index < nodes.Count - 1; index++)
|
||||||
|
{
|
||||||
|
running += nodes[index].Area;
|
||||||
|
double difference = Math.Abs(target - running);
|
||||||
|
|
||||||
|
if (difference <= bestDifference)
|
||||||
|
{
|
||||||
|
bestDifference = difference;
|
||||||
|
splitIndex = index + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return splitIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawTiles(Graphics graphics, IList<TreeMapNode> nodes)
|
||||||
|
{
|
||||||
|
foreach (TreeMapNode node in nodes)
|
||||||
|
{
|
||||||
|
RectangleF tileBounds = Inset(node.Bounds, TileGap / 2f);
|
||||||
|
if (tileBounds.Width <= 1f || tileBounds.Height <= 1f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color fill = GetFillColor(node.Item.ChangeRate);
|
||||||
|
using (Brush brush = new SolidBrush(fill))
|
||||||
|
using (Pen borderPen = new Pen(Color.FromArgb(70, 255, 255, 255), 1.5f))
|
||||||
|
{
|
||||||
|
if (TileRadius <= 0f)
|
||||||
|
{
|
||||||
|
graphics.FillRectangle(brush, tileBounds);
|
||||||
|
graphics.DrawRectangle(borderPen, tileBounds.X, tileBounds.Y, tileBounds.Width, tileBounds.Height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
using (GraphicsPath path = CreateRoundedRectangle(tileBounds, TileRadius))
|
||||||
|
{
|
||||||
|
graphics.FillPath(brush, path);
|
||||||
|
graphics.DrawPath(borderPen, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawTileText(graphics, node.Item, tileBounds, 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));
|
||||||
|
if (textBounds.Width <= 8f || textBounds.Height <= 8f)
|
||||||
|
{
|
||||||
|
textBounds = Inset(tileBounds, 2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color textColor = GetReadableTextColor(fill);
|
||||||
|
string rateText = FormatRate(item.ChangeRate);
|
||||||
|
float fontSize = FindFittingFontSize(graphics, item.Name, rateText, textBounds);
|
||||||
|
float rateFontSize = Math.Max(8f, fontSize * 0.78f);
|
||||||
|
|
||||||
|
using (Font nameFont = new Font("Malgun Gothic", fontSize, FontStyle.Bold, GraphicsUnit.Pixel))
|
||||||
|
using (Font rateFont = new Font("Malgun Gothic", rateFontSize, FontStyle.Bold, GraphicsUnit.Pixel))
|
||||||
|
using (Brush textBrush = new SolidBrush(textColor))
|
||||||
|
using (StringFormat centerFormat = new StringFormat())
|
||||||
|
{
|
||||||
|
centerFormat.Alignment = StringAlignment.Center;
|
||||||
|
centerFormat.LineAlignment = StringAlignment.Center;
|
||||||
|
centerFormat.Trimming = StringTrimming.EllipsisCharacter;
|
||||||
|
centerFormat.FormatFlags = StringFormatFlags.NoWrap;
|
||||||
|
|
||||||
|
float nameHeight = nameFont.GetHeight(graphics);
|
||||||
|
float rateHeight = rateFont.GetHeight(graphics);
|
||||||
|
float totalHeight = nameHeight + rateHeight + Math.Max(2f, fontSize * 0.12f);
|
||||||
|
float top = textBounds.Y + (textBounds.Height - totalHeight) / 2f;
|
||||||
|
|
||||||
|
RectangleF nameRect = new RectangleF(textBounds.X, top, textBounds.Width, nameHeight + 4f);
|
||||||
|
RectangleF rateRect = new RectangleF(textBounds.X, top + nameHeight + Math.Max(2f, fontSize * 0.12f), textBounds.Width, rateHeight + 4f);
|
||||||
|
|
||||||
|
graphics.DrawString(item.Name, nameFont, textBrush, nameRect, centerFormat);
|
||||||
|
graphics.DrawString(rateText, rateFont, textBrush, rateRect, centerFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
for (float size = maxSize; size >= 8f; size -= 1f)
|
||||||
|
{
|
||||||
|
using (Font nameFont = new Font("Malgun Gothic", size, FontStyle.Bold, GraphicsUnit.Pixel))
|
||||||
|
using (Font rateFont = new Font("Malgun Gothic", Math.Max(8f, size * 0.78f), FontStyle.Bold, GraphicsUnit.Pixel))
|
||||||
|
{
|
||||||
|
SizeF nameSize = graphics.MeasureString(name, nameFont);
|
||||||
|
SizeF rateSize = graphics.MeasureString(rateText, rateFont);
|
||||||
|
float totalHeight = nameFont.GetHeight(graphics) + rateFont.GetHeight(graphics) + Math.Max(2f, size * 0.12f);
|
||||||
|
|
||||||
|
if (nameSize.Width <= bounds.Width * 0.96f &&
|
||||||
|
rateSize.Width <= bounds.Width * 0.96f &&
|
||||||
|
totalHeight <= bounds.Height * 0.92f)
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 8f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color GetFillColor(decimal changeRate)
|
||||||
|
{
|
||||||
|
double intensity = Math.Min(1d, Math.Abs(decimal.ToDouble(changeRate)) / decimal.ToDouble(MaxRatePercent));
|
||||||
|
|
||||||
|
if (changeRate > 0m)
|
||||||
|
{
|
||||||
|
return Interpolate(Color.FromArgb(255, 206, 206), Color.FromArgb(204, 0, 0), intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changeRate < 0m)
|
||||||
|
{
|
||||||
|
return Interpolate(Color.FromArgb(209, 225, 255), Color.FromArgb(0, 70, 190), intensity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Color.FromArgb(226, 228, 232);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color Interpolate(Color from, Color to, double amount)
|
||||||
|
{
|
||||||
|
amount = Math.Max(0d, Math.Min(1d, amount));
|
||||||
|
int r = (int)Math.Round(from.R + (to.R - from.R) * amount);
|
||||||
|
int g = (int)Math.Round(from.G + (to.G - from.G) * amount);
|
||||||
|
int b = (int)Math.Round(from.B + (to.B - from.B) * amount);
|
||||||
|
return Color.FromArgb(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color GetReadableTextColor(Color background)
|
||||||
|
{
|
||||||
|
double luminance = (0.299d * background.R) + (0.587d * background.G) + (0.114d * background.B);
|
||||||
|
return luminance < 145d ? Color.White : Color.FromArgb(24, 28, 36);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatRate(decimal value)
|
||||||
|
{
|
||||||
|
string format = value > 0m ? "+0.00'%'"
|
||||||
|
: value < 0m ? "0.00'%'"
|
||||||
|
: "0.00'%'";
|
||||||
|
|
||||||
|
return value.ToString(format, CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RectangleF Inset(RectangleF rectangle, float padding)
|
||||||
|
{
|
||||||
|
float horizontal = Math.Min(padding, rectangle.Width / 2f - 0.5f);
|
||||||
|
float vertical = Math.Min(padding, rectangle.Height / 2f - 0.5f);
|
||||||
|
|
||||||
|
if (horizontal < 0f)
|
||||||
|
{
|
||||||
|
horizontal = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vertical < 0f)
|
||||||
|
{
|
||||||
|
vertical = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RectangleF(
|
||||||
|
rectangle.X + horizontal,
|
||||||
|
rectangle.Y + vertical,
|
||||||
|
Math.Max(1f, rectangle.Width - horizontal * 2f),
|
||||||
|
Math.Max(1f, rectangle.Height - vertical * 2f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GraphicsPath CreateRoundedRectangle(RectangleF bounds, float radius)
|
||||||
|
{
|
||||||
|
float diameter = radius * 2f;
|
||||||
|
GraphicsPath path = new GraphicsPath();
|
||||||
|
path.AddArc(bounds.X, bounds.Y, diameter, diameter, 180, 90);
|
||||||
|
path.AddArc(bounds.Right - diameter, bounds.Y, diameter, diameter, 270, 90);
|
||||||
|
path.AddArc(bounds.Right - diameter, bounds.Bottom - diameter, diameter, diameter, 0, 90);
|
||||||
|
path.AddArc(bounds.X, bounds.Bottom - diameter, diameter, diameter, 90, 90);
|
||||||
|
path.CloseFigure();
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawEmptyMessage(Graphics graphics, RectangleF bounds)
|
||||||
|
{
|
||||||
|
using (Font font = new Font("Malgun Gothic", 40f, FontStyle.Bold, GraphicsUnit.Pixel))
|
||||||
|
using (Brush brush = new SolidBrush(Color.White))
|
||||||
|
using (StringFormat format = new StringFormat())
|
||||||
|
{
|
||||||
|
format.Alignment = StringAlignment.Center;
|
||||||
|
format.LineAlignment = StringAlignment.Center;
|
||||||
|
graphics.DrawString("표시할 데이터가 없습니다.", font, brush, bounds, format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TreeMapNode
|
||||||
|
{
|
||||||
|
public TreeMapNode(SectorHeatMapItem item, double area)
|
||||||
|
{
|
||||||
|
Item = item;
|
||||||
|
Area = area;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SectorHeatMapItem Item { get; private set; }
|
||||||
|
|
||||||
|
public double Area { get; private set; }
|
||||||
|
|
||||||
|
public RectangleF Bounds { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
mbn_stock/Program.cs
Normal file
22
mbn_stock/Program.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace mbn_stock
|
||||||
|
{
|
||||||
|
static class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 해당 애플리케이션의 주 진입점입니다.
|
||||||
|
/// </summary>
|
||||||
|
[STAThread]
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
Application.Run(new Form1());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
mbn_stock/Properties/AssemblyInfo.cs
Normal file
36
mbn_stock/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
|
||||||
|
// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
|
||||||
|
// 이러한 특성 값을 변경하세요.
|
||||||
|
[assembly: AssemblyTitle("mbn_stock")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("mbn_stock")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2026")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에
|
||||||
|
// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
|
||||||
|
// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
|
||||||
|
[assembly: Guid("c3638a86-3dde-40d7-b896-a58dfc53e372")]
|
||||||
|
|
||||||
|
// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
|
||||||
|
//
|
||||||
|
// 주 버전
|
||||||
|
// 부 버전
|
||||||
|
// 빌드 번호
|
||||||
|
// 수정 버전
|
||||||
|
//
|
||||||
|
// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를
|
||||||
|
// 기본값으로 할 수 있습니다.
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
70
mbn_stock/Properties/Resources.Designer.cs
generated
Normal file
70
mbn_stock/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// 이 코드는 도구를 사용하여 생성되었습니다.
|
||||||
|
// 런타임 버전:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// 파일 내용을 변경하면 잘못된 동작이 발생할 수 있으며, 코드를 다시 생성하면
|
||||||
|
// 이러한 변경 내용이 손실됩니다.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
namespace mbn_stock.Properties
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 지역화된 문자열 등을 찾기 위한 강력한 형식의 리소스 클래스입니다.
|
||||||
|
/// </summary>
|
||||||
|
// 이 클래스는 ResGen 또는 Visual Studio와 같은 도구를 통해 StronglyTypedResourceBuilder
|
||||||
|
// 클래스에서 자동으로 생성되었습니다.
|
||||||
|
// 멤버를 추가하거나 제거하려면 .ResX 파일을 편집한 다음 /str 옵션을 사용하여
|
||||||
|
// ResGen을 다시 실행하거나 VS 프로젝트를 다시 빌드하십시오.
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||||
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
internal class Resources
|
||||||
|
{
|
||||||
|
|
||||||
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
|
internal Resources()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 이 클래스에서 사용하는 캐시된 ResourceManager 인스턴스를 반환합니다.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Resources.ResourceManager ResourceManager
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if ((resourceMan == null))
|
||||||
|
{
|
||||||
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("mbn_stock.Properties.Resources", typeof(Resources).Assembly);
|
||||||
|
resourceMan = temp;
|
||||||
|
}
|
||||||
|
return resourceMan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 이 강력한 형식의 리소스 클래스를 사용하여 모든 리소스 조회에 대해 현재 스레드의 CurrentUICulture 속성을
|
||||||
|
/// 재정의합니다.
|
||||||
|
/// </summary>
|
||||||
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
|
internal static global::System.Globalization.CultureInfo Culture
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return resourceCulture;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
resourceCulture = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
117
mbn_stock/Properties/Resources.resx
Normal file
117
mbn_stock/Properties/Resources.resx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
||||||
29
mbn_stock/Properties/Settings.Designer.cs
generated
Normal file
29
mbn_stock/Properties/Settings.Designer.cs
generated
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// <auto-generated>
|
||||||
|
// This code was generated by a tool.
|
||||||
|
// Runtime Version:4.0.30319.42000
|
||||||
|
//
|
||||||
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
|
// the code is regenerated.
|
||||||
|
// </auto-generated>
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
namespace mbn_stock.Properties
|
||||||
|
{
|
||||||
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||||
|
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||||
|
{
|
||||||
|
|
||||||
|
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||||
|
|
||||||
|
public static Settings Default
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return defaultInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
mbn_stock/Properties/Settings.settings
Normal file
7
mbn_stock/Properties/Settings.settings
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||||
|
<Profiles>
|
||||||
|
<Profile Name="(Default)" />
|
||||||
|
</Profiles>
|
||||||
|
<Settings />
|
||||||
|
</SettingsFile>
|
||||||
67
mbn_stock/SectorHeatMapItem.cs
Normal file
67
mbn_stock/SectorHeatMapItem.cs
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace mbn_stock
|
||||||
|
{
|
||||||
|
public class SectorHeatMapItem : INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private string _name;
|
||||||
|
private decimal _marketCap;
|
||||||
|
private decimal _changeRate;
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return _name; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_name == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_name = value;
|
||||||
|
OnPropertyChanged("Name");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal MarketCap
|
||||||
|
{
|
||||||
|
get { return _marketCap; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_marketCap == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_marketCap = value;
|
||||||
|
OnPropertyChanged("MarketCap");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public decimal ChangeRate
|
||||||
|
{
|
||||||
|
get { return _changeRate; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_changeRate == value)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_changeRate = value;
|
||||||
|
OnPropertyChanged("ChangeRate");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPropertyChanged(string propertyName)
|
||||||
|
{
|
||||||
|
PropertyChangedEventHandler handler = PropertyChanged;
|
||||||
|
if (handler != null)
|
||||||
|
{
|
||||||
|
handler(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
mbn_stock/mbn_stock.csproj
Normal file
82
mbn_stock/mbn_stock.csproj
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{C3638A86-3DDE-40D7-B896-A58DFC53E372}</ProjectGuid>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<RootNamespace>mbn_stock</RootNamespace>
|
||||||
|
<AssemblyName>mbn_stock</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Deployment" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Form1.cs">
|
||||||
|
<SubType>Form</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="Form1.Designer.cs">
|
||||||
|
<DependentUpon>Form1.cs</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="HeatMapRenderer.cs" />
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="SectorHeatMapItem.cs" />
|
||||||
|
<EmbeddedResource Include="Properties\Resources.resx">
|
||||||
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<Compile Include="Properties\Resources.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Resources.resx</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<None Include="Properties\Settings.settings">
|
||||||
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
|
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<Compile Include="Properties\Settings.Designer.cs">
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Settings.settings</DependentUpon>
|
||||||
|
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="App.config" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user