5.1 D3.js κ°μ5.1.1 D3.jsλ 무μμΈκ°?5.1.2 D3 is a low level toolbox5.1.3 D3 ꡬμ±μμ5.1.4 D3μ νΉμ§5.1.5 D3μ Reactλ μ
μ΄μ μ
μ΄μ κ΄κ³μ΄λ€.5.2 D3 μμνκΈ°5.2.1 μ€μΉ λ°©λ²5.2.2 D3 μ΄μ©μ νμν μμ λ° μ£Όμν μ 5.3 D3λ‘ μκ°ν ꡬμ±νκΈ°5.3.1 μκ°ν κ΅¬μ± μμνκΈ°5.3.2 D3 Architecture flow5.3.3 μκ°ν ꡬμ±μμ1. selection2. axis3. scale4. color5. transition5.3.3 D3 shape μ’
λ₯1. Lines2. Bar3. Areas4. Pies5.4 μ€μ μμ λ λ§μΉλ©°
5.1 D3.js κ°μ
5.1.1 D3.jsλ 무μμΈκ°?
- D3.js(μ΄ν D3)λ λ°μ΄ν°λ₯Ό μκ°ννκΈ° μν λ¬΄λ£ μ€ν μμ€ μλ°μ€ν¬λ¦½νΈ λΌμ΄λΈλ¬λ¦¬μ΄λ€. D3λ μΉ νμ€μ κΈ°λ°ν μ μμ€ μ κ·Ό λ°©μμ ννκ³ μμΌλ©°, μ΄λ λμ μ΄κ³ λ°μ΄ν° κΈ°λ°μ κ·Έλν½μ μμ±νλ λ° μμ΄ μ μ°ν¨μ μ 곡νλ€.
5.1.2 D3 is a low level toolbox
- D3λ μ ν΅μ μΈ μ°¨νΈ λΌμ΄λΈλ¬λ¦¬κ° μλλ€. μ°¨νΈλΌλ κ°λ μ΄λΌκΈ°λ³΄λ€λ λ€μκ³Ό κ°μ κ΅¬μ± μμλ€λ‘ λ°μ΄ν°λ₯Ό μκ°ννλ€.
5.1.3 D3 ꡬμ±μμ
stacked area chartλ₯Ό λ§λλ μμ λ₯Ό κΈ°μ€μΌλ‘ νμν ꡬμ±μμλ λ€μκ³Ό κ°λ€.
- a CSV parser to load data,
- a time scale for horizontal position (x),
- a linear scale for vertical position (y),
- an ordinal scale and categorical scheme for color,
- a stack layout for arranging values,
- an area shape with a linear curve for generating SVG path data,
- axes for documenting the position encodings, and
- selections for creating SVG elements.
μ΄κ²λ§ 보면 κ³ λ € μ¬νμ΄ λ§μ κ²μ²λΌ 보μΈλ€. νμ§λ§ κ° μμλ λ
립μ μΌλ‘ μ¬μ©ν μ μμΌλ―λ‘ λͺ¨λ κ²μ νκΊΌλ²μ λ°°μΈ νμλ μκ³ κ°λ³ μμλ₯Ό λ°°μ΄ λ€μ μ‘°ν©ν΄μ μ¬μ©νλ©΄ λλ€.
(D3λ κ±°λν λ¨μΌ κ΅¬μ‘°κ° μλλΌ 30κ°μ κ°λ³ λͺ¨λ λͺ¨μμ΄λ€!)
5.1.4 D3μ νΉμ§
- D3λ μ μ°νλ€
- D3μ λͺ¨λ μμλ μμ ν μ μ΄κ° κ°λ₯νλ©° μνλ λλ‘ μκ°νλ₯Ό ν μ μλ€. (D3μλ λ°μ΄ν°λ₯Ό λνλ΄λ κΈ°λ³Έμ μΈ ννμ΄ μμΌλ©° μ€μ§ μ¬μ©μκ° μ§μ μμ±ν μ½λλ§μ΄ μ‘΄μ¬νλ€.)
- D3λ μΉκ³Ό ν¨κ» λμνλ€
- D3λ μλ‘μ΄ κ·Έλν½ ννμ λμ νμ§ μκ³ SVG λ° Canvasμ κ°μ μΉ νμ€μ μ§μ μ¬μ©νμ¬ D3λ₯Ό νμ©νλ€.
- D3λ λ§μΆ€ν μκ°νλ₯Ό μν λꡬμ΄λ€
- D3λ λμ μκ°νλ₯Ό μν λꡬμ΄λ€
- D3λ λμ λ λλ§μ μ§μνλ€. λ°λΌμ D3λ λ°μ΄ν° λ°°μ΄μ κΈ°λ°μΌλ‘ νμ¬ μ‘°κ±΄λΆλ‘ HTML μμλ₯Ό μμ±νκ³ λ λλ§ν μ μλ€.
5.1.5 D3μ Reactλ μ μ΄μ μ μ΄μ κ΄κ³μ΄λ€.
- D3μ Reactλ μ μ΄μ μ μ΄μμ²λΌ μλ‘μκ² μμ΄ μ΄μ΅μ μ£Όκ³ λ°λ μ리곡μ κ΄κ³μ λμ¬ μλ€.
- μ»΄ν¬λνΈ κΈ°λ° μν€ν μ²μ ν¨μ¨μ μΈ κ°μ DOM λ λλ§μ΄λΌλ νΉμ§μ μ§λλ Reactλ μ¬μ©μ μΈν°νμ΄μ€λ₯Ό ꡬμΆνλ λ°λ©΄, λ°μ΄ν° κΈ°λ° λ¬Έμ(Data Driven Documents)μΈ D3λ λ°μ΄ν° κΈ°λ°μ μ§μ μ μΈ DOM μ‘°μμ ν΅ν΄ μ κ΅νκ³ μ μ°ν μκ°νλ₯Ό ꡬμΆνλ€.
- μ΄λ¬ν 리μ‘νΈμ D3λ ν΅ν©λλ©΄ μλ‘μ κ°μ μ μ΄λ €μ interactiveν λ°μ΄ν° μκ°νμ ν¬κ² κΈ°μ¬ν μ μλ€. μλνλ©΄ 리μ‘νΈ μ»΄ν¬λνΈλ μ ν리μΌμ΄μ μ μνμ UI λ‘μ§μ κ΄λ¦¬νκ³ , D3λ μκ°μ μμμ μ νν λ λλ§κ³Ό μ λλ©μ΄μ μ μ²λ¦¬νκΈ° λλ¬Έμ΄λ€. μ¦, λͺ νν μ± μ ꡬλΆμ ν΅ν΄ κ°λ°μλ λ°μμ±μ΄ λ°μ΄λλ©° μ μ§ κ΄λ¦¬ μ©μ΄ν λ°μ΄ν° κΈ°λ° μ ν리μΌμ΄μ μ ꡬμΆν μ μκ² λλ κ²μ΄λ€.
- μ€μ λ‘, λ°μ΄ν°λ₯Ό κ°μ Έμ€κ³ λμ React μ»΄ν¬λνΈ λ΄μμ μ΄ λ°μ΄ν°λ₯Ό μ²λ¦¬νκ² λλ€. μ€λΉκ° μλ£λλ©΄ ν¨μν μ»΄ν¬λνΈμ useEffectμ κ°μ Reactμ μλͺ μ£ΌκΈ° λ©μλ λ΄μμ D3 λ©μλκ° νΈμΆλμ΄ SVG μμλ₯Ό μμ±νκ³ μ λ°μ΄νΈνλ€. μ΄λ¬ν κ³Όμ μ ν΅ν΄ νμν κ²½μ°μλ§ D3 μ‘°μμ΄ μνλλ―λ‘ Reactμ κ°μ DOMμ μ±λ₯μ μ΄μ μ 보쑴ν μ μλ€.
5.2 D3 μμνκΈ°
5.2.1 μ€μΉ λ°©λ²
Nodeλ₯Ό μ΄μ©ν μΉ μ΄ν리μΌμ΄μ
μ κ°λ°νλ κ²½μ° yarn, npm, pnpmκ³Ό κ°μ ν¨ν€μ§ λ§€λμ λ₯Ό ν΅ν΄ D3λ₯Ό μ€μΉν μ μλ€.
# npm npm install d3 # yarn yarn add d3 # pnpm pnpm add d3
λ€μ ꡬ문μ ν΅ν΄ D3λ₯Ό λ‘λν μ μλ€
import * as d3 from 'd3';
λ³Έ νλ‘μ νΈλ npmμ κΈ°λ°μΌλ‘ μ€λͺ
νκ² λ€.
5.2.2 D3 μ΄μ©μ νμν μμ λ° μ£Όμν μ
- svg
λΈλΌμ°μ μμμ λ°μ΄ν°λ₯Ό μκ°ννλ κ²½μ°, μ°λ¦¬λ λ³΄ν΅ SVG μμλ€λ‘ μμ
νλ€.
(SVGλ ννλ ₯μ΄ λ°μ΄λκ³ μμΉκ° νμ€νκ² μ‘νμκΈ° λλ¬Έμ΄λ€)
SVG (Scalable Vector Graphics)λ, λ²‘ν° κ·Έλν½μ μμ νλ XML κΈ°λ°μ λ§ν¬μ
μΈμ΄μ΄λ€.
ν
μ€νΈ κΈ°λ°μ μ΄λ¦° μΉ νμ€ μ€ νλμ΄λ©° λͺ¨λ μ¬μ΄μ¦μμ κΉλνκ² λ λλ§ λλ μ΄λ―Έμ§λ₯Ό μμ νλ€.
(νλ λ° μΆμλ₯Ό νκ±°λ νμ νμ¬λ μ΄λ―Έμ§ μμμ΄ μΌμ΄λμ§ μλλ€.)
μ¦, SVGλ HTMLκ³Ό ν
μ€νΈμ κ΄κ³λ₯Ό κ·Έλν½ μμ μ μ©ν κ²μ΄λ€.
- useRef
svg μμ(DOM μμ)μ λν μ°Έμ‘°λ₯Ό μ μ₯νκΈ° μν΄ μ¬μ©
- useEffect
D3.js μ½λλ₯Ό React λ λλ§ μ£ΌκΈ°μ λ§μΆ° μ€ννκ³ , λ°μ΄ν° λ³κ²½μ λ°λΌ μκ°νλ₯Ό μ
λ°μ΄νΈνκΈ° μν΄μ useEffectλ₯Ό μ¬μ©νλ€. useEffectλ μ»΄ν¬λνΈκ° λ λλ§ λ νμ μ€νλλ―λ‘, DOM μμκ° μ‘΄μ¬νλ μμ μ D3 μ½λλ₯Ό μ€νν μ μλ€.
useEffect λ°μ d3.select κ΄λ ¨ μ½λλ₯Ό λλ©΄, μμ§ DOM μμκ° μ‘΄μ¬νμ§ μλ μμ μ D3 μ½λκ° μ€νλμ΄ μ무κ²λ λ λλ§ λμ§ μλλ€. (Reactμ λ λλ§ μ¬μ΄ν΄κ³Ό λ§μ§ μκΈ° λλ¬Έμ κ²°κ³Όκ° λ λλ§ λμ§ μμ)
- νλ‘μ νΈ κΈ°λ³Έ ꡬμ±
import { useEffect, useRef } from 'react'; import * as d3 from 'd3'; const ComponentName = () => { const ref = useRef; useEffect(() => { const svg = d3.select(ref.current); /* λ°μ΄ν°λ₯Ό μ¬μ©νμ¬ μκ°ννκΈ° μν D3 μ½λ μμ± */ },[]) } export default ComponentName;
5.3 D3λ‘ μκ°ν ꡬμ±νκΈ°
5.3.1 μκ°ν κ΅¬μ± μμνκΈ°
- SVG μμ λ§λ€κΈ°
D3λ₯Ό μμνκΈ° μν΄ κ°λ¨ν <svg> μμλ₯Ό λ λλ§ ν΄λ³΄μ.
width, height λ° backgroundColorλ₯Ό μΆκ°νμ¬ μ°λ¦¬λ <svg>μμλ₯Ό μμ±ν κ²μ νμΈν μ μλ€.
const Svg1 = () => { return ( <> <svg style={{ width: "100px", height: "100px", backgroundColor: "red" }} /> </> ); }; export default Svg1;

μ΄λ²μλ μμ κ·Έλ €λ³΄μ.

리μ‘νΈλ₯Ό μ΄μ©ν΄μ μμ λ€μκ³Ό κ°μ΄ λνλΌ μ μλ€.
import { useEffect, useRef } from "react"; import * as d3 from "d3"; const Svg2 = () => { const ref = useRef(); // 1) useEffect(() => { // 2) const svgElement = d3.select(ref.current); // 3) svgElement .append("circle") // 4) .attr("cx", 100) .attr("cy", 80) .attr("r", 50); }, []); return ( <> {/* 1 */} <svg ref={ref} /> </> ); }; export default Svg2;
useRef
λ₯Ό μ¬μ©ν΄μ λ λλ§λ<svg>
μμμ λν μ°Έμ‘°λ₯Ό μ μ₯νλ€.
- μ»΄ν¬λνΈκ° λ§μ΄νΈλ λ d3 μ½λλ₯Ό μ€νμν¨λ€.
d3.select()
λ₯Ό μ΄μ©νμ¬ref
λ₯Όd3 selection object
λ‘ λ°κΎΌλ€.
d3 selection object
λ₯Ό μ¬μ©ν΄μ<circle>
μμλ₯Ό μΆκ°νλ€.
μ μ½λλ νλμ μμ 그리기 μν΄ λ§μ μμ μ½λλ₯Ό μ¬μ©νκ³ μλ€. μ€μ λ‘λ μλμ κ°μ΄ κ°λ¨νκ² μμ±νλλΌλ λκ°μ κ²°κ³Όλ¬Όμ λμΆν΄ λΌ μ μλ€.

const Svg3 = () => { return ( <> <svg> <circle cx="100" cy="80" r="50"></circle> </svg> </> ); }; export default Svg3;
νμ§λ§ μ°λ¦¬λ νμ΅μ λ¨κ³μ μκΈ° λλ¬Έμ 2λ²μ λ°©λ²μ μ£Όλ‘ μ¬μ©ν΄μ μ½λλ₯Ό ꡬμ±νκΈ°λ‘ νλ€.
5.3.2 D3 Architecture flow
D3λ λ€μκ³Ό κ°μ νλ¦λλ‘ λμνλ€.
- μΈλΆ νμΌ(JSON, CSV λ±)λ‘λΆν° λ°μ΄ν°λ₯Ό λΆλ¬μ€κ±°λ JS array λ° object ννλ‘ λ°μ΄ν°λ₯Ό μ 곡νλ€.
- CSS selector νΉμ useRefλ±μ μ¬μ©ν΄μ HTML, SVG μμλ₯Ό μ ννλ€.
- μ νλ DOMλ€μ κ° λ°μ΄ν°μ λ§μΆμ΄μ μκ°νλ μ μλλ‘ μ‘°μμ κ°νλ€.

5.3.3 μκ°ν ꡬμ±μμ
1. selection
- D3λ λ©μλ 체μ΄λμ μ΄μ©νμ¬ svgλ₯Ό λ€λ£¬λ€.
- D3μ selectionμ selection κ°μ²΄λ₯Ό λ°ννλλ°, λ©μλ 체μ΄λμ ν΅ν΄ μ νλ DOM μμμ attributeμ style λ±μ μ μ©ν μ μκ² λμμ€λ€. β μ΄λ₯Ό ν΅ν΄ λ°μ΄ν° μ‘°μκ³Ό μκ°νλ₯Ό ν¨μ¨μ μΌλ‘ μ§νν μ μκ² λλ€.
μμλ₯Ό ν΅ν΄ selectionμ΄ μ΄λ»κ² λμνλμ§ μ΄ν΄λ³΄μ.
import { useEffect, useRef } from "react"; import * as d3 from "d3"; const Selection = () => { const ref = useRef(); useEffect(() => { const svg = d3.select(ref.current); svg.append("circle") .data() // option .attr("cx", 100) .attr("cy", 80) .attr("r", 50); }, []); return <svg ref={ref} width={200} height={200} />; }; export default Selection;
- selectλ₯Ό μ΄μ©ν΄μ Elementλ₯Ό νμν λ€μ selection κ°μ²΄λ₯Ό μμ±νλ€.
- dataκ° μλ€λ©΄ λ°μ΄ν°λ₯Ό λ£λ κ²½μ°λ μλ€
- appendλ₯Ό ν΅ν΄ circleμμλ₯Ό μΆκ°νλ€
- μμ±λ μμμ cx, cy, r κ°μ λ°μ΄ν°λ₯Ό λ£λλ€.
μ΄λ₯Ό ν΅ν΄ selectλ κ°μ₯ μ νλμ΄μΌ νλ νμμμ μ§μν μ μλ€.
selectionμ λν΄ λ μμΈν μκ³ μΆλ€λ©΄ μλ λ΄μ©μ μ°Έκ³ νλ©΄ μ’λ€.
2. axis
μΆ(axis) μ»΄ν¬λνΈλ SVG 컨ν
μ΄λ(μΌλ°μ μΌλ‘ λ¨μΌ
g μμ
)μ μ ν ν νΈμΆνλ©΄ μΆμ μ±μΈ μ μλ€.
import { useEffect, useRef } from "react"; import * as d3 from "d3"; const Axis = () => { const ref = useRef(); useEffect(() => { const svg = d3.select(ref.current) .attr("width", 1000) .attr("height", 200); const x = d3.scaleLinear() .domain([1, 10]) .range([1,500]); svg .append("g") .attr("transform", "translate(0,100)") .call(d3.axisBottom(x)); }, []); return <svg ref={ref} />; }; export default Axis;
- μΆ μμ± λ° μΆκ°
svg.append("g") .attr("transform", "translate(0,100)") .call(d3.axisBottom(x));
- SVG μμμ κ·Έλ£Ή μμ(g)λ₯Ό μΆκ°νλ€.
- transform μμ±μ μ¬μ©νμ¬ yμΆ λ°©ν₯μΌλ‘ 100 ν½μ μ΄λμν¨λ€.
- d3.axisBottom(x)μ νΈμΆνμ¬ μν μΆμ μμ±νλ€.
3. scale
scaleμ μ¬μ©νλ©΄ rangeμ domainμ μ§μ ν¨μΌλ‘μ¨ ννμ ν¬κΈ°λ₯Ό μνλ λλ‘ λ§λ€ μ μλ€.

import { useEffect, useRef } from "react"; import * as d3 from "d3"; const Scale = () => { const ref = useRef(); useEffect(() => { // SVG ν¬κΈ° μ€μ const width = 2000; const height = 200; // λ°μ΄ν° (0 ~ 10,000) const data = Array.from({ length: 10000 }, (_, i) => i + 1); // SVG μμ μ ν const svg = d3 .select(ref.current) .attr("width", width) .attr("height", height); // x μ€μΌμΌ μ μ (μ ν μ€μΌμΌ) const xScale = d3 .scaleLinear() .domain([0, d3.max(data)]) // μ λ ₯ λλ©μΈ: λ°μ΄ν°μ μ΅μκ°κ³Ό μ΅λκ° .range([0,1000]); // μΆλ ₯ λ²μ: SVGμ λλΉ // xμΆ μΆκ° const xAxis = d3.axisBottom(xScale); svg .append("g") .attr("transform", `translate(0, ${height - 100})`) .call(xAxis); }, []); return ( <> <svg ref={ref}></svg> </> ); }; export default Scale;
- d3.scaleLinear
- (μ μ½λμ)
xScaleLinear
λΌλ λ³μμd3.scaleLinear()
ν¨μλ₯Ό μ¬μ©νμ¬ μ λ ₯ λ²μ(domain)λ₯Ό μΆλ ₯ λ²μ(range)μ λ§€ννλ€. κ·Έλ¦¬κ³ μ΄ λκ°μ μ ν κ΄κ³λ₯Ό κ°μ§λ scaleμ μμ±νλ€. - κΈ°λ³ΈκΌ΄

scaleLinear(domain, range)
- linear.domain([μ΅μκ°, μ΅λκ°])
- domainμ μ
λ ₯ λ°μ΄ν°μ λ²μλ₯Ό μ§μ νλ
μ λ ₯ λ°μ΄ν°μ μ§ν©
μ΄λ€. (μκ°ννλ €λ λ°μ΄ν°μ μ΅μκ°κ³Ό μ΅λκ°) - κΈ°λ³ΈκΌ΄
const xScale = d3 .scaleLinear() .domain([μ΅μκ°, μ΅λκ°])
- linear.range([μ΅μκ°, μ΅λκ°])
- rangeλ μΆλ ₯ λ²μλ₯Ό μ§μ νλ
μΆλ ₯ κ°μ μ§ν©
μ΄λ€. - κΈ°λ³Έ κΌ΄
const xScale = d3 .scaleLinear() .domain([μ΅μκ°, μ΅λκ°]) .range([μ΅μκ°, μ΅λκ°]
μμ μ΄ν΄λ³Έ rangeλ₯Ό 1000 β 2000μΌλ‘ λ°κΎΈλ©΄ λ€μκ³Ό κ°μ κ²°κ³Όκ° λμ¨λ€.
λ§€νλ κ°μ΄ μ΄μ κ³Ό λ¬λΌμ§ κ²μ νμΈν μ μλ€.
(μ€μ λ‘ νλ©΄μ κΈΈμ΄λ 2λ°°μ λ λμ΄λ κ²μ λ³Ό μ μλ€.)

4. color
μμ μ΄ν΄λ³΄μλ μ 그리기 μμ μμ colorλ₯Ό μ±μ°κΈ° μν΄μλ fill μμ±μ μ΄μ©νκ³ μμμ μ§μ ν΄ μ£Όλ©΄ λλ€

import { useEffect, useRef } from "react"; import * as d3 from "d3"; const Color = () => { const ref = useRef(); useEffect(() => { const svg= d3.select(ref.current); const color = d3.color('steelblue'); svg .append("circle") .attr("cx", 100) .attr("cy", 80) .attr("r", 50) .attr("fill", color); // μμ μ§μ }, []); return ( <> <svg ref={ref} /> </> ); }; export default Color;
5. transition
μ΄λ²μλ transitionμ μ΄μ©ν΄μ νλ©΄μμ μμ΄ μλ€κ°λ€νλ κ²μ ꡬνν΄λ³΄μ.
D3μμ νΈλμ§μ
μ μμμ μμ±μ΄λ μ€νμΌμ λΆλλ½κ² λ³νμν€λ λ°©λ²μ΄λ€.
μλμ μ½λμμλ μ(circle)μ λ°μ§λ¦(r)κ³Ό μμ(fill)μ νΈλμ§μ
μ μ μ©νκ³ μλ€.


import React, { useEffect, useRef, useState } from "react"; import * as d3 from "d3"; const generateCircles = () => { return Array.from({ length: 10 }, () => Math.floor(Math.random() * 10)); }; const Transition = () => { const [visibleCircles, setVisibleCircles] = useState(generateCircles()); const ref = useRef(); useEffect(() => { const timer = setInterval(() => { setVisibleCircles(generateCircles()); }, 2000); return () => clearInterval(timer); }, []); useEffect(() => { const svg = d3.select(ref.current); svg .selectAll("circle") .data(visibleCircles, (d) => d) .join( (enter) => enter .append("circle") .attr("cx", (d) => d * 10 + 5) .attr("cy", 10) .attr("r", 0) .attr("fill", "cornflowerblue") .transition() .duration(1000) .attr("r", 5), (update) => update.attr("fill", "lightgrey"), (exit) => exit.transition().duration(1000).attr("r", 0).remove() ); }, [visibleCircles]); return <svg viewBox="0 0 100 20" ref={ref} />; }; export default Transition;
μ²΄ν¬ ν¬μΈνΈ
- enter tarnsition
enter.append("circle") .attr("cx", d => d * 10 + 5) .attr("cy", 10) .attr("r", 0) .attr("fill", "cornflowerblue") .transition() .duration(1000) .attr("r", 5)
μλ‘ μμ±λλ μμ λ°μ§λ¦ 0μμ μμν΄ 1μ΄(1000ms) λμ λ°μ§λ¦ 5λ‘ μ»€μ§λ μ λλ©μ΄μ
μ μ μ©νκ³ μλ€.
- update transition
update.attr("fill", "lightgrey")
κΈ°μ‘΄ μμ μμμ μ¦μ λ³κ²½νλ€.
μ΄ μ½λκ° μλ€λ©΄ lightgreyλ‘ μμ΄ λ³νμ§ μλλ€.
- exit transition
exit.transition().duration(1000) .attr("r", 0) .remove()
μμ λ μμ 1μ΄ λμ λ°μ§λ¦μ΄ 0μΌλ‘ μ€μ΄λλ μ λλ©μ΄μ
μ μ μ©ν ν μ κ±°λλ€.
useEffect(() => { const timer = setInterval(() => { setVisibleCircles(generateCircles()); }, 2000); return () => clearInterval(timer); }, []);
μ΄
useEffect
λ μ»΄ν¬λνΈκ° λ§μ΄νΈλ λ ν λ²λ§ μ€νλλ©°, 2μ΄λ§λ€ visibleCircles
μνλ₯Ό μ
λ°μ΄νΈνλ€. (μ»΄ν¬λνΈκ° μΈλ§μ΄νΈλ λ μΈν°λ²μ μ 리νλ ν΄λ¦°μ
ν¨μλ ν¬ν¨μμΌ μνμ±μ μ κ±°νμλ€.)5.3.3 D3 shape μ’ λ₯
1. Lines

import { useEffect, useRef } from "react"; import * as d3 from "d3"; import data from './data.js' const LineShape = () => { const ref = useRef(); useEffect(() => { const width = 1000; const height = 400; const margin = { top:60, right: 40, bottom: 60, left: 60 }; const svg = d3.select(ref.current) .attr("width", width) .attr("height", height) const x = d3.scaleTime() // scaleTime: xμΆ μκ° λνλ .domain(d3.extent(data, d => d.date)) .range([margin.left, width - margin.right]); const y = d3.scaleLinear() .domain([0, d3.max(data, d => d.value)]) .range([height - margin.bottom, margin.top]); // λΌμΈ 그리기 - line λ©μλ, path μ¬μ© const line = d3.line() .x(d => x(d.date)) .y(d => y(d.value)); svg.append("path") .datum(data) .attr("fill", "none") .attr("stroke", "steelblue") .attr("stroke-width", 2) .attr("d", line); const xAxis = d3.axisBottom(x) .ticks(d3.timeMonth.every(1)) .tickSizeOuter(0) .tickFormat(d3.timeFormat("%mμ")); // νκ΅μ΄ λ³ν svg.append("g") .attr("transform", `translate(0,${height - margin.bottom})`) .call(xAxis) .append("text") // xμΆ λ μ΄λΈ ν μ€νΈ (λ²λ‘) .attr("fill", "red") .attr("x", width/2) .attr("y", margin.bottom - 10) .style("font-size", "12px") .text("μ"); svg.append("g") .attr("transform", `translate(${margin.left},0)`) .call(d3.axisLeft(y)) // yμΆ λ μ΄λΈ ν μ€νΈ (λ²λ‘) .append("text") .attr("fill", "green") .attr("x", -margin.left-1) .attr("y", height/2) .attr("text-anchor", "start") .style("font-size", "12px") .text("μΈμ μ"); // μ°¨νΈ μ λͺ© svg.append("text") .attr("x", width / 2) .attr("y", margin.top / 2 +10) .attr("text-anchor", "middle") .style("font-size", "20px") .style("font-weight", "bold") .text("μλ³ λμ λμ¬ μ μ²μ μ"); }, []); return <svg ref={ref}/> }; export default LineShape;
- scale μ μ
const x = d3.scaleTime() // scaleTime: xμΆ μκ° λνλ .domain(d3.extent(data, d => d.date)) .range([margin.left, width - margin.right]); const y = d3.scaleLinear() .domain([0, d3.max(data, d => d.value)]) .range([height - margin.bottom, margin.top]);
- μ 그리기
const line = d3.line() .x(d => x(d.date)) .y(d => y(d.value)); svg.append("path") .datum(data) .attr("fill", "none") .attr("stroke", "steelblue") .attr("stroke-width", 2) .attr("d", line);
λΌμΈ μ°¨νΈλ₯Ό λ§λ€κΈ° μν΄ μ 그리기 λ©μλμΈ lineμ μ¬μ©νμλ€.
λν x μμ±μ d.date κ°μ xμΆ scaleμ λ°λΌ λ§€ννμκ³ , y μμ±μ d.valueκ°μ yμΆ scaleμ λ°λΌ λ§€ννμλ€.
λΌμΈ μ°¨νΈλ₯Ό λ§λ€κΈ° μν΄ μ€μν path elementλ₯Ό svgμ μΆκ°νκΈ°λ νμλ€.
data λ°°μ΄μ κΈ°μ€μΌλ‘ λ°μ΄ν°λ₯Ό μ±μ λ£μκ³ , μμλλ‘ μ±μ°κΈ° μμ / μ μμ / μ λκ» μ€μ μ νμλ€.
λ§μ§λ§μΌλ‘ line λ©μλλ₯Ό μ΄μ©ν΄μ λ°μ΄ν° pointλ€μ μ°κ²° νμλ€.
- λΌμΈ μ€νμΌ
stroke
,stroke-width
,stroke-dasharray
λ±μ μ¬μ©νμ¬ λΌμΈ μ€νμΌμ λ³κ²½ν μ μλ€.
2. Bar

import { useEffect, useRef } from "react"; import * as d3 from "d3"; import data from "./data.js"; const BarShape = () => { const ref = useRef(); useEffect(() => { const width = 1000; const height = 400; const margin = { top: 50, right: 30, bottom: 40, left: 40 }; const svg = d3 .select(ref.current) .attr("width", width) .attr("height", height); // x, y μΆ μ€μΌμΌ μ μ const xScale = d3 .scaleBand() .domain(data.map((d) => d.name)) .range([margin.left, width - margin.right]) .padding(0.25); const yScale = d3 .scaleLinear() .domain([0, d3.max(data, (d) => d.value)]) .nice() .range([height - margin.bottom, margin.top]); svg.selectAll("*").remove(); // Bar μμ± svg .append("g") .attr("fill", "#5ED3F3") .selectAll("rect") .data(data) .join("rect") .attr("x", (d) => xScale(d.name)) .attr("y", (d) => yScale(d.value)) .attr("height", (d) => yScale(0) - yScale(d.value)) .attr("width", xScale.bandwidth()); // μΆ μμ± svg .append("g") .call(d3.axisLeft(yScale)) .attr("transform", `translate(${margin.left},0)`); svg .append("g") .call(d3.axisBottom(xScale).tickSizeOuter(0)) .attr("transform", `translate(0,${height - margin.bottom})`); // μ°¨νΈ μ λͺ© svg .append("text") .attr("x", width / 2) .attr("y", margin.top / 2 - 3) .attr("text-anchor", "middle") .style("font-size", "20px") .style("font-weight", "bold") .text("λΆλ¬Έλ³ μΈμ νν©"); }, [data]); return <svg ref={ref} />; }; export default BarShape;
- x,yμΆ μ€μΌμΌ μ μ
const xScale = d3 .scaleBand() // xμΆμ μ€μΌμΌμ μμ±νλ ν¨μ .domain(data.map((d) => d.name)) // xμΆμ νμν λ°μ΄ν°. map()μ μ¬μ©νμ¬ nameμ μΆμΆν λ°°μ΄μ λ°ν .range([margin.left, width - margin.right]) .padding(0.25); // λ§λμ κ°κ²©μ μ‘°μ const yScale = d3 .scaleLinear() .domain([0, d3.max(data, (d) => d.value)]) .nice() .range([height - margin.bottom, margin.top]);
- λ°μ΄ν° μ λ°μ΄νΈ λ°μ
svg.selectAll("*").remove();
μ΄μ λ°μ΄ν°λ₯Ό μ κ±°νλ μν μ ν΄μ λ°μ΄ν° μ
λ°μ΄νΈλ₯Ό λ°μνλ€.
(μ΄κ²μ΄ μλ€λ©΄ λ°μ΄ν°λ₯Ό μ
λ°μ΄νΈ νμ λ λΈλμ΄ κ²Ήμ³μ Έ 보μ΄λ κ²μ νμΈν μ μλ€.
β dataμ 맨 λ§μ§λ§ μμλ₯Ό μ§μ λ€κ° μ°¨νΈλ₯Ό μ
λ°μ΄νΈ νλ©΄μ νμΈν΄λ³΄κΈ° λ°λλ€.)
- Bar μμ±
svg .append("g") .attr("fill", "#5ED3F3") .selectAll("rect") .data(data) .join("rect") .attr("x", (d) => xScale(d.name)) .attr("y", (d) => yScale(d.value)) .attr("height", (d) => yScale(0) - yScale(d.value)) .attr("width", xScale.bandwidth());
- SVGμ κ·Έλ£Ή μμ <g> μΆκ°
- λ§λ μμ μ±μ°κΈ° μ€μ
- κ·Έλ£Ήλ΄μ λͺ¨λ <rect> μμλ₯Ό μ ν
- λ°μ΄ν° λ°°μ΄ dataλ₯Ό κ°μ Έμμ μ¬κ°νμ λ°μΈλ©
- κ° λ§λμ x μμ± μ§μ
- κ° λ§λμ y μμ± μ§μ
- κ° λ§λμ λμ΄ μ€μ
- κ° λ§λμ λλΉ μ€μ
3. Areas

import { useEffect, useRef } from "react"; import * as d3 from "d3"; import data from "./data.js"; const AreaShape = () => { const ref = useRef(); useEffect(() => { const width = 1000; const height = 500; const margin = { top: 20, right: 30, bottom: 30, left: 50 }; const svg = d3 .select(ref.current) .attr("width", width) .attr("height", height); const xScale = d3 .scaleTime() .domain(d3.extent(data, (d) => d.date)) .range([margin.left + 5, width - margin.right]); // +5λ .attr("transform", `translate(${margin.left+5},0)`)μ +5μ λ§μΆκ²μ (κ·ΈλμΌ μΌμ§μ μΌλ‘ μμ§, μνμ κ²½κ³κ° κ²ΉμΉμ§ μκ²λ¨) const yScale = d3 .scaleLinear() .domain([0, d3.max(data, (d) => d.close)]) .nice() .range([height - margin.bottom, margin.top]); const area = d3 .area() .x((d) => xScale(d.date)) .y0(yScale(0)) .y1((d) => yScale(d.close)); // μμ μ°¨νΈ κ·Έλ¦¬κΈ° svg.append("path").attr("fill", "#5ED3F3").attr("d", area(data)); // XμΆ svg .append("g") .attr("transform", `translate(0,${height - margin.bottom})`) .call( d3 .axisBottom(xScale) .ticks(d3.timeYear.every(1)) .tickSizeOuter(0) .tickFormat(d3.timeFormat("%Y")) // μ°λ λ¨μλ‘ νμ ); // YμΆ svg .append("g") .attr("transform", `translate(${margin.left + 5},0)`) .call(d3.axisLeft(yScale).ticks(6)) // YμΆ λ μ΄λΈ μΆκ° .call((g) => g.select(".domain").remove()) // YμΆ λΌμΈ μ κ±° .call((g) => g .selectAll(".tick line") .clone() .attr("x2", width - margin.left - margin.right) .attr("stroke-opacity", 0.05) ) // 보쑰μ μΆκ° .call((g) => g .append("text") .attr("x", -margin.left) .attr("y", 10) .attr("fill", "currentColor") .attr("text-anchor", "start") .text("d3 λ€μ΄λ‘λ νμ") ); // YμΆ μ λͺ© μ€μ }, []); return <svg ref={ref} />; }; export default AreaShape;
- μμ generatorλ₯Ό λ§λ λ€
const area = d3.area() .x((d) => xScale(d.date)) .y0(yScale(0)) .y1((d) => yScale(d.close));
d3.area()λ₯Ό ν΅ν΄ μμ generatorλ₯Ό λ§λ€κ³
λ°μ΄ν°μ date κ°μ xμΆ μ€μΌμΌ ν¨μ xμ λ§€ννμ¬ x μ’νλ₯Ό μ€μ νλ€.
κ·Έλ¦¬κ³ μμμ νλ¨ κ²½κ³λ₯Ό yμΆ μ€μΌμΌ ν¨μ yμ 0μ μ
λ ₯νμ¬ μ€μ νλ€.
(μμμΌ κ²½μ° μΆμ κΈ°μ€μΌλ‘ μμͺ½μΌλ‘ λ¨κ²λκ³ , μμμΌ κ²½μ° μΆμ κΈ°μ€μΌλ‘ μλμͺ½κΉμ§ νκ³ λ λ€.)
λ§μ§λ§μΌλ‘ λ°μ΄ν°μ close κ°μ yμΆ μ€μΌμΌ ν¨μ yμ λ§€ννμ¬ μμμ μλ¨ κ²½κ³λ₯Ό μ€μ νλ€.
4. Pies

import { useEffect, useRef } from "react"; import * as d3 from "d3"; import data from "./data.js"; const PieShape = () => { const ref = useRef(); useEffect(() => { // μ°¨νΈ λ©΄μ κ΅¬μ± const width = 1000; const height = 500; // SVG container μμ± const svg = d3 .select(ref.current) .attr("width", width) .attr("height", height) .attr("viewBox", [-width / 2, -height / 2, width, height]); // μμ μ²λ μμ± const color = d3 .scaleOrdinal() .domain(data.map((d) => d.name)) .range(["red", "orange", "green", "blue", "yellow"]); // μ¬μ©μ μ μ μμ λ°°μ΄ // pie layoutκ³Ό arc generator μμ± const pie = d3.pie().value((d) => d.value); const arc = d3 .arc() .innerRadius(0) .outerRadius(height / 2); const labelRadius = arc.outerRadius()() * 0.6; // labelλ³ arc generator const arcLabel = d3.arc().innerRadius(labelRadius).outerRadius(labelRadius); const arcs = pie(data); // μ΄ν© κ³μ° const total = d3.sum(data, (d) => d.value); // νμ΄ν μΆκ° svg .append("text") .attr("x", -width / 2 + 20) // x μμ±μ μ‘°μ νμ¬ μΌμͺ½μ λ°°μΉ .attr("y", -height / 2 + 20) // y μμ±μ μ‘°μ νμ¬ μλ¨μ λ°°μΉ .attr("text-anchor", "start") // μΌμͺ½ μ λ ¬ .attr("font-size", "24px") .attr("font-weight", "bold") .text("μμ μ νΈλ"); // νμ΄ μ‘°κ° μΆκ° svg .append("g") .attr("stroke", "white") .selectAll("path") .data(arcs) .join("path") .attr("fill", (d) => color(d.data.name)) .attr("d", arc) // λΌλ²¨ μΆκ° svg .append("g") .attr("text-anchor", "middle") .selectAll("text") .data(arcs) .join("text") .attr("transform", (d) => `translate(${arcLabel.centroid(d)})`) .call((text) => text .append("tspan") .attr("y", "-0.4em") .attr("font-weight", "bold") .text((d) => d.data.name) ) .call((text) => text .append("tspan") .attr("x", 0) .attr("y", "0.7em") .text((d) => `${((d.data.value / total) * 100).toFixed(2)}%`) ); }, []); return <svg ref={ref} />; }; export default PieShape;
μ°¨νΈ λ―μ΄λ³΄κΈ°
- μ°¨νΈ λ©΄μ ꡬμ±
const width = 1000; const height = 500;
- SVG 컨ν μ΄λ μμ±
const svg = d3.create("svg") .attr("width", width) .attr("height", height) .attr("viewBox", [-width / 2, -height / 2, width, height]) .attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");
- μμ μ²λ μμ±
const color = d3 .scaleOrdinal() .domain(data.map((d) => d.name)) .range(["red", "orange", "green", "blue", "yellow"]); // μ¬μ©μ μ μ μμ λ°°μ΄
- pie layoutκ³Ό arc generator μμ±
const pie = d3.pie() .sort(null) // νμ΄ μ‘°κ°μ μ λ ¬νμ§ μμ -> μμΌλ©΄ μ λ ¬λ¨ .value(d => d.value); // value μμ±μ μ¬μ©ν΄ νμ΄ μ‘°κ°μ ν¬κΈ° κ²°μ const arc = d3.arc() .innerRadius(0) .outerRadius(height/2); const labelRadius = arc.outerRadius()() * 0.6; // λΌλ²¨μ νμ΄ μ‘°κ° μ€μ¬μμ μ½κ° μμͺ½μ μμΉμν€κΈ° μν΄ μΈλΆ λ°μ§λ¦μ 60%λ‘ μ€μ
- labelλ³ arc generator
const arcLabel = d3.arc() .innerRadius(labelRadius) // λΌλ²¨μ λ΄λΆ λ°μ§λ¦ μ€μ .outerRadius(labelRadius); // λΌλ²¨ μΈλΆ λ°μ§λ¦ μ€μ const arcs = pie(data); // λ°μ΄ν°λ₯Ό λ°νμΌλ‘ νμ΄ μμ±
- μ΄ν© κ³μ°
const total = d3.sum(data, (d) => d.value);
- νμ΄ν μΆκ°
svg .append("text") .attr("x", -width / 2 + 20) // x μμ±μ μ‘°μ νμ¬ μΌμͺ½μ λ°°μΉ .attr("y", -height / 2 + 20) // y μμ±μ μ‘°μ νμ¬ μλ¨μ λ°°μΉ .attr("text-anchor", "start") // μΌμͺ½ μ λ ¬ .attr("font-size", "24px") .attr("font-weight", "bold") .text("μμ μ νΈλ");
- νμ΄ μ‘°κ° μΆκ°
svg .append("g") .attr("stroke", "white") .selectAll("path") .data(arcs) .join("path") .attr("fill", (d) => color(d.data.name)) .attr("d", arc)
- svg.append("g").attr("stroke", "white") β μλ‘μ΄ κ·Έλ£Ή μμλ₯Ό μΆκ°νκ³ , κ·Έλ£Ήμ κ²½κ³μ μ ν°μμΌλ‘ μ€μ .
- .selectAll("path").data(arcs).join("path") β arcs λ°μ΄ν°λ₯Ό μ¬μ©νμ¬ path μμλ₯Ό μμ±.
- .attr("fill", (d) => color(d.data.name)) β κ° νμ΄ μ‘°κ°μ μμμ μ€μ .
- .attr("d", arc) β κ° νμ΄ μ‘°κ°μ κ²½λ‘ λ°μ΄ν°λ₯Ό μ€μ .
- λΌλ²¨ μΆκ°
svg .append("g") .attr("text-anchor", "middle") .selectAll("text") .data(arcs) .join("text") .attr("transform", (d) => `translate(${arcLabel.centroid(d)})`) .call((text) => text .append("tspan") .attr("y", "-0.4em") .attr("font-weight", "bold") .text((d) => d.data.name) ) .call((text) => text .append("tspan") .attr("x", 0) .attr("y", "0.7em") .text((d) => `${((d.data.value / total) * 100).toFixed(2)}%`) );
- svg.append("g").attr("text-anchor", "middle") β μλ‘μ΄ κ·Έλ£Ή μμλ₯Ό μΆκ°νκ³ , ν μ€νΈ μ λ ¬μ κ°μ΄λ°λ‘ μ€μ .
- .selectAll("text").data(arcs).join("text") β arcs λ°μ΄ν°λ₯Ό μ¬μ©νμ¬ text μμλ₯Ό μμ±.
- .attr("transform", (d) =>translate(${arcLabel.centroid(d)})) β κ° λΌλ²¨μ νμ΄ μ‘°κ°μ μ€μ¬μΌλ‘ μ΄λ.
- .call((text) => text.append("tspan").attr("y", "-0.4em").attr("font-weight", "bold").text((d) => d.data.name)) β κ° λΌλ²¨μ μ΄λ¦μ μΆκ°.
- .call((text) => text.append("tspan").attr("x", 0).attr("y", "0.7em").text((d) =>${((d.data.value / total) * 100).toFixed(2)}%)) β κ° λΌλ²¨μ λ°±λΆμ¨μ μΆκ°
5.4 μ€μ μμ

import React, { useEffect, useRef } from "react"; import * as d3 from "d3"; import data from "./data.js"; const colors = [ "#FF0000", "#800000", "#FF69B4", "#FFA500", "#32CD32", "#00BFFF", "#8A2BE2", "#0000FF", "#8B4513", "#006400", ]; const ActualEx1 = () => { const ref = useRef(null); useEffect(() => { const svg = d3.select(ref.current).attr("width", 1000).attr("height", 800); svg.selectAll("*").remove(); const margin = { top: 50, right: 50, bottom: 50, left: 50 }; const width = 1000 - margin.left - margin.right; const height = 600 - margin.top - margin.bottom; svg .append("text") .attr("x", width / 2 + margin.left) .attr("y", margin.top / 2) .attr("text-anchor", "middle") .style("font-size", "24px") .text("μ£Όμ μ’ ν©λͺ° μ± TOP 10 μ¬μ©μ μΆμ΄ (2019λ ~ 2024λ )"); const chart = svg .append("g") .attr("transform", `translate(${margin.left}, ${margin.top})`); const x = d3 .scaleBand() .domain(data.map((d) => d.date)) .range([0, width]) .padding(0.1); chart .append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x)) .selectAll("text") .attr("transform", "rotate(-45)") .style("text-anchor", "end"); const y = d3.scaleLinear().domain([0, 3500]).range([height, 0]); chart.append("g").call(d3.axisLeft(y).tickValues(d3.range(0, 3600, 200))); chart .append("g") .attr("class", "grid") .call(d3.axisLeft(y).tickSize(-width).tickFormat("")) .selectAll("line") .style("opacity", 0.1); chart.selectAll(".grid .domain").remove(); const line = d3 .line() .x((d) => x(d.date) + x.bandwidth() / 2) .y((d) => y(d.value)); Object.keys(data[0]) .filter((key) => key !== "date") .forEach((key, i) => { const appData = data.map((d) => ({ date: d.date, value: d[key] })); chart .append("path") .datum(appData) .attr("fill", "none") .attr("stroke", colors[i % colors.length]) .attr("stroke-width", 2) .attr("d", line); }); const legend = svg .append("g") .attr( "transform", `translate(${width + margin.left + 10}, ${margin.top})` ); Object.keys(data[0]) .filter((key) => key !== "date") .forEach((key, i) => { const legendRow = legend .append("g") .attr("transform", `translate(-50, ${i * 20})`); legendRow .append("rect") .attr("width", 10) .attr("height", 10) .attr("fill", colors[i % colors.length]); legendRow.append("text").attr("x", 12).attr("y", 10).text(key); }); const tooltip = d3 .select("body") .append("div") .attr("class", "tooltip") .style("opacity", 0) .style("position", "absolute") .style("background-color", "white") .style("border", "solid") .style("border-width", "1px") .style("border-radius", "5px") .style("padding", "10px"); Object.keys(data[0]) .filter((key) => key !== "date") .forEach((key, i) => { chart .selectAll(`.dot-${key}`) .data(data) .enter() .append("circle") .attr("class", `dot-${key}`) .attr("cx", (d) => x(d.date) + x.bandwidth() / 2) .attr("cy", (d) => y(d[key])) .attr("r", 5) .attr("fill", colors[i % colors.length]) .on("mouseover", (evt, d) => { const date = d.date; const values = Object.keys(d) .filter((key) => key !== "date") .map( (key, index) => `<div style="display: flex; align-items: center;"><div style="width: 10px; height: 10px; background-color: ${ colors[index % colors.length] }; margin-right: 5px;"></div>${key}: ${d[key]}</div>` ) .join(""); tooltip.transition().duration(200).style("opacity", 0.9); tooltip .html(`<strong>${date}</strong><br/><br/>${values}`) .style("left", evt.pageX + 5 + "px") .style("top", evt.pageY - 28 + "px"); }) .on("mouseout", () => { tooltip.transition().duration(500).style("opacity", 0); }); }); }, [data]); return <svg ref={ref} />; }; export default ActualEx1;
- μ°¨νΈ μ λͺ© μΆκ°
svg.append("text") .attr("x", width / 2 + margin.left) .attr("y", margin.top / 2) .attr("text-anchor", "middle") .style("font-size", "24px") .text("μ£Όμ μ’ ν©λͺ° μ± TOP 10 μ¬μ©μ μΆμ΄ (2019λ ~ 2024λ )");
- X,YμΆ μ€μ
- XμΆ
β κ° μ°λλ₯Ό ꡬλΆνλ
Band Scale
μ μ¬μ©νμ¬ μ€μ νκ³ , ν μ€νΈλ₯Ό 45λ νμ μμΌ μ°λλ₯Ό νμνλ€. - YμΆ
β μ± μ¬μ©μλ₯Ό λνλ΄λ
Linear Scale
μ μ¬μ©ν©λλ€. 0μμ 3500κΉμ§ λ²μλ₯Ό μ€μ νκ³ , λκΈμ 200 λ¨μλ‘ νμνλ€.
const x = d3.scaleBand() .domain(data.map((d) => d.date)) // μ°λ λ²μ μ€μ .range([0, width]) .padding(0.1); const y = d3.scaleLinear() .domain([0, 3500]) // μ¬μ©μ μ λ²μ μ€μ .range([height, 0]); chart.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(x)); chart.append("g") .call(d3.axisLeft(y).tickValues(d3.range(0, 3600, 200)));
- Gridlines
- YμΆμ κΈ°μ€μΌλ‘ κ°λ‘μ (gridlines)μ μΆκ°νμ¬ λ°μ΄ν° νλ μ μ©μ΄νκ² λ§λ λ€.
chart.append("g") .attr("class", "grid") .call(d3.axisLeft(y).tickSize(-width).tickFormat("")) // λκΈμ μ€μ .selectAll("line").style("opacity", 0.1); // λκΈμ ν¬λͺ λ μ€μ chart.selectAll(".grid .domain").remove(); // μΆμ κΈ°λ³Έ κ²½κ³μ μ κ±°
- λ²λ‘ λ° ν΄ν μΆκ°
- κ° μ±μ μ μμμ΄ λ¬΄μμ λνλ΄λμ§ λ²λ‘λ₯Ό μΆκ°νλ€.
- rectλ‘ μμμ λνλ΄κ³ textλ‘ μ± μ΄λ¦μ νμνλ€.
- λ§μ°μ€λ₯Ό κ° λ°μ΄ν°μ μ μ¬λ¦¬λ©΄ ν΄νμ΄ λνλμ μ±μ μ¬μ©μ λ°μ΄ν°λ₯Ό νμνλ€.
- ν΄νμ divλ‘ κ΅¬νλκ³ , mouseover μ΄λ²€νΈμμ λνλλ€.
const tooltip = d3 .select("body") .append("div") .attr("class", "tooltip") .style("opacity", 0) .style("position", "absolute") .style("background-color", "white") .style("border", "solid") .style("border-width", "1px") .style("border-radius", "5px") .style("padding", "10px"); Object.keys(data[0]) .filter((key) => key !== "date") .forEach((key, i) => { chart .selectAll(`.dot-${key}`) .data(data) .enter() .append("circle") .attr("class", `dot-${key}`) .attr("cx", (d) => x(d.date) + x.bandwidth() / 2) .attr("cy", (d) => y(d[key])) .attr("r", 5) .attr("fill", colors[i % colors.length]) .on("mouseover", (evt, d) => { const date = d.date; const values = Object.keys(d) .filter((key) => key !== "date") .map( (key, index) => `<div style="display: flex; align-items: center;"><div style="width: 10px; height: 10px; background-color: ${ colors[index % colors.length] }; margin-right: 5px;"></div>${key}: ${d[key]}</div>` ) .join(""); tooltip.transition().duration(200).style("opacity", 0.9); tooltip .html(`<strong>${date}</strong><br/><br/>${values}`) .style("left", evt.pageX + 5 + "px") .style("top", evt.pageY - 28 + "px"); }) .on("mouseout", () => { tooltip.transition().duration(500).style("opacity", 0); }); });
λ λ§μΉλ©°
μμ Reactλ₯Ό μ¬μ©νλ κ²μ λͺ
μμ μΌλ‘ 보μ΄κΈ° μν΄ refλ₯Ό μ¬μ©ν μμ κ° μΌλΆ μ‘΄μ¬νλ€.
νμ§λ§ μ΄ κ³Όμ μ νλμ λνμ 그리기 μν΄ λ§μ μμ μ½λλ₯Ό νμλ‘νλ€.
리μ‘νΈ κ³΅μ λ¬Έμμλ λ€μκ³Ό κ°μ λ¬Έκ΅¬κ° μλ€.
Avoid using refs for anything that can be done declaratively.
λ°λΌμ μ°λ¦¬λ ref μ¬μ©μ μμ ν νμκ° μλ€.
React 15λΆν°λ JSX νμΌ λ΄μμ svg μμμ λν μ§μμ΄ μ΄λ£¨μ΄μ§λ―λ‘ λ€μκ³Ό κ°μ΄ μμ κ°λ¨νκ² ννν μ μμμ 보μ¬μ€ λ° μλ€.
const Svg3 = () => { return ( <> <svg> <circle cx="100" cy="80" r="50"></circle> </svg> </> ); }; export default Svg3;
μ΄λ κ² λͺ
λ Ήν λμ μ μΈνμ μ¬μ©ν¨μΌλ‘μ¨ μ½λλ₯Ό 그리λ βλ°©λ²βμ μ§μ€νκΈ°λ³΄λ€ κ·Έλ €μ§ κ²μ λν΄ λ¬μ¬νλ κ²μ μ§μ€ν μ μκ³ , μ½λμ λν κ°μμν¬ μ μλ€