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 @@ + - + + + +