트리 이미지 생성, 두 번째 부분.형상, 그래픽 및 DOM
두 번째 부분에서 우리는 응용 프로그램 구조를 설계하고 환경과 의존 주입을 설정했다.마지막으로 트리의 문자열 표현을 생성할 수 있는 L-Systems 모듈을 만들었습니다.
본문에서 우리는 기하학적 모듈을 만들 것이다.
canvas
의 점 위치를 계산합니다.그런 다음 canvas
개의 요소에 액세스할 수 있는 DOM 어댑터를 만듭니다.마지막으로, 우리는 화면에 첫 번째 그림을 표시할 것이다.기하학적 모듈
역층의 두 번째 모듈은 기하학체다.split its interface은
ShapeBuilder
은 기하학적 형태 StartSelector
은 캔버스에서 첫 번째 행의 시작점을 선택합니다.// src/geometry/shape/types.ts
export interface ShapeBuilder {
createLine(start: Point, length: Length, angle?: Angle): Line;
}
누락된 도메인 유형을 추가하려면 다음과 같이 하십시오.// typings/geometry.d.ts
type PixelsAmount = number;
type DegreesAmount = number;
type Coordinate = number;
type Length = PixelsAmount;
type Angle = DegreesAmount;
type Point = {
x: Coordinate;
y: Coordinate;
};
type Size = {
width: Length;
height: Length;
};
type Line = {
start: Point;
end: Point;
};
이제 모듈을 구현해 보겠습니다.// src/geometry/shape/implementation.ts
import { ShapeBuilder } from "./types";
export class CoreShapeBuilder implements ShapeBuilder {
public createLine(start: Point, length: Length, angle: Angle = 0): Line {
const radians = (angle * Math.PI) / 180;
const end: Point = {
x: start.x + length * Math.sin(radians),
y: start.y - length * Math.cos(radians),
};
return { start, end };
}
}
이 나무는 canvas
의 밑부분에서 꼭대기까지 자라기 때문에 우리는 Y 좌표를 선의 길이를 줄인다.각도를 설정하면 점의 위치가 그에 따라 변경됩니다.모듈을 등록합니다.
// src/geometry/shape/composition.ts
import { container } from "../../composition";
import { CoreShapeBuilder } from "./implementation";
import { ShapeBuilder } from "./types";
container.registerSingleton<ShapeBuilder, CoreShapeBuilder>();
// Also, we need to import `src/geometry/shape/composition.ts`
// inside of `src/composition/index.ts`.
// Later, I won't remind you of composition imports.
명명 및 표준 구현 정보
사실 나는 이 이름
CoreShapeBuilder
을 좋아하지 않는다.ShapeBuilder
만 사용할 수 있지만 인터페이스는 이미 이 이름을 사용했다.인터페이스가 1가지 방식으로만 제공되는 경우 class methods as the public API을 사용할 수 있습니다.
class ShapeBuilder {
/* ... */
}
container.registerSingleton<ShapeBuilder>();
그러나 일치성을 유지하기 위해 우리는 인터페이스를 동시에 사용하고 실현할 것이다.By the way, in C# the naming issue is solved with
I
prefixes.
시작 점 선택
초기점을 선택하기 위해서 다른 모듈을 만들 것입니다.공통 API를 정의하려면 다음과 같이 하십시오.
// src/geometry/location/types.ts
export interface StartSelector {
selectStart(): Point;
}
selectStart
방법을 실현하기 위해서는 canvas
의 크기를 알아야 한다.우리는 두 가지 방법으로 이 문제를 해결할 수 있다.// src/geometry/location/implementation.ts
import { AppSettings } from "../../settings";
import { StartSelector } from "./types";
export class StartPointSelector implements StartSelector {
public selectStart(): Point {
const { width, height } = this.settings.canvasSize;
return {
x: Math.round(width / 2),
y: height,
};
}
}
안에서 우리가 가리키는 것은 this.settings.canvasSize
이다.현재, 우리는 이 필드가 없습니다. 우리는 그것을 만들어야 합니다.// 1. We can do it directly:
export class StartPointSelector {
settings = {/*…*/}
}
// 2. Or via constructor:
export class StartPointSelector {
constructor(settings) {
this.settings = settings;
}
}
가장 편리한 방법은 두 번째 옵션을 사용하는 것이다.따라서 우리는 선택한 대상의 모든 작업을 DI용기에 의뢰할 수 있다.// src/geometry/location/implementation.ts
export class StartPointSelector implements StartSelector {
constructor(private settings: AppSettings) {}
// …
}
위 코드에서 컨테이너에 대해 설명합니다.— When you create an instance of the
StartPointSelector
class pass in its constructor something that implementsAppSettings
인터페이스를 요청했기 때문에 don't depend on any implementation details이 필요합니다.실현이 클래스 실례인지 일반 대상인지는 중요하지 않다.유일하게 중요한 것은 이 대상은 인터페이스에 정의된 모든 속성을 포함한다.
잠시 후, 우리는 이런 방식으로 모든 의존항을 주입할 것이다.
설정 만들기
코드가 많지 않기 때문에 우리는 한 파일에서 완성할 것이다.
// src/settings/index.ts
import { container } from "../composition";
export type AppSettings = {
canvasSize: Size;
};
export const settings: AppSettings = {
canvasSize: {
width: 800,
height: 600,
},
};
container.registerSingleton<AppSettings>(() => settings);
마지막 줄에서 우리는 settings
대상을 AppSettings
실현의 대상으로 등록할 것이다.이제부터 구조 함수에서 AppSettings
을 요청하는 모든 모듈은 settings
대상을 획득할 수 있다.등록 모듈
형상 모듈을 등록합니다.
// src/geometry/location/composition.ts
import { container } from "../../composition";
import { StartPointSelector } from "./implementation";
import { StartSelector } from "./types";
container.registerSingleton<StartSelector, StartPointSelector>();
완성!도메인 레이어가 설정되었습니다.그래픽 사용
좋은 구조가 있으면 우리는 매 층을 독립적으로 처리할 수 있다.
팀의 일부분은 역층에서 일할 수 있고, 다른 일부분은 응용 프로그램이나 어댑터 층에서 일할 수 있다.개발자가 모듈의 API에 합의하기만 하면 이들은 단독으로 실현할 수 있다.
응용층을 뛰어넘어 어댑터를 연구해 보자. 이것이 실제로 가능한지 보자.
우리가 연구해야 할 어댑터는 도형이다.공용 API를 정의합니다.
이 모듈은
Drawer
인터페이스를 제공하고 DrawingContextProvider
에 의존할 것이다.어댑터는 응용 프로그램의 수요를 충족시켜야 한다는 것을 기억하십시오. 우리는 외부 세계가 다른 방식이 아니라 우리의 규칙에 따라 행동하기를 원합니다.Notice that we don't name it
CanvasDrawer
but justDrawer
.
인터페이스 이름은 서로 다른 모듈이 이를 실현할 수 있도록 추상적이어야 한다.
CanvasDrawer
canvas
, SvgDrawer
은 SVG
소자 등 외부 세계에 대한 숨겨진 디테일을 실현하는 데도 도움이 된다.따라서 변경 사항이 이루어져야 할 때 all the other modules은 변하지 않습니다.
DrawingContextProvider
은 canvas
컴포넌트에 액세스할 수 있습니다.DOM에서 요소를 가져오지 않는 이유는 무엇입니까?우리는 실체 간에 separate concerns을 만들기를 희망하기 때문에 모든 모듈은 하나의 임무만 수행해야 한다.액세스 권한 제공과 드로잉 명령 처리는 서로 다른 작업이므로 개별 솔리드를 제공해야 합니다.
서랍 커넥터
Drawer
인터페이스에서 우리는 drawLine
방법을 정의했다.선 및 붓 설정을 매개변수로 사용합니다.// src/graphics/drawer/types.ts
export type BrushSettings = {
color?: Color;
width?: PixelsAmount;
};
export interface Drawer {
drawLine(line: Line, settings?: BrushSettings): void;
}
또한 유형 주석을 추가합니다.// typings/graphics.d.ts
type HexadecimalColor = string;
type Color = HexadecimalColor;
서랍 실현
종속성을 정의하고 공용 API를 구현합니다.
// src/graphics/drawer/implementation.ts
import { Drawer, BrushSettings } from "./types";
import { DrawingContext, DrawingContextProvider } from "../context/types";
export class CanvasDrawer implements Drawer {
private context: DrawingContext = null;
constructor(private contextProvider: DrawingContextProvider) {
this.context = this.contextProvider.getInstance();
if (!this.context) throw new Error("Failed to access the drawing context.");
}
public drawLine({ start, end }: Line, { color, width }: BrushSettings = {}): void {
// Handle the drawing commands here...
}
}
구조 함수에서 우리는 DrawingContextProvider
을 방문할 수 있다.이것은 그릴 수 있는 요소를 제공할 것이다.원소가 없으면 오류가 발생합니다.이 클래스는 지정된 행을
DrawingContextProvider
에서 제공하는 요소의 API로 변환합니다.우리의 예에서, 이 요소는 DOM 노드이다.그러나 기본적으로 API와 호환되는 모든 것이 될 수 있습니다.
Drawer
에서 DOM에 직접 액세스할 수 없는 이유입니다.참고로
DrawingContext
은 포장기일 뿐입니다.export type DrawingContext = Nullable<CanvasRenderingContext2D>;
이것은 좋지 않습니다. CanvasRenderingContext2D
메서드로 우리를 바인딩하기 때문입니다.// src/graphics/drawer/implementation.ts
export class CanvasDrawer implements Drawer {
// ...
public drawLine({ start, end }: Line, { color, width }: BrushSettings = {}): void {
if (!this.context) return;
this.context.strokeStyle = color ?? DEFAULT_COLOR;
this.context.lineWidth = width ?? DEFAULT_WIDTH;
// The beginPath, moveTo, lineTo, and stroke methods are
// a direct dependency on `CanvasRenderingContext2D`:
this.context.beginPath();
this.context.moveTo(start.x, start.y);
this.context.lineTo(end.x, end.y);
this.context.stroke();
}
}
이상적인 경우 이러한 방법을 facade으로 작성하여 다음과 같은 API를 제공합니다.this.context.line(start, end);
그러나 이런 상황에서 직위는 더욱 커질 것이다😃그래서 우리는 외관을 실현하지 못하지만, 우리는 그것을 기억할 것이다.
서랍을 등록하다
마지막으로 컨테이너에 서랍 등록을 추가합니다.
// src/graphics/drawer/composition.ts
import { container } from "../../composition";
import { CanvasDrawer } from "./implementation";
import { Drawer } from "./types";
container.registerSingleton<Drawer, CanvasDrawer>();
DrawingContextProvider 설계
DrawingContextProvider
은 두 가지에 달려 있다.ElementSource
, canvas
요소 제공;PixelRatioSource
은 화면 픽셀 밀도에 대한 정보를 제공합니다.canvas
의 크기를 규범화하기 위해서는 두 번째가 필요합니다. 픽셀 밀도가 높은 모니터는 요소를 다시 축소해서 이미지를 더욱 뚜렷하게 해야 하기 때문입니다.인터페이스를 정의합니다.
// src/graphics/context/types.ts
// Keep in mind that the context
// should be a facade over `CanvasRenderingContext2D`
export type DrawingContext = Nullable<CanvasRenderingContext2D>;
export interface DrawingContextProvider {
getInstance(): DrawingContext;
}
구현 공급자
요소 및 해당 2D 컨텍스트에 대한 참조는 내부에서 유지됩니다.
import { AppSettings } from "../../settings";
import { ElementSource, PixelRatioSource } from "../../dom/types";
import { DrawingContext, DrawingContextProvider } from "./types";
export class CanvasContextProvider implements DrawingContextProvider {
private element: Nullable<HTMLCanvasElement> = null;
private context: Nullable<DrawingContext> = null;
constructor(
private elementSource: ElementSource,
private pixelRatioSource: PixelRatioSource,
private settings: AppSettings,
) {
const element = this.elementSource.getElementById("canvas");
if (!element) throw new Error("Failed to find a canvas element.");
this.element = element as HTMLCanvasElement;
this.context = this.element.getContext("2d");
this.normalizeScale();
}
public getInstance(): DrawingContext {
return this.context;
}
// ...
}
구조기에서 우리는 ElementSource
을 통해 요소에 접근하고 성공하면 2D 상하문을 가져온다.그리고 우리는 원소의 비례를 표준화할 것이다.getInstance
방법으로 상하문으로 돌아가다.원소 자체는 사유라는 것을 주의하세요.이것은 이 클래스에 봉인되어 있으며, 상하문이 어떻게 만들어졌는지 아는 다른 모듈이 없습니다.만약 우리가
canvas
에서 SVG
으로 이전하기로 결정한다면, 우리는 이 종류를 바꾸기만 하면 된다.(DrawingContext
이 긍정적이라면😃)이 클래스에서도 비례 표준화를 집행할 것이다.다른 모듈은
canvas
을 어떻게 준비할지 걱정해서는 안 된다.You can find its code on GitHub ;–)등록 공급자
이전과 같이 컨테이너에 모듈을 추가합니다.
// src/graphics/context/composition.ts
import { container } from "../../composition";
import { CanvasContextProvider } from "./implementation";
import { DrawingContextProvider } from "./types";
container.registerSingleton<DrawingContextProvider, CanvasContextProvider>();
그리고 무엇
우리는
ElementSource
과 PixelRatioSource
을 창설하고 등록해야 한다.첫 번째는
document
의 어댑터이고, 두 번째는 window
이다.// src/dom/types.ts
export interface ElementSource {
getElement(id: string): Nullable<HTMLElement>;
}
export interface PixelRatioSource {
devicePixelRatio?: number;
}
implementation of these modules on GitHub as well을 찾을 수 있습니다.조합 모듈
어댑터 그림은 다음과 같습니다.
추가 모듈 depend on interfaces입니다.이로써 코드를 재구성하고 갱신하며 다른 모듈로 모듈을 교체하는 것이 더욱 쉬워졌다.
테스트 응용
서랍의 작동 방식을 테스트하기 위해 우리는
Drawer
인터페이스를 실현하는 대상을 방문했고 두 가지 방법으로 drawLine
방법을 호출했다.// src/index.ts
import { container } from "./composition";
import { Drawer } from "./graphics/types";
const drawer = container.get<Drawer>();
drawer.drawLine({
start: { x: 0, y: 0 },
end: { x: 100, y: 100 },
});
이 코드는 canvas
에 대각선을 그려야 합니다.작품!🎉
현재 유일하게 해야 할 일은 도형과 역층을 연결하는 것이다🤓
계속
이전 기사에서 우리는 L 시스템 문자를 위해 '번역 프로그램' 을 만들 것이다.그 밖에 우리는 피타고라스 나무를 생성하고 랜덤성을 첨가하여 진정한 나무처럼 보일 것이다.
출처
애플리케이션 소스와 프로젝트 자체:
Reference
이 문제에 관하여(트리 이미지 생성, 두 번째 부분.형상, 그래픽 및 DOM), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/bespoyasov/generating-trees-images-part-2-geometry-graphics-and-dom-i64텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)