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