하아찡
WPF - 차트(2) 본문
현재 진행사항
- 현재 차트 전 데이터를 받아옴.
- 받아온 데이터를 앞에다가 추가함.
- 좌우 드래그방식으로 앞뒤 차트를 볼 수 있게함.
- 화면 크기가 변하면 차트 크기도 따라 변함.
(화면 하단 리스트박스는 차트 데이터 순서가 맞는지 확인하기위해 넣어둠 지울 예정)

XAML
<Grid x:Name="grid" Background="Transparent" ClipToBounds="True" SizeChanged="WpanelResize" >
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="100"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<StackPanel
PreviewMouseLeftButtonDown="PreviewDown" PreviewMouseLeftButtonUp="PreviewUp" MouseMove="MouseMove"
Orientation="Horizontal" x:Name="wPanel" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="1" Background="Black">
<Popup x:Name="popInfo"
IsOpen="False"
StaysOpen="False"
Placement="MousePoint">
<Border Width="100" Height="60" Background="Aqua"/>
</Popup>
</StackPanel>
<Button Grid.Row="1" Click="BtnDraw_Click" Grid.Column="1" Content="Button"/>
<Label x:Name="label1" Content="Label" Margin="0,10,-740,10" Grid.Column="1"/>
<Label x:Name="label2" Content="Label" Margin="1,197,-741,38" Grid.Column="1"/>
<ListBox x:Name="lb" Grid.Row="1"/>
</Grid>
현재 진행코드
AccessUpbit upbit = new AccessUpbit();
GridData griddata = new GridData();//현재 보이는 차트 최대값과 최소값을 저장함
int ShowBars = 200;//해당화면에 보여줄 막대그래프 수 이 변수를통해 추후에 휠을 돌릴시 확대하는기능을 추가할예정
double GridRight = 50.0f;//그리드 영역 오른쪽 공백크기
double GridBottom = 100.0f;//그리드 영역에 밑에 공백크기
double KeepWGrid;//창 크기 변하기전 그리드 넓이값
double KeepHGrid;//창 크기 변하기전 그리드 높이값
public string Coin { get; set; }//현재 그래프를 그릴 코인명
bool FirstSerch = true;//첫번째 서치일때만 역순으로 저장하기위해 설정해줌
int min = 60;//그래프를 불러올 분을 저장 ex)1,3,5,10,15,30,60,240
List<Rectangle> Ticks = new List<Rectangle>();//현재 표시된 몸통값을 저장함
List<Rectangle> Tails = new List<Rectangle>();//현재 표시된 꼬리값을 저장함
//타이머
DispatcherTimer timerResize;//창이 변할때 바로작동하지않고 딜레이를 주기위해 만듬
DispatcherTimer timerDrawDelay;//그리는 딜레이 계속 이상하게 그려져서 추가하긴했는데 추후에 삭제할예정
//현재 차트의 모든 데이터를 저장해둠
List<Structs.TickMin> keeptm;
//그래프 좌우를 움직이게 볼수있게해줌
private object movingObject;
private double firstXPos, firstYPos;
//해당 변수값이 True일때만 그래프 크기가 변경이됨
bool Resize = true;
public double rectSpace { get; set; }
public double rectWidth { get; set; }
//해당 컨트롤 기본색상 정해줌
private static readonly DependencyProperty panelBackcolor = DependencyProperty.Register("panelBackColor", typeof(Brush), typeof(Charts));
private static readonly DependencyProperty barUPColor = DependencyProperty.Register("barsUpColor", typeof(Brush), typeof(Charts));
private static readonly DependencyProperty barDownColor = DependencyProperty.Register("barsDownColor", typeof(Brush), typeof(Charts));
public Brush PanelBackColor
{
get { return (Brush)this.GetValue(panelBackcolor); }
set { this.SetValue(panelBackcolor, value); }
}
public Brush BarsUpColor
{
get { return (Brush)this.GetValue(barUPColor); }
set { this.SetValue(barUPColor, value); }
}
public Brush BarsDownColor
{
get { return (Brush)this.GetValue(barDownColor); }
set { this.SetValue(barDownColor, value); }
}
public Charts()
{
InitializeComponent();
//0.3초 그래프 사이즈 변경 딜레이
timerResize = new DispatcherTimer();
timerResize.Interval = TimeSpan.FromMilliseconds(300);
timerResize.Tick += new EventHandler(TimerResize_Tick);
timerResize.Stop();
//0.1초 그리는거 딜레이
timerDrawDelay = new DispatcherTimer();
timerDrawDelay.Interval = TimeSpan.FromMilliseconds(100);
timerDrawDelay.Tick += new EventHandler(TimerDrawDelay_Tick);
timerDrawDelay.Stop();
}
public void GetChart(string coin)
{
wPanel.Background = PanelBackColor;
//새로운 차트를 불러올때 다시 초기화해줌
//현재 선택된 코인이름을 가져옴
Coin = coin;
griddata = new GridData();
//현재 창에서 그리드 크기
KeepWGrid = grid.ActualWidth;
KeepHGrid = grid.ActualHeight;
//생성되어있던 틱과 꼬리들 초기화
Ticks = new List<Rectangle>();
Tails = new List<Rectangle>();
keeptm = new List<Structs.TickMin>();
//현재 패널에있는 아이들 삭제
wPanel.Children.Clear();
//다시처음부터 스캔
FirstSerch = true;
//좌우로 움직였을가능성이 있기에 맨앞으로 땡겨줌.
Thickness Margin = wPanel.Margin;
Margin.Left = 0;
wPanel.Margin = Margin;
DrawBars();
}
public void DrawBars()
{
string TimeStamp = "";
if (!FirstSerch)
{
DateTime dt = Convert.ToDateTime(keeptm[0].candle_date_time_kst);
DateTime.TryParse(keeptm[0].candle_date_time_kst, null, System.Globalization.DateTimeStyles.AssumeLocal, out dt);
//-30 * 18 ?
DateTime dt1 = dt.AddMinutes(-30 * 18);
TimeStamp = $",to:{dt1.ToString("yyyy-MM-dd")}T{dt1.ToString("HH")}%3A{dt1.ToString("mm")}%3A{dt1.ToString("ss")}Z";
}
List<Structs.TickMin> tm = upbit.TickMin(min, $"market:{Coin}{TimeStamp},count:{200}");
//해당 그리드에서 패널 사이즈를 구한후 그 패널에서 보여줄 ShowBars 를나눠서 틱당 할당할수있는 크기를 구해줌
double section = (grid.ActualWidth - GridRight) / ShowBars;
rectSpace = (section * 30) / 100;
rectWidth = (section * 70) / 100;
//데이터가 역순으로 들어와 정순으로 바꿔줌
//데이터를 불러올떄 제일 마지막부분이 최근데이터이며 최근데이터를 가장먼저 출력하기위해 뒤집어줌
if(FirstSerch)
{
tm.Reverse();
//데이터가 순서대로 잘들어오는지 확인하기위해 추가함
lb.Items.Add(tm[199].candle_date_time_kst);
lb.Items.Add(tm[0].candle_date_time_kst);
}
else
{
//데이터가 순서대로 잘들어오는지 확인하기위해 추가함
lb.Items.Add(tm[0].candle_date_time_kst);
lb.Items.Add(tm[199].candle_date_time_kst);
}
//현재 틱들 최대값과 최소값을 구함
foreach (var tick in tm)
{
griddata.SetMax(tick.high_price);
griddata.SetMin(tick.low_price);
if (!FirstSerch)
keeptm.Insert(0, tick);
else
keeptm.Add( tick);
//값 확인을위해 라벨을 추가하여 맥스값과 최소값을 확인하는중
label1.Content = griddata.Max;
label2.Content = griddata.Min;
}
foreach (var tick in tm)
{
//현재 그릴 높이값을 구함 (기존 그리드 크기 - 그리드 밑공백) * 현재 비율
double Height = (grid.ActualHeight - GridBottom);
//몸통
double tickheight = ( Math.Abs(tick.opening_price - tick.trade_price) / (griddata.Max - griddata.Min)) * Height;
double tickstartpoint = ((griddata.Max - (tick.opening_price >= tick.trade_price ? tick.opening_price : tick.trade_price))
/ (griddata.Max - griddata.Min)) * Height;
//꼬리
double tailheight = ((tick.high_price - tick.low_price) / (griddata.Max - griddata.Min)) * Height;
double tailstartpoint = ((griddata.Max - tick.high_price) / (griddata.Max - griddata.Min)) * Height;
Rectangle tail = new Rectangle();
tail.Width = 1;
tail.Height = tailheight;
tail.Margin = new Thickness(rectSpace - (rectWidth + 1), tailstartpoint, 0, 0);
tail.VerticalAlignment = VerticalAlignment.Top;
Rectangle rec = new Rectangle();
rec.Width = rectWidth;
rec.Height = tickheight ;
rec.Margin = new Thickness(rectSpace, tickstartpoint, 0, 0);
rec.VerticalAlignment = VerticalAlignment.Top;
//해당 틱 색상을 정해줌
if (tick.opening_price >= tick.trade_price)
{
rec.Fill = BarsDownColor;
tail.Fill = BarsDownColor;
}
else
{
rec.Fill = BarsUpColor;
tail.Fill = BarsUpColor;
}
if (FirstSerch)
{
wPanel.Children.Add(rec);
wPanel.Children.Add(tail);
Ticks.Add(rec);
Tails.Add(tail);
}
else
{
wPanel.Children.Insert(0, tail) ;
wPanel.Children.Insert(0,rec);
Ticks.Insert(0,rec);
Tails.Insert(0,tail);
}
}
if (FirstSerch)
{
FirstSerch = false;
}
else
{
//비효율적 연산이 들어가니 추후에 수정.
SetTickPositions();
}
//Resize = false;
//for()
}
private void PreviewDown(object sender, MouseButtonEventArgs e)
{
//마우스버튼을 눌렀을때 현재오브젝트를 저장하며 클릭한 좌표값을 저장한다.
firstXPos = e.GetPosition(wPanel).X;
firstYPos = e.GetPosition(wPanel).Y;
movingObject = sender;
}
private void PreviewUp(object sender, MouseButtonEventArgs e)
{
//마우스버튼을 때면 현재 오브젝트값을 없앰.
movingObject = null;
}
private void MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && sender == movingObject)
{
//감도 0.5값이 적당하다고봄.
double newLeft = (e.GetPosition(wPanel).X - firstXPos) * 0.5;
//double newTop = (e.GetPosition(wPanel).Y - firstYPos) * 0.3;
//Margin Left값을줘서 X축을 기준으로 이동하는것처럼 보이게함
Thickness newMargin = wPanel.Margin;
newMargin.Left += newLeft;
wPanel.Margin = newMargin;
//newMargin.Top += newTop;
firstXPos = e.GetPosition(wPanel).X;
//firstYPos = e.GetPosition(wPanel).Y;
}
}
private void WpanelResize(object sender, System.EventArgs e)
{
//원래는 패널자체 크기가 변경될때 작동하게했지만 children이 추가될때도 사이즈가 변경이되어 그리드로 변경을함
//label1.Content = wPanel.ActualWidth.ToString();
if (Resize)
{
Resize = false;
timerResize.Stop();
timerResize.Start();
}
}
private void TimerDrawDelay_Tick(object sender, EventArgs e)
{
Resize = true;
timerDrawDelay.Stop();
}
private void TimerResize_Tick(object sender, EventArgs e)
{
SetTickPositions();
Resize = true;
timerResize.Stop();
}
private void SetTickPositions()
{
if (Ticks.Count == 0)
return;
double section = (grid.ActualWidth - GridRight) / ShowBars;
rectSpace = (section * 30) / 100;
rectWidth = (section * 70) / 100;
foreach (var tick in Ticks.Select((value, index) => new { value, index }))
{
double Height = (grid.ActualHeight - GridBottom);
//몸통
double tickheight = (Math.Abs(keeptm[tick.index].opening_price - keeptm[tick.index].trade_price) / (griddata.Max - griddata.Min)) * Height;
double tickstartpoint = ((griddata.Max - (keeptm[tick.index].opening_price > keeptm[tick.index].trade_price ? keeptm[tick.index].opening_price : keeptm[tick.index].trade_price)) / (griddata.Max - griddata.Min)) * Height;
//꼬리
double tailheight = ((keeptm[tick.index].high_price - keeptm[tick.index].low_price) / (griddata.Max - griddata.Min)) * Height;
double tailstartpoint = ((griddata.Max - keeptm[tick.index].high_price) / (griddata.Max - griddata.Min)) * Height;
Thickness margin = tick.value.Margin;
margin.Top = tickstartpoint;
margin.Left = rectSpace;
tick.value.Height = tickheight;
tick.value.Width = rectWidth;
tick.value.Margin = margin;
margin = Tails[tick.index].Margin;
margin.Top = tailstartpoint;
margin.Left = rectSpace - ((tick.value.Width ) + 1) ;
Tails[tick.index].Margin = margin;
Tails[tick.index].Height = tailheight;
}
}
private void BtnDraw_Click(object sender, RoutedEventArgs e)
{
Resize = false;
DrawBars();
timerDrawDelay.Start();
}
추가해야할것들
- 휠 돌릴시 확대하는 기능.
- 보이는 화면에 맞게 MAX값과 MIN값을 수정해서 꽉차게 만들기
- 차트 데이터 로드를 버튼으로 눌러서 보는 방식말고 좌측올 땡겼을떄 데이터가 없을때 자동으로 로드게되게 만들예정.
(예상구도는 StackPanel Margin값으로 좌우측을 움직이는 방식임으로 좌측 Margin값이 0이아닌 양수가 되어버리면 데이터를 로드 시킨 후 로드시킨 만큼 마진값을 빼서 현재창을 유지시키게 만들예정)
- 각 분봉 버튼 추가.(1, 3, 5, 10, 15, 30, 60, 240)
반응형
'C# > 코인프로그램 - 진행상황' 카테고리의 다른 글
WPF - 차트(4) (1) | 2022.08.05 |
---|---|
WPF - 차트(3) (0) | 2022.08.04 |
WPF - 차트(1) (0) | 2022.08.03 |
WPF 공부시작 -차트 (0) | 2022.08.03 |
업비트 C# 프로그램 (0) | 2022.07.27 |