하아찡

WPF - 차트(2) 본문

C#/코인프로그램 - 진행상황

WPF - 차트(2)

하아찡 2022. 8. 4. 18:37

현재 진행사항

- 현재 차트 전 데이터를 받아옴.

- 받아온 데이터를 앞에다가 추가함.

- 좌우 드래그방식으로 앞뒤 차트를 볼 수 있게함.

- 화면 크기가 변하면 차트 크기도 따라 변함.

(화면 하단 리스트박스는 차트 데이터 순서가 맞는지 확인하기위해 넣어둠 지울 예정)

 

 

 

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)  (0) 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