diff --git a/Clustering/c3/c3.js b/Clustering/c3/c3.js
index 8a49060..3583777 100644
--- a/Clustering/c3/c3.js
+++ b/Clustering/c3/c3.js
@@ -4989,6 +4989,10 @@ c3_chart_internal_fn.getDefaultConfig = function () {
data_columns: undefined,
data_mimeType: undefined,
data_keys: undefined,
+
+ //ahmednoureldeen: custom code to support random scattring
+ data_xRandomScattering: false,
+ data_yRandomScattering: false,
// configuration for no plot-able data supplied.
data_empty_label_text: "",
// subchart
@@ -5402,6 +5406,13 @@ c3_chart_internal_fn.convertDataToTargets = function (data, appendXs) {
if (isUndefined(d[id]) || $$.data.xs[id].length <= i) {
x = undefined;
}
+ //ahmednoureldeen-- custom code
+ //console.log(config);
+ if(config.data_xRandomScattering)
+ x = x + (Math.random() * 0.6 - 0.3);
+ if(config.data_yRandomScattering)
+ value = value + (Math.random() * 0.6 - 0.3);
+ ////////////////////////////////////////
return { x: x, value: value, id: convertedId };
}).filter(function (v) {
return isDefined(v.x);
diff --git a/Clustering/css/sideBar.css b/Clustering/css/sideBar.css
index 646b184..9d3526c 100644
--- a/Clustering/css/sideBar.css
+++ b/Clustering/css/sideBar.css
@@ -1,103 +1,132 @@
#sidebar-left {
- position: fixed;
- height: 550px;
- top: 10px;
- right: 10px;
- z-index: 1000;
- padding: 1em;
- background: white;
- }
+ position: fixed;
+ height: 98%;
+ top: 10px;
+ right: 10px;
+ z-index: 1000;
+ padding: 1em;
+ background: white;
+}
#sidebar {
- position: absolute;
- height: 550px;
- top: 10px;
- right: 10px;
- z-index: 1000;
- padding: 1em;
- background: white;
- }
+ position: absolute;
+ height: 550px;
+ top: 10px;
+ right: 10px;
+ z-index: 1000;
+ padding: 1em;
+ background: white;
+}
.leaflet-popup {
- margin-right: 17px;
- }
-
+ margin-right: 17px;
+}
+
.scroll-bar-wrap:hover .cover-bar {
- opacity: 0;
+ opacity: 0;
-webkit-transition: all .5s;
}
-
- .leaflet-popup-content {
- max-width: 300px;
- height: 100px;
- margin-right: 17px;
- overflow: auto;
- }
-
- #map {
- position: fixed;
- top:0px; bottom:0; right:0; left:0;
- }
-
- #info-pane {
- position: absolute;
- height: 550px;
- top: 0px;
- right: 10px;
- z-index: 400;
- padding: 1em;
- background: black;
- text-align: right;
- }
- #sidebar {
- position: absolute;
- height: 550px;
- top: 10px;
- right: 10px;
- z-index: 1000;
- padding: 1em;
- background: white;
- }
-
- .sidebar {
+.leaflet-popup-content {
+ max-width: 300px;
+ height: 100px;
+ margin-right: 17px;
+ overflow: auto;
+}
+
+#map {
+ position: fixed;
+ top: 0px;
+ bottom: 0;
+ right: 0;
+ left: 0;
+}
+
+#info-pane {
+ position: absolute;
+ height: 550px;
+ top: 0px;
+ right: 10px;
+ z-index: 400;
+ padding: 1em;
+ background: black;
+ text-align: right;
+}
+
+#sidebar {
+ position: absolute;
+ height: 550px;
+ top: 10px;
+ right: 10px;
+ z-index: 1000;
+ padding: 1em;
+ background: white;
+}
+
+.sidebar {
position: absolute;
top: 0;
bottom: 0;
width: 100%;
overflow: hidden;
- z-index: 2000; }
- .sidebar.collapsed {
- width: 40px; }
- @media (max-width: 767px) {
- .sidebar {
- width: 250px; } }
- @media (min-width: 768px) {
- .sidebar {
- top: 10px;
- bottom: 10px;
- transition: width 500ms; } }
- @media (min-width: 768px) and (max-width: 991px) {
- .sidebar {
- width: 305px; } }
- @media (min-width: 992px) and (max-width: 1199px) {
- .sidebar {
- width: 350px; } }
- @media (min-width: 1200px) {
- .sidebar {
- width: 300px; } }
+ z-index: 2000;
+}
+
+.sidebar.collapsed {
+ width: 40px;
+}
+
+@media (max-width: 767px) {
+ .sidebar {
+ width: 250px;
+ }
+}
+
+@media (min-width: 768px) {
+ .sidebar {
+ top: 10px;
+ bottom: 10px;
+ transition: width 500ms;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .sidebar {
+ width: 305px;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .sidebar {
+ width: 350px;
+ }
+}
+
+@media (min-width: 1200px) {
+ .sidebar {
+ width: 35%;
+ }
+}
.sidebar-left {
- left: 0; }
- @media (min-width: 768px) {
- .sidebar-left {
- left: 10px; } }
+ left: 0;
+}
+
+@media (min-width: 768px) {
+ .sidebar-left {
+ left: 10px;
+ }
+}
.sidebar-right {
- right: 0; }
- @media (min-width: 768px) {
- .sidebar-right {
- right: 20px; } }
+ right: 0;
+}
+
+@media (min-width: 768px) {
+ .sidebar-right {
+ right: 20px;
+ }
+}
.sidebar-tabs {
top: 0;
@@ -143,33 +172,104 @@
.sidebar-tabs > ul + ul {
bottom: 0; }
+.sidebar-left .sidebar-tabs {
+ left: 0;
+}
+
+.sidebar-right .sidebar-tabs {
+ right: 0;
+}
+
+.sidebar-tabs, .sidebar-tabs>ul {
+ position: absolute;
+ width: 40px;
+ margin: 0;
+ padding: 0;
+}
+
+.sidebar-tabs>li, .sidebar-tabs>ul>li {
+ width: 100%;
+ height: 40px;
+ color: #47a23d;
+ font-size: 12pt;
+ overflow: hidden;
+ transition: all 80ms;
+}
+
+.sidebar-tabs>li:hover, .sidebar-tabs>ul>li:hover {
+ color: #000;
+ background-color: #eee;
+}
+
+.sidebar-tabs>li.active, .sidebar-tabs>ul>li.active {
+ color: #fff;
+ background-color: #47a23d;
+}
+
+.sidebar-tabs>li.disabled, .sidebar-tabs>ul>li.disabled {
+ color: rgba(51, 51, 51, 0.4);
+}
+
+.sidebar-tabs>li.disabled:hover, .sidebar-tabs>ul>li.disabled:hover {
+ background: transparent;
+}
+
+.sidebar-tabs>li.disabled>a, .sidebar-tabs>ul>li.disabled>a {
+ cursor: default;
+}
+
+.sidebar-tabs>li>a, .sidebar-tabs>ul>li>a {
+ display: block;
+ width: 100%;
+ height: 100%;
+ line-height: 40px;
+ color: inherit;
+ text-decoration: none;
+ text-align: center;
+}
+
+.sidebar-tabs>ul+ul {
+ bottom: 0;
+}
+
.sidebar-content {
position: absolute;
top: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.95);
overflow-x: hidden;
- overflow-y: auto; }
- .sidebar-left .sidebar-content {
- left: 40px;
- right: 0; }
- .sidebar-right .sidebar-content {
- left: 0;
- right: 40px; }
- .sidebar.collapsed > .sidebar-content {
- overflow-y: hidden; }
+ overflow-y: auto;
+}
+
+.sidebar-left .sidebar-content {
+ left: 40px;
+ right: 0;
+}
+
+.sidebar-right .sidebar-content {
+ left: 0;
+ right: 40px;
+}
+
+.sidebar.collapsed>.sidebar-content {
+ overflow-y: hidden;
+}
.sidebar-pane {
display: block;
left: 0;
right: 0;
box-sizing: border-box;
- padding: 10px 20px; }
- .sidebar-pane.active {
- display: block; }
- @media (max-width: 767px){
- .sidebar-pane {
- min-width: 385px; }
+ padding: 10px 20px;
+}
+
+.sidebar-pane.active {
+ display: block;
+}
+
+@media (max-width: 767px) {
+ .sidebar-pane {
+ min-width: 385px;
}
@media (min-width: 768px) and (max-width: 991px) {
.sidebar-pane {
@@ -181,6 +281,24 @@
.sidebar-pane {
min-width: 170px; } }
+@media (min-width: 768px) and (max-width: 991px) {
+ .sidebar-pane {
+ min-width: 305px;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .sidebar-pane {
+ min-width: 350px;
+ }
+}
+
+@media (min-width: 1200px) {
+ .sidebar-pane {
+ min-width: 340px;
+ }
+}
+
.sidebar-header {
margin: -10px -20px 0;
height: 40px;
@@ -188,15 +306,18 @@
line-height: 40px;
font-size: 14.4pt;
color: #fff;
- background-color: #47a23d; }
- .sidebar-right .sidebar-header {
- padding-left: 40px; }
-
- @media (max-width: 767px){
- .sidebar-header {
- font-size: 12pt;
- }
+ background-color: #47a23d;
+}
+
+.sidebar-right .sidebar-header {
+ padding-left: 40px;
+}
+
+@media (max-width: 767px) {
+ .sidebar-header {
+ font-size: 12pt;
}
+}
.sidebar-close {
position: absolute;
@@ -204,75 +325,172 @@
width: 40px;
height: 40px;
text-align: center;
- cursor: pointer; }
- .sidebar-left .sidebar-close {
- right: 0; }
- .sidebar-right .sidebar-close {
- left: 0; }
+ cursor: pointer;
+}
+
+.sidebar-left .sidebar-close {
+ right: 0;
+}
+
+.sidebar-right .sidebar-close {
+ left: 0;
+}
+
+.sidebar-left~.sidebar-map {
+ margin-left: 40px;
+}
+
+@media (min-width: 768px) {
+ .sidebar-left~.sidebar-map {
+ margin-left: 0;
+ }
+}
+
+.sidebar-right~.sidebar-map {
+ margin-right: 40px;
+}
+
+@media (max-width: 767px) {
+ .sidebar-right~.sidebar-map {
+ margin-right: 0;
+ }
+}
-.sidebar-left ~ .sidebar-map {
- margin-left: 40px; }
- @media (min-width: 768px) {
- .sidebar-left ~ .sidebar-map {
- margin-left: 0; } }
-
-.sidebar-right ~ .sidebar-map {
- margin-right: 40px; }
- @media (max-width: 767px) {
- .sidebar-right ~ .sidebar-map {
- margin-right: 0; }
+@media (min-width: 768px) {
+ .sidebar-right~.sidebar-map {
+ margin-right: 0;
}
- @media (min-width: 768px) {
- .sidebar-right ~ .sidebar-map {
- margin-right: 0; } }
+}
.sidebar {
- box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65); }
+ box-shadow: 0 1px 5px rgba(0, 0, 0, 0.65);
+}
+
+.sidebar.leaflet-touch {
+ box-shadow: none;
+ border-right: 2px solid rgba(0, 0, 0, 0.2);
+}
+
+@media (min-width: 768px) {
+ .sidebar {
+ border-radius: 4px;
+ }
.sidebar.leaflet-touch {
- box-shadow: none;
- border-right: 2px solid rgba(0, 0, 0, 0.2); }
- @media (min-width: 768px) {
- .sidebar {
- border-radius: 4px; }
- .sidebar.leaflet-touch {
- border: 2px solid rgba(0, 0, 0, 0.2); } }
+ border: 2px solid rgba(0, 0, 0, 0.2);
+ }
+}
@media (min-width: 768px) {
- .sidebar-left ~ .sidebar-map .leaflet-left {
- transition: left 500ms; } }
+ .sidebar-left~.sidebar-map .leaflet-left {
+ transition: left 500ms;
+ }
+}
@media (min-width: 768px) and (max-width: 991px) {
- .sidebar-left ~ .sidebar-map .leaflet-left {
- left: 315px; } }
+ .sidebar-left~.sidebar-map .leaflet-left {
+ left: 315px;
+ }
+}
@media (min-width: 992px) and (max-width: 1199px) {
- .sidebar-left ~ .sidebar-map .leaflet-left {
- left: 400px; } }
+ .sidebar-left~.sidebar-map .leaflet-left {
+ left: 400px;
+ }
+}
@media (min-width: 1200px) {
- .sidebar-left ~ .sidebar-map .leaflet-left {
- left: 470px; } }
+ .sidebar-left~.sidebar-map .leaflet-left {
+ left: 470px;
+ }
+}
@media (min-width: 768px) {
- .sidebar-left.collapsed ~ .sidebar-map .leaflet-left {
- left: 50px; } }
+ .sidebar-left.collapsed~.sidebar-map .leaflet-left {
+ left: 50px;
+ }
+}
@media (min-width: 768px) {
- .sidebar-right ~ .sidebar-map .leaflet-right {
- transition: right 500ms; } }
+ .sidebar-right~.sidebar-map .leaflet-right {
+ transition: right 500ms;
+ }
+}
@media (min-width: 768px) and (max-width: 991px) {
- .sidebar-right ~ .sidebar-map .leaflet-right {
- right: 315px; } }
+ .sidebar-right~.sidebar-map .leaflet-right {
+ right: 315px;
+ }
+}
@media (min-width: 992px) and (max-width: 1199px) {
- .sidebar-right ~ .sidebar-map .leaflet-right {
- right: 400px; } }
+ .sidebar-right~.sidebar-map .leaflet-right {
+ right: 400px;
+ }
+}
@media (min-width: 1200px) {
- .sidebar-right ~ .sidebar-map .leaflet-right {
- right: 315px; } }
+ .sidebar-right~.sidebar-map .leaflet-right {
+ right: 315px;
+ }
+}
@media (min-width: 768px) {
- .sidebar-right.collapsed ~ .sidebar-map .leaflet-right {
- right: 50px; } }
+ .sidebar-right.collapsed~.sidebar-map .leaflet-right {
+ right: 50px;
+ }
+}
+
+
+#chart {
+ width:90%;
+ min-height: 75%;
+}
+
+#data-nav{
+ max-width: 50%;
+}
+
+#viz{
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.chart-wrapper text{
+ font-size: 10px !important;
+}
+
+.d3-tip {
+ line-height: 1;
+ padding: 12px;
+ /*background-color:rgba(255, 255, 255, 0.5);*/
+ color: black;
+ border-radius: 2px;
+ z-index: 1000;
+ border: 1px solid gray;
+ background-color: #fff;
+ -webkit-box-shadow: 7px 7px 12px -9px #777777;
+ -moz-box-shadow: 7px 7px 12px -9px #777777;
+ box-shadow: 7px 7px 12px -9px #777777;
+ opacity: 0.9;
+}
+
+
+/* Creates a small triangle extender for the tooltip */
+/*.d3-tip:after {
+ box-sizing: border-box;
+ display: inline;
+ font-size: 10px;
+ width: 100%;
+ line-height: 1;
+ color: rgba(0, 0, 0, 0.8);
+ content: "\25BC";
+ position: absolute;
+ text-align: center;
+}
+
+/* Style northward tooltips differently */
+/*.d3-tip.n:after {
+ margin: -1px 0 0 0;
+ top: 100%;
+ left: 0;
+}*/
diff --git a/Clustering/distroplot/LICENSE b/Clustering/distroplot/LICENSE
new file mode 100644
index 0000000..f66d59d
--- /dev/null
+++ b/Clustering/distroplot/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Constantino Schillebeeckx
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Clustering/distroplot/README.md b/Clustering/distroplot/README.md
new file mode 100644
index 0000000..05b8094
--- /dev/null
+++ b/Clustering/distroplot/README.md
@@ -0,0 +1,6 @@
+# distroplot
+D3 violin, box and swarm plots
+
+Building on the very excellent [Violin Plot + Box Plot v3](http://bl.ocks.org/asielen/92929960988a8935d907e39e60ea8417), just putting it into a repo so that I can build off of it.
+
+
diff --git a/Clustering/distroplot/css/distrochart.css b/Clustering/distroplot/css/distrochart.css
new file mode 100644
index 0000000..4b0f88b
--- /dev/null
+++ b/Clustering/distroplot/css/distrochart.css
@@ -0,0 +1,177 @@
+/*Primary Chart*/
+
+/*Nested divs for responsiveness*/
+.chart-wrapper {
+ height:60%;
+ /* background-color: #FAF7F7; */
+}
+.chart-wrapper .inner-wrapper {
+ position: relative;
+ padding-bottom: 50%; /*Overwritten by the JS*/
+ width: 100%;
+}
+.chart-wrapper .outer-box {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+}
+.chart-wrapper .inner-box {
+ width: 100%;
+ height: 100%;
+}
+
+.chart-wrapper text {
+ font-family: sans-serif;
+ font-size: 8px;
+ width: 50px;
+ text-overflow: ellipsis;
+}
+
+.chart-wrapper .axis path,
+.chart-wrapper .axis line {
+ fill: none;
+ stroke: #888;
+ stroke-width: 2px;
+ shape-rendering: crispEdges;
+}
+
+.chart-wrapper .y.axis .tick line {
+ stroke: lightgrey;
+ opacity: 0.6;
+ stroke-dasharray: 2,1;
+ stroke-width: 1;
+ shape-rendering: crispEdges;
+}
+
+.chart-wrapper .chart-title {
+ font-size: 16px;
+}
+
+.chart-wrapper .x.axis .domain {
+ display: none;
+}
+
+.chart-wrapper div.tooltip {
+ position: absolute;
+ background-color: rgba(255,255,255,1.0);
+ color: rgba(0,0,0,1.0);
+ padding: 10px;
+ border: 1px solid rgba(0,0,0,.2);
+ z-index: 10000;
+ display: block;
+
+ font-family: Arial;
+ font-size: 13px;
+ text-align: left;
+ pointer-events: none;
+
+ white-space: nowrap;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ background: rgba(255,255,255, 0.8);
+ border: 1px solid rgba(0,0,0,0.5);
+ border-radius: 4px;
+}
+
+/*Box Plot*/
+.chart-wrapper .box-plot .box {
+ fill-opacity: 0.4;
+ stroke-width: 2;
+}
+.chart-wrapper .box-plot line {
+ stroke-width: 2px;
+}
+.chart-wrapper .box-plot circle {
+ fill: white;
+ stroke: black;
+}
+
+.chart-wrapper .box-plot .median {
+ stroke: black;
+}
+
+.chart-wrapper .box-plot circle.median {
+ /*the script makes the circles the same color as the box, you can override this in the js*/
+ fill: white !important;
+}
+
+.chart-wrapper .box-plot .mean {
+ stroke: white;
+ stroke-dasharray: 2,1;
+ stroke-width: 1px;
+}
+
+@media (max-width:500px){
+ .chart-wrapper .box-plot circle {display: none;}
+}
+
+/*Violin Plot*/
+
+.chart-wrapper .violin-plot .area {
+ shape-rendering: geometricPrecision;
+ opacity: 0.4;
+}
+
+.chart-wrapper .violin-plot .line {
+ fill: none;
+ stroke-width: 2px;
+ shape-rendering: geometricPrecision;
+}
+
+/*Notch Plot*/
+.chart-wrapper .notch-plot .notch {
+ fill-opacity: 0.4;
+ stroke-width: 2;
+}
+
+/* Point Plots*/
+.chart-wrapper .points-plot .point {
+ stroke: black;
+ stroke-width: 1px;
+}
+
+.chart-wrapper .metrics-lines {
+ stroke-width: 4px;
+}
+
+/* Non-Chart Styles for demo*/
+.chart-options {
+ min-width: 200px;
+ font-size: 13px;
+ font-family: sans-serif;
+}
+.chart-options button {
+ margin: 3px;
+ padding: 3px;
+ font-size: 12px;
+}
+.chart-options p {
+ display: inline;
+}
+@media (max-width:500px){
+ .chart-options p {display: block;}
+}
+
+
+.d3-tip {
+ line-height: 1;
+ padding: 12px;
+ /*background-color:rgba(255, 255, 255, 0.5);*/
+ color: black;
+ border-radius: 2px;
+ z-index: 1000;
+ border: 1px solid gray;
+ background-color: #fff;
+ -webkit-box-shadow: 7px 7px 12px -9px #777777;
+ -moz-box-shadow: 7px 7px 12px -9px #777777;
+ box-shadow: 7px 7px 12px -9px #777777;
+ opacity: 0.9;
+}
diff --git a/Clustering/distroplot/dat/data.csv b/Clustering/distroplot/dat/data.csv
new file mode 100644
index 0000000..06e0139
--- /dev/null
+++ b/Clustering/distroplot/dat/data.csv
@@ -0,0 +1,501 @@
+date,value
+ECS,208.4968974
+ECS,160.5328879
+FGH,292.3321976
+LKJ,95.07969441
+DER,251.6346499
+ASD,4.723143097
+TTL,221.3608926
+FGH,257.5135771
+PSD,256.6401961
+LKJ,20.19655313
+ECS,280.5287882
+FGH,195.5122557
+LKJ,177.9101782
+LKJ,35.8381779
+TTL,157.4465176
+PSD,134.3793597
+TTL,150.604782
+ASD,-163.8499657
+TTL,137.4253423
+DER,142.7192938
+PSD,180.7018929
+LKJ,115.9725529
+PSD,209.3638088
+LKJ,67.84781771
+ASD,175.231925
+FGH,51.47799284
+PSD,188.9962324
+ASD,-48.35468425
+TTL,169.423597
+LKJ,-53.22055537
+TTL,292.1632604
+DER,136.4384768
+FGH,321.5455618
+PSD,53.06249276
+FGH,340.8281495
+FGH,130.9466336
+FGH,286.8816131
+ECS,176.4176712
+LKJ,191.6883802
+DER,150.0037128
+ECS,197.7215175
+DER,305.2651151
+PSD,210.168763
+DER,115.5839981
+LKJ,175.7373095
+PSD,116.9958817
+LKJ,154.8568107
+ASD,14.6993532
+DER,198.5466972
+PSD,74.15721631
+ASD,114.734763
+PSD,102.2094761
+LKJ,177.7200953
+FGH,135.5771092
+FGH,262.2642028
+DER,146.5137898
+LKJ,157.1558524
+FGH,100.7217744
+PSD,215.9330216
+LKJ,77.73977658
+FGH,307.4118429
+FGH,183.3339337
+PSD,197.9264315
+ASD,17.60508917
+ASD,210.2650095
+ASD,-61.72121173
+ASD,114.4151786
+FGH,137.0691326
+ASD,196.452651
+ASD,-93.70487623
+ASD,94.04043151
+FGH,243.9383793
+LKJ,185.4923709
+DER,86.83137214
+TTL,189.7194604
+PSD,107.6012989
+TTL,111.3635375
+ASD,-18.48801027
+DER,284.114423
+LKJ,25.03677561
+DER,194.6109073
+FGH,222.8485575
+DER,269.0836685
+TTL,42.56959913
+FGH,263.6498678
+FGH,141.9210707
+ASD,108.4558658
+ECS,136.6209948
+LKJ,172.4753343
+PSD,147.918509
+LKJ,153.3322857
+ECS,165.9668168
+PSD,177.2947913
+FGH,-74.31511032
+FGH,335.3878377
+PSD,87.78180299
+DER,256.9765118
+ECS,156.3968699
+LKJ,187.2355674
+ASD,26.95490135
+ASD,205.3224574
+ASD,-146.8977273
+TTL,111.4247665
+TTL,39.31960853
+LKJ,165.4031941
+DER,76.54635096
+ECS,211.4411524
+FGH,85.38760996
+FGH,258.6304837
+PSD,101.40771
+FGH,319.3656086
+PSD,48.89019215
+ASD,185.5018042
+FGH,44.27040391
+LKJ,163.9139191
+PSD,64.91277185
+PSD,214.75898
+ECS,95.95428713
+TTL,152.1584732
+DER,105.5137981
+ECS,204.940937
+FGH,168.8783255
+TTL,109.6414378
+ASD,8.294135496
+ASD,170.1018831
+DER,133.4457303
+TTL,154.7432792
+DER,115.9420248
+TTL,161.6765493
+DER,318.3716388
+ECS,185.0529758
+ASD,-25.0084555
+LKJ,179.3206217
+FGH,23.77085763
+ASD,109.5537878
+DER,104.5309686
+LKJ,188.4592993
+TTL,-42.71530849
+LKJ,191.2920462
+PSD,133.7938658
+LKJ,159.0451771
+FGH,178.4659497
+LKJ,236.824034
+DER,65.69920953
+TTL,176.8594544
+FGH,224.9232276
+DER,353.1720826
+ASD,-42.54134484
+FGH,352.5103937
+FGH,100.4976596
+DER,262.7544883
+LKJ,78.31221195
+ASD,161.2249696
+LKJ,77.25946692
+FGH,320.1315855
+ECS,147.2817322
+FGH,257.4599337
+TTL,69.08830619
+LKJ,146.0831955
+FGH,113.8032144
+PSD,205.7691001
+DER,117.1322359
+TTL,130.8596499
+ASD,1.95131609
+DER,262.9490431
+DER,34.79418313
+TTL,101.7745406
+PSD,49.77164944
+DER,200.7904755
+FGH,161.5282583
+DER,216.4782181
+ASD,-33.33688556
+DER,235.903581
+LKJ,77.52683993
+ASD,109.03816
+LKJ,46.23212288
+FGH,334.8055355
+FGH,-28.40462897
+DER,259.6404954
+ECS,146.3087239
+FGH,377.0370575
+TTL,26.75431767
+FGH,263.8179041
+LKJ,-16.58595091
+PSD,225.6157298
+FGH,-42.35546988
+ECS,234.5228736
+ASD,-38.9393706
+PSD,211.1955424
+LKJ,37.78872187
+LKJ,186.3913279
+DER,162.9298056
+DER,326.0401303
+FGH,244.4557295
+ASD,121.3493094
+ASD,-4.908452899
+ECS,289.8393967
+FGH,231.050691
+ECS,185.6270916
+DER,217.0400562
+ECS,233.1733188
+TTL,-108.585529
+TTL,132.1325814
+ECS,168.6266924
+TTL,192.0853546
+LKJ,46.22287178
+PSD,192.0663673
+TTL,-76.42243079
+ASD,-166.2188619
+TTL,50.57489598
+TTL,161.6687837
+ASD,11.57283366
+ASD,176.3964678
+FGH,67.80298236
+FGH,225.2487353
+FGH,132.5723879
+ECS,276.3019917
+PSD,124.5530979
+DER,301.9152608
+FGH,85.22160659
+FGH,291.9140151
+FGH,122.4231766
+TTL,213.9817405
+ECS,164.1858424
+ASD,110.8755204
+DER,-12.51757909
+FGH,364.7130522
+TTL,25.74815884
+FGH,362.1798034
+TTL,19.35952907
+PSD,171.6071014
+PSD,124.2586256
+ECS,242.8487277
+DER,149.2189275
+TTL,153.4503189
+TTL,30.03059153
+TTL,140.9275416
+ASD,-51.29477103
+ECS,250.9379606
+FGH,158.3533996
+LKJ,130.182317
+DER,138.7092058
+FGH,253.3304494
+FGH,144.9757234
+ASD,178.5478547
+ECS,72.20396078
+ASD,553.9499109
+FGH,219.5272559
+LKJ,135.3017077
+ASD,2.750346155
+PSD,164.5810382
+ASD,29.28765195
+LKJ,171.8155041
+ASD,-62.47847974
+TTL,151.5809857
+FGH,134.6323019
+PSD,212.9892487
+FGH,89.75102376
+ECS,283.2522823
+PSD,89.39028149
+DER,278.4404473
+ASD,-109.3304066
+PSD,229.1511074
+PSD,62.34497978
+ECS,85.230187
+PSD,100.4950058
+TTL,200.2309017
+PSD,76.72850604
+ECS,229.9301867
+LKJ,72.15344724
+LKJ,195.0161825
+PSD,94.87059541
+TTL,157.0910643
+DER,65.01399632
+DER,297.1591558
+LKJ,20.07084747
+PSD,233.4660872
+DER,216.3095206
+TTL,170.52204
+PSD,78.50367791
+ECS,239.9552241
+TTL,2.147629172
+FGH,379.3151119
+LKJ,51.57920743
+ECS,261.4090462
+LKJ,43.44942227
+TTL,132.7226702
+ECS,175.8934445
+ECS,277.2232739
+FGH,184.9889427
+ASD,120.8580358
+TTL,191.6720426
+DER,187.6245982
+FGH,179.1492148
+PSD,157.2360451
+DER,80.04527985
+TTL,212.3687904
+LKJ,24.00284469
+ASD,114.4805217
+TTL,-4.064305421
+TTL,226.8353268
+FGH,227.1109639
+ECS,279.0223834
+ASD,21.41081879
+TTL,143.8646094
+DER,158.1113357
+LKJ,184.2694171
+LKJ,59.4411768
+ASD,150.9424472
+FGH,227.4581954
+DER,293.3287564
+ECS,155.2869436
+ASD,181.2817844
+PSD,118.3508146
+FGH,290.9272223
+LKJ,-25.95669287
+ECS,261.577609
+DER,137.9238059
+ASD,104.2415804
+DER,110.8406592
+LKJ,214.1830759
+ECS,182.1599734
+TTL,-80.82039329
+PSD,80.93972737
+ECS,233.3097023
+ASD,-148.9825013
+ASD,102.8203318
+ASD,17.94859818
+ECS,232.4654949
+PSD,127.3053161
+LKJ,189.5161067
+TTL,52.03000927
+ECS,266.2037164
+DER,19.61896068
+FGH,310.2054732
+LKJ,95.51888317
+FGH,565.7785986
+TTL,49.75458286
+TTL,165.5522385
+TTL,46.2049385
+LKJ,178.0625039
+ASD,17.27953926
+TTL,261.8950031
+DER,143.8183958
+ECS,250.1691319
+ASD,25.95785178
+ECS,179.6837376
+ASD,-43.26549148
+LKJ,151.4800229
+ASD,-111.4736412
+DER,233.9101271
+DER,164.7412837
+ASD,208.0000028
+ASD,20.66494709
+TTL,235.1549474
+LKJ,35.52670759
+ECS,228.8558584
+PSD,67.91927028
+ASD,514.1211521
+FGH,137.5345718
+TTL,137.2434424
+LKJ,18.38698421
+DER,188.2074573
+LKJ,-27.98708345
+ASD,196.0813888
+ECS,156.5011947
+PSD,164.2303054
+DER,155.72949
+ASD,188.3434843
+DER,172.8608446
+ASD,108.7538702
+FGH,158.4953604
+TTL,295.1204317
+ECS,202.7568375
+PSD,192.4999169
+LKJ,70.87167826
+FGH,434.4384007
+FGH,14.89312532
+ECS,282.049065
+TTL,33.9431407
+PSD,226.8977153
+TTL,26.20327452
+ASD,118.5680419
+FGH,116.6038789
+FGH,701.1076239
+LKJ,18.20232892
+ASD,97.88270558
+TTL,-57.92522621
+ECS,255.764516
+ECS,54.52055825
+PSD,206.9950256
+FGH,222.5568434
+PSD,209.7686251
+ASD,10.84328606
+LKJ,170.9119633
+DER,178.7836109
+DER,404.1838318
+ECS,75.59836591
+FGH,335.7867388
+ECS,188.021937
+LKJ,35.05881498
+TTL,60.93804001
+ASD,105.3636852
+LKJ,45.08619354
+PSD,182.6039742
+LKJ,41.82386356
+ASD,126.2237861
+ECS,106.6725667
+ASD,167.2021452
+FGH,88.59645944
+FGH,334.4911434
+LKJ,124.9516826
+FGH,308.8227928
+DER,98.87445255
+LKJ,127.9427486
+FGH,139.3041594
+TTL,144.5111193
+LKJ,146.7772939
+ASD,111.0311866
+ECS,143.0060368
+TTL,266.3802546
+ASD,-56.52883643
+TTL,165.2809079
+PSD,76.76795913
+FGH,357.0434218
+ECS,39.42975856
+DER,200.3437131
+ASD,-7.375059038
+FGH,402.6828173
+PSD,138.1697845
+TTL,133.987686
+PSD,133.9946493
+DER,419.2625726
+ASD,-54.20342289
+TTL,177.5902054
+ASD,-5.268905046
+ASD,110.6727969
+DER,76.98892296
+ECS,220.6703596
+ECS,84.80589751
+FGH,-133.7878417
+DER,159.1013487
+ASD,101.4781021
+FGH,221.1297277
+TTL,160.6555138
+PSD,100.9936022
+TTL,126.2748973
+ECS,66.52701701
+ASD,110.6464315
+FGH,36.15946532
+PSD,226.3014108
+TTL,21.72055667
+ECS,167.935579
+LKJ,20.81132199
+PSD,227.8543829
+ASD,25.76979155
+TTL,244.1586111
+ECS,177.1136973
+PSD,221.050831
+PSD,110.4931264
+ECS,223.5116122
+PSD,122.060817
+TTL,148.775981
+DER,135.7563109
+TTL,208.8947212
+DER,131.5311888
+LKJ,179.4150518
+TTL,27.32787774
+TTL,231.3493247
+TTL,37.53502314
+ASD,118.1465839
+ECS,31.11532162
+FGH,267.8910308
+DER,102.2021658
+TTL,193.4957639
+PSD,63.44883985
+ECS,261.3125672
+ECS,33.74883377
+PSD,195.3846233
+DER,83.74423595
+FGH,484.2443322
+ASD,-38.29618771
+TTL,147.840383
+ASD,1.485343235
+LKJ,165.556157
+PSD,144.0741205
+DER,403.9901334
+ASD,2.132530501
+DER,350.3402704
+TTL,-25.94177964
+ECS,240.3780517
+LKJ,41.14205171
+FGH,35.363135
+FGH,113.1600897
+ECS,168.8637489
+ASD,-25.96838117
+TTL,125.7448262
+ASD,-133.4504018
+PSD,165.2567402
+TTL,39.80787742
diff --git a/Clustering/distroplot/index.html b/Clustering/distroplot/index.html
new file mode 100644
index 0000000..e52d0e3
--- /dev/null
+++ b/Clustering/distroplot/index.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Show:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Clustering/distroplot/js/README.md b/Clustering/distroplot/js/README.md
new file mode 100644
index 0000000..24fdbf2
--- /dev/null
+++ b/Clustering/distroplot/js/README.md
@@ -0,0 +1,50 @@
+Moving some of the documentation from the source code into this readme to make options more transparent.
+
+### Box plot
+
+* @param [options.show=true] Toggle the whole plot on and off
+* @param [options.showBox=true] Show the box part of the box plot
+* @param [options.showWhiskers=true] Show the whiskers
+* @param [options.showMedian=true] Show the median line
+* @param [options.showMean=false] Show the mean line
+* @param [options.medianCSize=3] The size of the circle on the median
+* @param [options.showOutliers=true] Plot outliers
+* @param [options.boxwidth=30] The max percent of the group rangeBand that the box can be
+* @param [options.lineWidth=boxWidth] The max percent of the group rangeBand that the line can be
+* @param [options.outlierScatter=false] Spread out the outliers so they don't all overlap (in development)
+* @param [options.outlierCSize=2] Size of the outliers
+* @param [options.colors=chart default] The color mapping for the box plot
+
+### Notched box plot
+
+* @param [options.show=true] Toggle the whole plot on and off
+* @param [options.showNotchBox=true] Show the notch box
+* @param [options.showLines=false] Show lines at the confidence intervals
+* @param [options.boxWidth=35] The width of the widest part of the box
+* @param [options.medianWidth=20] The width of the narrowist part of the box
+* @param [options.lineWidth=50] The width of the confidence interval lines
+* @param [options.notchStyle=null] null=traditional style, 'box' cuts out the whole notch in right angles
+* @param [options.colors=chart default] The color mapping for the notch boxes
+
+### Violin plot
+
+* @param [options.showViolinPlot=true] True or False, show the violin plot
+* @param [options.resolution=100 default]
+* @param [options.bandwidth=10 default] May need higher bandwidth for larger data sets
+* @param [options.width=50] The max percent of the group rangeBand that the violin can be
+* @param [options.interpolation=''] How to render the violin
+* @param [options.clamp=0 default]
+* 0 = keep data within chart min and max, clamp once data = 0. May extend beyond data set min and max
+* 1 = clamp at min and max of data set. Possibly no tails
+* -1 = extend chart axis to make room for data to interpolate to 0. May extend axis and data set min and max
+
+### Swarm/scatter plot
+
+* @param [options.show=true] Toggle the whole plot on and off
+* @param [options.showPlot=false] True or false, show points
+* @param [options.plotType='none'] Options: no scatter = (false or 'none'); scatter points= (true or [amount=% of width (default=10)]); beeswarm points = ('beeswarm')
+* @param [options.pointSize=6] Diameter of the circle in pizels (not the radius)
+* @param [options.showLines=['median']] Can equal any of the metrics lines
+* @param [options.showbeanLines=false] Options: no lines = false
+* @param [options.beanWidth=20] % width
+* @param [options.colors=chart default]
diff --git a/Clustering/distroplot/js/distrochart.js b/Clustering/distroplot/js/distrochart.js
new file mode 100644
index 0000000..35296f6
--- /dev/null
+++ b/Clustering/distroplot/js/distrochart.js
@@ -0,0 +1,1575 @@
+/**
+ * @fileOverview A D3 based distribution chart system. Supports: Box plots, Violin plots, Notched box plots, trend lines, beeswarm plot
+ * @version 3.0
+ */
+
+
+/**
+ * Creates a box plot, violin plot, and or notched box plot
+ * @param settings Configuration options for the base plot
+ * @param settings.data The data for the plot
+ * @param settings.xName The name of the column that should be used for the x groups
+ * @param settings.yName The name of the column used for the y values
+ * @param {string} settings.selector The selector string for the main chart div
+ * @param [settings.axisLabels={}] Defaults to the xName and yName
+ * @param [settings.yTicks = 1] 1 = default ticks. 2 = double, 0.5 = half
+ * @param [settings.scale='linear'] 'linear' or 'log' - y scale of the chart
+ * @param [settings.chartSize={width:parent width, height:500}] The height and width of the chart itself (doesn't include the container)
+ * @param [settings.margin={top: 15, right: 60, bottom: 40, left: 50}] The margins around the chart (inside the main div)
+ * @param [settings.constrainExtremes=false] Should the y scale include outliers?
+ * @returns {object} chart A chart object
+ */
+function makeDistroChart(settings) {
+
+ var chart = {};
+
+ // Defaults
+ chart.settings = {
+ data: null,
+ xName: null,
+ yName: null,
+ selector: null,
+ axisLables: null,
+ title: null,
+ yTicks: 1,
+ scale: 'linear',
+ chartSize: {width: getParentWidth(settings), height: getParentHeight(settings)},
+ margin: {top: 15, right: 10, bottom: 50, left: 50},
+ constrainExtremes: false,
+ color: d3.scale.category10()
+ };
+ for (var setting in settings) {
+ chart.settings[setting] = settings[setting]
+ }
+
+ if (chart.settings.title) chart.settings['margin']['top'] += 35;
+
+ function getParentWidth(settings) {
+ var sel = settings.selector;
+ return jQuery(sel).width();
+ }
+
+ function getParentHeight(settings) {
+ var sel = settings.selector;
+ var h = jQuery(sel).height();
+ return h > 0 ? h : 500;
+ }
+
+
+ function formatAsFloat(d) {
+ if (d % 1 !== 0) {
+ return d3.format(".2f")(d);
+ } else {
+ return d3.format(".0f")(d);
+ }
+ }
+
+ function logFormatNumber(d) {
+ var x = Math.log(d) / Math.log(10) + 1e-6;
+ return Math.abs(x - Math.floor(x)) < 0.6 ? formatAsFloat(d) : "";
+ }
+
+ chart.yFormatter = formatAsFloat;
+
+ chart.data = chart.settings.data;
+
+ chart.groupObjs = {}; //The data organized by grouping and sorted as well as any metadata for the groups
+ chart.objs = {mainDiv: null, chartDiv: null, g: null, xAxis: null, yAxis: null};
+ chart.colorFunct = null;
+
+ /**
+ * Takes an array, function, or object mapping and created a color function from it
+ * @param {function|[]|object} colorOptions
+ * @returns {function} Function to be used to determine chart colors
+ */
+ function getColorFunct(colorOptions) {
+ if (typeof colorOptions == 'function') {
+ return colorOptions
+ } else if (Array.isArray(colorOptions)) {
+ // If an array is provided, map it to the domain
+ var colorMap = {}, cColor = 0;
+ for (var cName in chart.groupObjs) {
+ colorMap[cName] = colorOptions[cColor];
+ cColor = (cColor + 1) % colorOptions.length;
+ }
+ return function (group) {
+ return colorMap[group];
+ }
+ } else if (typeof colorOptions == 'object') {
+ // if an object is provided, assume it maps to the colors
+ return function (group) {
+ return colorOptions[group];
+ }
+ } else {
+ return d3.scale.category10();
+ }
+ }
+
+ /**
+ * Takes a percentage as returns the values that correspond to that percentage of the group range witdh
+ * @param objWidth Percentage of range band
+ * @param gName The bin name to use to get the x shift
+ * @returns {{left: null, right: null, middle: null}}
+ */
+ function getObjWidth(objWidth, gName) {
+ var objSize = {left: null, right: null, middle: null};
+ var width = chart.xScale.rangeBand() * (objWidth / 100);
+ var padding = (chart.xScale.rangeBand() - width) / 2;
+ var gShift = chart.xScale(gName);
+ objSize.middle = chart.xScale.rangeBand() / 2 + gShift;
+ objSize.left = padding + gShift;
+ objSize.right = objSize.left + width;
+ return objSize;
+ }
+
+ /**
+ * Adds jitter to the scatter point plot
+ * @param doJitter true or false, add jitter to the point
+ * @param width percent of the range band to cover with the jitter
+ * @returns {number}
+ */
+ function addJitter(doJitter, width) {
+ if (doJitter !== true || width == 0) {
+ return 0
+ }
+ return Math.floor(Math.random() * width) - width / 2;
+ }
+
+ function shallowCopy(oldObj) {
+ var newObj = {};
+ for (var i in oldObj) {
+ if (oldObj.hasOwnProperty(i)) {
+ newObj[i] = oldObj[i];
+ }
+ }
+ return newObj;
+ }
+
+ /**
+ * Closure that creates the tooltip hover function
+ * @param groupName Name of the x group
+ * @param metrics Object to use to get values for the group
+ * @returns {Function} A function that provides the values for the tooltip
+ */
+ function tooltipHover(groupName, metrics) {
+ var tooltipString = "Group: " + groupName;
+ tooltipString += "
Max: " + formatAsFloat(metrics.max, 0.1);
+ tooltipString += "
Q3: " + formatAsFloat(metrics.quartile3);
+ tooltipString += "
Median: " + formatAsFloat(metrics.median);
+ tooltipString += "
Q1: " + formatAsFloat(metrics.quartile1);
+ tooltipString += "
Min: " + formatAsFloat(metrics.min);
+ return function () {
+ chart.objs.tooltip.transition().duration(200).style("opacity", 0.9);
+ chart.objs.tooltip.html(tooltipString)
+ };
+ }
+
+ /**
+ * Parse the data and calculates base values for the plots
+ */
+ !function prepareData() {
+ function calcMetrics(values) {
+
+ var metrics = { //These are the original non�scaled values
+ max: null,
+ upperOuterFence: null,
+ upperInnerFence: null,
+ quartile3: null,
+ median: null,
+ mean: null,
+ iqr: null,
+ quartile1: null,
+ lowerInnerFence: null,
+ lowerOuterFence: null,
+ min: null
+ };
+
+ metrics.min = d3.min(values);
+ metrics.quartile1 = d3.quantile(values, 0.25);
+ metrics.median = d3.median(values);
+ metrics.mean = d3.mean(values);
+ metrics.quartile3 = d3.quantile(values, 0.75);
+ metrics.max = d3.max(values);
+ metrics.iqr = metrics.quartile3 - metrics.quartile1;
+
+ //The inner fences are the closest value to the IQR without going past it (assumes sorted lists)
+ var LIF = metrics.quartile1 - (1.5 * metrics.iqr);
+ var UIF = metrics.quartile3 + (1.5 * metrics.iqr);
+ for (var i = 0; i <= values.length; i++) {
+ if (values[i] < LIF) {
+ continue;
+ }
+ if (!metrics.lowerInnerFence && values[i] >= LIF) {
+ metrics.lowerInnerFence = values[i];
+ continue;
+ }
+ if (values[i] > UIF) {
+ metrics.upperInnerFence = values[i - 1];
+ break;
+ }
+ }
+
+
+ metrics.lowerOuterFence = metrics.quartile1 - (3 * metrics.iqr);
+ metrics.upperOuterFence = metrics.quartile3 + (3 * metrics.iqr);
+ if (!metrics.lowerInnerFence) {
+ metrics.lowerInnerFence = metrics.min;
+ }
+ if (!metrics.upperInnerFence) {
+ metrics.upperInnerFence = metrics.max;
+ }
+ return metrics
+ }
+
+ var current_x = null;
+ var current_y = null;
+ var current_row;
+
+ // Group the values
+ for (current_row = 0; current_row < chart.data.length; current_row++) {
+ current_x = chart.data[current_row][chart.settings.xName];
+ current_y = chart.data[current_row][chart.settings.yName];
+
+ if (chart.groupObjs.hasOwnProperty(current_x)) {
+ chart.groupObjs[current_x].values.push(current_y);
+ } else {
+ chart.groupObjs[current_x] = {};
+ chart.groupObjs[current_x].values = [current_y];
+ }
+ }
+
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].values.sort(d3.ascending);
+ chart.groupObjs[cName].metrics = {};
+ chart.groupObjs[cName].metrics = calcMetrics(chart.groupObjs[cName].values);
+
+ }
+ }();
+
+ /**
+ * Prepare the chart settings and chart div and svg
+ */
+ !function prepareSettings() {
+ //Set base settings
+ chart.margin = chart.settings.margin;
+ chart.divWidth = chart.settings.chartSize.width;
+ chart.divHeight = chart.settings.chartSize.height;
+ chart.width = chart.divWidth - chart.margin.left - chart.margin.right;
+ chart.height = chart.divHeight - chart.margin.top - chart.margin.bottom;
+ chart.title = chart.settings.title;
+
+ if (chart.settings.axisLabels) {
+ chart.xAxisLable = chart.settings.axisLabels.xAxis;
+ chart.yAxisLable = chart.settings.axisLabels.yAxis;
+ } else {
+ chart.xAxisLable = chart.settings.xName;
+ chart.yAxisLable = chart.settings.yName;
+ }
+
+ if (chart.settings.scale === 'log') {
+ chart.yScale = d3.scale.log();
+ chart.yFormatter = logFormatNumber;
+ } else {
+ chart.yScale = d3.scale.linear();
+ }
+
+ if (chart.settings.constrainExtremes === true) {
+ var fences = [];
+ for (var cName in chart.groupObjs) {
+ fences.push(chart.groupObjs[cName].metrics.lowerInnerFence);
+ fences.push(chart.groupObjs[cName].metrics.upperInnerFence);
+ }
+ chart.range = d3.extent(fences);
+
+ } else {
+ chart.range = d3.extent(chart.data, function (d) {return d[chart.settings.yName];});
+ }
+
+ chart.colorFunct = getColorFunct(chart.settings.colors);
+
+ // Build Scale functions
+ chart.yScale.range([chart.height, 0]).domain(chart.range).nice().clamp(true);
+ chart.xScale = d3.scale.ordinal().domain(Object.keys(chart.groupObjs)).rangeBands([0, chart.width]);
+
+ //Build Axes Functions
+ chart.objs.yAxis = d3.svg.axis()
+ .scale(chart.yScale)
+ .orient("left")
+ .tickFormat(chart.yFormatter)
+ .outerTickSize(0)
+ .innerTickSize(-chart.width + (chart.margin.right + chart.margin.left));
+ chart.objs.yAxis.ticks(chart.objs.yAxis.ticks()*chart.settings.yTicks);
+ chart.objs.xAxis = d3.svg.axis().scale(chart.xScale).orient("bottom").tickSize(5);
+ }();
+
+ /**
+ * Updates the chart based on the current settings and window size
+ * @returns {*}
+ */
+ chart.update = function () {
+ // Update chart size based on view port size
+ chart.width = parseInt(chart.objs.chartDiv.style("width"), 10) - (chart.margin.left + chart.margin.right);
+ chart.height = parseInt(chart.objs.chartDiv.style("height"), 10) - (chart.margin.top + chart.margin.bottom);
+
+ // Update scale functions
+ chart.xScale.rangeBands([0, chart.width]);
+ chart.yScale.range([chart.height, 0]);
+
+ // Update the yDomain if the Violin plot clamp is set to -1 meaning it will extend the violins to make nice points
+ if (chart.violinPlots && chart.violinPlots.options.show == true && chart.violinPlots.options._yDomainVP != null) {
+ chart.yScale.domain(chart.violinPlots.options._yDomainVP).nice().clamp(true);
+ } else {
+ chart.yScale.domain(chart.range).nice().clamp(true);
+ }
+
+ //Update axes
+ chart.objs.g.select('.x.axis').attr("transform", "translate(0," + chart.height + ")").call(chart.objs.xAxis)
+ .selectAll("text")
+ .attr("y", 10)
+ .attr("x", 0)
+ .style("text-anchor", "middle");
+ chart.objs.g.select('.x.axis').selectAll("text.label")
+ .attr("y", 42)
+ chart.objs.g.select('.x.axis .label').attr("x", chart.width / 2);
+ chart.objs.g.select('.y.axis').call(chart.objs.yAxis.innerTickSize(-chart.width));
+ chart.objs.g.select('.y.axis .label').attr("x", -chart.height / 2);
+ chart.objs.chartDiv.select('svg').attr("width", chart.width + (chart.margin.left + chart.margin.right)).attr("height", chart.height + (chart.margin.top + chart.margin.bottom));
+
+ return chart;
+ };
+
+ /**
+ * Prepare the chart html elements
+ */
+ !function prepareChart() {
+ // Build main div and chart div
+ chart.objs.mainDiv = d3.select(chart.settings.selector)
+
+ // Add all the divs to make it centered and responsive
+ chart.objs.mainDiv.append("div")
+ .attr("class", "inner-wrapper")
+ .style("padding-bottom", (chart.divHeight / chart.divWidth) * 100 + "%")
+ .append("div").attr("class", "outer-box")
+ .append("div").attr("class", "inner-box");
+ // Capture the inner div for the chart (where the chart actually is)
+ chart.selector = chart.settings.selector + " .inner-box";
+ chart.objs.chartDiv = d3.select(chart.selector);
+ d3.select(window).on('resize.' + chart.selector, chart.update);
+
+ // Create the svg
+ chart.objs.g = chart.objs.chartDiv.append("svg")
+ .attr("class", "chart-area")
+ .attr("width", chart.width + (chart.margin.left + chart.margin.right))
+ .attr("height", chart.height + (chart.margin.top + chart.margin.bottom))
+ .append("g")
+ .attr("transform", "translate(" + chart.margin.left + "," + chart.margin.top + ")");
+
+ // Create title
+ chart.objs.title = chart.objs.g.append("text")
+ .attr("class", "chart-title")
+ .attr("x", (chart.width/2))
+ .attr("y", -(chart.margin.top / 2))
+ .attr("text-anchor", "middle")
+ .text(chart.title);
+
+ // Create axes
+ chart.objs.axes = chart.objs.g.append("g").attr("class", "axis");
+ chart.objs.axes.append("g")
+ .attr("class", "x axis")
+ .attr("transform", "translate(0," + chart.height + ")")
+ .call(chart.objs.xAxis)
+ .append("text")
+ .attr("class", "label")
+ .style("text-anchor", "middle")
+ .text(chart.xAxisLable);
+ chart.objs.axes.append("g")
+ .attr("class", "y axis")
+ .call(chart.objs.yAxis)
+ .append("text")
+ .attr("class", "label")
+ .attr("transform", "rotate(-90)")
+ .attr("y", -42)
+ .attr("x", -chart.height / 2)
+ .attr("dy", ".71em")
+ .style("text-anchor", "middle")
+ .text(chart.yAxisLable);
+
+ // Create tooltip div
+ chart.objs.tooltip = chart.objs.mainDiv.append('div').attr('class', 'tooltip');
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].g = chart.objs.g.append("g").attr("class", "group");
+ chart.groupObjs[cName].g.on("mouseover", function () {
+ chart.objs.tooltip
+ .style("display", null)
+ .style("left", (d3.event.pageX - 50) + "px")
+ .style("top", (d3.event.pageY - 50) + "px");
+ }).on("mouseout", function () {
+ chart.objs.tooltip.style("display", "none");
+ }).on("mousemove", tooltipHover(cName, chart.groupObjs[cName].metrics))
+ }
+ chart.update();
+ }();
+
+ /**
+ * Render a violin plot on the current chart
+ * @param options
+ * @param [options.showViolinPlot=true] True or False, show the violin plot
+ * @param [options.resolution=100 default]
+ * @param [options.bandwidth=10 default] May need higher bandwidth for larger data sets
+ * @param [options.width=50] The max percent of the group rangeBand that the violin can be
+ * @param [options.interpolation=''] How to render the violin
+ * @param [options.clamp=0 default]
+ * 0 = keep data within chart min and max, clamp once data = 0. May extend beyond data set min and max
+ * 1 = clamp at min and max of data set. Possibly no tails
+ * -1 = extend chart axis to make room for data to interpolate to 0. May extend axis and data set min and max
+ * @param [options.colors=chart default] The color mapping for the violin plot
+ * @returns {*} The chart object
+ */
+ chart.renderViolinPlot = function (options) {
+ chart.violinPlots = {};
+
+ var defaultOptions = {
+ show: true,
+ showViolinPlot: true,
+ resolution: 100,
+ bandwidth: 2,
+ width: 50,
+ interpolation: 'cardinal',
+ clamp: 1,
+ colors: chart.colorFunct,
+ _yDomainVP: null // If the Violin plot is set to close all violin plots, it may need to extend the domain, that extended domain is stored here
+ };
+ chart.violinPlots.options = shallowCopy(defaultOptions);
+ for (var option in options) {
+ chart.violinPlots.options[option] = options[option]
+ }
+ var vOpts = chart.violinPlots.options;
+
+ // Create violin plot objects
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].violin = {};
+ chart.groupObjs[cName].violin.objs = {};
+ }
+
+ /**
+ * Take a new set of options and redraw the violin
+ * @param updateOptions
+ */
+ chart.violinPlots.change = function (updateOptions) {
+ if (updateOptions) {
+ for (var key in updateOptions) {
+ vOpts[key] = updateOptions[key]
+ }
+ }
+
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].violin.objs.g.remove()
+ }
+
+ chart.violinPlots.prepareViolin();
+ chart.violinPlots.update();
+ };
+
+ chart.violinPlots.reset = function () {
+ chart.violinPlots.change(defaultOptions)
+ };
+ chart.violinPlots.show = function (opts) {
+ if (opts !== undefined) {
+ opts.show = true;
+ if (opts.reset) {
+ chart.violinPlots.reset()
+ }
+ } else {
+ opts = {show: true};
+ }
+ chart.violinPlots.change(opts);
+
+ };
+
+ chart.violinPlots.hide = function (opts) {
+ if (opts !== undefined) {
+ opts.show = false;
+ if (opts.reset) {
+ chart.violinPlots.reset()
+ }
+ } else {
+ opts = {show: false};
+ }
+ chart.violinPlots.change(opts);
+
+ };
+
+ /**
+ * Update the violin obj values
+ */
+ chart.violinPlots.update = function () {
+ var cName, cViolinPlot;
+
+ for (cName in chart.groupObjs) {
+ cViolinPlot = chart.groupObjs[cName].violin;
+
+ // Build the violins sideways, so use the yScale for the xScale and make a new yScale
+ var xVScale = chart.yScale.copy();
+
+
+ // Create the Kernel Density Estimator Function
+ cViolinPlot.kde = kernelDensityEstimator(eKernel(vOpts.bandwidth), xVScale.ticks(vOpts.resolution));
+ cViolinPlot.kdedata = cViolinPlot.kde(chart.groupObjs[cName].values);
+
+ var interpolateMax = chart.groupObjs[cName].metrics.max,
+ interpolateMin = chart.groupObjs[cName].metrics.min;
+
+ if (vOpts.clamp == 0 || vOpts.clamp == -1) { //
+ // When clamp is 0, calculate the min and max that is needed to bring the violin plot to a point
+ // interpolateMax = the Minimum value greater than the max where y = 0
+ interpolateMax = d3.min(cViolinPlot.kdedata.filter(function (d) {
+ return (d.x > chart.groupObjs[cName].metrics.max && d.y == 0)
+ }), function (d) {
+ return d.x;
+ });
+ // interpolateMin = the Maximum value less than the min where y = 0
+ interpolateMin = d3.max(cViolinPlot.kdedata.filter(function (d) {
+ return (d.x < chart.groupObjs[cName].metrics.min && d.y == 0)
+ }), function (d) {
+ return d.x;
+ });
+ // If clamp is -1 we need to extend the axises so that the violins come to a point
+ if (vOpts.clamp == -1) {
+ kdeTester = eKernelTest(eKernel(vOpts.bandwidth), chart.groupObjs[cName].values);
+ if (!interpolateMax) {
+ var interMaxY = kdeTester(chart.groupObjs[cName].metrics.max);
+ var interMaxX = chart.groupObjs[cName].metrics.max;
+ var count = 25; // Arbitrary limit to make sure we don't get an infinite loop
+ while (count > 0 && interMaxY != 0) {
+ interMaxY = kdeTester(interMaxX);
+ interMaxX += 1;
+ count -= 1;
+ }
+ interpolateMax = interMaxX;
+ }
+ if (!interpolateMin) {
+ var interMinY = kdeTester(chart.groupObjs[cName].metrics.min);
+ var interMinX = chart.groupObjs[cName].metrics.min;
+ var count = 25; // Arbitrary limit to make sure we don't get an infinite loop
+ while (count > 0 && interMinY != 0) {
+ interMinY = kdeTester(interMinX);
+ interMinX -= 1;
+ count -= 1;
+ }
+ interpolateMin = interMinX;
+ }
+
+ }
+ // Check to see if the new values are outside the existing chart range
+ // If they are assign them to the master _yDomainVP
+ if (!vOpts._yDomainVP) vOpts._yDomainVP = chart.range.slice(0);
+ if (interpolateMin && interpolateMin < vOpts._yDomainVP[0]) {
+ vOpts._yDomainVP[0] = interpolateMin;
+ }
+ if (interpolateMax && interpolateMax > vOpts._yDomainVP[1]) {
+ vOpts._yDomainVP[1] = interpolateMax;
+ }
+
+
+ }
+
+
+ if (vOpts.showViolinPlot) {
+ chart.update();
+ xVScale = chart.yScale.copy();
+
+ // Need to recalculate the KDE because the xVScale changed
+ cViolinPlot.kde = kernelDensityEstimator(eKernel(vOpts.bandwidth), xVScale.ticks(vOpts.resolution));
+ cViolinPlot.kdedata = cViolinPlot.kde(chart.groupObjs[cName].values);
+ }
+
+ cViolinPlot.kdedata = cViolinPlot.kdedata
+ .filter(function (d) {
+ return (!interpolateMin || d.x >= interpolateMin)
+ })
+ .filter(function (d) {
+ return (!interpolateMax || d.x <= interpolateMax)
+ });
+ }
+ for (cName in chart.groupObjs) {
+ cViolinPlot = chart.groupObjs[cName].violin;
+
+ // Get the violin width
+ var objBounds = getObjWidth(vOpts.width, cName);
+ var width = (objBounds.right - objBounds.left) / 2;
+
+ var yVScale = d3.scale.linear()
+ .range([width, 0])
+ .domain([0, d3.max(cViolinPlot.kdedata, function (d) {return d.y;})])
+ .clamp(true);
+
+ var area = d3.svg.area()
+ .interpolate(vOpts.interpolation)
+ .x(function (d) {return xVScale(d.x);})
+ .y0(width)
+ .y1(function (d) {return yVScale(d.y);});
+
+ var line = d3.svg.line()
+ .interpolate(vOpts.interpolation)
+ .x(function (d) {return xVScale(d.x);})
+ .y(function (d) {return yVScale(d.y)});
+
+ if (cViolinPlot.objs.left.area) {
+ cViolinPlot.objs.left.area
+ .datum(cViolinPlot.kdedata)
+ .attr("d", area);
+ cViolinPlot.objs.left.line
+ .datum(cViolinPlot.kdedata)
+ .attr("d", line);
+
+ cViolinPlot.objs.right.area
+ .datum(cViolinPlot.kdedata)
+ .attr("d", area);
+ cViolinPlot.objs.right.line
+ .datum(cViolinPlot.kdedata)
+ .attr("d", line);
+ }
+
+ // Rotate the violins
+ cViolinPlot.objs.left.g.attr("transform", "rotate(90,0,0) translate(0,-" + objBounds.left + ") scale(1,-1)");
+ cViolinPlot.objs.right.g.attr("transform", "rotate(90,0,0) translate(0,-" + objBounds.right + ")");
+ }
+ };
+
+ /**
+ * Create the svg elements for the violin plot
+ */
+ chart.violinPlots.prepareViolin = function () {
+ var cName, cViolinPlot;
+
+ if (vOpts.colors) {
+ chart.violinPlots.color = getColorFunct(vOpts.colors);
+ } else {
+ chart.violinPlots.color = chart.colorFunct
+ }
+
+ if (vOpts.show == false) {return}
+
+ for (cName in chart.groupObjs) {
+ cViolinPlot = chart.groupObjs[cName].violin;
+
+ cViolinPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "violin-plot");
+ cViolinPlot.objs.left = {area: null, line: null, g: null};
+ cViolinPlot.objs.right = {area: null, line: null, g: null};
+
+ cViolinPlot.objs.left.g = cViolinPlot.objs.g.append("g");
+ cViolinPlot.objs.right.g = cViolinPlot.objs.g.append("g");
+
+ if (vOpts.showViolinPlot !== false) {
+ //Area
+ cViolinPlot.objs.left.area = cViolinPlot.objs.left.g.append("path")
+ .attr("class", "area")
+ .style("fill", chart.violinPlots.color(cName));
+ cViolinPlot.objs.right.area = cViolinPlot.objs.right.g.append("path")
+ .attr("class", "area")
+ .style("fill", chart.violinPlots.color(cName));
+
+ //Lines
+ cViolinPlot.objs.left.line = cViolinPlot.objs.left.g.append("path")
+ .attr("class", "line")
+ .attr("fill", 'none')
+ .style("stroke", chart.violinPlots.color(cName));
+ cViolinPlot.objs.right.line = cViolinPlot.objs.right.g.append("path")
+ .attr("class", "line")
+ .attr("fill", 'none')
+ .style("stroke", chart.violinPlots.color(cName));
+ }
+
+ }
+
+ };
+
+ function kernelDensityEstimator(kernel, x) {
+ return function (sample) {
+ return x.map(function (x) {
+ return {x:x, y:d3.mean(sample, function (v) {return kernel(x - v);})};
+ });
+ };
+ }
+
+ function eKernel(scale) {
+ return function (u) {
+ return Math.abs(u /= scale) <= 1 ? .75 * (1 - u * u) / scale : 0;
+ };
+ }
+
+ // Used to find the roots for adjusting violin axis
+ // Given an array, find the value for a single point, even if it is not in the domain
+ function eKernelTest(kernel, array) {
+ return function (testX) {
+ return d3.mean(array, function (v) {return kernel(testX - v);})
+ }
+ }
+
+ chart.violinPlots.prepareViolin();
+
+ d3.select(window).on('resize.' + chart.selector + '.violinPlot', chart.violinPlots.update);
+ chart.violinPlots.update();
+ return chart;
+ };
+
+ /**
+ * Render a box plot on the current chart
+ * @param options
+ * @param [options.show=true] Toggle the whole plot on and off
+ * @param [options.showBox=true] Show the box part of the box plot
+ * @param [options.showWhiskers=true] Show the whiskers
+ * @param [options.showMedian=true] Show the median line
+ * @param [options.showMean=false] Show the mean line
+ * @param [options.medianCSize=3] The size of the circle on the median
+ * @param [options.showOutliers=true] Plot outliers
+ * @param [options.boxwidth=30] The max percent of the group rangeBand that the box can be
+ * @param [options.lineWidth=boxWidth] The max percent of the group rangeBand that the line can be
+ * @param [options.outlierScatter=false] Spread out the outliers so they don't all overlap (in development)
+ * @param [options.outlierCSize=2] Size of the outliers
+ * @param [options.colors=chart default] The color mapping for the box plot
+ * @returns {*} The chart object
+ */
+ chart.renderBoxPlot = function (options) {
+ chart.boxPlots = {};
+
+ // Defaults
+ var defaultOptions = {
+ show: true,
+ showBox: true,
+ showWhiskers: true,
+ showMedian: true,
+ showMean: false,
+ medianCSize: 3.5,
+ showOutliers: true,
+ boxWidth: 30,
+ lineWidth: null,
+ scatterOutliers: false,
+ outlierCSize: 2.5,
+ colors: chart.colorFunct
+ };
+ chart.boxPlots.options = shallowCopy(defaultOptions);
+ for (var option in options) {
+ chart.boxPlots.options[option] = options[option]
+ }
+ var bOpts = chart.boxPlots.options;
+
+ //Create box plot objects
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].boxPlot = {};
+ chart.groupObjs[cName].boxPlot.objs = {};
+ }
+
+
+ /**
+ * Calculates all the outlier points for each group
+ */
+ !function calcAllOutliers() {
+
+ /**
+ * Create lists of the outliers for each content group
+ * @param cGroup The object to modify
+ * @return null Modifies the object in place
+ */
+ function calcOutliers(cGroup) {
+ var cExtremes = [];
+ var cOutliers = [];
+ var cOut, idx;
+ for (idx = 0; idx <= cGroup.values.length; idx++) {
+ cOut = {value: cGroup.values[idx]};
+
+ if (cOut.value < cGroup.metrics.lowerInnerFence) {
+ if (cOut.value < cGroup.metrics.lowerOuterFence) {
+ cExtremes.push(cOut);
+ } else {
+ cOutliers.push(cOut);
+ }
+ } else if (cOut.value > cGroup.metrics.upperInnerFence) {
+ if (cOut.value > cGroup.metrics.upperOuterFence) {
+ cExtremes.push(cOut);
+ } else {
+ cOutliers.push(cOut);
+ }
+ }
+ }
+ cGroup.boxPlot.objs.outliers = cOutliers;
+ cGroup.boxPlot.objs.extremes = cExtremes;
+ }
+
+ for (var cName in chart.groupObjs) {
+ calcOutliers(chart.groupObjs[cName]);
+ }
+ }();
+
+ /**
+ * Take updated options and redraw the box plot
+ * @param updateOptions
+ */
+ chart.boxPlots.change = function (updateOptions) {
+ if (updateOptions) {
+ for (var key in updateOptions) {
+ bOpts[key] = updateOptions[key]
+ }
+ }
+
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].boxPlot.objs.g.remove()
+ }
+ chart.boxPlots.prepareBoxPlot();
+ chart.boxPlots.update()
+ };
+
+ chart.boxPlots.reset = function () {
+ chart.boxPlots.change(defaultOptions)
+ };
+ chart.boxPlots.show = function (opts) {
+ if (opts !== undefined) {
+ opts.show = true;
+ if (opts.reset) {
+ chart.boxPlots.reset()
+ }
+ } else {
+ opts = {show: true};
+ }
+ chart.boxPlots.change(opts)
+
+ };
+ chart.boxPlots.hide = function (opts) {
+ if (opts !== undefined) {
+ opts.show = false;
+ if (opts.reset) {
+ chart.boxPlots.reset()
+ }
+ } else {
+ opts = {show: false};
+ }
+ chart.boxPlots.change(opts)
+ };
+
+ /**
+ * Update the box plot obj values
+ */
+ chart.boxPlots.update = function () {
+ var cName, cBoxPlot;
+
+ for (cName in chart.groupObjs) {
+ cBoxPlot = chart.groupObjs[cName].boxPlot;
+
+ // Get the box width
+ var objBounds = getObjWidth(bOpts.boxWidth, cName);
+ var width = (objBounds.right - objBounds.left);
+
+ var sMetrics = {}; //temp var for scaled (plottable) metric values
+ for (var attr in chart.groupObjs[cName].metrics) {
+ sMetrics[attr] = null;
+ sMetrics[attr] = chart.yScale(chart.groupObjs[cName].metrics[attr]);
+ }
+
+ // Box
+ if (cBoxPlot.objs.box) {
+ cBoxPlot.objs.box
+ .attr("x", objBounds.left)
+ .attr('width', width)
+ .attr("y", sMetrics.quartile3)
+ .attr("rx", 1)
+ .attr("ry", 1)
+ .attr("height", -sMetrics.quartile3 + sMetrics.quartile1)
+ }
+
+ // Lines
+ var lineBounds = null;
+ if (bOpts.lineWidth) {
+ lineBounds = getObjWidth(bOpts.lineWidth, cName)
+ } else {
+ lineBounds = objBounds
+ }
+ // --Whiskers
+ if (cBoxPlot.objs.upperWhisker) {
+ cBoxPlot.objs.upperWhisker.fence
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', sMetrics.upperInnerFence)
+ .attr("y2", sMetrics.upperInnerFence);
+ cBoxPlot.objs.upperWhisker.line
+ .attr("x1", lineBounds.middle)
+ .attr("x2", lineBounds.middle)
+ .attr('y1', sMetrics.quartile3)
+ .attr("y2", sMetrics.upperInnerFence);
+
+ cBoxPlot.objs.lowerWhisker.fence
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', sMetrics.lowerInnerFence)
+ .attr("y2", sMetrics.lowerInnerFence);
+ cBoxPlot.objs.lowerWhisker.line
+ .attr("x1", lineBounds.middle)
+ .attr("x2", lineBounds.middle)
+ .attr('y1', sMetrics.quartile1)
+ .attr("y2", sMetrics.lowerInnerFence);
+ }
+
+ // --Median
+ if (cBoxPlot.objs.median) {
+ cBoxPlot.objs.median.line
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', sMetrics.median)
+ .attr("y2", sMetrics.median);
+ cBoxPlot.objs.median.circle
+ .attr("cx", lineBounds.middle)
+ .attr("cy", sMetrics.median)
+ }
+
+ // --Mean
+ if (cBoxPlot.objs.mean) {
+ cBoxPlot.objs.mean.line
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', sMetrics.mean)
+ .attr("y2", sMetrics.mean);
+ cBoxPlot.objs.mean.circle
+ .attr("cx", lineBounds.middle)
+ .attr("cy", sMetrics.mean);
+ }
+
+ // Outliers
+
+ var pt;
+ if (cBoxPlot.objs.outliers) {
+ for (pt in cBoxPlot.objs.outliers) {
+ cBoxPlot.objs.outliers[pt].point
+ .attr("cx", objBounds.middle + addJitter(bOpts.scatterOutliers, width))
+ .attr("cy", chart.yScale(cBoxPlot.objs.outliers[pt].value));
+ }
+ }
+ if (cBoxPlot.objs.extremes) {
+ for (pt in cBoxPlot.objs.extremes) {
+ cBoxPlot.objs.extremes[pt].point
+ .attr("cx", objBounds.middle + addJitter(bOpts.scatterOutliers, width))
+ .attr("cy", chart.yScale(cBoxPlot.objs.extremes[pt].value));
+ }
+ }
+ }
+ };
+
+ /**
+ * Create the svg elements for the box plot
+ */
+ chart.boxPlots.prepareBoxPlot = function () {
+ var cName, cBoxPlot;
+
+ if (bOpts.colors) {
+ chart.boxPlots.colorFunct = getColorFunct(bOpts.colors);
+ } else {
+ chart.boxPlots.colorFunct = chart.colorFunct
+ }
+
+ if (bOpts.show == false) {
+ return
+ }
+
+ for (cName in chart.groupObjs) {
+ cBoxPlot = chart.groupObjs[cName].boxPlot;
+
+ cBoxPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "box-plot");
+
+ //Plot Box (default show)
+ if (bOpts.showBox) {
+ cBoxPlot.objs.box = cBoxPlot.objs.g.append("rect")
+ .attr("class", "box")
+ .style("fill", chart.boxPlots.colorFunct(cName))
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ //A stroke is added to the box with the group color, it is
+ // hidden by default and can be shown through css with stroke-width
+ }
+
+ //Plot Median (default show)
+ if (bOpts.showMedian) {
+ cBoxPlot.objs.median = {line: null, circle: null};
+ cBoxPlot.objs.median.line = cBoxPlot.objs.g.append("line")
+ .attr("class", "median");
+ cBoxPlot.objs.median.circle = cBoxPlot.objs.g.append("circle")
+ .attr("class", "median")
+ .attr('r', bOpts.medianCSize)
+ .style("fill", chart.boxPlots.colorFunct(cName));
+ }
+
+ // Plot Mean (default no plot)
+ if (bOpts.showMean) {
+ cBoxPlot.objs.mean = {line: null, circle: null};
+ cBoxPlot.objs.mean.line = cBoxPlot.objs.g.append("line")
+ .attr("class", "mean");
+ cBoxPlot.objs.mean.circle = cBoxPlot.objs.g.append("circle")
+ .attr("class", "mean")
+ .attr('r', bOpts.medianCSize)
+ .style("fill", chart.boxPlots.colorFunct(cName));
+ }
+
+ // Plot Whiskers (default show)
+ if (bOpts.showWhiskers) {
+ cBoxPlot.objs.upperWhisker = {fence: null, line: null};
+ cBoxPlot.objs.lowerWhisker = {fence: null, line: null};
+ cBoxPlot.objs.upperWhisker.fence = cBoxPlot.objs.g.append("line")
+ .attr("class", "upper whisker")
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ cBoxPlot.objs.upperWhisker.line = cBoxPlot.objs.g.append("line")
+ .attr("class", "upper whisker")
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+
+ cBoxPlot.objs.lowerWhisker.fence = cBoxPlot.objs.g.append("line")
+ .attr("class", "lower whisker")
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ cBoxPlot.objs.lowerWhisker.line = cBoxPlot.objs.g.append("line")
+ .attr("class", "lower whisker")
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ }
+
+ // Plot outliers (default show)
+ if (bOpts.showOutliers) {
+ if (!cBoxPlot.objs.outliers) calcAllOutliers();
+ var pt;
+ if (cBoxPlot.objs.outliers.length) {
+ var outDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot outliers");
+ for (pt in cBoxPlot.objs.outliers) {
+ cBoxPlot.objs.outliers[pt].point = outDiv.append("circle")
+ .attr("class", "outlier")
+ .attr('r', bOpts.outlierCSize)
+ .style("fill", chart.boxPlots.colorFunct(cName));
+ }
+ }
+
+ if (cBoxPlot.objs.extremes.length) {
+ var extDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot extremes");
+ for (pt in cBoxPlot.objs.extremes) {
+ cBoxPlot.objs.extremes[pt].point = extDiv.append("circle")
+ .attr("class", "extreme")
+ .attr('r', bOpts.outlierCSize)
+ .style("stroke", chart.boxPlots.colorFunct(cName));
+ }
+ }
+ }
+
+
+ }
+ };
+ chart.boxPlots.prepareBoxPlot();
+
+ d3.select(window).on('resize.' + chart.selector + '.boxPlot', chart.boxPlots.update);
+ chart.boxPlots.update();
+ return chart;
+
+ };
+
+ /**
+ * Render a notched box on the current chart
+ * @param options
+ * @param [options.show=true] Toggle the whole plot on and off
+ * @param [options.showNotchBox=true] Show the notch box
+ * @param [options.showLines=false] Show lines at the confidence intervals
+ * @param [options.boxWidth=35] The width of the widest part of the box
+ * @param [options.medianWidth=20] The width of the narrowist part of the box
+ * @param [options.lineWidth=50] The width of the confidence interval lines
+ * @param [options.notchStyle=null] null=traditional style, 'box' cuts out the whole notch in right angles
+ * @param [options.colors=chart default] The color mapping for the notch boxes
+ * @returns {*} The chart object
+ */
+ chart.renderNotchBoxes = function (options) {
+ chart.notchBoxes = {};
+
+ //Defaults
+ var defaultOptions = {
+ show: true,
+ showNotchBox: true,
+ showLines: false,
+ boxWidth: 35,
+ medianWidth: 20,
+ lineWidth: 50,
+ notchStyle: null,
+ colors: null
+ };
+ chart.notchBoxes.options = shallowCopy(defaultOptions);
+ for (var option in options) {
+ chart.notchBoxes.options[option] = options[option]
+ }
+ var nOpts = chart.notchBoxes.options;
+
+ //Create notch objects
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].notchBox = {};
+ chart.groupObjs[cName].notchBox.objs = {};
+ }
+
+ /**
+ * Makes the svg path string for a notched box
+ * @param cNotch Current notch box object
+ * @param notchBounds objBound object
+ * @returns {string} A string in the proper format for a svg polygon
+ */
+ function makeNotchBox(cNotch, notchBounds) {
+ var scaledValues = [];
+ if (nOpts.notchStyle == 'box') {
+ scaledValues = [
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile1)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.medianLeft, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.medianLeft, chart.yScale(cNotch.metrics.median)],
+ [notchBounds.medianLeft, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile3)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile3)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.medianRight, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.medianRight, chart.yScale(cNotch.metrics.median)],
+ [notchBounds.medianRight, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile1)]
+ ];
+ } else {
+ scaledValues = [
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile1)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.medianLeft, chart.yScale(cNotch.metrics.median)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.boxLeft, chart.yScale(cNotch.metrics.quartile3)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile3)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.upperNotch)],
+ [notchBounds.medianRight, chart.yScale(cNotch.metrics.median)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.lowerNotch)],
+ [notchBounds.boxRight, chart.yScale(cNotch.metrics.quartile1)]
+ ];
+ }
+ return scaledValues.map(function (d) {
+ return [d[0], d[1]].join(",");
+ }).join(" ");
+ }
+
+ /**
+ * Calculate the confidence intervals
+ */
+ !function calcNotches() {
+ var cNotch, modifier;
+ for (var cName in chart.groupObjs) {
+ cNotch = chart.groupObjs[cName];
+ modifier = (1.57 * (cNotch.metrics.iqr / Math.sqrt(cNotch.values.length)));
+ cNotch.metrics.upperNotch = cNotch.metrics.median + modifier;
+ cNotch.metrics.lowerNotch = cNotch.metrics.median - modifier;
+ }
+ }();
+
+ /**
+ * Take a new set of options and redraw the notch boxes
+ * @param updateOptions
+ */
+ chart.notchBoxes.change = function (updateOptions) {
+ if (updateOptions) {
+ for (var key in updateOptions) {
+ nOpts[key] = updateOptions[key]
+ }
+ }
+
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].notchBox.objs.g.remove()
+ }
+ chart.notchBoxes.prepareNotchBoxes();
+ chart.notchBoxes.update();
+ };
+
+ chart.notchBoxes.reset = function () {
+ chart.notchBoxes.change(defaultOptions)
+ };
+ chart.notchBoxes.show = function (opts) {
+ if (opts !== undefined) {
+ opts.show = true;
+ if (opts.reset) {
+ chart.notchBoxes.reset()
+ }
+ } else {
+ opts = {show: true};
+ }
+ chart.notchBoxes.change(opts)
+ };
+ chart.notchBoxes.hide = function (opts) {
+ if (opts !== undefined) {
+ opts.show = false;
+ if (opts.reset) {
+ chart.notchBoxes.reset()
+ }
+ } else {
+ opts = {show: false};
+ }
+ chart.notchBoxes.change(opts)
+ };
+
+ /**
+ * Update the notch box obj values
+ */
+ chart.notchBoxes.update = function () {
+ var cName, cGroup;
+
+ for (cName in chart.groupObjs) {
+ cGroup = chart.groupObjs[cName];
+
+ // Get the box size
+ var boxBounds = getObjWidth(nOpts.boxWidth, cName);
+ var medianBounds = getObjWidth(nOpts.medianWidth, cName);
+
+ var notchBounds = {
+ boxLeft: boxBounds.left,
+ boxRight: boxBounds.right,
+ middle: boxBounds.middle,
+ medianLeft: medianBounds.left,
+ medianRight: medianBounds.right
+ };
+
+ // Notch Box
+ if (cGroup.notchBox.objs.notch) {
+ cGroup.notchBox.objs.notch
+ .attr("points", makeNotchBox(cGroup, notchBounds));
+ }
+ if (cGroup.notchBox.objs.upperLine) {
+ var lineBounds = null;
+ if (nOpts.lineWidth) {
+ lineBounds = getObjWidth(nOpts.lineWidth, cName)
+ } else {
+ lineBounds = objBounds
+ }
+
+ var confidenceLines = {
+ upper: chart.yScale(cGroup.metrics.upperNotch),
+ lower: chart.yScale(cGroup.metrics.lowerNotch)
+ };
+ cGroup.notchBox.objs.upperLine
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', confidenceLines.upper)
+ .attr("y2", confidenceLines.upper);
+ cGroup.notchBox.objs.lowerLine
+ .attr("x1", lineBounds.left)
+ .attr("x2", lineBounds.right)
+ .attr('y1', confidenceLines.lower)
+ .attr("y2", confidenceLines.lower);
+ }
+ }
+ };
+
+ /**
+ * Create the svg elements for the notch boxes
+ */
+ chart.notchBoxes.prepareNotchBoxes = function () {
+ var cName, cNotch;
+
+ if (nOpts && nOpts.colors) {
+ chart.notchBoxes.colorFunct = getColorFunct(nOpts.colors);
+ } else {
+ chart.notchBoxes.colorFunct = chart.colorFunct
+ }
+
+ if (nOpts.show == false) {
+ return
+ }
+
+ for (cName in chart.groupObjs) {
+ cNotch = chart.groupObjs[cName].notchBox;
+
+ cNotch.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "notch-plot");
+
+ // Plot Box (default show)
+ if (nOpts.showNotchBox) {
+ cNotch.objs.notch = cNotch.objs.g.append("polygon")
+ .attr("class", "notch")
+ .style("fill", chart.notchBoxes.colorFunct(cName))
+ .style("stroke", chart.notchBoxes.colorFunct(cName));
+ //A stroke is added to the notch with the group color, it is
+ // hidden by default and can be shown through css with stroke-width
+ }
+
+ //Plot Confidence Lines (default hide)
+ if (nOpts.showLines) {
+ cNotch.objs.upperLine = cNotch.objs.g.append("line")
+ .attr("class", "upper confidence line")
+ .style("stroke", chart.notchBoxes.colorFunct(cName));
+
+ cNotch.objs.lowerLine = cNotch.objs.g.append("line")
+ .attr("class", "lower confidence line")
+ .style("stroke", chart.notchBoxes.colorFunct(cName));
+ }
+ }
+ };
+ chart.notchBoxes.prepareNotchBoxes();
+
+ d3.select(window).on('resize.' + chart.selector + '.notchBox', chart.notchBoxes.update);
+ chart.notchBoxes.update();
+ return chart;
+ };
+
+ /**
+ * Render a raw data in various forms
+ * @param options
+ * @param [options.show=true] Toggle the whole plot on and off
+ * @param [options.showPlot=false] True or false, show points
+ * @param [options.plotType='none'] Options: no scatter = (false or 'none'); scatter points= (true or [amount=% of width (default=10)]); beeswarm points = ('beeswarm')
+ * @param [options.pointSize=6] Diameter of the circle in pizels (not the radius)
+ * @param [options.showLines=['median']] Can equal any of the metrics lines
+ * @param [options.showbeanLines=false] Options: no lines = false
+ * @param [options.beanWidth=20] % width
+ * @param [options.colors=chart default]
+ * @returns {*} The chart object
+ *
+ */
+ chart.renderDataPlots = function (options) {
+ chart.dataPlots = {};
+
+
+ //Defaults
+ var defaultOptions = {
+ show: true,
+ showPlot: false,
+ plotType: 'none',
+ pointSize: 6,
+ showLines: false,//['median'],
+ showBeanLines: false,
+ beanWidth: 20,
+ colors: null
+ };
+ chart.dataPlots.options = shallowCopy(defaultOptions);
+ for (var option in options) {
+ chart.dataPlots.options[option] = options[option]
+ }
+ var dOpts = chart.dataPlots.options;
+
+ //Create notch objects
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].dataPlots = {};
+ chart.groupObjs[cName].dataPlots.objs = {};
+ }
+ // The lines don't fit into a group bucket so they live under the dataPlot object
+ chart.dataPlots.objs = {};
+
+ /**
+ * Take updated options and redraw the data plots
+ * @param updateOptions
+ */
+ chart.dataPlots.change = function (updateOptions) {
+ if (updateOptions) {
+ for (var key in updateOptions) {
+ dOpts[key] = updateOptions[key]
+ }
+ }
+
+ chart.dataPlots.objs.g.remove();
+ for (var cName in chart.groupObjs) {
+ chart.groupObjs[cName].dataPlots.objs.g.remove()
+ }
+ chart.dataPlots.preparePlots();
+ chart.dataPlots.update()
+ };
+
+ chart.dataPlots.reset = function () {
+ chart.dataPlots.change(defaultOptions)
+ };
+ chart.dataPlots.show = function (opts) {
+ if (opts !== undefined) {
+ opts.show = true;
+ if (opts.reset) {
+ chart.dataPlots.reset()
+ }
+ } else {
+ opts = {show: true};
+ }
+ chart.dataPlots.change(opts)
+ };
+ chart.dataPlots.hide = function (opts) {
+ if (opts !== undefined) {
+ opts.show = false;
+ if (opts.reset) {
+ chart.dataPlots.reset()
+ }
+ } else {
+ opts = {show: false};
+ }
+ chart.dataPlots.change(opts)
+ };
+
+ /**
+ * Update the data plot obj values
+ */
+ chart.dataPlots.update = function () {
+ var cName, cGroup, cPlot;
+
+ // Metrics lines
+ if (chart.dataPlots.objs.g) {
+ var halfBand = chart.xScale.rangeBand() / 2; // find the middle of each band
+ for (var cMetric in chart.dataPlots.objs.lines) {
+ chart.dataPlots.objs.lines[cMetric].line
+ .x(function (d) {
+ return chart.xScale(d.x) + halfBand
+ });
+ chart.dataPlots.objs.lines[cMetric].g
+ .datum(chart.dataPlots.objs.lines[cMetric].values)
+ .attr('d', chart.dataPlots.objs.lines[cMetric].line);
+ }
+ }
+
+
+ for (cName in chart.groupObjs) {
+ cGroup = chart.groupObjs[cName];
+ cPlot = cGroup.dataPlots;
+
+ if (cPlot.objs.points) {
+ if (dOpts.plotType == 'beeswarm') {
+ var swarmBounds = getObjWidth(100, cName);
+ var yPtScale = chart.yScale.copy()
+ .range([Math.floor(chart.yScale.range()[0] / dOpts.pointSize), 0])
+ .interpolate(d3.interpolateRound)
+ .domain(chart.yScale.domain());
+ var maxWidth = Math.floor(chart.xScale.rangeBand() / dOpts.pointSize);
+ var ptsObj = {};
+ var cYBucket = null;
+ // Bucket points
+ for (var pt = 0; pt < cGroup.values.length; pt++) {
+ cYBucket = yPtScale(cGroup.values[pt]);
+ if (ptsObj.hasOwnProperty(cYBucket) !== true) {
+ ptsObj[cYBucket] = [];
+ }
+ ptsObj[cYBucket].push(cPlot.objs.points.pts[pt]
+ .attr("cx", swarmBounds.middle)
+ .attr("cy", yPtScale(cGroup.values[pt]) * dOpts.pointSize));
+ }
+ // Plot buckets
+ var rightMax = Math.min(swarmBounds.right - dOpts.pointSize);
+ for (var row in ptsObj) {
+ var leftMin = swarmBounds.left + (Math.max((maxWidth - ptsObj[row].length) / 2, 0) * dOpts.pointSize);
+ var col = 0;
+ for (pt in ptsObj[row]) {
+ ptsObj[row][pt].attr("cx", Math.min(leftMin + col * dOpts.pointSize, rightMax) + dOpts.pointSize / 2);
+ col++
+ }
+ }
+ } else { // For scatter points and points with no scatter
+ var plotBounds = null,
+ scatterWidth = 0,
+ width = 0;
+ if (dOpts.plotType == 'scatter' || typeof dOpts.plotType == 'number') {
+ //Default scatter percentage is 20% of box width
+ scatterWidth = typeof dOpts.plotType == 'number' ? dOpts.plotType : 20;
+ }
+
+ plotBounds = getObjWidth(scatterWidth, cName);
+ width = plotBounds.right - plotBounds.left;
+
+ for (var pt = 0; pt < cGroup.values.length; pt++) {
+ cPlot.objs.points.pts[pt]
+ .attr("cx", plotBounds.middle + addJitter(true, width))
+ .attr("cy", chart.yScale(cGroup.values[pt]));
+ }
+ }
+ }
+
+
+ if (cPlot.objs.bean) {
+ var beanBounds = getObjWidth(dOpts.beanWidth, cName);
+ for (var pt = 0; pt < cGroup.values.length; pt++) {
+ cPlot.objs.bean.lines[pt]
+ .attr("x1", beanBounds.left)
+ .attr("x2", beanBounds.right)
+ .attr('y1', chart.yScale(cGroup.values[pt]))
+ .attr("y2", chart.yScale(cGroup.values[pt]));
+ }
+ }
+ }
+ };
+
+ /**
+ * Create the svg elements for the data plots
+ */
+ chart.dataPlots.preparePlots = function () {
+ var cName, cPlot;
+
+ if (dOpts && dOpts.colors) {
+ chart.dataPlots.colorFunct = getColorFunct(dOpts.colors);
+ } else {
+ chart.dataPlots.colorFunct = chart.colorFunct
+ }
+
+
+ if (dOpts && dOpts.lineColors) {
+ chart.dataPlots.colorFunctLine = getColorFunct(dOpts.lineColors);
+ } else {
+ chart.dataPlots.colorFunctLine = chart.colorFunct
+ }
+
+ if (dOpts.show == false) {
+ return
+ }
+
+ // Metrics lines
+ chart.dataPlots.objs.g = chart.objs.g.append("g").attr("class", "metrics-lines");
+ if (dOpts.showLines && dOpts.showLines.length > 0) {
+ chart.dataPlots.objs.lines = {};
+ var cMetric;
+ for (var line in dOpts.showLines) {
+ cMetric = dOpts.showLines[line];
+ chart.dataPlots.objs.lines[cMetric] = {};
+ chart.dataPlots.objs.lines[cMetric].values = [];
+ for (var cGroup in chart.groupObjs) {
+ chart.dataPlots.objs.lines[cMetric].values.push({
+ x: cGroup,
+ y: chart.groupObjs[cGroup].metrics[cMetric]
+ })
+ }
+ chart.dataPlots.objs.lines[cMetric].line = d3.svg.line()
+ .interpolate("cardinal")
+ .y(function (d) {
+ return chart.yScale(d.y)
+ });
+ chart.dataPlots.objs.lines[cMetric].g = chart.dataPlots.objs.g.append("path")
+ .attr("class", "line " + cMetric)
+ .attr("data-metric", cMetric)
+ .style("fill", 'none')
+ .style("stroke", chart.dataPlots.colorFunctLine(cMetric));
+ }
+
+ }
+
+
+ for (cName in chart.groupObjs) {
+
+ cPlot = chart.groupObjs[cName].dataPlots;
+ cPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "data-plot");
+
+ // Points Plot
+ if (dOpts.showPlot) {
+ cPlot.objs.points = {g: null, pts: []};
+ cPlot.objs.points.g = cPlot.objs.g.append("g").attr("class", "points-plot");
+ for (var pt = 0; pt < chart.groupObjs[cName].values.length; pt++) {
+ cPlot.objs.points.pts.push(cPlot.objs.points.g.append("circle")
+ .attr("class", "point")
+ .attr('r', dOpts.pointSize / 2)// Options is diameter, r takes radius so divide by 2
+ .style("fill", chart.dataPlots.colorFunct(cName)));
+ }
+ }
+
+
+ // Bean lines
+ if (dOpts.showBeanLines) {
+ cPlot.objs.bean = {g: null, lines: []};
+ cPlot.objs.bean.g = cPlot.objs.g.append("g").attr("class", "bean-plot");
+ for (var pt = 0; pt < chart.groupObjs[cName].values.length; pt++) {
+ cPlot.objs.bean.lines.push(cPlot.objs.bean.g.append("line")
+ .attr("class", "bean line")
+ .style("stroke-width", '1')
+ .style("stroke", chart.dataPlots.colorFunct(cName)));
+ }
+ }
+ }
+
+ };
+ chart.dataPlots.preparePlots();
+
+ d3.select(window).on('resize.' + chart.selector + '.dataPlot', chart.dataPlots.update);
+ chart.dataPlots.update();
+ return chart;
+ };
+
+ return chart;
+}
diff --git a/Clustering/distroplot/ss.png b/Clustering/distroplot/ss.png
new file mode 100644
index 0000000..8381b65
Binary files /dev/null and b/Clustering/distroplot/ss.png differ
diff --git a/Clustering/index.html b/Clustering/index.html
index 664f5fa..23644e4 100644
--- a/Clustering/index.html
+++ b/Clustering/index.html
@@ -103,11 +103,15 @@
+
-
+
+
+
+