Documentation / @finchart/tools
@finchart/tools
드로잉 툴 — 수평선·추세선·피보나치. 드로잉은 도메인 좌표(데이터 x·가격)의 순수 데이터라 직렬화가 세션을 넘고, 드래그는 입력 스택의 캡처 위에서 pan을 이긴다. 역시 코어 커밋 0건.
설치
아직 npm에 발행 전이다 — 스코프 선점 전이고 버전은
0.0.1이다. 지금 써 보려면 저장소를 클론해pnpm build뒤pnpm pack으로 tarball을 만들어 설치한다(절차는 저장소 루트 README의 "시작하기"에 있다). 아래는 발행 뒤의 모습이다.
pnpm add @finchart/tools @finchart/core@finchart/core는 peer다 — 함께 설치한다.
얹기
pane에 매단다. 드로잉은 그 pane의 값 축(가격)으로 살기 때문이고, plot을 따로 받는 것은 x 좌표계와 입력 스택이 무대의 것이라서다.
import { drawingTools } from "@finchart/tools";
const tools = plot.mainPane.use(drawingTools({ plot }));
tools.begin("trend"); // 다음 클릭 둘이 추세선
tools.add({ type: "horizontal", price: 105 }); // 프로그램으로 얹기
tools.dispose(); // 배선 전부가 한 번에편집까지 손으로 된다 — 선택하면 끝점 핸들이 보이고, 핸들을 끌면 그 점만 움직인다(수평선은 가운데 핸들 하나로 전체 이동). Esc 취소, Delete 삭제, ]·[ 선택 순회.
손으로 그은 것은 완성과 함께 선택되고, add()로 얹은 것은 안 된다. 비대칭이지만 의도다 — 손 긋기는 사용자의 손이 방금 끝낸 것이라 의도가 그 자리에 있고, add()는 사용자 의도 없이도 발화한다(서버 푸시의 알림선, 마운트 시 여러 개 얹기). 항상 선택하면 편집 중인 선택을 뺏고, 아무도 만들지 않은 도형이 핸들과 함께 선택된 채 화면이 시작한다.
사용자가 방금 버튼을 누른 자리라면 손 긋기와 같게 끝낼 수 있다:
tools.add({ type: "horizontal", price: 105 }, { select: true });선택이 바뀌면 알린다 — 속성 패널·휴지통 버튼이 듣는 자리다. 포인터· 더블클릭·우클릭·]·[·select()·해제가 전부 여기로 온다.
tools.selectionChanges.subscribe(({ selection, handle }) => {
panel.show(selection); // 복사본 — 없으면 null
trashButton.disabled = !handle; // 손잡이가 곧 신원이다
});handle이 오는 것이 요점이다. 값만 오면 같은 값 둘(같은 가격의 수평선 둘)에서 앱이 어느 것인지 못 가른다 — 커서가 가리킨 것은 위에 그려진 뒤엣것인데 값으로 찾으면 앞엣것이 나온다.
키는 차트가 포커스를 가진 동안만 온다. 툴바 버튼으로 도구를 켜면 포커스가 그 버튼으로 가므로, 켠 뒤에
build()에 넘긴 요소로 포커스를 돌려줘야 위 네 키가 산다 — 안 그러면 도구를 켜고 마음을 바꿔 누른 Esc가 아무것도 안 한다. 한 줄이다:tsbutton.addEventListener("click", () => { tools.begin("trend"); chartEl.focus(); // build()에 넘긴 그 요소 });전말은
docs/plot-contract.md의 "키보드 조작".
자석(스냅) — drawingTools({ plot, snap: true }) 또는 api.setSnap(on). 작도와 끝점 드래그가 봉의 값(종가·저가·고가)과 봉 x에 붙는다 — 반경 8px(snapRadius) 안일 때만이고, 통째 이동은 붙지 않는다(상대 배치 유지). 기본 꺼짐. 후보 값은 코어 접근자의 getYRange(점의 데이터 값 폭)에서 온다 — 커스텀 시리즈도 접근자에 그것만 주면 자석이 안다.
직렬화는 두 층이다. 앱은 위층 — tools.serialize()가 무대의 드로잉 전부를 문자열로, tools.load(s)가 복원(못 읽으면 false, 기존 목록은 지킨다). 저수준 serializeDrawings/parseDrawings(드로잉 배열 ↔ 문자열) 는 자기 저장 형식·부분 저장을 가진 앱의 것이다 — 둘 중 하나만 쓰면 되고, 보통은 위층이다. 저장 시점은 tools.changes 구독으로: 드래그 이동은 reason: "move"로 따로 오므로 그것만 디바운스한다. 실물은 드로잉 툴 예제의 localStorage 왕복이다.
저장한 것을 다시 지목하기
serialize()/load()는 그림을 세션 너머로 나른다. 복원된 드로잉도 handles()로 지목한다 — list()와 같은 순서다.
tools.load(localStorage.getItem(KEY) ?? "");
// 사이드 패널: 목록의 n번째 행을 누르면 차트에서 하이라이트
const handles = tools.handles();
row.onclick = () => tools.select(handles[n]);
handles[n].remove();손잡이 객체는 부를 때마다 새로 나온다 — 신원이 아니라 가리키는 대상이 계약이라 select·remove 모두 그대로 동작한다.
얼마나 가까이 눌러야 잡히나
히트 판정 거리는 픽셀 고정이다 — 줌·화면 밀도와 무관하다.
| 무엇을 | 거리 | |
|---|---|---|
| 선(추세선 몸통 · 수평선 · 피보 레벨) | 4px | |
끝점 손잡이(a·b) | 6px | 넉넉하다 — 끝점이 선보다 먼저 잡힌다 |
끝점이 더 넓은 것이 의도다. 둘이 겹치는 자리에서 선이 먼저 잡히면 끝점을 영영 못 잡는다 — 끌어서 길이를 바꾸는 동작이 통째로 죽는다.
이 값은 스타일이 아니라 판정 거리라 CSS 변수가 아니다. 자석의 snapRadius(기본 8px)와 같은 계보다 — 눈에 보이는 것이 아니라 손의 정확도에 대한 가정이다. 지금은 옵션으로 열려 있지 않다: 터치에서 *"핸들을 못 잡겠다"*는 실보고가 오면 그때 hitRadius를 연다(원장 Q25). 그때까지도 값 자체는 알아야 자기 UI를 맞출 수 있어서 여기 적는다.
지원 범위
- Node 18+ — SSR·worker·테스트가 지나는 headless 경로.
- 브라우저 — Chrome 98+ · Edge 98+ · Firefox 94+ · Safari 15.4+ (2022-03). 하한을 정하는 것은
structuredClone·Object.hasOwn·Array.prototype.at이고, 폴리필은 싣지 않는다 — 더 낮은 곳을 지원하려면 소비자가 넣는다. - 저장소의 개발 환경(Node 24 · pnpm 11)은 이보다 높다. 그것은 기여자의 하한이지 소비자의 것이 아니다.
문서
- 스타일 토큰 — CSS 변수 전체 표 —
theme.md - Plot 계약 — plot-contract.md
- 용어집 — glossary.md
Interfaces
- AddDrawingOptions
- Anchor
- DrawingHandle
- DrawingModeChange
- DrawingsChange
- DrawingSelectionChange
- DrawingToolsApi
- DrawingToolsOptions
- FibRetracement
- HorizontalLine
- TrendLine