├── README.md
├── index.qmd
├── .Rbuildignore
├── .DS_Store
├── style
├── akbar.woff
├── edav3.scss
├── joyce.theme
└── joyce_temp.R
├── .gitignore
├── sandbox
└── notes.txt
├── statapps.Rproj
├── DESCRIPTION
├── .github
└── workflows
│ └── quartobook.yml
├── _quarto.yml
├── clustering.qmd
├── clustering-simdata.qmd
└── scripts
├── kmeans.js
└── kmeans-simdata.js
/README.md:
--------------------------------------------------------------------------------
1 | # statapps
2 |
--------------------------------------------------------------------------------
/index.qmd:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
--------------------------------------------------------------------------------
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^.*\.Rproj$
2 | ^\.Rproj\.user$
3 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtr13/statapps/main/.DS_Store
--------------------------------------------------------------------------------
/style/akbar.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtr13/statapps/main/style/akbar.woff
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | .Ruserdata
5 |
6 | /.quarto/
7 | docs/
8 | _site/
9 | d3.v7.js
10 |
--------------------------------------------------------------------------------
/sandbox/notes.txt:
--------------------------------------------------------------------------------
1 | https://stackoverflow.com/questions/63029805/circle-leaving-trails-while-transition-in-d3-js
2 |
3 | https://stackoverflow.com/questions/32544283/scheduling-a-timeout-on-mousemove
4 |
--------------------------------------------------------------------------------
/statapps.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 | ProjectId: 87c0b275-8c2c-4706-be8c-19cd526564f9
3 |
4 | RestoreWorkspace: Default
5 | SaveWorkspace: Default
6 | AlwaysSaveHistory: Default
7 |
8 | EnableCodeIndexing: Yes
9 | UseSpacesForTab: Yes
10 | NumSpacesForTab: 2
11 | Encoding: UTF-8
12 |
13 | RnwWeave: knitr
14 | LaTeX: XeLaTeX
15 |
16 | AutoAppendNewline: Yes
17 | StripTrailingWhitespace: Yes
18 |
19 | BuildType: None
20 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: edav3
2 | Title: What the Package Does (One Line, Title Case)
3 | Version: 0.0.0.9000
4 | Authors@R:
5 | person(given = "First",
6 | family = "Last",
7 | role = c("aut", "cre"),
8 | email = "first.last@example.com",
9 | comment = c(ORCID = "YOUR-ORCID-ID"))
10 | Description: What the package does (one paragraph).
11 | License: `use_mit_license()`, `use_gpl3_license()` or friends to
12 | pick a license
13 | Encoding: UTF-8
14 | LazyData: true
15 | Roxygen: list(markdown = TRUE)
16 | RoxygenNote: 7.1.1
17 | Imports:
18 | ggthemes,
19 | knitr,
20 | rmarkdown,
21 | tidyverse
22 |
--------------------------------------------------------------------------------
/.github/workflows/quartobook.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | schedule:
6 | # run every Saturday at 8am (12 UTC)
7 | - cron: '0 12 * * 6'
8 |
9 | name: quarto
10 |
11 | jobs:
12 | build-deploy:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v3
16 |
17 | - name: Install Quarto
18 | uses: quarto-dev/quarto-actions/setup@v2
19 |
20 | - name: setup R
21 | uses: r-lib/actions/setup-r@v2
22 |
23 | - name: setup dependencies
24 | uses: r-lib/actions/setup-r-dependencies@v2
25 |
26 | - name: Deploy 🚀
27 | uses: quarto-dev/quarto-actions/publish@v2
28 | with:
29 | target: gh-pages
30 |
--------------------------------------------------------------------------------
/_quarto.yml:
--------------------------------------------------------------------------------
1 | project:
2 | type: website
3 |
4 | website:
5 | title: "statapps"
6 | navbar:
7 | left:
8 | - href: index.qmd
9 | text: Home
10 | - clustering.qmd
11 | - clustering-simdata.qmd
12 | tools:
13 | - icon: twitter
14 | href: https://twitter.com/
15 | - icon: github
16 | href: https://github.com/jtr13/statapps
17 | page-footer:
18 | right: "Built with [Quarto](https://quarto.org/)"
19 | left: "© Copyright 2024, Joyce Robbins"
20 |
21 | format:
22 | html:
23 | theme: [cerulean, style/edav3.scss]
24 | code-link: true
25 | highlight-style: style/joyce.theme
26 | callout-icon: false
27 | fig-width: 4
28 | fig-height: 3
29 |
30 | knitr:
31 | opts_chunk:
32 | fig.align: center
33 | out.width: 50%
34 |
35 | execute:
36 | echo: true
37 | warning: false
38 | message: false
39 | error: true
40 | cache: false
41 |
42 | editor:
43 | source
44 |
45 |
--------------------------------------------------------------------------------
/style/edav3.scss:
--------------------------------------------------------------------------------
1 | /*-- scss:defaults --*/
2 |
3 | @import url('https://fonts.googleapis.com/css2?family=Lato&display=swap');
4 |
5 | $border-color-left: #80abd7!default;
6 | $background-color: #e6eef7 !default;
7 | $headings-color: #3379be;
8 | $link-color: #3379be;
9 | $code-color: #3379be;
10 |
11 | $font-family-sans-serif: "Lato", sans-serif;
12 |
13 | /*-- scss:rules --*/
14 |
15 | text, circle {
16 | pointer-events: none;
17 | user-select: none;
18 | -webkit-user-select: none;
19 | -moz-user-select: none;
20 | }
21 |
22 | p code:not(.sourceCode), td code:not(.sourceCode),
23 | li code:not(.sourceCode) {
24 | font-weight: bold;
25 | background-color: white;
26 | }
27 |
28 | .big {
29 | font-size: 1.1rem;
30 | }
31 |
32 | .footnotes {
33 | font-size: .85rem;
34 | }
35 |
36 | // https://stackoverflow.com/questions/74647399/define-a-new-callout-in-quarto
37 |
38 | div.callout-tip.callout {
39 | border-left-color: $border-color-left;
40 | }
41 |
42 | div.callout-tip.callout-style-default>.callout-header {
43 | background-color: $background-color;
44 | }
45 |
46 | button {
47 | font-size: .85rem;
48 | margin: 10px;
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/clustering.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: Kmeans
3 | ---
4 |
5 |
6 |
7 |
8 |
9 |
10 | |
11 |
12 |
13 |
14 | |
15 |
16 |
17 | Initialize centroids by randomizing:
18 | points
19 | centroids
20 |
21 | points = randomly choose cluster for each point, then calculate centroids
22 | centroids = randomly choose k points to be centroids, then reassign points
23 |
24 |
25 | |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
74 |
--------------------------------------------------------------------------------
/clustering-simdata.qmd:
--------------------------------------------------------------------------------
1 | ---
2 | title: Kmeans with simulated data
3 | ---
4 |
5 |
6 |
7 |
8 |
9 |
10 | |
11 |
12 |
13 |
14 | |
15 |
16 |
17 | Initialize centroids by randomizing:
18 | points
19 | centroids
20 |
21 | points = randomly choose cluster for each point, then calculate centroids
22 | centroids = randomly choose k points to be centroids, then reassign points
23 |
24 |
25 | |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
74 |
--------------------------------------------------------------------------------
/style/joyce.theme:
--------------------------------------------------------------------------------
1 | {
2 | "metadata" : {
3 | "copyright": [
4 | "SPDX-FileCopyrightText: 2016 Volker Krause ",
5 | "SPDX-FileCopyrightText: 2016 Dominik Haumann "
6 | ],
7 | "license": "SPDX-License-Identifier: MIT",
8 | "revision" : 5,
9 | "name" : "Printing"
10 | },
11 | "text-styles": {
12 | "Normal" : {
13 | "text-color" : "#000000",
14 | "selected-text-color" : "#ffffff",
15 | "bold" : false,
16 | "italic" : false,
17 | "underline" : false,
18 | "strike-through" : false
19 | },
20 | "Attribute" : {
21 | "text-color" : "#9753B8",
22 | "bold": true
23 | },
24 | "Function" : {
25 | "text-color" : "#3379be",
26 | "bold" : true
27 | },
28 | "SpecialChar" : {
29 | "text-color" : "#ff5500",
30 | "bold" : true
31 | },
32 | "String" : {
33 | "text-color" : "#666666"
34 | },
35 | "DecVal" : {
36 | "text-color" : "#666666"
37 | },
38 | "Comment" : {
39 | "text-color" : "#666666"
40 | },
41 | "Float" : {
42 | "text-color" : "#666666"
43 | },
44 | "Attribute-old" : {
45 | "text-color" : "#2E8B57",
46 | "bold": true
47 | },
48 |
49 |
50 |
51 | "Keyword" : {
52 | "text-color" : "#000000",
53 | "selected-text-color" : "#ffffff",
54 | "bold" : true
55 | },
56 | "Variable" : {
57 | "text-color" : "#0057ae",
58 | "selected-text-color" : "#00316e"
59 | },
60 | "ControlFlow" : {
61 | "text-color" : "#000000",
62 | "selected-text-color" : "#ffffff",
63 | "bold" : true
64 | },
65 | "Operator" : {
66 | "text-color" : "#000000",
67 | "selected-text-color" : "#ffffff"
68 | },
69 | "BuiltIn" : {
70 | "text-color" : "#644a9b",
71 | "selected-text-color" : "#452886"
72 | },
73 | "Extension" : {
74 | "text-color" : "#0095ff",
75 | "selected-text-color" : "#ffffff",
76 | "bold" : true
77 | },
78 | "Preprocessor" : {
79 | "text-color" : "#006e28",
80 | "selected-text-color" : "#006e28"
81 | },
82 | "Char" : {
83 | "text-color" : "#924c9d",
84 | "selected-text-color" : "#6c2477"
85 | },
86 | "VerbatimString" : {
87 | "text-color" : "#ea0404",
88 | "selected-text-color" : "#9c0e0e"
89 | },
90 | "SpecialString" : {
91 | "text-color" : "#ff5500",
92 | "selected-text-color" : "#ff5500"
93 | },
94 | "Import" : {
95 | "text-color" : "#644a9b",
96 | "selected-text-color" : "#452886"
97 | },
98 | "DataType" : {
99 | "text-color" : "#0057ae",
100 | "selected-text-color" : "#00316e"
101 | },
102 | "BaseN" : {
103 | "text-color" : "#b08000",
104 | "selected-text-color" : "#805c00"
105 | },
106 | "Constant" : {
107 | "text-color" : "#aa5500",
108 | "selected-text-color" : "#5e2f00"
109 | },
110 |
111 | "Documentation" : {
112 | "text-color" : "#607880",
113 | "selected-text-color" : "#46585e"
114 | },
115 | "Annotation" : {
116 | "text-color" : "#ca60ca",
117 | "selected-text-color" : "#a44ea4"
118 | },
119 | "CommentVar" : {
120 | "text-color" : "#0095ff",
121 | "selected-text-color" : "#ffffff"
122 | },
123 | "RegionMarker" : {
124 | "text-color" : "#0057ae",
125 | "selected-text-color" : "#00316e",
126 | "background-color" : "#e0e9f8"
127 | },
128 | "Information" : {
129 | "text-color" : "#d2d2d2",
130 | "selected-text-color" : "#805c00"
131 | },
132 | "Warning" : {
133 | "text-color" : "#d2d2d2",
134 | "selected-text-color" : "#9c0e0e"
135 | },
136 | "Alert" : {
137 | "text-color" : "#bf0303",
138 | "selected-text-color" : "#9c0e0e",
139 | "background-color" : "#f7e6e6",
140 | "bold" : true
141 | },
142 | "Error" : {
143 | "text-color" : "#bf0303",
144 | "selected-text-color" : "#9c0e0e",
145 | "underline" : true
146 | },
147 | "Others" : {
148 | "text-color" : "#006e28",
149 | "selected-text-color" : "#006e28"
150 | }
151 | },
152 | "editor-colors": {
153 | "BackgroundColor" : "#f5f5f5",
154 | "CodeFolding" : "#94caef",
155 | "BracketMatching" : "#edf9ff",
156 | "CurrentLine" : "#f8f7f6",
157 | "IconBorder" : "#d6d2d0",
158 | "IndentationLine" : "#d2d2d2",
159 | "LineNumbers" : "#221f1e",
160 | "CurrentLineNumber" : "#221f1e",
161 | "MarkBookmark" : "#0000ff",
162 | "MarkBreakpointActive" : "#ff0000",
163 | "MarkBreakpointReached" : "#ffff00",
164 | "MarkBreakpointDisabled" : "#ff00ff",
165 | "MarkExecution" : "#a0a0a4",
166 | "MarkWarning" : "#00ff00",
167 | "MarkError" : "#ff0000",
168 | "ModifiedLines" : "#f6e6e6",
169 | "ReplaceHighlight" : "#00ff00",
170 | "SavedLines" : "#baf8ce",
171 | "SearchHighlight" : "#ffff00",
172 | "TextSelection" : "#94caef",
173 | "Separator" : "#221f1e",
174 | "SpellChecking" : "#bf0303",
175 | "TabMarker" : "#d2d2d2",
176 | "TemplateBackground" : "#d6d2d0",
177 | "TemplatePlaceholder" : "#baf8ce",
178 | "TemplateFocusedPlaceholder" : "#76da98",
179 | "TemplateReadOnlyPlaceholder" : "#f6e6e6",
180 | "WordWrapMarker" : "#ededed"
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/style/joyce_temp.R:
--------------------------------------------------------------------------------
1 | {
2 | "metadata" : {
3 | "copyright": [
4 | "SPDX-FileCopyrightText: 2016 Volker Krause ",
5 | "SPDX-FileCopyrightText: 2016 Dominik Haumann "
6 | ],
7 | "license": "SPDX-License-Identifier: MIT",
8 | "revision" : 5,
9 | "name" : "Printing"
10 | },
11 | "text-styles": {
12 | "Normal" : {
13 | "text-color" : "#000000",
14 | "selected-text-color" : "#ffffff",
15 | "bold" : false,
16 | "italic" : false,
17 | "underline" : false,
18 | "strike-through" : false
19 | },
20 | "Attribute" : {
21 | "text-color" : "#9753B8",
22 | "bold": true
23 | },
24 | "Function" : {
25 | "text-color" : "#3379be",
26 | "bold" : true
27 | },
28 | "SpecialChar" : {
29 | "text-color" : "#ff5500",
30 | "bold" : true
31 | },
32 | "String" : {
33 | "text-color" : "#666666"
34 | },
35 | "DecVal" : {
36 | "text-color" : "#666666"
37 | },
38 | "Comment" : {
39 | "text-color" : "#666666"
40 | },
41 | "Float" : {
42 | "text-color" : "#666666"
43 | },
44 | "Attribute-old" : {
45 | "text-color" : "#2E8B57",
46 | "bold": true
47 | },
48 |
49 |
50 |
51 | "Keyword" : {
52 | "text-color" : "#000000",
53 | "selected-text-color" : "#ffffff",
54 | "bold" : true
55 | },
56 | "Variable" : {
57 | "text-color" : "#0057ae",
58 | "selected-text-color" : "#00316e"
59 | },
60 | "ControlFlow" : {
61 | "text-color" : "#000000",
62 | "selected-text-color" : "#ffffff",
63 | "bold" : true
64 | },
65 | "Operator" : {
66 | "text-color" : "#000000",
67 | "selected-text-color" : "#ffffff"
68 | },
69 | "BuiltIn" : {
70 | "text-color" : "#644a9b",
71 | "selected-text-color" : "#452886"
72 | },
73 | "Extension" : {
74 | "text-color" : "#0095ff",
75 | "selected-text-color" : "#ffffff",
76 | "bold" : true
77 | },
78 | "Preprocessor" : {
79 | "text-color" : "#006e28",
80 | "selected-text-color" : "#006e28"
81 | },
82 | "Char" : {
83 | "text-color" : "#924c9d",
84 | "selected-text-color" : "#6c2477"
85 | },
86 | "VerbatimString" : {
87 | "text-color" : "#ea0404",
88 | "selected-text-color" : "#9c0e0e"
89 | },
90 | "SpecialString" : {
91 | "text-color" : "#ff5500",
92 | "selected-text-color" : "#ff5500"
93 | },
94 | "Import" : {
95 | "text-color" : "#644a9b",
96 | "selected-text-color" : "#452886"
97 | },
98 | "DataType" : {
99 | "text-color" : "#0057ae",
100 | "selected-text-color" : "#00316e"
101 | },
102 | "BaseN" : {
103 | "text-color" : "#b08000",
104 | "selected-text-color" : "#805c00"
105 | },
106 | "Constant" : {
107 | "text-color" : "#aa5500",
108 | "selected-text-color" : "#5e2f00"
109 | },
110 |
111 | "Documentation" : {
112 | "text-color" : "#607880",
113 | "selected-text-color" : "#46585e"
114 | },
115 | "Annotation" : {
116 | "text-color" : "#ca60ca",
117 | "selected-text-color" : "#a44ea4"
118 | },
119 | "CommentVar" : {
120 | "text-color" : "#0095ff",
121 | "selected-text-color" : "#ffffff"
122 | },
123 | "RegionMarker" : {
124 | "text-color" : "#0057ae",
125 | "selected-text-color" : "#00316e",
126 | "background-color" : "#e0e9f8"
127 | },
128 | "Information" : {
129 | "text-color" : "#d2d2d2",
130 | "selected-text-color" : "#805c00"
131 | },
132 | "Warning" : {
133 | "text-color" : "#d2d2d2",
134 | "selected-text-color" : "#9c0e0e"
135 | },
136 | "Alert" : {
137 | "text-color" : "#bf0303",
138 | "selected-text-color" : "#9c0e0e",
139 | "background-color" : "#f7e6e6",
140 | "bold" : true
141 | },
142 | "Error" : {
143 | "text-color" : "#bf0303",
144 | "selected-text-color" : "#9c0e0e",
145 | "underline" : true
146 | },
147 | "Others" : {
148 | "text-color" : "#006e28",
149 | "selected-text-color" : "#006e28"
150 | }
151 | },
152 | "editor-colors": {
153 | "BackgroundColor" : "#f5f5f5",
154 | "CodeFolding" : "#94caef",
155 | "BracketMatching" : "#edf9ff",
156 | "CurrentLine" : "#f8f7f6",
157 | "IconBorder" : "#d6d2d0",
158 | "IndentationLine" : "#d2d2d2",
159 | "LineNumbers" : "#221f1e",
160 | "CurrentLineNumber" : "#221f1e",
161 | "MarkBookmark" : "#0000ff",
162 | "MarkBreakpointActive" : "#ff0000",
163 | "MarkBreakpointReached" : "#ffff00",
164 | "MarkBreakpointDisabled" : "#ff00ff",
165 | "MarkExecution" : "#a0a0a4",
166 | "MarkWarning" : "#00ff00",
167 | "MarkError" : "#ff0000",
168 | "ModifiedLines" : "#f6e6e6",
169 | "ReplaceHighlight" : "#00ff00",
170 | "SavedLines" : "#baf8ce",
171 | "SearchHighlight" : "#ffff00",
172 | "TextSelection" : "#94caef",
173 | "Separator" : "#221f1e",
174 | "SpellChecking" : "#bf0303",
175 | "TabMarker" : "#d2d2d2",
176 | "TemplateBackground" : "#d6d2d0",
177 | "TemplatePlaceholder" : "#baf8ce",
178 | "TemplateFocusedPlaceholder" : "#76da98",
179 | "TemplateReadOnlyPlaceholder" : "#f6e6e6",
180 | "WordWrapMarker" : "#ededed"
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/scripts/kmeans.js:
--------------------------------------------------------------------------------
1 | // ############ SETUP ###############
2 |
3 | function setup() {
4 | d3.select("h3#info").text("Click and drag to add points.");
5 | d3.select("#kmse").text("Kmeans square error:");
6 | d3.select("div#plot").select("svg").remove();
7 | d3.select("div#buttons").select("input").remove();
8 |
9 | d3.select("div#buttons")
10 | .append("input")
11 | .attr("type", "button")
12 | .attr("value", "Done adding points")
13 | .attr("onclick", "choosek()");
14 |
15 | svg = d3.select("div#plot")
16 | .append("svg")
17 | .attr("width", w)
18 | .attr("height", h);
19 |
20 | // create plot area
21 | svg.append("g")
22 | .attr("id", "plotarea")
23 | .attr("transform", `translate(${margin.left}, ${margin.top})`);
24 |
25 | svg.select("g#plotarea")
26 | .append("rect")
27 | .attr("width", innerWidth)
28 | .attr("height", innerHeight)
29 | .attr("fill", "transparent");
30 |
31 | // create x-axis
32 | svg.select("g#plotarea")
33 | .append("g")
34 | .attr("id", "xaxis")
35 | .attr("transform", `translate(0, ${innerHeight})`)
36 | .call(xAxis);
37 |
38 | // create x-axis label
39 | svg.select("g#plotarea")
40 | .append("text")
41 | .attr("id", "xlab")
42 | .attr("x", innerWidth / 2)
43 | .attr("y", innerHeight + .75 * margin.bottom)
44 | .attr("text-anchor", "middle")
45 | .text("x");
46 |
47 | // create y-axis
48 | svg.select("g#plotarea")
49 | .append("g")
50 | .attr("id", "yaxis")
51 | .call(yAxis);
52 |
53 | // create y-axis label
54 | svg.select("g#plotarea")
55 | .append("text")
56 | .attr("id", "ylab")
57 | .attr("x", -margin.left / 2)
58 | .attr("y", innerHeight / 2)
59 | .attr("text-anchor", "middle")
60 | .attr("transform", `rotate(-90, ${0 - .75 * margin.left}, ${innerHeight / 2})`)
61 | .text("y");
62 |
63 | // create data
64 |
65 | // https://stackoverflow.com/questions/18273884/live-drawing-of-a-line-in-d3-js
66 | svg.select("g#plotarea")
67 | .select("rect")
68 | .on("mousedown", mousedown)
69 | .on("mouseup", mouseup);
70 | } // end of setup()
71 |
72 |
73 | // ############ THROTTLE ###############
74 |
75 | // Throttle function to limit the rate at which the mousemove function is called, see: https://stackoverflow.com/questions/78859948/how-can-i-slow-down-drag-behavior-with-d3-javascript
76 |
77 | function throttle(func, delay) {
78 | let lastCall = 0;
79 | return function (...args) {
80 | const now = new Date().getTime();
81 | if (now - lastCall >= delay) {
82 | lastCall = now;
83 | return func(...args);
84 | }
85 | };
86 | }
87 |
88 |
89 | // ############ ADDPOINT ###############
90 |
91 | function addpoint() {
92 | const new_x = xScale.invert(d3.pointer(event)[0]);
93 | const new_y = yScale.invert(d3.pointer(event)[1]);
94 | svg.select("g#plotarea")
95 | .append("circle")
96 | .data([{ x: new_x, y: new_y }])
97 | .attr("cx", d => xScale(d.x))
98 | .attr("cy", d => yScale(d.y))
99 | .attr("fill-opacity", "0.5")
100 | .attr("r", "3");
101 | }
102 |
103 |
104 | // ############ MOUSEDOWN ###############
105 |
106 | function mousedown() {
107 | addpoint();
108 | svg.select("g#plotarea")
109 | .select("rect")
110 | .on("mousemove", throttle(addpoint, 50));
111 | }
112 |
113 | // ############ MOUSEUP ###############
114 |
115 | function mouseup() {
116 | svg.select("g#plotarea")
117 | .select("rect")
118 | .on("mousemove", null);
119 | }
120 |
121 |
122 | // ############ CHOOSEK ###############
123 |
124 | function choosek() {
125 | svg.select("g#plotarea")
126 | .select("rect")
127 | .on("mousedown", null)
128 | .on("mouseup", null)
129 | .on("mousemove", null);
130 |
131 | d3.select("h3#info").text("Choose the number of clusters");
132 |
133 | d3.select("div#buttons")
134 | .select("input")
135 | .attr("hidden", "hidden");
136 |
137 | const kvalues = d3.range(11);
138 |
139 | d3.select("div#buttons")
140 | .append("select")
141 | .attr("name", "numclusters")
142 | .attr("id", "k")
143 | .on("change", function () {
144 | const k = d3.select(this).property("value");
145 | d3.select("svg").datum(k);
146 | kmeansbegin();
147 | })
148 | .append("option")
149 | .attr("value", "select")
150 | .text("Choose k");
151 |
152 | d3.select("div#buttons").select("select#k")
153 | .selectAll("option")
154 | .data(kvalues)
155 | .enter()
156 | .append("option")
157 | .attr("value", d => d)
158 | .text(d => d);
159 | }
160 |
161 | // ############ REDO ###############
162 |
163 | function redo() {
164 | d3.select("#centroids").remove();
165 | d3.select("#lines").remove();
166 | d3.select("#kmse").text("Kmeans square error:");
167 | kmeansbegin();
168 | }
169 |
170 | // ############ KMEANSBEGIN ###############
171 |
172 | function kmeansbegin() {
173 | d3.select("div#buttons").select("select#k").remove();
174 |
175 | const allpoints = svg.selectAll("circle");
176 | data = allpoints.data();
177 |
178 | const k = d3.select("svg").datum();
179 |
180 | data = data.map(d => ({ x: d.x, y: d.y, cluster: d3.randomInt(k)() }));
181 |
182 | const method = d3.select('input[name="initmethod"]:checked').node().value;
183 |
184 | if (method == "points") {
185 |
186 | d3.select("div#buttons").select("input")
187 | .attr("hidden", null)
188 | .attr("value", "Add centroids")
189 | .attr("type", "button")
190 | .attr("onclick", "update_centroids()");
191 |
192 | allpoints
193 | .data(data) // updates with cluster info
194 | .style("fill", d => colorScale(d.cluster));
195 |
196 | // draw initial centroids (with no area)
197 | let centroids = d3.range(k).map(e => ({
198 | x: d3.mean(data.filter(d => d.cluster == e).map(d => d.x)),
199 | y: d3.mean(data.filter(d => d.cluster == e).map(d => d.y)),
200 | cluster: e
201 | }));
202 |
203 | svg.select("g#plotarea")
204 | .append("g")
205 | .attr("id", "centroids")
206 | .selectAll("circle")
207 | .data(centroids)
208 | .enter()
209 | .append("circle")
210 | .attr("cx", d => xScale(d.x))
211 | .attr("cy", d => yScale(d.y))
212 | .attr("r", "0")
213 | .style("fill", d => colorScale(d.cluster));
214 |
215 | } else {
216 | const indices = d3.shuffle(d3.range(data.length)).slice(0, k);
217 | let centroids = data.filter((d, i) => indices.includes(i))
218 | .map((d, i) => ({...d, cluster:i})); // add cluster
219 |
220 | svg.select("g#plotarea")
221 | .append("g")
222 | .attr("id", "centroids")
223 | .selectAll("circle")
224 | .data(centroids)
225 | .enter()
226 | .append("circle")
227 | .attr("cx", d => xScale(d.x))
228 | .attr("cy", d => yScale(d.y))
229 | .attr("r", "5")
230 | .style("fill", d => colorScale(d.cluster));
231 |
232 | d3.select("div#buttons").select("input")
233 | .attr("hidden", null)
234 | .attr("value", "Assign points")
235 | .attr("type", "button")
236 | .attr("onclick", "reassign_points()");
237 | }
238 |
239 |
240 |
241 | // create lines group
242 | svg.select("g#plotarea")
243 | .append("g")
244 | .attr("id", "lines");
245 | }
246 |
247 |
248 | // ############ UPDATE_CENTROIDS ###############
249 |
250 | function update_centroids() {
251 | d3.select("h3#info").text("Click button to reassign points.");
252 |
253 | const k = d3.select("svg").datum();
254 |
255 | const oldcentroids = svg
256 | .select("#centroids")
257 | .selectAll("circle")
258 | .data();
259 |
260 | const centroids = d3.range(k).map(e => ({
261 | x: d3.mean(data.filter(d => d.cluster == e).map(d => d.x)),
262 | y: d3.mean(data.filter(d => d.cluster == e).map(d => d.y)),
263 | cluster: e
264 | }));
265 |
266 | // calculate and display mse
267 | let kmse = 0;
268 | for (let j = 0; j < data.length; j++) {
269 | kmse = kmse + distsquared(data[j], centroids[data[j].cluster]);
270 | }
271 |
272 | d3.select("#kmse").text("Kmeans square error: " + kmse.toFixed(5));
273 |
274 | let done = false;
275 |
276 | if (d3.select("g#centroids circle").attr("r") != 0) {
277 | done = true;
278 |
279 | for (let i = 0; i < centroids.length; i++) {
280 | if (oldcentroids[i].x != centroids[i].x) done = false;
281 | if (oldcentroids[i].y != centroids[i].y) done = false;
282 | }
283 | }
284 |
285 | if (done) {
286 | d3.select("h3#info").text("Algorithm converged. Click to restart.");
287 | d3.select("div#buttons")
288 | .select("input")
289 | .attr("hidden", "hidden");
290 | } else {
291 | // update centroids
292 | svg.select("#centroids")
293 | .selectAll("circle")
294 | .data(centroids)
295 | .transition()
296 | .duration(1000)
297 | .attr("r", "5")
298 | .attr("cx", d => xScale(d.x))
299 | .attr("cy", d => yScale(d.y));
300 |
301 | svg.select("g#plotarea")
302 | .select("#lines")
303 | .append("g")
304 | .selectAll("line")
305 | .data(oldcentroids)
306 | .enter()
307 | .append("line")
308 | .attr("x1", d => xScale(d.x))
309 | .attr("y1", d => yScale(d.y))
310 | .attr("x2", d => xScale(d.x))
311 | .attr("y2", d => yScale(d.y))
312 | .attr("stroke", d => colorScale(d.cluster))
313 | .data(centroids)
314 | .transition()
315 | .duration(1000)
316 | .attr("x2", d => xScale(d.x))
317 | .attr("y2", d => yScale(d.y));
318 |
319 | d3.select("div#buttons").select("input")
320 | .attr("value", "Reassign points")
321 | .attr("onclick", "reassign_points()");
322 | }
323 | }
324 |
325 | // ############ DIST ###############
326 |
327 | // source: https://www.naftaliharris.com/blog/visualizing-k-means-clustering/
328 |
329 | function dist(w, z) {
330 | return Math.sqrt(Math.pow(w.x - z.x, 2) + Math.pow(w.y - z.y, 2));
331 | }
332 |
333 | // ############ DISTSQUARED ###############
334 |
335 | function distsquared(w, z) {
336 | return Math.pow(w.x - z.x, 2) + Math.pow(w.y - z.y, 2);
337 | }
338 |
339 |
340 | // ############ REASSIGN_POINTS ###############
341 |
342 | // source: https://www.naftaliharris.com/blog/visualizing-k-means-clustering/
343 | function reassign_points() {
344 | d3.select("h3#info").text("Click button to recalculate centroids.");
345 |
346 | const centroids = d3.select("#centroids")
347 | .selectAll("circle").data();
348 |
349 | for (let j = 0; j < data.length; j++) {
350 | let ibest = 0;
351 | let dbest = Infinity;
352 | for (let i = 0; i < centroids.length; i++) {
353 | const d = dist(data[j], centroids[i]);
354 | if (d < dbest) {
355 | dbest = d;
356 | ibest = i;
357 | }
358 | }
359 | data[j].cluster = ibest;
360 | }
361 |
362 | svg.selectAll("circle")
363 | .data(data)
364 | .style("fill", d => colorScale(d.cluster));
365 |
366 | d3.select("div#buttons").select("input")
367 | .attr("value", "Update centroids")
368 | .attr("onclick", "update_centroids()");
369 | }
370 |
371 |
372 | // ############ DOWNLOADSVG ###############
373 | // need to add styling
374 |
375 | function downloadSVG() {
376 | const svgData = new XMLSerializer().serializeToString(document.querySelector("div#plot > svg"));
377 | const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
378 | const url = URL.createObjectURL(blob);
379 | const a = document.createElement("a");
380 | a.href = url;
381 | a.download = "chart.svg";
382 | document.body.appendChild(a);
383 | a.click();
384 | document.body.removeChild(a);
385 | URL.revokeObjectURL(url);
386 | }
387 |
388 |
389 | // ########### DOWNLOAD SVG ################
390 |
391 | // https://observablehq.com/@jeremiak/download-data-button
392 | // converted to non-obvervable javascript with chatgpt
393 |
394 | function savedata() {
395 | const data = d3.selectAll("#plotarea > circle").data();
396 | const blob = new Blob([d3.csvFormat(data)], { type: "text/csv" });
397 | const url = URL.createObjectURL(blob);
398 | const a = document.createElement('a');
399 | a.href = url;
400 | a.download = "data.csv";
401 | document.body.appendChild(a);
402 | a.click();
403 | document.body.removeChild(a);
404 | URL.revokeObjectURL(url);
405 | }
406 |
--------------------------------------------------------------------------------
/scripts/kmeans-simdata.js:
--------------------------------------------------------------------------------
1 | // ############ SETUP ###############
2 |
3 | function setup() {
4 | d3.select("h3#info").text("Using simulated data");
5 | d3.select("#kmse").text("Kmeans square error:");
6 | d3.select("div#plot").select("svg").remove();
7 | d3.select("div#buttons").select("input").remove();
8 |
9 | d3.select("div#buttons")
10 | .append("input")
11 | .attr("type", "button");
12 |
13 | svg = d3.select("div#plot")
14 | .append("svg")
15 | .attr("width", w)
16 | .attr("height", h);
17 |
18 | // create plot area
19 | svg.append("g")
20 | .attr("id", "plotarea")
21 | .attr("transform", `translate(${margin.left}, ${margin.top})`);
22 |
23 | svg.select("g#plotarea")
24 | .append("rect")
25 | .attr("width", innerWidth)
26 | .attr("height", innerHeight)
27 | .attr("fill", "transparent");
28 |
29 | // create x-axis
30 | svg.select("g#plotarea")
31 | .append("g")
32 | .attr("id", "xaxis")
33 | .attr("transform", `translate(0, ${innerHeight})`)
34 | .call(xAxis);
35 |
36 | // create x-axis label
37 | svg.select("g#plotarea")
38 | .append("text")
39 | .attr("id", "xlab")
40 | .attr("x", innerWidth / 2)
41 | .attr("y", innerHeight + .75 * margin.bottom)
42 | .attr("text-anchor", "middle")
43 | .text("x");
44 |
45 | // create y-axis
46 | svg.select("g#plotarea")
47 | .append("g")
48 | .attr("id", "yaxis")
49 | .call(yAxis);
50 |
51 | // create y-axis label
52 | svg.select("g#plotarea")
53 | .append("text")
54 | .attr("id", "ylab")
55 | .attr("x", -margin.left / 2)
56 | .attr("y", innerHeight / 2)
57 | .attr("text-anchor", "middle")
58 | .attr("transform", `rotate(-90, ${0 - .75 * margin.left}, ${innerHeight / 2})`)
59 | .text("y");
60 |
61 | // plot initial data
62 | plotpoints();
63 |
64 | // instruct user to choose k
65 | choosek();
66 | };
67 |
68 |
69 |
70 | // ############ CHOOSEK ###############
71 |
72 | function choosek() {
73 | svg.select("g#plotarea")
74 | .select("rect")
75 | .on("mousedown", null)
76 | .on("mouseup", null)
77 | .on("mousemove", null);
78 |
79 | d3.select("h3#info").text("Choose the number of clusters");
80 |
81 | d3.select("div#buttons")
82 | .select("input")
83 | .attr("hidden", "hidden");
84 |
85 | const kvalues = d3.range(11);
86 |
87 | d3.select("div#buttons")
88 | .append("select")
89 | .attr("name", "numclusters")
90 | .attr("id", "k")
91 | .on("change", function () {
92 | const k = d3.select(this).property("value");
93 | d3.select("svg").datum(k);
94 | kmeansbegin();
95 | })
96 | .append("option")
97 | .attr("value", "select")
98 | .text("Choose k");
99 |
100 | d3.select("div#buttons").select("select#k")
101 | .selectAll("option")
102 | .data(kvalues)
103 | .enter()
104 | .append("option")
105 | .attr("value", d => d)
106 | .text(d => d);
107 | }
108 |
109 | // ############ REDO ###############
110 |
111 | function redo() {
112 | d3.select("#centroids").remove();
113 | d3.select("#lines").remove();
114 | d3.select("#kmse").text("Kmeans square error:");
115 | kmeansbegin();
116 | }
117 | // ############ PLOTPOINTS ################
118 |
119 | function plotpoints() {
120 | const simdata =[
121 | {"x":3.5019,"y":2.9494,"label":"1"},
122 | {"x":3.7128,"y":3.1593,"label":"1"},
123 | {"x":2.7774,"y":4.772,"label":"1"},
124 | {"x":2.2125,"y":3.8176,"label":"1"},
125 | {"x":2.267,"y":3.9078,"label":"1"},
126 | {"x":4.0295,"y":2.2929,"label":"1"},
127 | {"x":2.0212,"y":3.0446,"label":"1"},
128 | {"x":3.9489,"y":2.6175,"label":"1"},
129 | {"x":4.1963,"y":4.686,"label":"1"},
130 | {"x":3.8637,"y":4.0446,"label":"1"},
131 | {"x":3.4575,"y":3.9657,"label":"1"},
132 | {"x":3.3427,"y":3.5148,"label":"1"},
133 | {"x":2.8107,"y":1.6265,"label":"1"},
134 | {"x":1.4434,"y":3.2628,"label":"1"},
135 | {"x":3.0494,"y":0.8809,"label":"1"},
136 | {"x":2.0385,"y":2.7311,"label":"1"},
137 | {"x":1.6858,"y":3.4595,"label":"1"},
138 | {"x":4.311,"y":1.82,"label":"1"},
139 | {"x":3.9535,"y":3.4676,"label":"1"},
140 | {"x":2.6762,"y":2.4194,"label":"1"},
141 | {"x":3.0054,"y":3.8981,"label":"1"},
142 | {"x":3.7433,"y":2.9876,"label":"1"},
143 | {"x":3.948,"y":5.1148,"label":"1"},
144 | {"x":2.9715,"y":2.3159,"label":"1"},
145 | {"x":2.9767,"y":1.9933,"label":"1"},
146 | {"x":3.3094,"y":2.6568,"label":"1"},
147 | {"x":4.1419,"y":3.0713,"label":"1"},
148 | {"x":3.0114,"y":1.5122,"label":"1"},
149 | {"x":3.458,"y":2.103,"label":"1"},
150 | {"x":4.7713,"y":4.3625,"label":"1"},
151 | {"x":1.1431,"y":2.5759,"label":"1"},
152 | {"x":1.9044,"y":1.813,"label":"1"},
153 | {"x":3.6257,"y":1.4984,"label":"1"},
154 | {"x":3.9502,"y":2.4702,"label":"1"},
155 | {"x":1.769,"y":4.3291,"label":"1"},
156 | {"x":1.4147,"y":4.508,"label":"1"},
157 | {"x":3.6353,"y":2.5633,"label":"1"},
158 | {"x":2.941,"y":3.363,"label":"1"},
159 | {"x":4.0366,"y":2.3378,"label":"1"},
160 | {"x":3.0619,"y":3.9515,"label":"1"},
161 | {"x":3.9472,"y":3.8648,"label":"1"},
162 | {"x":2.726,"y":3.4227,"label":"1"},
163 | {"x":3.2645,"y":2.3922,"label":"1"},
164 | {"x":2.4336,"y":2.6133,"label":"1"},
165 | {"x":3.7929,"y":2.4003,"label":"1"},
166 | {"x":4.0245,"y":3.9804,"label":"1"},
167 | {"x":3.5699,"y":3.0337,"label":"1"},
168 | {"x":3.1165,"y":3.5344,"label":"1"},
169 | {"x":2.7792,"y":4.0049,"label":"1"},
170 | {"x":2.812,"y":2.5094,"label":"1"},
171 | {"x":3.0757,"y":2.2924,"label":"1"},
172 | {"x":1.476,"y":0.1116,"label":"1"},
173 | {"x":2.3396,"y":3.7499,"label":"1"},
174 | {"x":4.2594,"y":1.6989,"label":"1"},
175 | {"x":4.0219,"y":4.3458,"label":"1"},
176 | {"x":3.2993,"y":5.7255,"label":"1"},
177 | {"x":2.199,"y":2.1837,"label":"1"},
178 | {"x":2.1947,"y":3.1918,"label":"1"},
179 | {"x":4.518,"y":2.1244,"label":"1"},
180 | {"x":3.3986,"y":2.7282,"label":"1"},
181 | {"x":3.9973,"y":3.41,"label":"1"},
182 | {"x":3.5789,"y":2.7923,"label":"1"},
183 | {"x":3.3366,"y":2.4245,"label":"1"},
184 | {"x":2.378,"y":3.0485,"label":"1"},
185 | {"x":4.5065,"y":0.7755,"label":"1"},
186 | {"x":1.513,"y":3.0817,"label":"1"},
187 | {"x":1.6221,"y":1.5698,"label":"1"},
188 | {"x":3.2916,"y":2.1345,"label":"1"},
189 | {"x":2.3216,"y":2.1531,"label":"1"},
190 | {"x":3.2394,"y":3.4779,"label":"1"},
191 | {"x":3.5242,"y":2.5268,"label":"1"},
192 | {"x":1.8741,"y":4.1289,"label":"1"},
193 | {"x":1.342,"y":3.4545,"label":"1"},
194 | {"x":2.7556,"y":1.3899,"label":"1"},
195 | {"x":4.7469,"y":2.2321,"label":"1"},
196 | {"x":3.6302,"y":2.8057,"label":"1"},
197 | {"x":3.125,"y":1.5046,"label":"1"},
198 | {"x":2.9486,"y":4.4489,"label":"1"},
199 | {"x":3.9002,"y":3.246,"label":"1"},
200 | {"x":3.6976,"y":0.2164,"label":"1"},
201 | {"x":4.2044,"y":1.4301,"label":"1"},
202 | {"x":2.0037,"y":2.1244,"label":"1"},
203 | {"x":4.8721,"y":3.8061,"label":"1"},
204 | {"x":3.4758,"y":3.2934,"label":"1"},
205 | {"x":1.9362,"y":1.6979,"label":"1"},
206 | {"x":2.7723,"y":5.4168,"label":"1"},
207 | {"x":3.4196,"y":4.9538,"label":"1"},
208 | {"x":1.6428,"y":2.7445,"label":"1"},
209 | {"x":3.5165,"y":2.6891,"label":"1"},
210 | {"x":3.2257,"y":3.3098,"label":"1"},
211 | {"x":1.8778,"y":3.9472,"label":"1"},
212 | {"x":2.7067,"y":3.2655,"label":"1"},
213 | {"x":3.0351,"y":3.1367,"label":"1"},
214 | {"x":5.9492,"y":3.1096,"label":"1"},
215 | {"x":3.2037,"y":2.0879,"label":"1"},
216 | {"x":2.0178,"y":3.4754,"label":"1"},
217 | {"x":3.7436,"y":4.0208,"label":"1"},
218 | {"x":2.3821,"y":3.8267,"label":"1"},
219 | {"x":2.9153,"y":3.3642,"label":"1"},
220 | {"x":3.6274,"y":4.2118,"label":"1"},
221 | {"x":5.2621,"y":3.1344,"label":"2"},
222 | {"x":6.2711,"y":4.4175,"label":"2"},
223 | {"x":6.5025,"y":3.9842,"label":"2"},
224 | {"x":8.0915,"y":4.8114,"label":"2"},
225 | {"x":7.9721,"y":5.8069,"label":"2"},
226 | {"x":6.9628,"y":3.8207,"label":"2"},
227 | {"x":7.1664,"y":3.3014,"label":"2"},
228 | {"x":7.4658,"y":3.5348,"label":"2"},
229 | {"x":9.3746,"y":4.3946,"label":"2"},
230 | {"x":6.1778,"y":5.0503,"label":"2"},
231 | {"x":6.0798,"y":3.6051,"label":"2"},
232 | {"x":5.7062,"y":3.3331,"label":"2"},
233 | {"x":7.3402,"y":3.5298,"label":"2"},
234 | {"x":5.9549,"y":3.7333,"label":"2"},
235 | {"x":6.35,"y":3.3382,"label":"2"},
236 | {"x":6.0521,"y":2.7826,"label":"2"},
237 | {"x":7.4864,"y":2.8934,"label":"2"},
238 | {"x":8.1684,"y":1.9985,"label":"2"},
239 | {"x":7.3095,"y":4.5388,"label":"2"},
240 | {"x":7.3821,"y":5.2447,"label":"2"},
241 | {"x":7.3858,"y":3.6786,"label":"2"},
242 | {"x":7.6874,"y":4.3791,"label":"2"},
243 | {"x":7.3453,"y":4.3661,"label":"2"},
244 | {"x":6.1578,"y":3.4203,"label":"2"},
245 | {"x":6.3782,"y":5.4098,"label":"2"},
246 | {"x":5.815,"y":3.8861,"label":"2"},
247 | {"x":7.1741,"y":4.6241,"label":"2"},
248 | {"x":6.0327,"y":2.8209,"label":"2"},
249 | {"x":6.9737,"y":3.5025,"label":"2"},
250 | {"x":6.6572,"y":2.2583,"label":"2"},
251 | {"x":6.9768,"y":3.8685,"label":"2"},
252 | {"x":6.5668,"y":5.2594,"label":"2"},
253 | {"x":7.3106,"y":5.3806,"label":"2"},
254 | {"x":6.4711,"y":4.3611,"label":"2"},
255 | {"x":6.0687,"y":3.9081,"label":"2"},
256 | {"x":8.5728,"y":5.3328,"label":"2"},
257 | {"x":6.9163,"y":3.9181,"label":"2"},
258 | {"x":4.628,"y":3.1757,"label":"2"},
259 | {"x":5.1874,"y":4.6831,"label":"2"},
260 | {"x":7.6979,"y":3.7376,"label":"2"},
261 | {"x":6.4901,"y":3.8878,"label":"2"},
262 | {"x":6.817,"y":4.2619,"label":"2"},
263 | {"x":7.4312,"y":5.2919,"label":"2"},
264 | {"x":7.9078,"y":3.4758,"label":"2"},
265 | {"x":7.2028,"y":3.5635,"label":"2"},
266 | {"x":7.0123,"y":4.3069,"label":"2"},
267 | {"x":5.03,"y":3.6813,"label":"2"},
268 | {"x":6.6009,"y":2.3252,"label":"2"},
269 | {"x":5.985,"y":3.0548,"label":"2"},
270 | {"x":7.156,"y":3.531,"label":"2"},
271 | {"x":8.2951,"y":4.4487,"label":"2"},
272 | {"x":5.0986,"y":2.5336,"label":"2"},
273 | {"x":8.2441,"y":3.2693,"label":"2"},
274 | {"x":6.4458,"y":2.4368,"label":"2"},
275 | {"x":9.2298,"y":5.2144,"label":"2"},
276 | {"x":6.2682,"y":2.6196,"label":"2"},
277 | {"x":6.5624,"y":3.0026,"label":"2"},
278 | {"x":5.6468,"y":2.7381,"label":"2"},
279 | {"x":7.094,"y":4.9769,"label":"2"},
280 | {"x":7.1346,"y":3.8257,"label":"2"},
281 | {"x":7.9555,"y":2.5906,"label":"2"},
282 | {"x":7.9061,"y":4.4097,"label":"2"},
283 | {"x":7.3996,"y":4.6099,"label":"2"},
284 | {"x":8.0012,"y":5.152,"label":"2"},
285 | {"x":6.5441,"y":3.9683,"label":"2"},
286 | {"x":6.7397,"y":3.2817,"label":"2"},
287 | {"x":5.0871,"y":3.9778,"label":"2"},
288 | {"x":8.2142,"y":5.0206,"label":"2"},
289 | {"x":8.7823,"y":4.7919,"label":"2"},
290 | {"x":7.0402,"y":4.0694,"label":"2"},
291 | {"x":7.8911,"y":4.712,"label":"2"},
292 | {"x":6.1999,"y":4.3286,"label":"2"},
293 | {"x":6.3778,"y":4.7177,"label":"2"},
294 | {"x":6.4715,"y":3.1013,"label":"2"},
295 | {"x":7.9603,"y":5.3541,"label":"2"},
296 | {"x":7.1151,"y":3.164,"label":"2"},
297 | {"x":6.8823,"y":3.6901,"label":"2"},
298 | {"x":7.7391,"y":3.1736,"label":"2"},
299 | {"x":6.61,"y":3.3357,"label":"2"},
300 | {"x":5.5793,"y":4.2634,"label":"2"},
301 | {"x":6.7658,"y":3.5,"label":"2"},
302 | {"x":7.2368,"y":3.6727,"label":"2"},
303 | {"x":6.3741,"y":3.5897,"label":"2"},
304 | {"x":7.893,"y":4.3216,"label":"2"},
305 | {"x":7.2417,"y":3.572,"label":"2"},
306 | {"x":6.7672,"y":4.1559,"label":"2"},
307 | {"x":7.0879,"y":2.8853,"label":"2"},
308 | {"x":6.7526,"y":3.4758,"label":"2"},
309 | {"x":7.636,"y":3.1685,"label":"2"},
310 | {"x":7.5464,"y":3.5445,"label":"2"},
311 | {"x":6.916,"y":4.7919,"label":"2"},
312 | {"x":7.8637,"y":2.7206,"label":"2"},
313 | {"x":7.1443,"y":4.2881,"label":"2"},
314 | {"x":8.9747,"y":3.7378,"label":"2"},
315 | {"x":8.0509,"y":4.8152,"label":"2"},
316 | {"x":7.9234,"y":4.04,"label":"2"},
317 | {"x":6.6117,"y":4.4068,"label":"2"},
318 | {"x":5.0361,"y":2.2866,"label":"2"},
319 | {"x":6.6723,"y":3.7393,"label":"2"},
320 | {"x":6.3815,"y":3.8689,"label":"2"},
321 | {"x":6.188,"y":6.4635,"label":"3"},
322 | {"x":6.0341,"y":6.4713,"label":"3"},
323 | {"x":6.02,"y":5.9706,"label":"3"},
324 | {"x":6.2817,"y":4.7435,"label":"3"},
325 | {"x":6.1267,"y":6.4575,"label":"3"},
326 | {"x":4.6031,"y":3.9859,"label":"3"},
327 | {"x":7.9624,"y":6.7911,"label":"3"},
328 | {"x":4.3367,"y":5.0306,"label":"3"},
329 | {"x":5.6699,"y":5.4218,"label":"3"},
330 | {"x":4.5481,"y":5.0114,"label":"3"},
331 | {"x":5.8988,"y":6.1298,"label":"3"},
332 | {"x":5.9175,"y":3.8139,"label":"3"},
333 | {"x":4.3171,"y":4.6268,"label":"3"},
334 | {"x":5.2705,"y":4.9349,"label":"3"},
335 | {"x":3.2028,"y":5.5258,"label":"3"},
336 | {"x":6.4733,"y":5.5642,"label":"3"},
337 | {"x":6.0775,"y":6.2993,"label":"3"},
338 | {"x":5.6372,"y":5.0358,"label":"3"},
339 | {"x":4.5964,"y":4.7606,"label":"3"},
340 | {"x":6.0987,"y":5.3174,"label":"3"},
341 | {"x":5.2702,"y":5.8645,"label":"3"},
342 | {"x":4.6209,"y":4.3763,"label":"3"},
343 | {"x":5.8943,"y":5.4572,"label":"3"},
344 | {"x":4.4019,"y":5.4445,"label":"3"},
345 | {"x":4.2586,"y":4.1434,"label":"3"},
346 | {"x":4.5947,"y":4.8203,"label":"3"},
347 | {"x":4.2605,"y":5.4336,"label":"3"},
348 | {"x":6.4674,"y":7.8719,"label":"3"},
349 | {"x":4.6432,"y":4.7842,"label":"3"},
350 | {"x":5.1695,"y":4.5701,"label":"3"},
351 | {"x":5.4729,"y":5.2047,"label":"3"},
352 | {"x":5.8284,"y":6.0974,"label":"3"},
353 | {"x":5.2929,"y":6.6147,"label":"3"},
354 | {"x":5.3849,"y":5.3759,"label":"3"},
355 | {"x":5.0975,"y":4.8282,"label":"3"},
356 | {"x":4.9136,"y":7.1268,"label":"3"},
357 | {"x":7.1284,"y":5.808,"label":"3"},
358 | {"x":7.2266,"y":7.2876,"label":"3"},
359 | {"x":4.5011,"y":4.9988,"label":"3"},
360 | {"x":4.9946,"y":4.7806,"label":"3"},
361 | {"x":7.015,"y":6.9831,"label":"3"},
362 | {"x":6.4695,"y":5.628,"label":"3"},
363 | {"x":4.0106,"y":5.1953,"label":"3"},
364 | {"x":6.3334,"y":4.9281,"label":"3"},
365 | {"x":6.1837,"y":4.5495,"label":"3"},
366 | {"x":5.9282,"y":4.5451,"label":"3"},
367 | {"x":4.2125,"y":4.8391,"label":"3"},
368 | {"x":5.3515,"y":3.956,"label":"3"},
369 | {"x":5.2997,"y":6.0466,"label":"3"},
370 | {"x":5.4232,"y":5.2477,"label":"3"},
371 | {"x":4.6085,"y":5.8806,"label":"3"},
372 | {"x":4.2903,"y":4.2987,"label":"3"},
373 | {"x":6.2735,"y":5.6574,"label":"3"},
374 | {"x":6.4157,"y":4.1644,"label":"3"},
375 | {"x":7.0174,"y":6.1865,"label":"3"},
376 | {"x":4.6571,"y":4.3018,"label":"3"},
377 | {"x":8.0013,"y":7.1622,"label":"3"},
378 | {"x":4.2761,"y":4.4875,"label":"3"},
379 | {"x":6.1052,"y":4.7849,"label":"3"},
380 | {"x":5.1418,"y":6.2257,"label":"3"},
381 | {"x":6.9438,"y":6.1238,"label":"3"},
382 | {"x":6.0555,"y":4.9882,"label":"3"},
383 | {"x":5.2272,"y":5.3281,"label":"3"},
384 | {"x":5.721,"y":5.8949,"label":"3"},
385 | {"x":5.5356,"y":6.4462,"label":"3"},
386 | {"x":3.7686,"y":5.4844,"label":"3"},
387 | {"x":6.8206,"y":6.8895,"label":"3"},
388 | {"x":6.0382,"y":5.6374,"label":"3"},
389 | {"x":5.8729,"y":6.6102,"label":"3"},
390 | {"x":4.7779,"y":5.6307,"label":"3"},
391 | {"x":5.8779,"y":5.7791,"label":"3"},
392 | {"x":4.4796,"y":6.837,"label":"3"},
393 | {"x":5.4001,"y":5.8111,"label":"3"},
394 | {"x":6.228,"y":7.404,"label":"3"},
395 | {"x":6.3485,"y":7.1223,"label":"3"},
396 | {"x":5.6063,"y":5.0423,"label":"3"},
397 | {"x":6.3563,"y":5.9486,"label":"3"},
398 | {"x":6.12,"y":5.8677,"label":"3"},
399 | {"x":5.2092,"y":6.4636,"label":"3"},
400 | {"x":6.0949,"y":4.7255,"label":"3"},
401 | {"x":4.6609,"y":3.7447,"label":"3"},
402 | {"x":5.0421,"y":5.4947,"label":"3"},
403 | {"x":5.8744,"y":5.2845,"label":"3"},
404 | {"x":4.6873,"y":4.6924,"label":"3"},
405 | {"x":5.3603,"y":6.3301,"label":"3"},
406 | {"x":7.0677,"y":6.2666,"label":"3"},
407 | {"x":5.1995,"y":6.0448,"label":"3"},
408 | {"x":5.6817,"y":4.6444,"label":"3"},
409 | {"x":6.6403,"y":7.7442,"label":"3"},
410 | {"x":4.9148,"y":5.0792,"label":"3"},
411 | {"x":4.9431,"y":5.2353,"label":"3"},
412 | {"x":6.8811,"y":6.1372,"label":"3"},
413 | {"x":7.0076,"y":5.8088,"label":"3"},
414 | {"x":4.7459,"y":3.5769,"label":"3"},
415 | {"x":5.5573,"y":5.3282,"label":"3"},
416 | {"x":4.2909,"y":3.7249,"label":"3"},
417 | {"x":6.727,"y":6.2073,"label":"3"},
418 | {"x":5.0541,"y":5.3077,"label":"3"},
419 | {"x":5.9065,"y":4.5158,"label":"3"},
420 | {"x":6.1493,"y":6.0876,"label":"3"}];
421 |
422 | svg.select("g#plotarea")
423 | .selectAll("circle")
424 | .data(simdata)
425 | .enter()
426 | .append("circle")
427 | .attr("cx", d => xScale(d.x))
428 | .attr("cy", d => yScale(d.y))
429 | .attr("fill-opacity", "0.5")
430 | .attr("r", "3");
431 |
432 | };
433 |
434 |
435 | // ############ KMEANSBEGIN ###############
436 |
437 | function kmeansbegin() {
438 | d3.select("div#buttons").select("select#k").remove();
439 |
440 | const k = d3.select("svg").datum();
441 |
442 | const allpoints = svg.selectAll("circle");
443 | data = allpoints.data();
444 |
445 | data = data.map(d => ({ x: d.x, y: d.y, cluster: d3.randomInt(k)() }));
446 |
447 | const method = d3.select('input[name="initmethod"]:checked').node().value;
448 |
449 | if (method == "points") {
450 |
451 | d3.select("div#buttons").select("input")
452 | .attr("hidden", null)
453 | .attr("value", "Add centroids")
454 | .attr("type", "button")
455 | .attr("onclick", "update_centroids()");
456 |
457 | allpoints
458 | .data(data) // updates with cluster info
459 | .style("fill", d => colorScale(d.cluster));
460 |
461 | // draw initial centroids (with no area)
462 | let centroids = d3.range(k).map(e => ({
463 | x: d3.mean(data.filter(d => d.cluster == e).map(d => d.x)),
464 | y: d3.mean(data.filter(d => d.cluster == e).map(d => d.y)),
465 | cluster: e
466 | }));
467 |
468 | svg.select("g#plotarea")
469 | .append("g")
470 | .attr("id", "centroids")
471 | .selectAll("circle")
472 | .data(centroids)
473 | .enter()
474 | .append("circle")
475 | .attr("cx", d => xScale(d.x))
476 | .attr("cy", d => yScale(d.y))
477 | .attr("r", "0")
478 | .style("fill", d => colorScale(d.cluster));
479 |
480 | } else {
481 | const indices = d3.shuffle(d3.range(data.length)).slice(0, k);
482 | let centroids = data.filter((d, i) => indices.includes(i))
483 | .map((d, i) => ({...d, cluster:i})); // add cluster
484 |
485 | svg.select("g#plotarea")
486 | .append("g")
487 | .attr("id", "centroids")
488 | .selectAll("circle")
489 | .data(centroids)
490 | .enter()
491 | .append("circle")
492 | .attr("cx", d => xScale(d.x))
493 | .attr("cy", d => yScale(d.y))
494 | .attr("r", "5")
495 | .style("fill", d => colorScale(d.cluster));
496 |
497 | d3.select("div#buttons").select("input")
498 | .attr("hidden", null)
499 | .attr("value", "Assign points")
500 | .attr("type", "button")
501 | .attr("onclick", "reassign_points()");
502 | }
503 |
504 |
505 |
506 | // create lines group
507 | svg.select("g#plotarea")
508 | .append("g")
509 | .attr("id", "lines");
510 | }
511 |
512 |
513 | // ############ UPDATE_CENTROIDS ###############
514 |
515 | function update_centroids() {
516 | d3.select("h3#info").text("Click button to reassign points.");
517 |
518 | const k = d3.select("svg").datum();
519 |
520 | const oldcentroids = svg
521 | .select("#centroids")
522 | .selectAll("circle")
523 | .data();
524 |
525 | const centroids = d3.range(k).map(e => ({
526 | x: d3.mean(data.filter(d => d.cluster == e).map(d => d.x)),
527 | y: d3.mean(data.filter(d => d.cluster == e).map(d => d.y)),
528 | cluster: e
529 | }));
530 |
531 | // calculate and display mse
532 | let kmse = 0;
533 | for (let j = 0; j < data.length; j++) {
534 | kmse = kmse + distsquared(data[j], centroids[data[j].cluster]);
535 | }
536 |
537 | d3.select("#kmse").text("Kmeans square error: " + kmse.toFixed(5));
538 |
539 | let done = false;
540 |
541 | if (d3.select("g#centroids circle").attr("r") != 0) {
542 | done = true;
543 |
544 | for (let i = 0; i < centroids.length; i++) {
545 | if (oldcentroids[i].x != centroids[i].x) done = false;
546 | if (oldcentroids[i].y != centroids[i].y) done = false;
547 | }
548 | }
549 |
550 | if (done) {
551 | d3.select("h3#info").text("Algorithm converged. Click to restart.");
552 | d3.select("div#buttons")
553 | .select("input")
554 | .attr("hidden", "hidden");
555 | } else {
556 | // update centroids
557 | svg.select("#centroids")
558 | .selectAll("circle")
559 | .data(centroids)
560 | .transition()
561 | .duration(1000)
562 | .attr("r", "5")
563 | .attr("cx", d => xScale(d.x))
564 | .attr("cy", d => yScale(d.y));
565 |
566 | svg.select("g#plotarea")
567 | .select("#lines")
568 | .append("g")
569 | .selectAll("line")
570 | .data(oldcentroids)
571 | .enter()
572 | .append("line")
573 | .attr("x1", d => xScale(d.x))
574 | .attr("y1", d => yScale(d.y))
575 | .attr("x2", d => xScale(d.x))
576 | .attr("y2", d => yScale(d.y))
577 | .attr("stroke", d => colorScale(d.cluster))
578 | .data(centroids)
579 | .transition()
580 | .duration(1000)
581 | .attr("x2", d => xScale(d.x))
582 | .attr("y2", d => yScale(d.y));
583 |
584 | d3.select("div#buttons").select("input")
585 | .attr("value", "Reassign points")
586 | .attr("onclick", "reassign_points()");
587 | }
588 | }
589 |
590 | // ############ DIST ###############
591 |
592 | // source: https://www.naftaliharris.com/blog/visualizing-k-means-clustering/
593 |
594 | function dist(w, z) {
595 | return Math.sqrt(Math.pow(w.x - z.x, 2) + Math.pow(w.y - z.y, 2));
596 | }
597 |
598 | // ############ DISTSQUARED ###############
599 |
600 | function distsquared(w, z) {
601 | return Math.pow(w.x - z.x, 2) + Math.pow(w.y - z.y, 2);
602 | }
603 |
604 |
605 | // ############ REASSIGN_POINTS ###############
606 |
607 | // source: https://www.naftaliharris.com/blog/visualizing-k-means-clustering/
608 | function reassign_points() {
609 | d3.select("h3#info").text("Click button to recalculate centroids.");
610 |
611 | const centroids = d3.select("#centroids")
612 | .selectAll("circle").data();
613 |
614 | for (let j = 0; j < data.length; j++) {
615 | let ibest = 0;
616 | let dbest = Infinity;
617 | for (let i = 0; i < centroids.length; i++) {
618 | const d = dist(data[j], centroids[i]);
619 | if (d < dbest) {
620 | dbest = d;
621 | ibest = i;
622 | }
623 | }
624 | data[j].cluster = ibest;
625 | }
626 |
627 | svg.selectAll("circle")
628 | .data(data)
629 | .style("fill", d => colorScale(d.cluster));
630 |
631 | d3.select("div#buttons").select("input")
632 | .attr("value", "Update centroids")
633 | .attr("onclick", "update_centroids()");
634 | }
635 |
636 |
637 | // ############ DOWNLOADSVG ###############
638 | // need to add styling
639 |
640 | function downloadSVG() {
641 | const svgData = new XMLSerializer().serializeToString(document.querySelector("div#plot > svg"));
642 | const blob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" });
643 | const url = URL.createObjectURL(blob);
644 | const a = document.createElement("a");
645 | a.href = url;
646 | a.download = "chart.svg";
647 | document.body.appendChild(a);
648 | a.click();
649 | document.body.removeChild(a);
650 | URL.revokeObjectURL(url);
651 | }
652 |
653 |
654 | // ########### DOWNLOAD SVG ################
655 |
656 | // https://observablehq.com/@jeremiak/download-data-button
657 | // converted to non-obvervable javascript with chatgpt
658 |
659 | function savedata() {
660 | const data = d3.selectAll("#plotarea > circle").data();
661 | const blob = new Blob([d3.csvFormat(data)], { type: "text/csv" });
662 | const url = URL.createObjectURL(blob);
663 | const a = document.createElement('a');
664 | a.href = url;
665 | a.download = "data.csv";
666 | document.body.appendChild(a);
667 | a.click();
668 | document.body.removeChild(a);
669 | URL.revokeObjectURL(url);
670 | }
671 |
--------------------------------------------------------------------------------