Skip to content

Documentation / @finchart/tools

@finchart/tools

드로잉 툴 — 수평선·추세선·피보나치. 드로잉은 도메인 좌표(데이터 x·가격)의 순수 데이터라 직렬화가 세션을 넘고, 드래그는 입력 스택의 캡처 위에서 pan을 이긴다. 역시 코어 커밋 0건.

설치

아직 npm에 발행 전이다 — 스코프 선점 전이고 버전은 0.0.1이다. 지금 써 보려면 저장소를 클론해 pnpm buildpnpm pack으로 tarball을 만들어 설치한다(절차는 저장소 루트 README의 "시작하기"에 있다). 아래는 발행 뒤의 모습이다.

sh
pnpm add @finchart/tools @finchart/core

@finchart/core는 peer다 — 함께 설치한다.

얹기

pane에 매단다. 드로잉은 그 pane의 값 축(가격)으로 살기 때문이고, plot을 따로 받는 것은 x 좌표계와 입력 스택이 무대의 것이라서다.

ts
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()사용자 의도 없이도 발화한다(서버 푸시의 알림선, 마운트 시 여러 개 얹기). 항상 선택하면 편집 중인 선택을 뺏고, 아무도 만들지 않은 도형이 핸들과 함께 선택된 채 화면이 시작한다.

사용자가 방금 버튼을 누른 자리라면 손 긋기와 같게 끝낼 수 있다:

ts
tools.add({ type: "horizontal", price: 105 }, { select: true });

선택이 바뀌면 알린다 — 속성 패널·휴지통 버튼이 듣는 자리다. 포인터· 더블클릭·우클릭·]·[·select()·해제가 전부 여기로 온다.

ts
tools.selectionChanges.subscribe(({ selection, handle }) => {
  panel.show(selection);          // 복사본 — 없으면 null
  trashButton.disabled = !handle; // 손잡이가 곧 신원이다
});

handle이 오는 것이 요점이다. 값만 오면 같은 값 둘(같은 가격의 수평선 둘)에서 앱이 어느 것인지 못 가른다 — 커서가 가리킨 것은 위에 그려진 뒤엣것인데 값으로 찾으면 앞엣것이 나온다.

키는 차트가 포커스를 가진 동안만 온다. 툴바 버튼으로 도구를 켜면 포커스가 그 버튼으로 가므로, 켠 뒤에 build()에 넘긴 요소로 포커스를 돌려줘야 위 네 키가 산다 — 안 그러면 도구를 켜고 마음을 바꿔 누른 Esc가 아무것도 안 한다. 한 줄이다:

ts
button.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()와 같은 순서다.

ts
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)은 이보다 높다. 그것은 기여자의 하한이지 소비자의 것이 아니다.

문서

Interfaces

Type Aliases

Variables

Functions