├── 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 | 15 | 26 | 27 |
11 |

12 |

13 |
14 |
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 |
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 | 15 | 26 | 27 |
11 |

12 |

13 |
14 |
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 |
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 | --------------------------------------------------------------------------------