2022년 5월 28일 토요일

주식 프로그램 개발 (5)

 Data 분석을 통해 대략적인 흐름을 파악하였다면, 해당 조건을 저장한 후 기간을 정하여 

Back Testing 을 수행해 볼 수 있습니다. 물론 지난 데이터를 기준으로 구성하는 것이기 때문에

미래는 어떻게 반응할 지 모르지만, 테스팅을 통해 예측에 작은 영역이라도 도움이 되지 않을

까 합니다.   화면은 아래와 같습니다. 


조건 항목에서 수행할 기간, 종목 분산 수량, 목표판매기준, 손절기준, 최대보유일수, 조건이 

동일할 때 어떤 순서로 선택할지 등을 지정 합니다. 거래비용은 임의로 줄 수 있지만, 테스트

단계에서는 거래세금으로 0.0015 만 비용으로 제한합니다. 


위 화면의 조건은 예제로 정말 간단하게 5일 기울기가 0 미만 이었다가, 0 이상으로 올라오는

종목중 PBR 이 2 미만인 종목을 가져오도록 구성해 보았습니다. ... 물론 이런 간단한 방식으

로는 하지 않지만, 간단한 예로 구성해 보았습니다. ( 기준일 대비 5일 전에 5일 기울기가 

-3 보다 적고 4일째도 0보다 적은 후 2일 전에 0보다 크고 결정이 필요한 전일에 0보다 큰 

종목을 가져와서, 해당 종목이 많으면 20일 기울기가 높은 순으로 정렬 후 구매할 수 있는

범위까지 구매하는 구성 입니다. 

결과는 간단한 Chart 로 구성해 보았습니다.   하루 단위 보유 종목 등의 정보는 일단 출력

하지 않습니다. 


다행히 해당 기간에는 손실을 보지 않는 유형의 Chart 가 표현되고 있습니다. 

해당 기간에 대해서 구체적인 내용은 Grid 를 통해 확인 할 수 있습니다. 


위 조건으로 20211130일에는 일정실업을 사서 손절매 했다는 표현이고, 20211201 에는

위메이드플레이를 구매해서 목표가로 판매하였다는 내용등을 표기 합니다. 

대충 구매일, 판매일, 구매단가, 판매단가, 보유일수, 이익, 세금 등을 표기합니다. 

일자가 최근으로 가면 해당 조건에 맞는 종목을 확인할 수 있기 때문에 실제 Trading 에서

종목을 발굴하는데 조금은 도움이 되지 않을 까 합니다. 


위 내용을 기간을 늘려서 동일 조건으로 진행해 보면 아래와 같습니다. 


2021년 4월에서 2022년 5월 말(현재) 까지 하락기에 위의 단순한 패턴으로 구성하였을 때

이익도 손해도 별로 없는 그런 유형을 보여주고 있습니다. 

grid 에서 종목을 클릭하면 해당 종목의 Chart 를 확인할 수 있습니다. 

향후 여력이 된다면, 종목에 대한 Dart 의 공시 정보 등을 조회하여 심층적인 분석이 가능

하도록 구성할 수도 있을 것 같습니다. 




비슷하게 구성이 되겠지만, 이동평균과, 20일 추세 등을 보조 하였을 경우 아래와 같은

내용을 볼 수 있습니다. ( 조건에 따라 상승하기도 하강하기도 하기 때문에 기간과 상황

은 개별적으로 확인 할 수 밖에 없을 것 같습니다. )


2021. 11 ~ 2022. 05월 까지의 Back Testing 

그 아래는 2021. 04 ~ 2022. 05 까지의 동일 조건 Back Testing 입니다. 





Quant 구성은 확률 적으로 조금 더 높은 방향으로 패턴을 구성하고 해당 패턴을 테스트 하여

상승시점과 하강시점에도 유지될 수 있는 자기만의 패턴을 구성하는게 중요한 것 같습니다. 

사실 주식을 잘 모르고 단지 기본적인 통계 수치를 이용하여 구성해 본 것이라 많은 오류가 

프로그램에서도, 이해한 부분에서도 있을 것 같습니다.  

이렇게 접근하는 방법도 있을 것 같아서 기록해 보았습니다. 

향후 여력이 되면 해당 모듈을 발전시키고, 종목별 trend 를 AI 등을 통해 학습해 보는 것도 

재미 있을 것 같습니다.  


주식 프로그램 개발(4)

하루 단위 주식 데이터를 수집하고, 해당 데이터를 메모리에 적재하는 과정까지 진행이 되었다면 그 데이터를 활용하여 분석해 보는 작업을 진행해 볼 수 있을 것 같습니다. 

특정한 조건을 기술 하고, 기록된 내용을 기준으로 일자별 주식 종목을 표현한 후 해당 종목을 

클릭하면 상세한 정보를 Chart 로 표기해 보았습니다.  다음은 그것을 위한 간단한 조건을 

구성하는 화면입니다. 


Analysis > Data Analysis > Conditons 라는 항목에서 구성할 수 있습니다. 

사용자는 조건을 저장할 수도 저장된 조건을 불러올 수도 있도록 구성해 보았습니다. 

조건을 기술 하는 방법은 현재까지는 Text 인데 UI 로 변경이 가능하도록 할 수도 있을 

것 같습니다.   

조건은 DM1:: 로 시작합니다. Day Minus 1 이라는 의미기 때문에 기준일 대비 1일전 입니다.

예를 들어 5월 26일 기준이면 5월 25일이고 25일 기준이면 24일이 해당되는 날짜 입니다.

그 다음에 기술될 내용도 마찬가지로 DM1:CLOSE_PRICE < DM2:CLOSE_PRICE 등의 내용을

기재할 수 있습니다. 위의 의미는 그제 종가 보다 어제 종가가 더 적은 조건을 만족하는 항목

이라는 의미로 이야기 할 수 있습니다.  

이미 3일, 5일, 10일, 20일, 30일, 60일, 120일 이동평균, 표준편차, 선형회귀의 기울기, 거래량

등을 구해 놓았기 때문에 해당 항목을 찾아 올 수 있습니다. 

간단히 오늘 종가가 10일전 종가에 비해 1.3 배 이상 오른 종목을 검색해 보고자 하면

DM0::DM0:CLOSE_PRICE >= (DM10:CLOSE_PRICE *1.3) 이라고 기록후 검색해 볼 수 

있습니다. 



그 조건으로 검색해 보니 현재 메모리에 적재된 대상을 기준으로 20211119 ~ 20220527 

기간동안 3535 건이 출력되고 있습니다.  너무 많은 것 같아서 하루단위로 최대 5개만 출력

하고, 출력의 기준은 20일 기준 회귀 기울기가 높은 기준으로 출력해 보니, 3535건중 615 

건만 출력 하고 있습니다. 




그 항목중 마이더스AI 종목을 클릭해 보면 아래와 같은 Chart 내용을 확인해 볼 수 있습니다.




종목과 직결되는 항목은 아니지만, 코스닥과 코스피 지수를 조회 할 수 있습니다. 

종목을 분석하는데 많이 필요하지는 않지만, 없으면 허전한 것 같아서 간단히 조회가 가능한

화면을 구성해 보았습니다. 




데이터 분석화면에서 개인적으로 사용 할 수 있는 변수를 구성해 볼 수 있습니다. 

어떤 조건을 구성한 후 해당 조건을 계산해 놓고, 변수로 지정한 다음 그 변수를 이용하여

새로운 조건식을 구성할 수도 있을 것 같습니다. 

조금 번거로우 듯 해서 해당 모듈은 다음에 시간이 될 때 구성하고자 합니다. 


이제 어느 정도 분석이 되면, 해당 분석 내용을 기준으로 Back Testing 을 진행해 볼 수 있을 것 같습니다. 

차후 시간이 되면 구성해 보고자 합니다. 

 


 

2022년 5월 24일 화요일

주식 프로그램 개발 (3)

일 별 주식 데이터를 수집하는 단계에서는 가져온 데이터가 정상적인지, 그리고 그 데이터를 

기반으로 계산한 값이 문제가 없는 지를 확인하는 것이 첫 번째 단계 입니다.

그렇게 데이터가 수집 되고, 저장 되어 있다면 해당 데이터를 이용하여 분석 혹은 가상 트레이

딩을 하기 위해 데이터를 활용 하여야 합니다. 

이때 약간의 문제가 발생합니다.   

그 문제는 일반적인 데이터는 Database 의 Group 함수 혹은 분석 함수를 기준으로 로딩하는 

것이 어렵지 않으나, 주식 데이터는 하루 단위내의 비교와 같은 종목이라도 일자를 달리하는 

비교를 통해 데이터의 특징을 들어 내기가 더 좋은 것에 있습니다. 

예를 들어 오늘 기준의 종가가 20일전의 종가보다 1.2 배 정도 큰 종목만 확인하고 싶거나, 

할 때는 단순히 SQL 의 Query 로는 분석의 한계가 있습니다. 

그래서 데이터를 메모리에 올려 놓은 후 그 데이터를 활용하여 비교가 가능하도록 구성하는 

것이 필요할 것 같습니다.

그 첫 번째 단계가 특정 기간이 데이터를 메모리에 로딩해 놓는 작업입니다. 


Analysis > Basic Analysis > Load Data 는 기간을 정해서 데이터를 메모리에 올리는 작업을

진행합니다. 

이때 각 일자별로 간단한 통계를 만들어 보여 줍니다. 상승종목수, 하강종목수, 정배열, 역배열, 볼린져 상단돌파 등 일자별 통계를 만들고 해당 항목을 클릭하면 그에 해당하는 종목를 출력합니다. 


클릭했던 일자에 해당하는 종목에 대한 기본 정보를 출력 합니다. 

Trend Chart Tab 에서는 간단하게 상승 하강 주식 종목 숫자 및 20일 돌파, 볼린져 상단

돌파 등의 항목을 간단한 Chart 로 출력하고 있습니다. 

참고로 개발을 위해 사용한 Chart 는 NHN 의 toast chart 와 또 다른 Open source Chart

인 Plotly.js 를 사용하고 있습니다.  위 그리드는 NHN 의 toast grid 입니다. 


앞서 특정 종목의 내용이 궁금할 경우 해당 종목를 클릭하면 아래와 같은 Chart 를 확인할 

수 있습니다. 

클릭한 종목의 추이를 간단한 Chart 를 통해 확인할 수 있습니다. 

정리하자면, 데이터 메모리 적재 ( 이 시점에 간단한 통계 ), 해당 통계에 해당하는 일자별

데이터 조회, 해당 일자 종목에 대한 상세 조회 를 진행 할 수 있습니다. 

각 Tab 은 같은 페이지에 위치해 있기 때문에 화면은 전체 전환하기 전까지는 선택한 

내용을 탭 이동을 통해 확인할 수 있습니다. 

조금 더 자세한 사항은 데이터 분석 항목에서 확인 할 수 있습니다. 








 

2022년 5월 23일 월요일

주식 프로그램 개발(2)

주식 관련 예측을 위한 변수는 잘 알지는 못해도 상당히 많이 존재하는 것 같습니다.   

각각의 변수는 특징을 추출하기 위한 여러가지 경험과 방법을 제시하는 것 같습니다.   증권사 API 를 연계하여 해당 지표에 해당하는 값과 그에 따른 판단을 할 수 있도록 조건식 등의 항목으로 많이 제공하는 것 같습니다. 

개인 PC 에 포함될 수 있는 내용은 그런 모든 변수를 담기에는 자원이 그렇게 허락하지는 않는것 같습니다.   일단 앞서 글에서 언급한 데이터를 수집하는 것은 일 데이터의 시가, 고가, 저가, 종가 와 그 에 해당하는 조정 주가를 수집하는 것으로 부터 시작 하려고 합니다. 





Crawling 이라는 메뉴는 4개의  Tab 으로 구성되어 있습니다. 

1. Day Crawling 에서는 Crawling Data 조회 , 최근 일자 기준으로 최종 데이터 수집 이후의

  데이터를 수집, 지나간 기간에 필요한 하루데이터 단위로 기간을 설정하여 수집하는 기능입

  니다.  특히 Auto Crawling 에서는 하루단위 시,고,저,종 가 및 거래량, 해당 일자의 per, pbr, 

  코스닥, 코스피의 변동을 수집합니다. 

  Period Crawling 은 특정일자 - 예를 들어 20100101 ~ 20111231 년도 사이의 데이터를 

  수집하고자 할 때 사용합니다. 처음 데이터가 구성되면 조정된 주가는 종목별로 NHN 에서 

   수집할 수 있으나, 하루 단위의 real 데이터는 KRX site 에서 가져오기 때문에 그것을 구분

   하여 데이터를 수집하고 있습니다.    당연히 해당 데이터는 변경될 수 있으며, 조금 정교

   하게 프로그램을 구성한다면, 여러 원천에서 데이터를 가져오고, 그 데이터에서 필수 적인

   부분과 비 필수적인 부분을 나누면 더 좋으나, 현재는 실험용 데이터라 그런 구분 까지는 

   진행하지 않았습니다. 

2.  Day Stocks 에서는 특정 일자의 로딩된 데이터를 확인합니다. 

    

   이미지가 약간 작아 식별하기 어려울지 모르겠습니다.   20일 기울기 등을 sorting 하면

   데이터가 없는 종목을 식별할 수 있습니다.   이제 막 상장되어서 없는 것인지, 아니면, 

   계산이 안되어 없는 것인지 등을 확인 할 수 있습니다.  더구나,  수치가 연속적으로 

   나오지 않고 크게 튀거나 하면 데이터 계산이 다시 필요할 수도 있습니다.   그것을 

   식별하기 위해서 사용하는 tab 입니다.   이상하다고 생각하는 항목을 Grid 에서 클릭

   하면 다음의 Calculation Tab 으로 이동 합니다. 

3. Calculation Tab 에서는 특정 종목의 일자별 데이터를 확인합니다. 


    앞서의 항목에서 DL이앤씨2우.. 항목을 클릭하여 나타난 화면입니다. 

   데이터가 2022. 04. 28 일부터 시작되기 때문에 20일 평균등의 데이터가 계산되지 못한

   것을 확인할 수 있습니다.   혹시 문제가 있다면 Calculation Button 을 클릭하면 현재

   일자에서 과거 데이터를  조정주가 기준으로 찾아와 재 계산을 시도 합니다. 

4. Company 는 기본적인 회사 정보를 가져오기 위한 화면입니다. 

   사실 이 화면에서 DART 를 연동하면 해당 회사의 공시 정보 및 재무 등 상세 정보를 

   가져 올 수도 있으나, 일단 이곳에서는 간단한 회사 정보만을 출력하고 있습니다. 


5. 초기 사이트를 구성할 때는 아무런 데이터가 없기 때문에 회사에 대한 정보, 조정주가에 

  대한 정보 등을 가져올 필요가 있습니다.  그외 앞서 언급한 회사에 대한 정보 업데이트 및 

  공시정보등을 가져오는 로직을 구성할 수 있습니다.  필요한 부분은 많이 있지만, 가장 

  기본적인 정보 로딩 검증을 기준으로 구성해 보았습니다.   


 


2022년 5월 13일 금요일

주식 프로그램 개발 (1)

주식을 접하는 방법은 그 사람이 처한 환경과 조건에 따라 달리 바라보게 되는 것 같습니다. 

경제 분석을 기반으로 주식 시장을 바라 보는 분들도 있고, 오랜 기간 주식 시장에 있어서 

감각적으로 뛰어난 분들도 많은 것 같습니다.

주식이나, 경제에 대해 아는 것이 별로 없는 프로그램 개발자의 관점에서는 평균, 이동평균, 

표준편차 등으로 표기되는 Chart나 시가, 고가, 저가, 종가, 거래량 등의 데이터로 미래의 주가 예측이 가능한 지 의문이 들었습니다. 

현재 많이 회자되는 AI를 활용한 방법도 그런 예측을 높이고자 하는 방법이겠지만, 개인용 PC 정도의 사양에서 많은 데이터를 학습하는 것이 그리 간단한 일이 아닐 뿐더러, 그 방법이 최적의 해를 구할 수 있는지도 확신할 수 없는 것 같습니다. 

100% 예측이 가능한 방법이 있다면 좋겠지만, 아마도 사람이 심리가 녹아 있고, 변수가 참여한 

사람 수 만큼 많은, 어쩌면 더 많은 조건이 있기 때문에 쉬운 예측은 가능하지 않을 것 같습니다. 

다만, 간단한 통계 데이터로 수치화 된 값이 얼마나 예측의 질을 높일 수 있는지, 아주 조금이라도 예측에 도움이 되는 방법을 찾아보고자 프로그램을 구성해 보고자 합니다. 

1차로 개발하는 프로그램은 다음 정도의 제한을 두고자 합니다.

환경적인 요소는 시스템은 개인 PC 급에서 구동이 가능한 정도로 데이터를 제한 합니다. 

10년에서 20년 정도의 하루 데이터 정도(시가,고가,저가,종가등) 가 사용할 수 있는 한도가 아닐까 합니다.

프로그램은 크게 데이터 수집, 데이터 분석, 가상 트레이딩 의 3가지 큰 항목으로 구성할 

예정 입니다.  ( 아직 구성이 완료되지는 않았지만, 아래의 구성 입니다. )


데이터를 수집하기 위해서는 유료로 데이터를 구입하거나, 증권사 API 를 통해 구하거나, 사이트 데이터를 직접 찾아서 가져오거나, 그 데이터를 자동 Crawling 하는 방법이 있을 것 같습니다. 

가공하기 위한 데이터는 조정된 주가와, 하루 단위의 주식 거래 데이터를 가져 올 수 있어야 합니다.

조정된 주가는  Naver 의 finance.naver.com 의 chart data 를 통해 가져 올 수 있습니다. 

주의해 볼 부분은 예전에 어떤 가격 이었던, 현재 가격을 기준으로 조정해서 가격을 

표기해 주고 있는 부분입니다. 예를 들어 100만원 하던 주식이 액면분할로 5만원이 되면

이전 주식 금액도 현재에 맞춰서 조정한 가격을 표기하는 부분입니다.  이렇게 같은 잣대로

보아야 분석이 용이 할 수 있습니다.   

가격이 100만원인 주식과 만원인 주식을 비교해 더 오를 주식을 찾으려면 가급적 유사한

기준으로 판단할 수 있는 기준이 있어야 합니다.  특히 눈으로 보는 것이 아닌 수치로 해석

하기 위해서는 그런 기준이 있어야 하는데 앞선 글에 언급한 선형회귀의 1차 추세를 활용해

보겠습니다.

기초 데이터는 한국증권거래소의 데이터를 활용합니다. 

증권거래소의 주소는 http://www.krx.co.kr/main/main.jsp 이고, 정보데이터 시스템이 접속하

면 하루단위의 가격, 지수, PBR 자료등을 excel, 혹은 csv 로 다운 받을 수 있습니다. 

아래는 그중 하나를 받는 화면 입니다. 


위의 내용을 다운 받아 데이터를 분해 후 Database 등에 저장하면 해당 데이터를 분석할 수 

있는 원본 데이터를 구성할 수 있습니다. 

수동으로도 할 수 있고, 자동 crawling 할 수 있지만, 해당 데이터는 제공하는 사이트의 상황

에 따라 언제든 사라지거나 변경될 수 있습니다.  테스트 용도의 데이터는 이곳에서 수집하는

데이터로 구성합니다. 

참고로 위 사이트의 파일은 euc-kr (ms949) 의 유니코드 방식을 따릅니다. utf-8 의 방식으로

는 데이터가 정상적으로 파싱 되지 않습니다. ( 현재 개발 트랜드는 utf-8 입니다. )


위 사이트로 부터 원본 데이터를 가져오면, 데이터를 1차 가공 합니다. 

가공의 기준은 이동평균, 표준편차, 1차선형회귀, min, max, median 등의 가장 단순한 통계

입니다.  특히 거래량은 제한 폭이 없기 때문에 중앙값과, min, max 의 값이 중요할 것 같습니다.

5일간 거래량이 100, 101, 100000, 99, 100 일때 평균은 100000 이라는 값에 의해 왜곡이 

일어납니다. 이때 중앙값과, 당일 거래량을 비율로 수치화 하면 outlier 에 해당하는 날과

그 날이 포함된 영역 , 그 기간 이후의 추이 등을 수치화된 값으로 추적해 볼 수 있지 않을까

생각하고 있습니다. 

3일, 5일, 10일, 20일, 30일, 60일, 120일 정도의 데이터는 원본 데이터를 기준으로 먼저

계산을 해 놓습니다.  원본 데이터 수집, 기초 데이터 계산, 계산 데이터 확인 및 재 계산 등을

crawling 항목에서 관리하는 것이 어떨까 합니다  



아직 화면 구성이 안되어 있지만, 위의 Crawling Tab 에서 해당 기능을 수행하도록 구성해 보려 합니다.

현재 화면은 수동으로 구성된 데이터를 로딩하여 각 날짜별로 기초 통계를 보여주는 화면입니다. 

종목수가 조금 다른 이유는 시가총액등 제한을 두고 종목을 로딩하였기에 다르게 나타나고 

있습니다.  준비되면 다른 글에서 해당 내용을 기재하겠습니다. 













2022년 4월 14일 목요일

[기록용] Html Canvas 를 활용한 시계 만들기 (2)

 canvas 그리기 기능을 이용해서 translate 와 rotation을 구현하는 예제로 구성해 보았습니다.

arc 와 line 을 구성하는 것으로 거의 대부분 구성이 완료되지만, 

초침의 화살표는 각도에 따라 화살표 끝이 rotation 되어야 하기 때문에 화살표 그리는 부분

과, 라인을 그리는 부분을 분리하여 구성해 보았습니다. 


구성한 방법은 초, 분, 시침의 rotation 각도를 가져올 때 시작하는 0도가 시침의 3시 15분 15초가 기준이 되기 때문에 0도에서 시작하기 위해 각 단위 별로 시작 값을 옮겨주는 방식을 

사용하였습니다.  

시침, 분침, 초침의 각도는 초침은 단독으로 구성할 수 있으나, 분침은 초침이 흐른만큼 

더 움직여야 하고, 시침은 분침 초침이 움직인 만큼 더 움직여야 하기 때문에 움직임을 

그에 따라 계산해 주고 있습니다. 

대략 시침을 기준으로 30도 움직임을 가질 수 있고, 분은 60분을 기준으로 30도 움직일 수 

있고, 초는 60초를 기준으로 0.5도를 움직일 수 있습니다. 해당 부분을 계산에 포함 시켰습니다.


아래는 구성하기 위한 함수 들 입니다. 


function makeClocks(ctx, cw, ch, radious) {

let cx = cw/2;

let cy = ch/2;


ctx.clearRect(0,0,cw,ch);

var innerLineWidth = 12;

var outerLineWidth = 4;

var sOptions = getDefaultOptions();

sOptions.strokeStyle = "#008080";

sOptions.lineWidth = innerLineWidth;

drawClockLayers(ctx, cx, cy, radious, sOptions);


sOptions.lineWidth = outerLineWidth;

sOptions.strokeStyle = "#000080";

drawClockLayers(ctx, cx, cy, (radious+innerLineWidth/2+outerLineWidth+1), sOptions);


var markRadious = radious-innerLineWidth/2;

var txtRadious = markRadious-8;


drawClockTextArea(ctx, cx,cy, txtRadious, markRadious, innerLineWidth);


var now = new Date();

sOptions.strokeStyle = "#008080";

sOptions.lineCap = 'butt';

sOptions.joinCap = 'butt';

sOptions.lineWidth = 2;

drawClockSecond(ctx, cx, cy, txtRadious-4, now, sOptions);

sOptions.strokeStyle = "#000080";

sOptions.lineWidth = 4;

drawClockMinute(ctx, cx, cy, txtRadious-8, now, sOptions);

sOptions.strokeStyle = "#000080";

sOptions.lineWidth = 8;

drawClockHour(ctx, cx, cy, txtRadious-12, now, sOptions);

}


function drawClockLayers(ctx,cx,cy,radious, sOptions ) {

ctx.save();

ctx.beginPath();

setCurrentOptions(ctx, sOptions);

ctx.arc(cx,cy,radious,0, Math.PI*2);

ctx.stroke();

ctx.closePath();

ctx.restore();

}


function drawClockTextArea(ctx, cx, cy, txtRadious, markRadious, innerLineWidth ) {

ctx.save();

ctx.beginPath();

ctx.font = "12px consolas";

ctx.textAlign = "center";

ctx.textBaseline = "middle";

ctx.lineCap = "butt";

ctx.lineWidth = 1;

ctx.beginPath();

for ( let i = 0 ; i < 12 ; i++ ) {

ctx.lineWidth = 1;

ctx.strokeStyle = "#000080";

let rd = i*Math.PI/6;

let tx = txtRadious*Math.cos(rd)+cx;

let ty = txtRadious*Math.sin(rd)+cy;

let numStr = ((3+i)%12);

if ( numStr == 0 ) {

numStr = 12;

}

ctx.strokeText(numStr, tx,ty);


ctx.lineWidth = 2;

ctx.strokeStyle = "#00FFFF";


let tsx = (markRadious)*Math.cos(rd)+cx;

let tex = (markRadious+innerLineWidth)*Math.cos(rd)+cx;


let tsy = (markRadious)*Math.sin(rd)+cy;

let tey = (markRadious+innerLineWidth)*Math.sin(rd)+cy;

ctx.moveTo(tsx,tsy);

ctx.lineTo(tex,tey);

ctx.stroke();

}

ctx.closePath();

ctx.restore();

}


function drawArrowPoints(ctx, tx, ty, radians,pw,ph,sOptions ) {

ctx.save();

setCurrentOptions(ctx, sOptions);

ctx.beginPath();

ctx.translate(tx,ty);

ctx.rotate(radians);

ctx.moveTo(ph/2,0);

ctx.lineTo(-ph/2,-pw/2);

ctx.lineTo(-ph/2,pw/2);

ctx.closePath();

ctx.fill();

ctx.stroke();

ctx.restore();

}



function drawClockSecond(ctx, cx, cy, radious ,now, sOptions) {

ctx.save();

setCurrentOptions(ctx, sOptions);

let s = now.getSeconds();

let rd = ((s-15)*Math.PI*6)/180;

let tx = Math.cos(rd)*radious+cx

let ty = Math.sin(rd)*radious+cy;


ctx.beginPath();

ctx.moveTo(cx,cy);

ctx.lineTo(tx,ty);

ctx.stroke();

ctx.closePath();


if ( !sOptions ) {

sOptions = getDefaultOptions();

}

sOptions.strokeStyle = "blue";

sOptions.fillStyle = "#008080";

sOptions.lineWidth = 1;

sOptions.lineCap = 'butt';

sOptions.joinCap = 'butt';

drawArrowPoints(ctx, tx,ty, rd, 6,12, sOptions);

ctx.restore();

}


function drawClockMinute(ctx, cx, cy, radious , now, sOptions) {

ctx.save();


setCurrentOptions(ctx, sOptions);

let s = now.getSeconds();


let m = now.getMinutes();

let rd = ((m-15)*6+s/10)*Math.PI/180;

let tx = Math.cos(rd)*radious+cx

let ty = Math.sin(rd)*radious+cy;



ctx.beginPath();

ctx.moveTo(cx,cy);

ctx.lineTo(tx,ty);

ctx.stroke();

ctx.closePath();


ctx.restore();

}


function drawClockHour(ctx, cx, cy, radious , now, sOptions) {

ctx.save();


setCurrentOptions(ctx, sOptions);

let s = now.getSeconds();


let m = now.getMinutes();

let h = now.getHours();

let rd = (((h-3))*30+m/2+s/120)*Math.PI/180;

let tx = Math.cos(rd)*radious+cx

let ty = Math.sin(rd)*radious+cy;



ctx.beginPath();

ctx.moveTo(cx,cy);

ctx.lineTo(tx,ty);

ctx.stroke();

ctx.closePath();


ctx.lineWidth = 2;

ctx.fillStyle = "#00FFFF";

ctx.strokeStyle = "#0000FF";


ctx.beginPath();

ctx.arc(cx,cy,6,0,Math.PI*2);

ctx.fill();


ctx.closePath();


ctx.beginPath();


ctx.arc(cx,cy,6,0,Math.PI*2);

ctx.stroke();

ctx.closePath();


ctx.restore();

}



아래는 해당 소스의 실행 결과 입니다. 













2022년 4월 12일 화요일

[기록용] html canvas 를 활용한 간단한 그리기 (1)

 html 5 에서 제공하는 canvas 는 일반 application 에서 구현이 가능한 영역까지 작업이 가능

한 것이 특징 인 것 같습니다.

web을 기반한 업무 시스템에서 아주 많이 사용된다고는 할 수 없지만, 일반적인 도형 그리기, 

이미지 관련 작업, 경우에 따라서는 webgl 을 포함한 3d 작업등에 사용될 수 있는 기술 입니다.


아주 간단한 도형그리기 기초 함수를 구성해 보았습니다. 

Mouse Event로 사용자의 Action 을 받아 들여 canvas 를 도화지처럼 사용할 수 있는 

기초 함수 들 입니다. 


- Canvas 에서 사용할 꾸미기 옵션에 관련한 간단한 예시 입니다. 

   해당 내용이 추가 되더라도 이곳에서 더 추가후 진행할 수 있도록 함수화 하여 구성 

   ie 에서는 Object.assign 이 사용되지 못하기 때문에 json 객체는 직접 복제하여야 합니다.

  // Object.assign({}, source); 

// IE NOT WORKING ....

function getDefaultOptions() {

var obj = {

strokeStyle : "#000000",

fillStyle : "#000000",

lineWidth : 2

};

return obj;

}

function setCurrentOptions(ctx, sOptions) {

if ( !ctx ) {

return;

}

if ( !sOptions ) {

return;

}


ctx.strokeStyle = sOptions.strokeStyle;

ctx.lineWidth = sOptions.lineWidth;

ctx.fillStyle = sOptions.fillStyle;

}

- Line 은 다음과 같은 함수를 활용해 보았습니다. 
  Polyline 을 그리는 함수는 직접 쓰기 그리기 및 도형을 그리는 영역에서도 사용할 수 
  있습니다. 

function drawLine(ctx, sx, sy, ex, ey, sOptions) {
if ( !ctx ) 
return;

ctx.save();
setCurrentOptions(ctx, sOptions);
ctx.beginPath();
ctx.moveTo(sx,sy);
ctx.lineTo(ex,ey);
ctx.stroke();
ctx.closePath();
ctx.restore();
}

function makePolylines(ctx, sx, sy, ex, ey, points, sOptions, isClosed, isFilled ) {
if ( !ctx ) 
return;

ctx.save();
setCurrentOptions(ctx, sOptions);
ctx.beginPath();
ctx.moveTo(sx,sy);
if ( points ) {
for ( let i = 0, iSize= points.length; i < iSize; i++ ) {
ctx.lineTo(points[i][0],points[i][1]);
}
}
ctx.lineTo(ex,ey);
if ( isClosed || isFilled ) {
ctx.closePath();
if ( isFilled ) {
ctx.fill();
} else {
ctx.stroke();
}
} else {
ctx.stroke();
ctx.closePath();
}
ctx.restore();
}

function drawBezierLine(ctx, sx, sy, ex, ey, points, sOptions ) {
if ( !ctx ) 
return;

ctx.save();
setCurrentOptions(ctx, sOptions);
ctx.beginPath();
ctx.moveTo(sx,sy);
let mx01 = sx;
let my01 = sy;
let mx02 = ex;
let my02 = ey;
if ( points ) {
if ( points.length == 1 ) {
mx01 = points[0][0];
my01 = points[0][1];
if ( points.length >= 2 ) {
mx01 = points[0][0];
my01 = points[0][1];
mx02 = points[1][0];
my02 = points[1][1];
}
}
ctx.bezierCurveTo(mx01,my01, mx02, my02, ex,ey);
ctx.stroke();
ctx.closePath();
ctx.restore();
}

- 도형은 더 구성할 수도 있지만, 대략 사각형, 원, 타원 등을 그리도록 구성해 보았습니다.
  사각형이나, 타원은 위 polyline 으로 구성해 보았습니다. 
  ie 가 아닌 경우 타원을 그리는 함수가 지원되지만, 구현이 간단하기 때문에 구성해 
  보았습니다.  rotate 등의 각은 모두 radian 을 사용합니다. 

function makeRectangle(ctx, sx, sy, ex, ey, sOptions, isFilled ) {

if ( !ctx ) 
return;

let points = [];
points.push([sx,ey]);
points.push([ex,ey]);

makePolylines(ctx, sx,sy, ex,sy, points, sOptions, true, isFilled );
}

function makeEllipse(ctx, sx, sy, ex, ey, sOptions, isFilled ) {
if ( !ctx ) 
return;

let rx = Math.abs(sx-ex);
let ry = Math.abs(sy-ey);
let ssx = sx;
let ssy = sy;
let points = [];
for ( let i = 0; i < 360; i++ ) {
let rv = (Math.PI*i/180);
if ( i == 0 ) {
ssx = Math.sin(rv)*rx + sx;
ssy = Math.cos(rv)*ry + sy;
} else if ( i == 359 ) {
ex = Math.sin(rv)*rx + sx;
ey = Math.cos(rv)*ry + sy;
} else {
points.push([Math.sin(rv)*rx+sx, Math.cos(rv)*ry+sy]);
}
}

makePolylines(ctx, ssx,ssy, ex,ey, points, sOptions, true, isFilled );
}


function makeArc(ctx, sx, sy, ex, ey, sOptions , isFilled) {
if ( !ctx ) 
return;

let radious = Math.sqrt( Math.pow((sx-ex),2)+Math.pow((sy-ey),2));
alert ( radious );

ctx.save();
setCurrentOptions(ctx, sOptions);
ctx.beginPath();
ctx.arc(sx, sy, radious, 0, Math.PI*2);
ctx.closePath();
if ( isFilled ) {
ctx.fill();
} else {
ctx.stroke();
}
ctx.restore();
}

위 함수로 간단히 그려본 결과 입니다. 
Mouse Event를 활용 하면 간단한 그림 그리기 도구를 만들 수 있습니다. 
                
                function drawTest() {
......
var ctx = canvasObjects.display[1];
var opt = getDefaultOptions();
opt.strokeStyle = "red";
opt.lineWidth = 10;
drawBezierLine(ctx, 100, 100, 300, 300, [[300,100],[100,300]], opt) ;
opt.strokeStyle = "blue";
opt.lineWidth = 3;
makeRectangle(ctx, 100, 100, 300, 300, opt, false);
makeEllipse(ctx, 400,300, 450, 200, opt, false);
opt.strokeStyle = "cyan";
makeArc(ctx, 400, 300, 450, 200, opt, false);
            }