浏览代码

d3 matter opensource

tobby48 6 年前
父节点
当前提交
38296d5435

+ 78
- 0
src/main/html/js/d3/Bar_Chart.html 查看文件

@@ -0,0 +1,78 @@
1
+<!-- https://falsy.me/d3-js-%eb%a5%bc-%ec%82%ac%ec%9a%a9%ed%95%98%ec%97%ac-%eb%8d%b0%ec%9d%b4%ed%84%b0-%ec%8b%9c%ea%b0%81%ed%99%94%ed%95%98%ea%b8%b0-2-bar-charts/	-->
2
+<!doctype html>
3
+<html lang="ko">
4
+  <head>
5
+    <title>D3 bar chart example</title>
6
+    <script src="https://d3js.org/d3.v5.min.js"></script>
7
+  </head>
8
+  <body>
9
+    <script>
10
+	    // line chart와 동일
11
+		const width = 500;
12
+		const height = 500;
13
+		const margin = {top: 40, left: 40, bottom: 40, right: 40};
14
+		 
15
+		const data = [
16
+		    {name: 'a', value: 10},
17
+		    {name: 'b', value: 29},
18
+		    {name: 'c', value: 32},
19
+		    {name: 'd', value: 25},
20
+		    {name: 'e', value: 23},
21
+		    {name: 'f', value: 15}
22
+		  ];
23
+		 
24
+		const x = d3.scaleBand()
25
+		  // .scaleBand() 그래프의 막대의 반복되는 범위를 정해줍니다.
26
+		  .domain(data.map(d => d.name))
27
+		  // .domain() 각각의 막대에 순서대로 막대에 매핑합니다.
28
+		  .range([margin.left, width - margin.right])
29
+		  // 시작위치와 끝 위치로 눈금의 범위를 지정합니다.
30
+		  .padding(0.2);
31
+		  // 막대의 여백을 설정합니다.
32
+		 
33
+		// line chart와 동일
34
+		const y = d3.scaleLinear()
35
+		  .domain([0, d3.max(data, d => d.value)]).nice()
36
+		    .range([height - margin.bottom, margin.top]);
37
+		 
38
+		// line chart와 동일
39
+		const xAxis = g => g
40
+		  .attr('transform', `translate(0, ${height - margin.bottom})`)
41
+		  .call(d3.axisBottom(x)
42
+		    .tickSizeOuter(0));
43
+		 
44
+		// line chart와 동일
45
+		const yAxis = g => g
46
+		  .attr('transform', `translate(${margin.left}, 0)`)
47
+		  .call(d3.axisLeft(y))
48
+		  .call(g => g.select('.domain').remove());
49
+		 
50
+		// line chart와 동일
51
+		const svg = d3.select('body').append('svg').style('width', width).style('height', height);
52
+		 
53
+		svg.append('g').call(xAxis);
54
+		svg.append('g').call(yAxis);
55
+		svg.append('g')
56
+		  .attr('fill', 'steelblue')
57
+		  .selectAll('rect').data(data).enter().append('rect')
58
+		  // .selectAll() | .select() 메서드는 해당 엘리먼트를 찾지만, 가상의 요소로 선택되기도 합니다.
59
+		  // .data() 앞에 선택된 select에 (data)배열에 Join하여 새 선택항목을 반환합니다.
60
+		  // DOM에 없는 선택된 엘리먼트에 각 데이터에 대한 자리의 노드를 반환합니다.
61
+		  // 여기까지의 코드를 간략하게 풀어보면
62
+		  // svg에 g 엘리먼트를 추가하고 그 안의 rect 엘리먼트를 찾습니다.
63
+		  // 새로 추가된 g 엘리먼트이기 때문에 그 안의 rect 엘리먼트는 없기 때문에 가상의 엘리먼트로 선택되었습니다.
64
+		  // .data(data)로 가상의 엘리먼트에 data 배열 데이터와 Join 되고
65
+		  // .enter() Join 된 데이터에 각 자리에 대한 노드를 반환하고
66
+		  // .append() 반환된 노드 데이터를 담고 react 엘리먼트를 추가합니다.
67
+		  // ex. data = [1, 2, 3, 4] 값을 가지고 있었다면 1, 2, 3, 4 데이터와 매핑된 rect 엘리먼트가 4개 추가됩니다.
68
+		  .attr('x', d => x(d.name))
69
+		  .attr('y', d => y(d.value))
70
+		  .attr('height', d => y(0) - y(d.value))
71
+		  .attr('width', x.bandwidth());
72
+		  // .bandwidth() 이름 그대로 막대기의 너비값을 응답합니다.
73
+		  // 인자값으로 명칭된 d가 svg 엘리먼트 속성 d를 의미하는 줄 알았는데, 그냥 data 값을 의미하는 듯 합니다.
74
+		 
75
+		svg.node();
76
+    </script>
77
+  </body>
78
+</html>

+ 109
- 0
src/main/html/js/d3/Bubble_Chart.html 查看文件

@@ -0,0 +1,109 @@
1
+<!-- https://bl.ocks.org/alokkshukla/3d6be4be0ef9f6977ec6718b2916d168	-->
2
+<!DOCTYPE html>
3
+<html lang="en">
4
+<head>
5
+	<meta charset="utf-8">
6
+		<title>D3: A simple packed Bubble Chart</title>
7
+		<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
8
+
9
+		<style type="text/css">
10
+			/* No style rules here yet */		
11
+		</style>
12
+</head>
13
+<body>
14
+	<script type="text/javascript">
15
+
16
+        dataset = {
17
+            "children": [{"Name":"Olives","Count":4319},
18
+                {"Name":"Tea","Count":4159},
19
+                {"Name":"Mashed Potatoes","Count":2583},
20
+                {"Name":"Boiled Potatoes","Count":2074},
21
+                {"Name":"Milk","Count":1894},
22
+                {"Name":"Chicken Salad","Count":1809},
23
+                {"Name":"Vanilla Ice Cream","Count":1713},
24
+                {"Name":"Cocoa","Count":1636},
25
+                {"Name":"Lettuce Salad","Count":1566},
26
+                {"Name":"Lobster Salad","Count":1511},
27
+                {"Name":"Chocolate","Count":1489},
28
+                {"Name":"Apple Pie","Count":1487},
29
+                {"Name":"Orange Juice","Count":1423},
30
+                {"Name":"American Cheese","Count":1372},
31
+                {"Name":"Green Peas","Count":1341},
32
+                {"Name":"Assorted Cakes","Count":1331},
33
+                {"Name":"French Fried Potatoes","Count":1328},
34
+                {"Name":"Potato Salad","Count":1306},
35
+                {"Name":"Baked Potatoes","Count":1293},
36
+                {"Name":"Roquefort","Count":1273},
37
+                {"Name":"Stewed Prunes","Count":1268}]
38
+        };
39
+
40
+        var diameter = 600;
41
+        var color = d3.scaleOrdinal(d3.schemeCategory20);
42
+
43
+        var bubble = d3.pack(dataset)
44
+            .size([diameter, diameter])
45
+            .padding(1.5);
46
+
47
+        var svg = d3.select("body")
48
+            .append("svg")
49
+            .attr("width", diameter)
50
+            .attr("height", diameter)
51
+            .attr("class", "bubble");
52
+
53
+        var nodes = d3.hierarchy(dataset)
54
+            .sum(function(d) { return d.Count; });
55
+
56
+        var node = svg.selectAll(".node")
57
+            .data(bubble(nodes).descendants())
58
+            .enter()
59
+            .filter(function(d){
60
+                return  !d.children
61
+            })
62
+            .append("g")
63
+            .attr("class", "node")
64
+            .attr("transform", function(d) {
65
+                return "translate(" + d.x + "," + d.y + ")";
66
+            });
67
+
68
+        node.append("title")
69
+            .text(function(d) {
70
+                return d.Name + ": " + d.Count;
71
+            });
72
+
73
+        node.append("circle")
74
+            .attr("r", function(d) {
75
+                return d.r;
76
+            })
77
+            .style("fill", function(d,i) {
78
+                return color(i);
79
+            });
80
+
81
+        node.append("text")
82
+            .attr("dy", ".2em")
83
+            .style("text-anchor", "middle")
84
+            .text(function(d) {
85
+                return d.data.Name.substring(0, d.r / 3);
86
+            })
87
+            .attr("font-family", "sans-serif")
88
+            .attr("font-size", function(d){
89
+                return d.r/5;
90
+            })
91
+            .attr("fill", "white");
92
+
93
+        node.append("text")
94
+            .attr("dy", "1.3em")
95
+            .style("text-anchor", "middle")
96
+            .text(function(d) {
97
+                return d.data.Count;
98
+            })
99
+            .attr("font-family",  "Gill Sans", "Gill Sans MT")
100
+            .attr("font-size", function(d){
101
+                return d.r/5;
102
+            })
103
+            .attr("fill", "white");
104
+
105
+        d3.select(self.frameElement)
106
+            .style("height", diameter + "px");
107
+	</script>
108
+</body>
109
+</html>

+ 132
- 0
src/main/html/js/d3/Force_Based_Label.html 查看文件

@@ -0,0 +1,132 @@
1
+<!-- http://bl.ocks.org/MoritzStefaner/1377729	-->
2
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
3
+<html lang="en">
4
+	<head>
5
+		<meta charset="utf-8">
6
+		<title>Force based label placement</title>
7
+		<script type="text/javascript" src="https://mbostock.github.com/d3/d3.js?2.6.0"></script>
8
+		<script type="text/javascript" src="https://mbostock.github.com/d3/d3.layout.js?2.6.0"></script>
9
+		<script type="text/javascript" src="https://mbostock.github.com/d3/d3.geom.js?2.6.0"></script>
10
+	</head>
11
+	<body>
12
+		<script type="text/javascript" charset="utf-8">
13
+			var w = 960, h = 500;
14
+
15
+			var labelDistance = 0;
16
+
17
+			var vis = d3.select("body").append("svg:svg").attr("width", w).attr("height", h);
18
+
19
+			var nodes = [];
20
+			var labelAnchors = [];
21
+			var labelAnchorLinks = [];
22
+			var links = [];
23
+
24
+			for(var i = 0; i < 30; i++) {
25
+				var node = {
26
+					label : "node " + i
27
+				};
28
+				nodes.push(node);
29
+				labelAnchors.push({
30
+					node : node
31
+				});
32
+				labelAnchors.push({
33
+					node : node
34
+				});
35
+			};
36
+
37
+			for(var i = 0; i < nodes.length; i++) {
38
+				for(var j = 0; j < i; j++) {
39
+					if(Math.random() > .95)
40
+						links.push({
41
+							source : i,
42
+							target : j,
43
+							weight : Math.random()
44
+						});
45
+				}
46
+				labelAnchorLinks.push({
47
+					source : i * 2,
48
+					target : i * 2 + 1,
49
+					weight : 1
50
+				});
51
+			};
52
+
53
+			var force = d3.layout.force().size([w, h]).nodes(nodes).links(links).gravity(1).linkDistance(50).charge(-3000).linkStrength(function(x) {
54
+				return x.weight * 10
55
+			});
56
+
57
+
58
+			force.start();
59
+
60
+			var force2 = d3.layout.force().nodes(labelAnchors).links(labelAnchorLinks).gravity(0).linkDistance(0).linkStrength(8).charge(-100).size([w, h]);
61
+			force2.start();
62
+
63
+			var link = vis.selectAll("line.link").data(links).enter().append("svg:line").attr("class", "link").style("stroke", "#CCC");
64
+
65
+			var node = vis.selectAll("g.node").data(force.nodes()).enter().append("svg:g").attr("class", "node");
66
+			node.append("svg:circle").attr("r", 5).style("fill", "#555").style("stroke", "#FFF").style("stroke-width", 3);
67
+			node.call(force.drag);
68
+
69
+
70
+			var anchorLink = vis.selectAll("line.anchorLink").data(labelAnchorLinks)//.enter().append("svg:line").attr("class", "anchorLink").style("stroke", "#999");
71
+
72
+			var anchorNode = vis.selectAll("g.anchorNode").data(force2.nodes()).enter().append("svg:g").attr("class", "anchorNode");
73
+			anchorNode.append("svg:circle").attr("r", 0).style("fill", "#FFF");
74
+				anchorNode.append("svg:text").text(function(d, i) {
75
+				return i % 2 == 0 ? "" : d.node.label
76
+			}).style("fill", "#555").style("font-family", "Arial").style("font-size", 12);
77
+
78
+			var updateLink = function() {
79
+				this.attr("x1", function(d) {
80
+					return d.source.x;
81
+				}).attr("y1", function(d) {
82
+					return d.source.y;
83
+				}).attr("x2", function(d) {
84
+					return d.target.x;
85
+				}).attr("y2", function(d) {
86
+					return d.target.y;
87
+				});
88
+
89
+			}
90
+
91
+			var updateNode = function() {
92
+				this.attr("transform", function(d) {
93
+					return "translate(" + d.x + "," + d.y + ")";
94
+				});
95
+
96
+			}
97
+
98
+			force.on("tick", function() {
99
+
100
+				force2.start();
101
+
102
+				node.call(updateNode);
103
+
104
+				anchorNode.each(function(d, i) {
105
+					if(i % 2 == 0) {
106
+						d.x = d.node.x;
107
+						d.y = d.node.y;
108
+					} else {
109
+						var b = this.childNodes[1].getBBox();
110
+
111
+						var diffX = d.x - d.node.x;
112
+						var diffY = d.y - d.node.y;
113
+
114
+						var dist = Math.sqrt(diffX * diffX + diffY * diffY);
115
+
116
+						var shiftX = b.width * (diffX - dist) / (dist * 2);
117
+						shiftX = Math.max(-b.width, Math.min(0, shiftX));
118
+						var shiftY = 5;
119
+						this.childNodes[1].setAttribute("transform", "translate(" + shiftX + "," + shiftY + ")");
120
+					}
121
+				});
122
+
123
+				anchorNode.call(updateNode);
124
+
125
+				link.call(updateLink);
126
+				anchorLink.call(updateLink);
127
+
128
+			});
129
+
130
+		</script>
131
+	</body>
132
+</html>

+ 109
- 0
src/main/html/js/d3/Force_Directed_Graph.html 查看文件

@@ -0,0 +1,109 @@
1
+<!-- https://bl.ocks.org/heybignick/3faf257bbbbc7743bb72310d03b86ee8	-->
2
+<!DOCTYPE html>
3
+<meta charset="utf-8">
4
+<style>
5
+
6
+.links line {
7
+  stroke: #999;
8
+  stroke-opacity: 0.6;
9
+}
10
+
11
+.nodes circle {
12
+  stroke: #fff;
13
+  stroke-width: 1.5px;
14
+}
15
+
16
+text {
17
+  font-family: sans-serif;
18
+  font-size: 10px;
19
+}
20
+
21
+</style>
22
+<svg width="960" height="600"></svg>
23
+<script src="https://d3js.org/d3.v4.min.js"></script>
24
+<script>
25
+
26
+var svg = d3.select("svg"),
27
+    width = +svg.attr("width"),
28
+    height = +svg.attr("height");
29
+
30
+var color = d3.scaleOrdinal(d3.schemeCategory20);
31
+
32
+var simulation = d3.forceSimulation()
33
+    .force("link", d3.forceLink().id(function(d) { return d.id; }))
34
+    .force("charge", d3.forceManyBody())
35
+    .force("center", d3.forceCenter(width / 2, height / 2));
36
+
37
+d3.json("miserables.json", function(error, graph) {
38
+  if (error) throw error;
39
+
40
+  var link = svg.append("g")
41
+      .attr("class", "links")
42
+    .selectAll("line")
43
+    .data(graph.links)
44
+    .enter().append("line")
45
+      .attr("stroke-width", function(d) { return Math.sqrt(d.value); });
46
+
47
+  var node = svg.append("g")
48
+      .attr("class", "nodes")
49
+    .selectAll("g")
50
+    .data(graph.nodes)
51
+    .enter().append("g")
52
+    
53
+  var circles = node.append("circle")
54
+      .attr("r", 5)
55
+      .attr("fill", function(d) { return color(d.group); })
56
+      .call(d3.drag()
57
+          .on("start", dragstarted)
58
+          .on("drag", dragged)
59
+          .on("end", dragended));
60
+
61
+  var lables = node.append("text")
62
+      .text(function(d) {
63
+        return d.id;
64
+      })
65
+      .attr('x', 6)
66
+      .attr('y', 3);
67
+
68
+  node.append("title")
69
+      .text(function(d) { return d.id; });
70
+
71
+  simulation
72
+      .nodes(graph.nodes)
73
+      .on("tick", ticked);
74
+
75
+  simulation.force("link")
76
+      .links(graph.links);
77
+
78
+  function ticked() {
79
+    link
80
+        .attr("x1", function(d) { return d.source.x; })
81
+        .attr("y1", function(d) { return d.source.y; })
82
+        .attr("x2", function(d) { return d.target.x; })
83
+        .attr("y2", function(d) { return d.target.y; });
84
+
85
+    node
86
+        .attr("transform", function(d) {
87
+          return "translate(" + d.x + "," + d.y + ")";
88
+        })
89
+  }
90
+});
91
+
92
+function dragstarted(d) {
93
+  if (!d3.event.active) simulation.alphaTarget(0.3).restart();
94
+  d.fx = d.x;
95
+  d.fy = d.y;
96
+}
97
+
98
+function dragged(d) {
99
+  d.fx = d3.event.x;
100
+  d.fy = d3.event.y;
101
+}
102
+
103
+function dragended(d) {
104
+  if (!d3.event.active) simulation.alphaTarget(0);
105
+  d.fx = null;
106
+  d.fy = null;
107
+}
108
+
109
+</script>

+ 69
- 0
src/main/html/js/d3/Line_Chart.html 查看文件

@@ -0,0 +1,69 @@
1
+<!-- https://falsy.me/d3-js-%eb%a5%bc-%ec%82%ac%ec%9a%a9%ed%95%98%ec%97%ac-%eb%8d%b0%ec%9d%b4%ed%84%b0-%ec%8b%9c%ea%b0%81%ed%99%94%ed%95%98%ea%b8%b0-1-line-charts/	-->
2
+<!doctype html>
3
+<html lang="ko">
4
+  <head>
5
+    <title>D3 line chart example</title>
6
+    <script src="https://d3js.org/d3.v5.min.js"></script>
7
+  </head>
8
+  <body>
9
+    <script>
10
+    	// line-chart.js
11
+		const width = 500;
12
+		const height = 500;
13
+		const margin = {top: 40, right: 40, bottom: 40, left: 40};
14
+		const data = [
15
+		  {date: new Date('2018-01-01'), value: 10},
16
+		  {date: new Date('2018-01-02'), value: 20},
17
+		  {date: new Date('2018-01-03'), value: 30},
18
+		  {date: new Date('2018-01-04'), value: 25},
19
+		  {date: new Date('2018-01-05'), value: 35},
20
+		  {date: new Date('2018-01-06'), value: 45},
21
+		  {date: new Date('2018-01-07'), value: 60},
22
+		  {date: new Date('2018-01-08'), value: 50}
23
+		];
24
+		 
25
+		const x = d3.scaleTime()
26
+		  .domain(d3.extent(data, d => d.date))
27
+		  .range([margin.left, width - margin.right]);
28
+		 
29
+		const y = d3.scaleLinear()
30
+		  .domain([0, d3.max(data, d => d.value)]).nice()
31
+		  .range([height - margin.bottom, margin.top]);
32
+		 
33
+		const xAxis = g => g
34
+		  .attr("transform", `translate(0,${height - margin.bottom})`)
35
+		  .call(d3.axisBottom(x).ticks(width / 90).tickSizeOuter(0));
36
+		 
37
+		const yAxis = g => g
38
+		  .attr("transform", `translate(${margin.left},0)`)
39
+		  .call(d3.axisLeft(y))
40
+		  .call(g => g.select(".domain").remove())
41
+		  .call(g => {
42
+		    return g.select(".tick:last-of-type text").clone()
43
+		      .attr("x", 3)
44
+		      .attr("text-anchor", "start")
45
+		      .attr("font-weight", "bold")
46
+		      .attr("font-size", '20px')
47
+		      .text('Y축')
48
+		    });
49
+		 
50
+		const line = d3.line()
51
+		  .defined(d => !isNaN(d.value))
52
+		  .x(d => x(d.date))
53
+		  .y(d => y(d.value));
54
+		 
55
+		const svg = d3.select('body').append('svg').style('width', width).style('height', height);
56
+		svg.append("path")
57
+		  .datum(data)
58
+		  .attr("fill", "none")
59
+		  .attr("stroke", "steelblue")
60
+		  .attr("stroke-width", 1)
61
+		  .attr("stroke-linejoin", "round")
62
+		  .attr("stroke-linecap", "round")
63
+		  .attr("d", line);
64
+		svg.append('g').call(xAxis);
65
+		svg.append('g').call(yAxis);
66
+		svg.node();
67
+    </script>
68
+  </body>
69
+</html>

+ 95
- 0
src/main/html/js/d3/Liquid_Fill_Gauge.html 查看文件

@@ -0,0 +1,95 @@
1
+<!-- http://bl.ocks.org/brattonc/5e5ce9beee483220e2f6	-->
2
+<!DOCTYPE html>
3
+<html>
4
+<head lang="en">
5
+    <meta charset="UTF-8">
6
+    <title></title>
7
+    <script src="https://d3js.org/d3.v3.min.js" language="JavaScript"></script>
8
+    <script src="liquidFillGauge.js" language="JavaScript"></script>
9
+    <style>
10
+        .liquidFillGaugeText { font-family: Helvetica; font-weight: bold; }
11
+    </style>
12
+</head>
13
+<body>
14
+<svg id="fillgauge1" width="97%" height="250" onclick="gauge1.update(NewValue());"></svg>
15
+<svg id="fillgauge2" width="19%" height="200" onclick="gauge2.update(NewValue());"></svg>
16
+<svg id="fillgauge3" width="19%" height="200" onclick="gauge3.update(NewValue());"></svg>
17
+<svg id="fillgauge4" width="19%" height="200" onclick="gauge4.update(NewValue());"></svg>
18
+<svg id="fillgauge5" width="19%" height="200" onclick="gauge5.update(NewValue());"></svg>
19
+<svg id="fillgauge6" width="19%" height="200" onclick="gauge6.update(NewValue());"></svg>
20
+<script language="JavaScript">
21
+    var gauge1 = loadLiquidFillGauge("fillgauge1", 55);
22
+    var config1 = liquidFillGaugeDefaultSettings();
23
+    config1.circleColor = "#FF7777";
24
+    config1.textColor = "#FF4444";
25
+    config1.waveTextColor = "#FFAAAA";
26
+    config1.waveColor = "#FFDDDD";
27
+    config1.circleThickness = 0.2;
28
+    config1.textVertPosition = 0.2;
29
+    config1.waveAnimateTime = 1000;
30
+    var gauge2= loadLiquidFillGauge("fillgauge2", 28, config1);
31
+    var config2 = liquidFillGaugeDefaultSettings();
32
+    config2.circleColor = "#D4AB6A";
33
+    config2.textColor = "#553300";
34
+    config2.waveTextColor = "#805615";
35
+    config2.waveColor = "#AA7D39";
36
+    config2.circleThickness = 0.1;
37
+    config2.circleFillGap = 0.2;
38
+    config2.textVertPosition = 0.8;
39
+    config2.waveAnimateTime = 2000;
40
+    config2.waveHeight = 0.3;
41
+    config2.waveCount = 1;
42
+    var gauge3 = loadLiquidFillGauge("fillgauge3", 60.1, config2);
43
+    var config3 = liquidFillGaugeDefaultSettings();
44
+    config3.textVertPosition = 0.8;
45
+    config3.waveAnimateTime = 5000;
46
+    config3.waveHeight = 0.15;
47
+    config3.waveAnimate = false;
48
+    config3.waveOffset = 0.25;
49
+    config3.valueCountUp = false;
50
+    config3.displayPercent = false;
51
+    var gauge4 = loadLiquidFillGauge("fillgauge4", 50, config3);
52
+    var config4 = liquidFillGaugeDefaultSettings();
53
+    config4.circleThickness = 0.15;
54
+    config4.circleColor = "#808015";
55
+    config4.textColor = "#555500";
56
+    config4.waveTextColor = "#FFFFAA";
57
+    config4.waveColor = "#AAAA39";
58
+    config4.textVertPosition = 0.8;
59
+    config4.waveAnimateTime = 1000;
60
+    config4.waveHeight = 0.05;
61
+    config4.waveAnimate = true;
62
+    config4.waveRise = false;
63
+    config4.waveHeightScaling = false;
64
+    config4.waveOffset = 0.25;
65
+    config4.textSize = 0.75;
66
+    config4.waveCount = 3;
67
+    var gauge5 = loadLiquidFillGauge("fillgauge5", 60.44, config4);
68
+    var config5 = liquidFillGaugeDefaultSettings();
69
+    config5.circleThickness = 0.4;
70
+    config5.circleColor = "#6DA398";
71
+    config5.textColor = "#0E5144";
72
+    config5.waveTextColor = "#6DA398";
73
+    config5.waveColor = "#246D5F";
74
+    config5.textVertPosition = 0.52;
75
+    config5.waveAnimateTime = 5000;
76
+    config5.waveHeight = 0;
77
+    config5.waveAnimate = false;
78
+    config5.waveCount = 2;
79
+    config5.waveOffset = 0.25;
80
+    config5.textSize = 1.2;
81
+    config5.minValue = 30;
82
+    config5.maxValue = 150
83
+    config5.displayPercent = false;
84
+    var gauge6 = loadLiquidFillGauge("fillgauge6", 120, config5);
85
+
86
+    function NewValue(){
87
+        if(Math.random() > .5){
88
+            return Math.round(Math.random()*100);
89
+        } else {
90
+            return (Math.random()*100).toFixed(1);
91
+        }
92
+    }
93
+</script>
94
+</body>
95
+</html>

+ 90
- 0
src/main/html/js/d3/Pie_Chart.html 查看文件

@@ -0,0 +1,90 @@
1
+<!-- https://falsy.me/d3-js-%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%8B%9C%EA%B0%81%ED%99%94%ED%95%98%EA%B8%B0-3-pie-charts/	-->
2
+<!doctype html>
3
+<html lang="ko">
4
+  <head>
5
+    <title>D3 bar chart example</title>
6
+    <script src="https://d3js.org/d3.v5.min.js"></script>
7
+  </head>
8
+  <body>
9
+    <script>
10
+    	const width = 500;
11
+		const height = 500;
12
+		const data = [
13
+		  {name: 'A', value: 1000, color: '#efa86b'},
14
+		  {name: 'B', value: 1500, color: '#c1484f'},
15
+		  {name: 'C', value: 1300, color: '#d35d50'},
16
+		  {name: 'D', value: 900, color: '#f4c17c'},
17
+		  {name: 'E', value: 400, color: '#fae8a4'},
18
+		  {name: 'F', value: 1200, color: '#df7454'},
19
+		  {name: 'G', value: 1100, color: '#e88d5d'},
20
+		  {name: 'H', value: 600, color: '#f8d690'}
21
+		];
22
+		 
23
+		const arc = d3.arc().innerRadius(150).outerRadius(Math.min(width, height) / 2);
24
+		// .arc() 새로운 기본값의 아치(호) 생성
25
+		// .innerRadius() 안쪽 반지름 값, 0이면 완전한 원이되고 값이 있으면 도넛 형태가 됩니다.
26
+		// .outerRadius() 바깥쪽 반지름값
27
+		 
28
+		const arcLabel = (() => {
29
+		  const radius = Math.min(width, height) / 2 * 0.8;
30
+		  return d3.arc().innerRadius(radius).outerRadius(radius);
31
+		})();
32
+		// 라벨이 위치할 반지름 값을 설정합니다.
33
+		 
34
+		const pie = d3.pie()
35
+		  // 새로운 기본값의 파이 모양의 생성
36
+		  .sort((a, b) => b.value - a.value)
37
+		  // data의 value 큰값 > 작은값 순으로 정렬합니다. ex. 반대 순서는 a.value - b.value
38
+		  .value(d => d.value);
39
+		 
40
+		const arcs = pie(data);
41
+		 
42
+		const svg = d3.select('body').append('svg').style('width', width).style('height', height)
43
+		  .attr('text-anchor', 'middle')
44
+		  // text-anchor 텍스트의 정렬을 설정합니다 ( start | middle | end | inherit )
45
+		  .style('font-size', '12px sans-serif');
46
+		 
47
+		const g = svg.append('g')
48
+		  .attr('transform', `translate(${width/2}, ${height/2})`);
49
+		  // 우선 차트를 그릴 그룹 엘리먼트를 추가합니다.
50
+		  // 위치값을 각각 2로 나누는건 반지름 값을 기준으로 한바퀴 돌며 path를 그리기 때문인거 같습니다.
51
+		 
52
+		g.selectAll('path')
53
+		  .data(arcs)
54
+		  .enter().append('path')
55
+		  // 이전과 동일하게 가상 path 요소를 만들고 그래프 데이터와 매핑하여 엘리먼트를 추가합니다.
56
+		    .attr('fill', d => d.data.color)
57
+		    // 다른 그래프와 다르게 .data 라는 객체가 추가되어 있는데, 위에 arcs 변수를 선언할때
58
+		    // .pie(data)가 {data, value, index, startAngle, endAngle, padAngle} 의 값을 가지고 있습니다.
59
+		    .attr('stroke', 'white')
60
+		    .attr('d', arc)
61
+		  .append('title')
62
+		    .text(d => `${d.data.name}: ${d.data.value}`);
63
+		    // 각각 페스의 자식으로 title의 엘리먼트에 텍스트로 출력합니다.
64
+		    // 실제로 뷰에 출력되지는 않지만 시멘틱하게 각각의 요소의 설명 문자열을 제공합니다.
65
+		 
66
+		const text = g.selectAll('text')
67
+		  .data(arcs)
68
+		  .enter().append('text')
69
+		    .attr('transform', d => `translate(${arcLabel.centroid(d)})`)
70
+		    .attr('dy', '0.35em');
71
+		  // 라벨을 취가하기 위한 text 엘리먼트를 만들고 위치를 지정합니다.
72
+		 
73
+		text.append('tspan')
74
+		  .attr('x', 0)
75
+		  .attr('y', '-0.7em')
76
+		  .style('font-weight', 'bold')
77
+		  .text(d => d.data.name)
78
+		  // 해당 데이터 항목의 이름을 두꺼운 글씨로 출력합니다. ex. A
79
+		 
80
+		text.filter(d => (d.endAngle - d.startAngle > 0.25)).append('tspan')
81
+		  .attr('x', 0)
82
+		  .attr('y', '0.7em')
83
+		  .attr('fill-opacity', 0.7)
84
+		  .text(d => d.data.value);
85
+		  // 해당 데이터의 수치값을 투명도를 주어 출력합니다. ex. 1000
86
+		 
87
+		svg.node();
88
+    </script>
89
+  </body>
90
+</html>

+ 81
- 0
src/main/html/js/d3/Word_Cloud_v1.html 查看文件

@@ -0,0 +1,81 @@
1
+<!-- https://bl.ocks.org/wnghdcjfe/de87a75a9445c88061ad50edc1d4ac8d	-->
2
+<html>
3
+
4
+<head>
5
+    <meta charset="utf-8" />
6
+</head>
7
+<style>
8
+
9
+</style>
10
+
11
+<body>
12
+    <script src="https://d3js.org/d3.v3.min.js"></script>
13
+    <script src="https://rawgit.com/jasondavies/d3-cloud/master/build/d3.layout.cloud.js" type="text/JavaScript"></script>
14
+    <script>
15
+        var width = 960,
16
+            height = 500
17
+
18
+        var svg = d3.select("body").append("svg")
19
+            .attr("width", width)
20
+            .attr("height", height);
21
+        d3.csv("worddata.csv", function (data) {
22
+            showCloud(data)
23
+            setInterval(function(){
24
+                 showCloud(data)
25
+            },2000) 
26
+        });
27
+        //scale.linear: 선형적인 스케일로 표준화를 시킨다. 
28
+        //domain: 데이터의 범위, 입력 크기
29
+        //range: 표시할 범위, 출력 크기 
30
+        //clamp: domain의 범위를 넘어간 값에 대하여 domain의 최대값으로 고정시킨다.
31
+        wordScale = d3.scale.linear().domain([0, 100]).range([0, 150]).clamp(true);
32
+        var keywords = ["자리야", "트레이서", "한조"]
33
+        var svg = d3.select("svg")
34
+                    .append("g")
35
+                    .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
36
+
37
+        function showCloud(data) {
38
+            d3.layout.cloud().size([width, height])
39
+                //클라우드 레이아웃에 데이터 전달
40
+                .words(data)
41
+                .rotate(function (d) {
42
+                    return d.text.length > 3 ? 0 : 90;
43
+                })
44
+                //스케일로 각 단어의 크기를 설정
45
+                .fontSize(function (d) {
46
+                    return wordScale(d.frequency);
47
+                })
48
+                //클라우드 레이아웃을 초기화 > end이벤트 발생 > 연결된 함수 작동  
49
+                .on("end", draw)
50
+                .start();
51
+
52
+            function draw(words) { 
53
+                var cloud = svg.selectAll("text").data(words)
54
+                //Entering words
55
+                cloud.enter()
56
+                    .append("text")
57
+                    .style("font-family", "overwatch")
58
+                    .style("fill", function (d) {
59
+                        return (keywords.indexOf(d.text) > -1 ? "#fbc280" : "#405275");
60
+                    })
61
+                    .style("fill-opacity", .5)
62
+                    .attr("text-anchor", "middle") 
63
+                    .attr('font-size', 1)
64
+                    .text(function (d) {
65
+                        return d.text;
66
+                    }); 
67
+                cloud
68
+                    .transition()
69
+                    .duration(600)
70
+                    .style("font-size", function (d) {
71
+                        return d.size + "px";
72
+                    })
73
+                    .attr("transform", function (d) {
74
+                        return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
75
+                    })
76
+                    .style("fill-opacity", 1); 
77
+            }
78
+        }
79
+    </script>
80
+</body>
81
+</html>

+ 268
- 0
src/main/html/js/d3/liquidFillGauge.js 查看文件

@@ -0,0 +1,268 @@
1
+/*!
2
+ * @license Open source under BSD 2-clause (http://choosealicense.com/licenses/bsd-2-clause/)
3
+ * Copyright (c) 2015, Curtis Bratton
4
+ * All rights reserved.
5
+ *
6
+ * Liquid Fill Gauge v1.1
7
+ */
8
+function liquidFillGaugeDefaultSettings(){
9
+    return {
10
+        minValue: 0, // The gauge minimum value.
11
+        maxValue: 100, // The gauge maximum value.
12
+        circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
13
+        circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
14
+        circleColor: "#178BCA", // The color of the outer circle.
15
+        waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
16
+        waveCount: 1, // The number of full waves per width of the wave circle.
17
+        waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
18
+        waveAnimateTime: 18000, // The amount of time in milliseconds for a full wave to enter the wave circle.
19
+        waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
20
+        waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
21
+        waveAnimate: true, // Controls if the wave scrolls or is static.
22
+        waveColor: "#178BCA", // The color of the fill wave.
23
+        waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
24
+        textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
25
+        textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
26
+        valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
27
+        displayPercent: true, // If true, a % symbol is displayed after the value.
28
+        textColor: "#045681", // The color of the value text when the wave does not overlap it.
29
+        waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
30
+    };
31
+}
32
+
33
+function loadLiquidFillGauge(elementId, value, config) {
34
+    if(config == null) config = liquidFillGaugeDefaultSettings();
35
+
36
+    var gauge = d3.select("#" + elementId);
37
+    var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
38
+    var locationX = parseInt(gauge.style("width"))/2 - radius;
39
+    var locationY = parseInt(gauge.style("height"))/2 - radius;
40
+    var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
41
+
42
+    var waveHeightScale;
43
+    if(config.waveHeightScaling){
44
+        waveHeightScale = d3.scale.linear()
45
+            .range([0,config.waveHeight,0])
46
+            .domain([0,50,100]);
47
+    } else {
48
+        waveHeightScale = d3.scale.linear()
49
+            .range([config.waveHeight,config.waveHeight])
50
+            .domain([0,100]);
51
+    }
52
+
53
+    var textPixels = (config.textSize*radius/2);
54
+    var textFinalValue = parseFloat(value).toFixed(2);
55
+    var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
56
+    var percentText = config.displayPercent?"%":"";
57
+    var circleThickness = config.circleThickness * radius;
58
+    var circleFillGap = config.circleFillGap * radius;
59
+    var fillCircleMargin = circleThickness + circleFillGap;
60
+    var fillCircleRadius = radius - fillCircleMargin;
61
+    var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
62
+
63
+    var waveLength = fillCircleRadius*2/config.waveCount;
64
+    var waveClipCount = 1+config.waveCount;
65
+    var waveClipWidth = waveLength*waveClipCount;
66
+
67
+    // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
68
+    var textRounder = function(value){ return Math.round(value); };
69
+    if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
70
+        textRounder = function(value){ return parseFloat(value).toFixed(1); };
71
+    }
72
+    if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
73
+        textRounder = function(value){ return parseFloat(value).toFixed(2); };
74
+    }
75
+
76
+    // Data for building the clip wave area.
77
+    var data = [];
78
+    for(var i = 0; i <= 40*waveClipCount; i++){
79
+        data.push({x: i/(40*waveClipCount), y: (i/(40))});
80
+    }
81
+
82
+    // Scales for drawing the outer circle.
83
+    var gaugeCircleX = d3.scale.linear().range([0,2*Math.PI]).domain([0,1]);
84
+    var gaugeCircleY = d3.scale.linear().range([0,radius]).domain([0,radius]);
85
+
86
+    // Scales for controlling the size of the clipping path.
87
+    var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
88
+    var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
89
+
90
+    // Scales for controlling the position of the clipping path.
91
+    var waveRiseScale = d3.scale.linear()
92
+        // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
93
+        // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
94
+        // circle at 100%.
95
+        .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
96
+        .domain([0,1]);
97
+    var waveAnimateScale = d3.scale.linear()
98
+        .range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
99
+        .domain([0,1]);
100
+
101
+    // Scale for controlling the position of the text within the gauge.
102
+    var textRiseScaleY = d3.scale.linear()
103
+        .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
104
+        .domain([0,1]);
105
+
106
+    // Center the gauge within the parent SVG.
107
+    var gaugeGroup = gauge.append("g")
108
+        .attr('transform','translate('+locationX+','+locationY+')');
109
+
110
+    // Draw the outer circle.
111
+    var gaugeCircleArc = d3.svg.arc()
112
+        .startAngle(gaugeCircleX(0))
113
+        .endAngle(gaugeCircleX(1))
114
+        .outerRadius(gaugeCircleY(radius))
115
+        .innerRadius(gaugeCircleY(radius-circleThickness));
116
+    gaugeGroup.append("path")
117
+        .attr("d", gaugeCircleArc)
118
+        .style("fill", config.circleColor)
119
+        .attr('transform','translate('+radius+','+radius+')');
120
+
121
+    // Text where the wave does not overlap.
122
+    var text1 = gaugeGroup.append("text")
123
+        .text(textRounder(textStartValue) + percentText)
124
+        .attr("class", "liquidFillGaugeText")
125
+        .attr("text-anchor", "middle")
126
+        .attr("font-size", textPixels + "px")
127
+        .style("fill", config.textColor)
128
+        .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');
129
+
130
+    // The clipping wave area.
131
+    var clipArea = d3.svg.area()
132
+        .x(function(d) { return waveScaleX(d.x); } )
133
+        .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
134
+        .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
135
+    var waveGroup = gaugeGroup.append("defs")
136
+        .append("clipPath")
137
+        .attr("id", "clipWave" + elementId);
138
+    var wave = waveGroup.append("path")
139
+        .datum(data)
140
+        .attr("d", clipArea)
141
+        .attr("T", 0);
142
+
143
+    // The inner circle with the clipping wave attached.
144
+    var fillCircleGroup = gaugeGroup.append("g")
145
+        .attr("clip-path", "url(#clipWave" + elementId + ")");
146
+    fillCircleGroup.append("circle")
147
+        .attr("cx", radius)
148
+        .attr("cy", radius)
149
+        .attr("r", fillCircleRadius)
150
+        .style("fill", config.waveColor);
151
+
152
+    // Text where the wave does overlap.
153
+    var text2 = fillCircleGroup.append("text")
154
+        .text(textRounder(textStartValue) + percentText)
155
+        .attr("class", "liquidFillGaugeText")
156
+        .attr("text-anchor", "middle")
157
+        .attr("font-size", textPixels + "px")
158
+        .style("fill", config.waveTextColor)
159
+        .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');
160
+
161
+    // Make the value count up.
162
+    if(config.valueCountUp){
163
+        var textTween = function(){
164
+            var i = d3.interpolate(this.textContent, textFinalValue);
165
+            return function(t) { this.textContent = textRounder(i(t)) + percentText; }
166
+        };
167
+        text1.transition()
168
+            .duration(config.waveRiseTime)
169
+            .tween("text", textTween);
170
+        text2.transition()
171
+            .duration(config.waveRiseTime)
172
+            .tween("text", textTween);
173
+    }
174
+
175
+    // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
176
+    var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
177
+    if(config.waveRise){
178
+        waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
179
+            .transition()
180
+            .duration(config.waveRiseTime)
181
+            .attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
182
+            .each("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
183
+    } else {
184
+        waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
185
+    }
186
+
187
+    if(config.waveAnimate) animateWave();
188
+
189
+    function animateWave() {
190
+        wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
191
+        wave.transition()
192
+            .duration(config.waveAnimateTime * (1-wave.attr('T')))
193
+            .ease('linear')
194
+            .attr('transform','translate('+waveAnimateScale(1)+',0)')
195
+            .attr('T', 1)
196
+            .each('end', function(){
197
+                wave.attr('T', 0);
198
+                animateWave(config.waveAnimateTime);
199
+            });
200
+    }
201
+
202
+    function GaugeUpdater(){
203
+        this.update = function(value){
204
+            var newFinalValue = parseFloat(value).toFixed(2);
205
+            var textRounderUpdater = function(value){ return Math.round(value); };
206
+            if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
207
+                textRounderUpdater = function(value){ return parseFloat(value).toFixed(1); };
208
+            }
209
+            if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
210
+                textRounderUpdater = function(value){ return parseFloat(value).toFixed(2); };
211
+            }
212
+
213
+            var textTween = function(){
214
+                var i = d3.interpolate(this.textContent, parseFloat(value).toFixed(2));
215
+                return function(t) { this.textContent = textRounderUpdater(i(t)) + percentText; }
216
+            };
217
+
218
+            text1.transition()
219
+                .duration(config.waveRiseTime)
220
+                .tween("text", textTween);
221
+            text2.transition()
222
+                .duration(config.waveRiseTime)
223
+                .tween("text", textTween);
224
+
225
+            var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
226
+            var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
227
+            var waveRiseScale = d3.scale.linear()
228
+                // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
229
+                // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
230
+                // circle at 100%.
231
+                .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
232
+                .domain([0,1]);
233
+            var newHeight = waveRiseScale(fillPercent);
234
+            var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
235
+            var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
236
+            var newClipArea;
237
+            if(config.waveHeightScaling){
238
+                newClipArea = d3.svg.area()
239
+                    .x(function(d) { return waveScaleX(d.x); } )
240
+                    .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
241
+                    .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
242
+            } else {
243
+                newClipArea = clipArea;
244
+            }
245
+
246
+            var newWavePosition = config.waveAnimate?waveAnimateScale(1):0;
247
+            wave.transition()
248
+                .duration(0)
249
+                .transition()
250
+                .duration(config.waveAnimate?(config.waveAnimateTime * (1-wave.attr('T'))):(config.waveRiseTime))
251
+                .ease('linear')
252
+                .attr('d', newClipArea)
253
+                .attr('transform','translate('+newWavePosition+',0)')
254
+                .attr('T','1')
255
+                .each("end", function(){
256
+                    if(config.waveAnimate){
257
+                        wave.attr('transform','translate('+waveAnimateScale(0)+',0)');
258
+                        animateWave(config.waveAnimateTime);
259
+                    }
260
+                });
261
+            waveGroup.transition()
262
+                .duration(config.waveRiseTime)
263
+                .attr('transform','translate('+waveGroupXPosition+','+newHeight+')')
264
+        }
265
+    }
266
+
267
+    return new GaugeUpdater();
268
+}

+ 337
- 0
src/main/html/js/d3/miserables.json 查看文件

@@ -0,0 +1,337 @@
1
+{
2
+  "nodes": [
3
+    {"id": "Myriel", "group": 1},
4
+    {"id": "Napoleon", "group": 1},
5
+    {"id": "Mlle.Baptistine", "group": 1},
6
+    {"id": "Mme.Magloire", "group": 1},
7
+    {"id": "CountessdeLo", "group": 1},
8
+    {"id": "Geborand", "group": 1},
9
+    {"id": "Champtercier", "group": 1},
10
+    {"id": "Cravatte", "group": 1},
11
+    {"id": "Count", "group": 1},
12
+    {"id": "OldMan", "group": 1},
13
+    {"id": "Labarre", "group": 2},
14
+    {"id": "Valjean", "group": 2},
15
+    {"id": "Marguerite", "group": 3},
16
+    {"id": "Mme.deR", "group": 2},
17
+    {"id": "Isabeau", "group": 2},
18
+    {"id": "Gervais", "group": 2},
19
+    {"id": "Tholomyes", "group": 3},
20
+    {"id": "Listolier", "group": 3},
21
+    {"id": "Fameuil", "group": 3},
22
+    {"id": "Blacheville", "group": 3},
23
+    {"id": "Favourite", "group": 3},
24
+    {"id": "Dahlia", "group": 3},
25
+    {"id": "Zephine", "group": 3},
26
+    {"id": "Fantine", "group": 3},
27
+    {"id": "Mme.Thenardier", "group": 4},
28
+    {"id": "Thenardier", "group": 4},
29
+    {"id": "Cosette", "group": 5},
30
+    {"id": "Javert", "group": 4},
31
+    {"id": "Fauchelevent", "group": 0},
32
+    {"id": "Bamatabois", "group": 2},
33
+    {"id": "Perpetue", "group": 3},
34
+    {"id": "Simplice", "group": 2},
35
+    {"id": "Scaufflaire", "group": 2},
36
+    {"id": "Woman1", "group": 2},
37
+    {"id": "Judge", "group": 2},
38
+    {"id": "Champmathieu", "group": 2},
39
+    {"id": "Brevet", "group": 2},
40
+    {"id": "Chenildieu", "group": 2},
41
+    {"id": "Cochepaille", "group": 2},
42
+    {"id": "Pontmercy", "group": 4},
43
+    {"id": "Boulatruelle", "group": 6},
44
+    {"id": "Eponine", "group": 4},
45
+    {"id": "Anzelma", "group": 4},
46
+    {"id": "Woman2", "group": 5},
47
+    {"id": "MotherInnocent", "group": 0},
48
+    {"id": "Gribier", "group": 0},
49
+    {"id": "Jondrette", "group": 7},
50
+    {"id": "Mme.Burgon", "group": 7},
51
+    {"id": "Gavroche", "group": 8},
52
+    {"id": "Gillenormand", "group": 5},
53
+    {"id": "Magnon", "group": 5},
54
+    {"id": "Mlle.Gillenormand", "group": 5},
55
+    {"id": "Mme.Pontmercy", "group": 5},
56
+    {"id": "Mlle.Vaubois", "group": 5},
57
+    {"id": "Lt.Gillenormand", "group": 5},
58
+    {"id": "Marius", "group": 8},
59
+    {"id": "BaronessT", "group": 5},
60
+    {"id": "Mabeuf", "group": 8},
61
+    {"id": "Enjolras", "group": 8},
62
+    {"id": "Combeferre", "group": 8},
63
+    {"id": "Prouvaire", "group": 8},
64
+    {"id": "Feuilly", "group": 8},
65
+    {"id": "Courfeyrac", "group": 8},
66
+    {"id": "Bahorel", "group": 8},
67
+    {"id": "Bossuet", "group": 8},
68
+    {"id": "Joly", "group": 8},
69
+    {"id": "Grantaire", "group": 8},
70
+    {"id": "MotherPlutarch", "group": 9},
71
+    {"id": "Gueulemer", "group": 4},
72
+    {"id": "Babet", "group": 4},
73
+    {"id": "Claquesous", "group": 4},
74
+    {"id": "Montparnasse", "group": 4},
75
+    {"id": "Toussaint", "group": 5},
76
+    {"id": "Child1", "group": 10},
77
+    {"id": "Child2", "group": 10},
78
+    {"id": "Brujon", "group": 4},
79
+    {"id": "Mme.Hucheloup", "group": 8}
80
+  ],
81
+  "links": [
82
+    {"source": "Napoleon", "target": "Myriel", "value": 1},
83
+    {"source": "Mlle.Baptistine", "target": "Myriel", "value": 8},
84
+    {"source": "Mme.Magloire", "target": "Myriel", "value": 10},
85
+    {"source": "Mme.Magloire", "target": "Mlle.Baptistine", "value": 6},
86
+    {"source": "CountessdeLo", "target": "Myriel", "value": 1},
87
+    {"source": "Geborand", "target": "Myriel", "value": 1},
88
+    {"source": "Champtercier", "target": "Myriel", "value": 1},
89
+    {"source": "Cravatte", "target": "Myriel", "value": 1},
90
+    {"source": "Count", "target": "Myriel", "value": 2},
91
+    {"source": "OldMan", "target": "Myriel", "value": 1},
92
+    {"source": "Valjean", "target": "Labarre", "value": 1},
93
+    {"source": "Valjean", "target": "Mme.Magloire", "value": 3},
94
+    {"source": "Valjean", "target": "Mlle.Baptistine", "value": 3},
95
+    {"source": "Valjean", "target": "Myriel", "value": 5},
96
+    {"source": "Marguerite", "target": "Valjean", "value": 1},
97
+    {"source": "Mme.deR", "target": "Valjean", "value": 1},
98
+    {"source": "Isabeau", "target": "Valjean", "value": 1},
99
+    {"source": "Gervais", "target": "Valjean", "value": 1},
100
+    {"source": "Listolier", "target": "Tholomyes", "value": 4},
101
+    {"source": "Fameuil", "target": "Tholomyes", "value": 4},
102
+    {"source": "Fameuil", "target": "Listolier", "value": 4},
103
+    {"source": "Blacheville", "target": "Tholomyes", "value": 4},
104
+    {"source": "Blacheville", "target": "Listolier", "value": 4},
105
+    {"source": "Blacheville", "target": "Fameuil", "value": 4},
106
+    {"source": "Favourite", "target": "Tholomyes", "value": 3},
107
+    {"source": "Favourite", "target": "Listolier", "value": 3},
108
+    {"source": "Favourite", "target": "Fameuil", "value": 3},
109
+    {"source": "Favourite", "target": "Blacheville", "value": 4},
110
+    {"source": "Dahlia", "target": "Tholomyes", "value": 3},
111
+    {"source": "Dahlia", "target": "Listolier", "value": 3},
112
+    {"source": "Dahlia", "target": "Fameuil", "value": 3},
113
+    {"source": "Dahlia", "target": "Blacheville", "value": 3},
114
+    {"source": "Dahlia", "target": "Favourite", "value": 5},
115
+    {"source": "Zephine", "target": "Tholomyes", "value": 3},
116
+    {"source": "Zephine", "target": "Listolier", "value": 3},
117
+    {"source": "Zephine", "target": "Fameuil", "value": 3},
118
+    {"source": "Zephine", "target": "Blacheville", "value": 3},
119
+    {"source": "Zephine", "target": "Favourite", "value": 4},
120
+    {"source": "Zephine", "target": "Dahlia", "value": 4},
121
+    {"source": "Fantine", "target": "Tholomyes", "value": 3},
122
+    {"source": "Fantine", "target": "Listolier", "value": 3},
123
+    {"source": "Fantine", "target": "Fameuil", "value": 3},
124
+    {"source": "Fantine", "target": "Blacheville", "value": 3},
125
+    {"source": "Fantine", "target": "Favourite", "value": 4},
126
+    {"source": "Fantine", "target": "Dahlia", "value": 4},
127
+    {"source": "Fantine", "target": "Zephine", "value": 4},
128
+    {"source": "Fantine", "target": "Marguerite", "value": 2},
129
+    {"source": "Fantine", "target": "Valjean", "value": 9},
130
+    {"source": "Mme.Thenardier", "target": "Fantine", "value": 2},
131
+    {"source": "Mme.Thenardier", "target": "Valjean", "value": 7},
132
+    {"source": "Thenardier", "target": "Mme.Thenardier", "value": 13},
133
+    {"source": "Thenardier", "target": "Fantine", "value": 1},
134
+    {"source": "Thenardier", "target": "Valjean", "value": 12},
135
+    {"source": "Cosette", "target": "Mme.Thenardier", "value": 4},
136
+    {"source": "Cosette", "target": "Valjean", "value": 31},
137
+    {"source": "Cosette", "target": "Tholomyes", "value": 1},
138
+    {"source": "Cosette", "target": "Thenardier", "value": 1},
139
+    {"source": "Javert", "target": "Valjean", "value": 17},
140
+    {"source": "Javert", "target": "Fantine", "value": 5},
141
+    {"source": "Javert", "target": "Thenardier", "value": 5},
142
+    {"source": "Javert", "target": "Mme.Thenardier", "value": 1},
143
+    {"source": "Javert", "target": "Cosette", "value": 1},
144
+    {"source": "Fauchelevent", "target": "Valjean", "value": 8},
145
+    {"source": "Fauchelevent", "target": "Javert", "value": 1},
146
+    {"source": "Bamatabois", "target": "Fantine", "value": 1},
147
+    {"source": "Bamatabois", "target": "Javert", "value": 1},
148
+    {"source": "Bamatabois", "target": "Valjean", "value": 2},
149
+    {"source": "Perpetue", "target": "Fantine", "value": 1},
150
+    {"source": "Simplice", "target": "Perpetue", "value": 2},
151
+    {"source": "Simplice", "target": "Valjean", "value": 3},
152
+    {"source": "Simplice", "target": "Fantine", "value": 2},
153
+    {"source": "Simplice", "target": "Javert", "value": 1},
154
+    {"source": "Scaufflaire", "target": "Valjean", "value": 1},
155
+    {"source": "Woman1", "target": "Valjean", "value": 2},
156
+    {"source": "Woman1", "target": "Javert", "value": 1},
157
+    {"source": "Judge", "target": "Valjean", "value": 3},
158
+    {"source": "Judge", "target": "Bamatabois", "value": 2},
159
+    {"source": "Champmathieu", "target": "Valjean", "value": 3},
160
+    {"source": "Champmathieu", "target": "Judge", "value": 3},
161
+    {"source": "Champmathieu", "target": "Bamatabois", "value": 2},
162
+    {"source": "Brevet", "target": "Judge", "value": 2},
163
+    {"source": "Brevet", "target": "Champmathieu", "value": 2},
164
+    {"source": "Brevet", "target": "Valjean", "value": 2},
165
+    {"source": "Brevet", "target": "Bamatabois", "value": 1},
166
+    {"source": "Chenildieu", "target": "Judge", "value": 2},
167
+    {"source": "Chenildieu", "target": "Champmathieu", "value": 2},
168
+    {"source": "Chenildieu", "target": "Brevet", "value": 2},
169
+    {"source": "Chenildieu", "target": "Valjean", "value": 2},
170
+    {"source": "Chenildieu", "target": "Bamatabois", "value": 1},
171
+    {"source": "Cochepaille", "target": "Judge", "value": 2},
172
+    {"source": "Cochepaille", "target": "Champmathieu", "value": 2},
173
+    {"source": "Cochepaille", "target": "Brevet", "value": 2},
174
+    {"source": "Cochepaille", "target": "Chenildieu", "value": 2},
175
+    {"source": "Cochepaille", "target": "Valjean", "value": 2},
176
+    {"source": "Cochepaille", "target": "Bamatabois", "value": 1},
177
+    {"source": "Pontmercy", "target": "Thenardier", "value": 1},
178
+    {"source": "Boulatruelle", "target": "Thenardier", "value": 1},
179
+    {"source": "Eponine", "target": "Mme.Thenardier", "value": 2},
180
+    {"source": "Eponine", "target": "Thenardier", "value": 3},
181
+    {"source": "Anzelma", "target": "Eponine", "value": 2},
182
+    {"source": "Anzelma", "target": "Thenardier", "value": 2},
183
+    {"source": "Anzelma", "target": "Mme.Thenardier", "value": 1},
184
+    {"source": "Woman2", "target": "Valjean", "value": 3},
185
+    {"source": "Woman2", "target": "Cosette", "value": 1},
186
+    {"source": "Woman2", "target": "Javert", "value": 1},
187
+    {"source": "MotherInnocent", "target": "Fauchelevent", "value": 3},
188
+    {"source": "MotherInnocent", "target": "Valjean", "value": 1},
189
+    {"source": "Gribier", "target": "Fauchelevent", "value": 2},
190
+    {"source": "Mme.Burgon", "target": "Jondrette", "value": 1},
191
+    {"source": "Gavroche", "target": "Mme.Burgon", "value": 2},
192
+    {"source": "Gavroche", "target": "Thenardier", "value": 1},
193
+    {"source": "Gavroche", "target": "Javert", "value": 1},
194
+    {"source": "Gavroche", "target": "Valjean", "value": 1},
195
+    {"source": "Gillenormand", "target": "Cosette", "value": 3},
196
+    {"source": "Gillenormand", "target": "Valjean", "value": 2},
197
+    {"source": "Magnon", "target": "Gillenormand", "value": 1},
198
+    {"source": "Magnon", "target": "Mme.Thenardier", "value": 1},
199
+    {"source": "Mlle.Gillenormand", "target": "Gillenormand", "value": 9},
200
+    {"source": "Mlle.Gillenormand", "target": "Cosette", "value": 2},
201
+    {"source": "Mlle.Gillenormand", "target": "Valjean", "value": 2},
202
+    {"source": "Mme.Pontmercy", "target": "Mlle.Gillenormand", "value": 1},
203
+    {"source": "Mme.Pontmercy", "target": "Pontmercy", "value": 1},
204
+    {"source": "Mlle.Vaubois", "target": "Mlle.Gillenormand", "value": 1},
205
+    {"source": "Lt.Gillenormand", "target": "Mlle.Gillenormand", "value": 2},
206
+    {"source": "Lt.Gillenormand", "target": "Gillenormand", "value": 1},
207
+    {"source": "Lt.Gillenormand", "target": "Cosette", "value": 1},
208
+    {"source": "Marius", "target": "Mlle.Gillenormand", "value": 6},
209
+    {"source": "Marius", "target": "Gillenormand", "value": 12},
210
+    {"source": "Marius", "target": "Pontmercy", "value": 1},
211
+    {"source": "Marius", "target": "Lt.Gillenormand", "value": 1},
212
+    {"source": "Marius", "target": "Cosette", "value": 21},
213
+    {"source": "Marius", "target": "Valjean", "value": 19},
214
+    {"source": "Marius", "target": "Tholomyes", "value": 1},
215
+    {"source": "Marius", "target": "Thenardier", "value": 2},
216
+    {"source": "Marius", "target": "Eponine", "value": 5},
217
+    {"source": "Marius", "target": "Gavroche", "value": 4},
218
+    {"source": "BaronessT", "target": "Gillenormand", "value": 1},
219
+    {"source": "BaronessT", "target": "Marius", "value": 1},
220
+    {"source": "Mabeuf", "target": "Marius", "value": 1},
221
+    {"source": "Mabeuf", "target": "Eponine", "value": 1},
222
+    {"source": "Mabeuf", "target": "Gavroche", "value": 1},
223
+    {"source": "Enjolras", "target": "Marius", "value": 7},
224
+    {"source": "Enjolras", "target": "Gavroche", "value": 7},
225
+    {"source": "Enjolras", "target": "Javert", "value": 6},
226
+    {"source": "Enjolras", "target": "Mabeuf", "value": 1},
227
+    {"source": "Enjolras", "target": "Valjean", "value": 4},
228
+    {"source": "Combeferre", "target": "Enjolras", "value": 15},
229
+    {"source": "Combeferre", "target": "Marius", "value": 5},
230
+    {"source": "Combeferre", "target": "Gavroche", "value": 6},
231
+    {"source": "Combeferre", "target": "Mabeuf", "value": 2},
232
+    {"source": "Prouvaire", "target": "Gavroche", "value": 1},
233
+    {"source": "Prouvaire", "target": "Enjolras", "value": 4},
234
+    {"source": "Prouvaire", "target": "Combeferre", "value": 2},
235
+    {"source": "Feuilly", "target": "Gavroche", "value": 2},
236
+    {"source": "Feuilly", "target": "Enjolras", "value": 6},
237
+    {"source": "Feuilly", "target": "Prouvaire", "value": 2},
238
+    {"source": "Feuilly", "target": "Combeferre", "value": 5},
239
+    {"source": "Feuilly", "target": "Mabeuf", "value": 1},
240
+    {"source": "Feuilly", "target": "Marius", "value": 1},
241
+    {"source": "Courfeyrac", "target": "Marius", "value": 9},
242
+    {"source": "Courfeyrac", "target": "Enjolras", "value": 17},
243
+    {"source": "Courfeyrac", "target": "Combeferre", "value": 13},
244
+    {"source": "Courfeyrac", "target": "Gavroche", "value": 7},
245
+    {"source": "Courfeyrac", "target": "Mabeuf", "value": 2},
246
+    {"source": "Courfeyrac", "target": "Eponine", "value": 1},
247
+    {"source": "Courfeyrac", "target": "Feuilly", "value": 6},
248
+    {"source": "Courfeyrac", "target": "Prouvaire", "value": 3},
249
+    {"source": "Bahorel", "target": "Combeferre", "value": 5},
250
+    {"source": "Bahorel", "target": "Gavroche", "value": 5},
251
+    {"source": "Bahorel", "target": "Courfeyrac", "value": 6},
252
+    {"source": "Bahorel", "target": "Mabeuf", "value": 2},
253
+    {"source": "Bahorel", "target": "Enjolras", "value": 4},
254
+    {"source": "Bahorel", "target": "Feuilly", "value": 3},
255
+    {"source": "Bahorel", "target": "Prouvaire", "value": 2},
256
+    {"source": "Bahorel", "target": "Marius", "value": 1},
257
+    {"source": "Bossuet", "target": "Marius", "value": 5},
258
+    {"source": "Bossuet", "target": "Courfeyrac", "value": 12},
259
+    {"source": "Bossuet", "target": "Gavroche", "value": 5},
260
+    {"source": "Bossuet", "target": "Bahorel", "value": 4},
261
+    {"source": "Bossuet", "target": "Enjolras", "value": 10},
262
+    {"source": "Bossuet", "target": "Feuilly", "value": 6},
263
+    {"source": "Bossuet", "target": "Prouvaire", "value": 2},
264
+    {"source": "Bossuet", "target": "Combeferre", "value": 9},
265
+    {"source": "Bossuet", "target": "Mabeuf", "value": 1},
266
+    {"source": "Bossuet", "target": "Valjean", "value": 1},
267
+    {"source": "Joly", "target": "Bahorel", "value": 5},
268
+    {"source": "Joly", "target": "Bossuet", "value": 7},
269
+    {"source": "Joly", "target": "Gavroche", "value": 3},
270
+    {"source": "Joly", "target": "Courfeyrac", "value": 5},
271
+    {"source": "Joly", "target": "Enjolras", "value": 5},
272
+    {"source": "Joly", "target": "Feuilly", "value": 5},
273
+    {"source": "Joly", "target": "Prouvaire", "value": 2},
274
+    {"source": "Joly", "target": "Combeferre", "value": 5},
275
+    {"source": "Joly", "target": "Mabeuf", "value": 1},
276
+    {"source": "Joly", "target": "Marius", "value": 2},
277
+    {"source": "Grantaire", "target": "Bossuet", "value": 3},
278
+    {"source": "Grantaire", "target": "Enjolras", "value": 3},
279
+    {"source": "Grantaire", "target": "Combeferre", "value": 1},
280
+    {"source": "Grantaire", "target": "Courfeyrac", "value": 2},
281
+    {"source": "Grantaire", "target": "Joly", "value": 2},
282
+    {"source": "Grantaire", "target": "Gavroche", "value": 1},
283
+    {"source": "Grantaire", "target": "Bahorel", "value": 1},
284
+    {"source": "Grantaire", "target": "Feuilly", "value": 1},
285
+    {"source": "Grantaire", "target": "Prouvaire", "value": 1},
286
+    {"source": "MotherPlutarch", "target": "Mabeuf", "value": 3},
287
+    {"source": "Gueulemer", "target": "Thenardier", "value": 5},
288
+    {"source": "Gueulemer", "target": "Valjean", "value": 1},
289
+    {"source": "Gueulemer", "target": "Mme.Thenardier", "value": 1},
290
+    {"source": "Gueulemer", "target": "Javert", "value": 1},
291
+    {"source": "Gueulemer", "target": "Gavroche", "value": 1},
292
+    {"source": "Gueulemer", "target": "Eponine", "value": 1},
293
+    {"source": "Babet", "target": "Thenardier", "value": 6},
294
+    {"source": "Babet", "target": "Gueulemer", "value": 6},
295
+    {"source": "Babet", "target": "Valjean", "value": 1},
296
+    {"source": "Babet", "target": "Mme.Thenardier", "value": 1},
297
+    {"source": "Babet", "target": "Javert", "value": 2},
298
+    {"source": "Babet", "target": "Gavroche", "value": 1},
299
+    {"source": "Babet", "target": "Eponine", "value": 1},
300
+    {"source": "Claquesous", "target": "Thenardier", "value": 4},
301
+    {"source": "Claquesous", "target": "Babet", "value": 4},
302
+    {"source": "Claquesous", "target": "Gueulemer", "value": 4},
303
+    {"source": "Claquesous", "target": "Valjean", "value": 1},
304
+    {"source": "Claquesous", "target": "Mme.Thenardier", "value": 1},
305
+    {"source": "Claquesous", "target": "Javert", "value": 1},
306
+    {"source": "Claquesous", "target": "Eponine", "value": 1},
307
+    {"source": "Claquesous", "target": "Enjolras", "value": 1},
308
+    {"source": "Montparnasse", "target": "Javert", "value": 1},
309
+    {"source": "Montparnasse", "target": "Babet", "value": 2},
310
+    {"source": "Montparnasse", "target": "Gueulemer", "value": 2},
311
+    {"source": "Montparnasse", "target": "Claquesous", "value": 2},
312
+    {"source": "Montparnasse", "target": "Valjean", "value": 1},
313
+    {"source": "Montparnasse", "target": "Gavroche", "value": 1},
314
+    {"source": "Montparnasse", "target": "Eponine", "value": 1},
315
+    {"source": "Montparnasse", "target": "Thenardier", "value": 1},
316
+    {"source": "Toussaint", "target": "Cosette", "value": 2},
317
+    {"source": "Toussaint", "target": "Javert", "value": 1},
318
+    {"source": "Toussaint", "target": "Valjean", "value": 1},
319
+    {"source": "Child1", "target": "Gavroche", "value": 2},
320
+    {"source": "Child2", "target": "Gavroche", "value": 2},
321
+    {"source": "Child2", "target": "Child1", "value": 3},
322
+    {"source": "Brujon", "target": "Babet", "value": 3},
323
+    {"source": "Brujon", "target": "Gueulemer", "value": 3},
324
+    {"source": "Brujon", "target": "Thenardier", "value": 3},
325
+    {"source": "Brujon", "target": "Gavroche", "value": 1},
326
+    {"source": "Brujon", "target": "Eponine", "value": 1},
327
+    {"source": "Brujon", "target": "Claquesous", "value": 1},
328
+    {"source": "Brujon", "target": "Montparnasse", "value": 1},
329
+    {"source": "Mme.Hucheloup", "target": "Bossuet", "value": 1},
330
+    {"source": "Mme.Hucheloup", "target": "Joly", "value": 1},
331
+    {"source": "Mme.Hucheloup", "target": "Grantaire", "value": 1},
332
+    {"source": "Mme.Hucheloup", "target": "Bahorel", "value": 1},
333
+    {"source": "Mme.Hucheloup", "target": "Courfeyrac", "value": 1},
334
+    {"source": "Mme.Hucheloup", "target": "Gavroche", "value": 1},
335
+    {"source": "Mme.Hucheloup", "target": "Enjolras", "value": 1}
336
+  ]
337
+}

+ 19
- 0
src/main/html/js/d3/worddata.csv 查看文件

@@ -0,0 +1,19 @@
1
+text,frequency
2
+자리야,80
3
+트레이서,100
4
+한조,20
5
+솔져,60
6
+시메트라,50
7
+윈스턴,30
8
+라인하르트,30 
9
+아나,20
10
+맥크리,20
11
+메르시,20
12
+박종선,20
13
+연제호,20
14
+최주원,20
15
+윤성용,20
16
+양영주,20
17
+준바,20
18
+로드호그,20
19
+정크랫,20

+ 111
- 0
src/main/html/js/matter/Matter-js.html 查看文件

@@ -0,0 +1,111 @@
1
+<!-- https://falsy.me/%ec%9b%b9%ec%97%90%ec%84%9c-2d-%eb%ac%bc%eb%a6%ac%ec%97%94%ec%a7%84-%ec%82%ac%ec%9a%a9%ed%95%98%ea%b8%b0-1-matter-js-%ec%8b%9c%ec%9e%91%ed%95%98%ea%b8%b0/	-->
2
+<!doctype html>
3
+<html lang="ko">
4
+  <head>
5
+    <title>2D Physical Engine example</title>
6
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.12.0/matter.min.js"></script>
7
+  </head>
8
+  <body>
9
+    <script>
10
+		// 윈도우(브라우저)의 크기를 변수에 담습니다.
11
+	    const windowHeight = window.innerHeight;
12
+		const windowWidth = window.innerWidth;
13
+		
14
+		// Matter-js
15
+		const Engine = Matter.Engine;
16
+		const Render = Matter.Render;
17
+		const World = Matter.World;
18
+		const Bodies = Matter.Bodies;
19
+		const Events = Matter.Events;
20
+		// 이 밖에도
21
+		// console.log(Matter); 를 찍어보면 알 수 있듯이
22
+		// Composite, Constraint, Mouse ... 등 많은 기능들이 있습니다.
23
+		
24
+		// 엔진을 생성합니다.
25
+		const engine = Engine.create();
26
+		const world = engine.world;
27
+		engine.world.gravity.y = 0.1;
28
+		
29
+		// engine.world.gravity 는 중력?? 이라고 해야하나 y축으로 떨어지는 속도를 정합니다.
30
+		// 0 보다 작으면 오브제들이 위로 올라갑니다.
31
+		// engine.world.gravity.x 속성도 있어서 오른쪽 또는 왼쪽으로 쏠리게 할 수도 있습니다.
32
+ 
33
+		// Bodies에서 오브제를 만듭니다.
34
+		// 이름 그대로 circle은 원, rectangle은 사각형을 만듭니다.
35
+		// 자세한 정보는 공식 홈페이지를 참고하는게 더 좋을 것 같지만
36
+		// Bodies.circle(x축 위치값, y축 위치값, 반지름값, 옵션);
37
+		// Bodies.rectangle(x축 위치값, y축 위치값, 너비값, 높이값, 옵션);
38
+		// 입니다.
39
+		const circle = Bodies.circle(windowWidth/2, 50, 10, {
40
+			friction: 0.2,
41
+		    // 단어 그대로 마찰력 값입니다. (0~1)
42
+		    restitution: 0.8,
43
+		    // 단어 그대로 복원력 입니다. (0~1)
44
+		    render: {
45
+		    	fillStyle: '#000',
46
+				trokeStyle: '#000',
47
+				lineWidth: 1
48
+			}
49
+			// fillStyle 은 채워질 색, strokeStyle은 선색, lineWidth 선 굵기
50
+		});
51
+		const ground = Bodies.rectangle(windowWidth/2, windowHeight-50, windowWidth/3, 10, {
52
+			isStatic: true
53
+			// 고정된 위치의 오브젝트
54
+		});
55
+		
56
+		// World에 위 오브젝트들을 추가합니다.
57
+		World.add(engine.world, [circle, ground]);
58
+		
59
+		// 1초 간격으로
60
+		setInterval(() => {
61
+			// 가로 중앙, 상단에서 50px 떨어진 곳에 반지름이 10인 원을 만들어서
62
+			const box = Bodies.circle(windowWidth/2, 50, 10, {
63
+				friction: 0.2,
64
+				restitution: 0.8
65
+			});
66
+			// 공간에 추가합니다.
67
+			World.add(engine.world, box);
68
+		}, 1000);
69
+		
70
+		
71
+		// 렌더를 생성합니다.
72
+		const render = Render.create({
73
+			element: document.body,
74
+			engine: engine,
75
+			options: {
76
+				width: windowWidth,
77
+				height: windowHeight,
78
+				// 기본값은 wireFrames 값이 ture이고 false를 주지 않으면 
79
+				// 위에 작성한 Bodies.circle의 render 옵션이 적용되지 않습니다.
80
+				wireframes: false,
81
+				background: '#fff'
82
+			}
83
+		});
84
+		
85
+		// 몇가지 이벤트를 추가합니다.
86
+		// render 객체의 canvas는 DOM body에 추가 될 canvas 엘리먼트를 가리킵니다.
87
+		render.canvas.addEventListener("click", (e) => {
88
+			// 캔버스를 마우스로 클릭하면
89
+			// 현재 마우스 위치에 반지름이 10인 원을 만들어서
90
+			const box = Bodies.circle(e.offsetX, e.offsetY, 10, {
91
+				friction: 0.2,
92
+				restitution: 0.8
93
+			});
94
+			// 공간에 추가합니다.
95
+			World.add(engine.world, box);
96
+		}, false);
97
+		
98
+		// 엔진을 실행하고
99
+		Engine.run(engine);
100
+		// 렌더를 실행합니다.
101
+		Render.run(render);
102
+		
103
+		// 아까 위에 선언한 Matter에서 지정되어 있는 이벤트 인데요.
104
+		// 그중에 'collisionStart'은 오브제 간 충돌의 시작한 시점의 이벤트입니다.
105
+		Events.on(engine, "collisionStart", (event) => {
106
+			// 오브젝트들이 충돌하면 console.log를 출력합니다.
107
+			console.log('collision');
108
+		});
109
+    </script>
110
+  </body>
111
+</html>