├── .gitignore ├── LICENSE ├── MANIFEST.in ├── PyPiNotes.md ├── README.md ├── articles ├── barBrains.html ├── barBrains.md ├── bar_brains.R ├── css │ ├── GlasseyeCharts.css │ ├── colorbrewer.css │ └── tufte.css ├── js │ ├── GlasseyeCharts.js │ ├── GlasseyeCharts.min.js │ ├── colorbrewer.js │ ├── d3.min.js │ ├── d3.tip.v0.6.3.js │ ├── glasseyePictograms.js │ ├── icheck.min.js │ ├── jquery-1.11.3.min.js │ └── venn.js ├── logreg ├── pandocHTML.html ├── stack.csv └── templates │ └── template.html ├── gantt.md ├── glasseye ├── .idea │ ├── .name │ ├── encodings.xml │ ├── glasseye.iml │ ├── misc.xml │ ├── modules.xml │ ├── scopes │ │ └── scope_settings.xml │ └── workspace.xml ├── MultiChart.html ├── __init__.py ├── __main__.py ├── css │ ├── GlasseyeCharts.css │ ├── colorbrewer.css │ └── tufte.css ├── demo │ ├── Tufte.gif │ ├── css │ │ ├── GlasseyeCharts.css │ │ ├── colorbrewer.css │ │ └── tufte.css │ ├── data │ │ ├── activeDecidedSim.csv │ │ ├── lineplotExample.csv │ │ └── share.csv │ ├── js │ │ ├── GlasseyeCharts.min.js │ │ ├── colorbrewer.js │ │ ├── d3.min.js │ │ ├── d3.tip.v0.6.3.js │ │ ├── glasseyeCharts.js │ │ ├── glasseyePictograms.js │ │ ├── jquery-1.11.3.min.js │ │ └── venn.js │ ├── markdownExample.html │ ├── markdownExample.md │ ├── pandocHTML.html │ ├── templates │ │ └── template.html │ └── viewMarkdown.txt ├── js │ ├── GlasseyeCharts.min.js │ ├── colorbrewer.js │ ├── d3.min.js │ ├── d3.tip.v0.6.3.js │ ├── glasseyeCharts.js │ ├── glasseyePictograms.js │ ├── jquery-1.11.3.min.js │ ├── modular │ │ ├── AnimatedBarChart.js │ │ ├── AnimatedDensity.js │ │ ├── AnimatedDonut.js │ │ ├── AnimatedVenn.js │ │ ├── BarChart.js │ │ ├── Build.sh │ │ ├── Concat.sh │ │ ├── Dial.js │ │ ├── Donut.js │ │ ├── DrillableVenn.js │ │ ├── Force.js │ │ ├── Gant.js │ │ ├── GlasseyeChart.js │ │ ├── GlobalFunctionsAndVariable.js │ │ ├── GridChart.js │ │ ├── GroupedBarChart.js │ │ ├── Heatmap.js │ │ ├── LeafletMap.js │ │ ├── LinePlot.js │ │ ├── LogReg.js │ │ ├── NonStandard.js │ │ ├── Parsers.js │ │ ├── PolygonMap.js │ │ ├── RandomNumber.js │ │ ├── ScatterPlot.js │ │ ├── Thermometers.js │ │ ├── TimeSeries.js │ │ ├── Tree.js │ │ ├── Venn.js │ │ ├── conf.json │ │ └── prepExport.sh │ └── venn.js ├── templates │ ├── pandocHTML.html │ └── tufteTemplate.html └── testingTemplate.html ├── glasseyeLayouts.md └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | 59 | # Misc 60 | glasseye/data 61 | *.DS_Store 62 | /css 63 | /js 64 | *.idea* 65 | .idea/ 66 | glasseye/.idea/ 67 | *workspace.xml 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 coppelia machine learning and analytics 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include glasseye/css/*.css 2 | include glasseye/js/*.js 3 | include glasseye/templates/*.html 4 | include README.md 5 | include glasseye/demo/*.gif 6 | include glasseye/demo/*.md 7 | include glasseye/demo/data/*.csv -------------------------------------------------------------------------------- /PyPiNotes.md: -------------------------------------------------------------------------------- 1 | From root run 2 | 3 | ``` 4 | python setup.py sdist upload 5 | ``` 6 | 7 | After changing release no in setup.py 8 | 9 | May need to uninstall before reinstalling as update doesn't seem to work 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##What is glasseye? 2 | 3 | Glasseye is something I'm developing to present the results of statistical analysis in an attractive and hopefully interesting way. It brings together three great things that I use a lot: 4 | 5 | 1. The markdown markup language. 6 | 2. The Tufte wide margin layout 7 | 3. Visualisation using d3.js 8 | 9 | 10 | See a full demo with a more in-depth explanation [here](http://coppeliamla.github.io/glasseye/demo/markdownExample.html) 11 | 12 | The idea is to be able to write up work in markdown and have the results transformed into something like a Tufte layout. For the Tufte layout I took the excellent tufte.css style sheet developed by [Dave Liepmann and co](https://github.com/daveliepmann/tufte-css) and adapted it for my purposes. Finally I've added some d3 charts (just a small selection at the moment but this is growing) that can easily invoked from within the markdown. 13 | 14 | ##What it can do 15 | ###Side notes and margin notes 16 | First there's the `` tag. Anything enclosed in these tags will generate a numbered side note in the wide margin as close as possible to the note number in the main text. These can be used for commentary, links, bits of maths, anything that's peripheral to the main discussion. 17 | 18 | You can easily add images to the side notes and margin notes just by including the usual markdown syntax for inserting an image within the tags. 19 | 20 | Then there is a `` tag which is the nearly the same as the side note, only there's no number linking it to a particular part in the main text. You'll see to the right an example of a margin note containing a d3 donut chart. 21 | 22 | 23 | ###d3 charts 24 | 25 | I've tried to create charts that are simple and uncluttered with the tooltip taking over some of the work. This is so that they can fit in the margin nicely. I've been thinking about making them as intellegent as possible so that choices are made for you about formatting (for example label positioning). That may prove annoying though so we'll see how it goes. It's easy to include any of the d3 charts into either the main body of the text or into the margin. 26 | 27 | Inserting a plot is again just a matter of using some custom tags. For example to generate a line plot just surround a string containing the path and filname of a csv file with a `` tag. 28 | 29 | Alternatively you can write the data in json into the markdown. For example we can create an interactive treemap by inserting the following into the markdown. See the section below for the full json 30 | 31 | ``` 32 | 33 | 34 | { 35 | "name": "All", 36 | "children": [ 37 | {"name": "Bakery", 38 | "size": 34}, 39 | {"name": "Tinned Goods", 40 | "children": [ 41 | {"name": "Beans", 42 | "size": 34}, 43 | {"name": "Soups", 44 | "size": 56}, 45 | {"name": "Puddings", 46 | "children": [ 47 | {"name": "Fruit", 48 | "children": [ 49 | {"name": "Tangerines", 50 | "size": 15}, 51 | {"name": "Pears", 52 | "size": 17} 53 | ] 54 | }, 55 | {"name": "Apricots", 56 | "size": 89} 57 | ... 58 | 59 | ``` 60 | 61 | ##How it works 62 | 63 | Glasseye is built using pandoc and the python beautifulsoup library. Pandoc is used to generate the html from the markdown and beautifulsoup is used to manipulate the extra tags and make the appropriate substitutions, including adding in the d3 charts. 64 | 65 | ##Installing glasseye 66 | 67 | I've now added glasseye to pip so installation is fairly straightforward 68 | 69 | 1. Install pandoc from [here](http://pandoc.org) 70 | 2. If you don't have it already install python 2.7 71 | 3. Then from the command line run `pip install glasseye` Assumes you already have the pip package. If you don't just run `easy_install pip` 72 | 73 | ##Using glasseye 74 | 75 | Just create your markdown file using a text editor and then from the commandline run 76 | 77 | ``` 78 | glasseye myMarkdownFile.md 79 | ``` 80 | The html will then be created in the current directory along with the supporting css and javascript. 81 | 82 | You can test it out using the demo that comes with the package. You'll find the demo files in `path_to_your_packages/glasseye/demo`. Copy them to a new directory and run 83 | 84 | ``` 85 | glasseye markdownExample.md 86 | ``` 87 | 88 | See the [demo](http://coppeliamla.github.io/glasseye/glasseye_markdownExample/demo.html) for more information. 89 | -------------------------------------------------------------------------------- /articles/bar_brains.R: -------------------------------------------------------------------------------- 1 | count<-c(4, 12, 44, 16) 2 | N<-sum(count) 3 | prop<-count/N 4 | 5 | #Classical 6 | sd_func<-function(p){ 7 | return( sqrt((p*(1-p)/N))) 8 | } 9 | c_sd<-sapply(prop, sd_func) 10 | c_u<-prop+1.96*c_sd 11 | c_l<-prop-1.96*c_sd 12 | plot(prop, ylim=c(-1,1)) 13 | points(c_u, col="Pink") 14 | points(c_l, col="Pink") 15 | 16 | #Note for reference only - doesn't really work as proportions are not independent in this case 17 | #Bonferroni (mean test only) 18 | bc_u<-prop+qnorm(1-(0.025/6))*c_sd 19 | bc_l<-prop-qnorm(1-(0.025/6))*c_sd 20 | 21 | points(bc_u, col="Green") 22 | points(bc_l, col="Green") 23 | 24 | #Bonferroni (all comparisons) 25 | 26 | comb <- function(n, x) { 27 | return(factorial(n) / (factorial(x) * factorial(n-x))) 28 | } 29 | 30 | comb(6,2) 31 | 32 | bc_u<-prop+qnorm(1-(0.025/15))*c_sd 33 | bc_l<-prop-qnorm(1-(0.025/15))*c_sd 34 | 35 | points(bc_u, col="Blue") 36 | points(bc_l, col="Blue") 37 | 38 | #Bayesian 39 | 40 | dir<-count+1 #with non informative dir prior 41 | bayesian<-dir/sum(dir) 42 | #plot(prop, ylim=c(-0.2,0.5)) 43 | 44 | points(bayesian, col="red", pch=6) 45 | 46 | a_0<-sum(dir) 47 | 48 | bayes_sd<-function(a){ 49 | var_a<-(a*(a_0-a))/(a_0^2*(a_0+1)) 50 | return(sqrt(var_a)) 51 | } 52 | 53 | 54 | b_sd<-sapply(bayesian, bayes_sd) 55 | b_u<-bayesian+1.96*b_sd 56 | b_l<-bayesian-1.96*b_sd 57 | points(b_u, col="Red", pch=6) 58 | points(b_l, col="Red", pch=6) 59 | 60 | #Work out joint distributions 61 | 62 | library(gtools) 63 | 64 | sample<-rdirichlet(1000, dir) 65 | diff_1_2<-sample[,1]-sample[,2] 66 | hist(diff_1_2) 67 | sum(diff_1_2>0) 68 | 69 | diff_2_3<-sample[,2]-sample[,3] 70 | hist(diff_2_3) 71 | sum(diff_2_3>0) 72 | 73 | diff_3_4<-sample[,3]-sample[,4] 74 | hist(diff_3_4) 75 | sum(diff_3_4>0) 76 | 77 | diff_2_4<-sample[,2]-sample[,4] 78 | hist(diff_2_4) 79 | sum(diff_2_4>0) 80 | 81 | 82 | #Using glm 83 | 84 | counts<-c(40,23,19,8) 85 | party<-c("Red", "Blue", "Green", "Yellow") 86 | party<-data.frame(counts, party) 87 | glm_party<-glm(counts~party, data = party, family ="poisson") 88 | #To test the hypotheses of no difference 89 | library(multcomp) 90 | summary(glht(glm_party, mcp(party="Tukey"))) 91 | 92 | counts<-c(100,230, 265) 93 | party<-c("Apples", "Pears", "Oranges") 94 | party<-data.frame(counts, party) 95 | glm_party<-glm(counts~party, data = party, family ="poisson") 96 | #To test the hypotheses of no difference 97 | library(multcomp) 98 | summary(glht(glm_party, mcp(party="Tukey"))) 99 | 100 | 101 | count<-c(100, 230, 265) 102 | N<-sum(count) 103 | prop<-count/N 104 | 105 | dir<-count+1 #with non informative dir prior 106 | bayesian<-dir/sum(dir) 107 | 108 | sample<-rdirichlet(1000, dir) 109 | diff_1_2<-sample[,1]-sample[,2] 110 | hist(diff_1_2) 111 | sum(diff_1_2>0) 112 | 113 | diff_2_3<-sample[,2]-sample[,3] 114 | hist(diff_2_3) 115 | sum(diff_2_3>0) 116 | 117 | diff_3_4<-sample[,3]-sample[,4] 118 | hist(diff_3_4) 119 | sum(diff_3_4>0) 120 | 121 | -------------------------------------------------------------------------------- /articles/css/GlasseyeCharts.css: -------------------------------------------------------------------------------- 1 | /* General chart formatting */ 2 | 3 | svg { 4 | clear: both; 5 | margin-top: 20px; 6 | margin-bottom: 20px; 7 | } 8 | 9 | text { 10 | stroke: none; 11 | font-family: 'Raleway'; 12 | font-size: 10px; 13 | } 14 | 15 | .line { 16 | fill: none; 17 | stroke: steelblue; 18 | stroke-width: 1.5px; 19 | } 20 | 21 | .chart_grid .tick { 22 | stroke: lightgrey; 23 | opacity: 0.7; 24 | } 25 | 26 | .chart_grid path { 27 | stroke-width: 0; 28 | shape-rendering: crispEdges; 29 | } 30 | 31 | .chart_grid .temp_y_label text { 32 | font-size: 10px; 33 | fill: grey; 34 | shape-rendering: crispEdges; 35 | } 36 | 37 | .legend_item text { 38 | font-size: 10px; 39 | fill: grey; 40 | shape-rendering: crispEdges; 41 | } 42 | 43 | .axis_label text { 44 | font-size: 8px; 45 | fill: grey; 46 | shape-rendering: crispEdges; 47 | text-transform: uppercase; 48 | stroke: none; 49 | } 50 | 51 | /*line plot*/ 52 | 53 | .line_points { 54 | opacity: 0; 55 | } 56 | 57 | /*timeseries*/ 58 | 59 | .timeseries_line { 60 | stroke-width: 3px; 61 | fill: transparent; 62 | } 63 | 64 | /*bar chart*/ 65 | 66 | .bar { 67 | fill: steelblue; 68 | stroke: none; 69 | } 70 | 71 | /*thermometers*/ 72 | 73 | .glass { 74 | stroke: none; 75 | fill: grey; 76 | } 77 | 78 | .glass-gap { 79 | stroke: none; 80 | fill: white; 81 | } 82 | 83 | .mercury { 84 | stroke: none; 85 | fill: red; 86 | } 87 | 88 | /*tree*/ 89 | 90 | .treenode circle { 91 | fill: #fff; 92 | stroke: steelblue; 93 | stroke-width: 1.5px; 94 | } 95 | 96 | .treenode { 97 | font: 10px sans-serif; 98 | fill: grey; 99 | } 100 | 101 | .treelink { 102 | fill: none; 103 | stroke: #ccc; 104 | stroke-width: 1.5px; 105 | } 106 | 107 | /*AnimatedDensity*/ 108 | 109 | .block { 110 | 111 | fill: #1abc9c; 112 | } 113 | 114 | /*force*/ 115 | 116 | .forcenode { 117 | stroke: #fff; 118 | stroke-width: 1.5px; 119 | fill: steelblue; 120 | } 121 | 122 | .forcelink { 123 | stroke: #999; 124 | stroke-opacity: .6; 125 | } 126 | 127 | /* Tip formatting */ 128 | 129 | .d3-tip { 130 | line-height: 1; 131 | font-weight: bold; 132 | padding: 8px; 133 | background: rgba(128, 128, 128, 0.9); 134 | color: #fff; 135 | border-radius: 2px; 136 | } 137 | 138 | /* Creates a small triangle extender for the tooltip */ 139 | .d3-tip:after { 140 | box-sizing: border-box; 141 | display: inline; 142 | font-size: 10px; 143 | width: 100%; 144 | line-height: 1; 145 | color: rgba(128, 128, 128, 0.9); 146 | content: "\25BC"; 147 | position: absolute; 148 | text-align: center; 149 | } 150 | 151 | /* Style northward tooltips differently */ 152 | .d3-tip.n:after { 153 | margin: -1px 0 0 0; 154 | top: 100%; 155 | left: 0; 156 | } 157 | 158 | /*Gantt elements */ 159 | 160 | .task { 161 | fill: steelblue; 162 | stroke: none; 163 | } 164 | -------------------------------------------------------------------------------- /articles/css/tufte.css: -------------------------------------------------------------------------------- 1 | html { font-size: 10px; } 2 | 3 | 4 | body { width: 87.5%; 5 | margin-left: auto; 6 | margin-right: auto; 7 | padding-left: 12.5%; 8 | font-family: 'Raleway'; 9 | padding-bottom: 20rem; 10 | color: #777; 11 | max-width: 1200px; } 12 | 13 | h1 { font-family: 'Lato'; 14 | font-weight: 400; 15 | margin-top: 4rem; 16 | margin-bottom: 2rem; 17 | font-size: 2.5rem; 18 | line-height: 1; 19 | width: 55%; } 20 | 21 | h2 { font-family: 'Lato'; 22 | font-weight: 400; 23 | margin-top: 2.1rem; 24 | margin-bottom: 0; 25 | font-size: 2rem; 26 | line-height: 1; } 27 | 28 | h3 { font-family: 'Lato'; 29 | font-style: italic; 30 | font-weight: 400; 31 | font-size: 1.7rem; 32 | margin-top: 2rem; 33 | margin-bottom: 0; 34 | line-height: 1; } 35 | 36 | h4, h5, h6 { font-family: 'Lato'; 37 | font-weight: 400; 38 | font-size: 1.5rem; 39 | margin-top: 2rem; 40 | margin-bottom: 0; 41 | line-height: 1; } 42 | 43 | subtitle { 44 | font-family: 'Lato'; 45 | font-style: italic; 46 | margin-top: 1rem; 47 | margin-bottom: 1rem; 48 | font-size: 1.8rem; 49 | display: block; 50 | line-height: 1; } 51 | 52 | table { width: 53%; 53 | text-align: right; 54 | font-size: 1.4rem; 55 | line-height: 1.4; 56 | margin-top: 1.4rem; 57 | margin-bottom: 1.4rem; 58 | margin-left: 1%; 59 | margin-right: 1%; 60 | border-top: 2px solid #333333; 61 | border-bottom: 2px solid #333333; 62 | border-collapse: separate; 63 | border-spacing: 0 5px; 64 | -webkit-font-feature-settings: 'tnum'; /* This is technically redundant */ 65 | -moz-font-feature-settings: 'tnum'; 66 | -ms-font-feature-settings: 'tnum'; } 67 | 68 | thead th { border-bottom: 1px solid #333333; 69 | font-weight: 400; 70 | border-collapse: separate; 71 | border-spacing: 5px 5px;} 72 | 73 | article { position: relative; 74 | padding-top: 5rem; } 75 | 76 | p, .list-container { font-size: 1.4rem; 77 | line-height: 2rem; 78 | margin-top: 1.4rem; 79 | margin-bottom: 1.4rem; 80 | width: 55%; 81 | padding-right: 0; 82 | vertical-align: baseline; } 83 | 84 | 85 | @media screen and (max-width: 600px) { p { width: 65%; }} 86 | @media screen and (max-width: 400px) { p { width: 90%; }} 87 | 88 | a { font-size: 1.4rem; 89 | color: #777; } 90 | 91 | 92 | img { max-width: 100%; 93 | clear: both; 94 | margin-bottom: 20px; 95 | margin-top: 20px;} 96 | 97 | sup, sub { vertical-align: baseline; 98 | position: relative; 99 | top: -0.4rem; } 100 | 101 | sub { top: 0.4rem; } 102 | 103 | sup.sidenote-number { 104 | font-size: 0.9rem; 105 | } 106 | 107 | .sans { font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif; } 108 | 109 | code { font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 110 | font-size: 1rem; 111 | line-height: 1.8; 112 | width: 55%; } 113 | 114 | .marginnote code { 115 | font-size: 0.9rem; 116 | } 117 | 118 | pre code { padding: 0 2em; } 119 | 120 | pre { 121 | width: 55%; 122 | overflow: hidden; 123 | } 124 | 125 | hr { 126 | width: 55%; 127 | float: left; 128 | clear: both; 129 | } 130 | 131 | .fullwidth { max-width: 95%; } 132 | 133 | ul {padding-left: 7%;} 134 | ol {padding-left: 7%;} 135 | 136 | .sidenote, .marginnote { float: right; 137 | clear: right; 138 | margin-right: -60%; 139 | width: 50%; 140 | margin-top: 0; 141 | margin-bottom: 20px; 142 | font-size: 1.0rem; 143 | line-height: 1.96; 144 | vertical-align: baseline; 145 | position: relative; } 146 | 147 | .marginnote a {font-size: 1.0rem; 148 | color: #777; } 149 | .sidenote a {font-size: 1.0rem; 150 | color: #777; } 151 | 152 | li .marginnote { float: right; 153 | clear: right; 154 | margin-right: -64.52%; 155 | width: 53.76%; 156 | margin-top: 0; 157 | margin-bottom: 20px; 158 | font-size: 1.0rem; 159 | line-height: 1.96; 160 | vertical-align: baseline; 161 | position: relative; } 162 | 163 | 164 | .checklist { padding-left:20px; list-style: none; } 165 | 166 | .checklist li { margin-bottom:10px; } 167 | .checklist li:before { 168 | font-family: FontAwesome; 169 | font-size: 11px; 170 | content: "\f096"; 171 | margin:0 8px 0 -15px; 172 | } 173 | 174 | @media screen and (max-width: 600px) { .sidenote, .marginnote { width: 25%; 175 | margin-right: -35%; }} 176 | @media screen and (max-width: 400px) { .sidenote, .marginnote { display:none; }} 177 | 178 | a.url { text-decoration: none; 179 | word-break: break-all; 180 | } 181 | 182 | span.newthought { font-variant: small-caps; } 183 | 184 | /* LaTeX-logo-specific styling */ 185 | .latex sub, .latex sup { text-transform: uppercase; } 186 | 187 | .latex sub { vertical-align: 0.2rem; 188 | margin-left: -0.1667rem; 189 | margin-right: -0.125rem; } 190 | 191 | .latex sup { vertical-align: -0.2rem; 192 | margin-left: -0.36rem; 193 | margin-right: -0.15rem; } 194 | 195 | @media print { *, *:before, *:after { background: transparent !important; 196 | /* Black prints faster: http://www.sanbeiji.com/archives/953 */ 197 | color: #000 !important; 198 | box-shadow: none !important; 199 | text-shadow: none !important; } 200 | 201 | thead { display: table-header-group; } 202 | 203 | tr, img { page-break-inside: avoid; } 204 | 205 | img { max-width: 100% !important; } 206 | 207 | 208 | p, h2, h3 { orphans: 3; 209 | widows: 3; } 210 | 211 | h2, h3 { page-break-after: avoid; }} -------------------------------------------------------------------------------- /articles/js/d3.tip.v0.6.3.js: -------------------------------------------------------------------------------- 1 | // d3.tip 2 | // Copyright (c) 2013 Justin Palmer 3 | // 4 | // Tooltips for d3.js SVG visualizations 5 | 6 | // Public - contructs a new tooltip 7 | // 8 | // Returns a tip 9 | d3.tip = function() { 10 | var direction = d3_tip_direction, 11 | offset = d3_tip_offset, 12 | html = d3_tip_html, 13 | node = initNode(), 14 | svg = null, 15 | point = null, 16 | target = null 17 | 18 | function tip(vis) { 19 | svg = getSVGNode(vis) 20 | point = svg.createSVGPoint() 21 | document.body.appendChild(node) 22 | } 23 | 24 | // Public - show the tooltip on the screen 25 | // 26 | // Returns a tip 27 | tip.show = function() { 28 | var args = Array.prototype.slice.call(arguments) 29 | if(args[args.length - 1] instanceof SVGElement) target = args.pop() 30 | 31 | var content = html.apply(this, args), 32 | poffset = offset.apply(this, args), 33 | dir = direction.apply(this, args), 34 | nodel = d3.select(node), i = 0, 35 | coords 36 | 37 | nodel.html(content) 38 | .style({ opacity: 1, 'pointer-events': 'all' }) 39 | 40 | while(i--) nodel.classed(directions[i], false) 41 | coords = direction_callbacks.get(dir).apply(this) 42 | nodel.classed(dir, true).style({ 43 | top: (coords.top + poffset[0]) + 'px', 44 | left: (coords.left + poffset[1]) + 'px' 45 | }) 46 | 47 | return tip 48 | } 49 | 50 | // Public - hide the tooltip 51 | // 52 | // Returns a tip 53 | tip.hide = function() { 54 | nodel = d3.select(node) 55 | nodel.style({ opacity: 0, 'pointer-events': 'none' }) 56 | return tip 57 | } 58 | 59 | // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value. 60 | // 61 | // n - name of the attribute 62 | // v - value of the attribute 63 | // 64 | // Returns tip or attribute value 65 | tip.attr = function(n, v) { 66 | if (arguments.length < 2 && typeof n === 'string') { 67 | return d3.select(node).attr(n) 68 | } else { 69 | var args = Array.prototype.slice.call(arguments) 70 | d3.selection.prototype.attr.apply(d3.select(node), args) 71 | } 72 | 73 | return tip 74 | } 75 | 76 | // Public: Proxy style calls to the d3 tip container. Sets or gets a style value. 77 | // 78 | // n - name of the property 79 | // v - value of the property 80 | // 81 | // Returns tip or style property value 82 | tip.style = function(n, v) { 83 | if (arguments.length < 2 && typeof n === 'string') { 84 | return d3.select(node).style(n) 85 | } else { 86 | var args = Array.prototype.slice.call(arguments) 87 | d3.selection.prototype.style.apply(d3.select(node), args) 88 | } 89 | 90 | return tip 91 | } 92 | 93 | // Public: Set or get the direction of the tooltip 94 | // 95 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest), 96 | // sw(southwest), ne(northeast) or se(southeast) 97 | // 98 | // Returns tip or direction 99 | tip.direction = function(v) { 100 | if (!arguments.length) return direction 101 | direction = v == null ? v : d3.functor(v) 102 | 103 | return tip 104 | } 105 | 106 | // Public: Sets or gets the offset of the tip 107 | // 108 | // v - Array of [x, y] offset 109 | // 110 | // Returns offset or 111 | tip.offset = function(v) { 112 | if (!arguments.length) return offset 113 | offset = v == null ? v : d3.functor(v) 114 | 115 | return tip 116 | } 117 | 118 | // Public: sets or gets the html value of the tooltip 119 | // 120 | // v - String value of the tip 121 | // 122 | // Returns html value or tip 123 | tip.html = function(v) { 124 | if (!arguments.length) return html 125 | html = v == null ? v : d3.functor(v) 126 | 127 | return tip 128 | } 129 | 130 | function d3_tip_direction() { return 'n' } 131 | function d3_tip_offset() { return [0, 0] } 132 | function d3_tip_html() { return ' ' } 133 | 134 | var direction_callbacks = d3.map({ 135 | n: direction_n, 136 | s: direction_s, 137 | e: direction_e, 138 | w: direction_w, 139 | nw: direction_nw, 140 | ne: direction_ne, 141 | sw: direction_sw, 142 | se: direction_se 143 | }), 144 | 145 | directions = direction_callbacks.keys() 146 | 147 | function direction_n() { 148 | var bbox = getScreenBBox() 149 | return { 150 | top: bbox.n.y - node.offsetHeight, 151 | left: bbox.n.x - node.offsetWidth / 2 152 | } 153 | } 154 | 155 | function direction_s() { 156 | var bbox = getScreenBBox() 157 | return { 158 | top: bbox.s.y, 159 | left: bbox.s.x - node.offsetWidth / 2 160 | } 161 | } 162 | 163 | function direction_e() { 164 | var bbox = getScreenBBox() 165 | return { 166 | top: bbox.e.y - node.offsetHeight / 2, 167 | left: bbox.e.x 168 | } 169 | } 170 | 171 | function direction_w() { 172 | var bbox = getScreenBBox() 173 | return { 174 | top: bbox.w.y - node.offsetHeight / 2, 175 | left: bbox.w.x - node.offsetWidth 176 | } 177 | } 178 | 179 | function direction_nw() { 180 | var bbox = getScreenBBox() 181 | return { 182 | top: bbox.nw.y - node.offsetHeight, 183 | left: bbox.nw.x - node.offsetWidth 184 | } 185 | } 186 | 187 | function direction_ne() { 188 | var bbox = getScreenBBox() 189 | return { 190 | top: bbox.ne.y - node.offsetHeight, 191 | left: bbox.ne.x 192 | } 193 | } 194 | 195 | function direction_sw() { 196 | var bbox = getScreenBBox() 197 | return { 198 | top: bbox.sw.y, 199 | left: bbox.sw.x - node.offsetWidth 200 | } 201 | } 202 | 203 | function direction_se() { 204 | var bbox = getScreenBBox() 205 | return { 206 | top: bbox.se.y, 207 | left: bbox.e.x 208 | } 209 | } 210 | 211 | function initNode() { 212 | var node = d3.select(document.createElement('div')) 213 | node.style({ 214 | position: 'absolute', 215 | opacity: 0, 216 | pointerEvents: 'none', 217 | boxSizing: 'border-box' 218 | }) 219 | 220 | return node.node() 221 | } 222 | 223 | function getSVGNode(el) { 224 | el = el.node() 225 | if(el.tagName.toLowerCase() == 'svg') 226 | return el 227 | 228 | return el.ownerSVGElement 229 | } 230 | 231 | // Private - gets the screen coordinates of a shape 232 | // 233 | // Given a shape on the screen, will return an SVGPoint for the directions 234 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest), 235 | // sw(southwest). 236 | // 237 | // +-+-+ 238 | // | | 239 | // + + 240 | // | | 241 | // +-+-+ 242 | // 243 | // Returns an Object {n, s, e, w, nw, sw, ne, se} 244 | function getScreenBBox() { 245 | var targetel = target || d3.event.target, 246 | bbox = {}, 247 | matrix = targetel.getScreenCTM(), 248 | tbbox = targetel.getBBox(), 249 | width = tbbox.width, 250 | height = tbbox.height, 251 | x = tbbox.x, 252 | y = tbbox.y, 253 | scrollTop = document.documentElement.scrollTop || document.body.scrollTop, 254 | scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft 255 | 256 | 257 | point.x = x + scrollLeft 258 | point.y = y + scrollTop 259 | bbox.nw = point.matrixTransform(matrix) 260 | point.x += width 261 | bbox.ne = point.matrixTransform(matrix) 262 | point.y += height 263 | bbox.se = point.matrixTransform(matrix) 264 | point.x -= width 265 | bbox.sw = point.matrixTransform(matrix) 266 | point.y -= height / 2 267 | bbox.w = point.matrixTransform(matrix) 268 | point.x += width 269 | bbox.e = point.matrixTransform(matrix) 270 | point.x -= width / 2 271 | point.y -= height / 2 272 | bbox.n = point.matrixTransform(matrix) 273 | point.y += height 274 | bbox.s = point.matrixTransform(matrix) 275 | 276 | return bbox 277 | } 278 | 279 | return tip 280 | }; 281 | -------------------------------------------------------------------------------- /articles/js/glasseyePictograms.js: -------------------------------------------------------------------------------- 1 | //Class amd methods 2 | 3 | var Bugs = function(processed_data, div, size, scales) { 4 | 5 | 6 | GlasseyeChart.call(this, div, size, undefined, 1000); 7 | 8 | this.processed_data = processed_data; 9 | 10 | //Make this less repetitive 11 | this.body_scale = scales[0].scale_func.range([5, 20]); 12 | this.ant_l_scale = scales[1].scale_func.range([0, 20]); 13 | this.ant_r_scale = scales[2].scale_func.range([0, 20]); 14 | 15 | this.leg_ml_scale = scales[3].scale_func.range([0, 20]); 16 | this.leg_mr_scale = scales[4].scale_func.range([0, 20]); 17 | 18 | this.leg_bl_scale = scales[5].scale_func.range([0, 20]); 19 | this.leg_br_scale = scales[6].scale_func.range([0, 20]); 20 | 21 | 22 | 23 | } 24 | 25 | Bugs.prototype = Object.create(GlasseyeChart.prototype); 26 | 27 | Bugs.prototype.add_bugs = function() { 28 | 29 | var body_scale = this.body_scale; 30 | var ant_r_scale = this.ant_r_scale; 31 | var ant_l_scale = this.ant_l_scale; 32 | var leg_ml_scale = this.leg_ml_scale; 33 | var leg_mr_scale = this.leg_mr_scale; 34 | var leg_bl_scale = this.leg_bl_scale; 35 | var leg_br_scale = this.leg_br_scale; 36 | 37 | var bug_glyphs = this.chart_area.selectAll("bug_glyph") 38 | .data(this.processed_data) 39 | .enter() 40 | .append("g") 41 | .attr("class", "bug_glyph") 42 | .attr("transform", function(d, i) { 43 | return ("translate(" + (10 + (i % 5) * 100) + "," + (10 + ((i - (i % 5))/5) * 100) + ")") 44 | }); 45 | 46 | 47 | 48 | 49 | //Right ant 50 | bug_glyphs.append("line") 51 | .attr("class", "bugleg") 52 | .attr("x1", 50) 53 | .attr("y1", 50) 54 | .attr("x2", function(d) { 55 | return(50 + Math.cos(Math.PI / 3)*(body_scale(d.body)+ant_r_scale(d.ant_r))); 56 | }) 57 | .attr("y2", function(d) { 58 | return(50 - Math.sin(Math.PI / 3)*(body_scale(d.body)+ant_r_scale(d.ant_r))); 59 | }); 60 | 61 | //Left ant 62 | bug_glyphs.append("line") 63 | .attr("class", "bugleg") 64 | .attr("x1", 50) 65 | .attr("y1", 50) 66 | .attr("x2", function(d) { 67 | return(50 - Math.cos(Math.PI / 3)* (body_scale(d.body)+ant_l_scale(d.ant_l))); 68 | }) 69 | .attr("y2", function(d) { 70 | return(50 - Math.sin(Math.PI / 3)*(body_scale(d.body)+ant_l_scale(d.ant_l))); 71 | }); 72 | 73 | //Right bottom 74 | bug_glyphs.append("line") 75 | .attr("class", "bugleg") 76 | .attr("x1", 50) 77 | .attr("y1", 50) 78 | .attr("x2", function(d) { 79 | return(50 + Math.cos(Math.PI / 3)*(body_scale(d.body)+leg_br_scale(d.leg_br))); 80 | }) 81 | .attr("y2", function(d) { 82 | return(50 + Math.sin(Math.PI / 3)*(body_scale(d.body)+leg_br_scale(d.leg_br))); 83 | }); 84 | 85 | //Left bottom 86 | bug_glyphs.append("line") 87 | .attr("class", "bugleg") 88 | .attr("x1", 50) 89 | .attr("y1", 50) 90 | .attr("x2", function(d) { 91 | return(50 - Math.cos(Math.PI / 3)*(body_scale(d.body)+leg_bl_scale(d.leg_bl))); 92 | }) 93 | .attr("y2", function(d) { 94 | return(50 + Math.sin(Math.PI / 3)*(body_scale(d.body)+leg_bl_scale(d.leg_bl))); 95 | }); 96 | 97 | //Right middle 98 | bug_glyphs.append("line") 99 | .attr("class", "bugleg") 100 | .attr("x1", 50) 101 | .attr("y1", 50) 102 | .attr("x2", function(d) { 103 | return (50+ (body_scale(d.body)+leg_mr_scale(d.leg_mr))); 104 | }) 105 | .attr("y2", 50); 106 | 107 | //Left middle 108 | bug_glyphs.append("line") 109 | .attr("class", "bugleg") 110 | .attr("x1", 50) 111 | .attr("y1", 50) 112 | .attr("x2", function(d) { 113 | return (50 - (body_scale(d.body)+leg_ml_scale(d.leg_ml))); 114 | }) 115 | .attr("y2", 50); 116 | 117 | bug_glyphs.append("circle") 118 | .attr("class", "bugbody") 119 | .attr("cx", 50) 120 | .attr("cy", 50) 121 | .attr("r", function(d) { 122 | return body_scale(d.body)}) 123 | //.attr("r", 10) 124 | .attr("fill", d3.hsl("hsl(204, 70%, 23%)")); 125 | 126 | bug_glyphs.append("text") 127 | .attr("x", 50) 128 | .attr("y", 95) 129 | .attr("text-anchor", "middle") 130 | .text(function(d){return (d.label)}); 131 | 132 | 133 | } 134 | 135 | 136 | //Builder function 137 | 138 | function bugs(data, div, size){ 139 | 140 | var inline_parser = function(data) { 141 | return data; 142 | } 143 | 144 | var csv_parser = function(data) { 145 | 146 | var processed_data = data.map(function(d) { 147 | return { 148 | label: d.label, 149 | body: -d.body, 150 | ant_l: +d.ant_l, 151 | ant_r: +d.ant_r, 152 | leg_ml: +d.leg_ml, 153 | leg_mr: +d.leg_mr, 154 | leg_bl: +d.leg_ml, 155 | leg_br: +d.leg_mr, 156 | } 157 | }); 158 | 159 | return processed_data; 160 | } 161 | 162 | var draw = function(processed_data, div, size) { 163 | 164 | console.log(processed_data); 165 | 166 | //Make this nicer 167 | 168 | var body_values = processed_data.map(function(d) { 169 | return d.body; 170 | }); 171 | 172 | var ant_l_values = processed_data.map(function(d) { 173 | return d.ant_l; 174 | }); 175 | 176 | var ant_r_values = processed_data.map(function(d) { 177 | return d.ant_r; 178 | }); 179 | 180 | var leg_ml_values = processed_data.map(function(d) { 181 | return d.leg_ml; 182 | }); 183 | 184 | var leg_mr_values = processed_data.map(function(d) { 185 | return d.leg_mr; 186 | }); 187 | 188 | var leg_bl_values = processed_data.map(function(d) { 189 | return d.leg_bl; 190 | }); 191 | 192 | var leg_br_values = processed_data.map(function(d) { 193 | return d.leg_br; 194 | }); 195 | 196 | 197 | var scales = [create_scale(body_values, d3.scale.linear()), create_scale(ant_l_values, d3.scale.linear()), create_scale(ant_r_values, 198 | d3.scale.linear()), create_scale(leg_ml_values, d3.scale.linear()), create_scale(leg_mr_values, d3.scale.linear()), create_scale(leg_bl_values, d3.scale.linear()) 199 | , create_scale(leg_br_values, d3.scale.linear())]; 200 | 201 | 202 | var glasseye_chart = new Bugs(processed_data, div, size, scales); 203 | 204 | glasseye_chart.add_svg().add_bugs(); 205 | 206 | } 207 | 208 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 209 | 210 | } 211 | 212 | -------------------------------------------------------------------------------- /articles/js/icheck.min.js: -------------------------------------------------------------------------------- 1 | /*! iCheck v1.0.2 by Damir Sultanov, http://git.io/arlzeA, MIT Licensed */ 2 | (function(f){function A(a,b,d){var c=a[0],g=/er/.test(d)?_indeterminate:/bl/.test(d)?n:k,e=d==_update?{checked:c[k],disabled:c[n],indeterminate:"true"==a.attr(_indeterminate)||"false"==a.attr(_determinate)}:c[g];if(/^(ch|di|in)/.test(d)&&!e)x(a,g);else if(/^(un|en|de)/.test(d)&&e)q(a,g);else if(d==_update)for(var f in e)e[f]?x(a,f,!0):q(a,f,!0);else if(!b||"toggle"==d){if(!b)a[_callback]("ifClicked");e?c[_type]!==r&&q(a,g):x(a,g)}}function x(a,b,d){var c=a[0],g=a.parent(),e=b==k,u=b==_indeterminate, 3 | v=b==n,s=u?_determinate:e?y:"enabled",F=l(a,s+t(c[_type])),B=l(a,b+t(c[_type]));if(!0!==c[b]){if(!d&&b==k&&c[_type]==r&&c.name){var w=a.closest("form"),p='input[name="'+c.name+'"]',p=w.length?w.find(p):f(p);p.each(function(){this!==c&&f(this).data(m)&&q(f(this),b)})}u?(c[b]=!0,c[k]&&q(a,k,"force")):(d||(c[b]=!0),e&&c[_indeterminate]&&q(a,_indeterminate,!1));D(a,e,b,d)}c[n]&&l(a,_cursor,!0)&&g.find("."+C).css(_cursor,"default");g[_add](B||l(a,b)||"");g.attr("role")&&!u&&g.attr("aria-"+(v?n:k),"true"); 4 | g[_remove](F||l(a,s)||"")}function q(a,b,d){var c=a[0],g=a.parent(),e=b==k,f=b==_indeterminate,m=b==n,s=f?_determinate:e?y:"enabled",q=l(a,s+t(c[_type])),r=l(a,b+t(c[_type]));if(!1!==c[b]){if(f||!d||"force"==d)c[b]=!1;D(a,e,s,d)}!c[n]&&l(a,_cursor,!0)&&g.find("."+C).css(_cursor,"pointer");g[_remove](r||l(a,b)||"");g.attr("role")&&!f&&g.attr("aria-"+(m?n:k),"false");g[_add](q||l(a,s)||"")}function E(a,b){if(a.data(m)){a.parent().html(a.attr("style",a.data(m).s||""));if(b)a[_callback](b);a.off(".i").unwrap(); 5 | f(_label+'[for="'+a[0].id+'"]').add(a.closest(_label)).off(".i")}}function l(a,b,f){if(a.data(m))return a.data(m).o[b+(f?"":"Class")]}function t(a){return a.charAt(0).toUpperCase()+a.slice(1)}function D(a,b,f,c){if(!c){if(b)a[_callback]("ifToggled");a[_callback]("ifChanged")[_callback]("if"+t(f))}}var m="iCheck",C=m+"-helper",r="radio",k="checked",y="un"+k,n="disabled";_determinate="determinate";_indeterminate="in"+_determinate;_update="update";_type="type";_click="click";_touch="touchbegin.i touchend.i"; 6 | _add="addClass";_remove="removeClass";_callback="trigger";_label="label";_cursor="cursor";_mobile=/ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);f.fn[m]=function(a,b){var d='input[type="checkbox"], input[type="'+r+'"]',c=f(),g=function(a){a.each(function(){var a=f(this);c=a.is(d)?c.add(a):c.add(a.find(d))})};if(/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(a))return a=a.toLowerCase(),g(this),c.each(function(){var c= 7 | f(this);"destroy"==a?E(c,"ifDestroyed"):A(c,!0,a);f.isFunction(b)&&b()});if("object"!=typeof a&&a)return this;var e=f.extend({checkedClass:k,disabledClass:n,indeterminateClass:_indeterminate,labelHover:!0},a),l=e.handle,v=e.hoverClass||"hover",s=e.focusClass||"focus",t=e.activeClass||"active",B=!!e.labelHover,w=e.labelHoverClass||"hover",p=(""+e.increaseArea).replace("%","")|0;if("checkbox"==l||l==r)d='input[type="'+l+'"]';-50>p&&(p=-50);g(this);return c.each(function(){var a=f(this);E(a);var c=this, 8 | b=c.id,g=-p+"%",d=100+2*p+"%",d={position:"absolute",top:g,left:g,display:"block",width:d,height:d,margin:0,padding:0,background:"#fff",border:0,opacity:0},g=_mobile?{position:"absolute",visibility:"hidden"}:p?d:{position:"absolute",opacity:0},l="checkbox"==c[_type]?e.checkboxClass||"icheckbox":e.radioClass||"i"+r,z=f(_label+'[for="'+b+'"]').add(a.closest(_label)),u=!!e.aria,y=m+"-"+Math.random().toString(36).substr(2,6),h='
")[_callback]("ifCreated").parent().append(e.insert);d=f('').css(d).appendTo(h);a.data(m,{o:e,s:a.attr("style")}).css(g);e.inheritClass&&h[_add](c.className||"");e.inheritID&&b&&h.attr("id",m+"-"+b);"static"==h.css("position")&&h.css("position","relative");A(a,!0,_update);if(z.length)z.on(_click+".i mouseover.i mouseout.i "+_touch,function(b){var d=b[_type],e=f(this);if(!c[n]){if(d==_click){if(f(b.target).is("a"))return; 10 | A(a,!1,!0)}else B&&(/ut|nd/.test(d)?(h[_remove](v),e[_remove](w)):(h[_add](v),e[_add](w)));if(_mobile)b.stopPropagation();else return!1}});a.on(_click+".i focus.i blur.i keyup.i keydown.i keypress.i",function(b){var d=b[_type];b=b.keyCode;if(d==_click)return!1;if("keydown"==d&&32==b)return c[_type]==r&&c[k]||(c[k]?q(a,k):x(a,k)),!1;if("keyup"==d&&c[_type]==r)!c[k]&&x(a,k);else if(/us|ur/.test(d))h["blur"==d?_remove:_add](s)});d.on(_click+" mousedown mouseup mouseover mouseout "+_touch,function(b){var d= 11 | b[_type],e=/wn|up/.test(d)?t:v;if(!c[n]){if(d==_click)A(a,!1,!0);else{if(/wn|er|in/.test(d))h[_add](e);else h[_remove](e+" "+t);if(z.length&&B&&e==v)z[/ut|nd/.test(d)?_remove:_add](w)}if(_mobile)b.stopPropagation();else return!1}})})}})(window.jQuery||window.Zepto); 12 | -------------------------------------------------------------------------------- /articles/logreg: -------------------------------------------------------------------------------- 1 | Although logistic regression has been around for a very long time now it is used less often than it should be. Part of the reason is that is difficult to explain the results to an audience that is not statistically minded. Unlike linear regression there is no one answer to the question how much does the increase in a particular input change the output. 2 | 3 | Changing: 4 | 5 | For a subject with attributes gender = male, age = 35 and ... the impact of changing the attribute X from y to z is that the probability of q increases from a to b. 6 | -------------------------------------------------------------------------------- /articles/stack.csv: -------------------------------------------------------------------------------- 1 | "group","label","value" 2 | kermit, burgers, 22 3 | kermit, hotdogs, 12 4 | kermit, pizzas, 55 5 | kermit, fries, 33 6 | gonzo, burgers, 13 7 | gonzo, hotdogs, 14 8 | gonzo, pizzas, 99 9 | gonzo, fries, 10 10 | beaker, burgers, 11 11 | beaker, hotdogs, 52 12 | beaker, pizzas, 35 13 | beaker, fries, 55 14 | -------------------------------------------------------------------------------- /articles/templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Glasseye 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /gantt.md: -------------------------------------------------------------------------------- 1 | Adding some simple Gantt charts to a Glasseye document 2 | ====================================================== 3 | 4 | Here's a way of adding a simple Gantt chart into your glasseyeA program built in python and javascript to combine markdown, d3 and a Tufte inspired layout. See below document. I find a simple version of a Gantt chart useful when creating plans and proposals. It gives a rough idea of the time scales involved. The syntax is as follows: 5 | 6 | ``` 7 | 8 | [{"task": "Analysis phase", "start": "01/03/2015", "end": "12/03/2015"}, 9 | {"task": "Build phase", "start": "13/03/2015", "end": "24/03/2015"}, 10 | {"task": "Testing phase", "start": "25/03/2015", "end": "15/04/2015"}] 11 | 12 | ``` 13 | 14 | This will give you the following 15 | 16 | 17 | [{"task": "Analysis phase", "start": "01/03/2015", "end": "12/03/2015"}, 18 | {"task": "Build phase", "start": "13/03/2015", "end": "24/03/2015"}, 19 | {"task": "Testing phase", "start": "25/03/2015", "end": "15/04/2015"}] 20 | 21 | 22 | As with all the charts in the glasseye package it is also designed to appear in the margin 23 | An example of a Gantt chart in the margin. 24 | 25 | [{"task": "Analysis phase", "start": "01/03/2015", "end": "12/03/2015"}, 26 | {"task": "Build phase", "start": "13/03/2015", "end": "24/03/2015"}, 27 | {"task": "Testing phase", "start": "25/03/2015", "end": "15/04/2015"}] 28 | 29 | To do this you'll need to wrap it in the `` or `` tags. 30 | 31 | ##What is glasseye? 32 | 33 | GlasseyeSee the [github repository](https://github.com/coppeliaMLA/glasseye) for the source code is something I'm developing to present the results of statistical analysis in an attractive and hopefully interesting way. It brings together three great things that I use a lot: 34 | 35 | 1. The markdown markup language. 36 | 2. The Tufte wide margin layout 37 | 3. Visualisation using d3.js 38 | 39 | The idea is to be able to write up work in markdownMarkdown is a lightweight markup language with a simple easy-to-use syntax. Text written in markdown can be converted into HTML as well as a many other formats and have the results transformed into something like a Tufte layout. For the Tufte layout I took the excellent tufte.css style sheet developed by [Dave Liepmann and co](https://github.com/daveliepmann/tufte-css) and made a few changes to suit my purpose. Finally I've added some d3 charts (just a small selection at the moment but this will grow) that can easily invoked from within the markdown. 40 | 41 | It's all very very beta at the moment. I'm not claiming it's ready to go. I would like to add lots more charts, redesign the d3 code and improve its overall usability (in particular replace the tags approach with something more in the spirit of markdown) however I thought I'd share it as it is. There's also a lot be done in terms of good design practice (css and all that). Please don't judge me **yet**! 42 | 43 | ##What it can do 44 | 45 | In case it's not obvious this web page was written using glasseyeYou can view the markdown [here](viewMarkdown.txt), that it is it was written in markdown with a few extra html-like tags thrown in. 46 | 47 | ###Side notes and margin notes 48 | First there's the `` tag. Anything enclosed in these tags will generate a numbered side note in the wide margin as close as possible to the note number in the main text. For example, I've used one hereI'm a side note! Use me for commentary, links, bits of maths, anything that's peripheral to the main discussion.. 49 | 50 | Then there is a `` tag which is the nearly the same as the side note, only there's no number linking it to a particular part in the main text. You'll see to the right an example of a margin note containing a d3 donut. 51 | 52 | Read more on [github](https://github.com/coppeliaMLA/glasseye) or on the [main demo page](demo.html). 53 | -------------------------------------------------------------------------------- /glasseye/.idea/.name: -------------------------------------------------------------------------------- 1 | glasseye -------------------------------------------------------------------------------- /glasseye/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /glasseye/.idea/glasseye.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /glasseye/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /glasseye/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /glasseye/.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /glasseye/MultiChart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Glasseye 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 62 | 63 | 64 | 65 | 66 | 67 |
68 |
69 |
Measure: 70 |
71 | Absolute 72 |
73 | Share 74 |
75 | Indexed 76 |
77 |
78 |
79 | 80 |
81 |
82 |
83 |
84 |
85 | 86 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /glasseye/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coppeliaMLA/glasseye/b7cf415923c1d26aa8aa3d36ddf510ce09fd6aac/glasseye/__init__.py -------------------------------------------------------------------------------- /glasseye/__main__.py: -------------------------------------------------------------------------------- 1 | import sys, os, re, pypandoc as py, shutil as sh 2 | from bs4 import BeautifulSoup 3 | 4 | def main(): 5 | #Function to wrap with tags 6 | def wrap(to_wrap, wrap_in): 7 | contents = to_wrap.replace_with(wrap_in) 8 | wrap_in.append(contents) 9 | 10 | #Function to add charts 11 | def add_chart(chart_id, code_string): 12 | for d in enumerate(soup.findAll(chart_id)): 13 | code_string += chart_id + "(" + str(d[1].contents[0]) + ", '#" + chart_id + "_" + str(d[0]) 14 | if d[1].parent.name == "span": 15 | code_string += "', 'margin'); \n" 16 | else: 17 | code_string += "', 'full_page'); \n" 18 | d[1].name = "span" 19 | d[1].contents = "" 20 | d[1]['id'] = chart_id + "_" + str(d[0]) 21 | tag = soup.new_tag("br") 22 | d[1].insert_after(tag) 23 | return code_string 24 | 25 | #Get paths and file names 26 | user_path = os.getcwd() + "/" 27 | input_file = sys.argv[1] 28 | glasseye_path = os.path.dirname(os.path.abspath(__file__)) + "/" 29 | pandoc_html = "pandocHTML.html" 30 | stem = os.path.splitext(input_file)[0] 31 | glasseye_file = stem + ".html" 32 | 33 | 34 | tufte_template = "templates/tufteTemplate.html" 35 | 36 | #Copy across directories to the user location 37 | if user_path != glasseye_path: 38 | css_path = user_path + "css" 39 | if os.path.exists(css_path): 40 | sh.rmtree(css_path) 41 | sh.copytree(glasseye_path + "css", css_path) 42 | 43 | js_path = user_path + "js" 44 | if os.path.exists(js_path): 45 | sh.rmtree(js_path) 46 | sh.copytree(glasseye_path + "js", js_path) 47 | 48 | template_path = user_path + 'templates/template.html' 49 | if not(os.path.isfile(template_path)): 50 | os.mkdir(user_path + 'templates/') 51 | sh.copy(glasseye_path + tufte_template, template_path) 52 | 53 | tufte_template = template_path 54 | 55 | #Convert markdown to html using pandoc 56 | py.convert(user_path+input_file, 'html', outputfile = user_path + "pandocHTML.html", extra_args=['--mathjax']) 57 | 58 | #Read in the html and soupify it 59 | read_html= open(user_path + pandoc_html,'r').read() 60 | soup = BeautifulSoup(read_html, 'html.parser') 61 | 62 | #Make the changes for the Tufte format 63 | for a in soup.findAll('marginnote'): 64 | p = soup.new_tag("p") 65 | a.replaceWith(p) 66 | p.insert(0, a) 67 | a.name = "span" 68 | a['class'] = "marginnote" 69 | 70 | for a in enumerate(soup.findAll('sidenote')): 71 | a[1].name = "span" 72 | a[1]['class'] = "marginnote" 73 | a[1].insert(0, str(a[0]+1) + ". ") 74 | tag = soup.new_tag("sup") 75 | tag.string = str(a[0]+1) 76 | a[1].insert_before(tag) 77 | 78 | for a in soup.findAll('checklist'): 79 | l = a.parent.findNext('ul') 80 | l['class'] = "checklist" 81 | a.extract() 82 | 83 | if soup.ol != None: 84 | for ol in soup.findAll('ol'): 85 | if ol.parent.name != 'li': 86 | wrap(ol, soup.new_tag("div", **{'class':'list-container'})) 87 | 88 | if soup.ul != None: 89 | for ul in soup.findAll('ul'): 90 | if ul.parent.name != 'li': 91 | wrap(ul, soup.new_tag("div", **{'class':'list-container'})) 92 | 93 | 94 | #Process the charts 95 | code_string = "" 96 | 97 | #Standard charts 98 | 99 | standard_charts = ["simplot", "treemap", "dot_plot", "gantt", "donut", "barchart", "tree", "force", "venn", "scatterplot", "timeseries"] 100 | 101 | for s in standard_charts: 102 | code_string = add_chart(s, code_string) 103 | 104 | #Charts with extra features (will modify the standard charts asap) 105 | 106 | for d in enumerate(soup.findAll('lineplot')): 107 | if d[1].parent.name == "span": 108 | size = "margin" 109 | else: 110 | size = "full_page" 111 | arguments = str(d[1].contents[0]) 112 | if "," in arguments and ".csv" in arguments: 113 | arguments = arguments.split(",", 1) 114 | code_string += "lineplot(" + arguments[0] + ", " + "'#lineplot_" + str(d[0]) + "','" + size + "'," + arguments[1] + "); \n" 115 | else: 116 | code_string += "lineplot(" + str(d[1].contents[0]) + ", " + "'#lineplot_" + str(d[0]) + "','" + size + "'); \n" 117 | d[1].name = "span" 118 | d[1].contents = "" 119 | d[1]['id'] = "lineplot_" + str(d[0]) 120 | tag = soup.new_tag("br") 121 | d[1].insert_after(tag) 122 | 123 | 124 | soup_string = str(soup) 125 | 126 | #Write to file with header and footer from template 127 | with open(template_path, "r") as template: 128 | with open(user_path + glasseye_file, "w") as glasseye_file: 129 | for line in template: 130 | if '
' in line: 131 | glasseye_file.write('
') 132 | glasseye_file.write(soup_string) 133 | elif '' in line: 134 | glasseye_file.write("") 135 | else: 136 | glasseye_file.write(line) 137 | 138 | if __name__ == '__main__': 139 | main() 140 | -------------------------------------------------------------------------------- /glasseye/css/GlasseyeCharts.css: -------------------------------------------------------------------------------- 1 | /* General chart formatting */ 2 | 3 | svg { 4 | clear: both; 5 | margin-top: 20px; 6 | margin-bottom: 20px; 7 | } 8 | 9 | text { 10 | stroke: none; 11 | font-family: 'Raleway'; 12 | font-size: 10px; 13 | } 14 | 15 | .line { 16 | fill: none; 17 | stroke: steelblue; 18 | stroke-width: 1.5px; 19 | } 20 | 21 | .chart_grid .tick { 22 | stroke: lightgrey; 23 | opacity: 0.7; 24 | } 25 | 26 | .chart_grid path { 27 | stroke-width: 0; 28 | shape-rendering: crispEdges; 29 | } 30 | 31 | .chart_grid .temp_y_label text { 32 | font-size: 10px; 33 | fill: grey; 34 | shape-rendering: crispEdges; 35 | } 36 | 37 | .legend_item text { 38 | font-size: 10px; 39 | fill: grey; 40 | shape-rendering: crispEdges; 41 | } 42 | 43 | .axis_label text { 44 | font-size: 8px; 45 | fill: grey; 46 | shape-rendering: crispEdges; 47 | text-transform: uppercase; 48 | stroke: none; 49 | } 50 | 51 | /*line plot*/ 52 | 53 | .line_points { 54 | opacity: 0; 55 | } 56 | 57 | /*timeseries*/ 58 | 59 | .timeseries_line { 60 | stroke-width: 3px; 61 | fill: transparent; 62 | } 63 | 64 | /*bar chart*/ 65 | 66 | .bar { 67 | fill: steelblue; 68 | stroke: none; 69 | } 70 | 71 | /*thermometers*/ 72 | 73 | .glass { 74 | stroke: none; 75 | fill: grey; 76 | } 77 | 78 | .glass-gap { 79 | stroke: none; 80 | fill: white; 81 | } 82 | 83 | .mercury { 84 | stroke: none; 85 | fill: red; 86 | } 87 | 88 | /*tree*/ 89 | 90 | .treenode circle { 91 | fill: #fff; 92 | stroke: steelblue; 93 | stroke-width: 1.5px; 94 | } 95 | 96 | .treenode { 97 | font: 10px sans-serif; 98 | fill: grey; 99 | } 100 | 101 | .treelink { 102 | fill: none; 103 | stroke: #ccc; 104 | stroke-width: 1.5px; 105 | } 106 | 107 | /*AnimatedDensity*/ 108 | 109 | .block { 110 | 111 | fill: #1abc9c; 112 | } 113 | 114 | /*force*/ 115 | 116 | .forcenode { 117 | stroke: #fff; 118 | stroke-width: 1.5px; 119 | fill: steelblue; 120 | } 121 | 122 | .forcelink { 123 | stroke: #999; 124 | stroke-opacity: .6; 125 | } 126 | 127 | /* Tip formatting */ 128 | 129 | .d3-tip { 130 | line-height: 1; 131 | font-weight: bold; 132 | padding: 8px; 133 | background: rgba(128, 128, 128, 0.9); 134 | color: #fff; 135 | border-radius: 2px; 136 | } 137 | 138 | /* Creates a small triangle extender for the tooltip */ 139 | .d3-tip:after { 140 | box-sizing: border-box; 141 | display: inline; 142 | font-size: 10px; 143 | width: 100%; 144 | line-height: 1; 145 | color: rgba(128, 128, 128, 0.9); 146 | content: "\25BC"; 147 | position: absolute; 148 | text-align: center; 149 | } 150 | 151 | /* Style northward tooltips differently */ 152 | .d3-tip.n:after { 153 | margin: -1px 0 0 0; 154 | top: 100%; 155 | left: 0; 156 | } 157 | 158 | /*Gantt elements */ 159 | 160 | .task { 161 | fill: steelblue; 162 | stroke: none; 163 | } 164 | -------------------------------------------------------------------------------- /glasseye/css/tufte.css: -------------------------------------------------------------------------------- 1 | html { font-size: 10px; } 2 | 3 | 4 | body { width: 87.5%; 5 | margin-left: auto; 6 | margin-right: auto; 7 | padding-left: 12.5%; 8 | font-family: 'Raleway'; 9 | padding-bottom: 20rem; 10 | color: #777; 11 | max-width: 1200px; } 12 | 13 | h1 { font-family: 'Lato'; 14 | font-weight: 400; 15 | margin-top: 4rem; 16 | margin-bottom: 2rem; 17 | font-size: 2.5rem; 18 | line-height: 1; 19 | width: 55%; } 20 | 21 | h2 { font-family: 'Lato'; 22 | font-weight: 400; 23 | margin-top: 2.1rem; 24 | margin-bottom: 0; 25 | font-size: 2rem; 26 | line-height: 1; } 27 | 28 | h3 { font-family: 'Lato'; 29 | font-style: italic; 30 | font-weight: 400; 31 | font-size: 1.7rem; 32 | margin-top: 2rem; 33 | margin-bottom: 0; 34 | line-height: 1; } 35 | 36 | h4, h5, h6 { font-family: 'Lato'; 37 | font-weight: 400; 38 | font-size: 1.5rem; 39 | margin-top: 2rem; 40 | margin-bottom: 0; 41 | line-height: 1; } 42 | 43 | subtitle { 44 | font-family: 'Lato'; 45 | font-style: italic; 46 | margin-top: 1rem; 47 | margin-bottom: 1rem; 48 | font-size: 1.8rem; 49 | display: block; 50 | line-height: 1; } 51 | 52 | table { width: 53%; 53 | text-align: right; 54 | font-size: 1.4rem; 55 | line-height: 1.4; 56 | margin-top: 1.4rem; 57 | margin-bottom: 1.4rem; 58 | margin-left: 1%; 59 | margin-right: 1%; 60 | border-top: 2px solid #333333; 61 | border-bottom: 2px solid #333333; 62 | border-collapse: separate; 63 | border-spacing: 0 5px; 64 | -webkit-font-feature-settings: 'tnum'; /* This is technically redundant */ 65 | -moz-font-feature-settings: 'tnum'; 66 | -ms-font-feature-settings: 'tnum'; } 67 | 68 | thead th { border-bottom: 1px solid #333333; 69 | font-weight: 400; 70 | border-collapse: separate; 71 | border-spacing: 5px 5px;} 72 | 73 | article { position: relative; 74 | padding-top: 5rem; } 75 | 76 | p, .list-container { font-size: 1.4rem; 77 | line-height: 2rem; 78 | margin-top: 1.4rem; 79 | margin-bottom: 1.4rem; 80 | width: 55%; 81 | padding-right: 0; 82 | vertical-align: baseline; } 83 | 84 | 85 | @media screen and (max-width: 600px) { p { width: 65%; }} 86 | @media screen and (max-width: 400px) { p { width: 90%; }} 87 | 88 | a { font-size: 1.4rem; 89 | color: #777; } 90 | 91 | 92 | img { max-width: 100%; 93 | clear: both; 94 | margin-bottom: 20px; 95 | margin-top: 20px;} 96 | 97 | sup, sub { vertical-align: baseline; 98 | position: relative; 99 | top: -0.4rem; } 100 | 101 | sub { top: 0.4rem; } 102 | 103 | sup.sidenote-number { 104 | font-size: 0.9rem; 105 | } 106 | 107 | .sans { font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif; } 108 | 109 | code { font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 110 | font-size: 1rem; 111 | line-height: 1.8; 112 | width: 55%; } 113 | 114 | .marginnote code { 115 | font-size: 0.9rem; 116 | } 117 | 118 | pre code { padding: 0 2em; } 119 | 120 | pre { 121 | width: 55%; 122 | overflow: hidden; 123 | } 124 | 125 | hr { 126 | width: 55%; 127 | float: left; 128 | clear: both; 129 | } 130 | 131 | .fullwidth { max-width: 95%; } 132 | 133 | ul {padding-left: 7%;} 134 | ol {padding-left: 7%;} 135 | 136 | .sidenote, .marginnote { float: right; 137 | clear: right; 138 | margin-right: -60%; 139 | width: 50%; 140 | margin-top: 0; 141 | margin-bottom: 20px; 142 | font-size: 1.0rem; 143 | line-height: 1.96; 144 | vertical-align: baseline; 145 | position: relative; } 146 | 147 | .marginnote a {font-size: 1.0rem; 148 | color: #777; } 149 | .sidenote a {font-size: 1.0rem; 150 | color: #777; } 151 | 152 | li .marginnote { float: right; 153 | clear: right; 154 | margin-right: -64.52%; 155 | width: 53.76%; 156 | margin-top: 0; 157 | margin-bottom: 20px; 158 | font-size: 1.0rem; 159 | line-height: 1.96; 160 | vertical-align: baseline; 161 | position: relative; } 162 | 163 | 164 | .checklist { padding-left:20px; list-style: none; } 165 | 166 | .checklist li { margin-bottom:10px; } 167 | .checklist li:before { 168 | font-family: FontAwesome; 169 | font-size: 11px; 170 | content: "\f096"; 171 | margin:0 8px 0 -15px; 172 | } 173 | 174 | @media screen and (max-width: 600px) { .sidenote, .marginnote { width: 25%; 175 | margin-right: -35%; }} 176 | @media screen and (max-width: 400px) { .sidenote, .marginnote { display:none; }} 177 | 178 | a.url { text-decoration: none; 179 | word-break: break-all; 180 | } 181 | 182 | span.newthought { font-variant: small-caps; } 183 | 184 | /* LaTeX-logo-specific styling */ 185 | .latex sub, .latex sup { text-transform: uppercase; } 186 | 187 | .latex sub { vertical-align: 0.2rem; 188 | margin-left: -0.1667rem; 189 | margin-right: -0.125rem; } 190 | 191 | .latex sup { vertical-align: -0.2rem; 192 | margin-left: -0.36rem; 193 | margin-right: -0.15rem; } 194 | 195 | @media print { *, *:before, *:after { background: transparent !important; 196 | /* Black prints faster: http://www.sanbeiji.com/archives/953 */ 197 | color: #000 !important; 198 | box-shadow: none !important; 199 | text-shadow: none !important; } 200 | 201 | thead { display: table-header-group; } 202 | 203 | tr, img { page-break-inside: avoid; } 204 | 205 | img { max-width: 100% !important; } 206 | 207 | 208 | p, h2, h3 { orphans: 3; 209 | widows: 3; } 210 | 211 | h2, h3 { page-break-after: avoid; }} -------------------------------------------------------------------------------- /glasseye/demo/Tufte.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coppeliaMLA/glasseye/b7cf415923c1d26aa8aa3d36ddf510ce09fd6aac/glasseye/demo/Tufte.gif -------------------------------------------------------------------------------- /glasseye/demo/css/GlasseyeCharts.css: -------------------------------------------------------------------------------- 1 | /* General chart formatting */ 2 | 3 | svg { 4 | clear: both; 5 | margin-top: 20px; 6 | margin-bottom: 20px; 7 | } 8 | 9 | text { 10 | stroke: none; 11 | font-family: 'Raleway'; 12 | font-size: 10px; 13 | } 14 | 15 | .line { 16 | fill: none; 17 | stroke: steelblue; 18 | stroke-width: 1.5px; 19 | } 20 | 21 | .chart_grid .tick { 22 | stroke: lightgrey; 23 | opacity: 0.7; 24 | } 25 | 26 | .chart_grid path { 27 | stroke-width: 0; 28 | shape-rendering: crispEdges; 29 | } 30 | 31 | .chart_grid .temp_y_label text { 32 | font-size: 10px; 33 | fill: grey; 34 | shape-rendering: crispEdges; 35 | } 36 | 37 | .legend_item text { 38 | font-size: 10px; 39 | fill: grey; 40 | shape-rendering: crispEdges; 41 | } 42 | 43 | .axis_label text { 44 | font-size: 8px; 45 | fill: grey; 46 | shape-rendering: crispEdges; 47 | text-transform: uppercase; 48 | stroke: none; 49 | } 50 | 51 | /*line plot*/ 52 | 53 | .line_points { 54 | opacity: 0; 55 | } 56 | 57 | /*timeseries*/ 58 | 59 | .timeseries_line { 60 | stroke-width: 3px; 61 | fill: transparent; 62 | } 63 | 64 | /*bar chart*/ 65 | 66 | .bar { 67 | fill: steelblue; 68 | stroke: none; 69 | } 70 | 71 | /*thermometers*/ 72 | 73 | .glass { 74 | stroke: none; 75 | fill: grey; 76 | } 77 | 78 | .glass-gap { 79 | stroke: none; 80 | fill: white; 81 | } 82 | 83 | .mercury { 84 | stroke: none; 85 | fill: red; 86 | } 87 | 88 | /*tree*/ 89 | 90 | .treenode circle { 91 | fill: #fff; 92 | stroke: steelblue; 93 | stroke-width: 1.5px; 94 | } 95 | 96 | .treenode { 97 | font: 10px sans-serif; 98 | fill: grey; 99 | } 100 | 101 | .treelink { 102 | fill: none; 103 | stroke: #ccc; 104 | stroke-width: 1.5px; 105 | } 106 | 107 | /*AnimatedDensity*/ 108 | 109 | .block { 110 | 111 | fill: #1abc9c; 112 | } 113 | 114 | /*force*/ 115 | 116 | .forcenode { 117 | stroke: #fff; 118 | stroke-width: 1.5px; 119 | fill: steelblue; 120 | } 121 | 122 | .forcelink { 123 | stroke: #999; 124 | stroke-opacity: .6; 125 | } 126 | 127 | /* Tip formatting */ 128 | 129 | .d3-tip { 130 | line-height: 1; 131 | font-weight: bold; 132 | padding: 8px; 133 | background: rgba(128, 128, 128, 0.9); 134 | color: #fff; 135 | border-radius: 2px; 136 | } 137 | 138 | /* Creates a small triangle extender for the tooltip */ 139 | .d3-tip:after { 140 | box-sizing: border-box; 141 | display: inline; 142 | font-size: 10px; 143 | width: 100%; 144 | line-height: 1; 145 | color: rgba(128, 128, 128, 0.9); 146 | content: "\25BC"; 147 | position: absolute; 148 | text-align: center; 149 | } 150 | 151 | /* Style northward tooltips differently */ 152 | .d3-tip.n:after { 153 | margin: -1px 0 0 0; 154 | top: 100%; 155 | left: 0; 156 | } 157 | 158 | /*Gantt elements */ 159 | 160 | .task { 161 | fill: steelblue; 162 | stroke: none; 163 | } 164 | -------------------------------------------------------------------------------- /glasseye/demo/css/tufte.css: -------------------------------------------------------------------------------- 1 | html { font-size: 10px; } 2 | 3 | 4 | body { width: 87.5%; 5 | margin-left: auto; 6 | margin-right: auto; 7 | padding-left: 12.5%; 8 | font-family: 'Raleway'; 9 | padding-bottom: 20rem; 10 | color: #777; 11 | max-width: 1200px; } 12 | 13 | h1 { font-family: 'Lato'; 14 | font-weight: 400; 15 | margin-top: 4rem; 16 | margin-bottom: 2rem; 17 | font-size: 2.5rem; 18 | line-height: 1; 19 | width: 55%; } 20 | 21 | h2 { font-family: 'Lato'; 22 | font-weight: 400; 23 | margin-top: 2.1rem; 24 | margin-bottom: 0; 25 | font-size: 2rem; 26 | line-height: 1; } 27 | 28 | h3 { font-family: 'Lato'; 29 | font-style: italic; 30 | font-weight: 400; 31 | font-size: 1.7rem; 32 | margin-top: 2rem; 33 | margin-bottom: 0; 34 | line-height: 1; } 35 | 36 | h4, h5, h6 { font-family: 'Lato'; 37 | font-weight: 400; 38 | font-size: 1.5rem; 39 | margin-top: 2rem; 40 | margin-bottom: 0; 41 | line-height: 1; } 42 | 43 | subtitle { 44 | font-family: 'Lato'; 45 | font-style: italic; 46 | margin-top: 1rem; 47 | margin-bottom: 1rem; 48 | font-size: 1.8rem; 49 | display: block; 50 | line-height: 1; } 51 | 52 | table { width: 53%; 53 | text-align: right; 54 | font-size: 1.4rem; 55 | line-height: 1.4; 56 | margin-top: 1.4rem; 57 | margin-bottom: 1.4rem; 58 | margin-left: 1%; 59 | margin-right: 1%; 60 | border-top: 2px solid #333333; 61 | border-bottom: 2px solid #333333; 62 | border-collapse: separate; 63 | border-spacing: 0 5px; 64 | -webkit-font-feature-settings: 'tnum'; /* This is technically redundant */ 65 | -moz-font-feature-settings: 'tnum'; 66 | -ms-font-feature-settings: 'tnum'; } 67 | 68 | thead th { border-bottom: 1px solid #333333; 69 | font-weight: 400; 70 | border-collapse: separate; 71 | border-spacing: 5px 5px;} 72 | 73 | article { position: relative; 74 | padding-top: 5rem; } 75 | 76 | p, .list-container { font-size: 1.4rem; 77 | line-height: 2rem; 78 | margin-top: 1.4rem; 79 | margin-bottom: 1.4rem; 80 | width: 55%; 81 | padding-right: 0; 82 | vertical-align: baseline; } 83 | 84 | 85 | @media screen and (max-width: 600px) { p { width: 65%; }} 86 | @media screen and (max-width: 400px) { p { width: 90%; }} 87 | 88 | a { font-size: 1.4rem; 89 | color: #777; } 90 | 91 | 92 | img { max-width: 100%; 93 | clear: both; 94 | margin-bottom: 20px; 95 | margin-top: 20px;} 96 | 97 | sup, sub { vertical-align: baseline; 98 | position: relative; 99 | top: -0.4rem; } 100 | 101 | sub { top: 0.4rem; } 102 | 103 | sup.sidenote-number { 104 | font-size: 0.9rem; 105 | } 106 | 107 | .sans { font-family: "Gill Sans", "Gill Sans MT", Calibri, sans-serif; } 108 | 109 | code { font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; 110 | font-size: 1rem; 111 | line-height: 1.8; 112 | width: 55%; } 113 | 114 | .marginnote code { 115 | font-size: 0.9rem; 116 | } 117 | 118 | pre code { padding: 0 2em; } 119 | 120 | pre { 121 | width: 55%; 122 | overflow: hidden; 123 | } 124 | 125 | hr { 126 | width: 55%; 127 | float: left; 128 | clear: both; 129 | } 130 | 131 | .fullwidth { max-width: 95%; } 132 | 133 | ul {padding-left: 7%;} 134 | ol {padding-left: 7%;} 135 | 136 | .sidenote, .marginnote { float: right; 137 | clear: right; 138 | margin-right: -60%; 139 | width: 50%; 140 | margin-top: 0; 141 | margin-bottom: 20px; 142 | font-size: 1.0rem; 143 | line-height: 1.96; 144 | vertical-align: baseline; 145 | position: relative; } 146 | 147 | .marginnote a {font-size: 1.0rem; 148 | color: #777; } 149 | .sidenote a {font-size: 1.0rem; 150 | color: #777; } 151 | 152 | li .marginnote { float: right; 153 | clear: right; 154 | margin-right: -64.52%; 155 | width: 53.76%; 156 | margin-top: 0; 157 | margin-bottom: 20px; 158 | font-size: 1.0rem; 159 | line-height: 1.96; 160 | vertical-align: baseline; 161 | position: relative; } 162 | 163 | 164 | .checklist { padding-left:20px; list-style: none; } 165 | 166 | .checklist li { margin-bottom:10px; } 167 | .checklist li:before { 168 | font-family: FontAwesome; 169 | font-size: 11px; 170 | content: "\f096"; 171 | margin:0 8px 0 -15px; 172 | } 173 | 174 | @media screen and (max-width: 600px) { .sidenote, .marginnote { width: 25%; 175 | margin-right: -35%; }} 176 | @media screen and (max-width: 400px) { .sidenote, .marginnote { display:none; }} 177 | 178 | a.url { text-decoration: none; 179 | word-break: break-all; 180 | } 181 | 182 | span.newthought { font-variant: small-caps; } 183 | 184 | /* LaTeX-logo-specific styling */ 185 | .latex sub, .latex sup { text-transform: uppercase; } 186 | 187 | .latex sub { vertical-align: 0.2rem; 188 | margin-left: -0.1667rem; 189 | margin-right: -0.125rem; } 190 | 191 | .latex sup { vertical-align: -0.2rem; 192 | margin-left: -0.36rem; 193 | margin-right: -0.15rem; } 194 | 195 | @media print { *, *:before, *:after { background: transparent !important; 196 | /* Black prints faster: http://www.sanbeiji.com/archives/953 */ 197 | color: #000 !important; 198 | box-shadow: none !important; 199 | text-shadow: none !important; } 200 | 201 | thead { display: table-header-group; } 202 | 203 | tr, img { page-break-inside: avoid; } 204 | 205 | img { max-width: 100% !important; } 206 | 207 | 208 | p, h2, h3 { orphans: 3; 209 | widows: 3; } 210 | 211 | h2, h3 { page-break-after: avoid; }} -------------------------------------------------------------------------------- /glasseye/demo/data/lineplotExample.csv: -------------------------------------------------------------------------------- 1 | "","x","y" 2 | "1",1,36 3 | "2",2,25 4 | "3",3,23 5 | "4",4,22 6 | "5",5,28 7 | "6",6,12 8 | "7",7,7 9 | "8",8,6 10 | "9",9,9 11 | "10",10,2 12 | "11",11,4 13 | "12",12,0 14 | "13",13,0 15 | "14",14,1 16 | "15",15,1 17 | "16",16,1 18 | "17",17,0 19 | "18",18,1 20 | "19",19,0 21 | "20",20,0 22 | "21",21,0 23 | "22",22,0 24 | "23",23,0 25 | "24",24,0 26 | "25",25,0 27 | "26",26,0 28 | "27",27,0 29 | "28",28,0 30 | "29",29,0 31 | "30",30,1 32 | "31",31,0 33 | "32",32,0 34 | "33",33,0 35 | "34",34,0 36 | "35",35,0 37 | "36",36,0 38 | "37",37,0 39 | "38",38,0 40 | "39",39,0 41 | "40",40,0 42 | -------------------------------------------------------------------------------- /glasseye/demo/data/share.csv: -------------------------------------------------------------------------------- 1 | "","label","value" 2 | "1","Mangos",326643 3 | "2","Bananas",269177 4 | "3","Apples",214828 5 | "4","Peaches",158987 6 | "5","Lemons",148878 7 | "6","Limes",138338 8 | "7","Kiwi",124419 9 | "8","Dates",103566 10 | "9","Blueberries",94012 11 | "10","Raspberries",83397 12 | "11","Loganberries",81986 13 | "12","Passion Fruit",77525 14 | "13","Melons",75488 15 | "14","Nectarines",67162 16 | "15","Satsumas",66334 17 | "16","Grapefruit",56200 18 | "17","Blood Oranges",53661 19 | "18","Strawberries",53544 20 | "19","Papaya",53512 21 | "20","Coconut",41066 22 | -------------------------------------------------------------------------------- /glasseye/demo/js/d3.tip.v0.6.3.js: -------------------------------------------------------------------------------- 1 | // d3.tip 2 | // Copyright (c) 2013 Justin Palmer 3 | // 4 | // Tooltips for d3.js SVG visualizations 5 | 6 | // Public - contructs a new tooltip 7 | // 8 | // Returns a tip 9 | d3.tip = function() { 10 | var direction = d3_tip_direction, 11 | offset = d3_tip_offset, 12 | html = d3_tip_html, 13 | node = initNode(), 14 | svg = null, 15 | point = null, 16 | target = null 17 | 18 | function tip(vis) { 19 | svg = getSVGNode(vis) 20 | point = svg.createSVGPoint() 21 | document.body.appendChild(node) 22 | } 23 | 24 | // Public - show the tooltip on the screen 25 | // 26 | // Returns a tip 27 | tip.show = function() { 28 | var args = Array.prototype.slice.call(arguments) 29 | if(args[args.length - 1] instanceof SVGElement) target = args.pop() 30 | 31 | var content = html.apply(this, args), 32 | poffset = offset.apply(this, args), 33 | dir = direction.apply(this, args), 34 | nodel = d3.select(node), i = 0, 35 | coords 36 | 37 | nodel.html(content) 38 | .style({ opacity: 1, 'pointer-events': 'all' }) 39 | 40 | while(i--) nodel.classed(directions[i], false) 41 | coords = direction_callbacks.get(dir).apply(this) 42 | nodel.classed(dir, true).style({ 43 | top: (coords.top + poffset[0]) + 'px', 44 | left: (coords.left + poffset[1]) + 'px' 45 | }) 46 | 47 | return tip 48 | } 49 | 50 | // Public - hide the tooltip 51 | // 52 | // Returns a tip 53 | tip.hide = function() { 54 | nodel = d3.select(node) 55 | nodel.style({ opacity: 0, 'pointer-events': 'none' }) 56 | return tip 57 | } 58 | 59 | // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value. 60 | // 61 | // n - name of the attribute 62 | // v - value of the attribute 63 | // 64 | // Returns tip or attribute value 65 | tip.attr = function(n, v) { 66 | if (arguments.length < 2 && typeof n === 'string') { 67 | return d3.select(node).attr(n) 68 | } else { 69 | var args = Array.prototype.slice.call(arguments) 70 | d3.selection.prototype.attr.apply(d3.select(node), args) 71 | } 72 | 73 | return tip 74 | } 75 | 76 | // Public: Proxy style calls to the d3 tip container. Sets or gets a style value. 77 | // 78 | // n - name of the property 79 | // v - value of the property 80 | // 81 | // Returns tip or style property value 82 | tip.style = function(n, v) { 83 | if (arguments.length < 2 && typeof n === 'string') { 84 | return d3.select(node).style(n) 85 | } else { 86 | var args = Array.prototype.slice.call(arguments) 87 | d3.selection.prototype.style.apply(d3.select(node), args) 88 | } 89 | 90 | return tip 91 | } 92 | 93 | // Public: Set or get the direction of the tooltip 94 | // 95 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest), 96 | // sw(southwest), ne(northeast) or se(southeast) 97 | // 98 | // Returns tip or direction 99 | tip.direction = function(v) { 100 | if (!arguments.length) return direction 101 | direction = v == null ? v : d3.functor(v) 102 | 103 | return tip 104 | } 105 | 106 | // Public: Sets or gets the offset of the tip 107 | // 108 | // v - Array of [x, y] offset 109 | // 110 | // Returns offset or 111 | tip.offset = function(v) { 112 | if (!arguments.length) return offset 113 | offset = v == null ? v : d3.functor(v) 114 | 115 | return tip 116 | } 117 | 118 | // Public: sets or gets the html value of the tooltip 119 | // 120 | // v - String value of the tip 121 | // 122 | // Returns html value or tip 123 | tip.html = function(v) { 124 | if (!arguments.length) return html 125 | html = v == null ? v : d3.functor(v) 126 | 127 | return tip 128 | } 129 | 130 | function d3_tip_direction() { return 'n' } 131 | function d3_tip_offset() { return [0, 0] } 132 | function d3_tip_html() { return ' ' } 133 | 134 | var direction_callbacks = d3.map({ 135 | n: direction_n, 136 | s: direction_s, 137 | e: direction_e, 138 | w: direction_w, 139 | nw: direction_nw, 140 | ne: direction_ne, 141 | sw: direction_sw, 142 | se: direction_se 143 | }), 144 | 145 | directions = direction_callbacks.keys() 146 | 147 | function direction_n() { 148 | var bbox = getScreenBBox() 149 | return { 150 | top: bbox.n.y - node.offsetHeight, 151 | left: bbox.n.x - node.offsetWidth / 2 152 | } 153 | } 154 | 155 | function direction_s() { 156 | var bbox = getScreenBBox() 157 | return { 158 | top: bbox.s.y, 159 | left: bbox.s.x - node.offsetWidth / 2 160 | } 161 | } 162 | 163 | function direction_e() { 164 | var bbox = getScreenBBox() 165 | return { 166 | top: bbox.e.y - node.offsetHeight / 2, 167 | left: bbox.e.x 168 | } 169 | } 170 | 171 | function direction_w() { 172 | var bbox = getScreenBBox() 173 | return { 174 | top: bbox.w.y - node.offsetHeight / 2, 175 | left: bbox.w.x - node.offsetWidth 176 | } 177 | } 178 | 179 | function direction_nw() { 180 | var bbox = getScreenBBox() 181 | return { 182 | top: bbox.nw.y - node.offsetHeight, 183 | left: bbox.nw.x - node.offsetWidth 184 | } 185 | } 186 | 187 | function direction_ne() { 188 | var bbox = getScreenBBox() 189 | return { 190 | top: bbox.ne.y - node.offsetHeight, 191 | left: bbox.ne.x 192 | } 193 | } 194 | 195 | function direction_sw() { 196 | var bbox = getScreenBBox() 197 | return { 198 | top: bbox.sw.y, 199 | left: bbox.sw.x - node.offsetWidth 200 | } 201 | } 202 | 203 | function direction_se() { 204 | var bbox = getScreenBBox() 205 | return { 206 | top: bbox.se.y, 207 | left: bbox.e.x 208 | } 209 | } 210 | 211 | function initNode() { 212 | var node = d3.select(document.createElement('div')) 213 | node.style({ 214 | position: 'absolute', 215 | opacity: 0, 216 | pointerEvents: 'none', 217 | boxSizing: 'border-box' 218 | }) 219 | 220 | return node.node() 221 | } 222 | 223 | function getSVGNode(el) { 224 | el = el.node() 225 | if(el.tagName.toLowerCase() == 'svg') 226 | return el 227 | 228 | return el.ownerSVGElement 229 | } 230 | 231 | // Private - gets the screen coordinates of a shape 232 | // 233 | // Given a shape on the screen, will return an SVGPoint for the directions 234 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest), 235 | // sw(southwest). 236 | // 237 | // +-+-+ 238 | // | | 239 | // + + 240 | // | | 241 | // +-+-+ 242 | // 243 | // Returns an Object {n, s, e, w, nw, sw, ne, se} 244 | function getScreenBBox() { 245 | var targetel = target || d3.event.target, 246 | bbox = {}, 247 | matrix = targetel.getScreenCTM(), 248 | tbbox = targetel.getBBox(), 249 | width = tbbox.width, 250 | height = tbbox.height, 251 | x = tbbox.x, 252 | y = tbbox.y, 253 | scrollTop = document.documentElement.scrollTop || document.body.scrollTop, 254 | scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft 255 | 256 | 257 | point.x = x + scrollLeft 258 | point.y = y + scrollTop 259 | bbox.nw = point.matrixTransform(matrix) 260 | point.x += width 261 | bbox.ne = point.matrixTransform(matrix) 262 | point.y += height 263 | bbox.se = point.matrixTransform(matrix) 264 | point.x -= width 265 | bbox.sw = point.matrixTransform(matrix) 266 | point.y -= height / 2 267 | bbox.w = point.matrixTransform(matrix) 268 | point.x += width 269 | bbox.e = point.matrixTransform(matrix) 270 | point.x -= width / 2 271 | point.y -= height / 2 272 | bbox.n = point.matrixTransform(matrix) 273 | point.y += height 274 | bbox.s = point.matrixTransform(matrix) 275 | 276 | return bbox 277 | } 278 | 279 | return tip 280 | }; 281 | -------------------------------------------------------------------------------- /glasseye/demo/js/glasseyePictograms.js: -------------------------------------------------------------------------------- 1 | //Class amd methods 2 | 3 | var Bugs = function(processed_data, div, size, scales) { 4 | 5 | 6 | GlasseyeChart.call(this, div, size, undefined, 1000); 7 | 8 | this.processed_data = processed_data; 9 | 10 | //Make this less repetitive 11 | this.body_scale = scales[0].scale_func.range([5, 20]); 12 | this.ant_l_scale = scales[1].scale_func.range([0, 20]); 13 | this.ant_r_scale = scales[2].scale_func.range([0, 20]); 14 | 15 | this.leg_ml_scale = scales[3].scale_func.range([0, 20]); 16 | this.leg_mr_scale = scales[4].scale_func.range([0, 20]); 17 | 18 | this.leg_bl_scale = scales[5].scale_func.range([0, 20]); 19 | this.leg_br_scale = scales[6].scale_func.range([0, 20]); 20 | 21 | 22 | 23 | } 24 | 25 | Bugs.prototype = Object.create(GlasseyeChart.prototype); 26 | 27 | Bugs.prototype.add_bugs = function() { 28 | 29 | var body_scale = this.body_scale; 30 | var ant_r_scale = this.ant_r_scale; 31 | var ant_l_scale = this.ant_l_scale; 32 | var leg_ml_scale = this.leg_ml_scale; 33 | var leg_mr_scale = this.leg_mr_scale; 34 | var leg_bl_scale = this.leg_bl_scale; 35 | var leg_br_scale = this.leg_br_scale; 36 | 37 | var bug_glyphs = this.chart_area.selectAll("bug_glyph") 38 | .data(this.processed_data) 39 | .enter() 40 | .append("g") 41 | .attr("class", "bug_glyph") 42 | .attr("transform", function(d, i) { 43 | return ("translate(" + (10 + (i % 5) * 100) + "," + (10 + ((i - (i % 5))/5) * 100) + ")") 44 | }); 45 | 46 | 47 | 48 | 49 | //Right ant 50 | bug_glyphs.append("line") 51 | .attr("class", "bugleg") 52 | .attr("x1", 50) 53 | .attr("y1", 50) 54 | .attr("x2", function(d) { 55 | return(50 + Math.cos(Math.PI / 3)*(body_scale(d.body)+ant_r_scale(d.ant_r))); 56 | }) 57 | .attr("y2", function(d) { 58 | return(50 - Math.sin(Math.PI / 3)*(body_scale(d.body)+ant_r_scale(d.ant_r))); 59 | }); 60 | 61 | //Left ant 62 | bug_glyphs.append("line") 63 | .attr("class", "bugleg") 64 | .attr("x1", 50) 65 | .attr("y1", 50) 66 | .attr("x2", function(d) { 67 | return(50 - Math.cos(Math.PI / 3)* (body_scale(d.body)+ant_l_scale(d.ant_l))); 68 | }) 69 | .attr("y2", function(d) { 70 | return(50 - Math.sin(Math.PI / 3)*(body_scale(d.body)+ant_l_scale(d.ant_l))); 71 | }); 72 | 73 | //Right bottom 74 | bug_glyphs.append("line") 75 | .attr("class", "bugleg") 76 | .attr("x1", 50) 77 | .attr("y1", 50) 78 | .attr("x2", function(d) { 79 | return(50 + Math.cos(Math.PI / 3)*(body_scale(d.body)+leg_br_scale(d.leg_br))); 80 | }) 81 | .attr("y2", function(d) { 82 | return(50 + Math.sin(Math.PI / 3)*(body_scale(d.body)+leg_br_scale(d.leg_br))); 83 | }); 84 | 85 | //Left bottom 86 | bug_glyphs.append("line") 87 | .attr("class", "bugleg") 88 | .attr("x1", 50) 89 | .attr("y1", 50) 90 | .attr("x2", function(d) { 91 | return(50 - Math.cos(Math.PI / 3)*(body_scale(d.body)+leg_bl_scale(d.leg_bl))); 92 | }) 93 | .attr("y2", function(d) { 94 | return(50 + Math.sin(Math.PI / 3)*(body_scale(d.body)+leg_bl_scale(d.leg_bl))); 95 | }); 96 | 97 | //Right middle 98 | bug_glyphs.append("line") 99 | .attr("class", "bugleg") 100 | .attr("x1", 50) 101 | .attr("y1", 50) 102 | .attr("x2", function(d) { 103 | return (50+ (body_scale(d.body)+leg_mr_scale(d.leg_mr))); 104 | }) 105 | .attr("y2", 50); 106 | 107 | //Left middle 108 | bug_glyphs.append("line") 109 | .attr("class", "bugleg") 110 | .attr("x1", 50) 111 | .attr("y1", 50) 112 | .attr("x2", function(d) { 113 | return (50 - (body_scale(d.body)+leg_ml_scale(d.leg_ml))); 114 | }) 115 | .attr("y2", 50); 116 | 117 | bug_glyphs.append("circle") 118 | .attr("class", "bugbody") 119 | .attr("cx", 50) 120 | .attr("cy", 50) 121 | .attr("r", function(d) { 122 | return body_scale(d.body)}) 123 | //.attr("r", 10) 124 | .attr("fill", d3.hsl("hsl(204, 70%, 23%)")); 125 | 126 | bug_glyphs.append("text") 127 | .attr("x", 50) 128 | .attr("y", 95) 129 | .attr("text-anchor", "middle") 130 | .text(function(d){return (d.label)}); 131 | 132 | 133 | } 134 | 135 | 136 | //Builder function 137 | 138 | function bugs(data, div, size){ 139 | 140 | var inline_parser = function(data) { 141 | return data; 142 | } 143 | 144 | var csv_parser = function(data) { 145 | 146 | var processed_data = data.map(function(d) { 147 | return { 148 | label: d.label, 149 | body: -d.body, 150 | ant_l: +d.ant_l, 151 | ant_r: +d.ant_r, 152 | leg_ml: +d.leg_ml, 153 | leg_mr: +d.leg_mr, 154 | leg_bl: +d.leg_ml, 155 | leg_br: +d.leg_mr, 156 | } 157 | }); 158 | 159 | return processed_data; 160 | } 161 | 162 | var draw = function(processed_data, div, size) { 163 | 164 | console.log(processed_data); 165 | 166 | //Make this nicer 167 | 168 | var body_values = processed_data.map(function(d) { 169 | return d.body; 170 | }); 171 | 172 | var ant_l_values = processed_data.map(function(d) { 173 | return d.ant_l; 174 | }); 175 | 176 | var ant_r_values = processed_data.map(function(d) { 177 | return d.ant_r; 178 | }); 179 | 180 | var leg_ml_values = processed_data.map(function(d) { 181 | return d.leg_ml; 182 | }); 183 | 184 | var leg_mr_values = processed_data.map(function(d) { 185 | return d.leg_mr; 186 | }); 187 | 188 | var leg_bl_values = processed_data.map(function(d) { 189 | return d.leg_bl; 190 | }); 191 | 192 | var leg_br_values = processed_data.map(function(d) { 193 | return d.leg_br; 194 | }); 195 | 196 | 197 | var scales = [create_scale(body_values, d3.scale.linear()), create_scale(ant_l_values, d3.scale.linear()), create_scale(ant_r_values, 198 | d3.scale.linear()), create_scale(leg_ml_values, d3.scale.linear()), create_scale(leg_mr_values, d3.scale.linear()), create_scale(leg_bl_values, d3.scale.linear()) 199 | , create_scale(leg_br_values, d3.scale.linear())]; 200 | 201 | 202 | var glasseye_chart = new Bugs(processed_data, div, size, scales); 203 | 204 | glasseye_chart.add_svg().add_bugs(); 205 | 206 | } 207 | 208 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 209 | 210 | } 211 | 212 | -------------------------------------------------------------------------------- /glasseye/demo/templates/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Glasseye 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /glasseye/demo/viewMarkdown.txt: -------------------------------------------------------------------------------- 1 | A Glasseye Demo 2 | ---------------- 3 | 4 | ##What is glasseye? 5 | 6 | GlasseyeSee the [github repository](https://github.com/coppeliaMLA/glasseye) for the source code is something I developed to present the results of statistical analysis in an attractive and hopefully interesting way. It brings together three great things that I use a lot: 7 | 8 | 1. The markdown markup language. 9 | 2. The Tufte wide margin layout 10 | 3. Visualisation using d3.js 11 | 12 | The idea is to be able to write up work in markdownMarkdown is a lightweight markup language with a simple easy-to-use syntax. Text written in markdown can be converted into HTML as well as a many other formats and have the results transformed into something like a Tufte layoutThe Tufte layout makes extenive use of a wide margin to display notes, images and charts

![](Tufte.gif)
of which more below. For the Tufte layout I took the excellent tufte.css style sheet developed by [Dave Liepmann and co](https://github.com/daveliepmann/tufte-css) and made a few changes to suit my purposes. Finally I've added some d3 charts (just a small selection at the moment but this will grow) that can easily invoked from within the markdown. 13 | 14 | It's all very very beta at the moment. I'm not claiming it's ready to go. I would like to add lots more charts, redesign the d3 code and improve it's overall usability (in particular replace the tags approach with something more in the spirit of markdown) however I thought I'd share it as it is. There's also a lot be done in terms of good design practice (css and all that). Please don't judge me **yet**! 15 | 16 | ##What it can do 17 | 18 | In case it's not obvious this web page was written using glasseyeYou can view the markdown here, that it is it was written in markdown with a few extra html-like tags thrown in. 19 | 20 | ###Side notes and margin notes 21 | First there's the `` tag. Anything enclosed in these tags will generate a numbered side note in the wide margin as close as possible to the note number in the main text. For example, I've used one hereI'm a side note! Use me for commentary, links, bits of maths, anything that's peripheral to the main discussion.. 22 | 23 | Then there is a `` tag which is the nearly the same as the side note, only there's no number linking it to a particular part in the main text. You'll see to the right an example of a margin note containing a d3 donut. 24 | 25 | 26 | An example of margin note containing a donut plot. Because a tooltip is available we can create a less cluttered chart with labels for the smaller segments demoted to the tooltip."data/share.csv"
Including d3 charts in a glasseye document is very easy. You just need to surround the name of the file containing the data with tags specfying the type of chart. For example this chart was generated using `"data/share.csv"` 27 |
28 | 29 | ###Latex 30 | 31 | I'm using pandoc to convert the markdown to html which means we can take advantage of its ability to transform latex into mathjax. For example the formula below was written in latex. 32 | 33 | $$E[L]= \frac{1}{B(\alpha)}\int \cdots \int_\mathbf{D}\ \sum_{j=1}^N \theta_{j}^2 \ \prod_{j=1}^N \theta_j^{\alpha_j-1} \ d\theta_1 \!\cdots d\theta_N $$ 34 | 35 | However sometimes the mathematical details are not central to a discussion, in which case it's nice to put them in a side note which can be easily done using the side-note or margin-note tags 36 | Here's an example of some maths that has been placed in a side note $$ f(\theta_1,\dots, \theta_N; \alpha_1,\dots, \alpha_N) = \frac{1}{\mathrm{B}(\alpha)} \prod_{i=1}^N \theta_i^{\alpha_i - 1} $$ 37 | Where the normalising constant is: 38 | $$ \mathrm{B}(\alpha) = \frac{\prod_{i=1}^N \Gamma(\alpha_i)}{\Gamma\bigl(\sum_{i=1}^N \alpha_i\bigr)},\qquad\alpha=(\alpha_1,\dots,\alpha_N)$$ 39 | 40 | 41 | ###d3 charts 42 | 43 | So far I've only a few charts for you to use but hopefully that will expand quite rapidly. I've tried to create charts that are simple and uncluttered with the tooltip taking over some of the work. This is so that can fit in the margin nicely. I've been thinking about making them as intellegent as possible so that choices are made for you about formatting (for example label positioning). That my prove annoying though so we'll see how it goes. It's easy to include any of the d3 charts into either the main body of the text or into the margin. 44 | 45 | Inserting a plot is again just a matter of using some custom tags. For example to generate a line plot just surround a string containing the path and filname of a csv file with a `` tag. You can optionally supply axis labels. 46 | An example of a line plot. Note the tooltip means we don't need y axis tick labels.
47 | "data/lineplotExample.csv", ["Size", "Number of explosions"]
48 | This plot was created by inserting the following line into the markdown. `"data/lineplotExample.csv", ["Size", "Number of explosions"]
49 | ` 50 | 51 | 52 | Alternatively you can write the data in json into the markdown. For example we can create an interactive treemapAn example of an intreactive treemap. Click on the rectangles to zoom in
53 |
{ "name": "All", "children": [{ "name": "Bakery", "size": 34 }, { "name": "Tinned Goods", "children": [{ "name": "Beans", "size": 34 }, { "name": "Soups", "size": 56 }, { "name": "Puddings", "children": [{ "name": "Fruit", "children": [{ "name": "Tangerines", "size": 15 }, { "name": "Pears", "size": 17 }]}, { "name": "Apricots", "size": 89 } ] }] }, { "name": "Meat and Fish", "children": [{ "name": "Meat", "children": [{ "name": "Poultry", "size": 15 }, { "name": "Beef", "size": 17 }]}, { "name": "Fish", "size": 89 } ] }] } 54 |
55 | by inserting the following into the markdown 56 | 57 | 58 | ``` 59 | { "name": "All", "children": [{ "name": "Bakery", "size": 34 }, 60 | { "name": "Tinned Goods", "children": [{ "name": "Beans", "size": 34 }, 61 | { "name": "Soups", "size": 56 }, { "name": "Puddings", "children": 62 | [{ "name": "Fruit", "children": [{ "name": "Tangerines", "size": 15 }, 63 | { "name": "Pears", "size": 17 }]}, { "name": "Apricots", "size": 89 } ] }] }, 64 | { "name": "Meat and Fish", "children": [{ "name": "Meat", "children": 65 | [{ "name": "Poultry", "size": 15 }, { "name": "Beef", "size": 17 }]}, 66 | { "name": "Fish", "size": 89 } ] }] } 67 | ``` 68 | 69 | Javascript charts also allow us to animate content which can be useful. I created a chart type `` for a project using agent based simulation. It animates a time line which helps bring home the fact that the data is computer generated 70 | 71 | "data/activeDecidedSim.csv"
72 | 73 | ##How it works 74 | 75 | At the moment it hangs together with pandoc and the python beautiful soup library. Pandoc is used to generate the html from the markdown and beautiful soup is used to manipulate the extra tags and make the appropriate substitutions. Until I get my act together with packaging it up you'll need to 76 | 77 | 1. Install pandoc from [here](http://pandoc.org) 78 | 2. Install python 2.7 and make sure you have the pypandoc and beautifulsoup libraries 79 | 3. Download the source code from github 80 | 81 | Then you should be able to run the following in a terminal 82 | 83 | ``` 84 | python glasseye.py your_md_file.md 85 | ``` 86 | 87 | It will produce a directory containing all the necessary html, js and css. 88 | 89 | Would be very pleased to hear from anyone giving it a go! 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /glasseye/js/d3.tip.v0.6.3.js: -------------------------------------------------------------------------------- 1 | // d3.tip 2 | // Copyright (c) 2013 Justin Palmer 3 | // 4 | // Tooltips for d3.js SVG visualizations 5 | 6 | // Public - contructs a new tooltip 7 | // 8 | // Returns a tip 9 | d3.tip = function() { 10 | var direction = d3_tip_direction, 11 | offset = d3_tip_offset, 12 | html = d3_tip_html, 13 | node = initNode(), 14 | svg = null, 15 | point = null, 16 | target = null 17 | 18 | function tip(vis) { 19 | svg = getSVGNode(vis) 20 | point = svg.createSVGPoint() 21 | document.body.appendChild(node) 22 | } 23 | 24 | // Public - show the tooltip on the screen 25 | // 26 | // Returns a tip 27 | tip.show = function() { 28 | var args = Array.prototype.slice.call(arguments) 29 | if(args[args.length - 1] instanceof SVGElement) target = args.pop() 30 | 31 | var content = html.apply(this, args), 32 | poffset = offset.apply(this, args), 33 | dir = direction.apply(this, args), 34 | nodel = d3.select(node), i = 0, 35 | coords 36 | 37 | nodel.html(content) 38 | .style({ opacity: 1, 'pointer-events': 'all' }) 39 | 40 | while(i--) nodel.classed(directions[i], false) 41 | coords = direction_callbacks.get(dir).apply(this) 42 | nodel.classed(dir, true).style({ 43 | top: (coords.top + poffset[0]) + 'px', 44 | left: (coords.left + poffset[1]) + 'px' 45 | }) 46 | 47 | return tip 48 | } 49 | 50 | // Public - hide the tooltip 51 | // 52 | // Returns a tip 53 | tip.hide = function() { 54 | nodel = d3.select(node) 55 | nodel.style({ opacity: 0, 'pointer-events': 'none' }) 56 | return tip 57 | } 58 | 59 | // Public: Proxy attr calls to the d3 tip container. Sets or gets attribute value. 60 | // 61 | // n - name of the attribute 62 | // v - value of the attribute 63 | // 64 | // Returns tip or attribute value 65 | tip.attr = function(n, v) { 66 | if (arguments.length < 2 && typeof n === 'string') { 67 | return d3.select(node).attr(n) 68 | } else { 69 | var args = Array.prototype.slice.call(arguments) 70 | d3.selection.prototype.attr.apply(d3.select(node), args) 71 | } 72 | 73 | return tip 74 | } 75 | 76 | // Public: Proxy style calls to the d3 tip container. Sets or gets a style value. 77 | // 78 | // n - name of the property 79 | // v - value of the property 80 | // 81 | // Returns tip or style property value 82 | tip.style = function(n, v) { 83 | if (arguments.length < 2 && typeof n === 'string') { 84 | return d3.select(node).style(n) 85 | } else { 86 | var args = Array.prototype.slice.call(arguments) 87 | d3.selection.prototype.style.apply(d3.select(node), args) 88 | } 89 | 90 | return tip 91 | } 92 | 93 | // Public: Set or get the direction of the tooltip 94 | // 95 | // v - One of n(north), s(south), e(east), or w(west), nw(northwest), 96 | // sw(southwest), ne(northeast) or se(southeast) 97 | // 98 | // Returns tip or direction 99 | tip.direction = function(v) { 100 | if (!arguments.length) return direction 101 | direction = v == null ? v : d3.functor(v) 102 | 103 | return tip 104 | } 105 | 106 | // Public: Sets or gets the offset of the tip 107 | // 108 | // v - Array of [x, y] offset 109 | // 110 | // Returns offset or 111 | tip.offset = function(v) { 112 | if (!arguments.length) return offset 113 | offset = v == null ? v : d3.functor(v) 114 | 115 | return tip 116 | } 117 | 118 | // Public: sets or gets the html value of the tooltip 119 | // 120 | // v - String value of the tip 121 | // 122 | // Returns html value or tip 123 | tip.html = function(v) { 124 | if (!arguments.length) return html 125 | html = v == null ? v : d3.functor(v) 126 | 127 | return tip 128 | } 129 | 130 | function d3_tip_direction() { return 'n' } 131 | function d3_tip_offset() { return [0, 0] } 132 | function d3_tip_html() { return ' ' } 133 | 134 | var direction_callbacks = d3.map({ 135 | n: direction_n, 136 | s: direction_s, 137 | e: direction_e, 138 | w: direction_w, 139 | nw: direction_nw, 140 | ne: direction_ne, 141 | sw: direction_sw, 142 | se: direction_se 143 | }), 144 | 145 | directions = direction_callbacks.keys() 146 | 147 | function direction_n() { 148 | var bbox = getScreenBBox() 149 | return { 150 | top: bbox.n.y - node.offsetHeight, 151 | left: bbox.n.x - node.offsetWidth / 2 152 | } 153 | } 154 | 155 | function direction_s() { 156 | var bbox = getScreenBBox() 157 | return { 158 | top: bbox.s.y, 159 | left: bbox.s.x - node.offsetWidth / 2 160 | } 161 | } 162 | 163 | function direction_e() { 164 | var bbox = getScreenBBox() 165 | return { 166 | top: bbox.e.y - node.offsetHeight / 2, 167 | left: bbox.e.x 168 | } 169 | } 170 | 171 | function direction_w() { 172 | var bbox = getScreenBBox() 173 | return { 174 | top: bbox.w.y - node.offsetHeight / 2, 175 | left: bbox.w.x - node.offsetWidth 176 | } 177 | } 178 | 179 | function direction_nw() { 180 | var bbox = getScreenBBox() 181 | return { 182 | top: bbox.nw.y - node.offsetHeight, 183 | left: bbox.nw.x - node.offsetWidth 184 | } 185 | } 186 | 187 | function direction_ne() { 188 | var bbox = getScreenBBox() 189 | return { 190 | top: bbox.ne.y - node.offsetHeight, 191 | left: bbox.ne.x 192 | } 193 | } 194 | 195 | function direction_sw() { 196 | var bbox = getScreenBBox() 197 | return { 198 | top: bbox.sw.y, 199 | left: bbox.sw.x - node.offsetWidth 200 | } 201 | } 202 | 203 | function direction_se() { 204 | var bbox = getScreenBBox() 205 | return { 206 | top: bbox.se.y, 207 | left: bbox.e.x 208 | } 209 | } 210 | 211 | function initNode() { 212 | var node = d3.select(document.createElement('div')) 213 | node.style({ 214 | position: 'absolute', 215 | opacity: 0, 216 | pointerEvents: 'none', 217 | boxSizing: 'border-box' 218 | }) 219 | 220 | return node.node() 221 | } 222 | 223 | function getSVGNode(el) { 224 | el = el.node() 225 | if(el.tagName.toLowerCase() == 'svg') 226 | return el 227 | 228 | return el.ownerSVGElement 229 | } 230 | 231 | // Private - gets the screen coordinates of a shape 232 | // 233 | // Given a shape on the screen, will return an SVGPoint for the directions 234 | // n(north), s(south), e(east), w(west), ne(northeast), se(southeast), nw(northwest), 235 | // sw(southwest). 236 | // 237 | // +-+-+ 238 | // | | 239 | // + + 240 | // | | 241 | // +-+-+ 242 | // 243 | // Returns an Object {n, s, e, w, nw, sw, ne, se} 244 | function getScreenBBox() { 245 | var targetel = target || d3.event.target, 246 | bbox = {}, 247 | matrix = targetel.getScreenCTM(), 248 | tbbox = targetel.getBBox(), 249 | width = tbbox.width, 250 | height = tbbox.height, 251 | x = tbbox.x, 252 | y = tbbox.y, 253 | scrollTop = document.documentElement.scrollTop || document.body.scrollTop, 254 | scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft 255 | 256 | 257 | point.x = x + scrollLeft 258 | point.y = y + scrollTop 259 | bbox.nw = point.matrixTransform(matrix) 260 | point.x += width 261 | bbox.ne = point.matrixTransform(matrix) 262 | point.y += height 263 | bbox.se = point.matrixTransform(matrix) 264 | point.x -= width 265 | bbox.sw = point.matrixTransform(matrix) 266 | point.y -= height / 2 267 | bbox.w = point.matrixTransform(matrix) 268 | point.x += width 269 | bbox.e = point.matrixTransform(matrix) 270 | point.x -= width / 2 271 | point.y -= height / 2 272 | bbox.n = point.matrixTransform(matrix) 273 | point.y += height 274 | bbox.s = point.matrixTransform(matrix) 275 | 276 | return bbox 277 | } 278 | 279 | return tip 280 | }; 281 | -------------------------------------------------------------------------------- /glasseye/js/glasseyePictograms.js: -------------------------------------------------------------------------------- 1 | //Class amd methods 2 | 3 | var Bugs = function(processed_data, div, size, scales) { 4 | 5 | 6 | GlasseyeChart.call(this, div, size, undefined, 1000); 7 | 8 | this.processed_data = processed_data; 9 | 10 | //Make this less repetitive 11 | this.body_scale = scales[0].scale_func.range([5, 20]); 12 | this.ant_l_scale = scales[1].scale_func.range([0, 20]); 13 | this.ant_r_scale = scales[2].scale_func.range([0, 20]); 14 | 15 | this.leg_ml_scale = scales[3].scale_func.range([0, 20]); 16 | this.leg_mr_scale = scales[4].scale_func.range([0, 20]); 17 | 18 | this.leg_bl_scale = scales[5].scale_func.range([0, 20]); 19 | this.leg_br_scale = scales[6].scale_func.range([0, 20]); 20 | 21 | 22 | 23 | } 24 | 25 | Bugs.prototype = Object.create(GlasseyeChart.prototype); 26 | 27 | Bugs.prototype.add_bugs = function() { 28 | 29 | var body_scale = this.body_scale; 30 | var ant_r_scale = this.ant_r_scale; 31 | var ant_l_scale = this.ant_l_scale; 32 | var leg_ml_scale = this.leg_ml_scale; 33 | var leg_mr_scale = this.leg_mr_scale; 34 | var leg_bl_scale = this.leg_bl_scale; 35 | var leg_br_scale = this.leg_br_scale; 36 | 37 | var bug_glyphs = this.chart_area.selectAll("bug_glyph") 38 | .data(this.processed_data) 39 | .enter() 40 | .append("g") 41 | .attr("class", "bug_glyph") 42 | .attr("transform", function(d, i) { 43 | return ("translate(" + (10 + (i % 5) * 100) + "," + (10 + ((i - (i % 5))/5) * 100) + ")") 44 | }); 45 | 46 | 47 | 48 | 49 | //Right ant 50 | bug_glyphs.append("line") 51 | .attr("class", "bugleg") 52 | .attr("x1", 50) 53 | .attr("y1", 50) 54 | .attr("x2", function(d) { 55 | return(50 + Math.cos(Math.PI / 3)*(body_scale(d.body)+ant_r_scale(d.ant_r))); 56 | }) 57 | .attr("y2", function(d) { 58 | return(50 - Math.sin(Math.PI / 3)*(body_scale(d.body)+ant_r_scale(d.ant_r))); 59 | }); 60 | 61 | //Left ant 62 | bug_glyphs.append("line") 63 | .attr("class", "bugleg") 64 | .attr("x1", 50) 65 | .attr("y1", 50) 66 | .attr("x2", function(d) { 67 | return(50 - Math.cos(Math.PI / 3)* (body_scale(d.body)+ant_l_scale(d.ant_l))); 68 | }) 69 | .attr("y2", function(d) { 70 | return(50 - Math.sin(Math.PI / 3)*(body_scale(d.body)+ant_l_scale(d.ant_l))); 71 | }); 72 | 73 | //Right bottom 74 | bug_glyphs.append("line") 75 | .attr("class", "bugleg") 76 | .attr("x1", 50) 77 | .attr("y1", 50) 78 | .attr("x2", function(d) { 79 | return(50 + Math.cos(Math.PI / 3)*(body_scale(d.body)+leg_br_scale(d.leg_br))); 80 | }) 81 | .attr("y2", function(d) { 82 | return(50 + Math.sin(Math.PI / 3)*(body_scale(d.body)+leg_br_scale(d.leg_br))); 83 | }); 84 | 85 | //Left bottom 86 | bug_glyphs.append("line") 87 | .attr("class", "bugleg") 88 | .attr("x1", 50) 89 | .attr("y1", 50) 90 | .attr("x2", function(d) { 91 | return(50 - Math.cos(Math.PI / 3)*(body_scale(d.body)+leg_bl_scale(d.leg_bl))); 92 | }) 93 | .attr("y2", function(d) { 94 | return(50 + Math.sin(Math.PI / 3)*(body_scale(d.body)+leg_bl_scale(d.leg_bl))); 95 | }); 96 | 97 | //Right middle 98 | bug_glyphs.append("line") 99 | .attr("class", "bugleg") 100 | .attr("x1", 50) 101 | .attr("y1", 50) 102 | .attr("x2", function(d) { 103 | return (50+ (body_scale(d.body)+leg_mr_scale(d.leg_mr))); 104 | }) 105 | .attr("y2", 50); 106 | 107 | //Left middle 108 | bug_glyphs.append("line") 109 | .attr("class", "bugleg") 110 | .attr("x1", 50) 111 | .attr("y1", 50) 112 | .attr("x2", function(d) { 113 | return (50 - (body_scale(d.body)+leg_ml_scale(d.leg_ml))); 114 | }) 115 | .attr("y2", 50); 116 | 117 | bug_glyphs.append("circle") 118 | .attr("class", "bugbody") 119 | .attr("cx", 50) 120 | .attr("cy", 50) 121 | .attr("r", function(d) { 122 | return body_scale(d.body)}) 123 | //.attr("r", 10) 124 | .attr("fill", d3.hsl("hsl(204, 70%, 23%)")); 125 | 126 | bug_glyphs.append("text") 127 | .attr("x", 50) 128 | .attr("y", 95) 129 | .attr("text-anchor", "middle") 130 | .text(function(d){return (d.label)}); 131 | 132 | 133 | } 134 | 135 | 136 | //Builder function 137 | 138 | function bugs(data, div, size){ 139 | 140 | var inline_parser = function(data) { 141 | return data; 142 | } 143 | 144 | var csv_parser = function(data) { 145 | 146 | var processed_data = data.map(function(d) { 147 | return { 148 | label: d.label, 149 | body: -d.body, 150 | ant_l: +d.ant_l, 151 | ant_r: +d.ant_r, 152 | leg_ml: +d.leg_ml, 153 | leg_mr: +d.leg_mr, 154 | leg_bl: +d.leg_ml, 155 | leg_br: +d.leg_mr, 156 | } 157 | }); 158 | 159 | return processed_data; 160 | } 161 | 162 | var draw = function(processed_data, div, size) { 163 | 164 | console.log(processed_data); 165 | 166 | //Make this nicer 167 | 168 | var body_values = processed_data.map(function(d) { 169 | return d.body; 170 | }); 171 | 172 | var ant_l_values = processed_data.map(function(d) { 173 | return d.ant_l; 174 | }); 175 | 176 | var ant_r_values = processed_data.map(function(d) { 177 | return d.ant_r; 178 | }); 179 | 180 | var leg_ml_values = processed_data.map(function(d) { 181 | return d.leg_ml; 182 | }); 183 | 184 | var leg_mr_values = processed_data.map(function(d) { 185 | return d.leg_mr; 186 | }); 187 | 188 | var leg_bl_values = processed_data.map(function(d) { 189 | return d.leg_bl; 190 | }); 191 | 192 | var leg_br_values = processed_data.map(function(d) { 193 | return d.leg_br; 194 | }); 195 | 196 | 197 | var scales = [create_scale(body_values, d3.scale.linear()), create_scale(ant_l_values, d3.scale.linear()), create_scale(ant_r_values, 198 | d3.scale.linear()), create_scale(leg_ml_values, d3.scale.linear()), create_scale(leg_mr_values, d3.scale.linear()), create_scale(leg_bl_values, d3.scale.linear()) 199 | , create_scale(leg_br_values, d3.scale.linear())]; 200 | 201 | 202 | var glasseye_chart = new Bugs(processed_data, div, size, scales); 203 | 204 | glasseye_chart.add_svg().add_bugs(); 205 | 206 | } 207 | 208 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 209 | 210 | } 211 | 212 | -------------------------------------------------------------------------------- /glasseye/js/modular/AnimatedBarChart.js: -------------------------------------------------------------------------------- 1 | var AnimatedBarChart = function (processed_data, div, size, labels, scales) { 2 | 3 | var self = this; 4 | 5 | var margin = { 6 | top: 50, 7 | bottom: 80, 8 | right: 50, 9 | left: 60 10 | }; 11 | 12 | BarChart.call(self, processed_data, div, size, labels, scales, margin); 13 | self.bar_width = self.width / self.processed_data.length; 14 | self.y_axis.tickFormat(d3.format(",%")).ticks(6); 15 | this.x_axis.tickSize(0); 16 | 17 | self.current_variable = ""; 18 | 19 | self.tooltip_text = function (d) { 20 | return d3.format(".1%")(d.value) + " of " + d.category + " have access to a " + self.current_variable; 21 | } 22 | 23 | self.tip = d3.tip() 24 | .attr('class', 'd3-tip') 25 | .offset([-10, 0]) 26 | .html(self.tooltip_text); 27 | 28 | }; 29 | 30 | AnimatedBarChart.prototype = Object.create(BarChart.prototype); 31 | 32 | AnimatedBarChart.prototype.add_bars = function () { 33 | 34 | var self = this; 35 | 36 | self.chart_area.call(self.tip); 37 | 38 | //Customisations 39 | self.svg.attr("class", "glasseye_chart animated_barchart"); 40 | 41 | //Get first Date 42 | var start_date = d3.min(self.processed_data[0].values.map(function(d) { 43 | return d.time; 44 | })); 45 | 46 | //Get first variable 47 | var start_variable = d3.min(self.processed_data[0].values.map(function(d) { 48 | return d.variable; 49 | })); 50 | 51 | var bars = self.chart_area.selectAll(".bar") 52 | .data(self.processed_data) 53 | .enter() 54 | .append("g") 55 | .attr("transform", function (d) { 56 | return "translate(" + (self.x(d.category) - self.bar_width * 0.4) + ", " + 0 + ")"; 57 | }); 58 | 59 | bars.append("rect") 60 | .attr("class", "bar") 61 | .attr("x", 0) 62 | .attr("y", function (d) { 63 | return self.y(d.values[0].value); 64 | }) 65 | .attr("width", self.bar_width * 0.8) 66 | .attr("height", function (d) { 67 | return self.height - self.y(d.values[0].value); 68 | }) 69 | .on('mouseover', self.tip.show) 70 | .on('mouseout', self.tip.hide); 71 | ; 72 | 73 | self.svg.append("text").attr("class", "context") 74 | .attr("y", self.height + self.margin.top + 60) 75 | .attr("x", self.margin.left + self.width / 2) 76 | .style("text-anchor", "middle") 77 | .text("At " + quarter_year(self.processed_data[0].values[0].time) + " for " + self.processed_data[0].values[0].variable); 78 | 79 | var max_string = d3.max(self.x.domain().map(function (d) { 80 | return d.length; 81 | })); 82 | var num_points = self.x.domain().length; 83 | 84 | if ((max_string * 5) > (1.5 * self.bar_width)) { 85 | self.chart_area.selectAll(".x_axis").selectAll("text") 86 | .style("text-anchor", "end") 87 | .attr("dx", "-1em") 88 | .attr("dy", "-0.8em") 89 | .attr("transform", "rotate(-90)"); 90 | } 91 | 92 | 93 | self.update_bars(start_date, start_variable); 94 | 95 | return this; 96 | 97 | }; 98 | 99 | 100 | AnimatedBarChart.prototype.update_bars = function (time, variable) { 101 | 102 | var self = this; 103 | 104 | 105 | //Set variable so that it can be accessed by the tooltip 106 | self.current_variable = variable.toLowerCase(); 107 | 108 | var filtered_bars = self.processed_data.map(function(d) { 109 | return { 110 | 111 | category: d.category, 112 | value: d.values.filter(function(e) { 113 | return (e.time.getTime() === time.getTime() & e.variable === variable); 114 | })[0].value 115 | }; 116 | }); 117 | 118 | self.chart_area.selectAll(".bar").data(filtered_bars) 119 | .transition() 120 | .duration(500) 121 | .attr("y", function (d){return self.y(d.value);}) 122 | .attr("height", function (d){return self.height - self.y(d.value);}); 123 | 124 | 125 | self.svg.selectAll(".context").text("In " + quarter_year(time) + " for " + variable); 126 | 127 | 128 | }; 129 | 130 | AnimatedBarChart.prototype.add_title = function (title, subtitle) { 131 | 132 | var self = this; 133 | self.title = title; 134 | self.svg.append('text').attr("class", "title") 135 | .text(title) 136 | .attr("transform", "translate(" + (self.margin.left - 10) + ",20)"); 137 | 138 | if (subtitle != undefined) { 139 | 140 | self.subtitle = subtitle; 141 | self.svg.append('text').attr("class", "subtitle") 142 | .text(subtitle) 143 | .attr("transform", "translate(" + (self.margin.left - 10) + ",35)"); 144 | 145 | } else { 146 | self.subtitle = ""; 147 | } 148 | 149 | return this; 150 | 151 | }; 152 | 153 | AnimatedBarChart.prototype.redraw_barchart = function (title) { 154 | 155 | var self = this; 156 | 157 | //Delete the existing svg and commentary 158 | d3.select(self.div).selectAll("svg").remove(); 159 | 160 | 161 | //Reset the size 162 | self.set_size(); 163 | self.bar_width = self.width / self.processed_data.length; 164 | self.x = self.scales[0].scale_func.rangePoints([0, self.width], 1); 165 | self.y_axis = d3.svg.axis() 166 | .scale(self.y) 167 | .orient("left") 168 | .tickSize(-self.width, 0, 0) 169 | .tickFormat(d3.format("%")).ticks(6); 170 | 171 | 172 | //Redraw the chart 173 | self.add_svg().add_grid().add_bars().add_title(self.title, self.subtitle); 174 | 175 | }; 176 | 177 | 178 | -------------------------------------------------------------------------------- /glasseye/js/modular/AnimatedDensity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds a AnimatedDensity object 3 | * @constructor 4 | * @param {array} processed_data Data that has been given a structure appropriate to the chart 5 | * @param {string} div The div in which the chart will be placed 6 | * @param {string} size The size (one of several preset sizes) 7 | * @param {array} [labels] An array of the axis labels 8 | * @param {array} scales Scales for the x and y axes 9 | * @param {object} [margin] Optional argument in case the default margin settings need to be overridden 10 | */ 11 | 12 | var AnimatedDensity = function (processed_data, div, size, labels, scales, margin) { 13 | 14 | var self = this; 15 | 16 | self.processed_data = processed_data; 17 | 18 | 19 | GridChart.call(self, div, size, labels, scales, margin); 20 | 21 | }; 22 | 23 | AnimatedDensity.prototype = Object.create(GridChart.prototype); 24 | 25 | 26 | /** 27 | * Adds the SVGs corresponding to the AnimatedDensity object 28 | * 29 | * @method 30 | * @returns {object} The modified AnimatedDensity object 31 | */ 32 | 33 | AnimatedDensity.prototype.add_density = function () { 34 | 35 | var self = this; 36 | 37 | /*self.chart_area.selectAll("rect").data(self.processed_data) 38 | .enter() 39 | .append("rect").attr("class", "block").attr("width", self.width / (10 * self.x.domain()[1])).attr("height", self.height / (self.y.domain()[1])).attr("x", function (d) { 40 | return self.x(d.value) 41 | }).attr("y", 0).attr("opacity", 0).transition() 42 | .duration(2500) 43 | .delay(function (d, i) { 44 | return i * 40; 45 | }) 46 | .attr("y", function (d) { 47 | return self.height - d.position * self.height / (self.y.domain()[1]); 48 | }) 49 | .attr("opacity", 1); 50 | */ 51 | 52 | var radius = d3.max([self.width / (10 * self.x.domain()[1]), self.height / (self.y.domain()[1])]) + 2; 53 | 54 | self.chart_area.selectAll("rect").data(self.processed_data) 55 | .enter() 56 | .append("circle").attr("class", "block").attr("r", radius).attr("cx", function (d) { 57 | return self.x(d.value) 58 | }).attr("cy", 0).attr("opacity", 0).transition() 59 | .duration(2500) 60 | .delay(function (d, i) { 61 | return i * 40; 62 | }) 63 | .attr("cy", function (d) { 64 | return self.height - d.position * self.height / (self.y.domain()[1]); 65 | }) 66 | .attr("opacity", 1); 67 | 68 | }; 69 | 70 | 71 | /** 72 | * Creates a animated density chart within a div 73 | * 74 | * @param {array} data Either the path to a csv file or inline data in glasseye 75 | * @param {string} div The div in which the chart will be placed 76 | * @param {string} size The size (one of several preset sizes) 77 | * @param {array} labels An array containing the labels of the x and y axes 78 | */ 79 | 80 | 81 | function animated_density(div, size) { 82 | 83 | var processed_data = [] 84 | var cl = random_gamma(5, 1); 85 | for (i = 0; i < 5000; i++) { 86 | processed_data.push(cl()); 87 | } 88 | 89 | processed_data = processed_data.map(function (d) { 90 | return Math.round(d * 10) / 10; 91 | }) 92 | 93 | 94 | var density_array = Array.apply(null, Array(500)).map(Number.prototype.valueOf, 0); 95 | 96 | processed_data = processed_data.map(function (d) { 97 | density_array[d * 10] = density_array[d * 10] + 1; 98 | return { 99 | value: d, 100 | position: density_array[d * 10] 101 | }; 102 | 103 | }); 104 | 105 | 106 | var draw = function (processed_data, div, size) { 107 | 108 | var x_vals = processed_data.map(function (d) { 109 | return d.value 110 | }); 111 | var y_vals = processed_data.map(function (d) { 112 | return d.position 113 | }); 114 | 115 | var scales = [create_scale(d3.extent(x_vals), d3.scale.linear()), create_scale([0, d3.max(y_vals) + 5], d3.scale.linear())]; 116 | 117 | var glasseye_chart = new AnimatedDensity(processed_data, div, size, ["Random Variable with Gamma Distribution", "Occurrences"], scales); 118 | 119 | glasseye_chart.add_svg().add_grid().add_density(); 120 | 121 | 122 | }; 123 | 124 | draw(processed_data, div, size); 125 | 126 | 127 | } 128 | -------------------------------------------------------------------------------- /glasseye/js/modular/AnimatedDonut.js: -------------------------------------------------------------------------------- 1 | var AnimatedDonut = function(processed_data, div, size) { 2 | 3 | var self = this; 4 | 5 | margin = { 6 | top: 80, 7 | bottom: 130, 8 | left: 30, 9 | right: 30 10 | }; 11 | 12 | GlasseyeChart.call(self, div, size, margin, 400); 13 | 14 | self.processed_data = processed_data; 15 | 16 | var total_value = d3.sum(processed_data.map(function(d) { 17 | return d.values[0].value; 18 | })); 19 | 20 | self.current_total = 1; 21 | 22 | self.tip = d3.tip() 23 | .attr('class', 'd3-tip') 24 | .offset([-10, 0]) 25 | .html(function(d) { 26 | return uni_format(d.data.value) + " households" + "
" + d3.format(".1%")(d.data.value/self.current_total) + " of the total"; 27 | }); 28 | 29 | var radius = self.width / 2; 30 | 31 | self.arc = d3.svg.arc() 32 | .outerRadius(radius - 10) 33 | .innerRadius(radius - 70); 34 | 35 | self.pie = d3.layout.pie() 36 | .sort(null) 37 | .value(function(d) { 38 | return d.value; 39 | }); 40 | 41 | }; 42 | 43 | 44 | AnimatedDonut.prototype = Object.create(GlasseyeChart.prototype); 45 | 46 | 47 | AnimatedDonut.prototype.add_donut = function() { 48 | 49 | var self = this; 50 | 51 | var svg_donut = self.chart_area.append("g") 52 | .attr("transform", "translate(" + self.width / 2 + "," + self.height / 2 + ")"); 53 | 54 | var color = d3.scale.ordinal() 55 | .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); 56 | 57 | //Get first Date 58 | 59 | var start_date = d3.min(self.processed_data[0].values.map(function(d) { 60 | return d.time; 61 | })); 62 | 63 | //Create filtered data Set 64 | 65 | var filtered_donut = self.processed_data.map(function(d) { 66 | return { 67 | category: d.category, 68 | value: d.values[0].value 69 | }; 70 | }); 71 | 72 | svg_donut.call(self.tip); 73 | 74 | self.donut_arc = svg_donut.selectAll(".arc") 75 | .data(self.pie(filtered_donut)) 76 | .enter().append("g") 77 | .attr("class", "arc"); 78 | 79 | var existing_text; 80 | 81 | self.donut_path = self.donut_arc.append("path") 82 | .attr("d", self.arc) 83 | .attr("class", function(d, i) { 84 | return ("d_" + i + " " + create_class_label("d", d.data.category)); 85 | }) 86 | .attr("fill", function(d) { 87 | return color(d.data.category); 88 | }) 89 | .on('mouseover', function(d, i) { 90 | /*existing_text = self.svg.selectAll(".context").text(); 91 | var text_line_1 = existing_text.substring(0, 11) + d3.format("%")(d.data.value/self.current_total) + " of households"; 92 | var text_line_2 = "with " + existing_text.substring(15, existing_text.length); 93 | var text_line_3 = "are at lifestage " + d.data.group; 94 | self.svg.selectAll(".context").text(text_line_1); 95 | self.svg.selectAll(".context_2").text(text_line_2); 96 | self.svg.selectAll(".context_3").text(text_line_3); 97 | */ 98 | self.tip.show(d); 99 | }) 100 | .on('mouseout', function(d, i) { 101 | //self.svg.selectAll(".context").text(existing_text); 102 | self.tip.hide(d); 103 | }); 104 | 105 | 106 | self.donut_text = self.donut_arc.append("text") 107 | .attr("transform", function(d) { 108 | return "translate(" + self.arc.centroid(d) + ")"; 109 | }) 110 | .attr("dy", ".35em") 111 | .style("text-anchor", "middle") 112 | .text(function(d) { 113 | if (d.endAngle - d.startAngle > 0.35) { 114 | return d.data.category; 115 | } else { 116 | return ""; 117 | } 118 | }); 119 | 120 | self.donut_path.transition() 121 | .duration(500) 122 | .attr("fill", function(d, i) { 123 | return color(d.data.category); 124 | }) 125 | .attr("d", self.arc) 126 | .each(function(d) { 127 | this._current = d; 128 | }); 129 | 130 | self.svg.append("text").attr("class", "context") 131 | .attr("y", self.height + self.margin.top + 70) 132 | .attr("x", self.margin.left + self.width / 2) 133 | .style("text-anchor", "middle"); 134 | 135 | /*self.svg.append("text").attr("class", "context_2") 136 | .attr("y", self.height + self.margin.top + 75) 137 | .attr("x", self.margin.left + self.width / 2) 138 | .style("text-anchor", "middle"); 139 | 140 | self.svg.append("text").attr("class", "context_3") 141 | .attr("y", self.height + self.margin.top + 90) 142 | .attr("x", self.margin.left + self.width / 2) 143 | .style("text-anchor", "middle"); 144 | */ 145 | 146 | self.update_donut(start_date, "No TV"); 147 | 148 | 149 | return this; 150 | 151 | }; 152 | 153 | 154 | AnimatedDonut.prototype.update_donut = function(time, variable) { 155 | 156 | var self = this; 157 | 158 | var color = d3.scale.ordinal() 159 | .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); 160 | 161 | 162 | var filtered_donut = self.processed_data.map(function(d) { 163 | 164 | return { 165 | 166 | category: d.category, 167 | value: d.values.filter(function(e) { 168 | return (e.time.getTime() === time.getTime() & e.variable === variable); 169 | })[0].value 170 | }; 171 | }); 172 | 173 | 174 | //Update tooltip 175 | 176 | self.current_total = d3.sum(filtered_donut.map(function(d){return d.value})); 177 | 178 | self.donut_path.data(self.pie(filtered_donut)).transition().duration(200).attrTween("d", arcTween); 179 | 180 | 181 | self.donut_text.data(self.pie(filtered_donut)).transition().duration(200).attr("transform", function(d) { 182 | return "translate(" + self.arc.centroid(d) + ")"; 183 | }) 184 | .attr("dy", ".35em") 185 | .style("text-anchor", "middle") 186 | .text(function(d) { 187 | return d.data.category; 188 | }); 189 | 190 | function arcTween(a) { 191 | var i = d3.interpolate(this._current, a); 192 | this._current = i(0); 193 | return function(t) { 194 | return self.arc(i(t)); 195 | }; 196 | } 197 | 198 | self.svg.selectAll(".context").text("In " + quarter_year(time) + " for " + variable); 199 | 200 | 201 | 202 | 203 | }; 204 | 205 | AnimatedDonut.prototype.add_title = function(title, subtitle) { 206 | 207 | var self = this; 208 | self.title = title; 209 | self.svg.append('text').attr("class", "title") 210 | .text(title) 211 | .attr("y", 20) 212 | .attr("x", self.margin.left + self.width / 2) 213 | .style("text-anchor", "middle"); 214 | 215 | if (subtitle != undefined) { 216 | 217 | self.subtitle = subtitle; 218 | self.svg.append('text').attr("class", "subtitle") 219 | .text(subtitle) 220 | .attr("y", 35) 221 | .attr("x", self.margin.left + self.width / 2) 222 | .style("text-anchor", "middle"); 223 | 224 | } else { 225 | self.subtitle = ""; 226 | } 227 | 228 | return this; 229 | 230 | }; 231 | 232 | AnimatedDonut.prototype.redraw_donut = function(title) { 233 | 234 | var self = this; 235 | 236 | //Delete the existing svg and commentary 237 | d3.select(self.div).selectAll("svg").remove(); 238 | d3.select(self.div).selectAll("#venn_context").remove(); 239 | 240 | //Reset the size 241 | self.set_size(); 242 | 243 | var radius = self.width / 2; 244 | 245 | self.arc = d3.svg.arc() 246 | .outerRadius(radius - 10) 247 | .innerRadius(radius - 70); 248 | 249 | 250 | //Redraw the chart 251 | self.add_svg().add_donut().add_title(self.title, self.subtitle); 252 | 253 | }; 254 | -------------------------------------------------------------------------------- /glasseye/js/modular/Build.sh: -------------------------------------------------------------------------------- 1 | GE_DIR='/Users/simon/Documents/CodeRepos/glasseye/glasseye' 2 | BB_DIR='/Users/simon/Documents/CodeRepos/barbarella/javascript/glasseye/' 3 | GP_DIR='/Users/simon/Documents/CodeRepos/glasseyePages/glasseye' 4 | 5 | # Remove the existing files 6 | rm $GE_DIR/js/glasseyeCharts.js 7 | rm $GE_DIR/js/glasseyeCharts.min.js 8 | #And from barbarella 9 | rm $BB_DIR/js/glasseyeCharts.js 10 | #And from the demo 11 | rm $GE_DIR/demo/js/glasseyeCharts.js 12 | rm -r $GP_DIR/demo 13 | 14 | #Concatenate the modules into one script 15 | cat GlasseyeChart.js \ 16 | GridChart.js \ 17 | BarChart.js \ 18 | Parsers.js \ 19 | GlobalFunctionsAndVariable.js \ 20 | AnimatedBarChart.js \ 21 | AnimatedDonut.js \ 22 | AnimatedVenn.js \ 23 | DrillableVenn.js \ 24 | Donut.js \ 25 | Force.js \ 26 | Gant.js \ 27 | LinePlot.js \ 28 | NonStandard.js \ 29 | ScatterPlot.js \ 30 | Thermometers.js \ 31 | TimeSeries.js \ 32 | Tree.js \ 33 | Venn.js \ 34 | Dial.js \ 35 | LogReg.js \ 36 | RandomNumber.js \ 37 | AnimatedDensity.js \ 38 | PolygonMap.js \ 39 | Heatmap.js \ 40 | > $GE_DIR/js/glasseyeCharts.js 41 | 42 | #And copy it to barbarella 43 | cp $GE_DIR/js/glasseyeCharts.js $BB_DIR/js/glasseyeCharts.js 44 | 45 | #And to the demo area 46 | cp $GE_DIR/js/glasseyeCharts.js $GE_DIR/demo/js/glasseyeCharts.js 47 | 48 | #And to the articles 49 | cp $GE_DIR/js/glasseyeCharts.js /Users/simon/Documents/CodeRepos/glasseye/articles/js/glasseyeCharts.js 50 | 51 | #Also copy across the css 52 | cp $GE_DIR/css/glasseyeCharts.css $BB_DIR/css/glasseyeCharts.css 53 | 54 | #And to the demo area 55 | cp $GE_DIR/css/glasseyeCharts.css $GE_DIR/demo/css/glasseyeCharts.css 56 | 57 | #And now copy the demo over to the pages dir 58 | cp -r $GE_DIR/demo $GP_DIR/demo 59 | 60 | #Minify it 61 | minify $GE_DIR/js/glasseyeCharts.js > $GE_DIR/js/glasseyeCharts.min.js 62 | 63 | #Export to pip 64 | #cd ../../.. 65 | #python setup.py sdist upload 66 | 67 | #Remove current version of glasseyeCharts and reinstall 68 | 69 | #pip uninstall glasseye 70 | #pip install glasseye 71 | -------------------------------------------------------------------------------- /glasseye/js/modular/Concat.sh: -------------------------------------------------------------------------------- 1 | #rm /Users/simon/Documents/CodeRepos/glasseye/glasseye/js/GlasseyeCharts.js 2 | rm /Users/simon/Documents/CodeRepos/barbarella/javascript/glasseye/js/GlasseyeCharts.js 3 | cat GlasseyeChart.js \ 4 | GridChart.js \ 5 | BarChart.js \ 6 | Parsers.js \ 7 | GlobalFunctionsAndVariable.js \ 8 | AnimatedBarChart.js \ 9 | AnimatedDonut.js \ 10 | AnimatedVenn.js \ 11 | Donut.js \ 12 | Force.js \ 13 | Gant.js \ 14 | LinePlot.js \ 15 | NonStandard.js \ 16 | ScatterPlot.js \ 17 | Thermometers.js \ 18 | TimeSeries.js \ 19 | Tree.js \ 20 | Venn.js \ 21 | Dial.js \ 22 | LogReg.js \ 23 | RandomNumber.js \ 24 | > /Users/simon/Documents/CodeRepos/barbarella/javascript/glasseye/js/GlasseyeCharts.js 25 | minify /Users/simon/Documents/CodeRepos/barbarella/javascript/glasseye/js/GlasseyeCharts.js > /Users/simon/Documents/CodeRepos/barbarella/javascript/glasseye/js/GlasseyeCharts.min.js -------------------------------------------------------------------------------- /glasseye/js/modular/Dial.js: -------------------------------------------------------------------------------- 1 | var Dial = function(processed_data, div, size, scales) { 2 | 3 | //Store arguments 4 | this.processed_data = processed_data; 5 | 6 | //Default parameters 7 | var pct_of_width = 0.55; 8 | 9 | //Inherit any attributes or functions of a parent class 10 | GlasseyeChart.call(this, div, size); 11 | 12 | //Any overides of parent attributes 13 | 14 | //Derive further attributes 15 | var dial_domain = scales[0].scale_func.domain(); 16 | this.dial_radius = pct_of_width * this.width / 2; 17 | 18 | //Create functions or closures to be used in methods 19 | this.dial_scale = scales[0].scale_func.range([0, 359]); 20 | 21 | //Temp overide of range 22 | this.dial_scale.domain([-0.3, 0.1]).clamp(true); 23 | 24 | this.current_angle = this.dial_scale(-0.123); 25 | 26 | //Create the dial face data 27 | 28 | var angles = d3.range(0, 360, 30); 29 | 30 | var local_scale = this.dial_scale; 31 | 32 | this.dial_face_data = angles.map(function(d) { 33 | return { 34 | angle: d, 35 | value: local_scale.invert((d+180) % 360) 36 | }; 37 | }); 38 | 39 | }; 40 | 41 | //Methods for the class. This is where svgs are created 42 | 43 | //Inherit methods from parent 44 | Dial.prototype = Object.create(GlasseyeChart.prototype); 45 | 46 | //Method for adding svgs 47 | Dial.prototype.add_dial = function() { 48 | 49 | //Store this locally so that it can reference in further functions 50 | var self = this; 51 | 52 | //Draw the chart 53 | var face = self.chart_area.append("g") 54 | .attr("transform", "translate(" + (self.width / 2) + ", " + (self.height / 2.5) + ")"); 55 | 56 | face.append("svg").attr("width", self.dial_radius).attr("height", self.dial_radius).append("circle").attr("r", self.dial_radius).style("fill", "#990000"); 57 | 58 | face.append("circle") 59 | .attr("class", "dial_face") 60 | .attr("r", self.dial_radius); 61 | 62 | face.append("circle") 63 | .attr("class", "dial_seg") 64 | .attr("r", self.dial_radius * 0.4) 65 | .attr("fill", "black"); 66 | 67 | face.append("circle") 68 | .attr("class", "dial_centre") 69 | .attr("r", self.dial_radius * 0.05); 70 | 71 | function rotate_tween() { 72 | var i = d3.interpolate(0, self.current_angle); 73 | return function(t) { 74 | return "rotate(" + i(t) + ")"; 75 | }; 76 | } 77 | 78 | face.append("line") 79 | .attr("class", "dial_hand") 80 | .attr("x2", self.dial_radius * 0.7) 81 | .transition() 82 | .duration(this.current_angle / 360 * 5000) 83 | .attrTween("transform", rotate_tween); 84 | 85 | //Add ticks 86 | var dial_ticks = face.selectAll(".dial_ticks") 87 | .data(self.dial_face_data) 88 | .enter().append("g") 89 | .attr("transform", function(d) { 90 | return "rotate(" + d.angle + ") translate(" + -self.dial_radius + ", 0) "; 91 | }); 92 | 93 | dial_ticks.append("line") 94 | .attr("x2", 7); 95 | 96 | dial_ticks.append("text") 97 | .style("text-anchor", "middle") 98 | .attr("class", "dial_tick_labels") 99 | .attr("dy", ".35em") 100 | .attr("transform", function(d) { 101 | return d.angle < 270 && d.angle > 90 ? "translate(20,0) rotate(-90) " : "translate(20,0) rotate(-90) "; 102 | }) 103 | .text(function(d) { 104 | return d3.format(",.0f")(d.value*100); 105 | }); 106 | 107 | //Add the counter 108 | 109 | self.chart_area.append("text") 110 | .attr("class", "dial_counter") 111 | .attr("transform", "translate(250," + (self.height / 2.5 +7) + ")") 112 | .text("") 113 | .style("text-anchor", "end") 114 | .transition() 115 | .delay(this.current_angle / 360 * 5000) 116 | .text("0%"); 117 | 118 | 119 | //Return the object so that we can use chaining 120 | return self; 121 | 122 | }; 123 | 124 | 125 | //Method for updating svgs 126 | Dial.prototype.update_dial = function(group, variable) { 127 | 128 | //Store this locally so that it can reference in further functions 129 | var self = this; 130 | 131 | //If necessary filter the data 132 | var filtered_data = self.processed_data.filter(function(d) { 133 | return d.group === group; 134 | })[0].values 135 | .filter(function(e) { 136 | return e.variable === variable; 137 | })[0]; 138 | 139 | //Update the chart 140 | var angle = self.dial_scale(filtered_data.value); 141 | var local_angle = self.current_angle; 142 | 143 | function rotate_tween() { 144 | var i = d3.interpolate(local_angle, angle); 145 | return function(t) { 146 | return "rotate(" + i(t) + ")"; 147 | }; 148 | } 149 | 150 | self.chart_area.selectAll(".dial_hand") 151 | .transition() 152 | .duration(1000) 153 | .attrTween("transform", rotate_tween); 154 | 155 | //Update the counter 156 | 157 | self.chart_area.selectAll(".dial_counter") 158 | .transition() 159 | .delay(1000) 160 | .text(d3.format("%")(filtered_data.value)); 161 | 162 | //Return the object so that we can use chaining 163 | self.current_angle = angle; 164 | return self; 165 | 166 | }; 167 | 168 | 169 | Dial.prototype.add_title = function(title) { 170 | 171 | var self = this; 172 | self.svg.append('text').attr("class", "title") 173 | .text(title) 174 | .style("text-anchor", "middle") 175 | .attr("transform", "translate(" + (self.margin.left + self.width/2) + ",20)"); 176 | 177 | return this; 178 | 179 | }; 180 | 181 | 182 | Dial.prototype.add_subtitle = function(subtitle) { 183 | 184 | var self = this; 185 | self.svg.append('text').attr("class", "subtitle") 186 | .text(subtitle) 187 | .style("text-anchor", "middle") 188 | .attr("transform", "translate(" + (self.margin.left + self.width/2) + ",40)"); 189 | 190 | return this; 191 | 192 | }; 193 | 194 | //wrapper function to process the data and draw the chart 195 | 196 | /* 197 | 198 | function x_chart(data, div, size) { 199 | 200 | //Define data parsers 201 | var inline_parser = function(data) { 202 | 203 | return processed_data; 204 | 205 | }; 206 | 207 | var csv_parser = function(data) { 208 | 209 | return processed_data; 210 | 211 | }; 212 | 213 | //Create draw function 214 | var draw = function(processed_data, div, size) { 215 | 216 | //Calculate values for scales 217 | 218 | //Create scales 219 | 220 | //Create new chart and chain methods 221 | 222 | var glasseye_chart = new XChart(processed_data, div, size); 223 | 224 | glasseye_chart.add_svg(); 225 | 226 | }; 227 | 228 | //Function that builds the chart based on whether the data is inline or from a file 229 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 230 | 231 | } 232 | */ 233 | -------------------------------------------------------------------------------- /glasseye/js/modular/Donut.js: -------------------------------------------------------------------------------- 1 | var Donut = function(processed_data, div, size) { 2 | 3 | margin = { 4 | top: 5, 5 | bottom: 5, 6 | left: 5, 7 | right: 5 8 | }; 9 | 10 | GlasseyeChart.call(this, div, size, margin); 11 | 12 | this.processed_data = processed_data; 13 | 14 | var total_value = d3.sum(processed_data.map(function(d) { 15 | return d.value; 16 | })); 17 | 18 | this.tip = d3.tip() 19 | .attr('class', 'd3-tip') 20 | .offset([-10, 0]) 21 | .html(function(d) { 22 | return d.data.label + "

" + d.data.value + "

" + d3.format("%")(d.data.value / total_value); 23 | }); 24 | 25 | var radius = this.height / 2; 26 | 27 | this.arc = d3.svg.arc() 28 | .outerRadius(radius - 10) 29 | .innerRadius(radius - 70); 30 | 31 | this.pie = d3.layout.pie() 32 | .sort(null) 33 | .value(function(d) { 34 | return d.value; 35 | }); 36 | 37 | }; 38 | 39 | 40 | Donut.prototype = Object.create(GlasseyeChart.prototype); 41 | 42 | Donut.prototype.add_donut = function() { 43 | 44 | var svg_donut = this.chart_area.append("g") 45 | .attr("transform", "translate(" + this.width / 2 + "," + this.height / 2 + ")"); 46 | 47 | var color = d3.scale.ordinal() 48 | .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]); 49 | 50 | svg_donut.call(this.tip); 51 | 52 | var g = svg_donut.selectAll(".arc") 53 | .data(this.pie(this.processed_data)) 54 | .enter().append("g") 55 | .attr("class", "arc"); 56 | 57 | g.append("path") 58 | .attr("d", this.arc) 59 | .style("fill", function(d) { 60 | return color(d.data.label); 61 | }) 62 | .on('mouseover', this.tip.show) 63 | .on('mouseout', this.tip.hide); 64 | 65 | var arc = this.arc; 66 | 67 | g.append("text") 68 | .attr("transform", function(d) { 69 | return "translate(" + arc.centroid(d) + ")"; 70 | }) 71 | .attr("dy", ".35em") 72 | .style("text-anchor", "middle") 73 | .text(function(d) { 74 | if (d.endAngle - d.startAngle > 0.35) { 75 | return d.data.label; 76 | } else { 77 | return ""; 78 | } 79 | }); 80 | 81 | }; 82 | 83 | function donut(data, div, size) { 84 | 85 | var inline_parser = function(data) { 86 | 87 | processed_data = []; 88 | 89 | for (i = 0; i < data.values.length; i++) { 90 | data_item = { 91 | "label": data.labels[i], 92 | "value": +data.values[i] 93 | }; 94 | processed_data.push(data_item); 95 | 96 | } 97 | 98 | return processed_data; 99 | 100 | }; 101 | 102 | var csv_parser = function(data) { 103 | return data; 104 | }; 105 | 106 | var draw = function(processed_data, div, size) { 107 | 108 | var glasseye_chart = new Donut(processed_data, div, size); 109 | 110 | glasseye_chart.add_svg().add_donut(); 111 | 112 | }; 113 | 114 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 115 | 116 | } 117 | -------------------------------------------------------------------------------- /glasseye/js/modular/DrillableVenn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds an DrillableVenn object 3 | * @constructor 4 | * @param {array} processed_data Data that has been given a structure appropriate to the chart 5 | * @param {string} div The div in which the chart will be placed 6 | * @param {string} size The size (one of several preset sizes) 7 | */ 8 | 9 | var DrillableVenn = function (processed_data, div, size) { 10 | 11 | var self = this; 12 | 13 | margin = { 14 | top: 40, 15 | bottom: 0, 16 | left: 5, 17 | right: 5 18 | }; 19 | 20 | GlasseyeChart.call(self, div, size, margin, 350); 21 | 22 | self.processed_data = processed_data; 23 | 24 | self.venn_chart = venn.VennDiagram() 25 | .width(self.width) 26 | .height(self.height); 27 | 28 | self.tip = d3.tip() 29 | .attr('class', 'd3-tip') 30 | .offset([-10, 0]) 31 | .html(function (d) { 32 | return d3.format(".3n")(d.size); 33 | }); 34 | 35 | self.current_level = "none"; 36 | 37 | self.interactive_text = function (d, existing_text) { 38 | 39 | var text, set_name = d.sets[0], 40 | set_size = d.size; 41 | 42 | var qualifier = +(self.current_level === "none") ? "" : self.current_level; 43 | 44 | //Get total number 45 | var all_sets = self.chart_area.selectAll("g")[0].filter(function(d){return d.__data__.sets[0] != "500k"}); 46 | console.log(all_sets); 47 | var signed = all_sets.map(function (e) { 48 | if (e.__data__.sets.length == 2) { 49 | return -e.__data__.size; 50 | } else { 51 | return e.__data__.size; 52 | } 53 | }); 54 | 55 | var total = signed.reduce(add, 0); 56 | 57 | if (set_name === "500k") { 58 | 59 | text = "This circle represents 500k households and can be used as reference point when the Venn diagrams change in scale."; 60 | 61 | } 62 | 63 | else if (d.sets.length == 1) { 64 | 65 | text = set_name + " consoles are in " + uni_format(set_size) + " households, making up " + d3.format(",.1%")(set_size / total) + " of all households with " + qualifier + " games consoles."; 66 | 67 | } else if (d.sets.length == 2) { 68 | 69 | var sub_1 = d.sets[0]; 70 | var sub_2 = d.sets[1]; 71 | 72 | //Work out set sizes 73 | var set_1_size = all_sets.filter(function (d) { 74 | return d.__data__.sets.length === 1 & d.__data__.sets[0] === sub_1; 75 | })[0].__data__.size; 76 | var set_2_size = all_sets.filter(function (d) { 77 | return d.__data__.sets.length === 1 & d.__data__.sets[0] === sub_2; 78 | })[0].__data__.size; 79 | 80 | text = "There are " + uni_format(set_size) + " households that have both " + sub_1 + " and " + sub_2 + " consoles (" + d3.format(",.1%")(set_size / total) + " of all households with " + qualifier + " games consoles.)"; 81 | } else { 82 | 83 | text = "There are " + uni_format(set_size) + " households that own all three types of consoles. That's " + d3.format(",.1%")(set_size / total) + " of all households with " + qualifier + " games consoles."; 84 | 85 | } 86 | 87 | function add(a, b) { 88 | return a + b; 89 | } 90 | 91 | return text; 92 | 93 | }; 94 | 95 | }; 96 | 97 | DrillableVenn.prototype = Object.create(GlasseyeChart.prototype); 98 | 99 | /** 100 | * Adds the SVGs corresponding to the DrillableVenn object 101 | * 102 | * @method 103 | * @returns {object} The modified DrillableVenn object 104 | */ 105 | 106 | DrillableVenn.prototype.add_venn = function (parent) { 107 | 108 | var self = this; 109 | 110 | self.current_level = parent; 111 | 112 | var filtered_data = self.processed_data.filter(function (d) { 113 | return d.parent === parent; 114 | })[0].venns; 115 | 116 | 117 | self.chart_area.datum(filtered_data).call(self.venn_chart); 118 | 119 | d3.selectAll(".venn-area text").style("fill", "white"); 120 | 121 | //Add the div for the commentary 122 | var parent_div = d3.selectAll("#chart_container"); 123 | parent_div.selectAll("#venn_context_side").remove(); 124 | 125 | var div = parent_div.append("div").attr("id", "venn_context_side"); 126 | div.append("div").attr("id", "venn_instructions").html("

Instructions

  • Click on each circle in the Venn diagram to drill a level further into the data.
  • Click again on a circle to return to the top level.
  • The scale of the diagram is adjusted as you drill into the data however there is always a circle showing 500k households as a point of reference.

Commentary

"); 127 | div.append("div").attr("id", "commentary").html("Hover over a circle and commentary will appear here."); 128 | 129 | //Add interactivity 130 | self.chart_area.selectAll("g") 131 | .on("mouseover", function (d, i) { 132 | 133 | //Set all charts back to no border-box 134 | self.chart_area.selectAll(".venn-area") 135 | .selectAll("path") 136 | .style("stroke-opacity", 0); 137 | 138 | // sort all the areas relative to the current item 139 | venn.sortAreas(self.chart_area, d); 140 | var selection = d3.select(this); 141 | selection.select("path").transition().duration(500) 142 | .style("stroke-opacity", 1); 143 | 144 | //update the text 145 | var existing_text = d3.selectAll("#commentary").html(); 146 | d3.selectAll("#commentary").html(self.interactive_text(d, existing_text)); 147 | 148 | }) 149 | .on("mouseout", function (d, i) { 150 | var selection = d3.select(this); 151 | selection.select("path").transition().duration(500) 152 | // .style("fill-opacity", d.sets.length == 1 ? 0.5 : 0) 153 | .style("stroke-opacity", 0); 154 | 155 | }) 156 | .on("click", function (d, i) { 157 | var selection = d3.select(this); 158 | 159 | if (parent == "none") { 160 | 161 | if (d.sets.length > 1) { 162 | console.log("Cannot click on intersections"); 163 | } 164 | else { 165 | self.add_venn(d.sets[0]); 166 | } 167 | } 168 | else { 169 | self.add_venn("none"); 170 | } 171 | }); 172 | 173 | 174 | //} 175 | 176 | return self; 177 | 178 | }; 179 | 180 | 181 | /** 182 | * Adds a title to the Venn 183 | * @method 184 | * @param {string} title The title to be placed at the top of the Venn 185 | * @returns {object} The modified AnimatedVenn object 186 | */ 187 | 188 | DrillableVenn.prototype.add_title = function (title) { 189 | 190 | var self = this; 191 | self.title = title; 192 | self.svg.append('text').attr("class", "title") 193 | .text(title) 194 | .attr("y", 20) 195 | .attr("x", self.margin.left + self.width / 2) 196 | .style("text-anchor", "middle"); 197 | 198 | return this; 199 | 200 | if (subtitle != undefined) { 201 | 202 | self.subtitle = subtitle; 203 | self.svg.append('text').attr("class", "subtitle") 204 | .text(subtitle) 205 | .attr("y", 35) 206 | .attr("x", self.margin.left + self.width / 2) 207 | .style("text-anchor", "middle"); 208 | 209 | } else { 210 | self.subtitle = ""; 211 | } 212 | 213 | }; 214 | 215 | /** 216 | * Redraws the Venn (for example after a resize of the div) 217 | * @method 218 | * @returns {object} The modified AnimatedVenn object 219 | */ 220 | 221 | DrillableVenn.prototype.redraw_venn = function (title) { 222 | 223 | var self = this; 224 | 225 | //Delete the existing svg and commentary 226 | d3.select(self.div).selectAll("svg").remove(); 227 | d3.select(self.div).selectAll("#venn_context_side").remove(); 228 | 229 | //Reset the size 230 | self.set_size(); 231 | 232 | 233 | self.venn_chart = venn.VennDiagram() 234 | .width(self.width) 235 | .height(self.height); 236 | 237 | //Redraw the chart 238 | self = self.add_svg().add_venn(self.current_level).add_title(self.title, self.subtitle); 239 | 240 | }; 241 | -------------------------------------------------------------------------------- /glasseye/js/modular/Force.js: -------------------------------------------------------------------------------- 1 | var Force = function(processed_data, div, size) { 2 | 3 | var margin = (size === "full_page") ? { 4 | top: 5, 5 | bottom: 5, 6 | left: 100, 7 | right: 100 8 | } : { 9 | top: 5, 10 | bottom: 5, 11 | left: 50, 12 | right: 50 13 | }; 14 | 15 | GlasseyeChart.call(this, div, size, margin, 300); 16 | 17 | this.processed_data = processed_data; 18 | 19 | //Set up the force layout 20 | this.force = d3.layout.force() 21 | .charge(-120) 22 | .linkDistance(30) 23 | .size([this.width, this.height]); 24 | 25 | this.tip = d3.tip() 26 | .attr('class', 'd3-tip') 27 | .offset([-10, 0]) 28 | .html(function(d) { 29 | return d.name; 30 | }); 31 | 32 | }; 33 | 34 | 35 | Force.prototype = Object.create(GlasseyeChart.prototype); 36 | 37 | Force.prototype.add_force = function() { 38 | 39 | var color = d3.scale.category20(); 40 | 41 | this.chart_area.call(this.tip); 42 | 43 | //Creates the graph data structure out of the json data 44 | this.force.nodes(this.processed_data.nodes) 45 | .links(this.processed_data.links) 46 | .start(); 47 | 48 | //Create all the line svgs but without locations yet 49 | var link = this.chart_area.selectAll(".forcelink") 50 | .data(this.processed_data.links) 51 | .enter().append("line") 52 | .attr("class", "forcelink") 53 | .style("stroke-width", function(d) { 54 | return Math.sqrt(d.value); 55 | }); 56 | 57 | //Do the same with the circles for the nodes - no 58 | var node = this.chart_area.selectAll(".forcenode") 59 | .data(this.processed_data.nodes) 60 | .enter().append("circle") 61 | .attr("class", "forcenode") 62 | .attr("r", 8) 63 | .style("fill", function(d) { 64 | return color(d.group); 65 | }) 66 | .call(this.force.drag) 67 | .on('mouseover', this.tip.show) 68 | .on('mouseout', this.tip.hide); 69 | 70 | //Now we are giving the SVGs co-ordinates - the force layout is generating the co-ordinates which this code is using to update the attributes of the SVG elements 71 | this.force.on("tick", function() { 72 | link.attr("x1", function(d) { 73 | return d.source.x; 74 | }) 75 | .attr("y1", function(d) { 76 | return d.source.y; 77 | }) 78 | .attr("x2", function(d) { 79 | return d.target.x; 80 | }) 81 | .attr("y2", function(d) { 82 | return d.target.y; 83 | }); 84 | 85 | node.attr("cx", function(d) { 86 | return d.x; 87 | }) 88 | .attr("cy", function(d) { 89 | return d.y; 90 | }); 91 | }); 92 | 93 | }; 94 | 95 | function force(data, div, size) { 96 | 97 | var inline_parser = function(data) { 98 | return data; 99 | }; 100 | 101 | var csv_parser = function(data) { 102 | return data; 103 | }; 104 | 105 | var draw = function(processed_data, div, size) { 106 | 107 | var glasseye_chart = new Force(processed_data, div, size); 108 | 109 | glasseye_chart.add_svg().add_force(); 110 | 111 | }; 112 | 113 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 114 | 115 | 116 | } 117 | -------------------------------------------------------------------------------- /glasseye/js/modular/Gant.js: -------------------------------------------------------------------------------- 1 | var Gantt = function(processed_data, div, size, scales) { 2 | 3 | this.div = div; 4 | this.processed_data = processed_data; 5 | 6 | GridChart.call(this, div, size, ["Time", "Tasks"], scales, { 7 | top: 20, 8 | bottom: 80, 9 | left: 80, 10 | right: 20 11 | }); 12 | 13 | this.tip = d3.tip() 14 | .attr('class', 'd3-tip') 15 | .offset([-10, 0]) 16 | .html(function(d) { 17 | return Math.floor((d.end - d.start) / (1000 * 60 * 60 * 24)) + " days"; 18 | }); 19 | 20 | this.bar_width = this.width / processed_data.length; 21 | 22 | }; 23 | 24 | Gantt.prototype = Object.create(GridChart.prototype); 25 | 26 | Gantt.prototype.add_tasks = function() { 27 | 28 | var x_scale = this.x, 29 | y_scale = this.y, 30 | bar_width = this.bar_width; 31 | 32 | this.chart_area.call(this.tip); 33 | this.chart_area.selectAll(".task") 34 | .data(this.processed_data) 35 | .enter() 36 | .append("rect") 37 | .attr("class", "task") 38 | .attr("y", function(d) { 39 | return y_scale(d.task) - bar_width / 6; 40 | }) 41 | .attr("x", function(d) { 42 | return x_scale(d.start); 43 | }) 44 | .attr("height", this.bar_width / 3) 45 | .attr("width", function(d) { 46 | return x_scale(d.end) - x_scale(d.start); 47 | }) 48 | .on('mouseover', this.tip.show) 49 | .on('mouseout', this.tip.hide); 50 | 51 | return this; 52 | 53 | }; 54 | 55 | function gantt(data, div, size) { 56 | 57 | var inline_parser = function(data) { 58 | 59 | var parse_date = d3.time.format("%d/%m/%Y").parse; 60 | 61 | //Parse the dates 62 | data.forEach(function(d) { 63 | d.start = parse_date(d.start); 64 | d.end = parse_date(d.end); 65 | }); 66 | 67 | 68 | data.sort(function(a, b) { 69 | return b.start - a.start; 70 | }); 71 | 72 | return data; 73 | 74 | }; 75 | 76 | 77 | var csv_parser = function(data) { 78 | 79 | //To be written 80 | 81 | }; 82 | 83 | var draw = function(processed_data, div, size) { 84 | 85 | var starts = processed_data.map(function(d) { 86 | return d.start; 87 | }); 88 | 89 | var ends = processed_data.map(function(d) { 90 | return d.end; 91 | }); 92 | 93 | var x_values = starts.concat(ends); 94 | 95 | var y_values = processed_data.map(function(d) { 96 | return d.task; 97 | }); 98 | 99 | var scales = [create_scale(x_values, d3.time.scale()), create_scale(y_values, d3.scale.ordinal())]; 100 | 101 | 102 | var glasseye_chart = new Gantt(processed_data, div, size, scales); 103 | glasseye_chart.add_svg().add_grid().add_tasks(); 104 | 105 | }; 106 | 107 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 108 | 109 | } 110 | -------------------------------------------------------------------------------- /glasseye/js/modular/GlasseyeChart.js: -------------------------------------------------------------------------------- 1 | //Glasseye chart super class: sets up the svg and the chart area 2 | var GlasseyeChart = function(div, size, margin, custom_height) { 3 | 4 | var self = this; 5 | 6 | self.div = div; 7 | self.size = size; 8 | self.margin = margin; 9 | self.custom_height = custom_height; 10 | 11 | //Set the size of the chart 12 | self.set_size(); 13 | 14 | }; 15 | 16 | GlasseyeChart.prototype.set_size = function() { 17 | 18 | var self = this; 19 | 20 | //Get dimension of the div 21 | var rect = d3.select(self.div).node().getBoundingClientRect(); 22 | 23 | //Set chart dimensions according to whether the chart is placed in the margin or the main page 24 | 25 | if (self.margin === undefined) { 26 | self.margin = { 27 | top: 20, 28 | bottom: 20, 29 | right: 20, 30 | left: 20 31 | }; 32 | } 33 | 34 | 35 | if (self.size === "full_page") { 36 | self.svg_width = (rect.width < 500 & rect.width > 0) ? rect.width : 500; 37 | self.svg_height = (self.custom_height === undefined) ? 300 : self.custom_height; 38 | } else if (self.size === "margin") { 39 | //self.svg_width = (rect.width < 300) ? rect.width : 300; 40 | self.svg_width = 300; 41 | self.svg_height = (self.custom_height === undefined) ? 250 : self.custom_height; 42 | } else if (self.size === "double_plot_wide") { 43 | self.svg_width = (rect.width < 600) ? rect.width : 600; 44 | self.svg_height = (self.custom_height === undefined) ? 360 : self.custom_height; 45 | //self.margin.bottom = 50; 46 | } else if (self.size === "double_plot_narrow"){ 47 | self.svg_width = (rect.width < 300) ? rect.width : 300; 48 | self.svg_height = (self.custom_height === undefined) ? 360 : self.custom_height; 49 | //self.margin.bottom = 50; 50 | } 51 | else { 52 | self.svg_width = 300; 53 | self.svg_height = (self.custom_height === undefined) ? 360 : self.custom_height; 54 | } 55 | 56 | 57 | //Work out the dimensions of the chart 58 | self.width = self.svg_width - self.margin.left - self.margin.right; 59 | self.height = self.svg_height - self.margin.top - self.margin.bottom; 60 | 61 | //Define color scales 62 | 63 | 64 | 65 | return self; 66 | 67 | }; 68 | 69 | GlasseyeChart.prototype.add_svg = function() { 70 | 71 | var self = this; 72 | 73 | //Add the svg to the div 74 | self.svg = d3.select(self.div).append("svg") 75 | .attr("class", "glasseye_chart") 76 | .attr("width", self.svg_width) 77 | .attr("height", self.svg_height); 78 | 79 | //Add the chart area to the svg 80 | self.chart_area = self.svg.append("g") 81 | .attr("class", "chart_area") 82 | .attr("transform", "translate(" + self.margin.left + "," + self.margin.top + ")"); 83 | 84 | return self; 85 | }; 86 | 87 | 88 | /** 89 | * Adds a label to the TimeSeries object 90 | * @method 91 | * @param {string} title The title to be placed at the top of the chart 92 | * @returns {object} The modified TimeSeries object 93 | */ 94 | 95 | GlasseyeChart.prototype.add_title = function(title, subtitle) { 96 | 97 | var self = this; 98 | self.title = title; 99 | self.svg.append('text').attr("class", "title") 100 | .text(title) 101 | .attr("transform", "translate(" + self.margin.left + ",20)"); 102 | 103 | if (subtitle != undefined) { 104 | 105 | self.subtitle = subtitle; 106 | self.svg.append('text').attr("class", "subtitle") 107 | .text(subtitle) 108 | .attr("transform", "translate(" + self.margin.left + ",35)"); 109 | 110 | } 111 | 112 | return self; 113 | 114 | }; 115 | 116 | 117 | GlasseyeChart.prototype.set_tooltip_text = function (commentary_strings, variable_names, formats) { 118 | 119 | var self = this; 120 | 121 | self.tooltip_text = function (d) { 122 | var embedded_vars = variable_names.map(function(e){ 123 | return (e==="filter")? self.current_variable : d[e]; 124 | }) 125 | var text = create_commentary(commentary_strings, embedded_vars, formats) 126 | return text; 127 | } 128 | 129 | self.tip = d3.tip() 130 | .attr('class', 'd3-tip') 131 | .offset([-10, 0]) 132 | .html(self.tooltip_text); 133 | 134 | return self; 135 | 136 | } 137 | -------------------------------------------------------------------------------- /glasseye/js/modular/GlobalFunctionsAndVariable.js: -------------------------------------------------------------------------------- 1 | //Global formatting functions 2 | 3 | //Add span around text for highlighting 4 | function highlight(d){ 5 | return "" + d + ""; 6 | }; 7 | 8 | //Get max string length in an array of strings 9 | 10 | function max_string_length(strings){ 11 | 12 | var lengths = strings.map(function(d){return d.length}) 13 | 14 | return Math.max.apply(null, lengths); 15 | 16 | } 17 | 18 | 19 | var uni_format = function(d){ 20 | var return_val; 21 | 22 | if (d > 999) { 23 | return_val = d3.format(".3s")(d); 24 | } 25 | else if (d > 100) { 26 | return_val = d3.format(".3r")(d); 27 | } 28 | else if (d >= 10) { 29 | if (Math.round(d) === d) { 30 | return_val = d3.format(".0f")(d); 31 | } 32 | else { 33 | return_val = d3.format(".1f")(d); 34 | } 35 | } 36 | else if (d > 1) { 37 | return_val = d3.format(".1f")(d); 38 | } 39 | else 40 | { 41 | return_val = d3.format(".1f")(d); 42 | } 43 | return return_val; 44 | 45 | }; 46 | 47 | 48 | var uni_format_range = function(d){ 49 | 50 | var min = d[0], max = d[1]; 51 | console.log(min); 52 | console.log(max); 53 | 54 | if (min > - 1000 & max < 1000) {return d3.format(",.0f");} 55 | else {return d3.format(",.0f");} 56 | 57 | } 58 | 59 | 60 | var uni_format_axis = function(d){ 61 | var return_val; 62 | if (d >= 1) { 63 | return_val = d3.format("s")(d); 64 | } 65 | else 66 | { 67 | return_val = d3.format("")(d); 68 | } 69 | return return_val; 70 | 71 | }; 72 | 73 | var format_millions = function(d) { 74 | return Math.round(d / 1000) + "m"; 75 | }; 76 | 77 | var format_millions_2d = function(d) { 78 | return d3.format(".3r")(d / 1000) + "m"; 79 | }; 80 | 81 | 82 | var quarter_year = function(d) { 83 | 84 | var month = d3.time.format("%m")(d); 85 | var year = d3.time.format("%Y")(d); 86 | var quarter = parseInt(month) / 3; 87 | 88 | return "Q" + quarter + " " + year; 89 | 90 | }; 91 | 92 | 93 | //Commentary function to be used in tool tips and on side bars 94 | 95 | function cap_first_letter(string) { 96 | return string.charAt(0).toUpperCase() + string.slice(1); 97 | } 98 | 99 | function lower_case(string) { 100 | return string.toLowerCase(); 101 | } 102 | 103 | function unchanged(string) { 104 | return string; 105 | } 106 | 107 | function create_commentary(commentary_strings, embedded_vars, formats){ 108 | 109 | 110 | var string_parts = commentary_strings.split("$"); 111 | 112 | var text = ""; 113 | 114 | embedded_vars.forEach(function(d, i){ 115 | var formatter = (formats===undefined)? uni_format:formats[i]; 116 | text = text + string_parts[i] + formatter(d); 117 | }); 118 | 119 | return text; 120 | 121 | } 122 | 123 | 124 | function create_scale(data, d3_scale, padding) { 125 | 126 | var min = d3.min(data), 127 | max = d3.max(data); 128 | var range = max - min; 129 | var range_max_ratio = range / max; 130 | 131 | var scale = d3_scale; 132 | 133 | if (typeof d3_scale.rangePoints === "function") { 134 | scale.domain(data); 135 | var scale_type = "ordinal"; 136 | } else { 137 | 138 | if (typeof data[0] === "number") { 139 | 140 | if (range_max_ratio < 0.25 || min < 0) { 141 | scale.domain([min - 0.1 * range, max + 0.1 * range]).nice; 142 | } else { 143 | scale.domain([0, max + 0.1 * range]).nice; 144 | } 145 | 146 | var scale_type = "linear"; 147 | 148 | } else { 149 | 150 | scale.domain([min, max]).nice; 151 | 152 | if (data[0].constructor.name === "Date") { 153 | var scale_type = "datetime"; 154 | } else { 155 | var scale_type = "nonlinear"; 156 | } 157 | } 158 | 159 | } 160 | 161 | 162 | 163 | return { 164 | scale_func: scale, 165 | scale_type: scale_type 166 | }; 167 | 168 | } 169 | 170 | //Data processing function 171 | 172 | function build_chart(data, div, size, labels, csv_parser, inline_parser, draw) { 173 | 174 | 175 | if (typeof data === "object") 176 | 177 | { 178 | 179 | var processed_data = inline_parser(data); 180 | 181 | draw(processed_data, div, size, labels); 182 | 183 | } else 184 | 185 | { 186 | 187 | 188 | d3.csv(data, function(error, data) { 189 | 190 | var processed_data = csv_parser(data); 191 | draw(processed_data, div, size, labels); 192 | 193 | }); 194 | 195 | } 196 | 197 | } 198 | 199 | 200 | function add_legend(svg, x, y, legend_data) { 201 | 202 | var legend_groups = svg.selectAll('.legend_item') 203 | .data(legend_data) 204 | .enter() 205 | .append('g') 206 | .attr('class', 'legend_item') 207 | .attr("transform", "translate(" + x + "," + y + ")"); 208 | 209 | 210 | legend_groups.append("rect") 211 | .attr("width", 10) 212 | .attr("height", 10) 213 | .attr('class', function(d) { 214 | return ('legend_block ' + d.class); 215 | }) 216 | .attr("x", 10) 217 | .attr("y", function(d, i) { 218 | return i * 20; 219 | }) 220 | .attr("fill", function(d, i) { 221 | return d.colour; 222 | }); 223 | 224 | legend_groups.append("text") 225 | .attr("x", 27) 226 | .attr("y", function(d, i) { 227 | return 8 + i * 20; 228 | }) 229 | .text(function(d) { 230 | return d.label; 231 | }); 232 | 233 | } 234 | 235 | 236 | function wrap(text, width) { 237 | text.each(function() { 238 | var text = d3.select(this), 239 | words = text.text().split(/\s+/).reverse(), 240 | word, 241 | line = [], 242 | lineNumber = 0, 243 | lineHeight = 1.1, // ems 244 | y = text.attr("y"), 245 | dy = parseFloat(text.attr("dy")), 246 | tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em"); 247 | while (word = words.pop()) { 248 | line.push(word); 249 | tspan.text(line.join(" ")); 250 | if (tspan.node().getComputedTextLength() > width) { 251 | line.pop(); 252 | tspan.text(line.join(" ")); 253 | line = [word]; 254 | tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word); 255 | } 256 | } 257 | }); 258 | } 259 | 260 | 261 | function abbrev(text, max) { 262 | 263 | if (text.length > max) { 264 | text = text.substring(0, max - 3) + "..."; 265 | } 266 | 267 | return text; 268 | 269 | } 270 | 271 | function minmax_across_groups(processed_data, variable) { 272 | 273 | y_values = processed_data.map(function(d) { 274 | return (d.values.map(function(e) { 275 | if (e.variable === variable) { 276 | return e.value; 277 | } 278 | })); 279 | }); 280 | y_values = [].concat.apply([], y_values); 281 | 282 | return ([d3.min(y_values), d3.max(y_values)]); 283 | 284 | } 285 | 286 | function create_class_label(prefix, x){ 287 | 288 | return prefix + "_" + x.replace(/[.,\/#!$%\^&\*;:{}=+\-_`~()]/g,"").replace(" ",""); 289 | 290 | } -------------------------------------------------------------------------------- /glasseye/js/modular/GridChart.js: -------------------------------------------------------------------------------- 1 | var GridChart = function (div, size, labels, scales, margin, height) { 2 | 3 | var self = this; 4 | self.scales = scales; 5 | self.labels = labels; 6 | 7 | GlasseyeChart.call(self, div, size, margin, height); 8 | 9 | if (scales[0].scale_type === "ordinal") { 10 | self.x = self.scales[0].scale_func.rangePoints([0, self.width], 1); 11 | } else { 12 | self.x = self.scales[0].scale_func.range([0, self.width]); 13 | } 14 | 15 | if (scales[1].scale_type === "ordinal") { 16 | self.y = self.scales[1].scale_func.rangePoints([self.height, 0], 1); 17 | } else { 18 | self.y = self.scales[1].scale_func.range([self.height, 0]); 19 | } 20 | 21 | 22 | self.x_axis = d3.svg.axis() 23 | .scale(self.x) 24 | .orient("bottom") 25 | .tickSize(-self.height, 0, 0) 26 | .tickPadding(10); 27 | 28 | self.y_axis = d3.svg.axis() 29 | .scale(self.y) 30 | .orient("left") 31 | .tickSize(-self.width, 0, 0); 32 | 33 | //If the scale is not ordinal apply the universal format 34 | if (scales[1].scale_type != "ordinal") {self.y_axis .tickFormat(uni_format_axis)}; 35 | 36 | self.tooltip_formtter = uni_format; 37 | 38 | }; 39 | 40 | GridChart.prototype = Object.create(GlasseyeChart.prototype); 41 | 42 | GridChart.prototype.set_y_axis_format = function (format) { 43 | 44 | var self = this; 45 | 46 | self.y_axis.tickFormat(format); 47 | self.tooltip_formtter = format; 48 | return self; 49 | 50 | } 51 | 52 | 53 | 54 | GridChart.prototype.add_grid = function () { 55 | 56 | var self = this; 57 | 58 | var x_axis_g = self.chart_area.append("g") 59 | .attr("class", "chart_grid x_axis") 60 | .attr("transform", "translate(0," + self.height + ")") 61 | .call(self.x_axis); 62 | 63 | if (self.scales[0].scale_type === "nonlinear") { 64 | x_axis_g.selectAll("text") 65 | .style("text-anchor", "end") 66 | .attr("dx", "-.8em") 67 | .attr("dy", ".15em") 68 | .attr("transform", "rotate(-90)"); 69 | } 70 | 71 | self.chart_area.append("g") 72 | .attr("class", "chart_grid y_axis") 73 | .call(self.y_axis); 74 | 75 | //Add labels if they have been provided 76 | 77 | if (typeof self.labels !== "undefined") { 78 | self.svg.append("g") 79 | .attr("class", "axis_label axis_label_x") 80 | .attr("transform", "translate(" + (self.margin.left + self.width + 15) + ", " + (self.height + self.margin.top) + ") rotate(-90)") 81 | .append("text") 82 | .text(self.labels[0]); 83 | 84 | self.svg.append("g") 85 | .attr("class", "axis_label axis_label_y") 86 | .attr("transform", "translate(" + self.margin.left + ", " + (self.margin.top - 8) + ")") 87 | .append("text") 88 | .text(self.labels[1]); 89 | } 90 | 91 | return self; 92 | 93 | }; 94 | -------------------------------------------------------------------------------- /glasseye/js/modular/GroupedBarChart.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | The chart object class. 5 | Contains functions (including closures) and variables that describe the chart in the abstract. 6 | No svg elements are created here. 7 | Even where the chart can be implemented almost entirely by a closure, the closure is constructed 8 | in the object and then executed (and modified) in the methods. This way we have framework that is flexible 9 | enough fpr both techniques. 10 | */ 11 | 12 | /** 13 | * Builds a GroupedBarChart object 14 | * @constructor 15 | * @param {array} processed_data Data that has been given a structure appropriate to the chart 16 | * @param {string} div The div in which the chart will be placed 17 | * @param {string} size The size (one of several preset sizes) 18 | * @param {array} [labels] An array of the axis labels 19 | * @param {array} scales Scales for the x and y axes 20 | * @param {object} [margin] Optional argument in case the default margin settings need to be overridden 21 | */ 22 | 23 | var GroupedBarChart = function(x, y, z) { 24 | 25 | //Store arguments 26 | this.x = x; 27 | this.y = y; 28 | this.z = z; 29 | 30 | //Default parameters 31 | 32 | //Inherit any attributes or functions of a parent class 33 | YParent.call(this, x, y); 34 | 35 | //Any overides of parent attributes 36 | 37 | //Derive further attributes 38 | 39 | //Create functions or closures to be used in methods 40 | 41 | }; 42 | 43 | //Methods for the class. This is where svgs are created 44 | 45 | //Inherit methods from parent 46 | XChart.prototype = Object.create(YParent.prototype); 47 | 48 | /** 49 | * Adds the SVGs corresponding to the X object 50 | * 51 | * @method 52 | * @returns {object} The modified X object 53 | */ 54 | 55 | //Method for adding svgs 56 | XChart.prototype.add_svg = function() { 57 | 58 | //Store this locally so that it can reference in further functions 59 | var self = this; 60 | 61 | //If necessary filter the data 62 | var filtered_data = function(){}; 63 | 64 | //Draw the chart 65 | 66 | //Return the object so that we can use chaining 67 | return self; 68 | 69 | }; 70 | 71 | /** 72 | * update_svg the SVGs corresponding to the X object 73 | * 74 | * @method 75 | * @returns {object} The modified X object 76 | */ 77 | 78 | //Method for updating svgs 79 | XChart.prototype.update_svg = function() { 80 | 81 | //Store this locally so that it can reference in further functions 82 | var self = this; 83 | 84 | //If necessary filter the data 85 | var filtered_data = function(){}; 86 | 87 | //Update the chart 88 | 89 | //Return the object so that we can use chaining 90 | return self; 91 | 92 | }; 93 | 94 | 95 | //wrapper function to process the data and draw the chart 96 | 97 | // 98 | 99 | function x_chart(data, div, size) { 100 | 101 | //Define data parsers 102 | var inline_parser = function(data) { 103 | 104 | return processed_data; 105 | 106 | }; 107 | 108 | var csv_parser = function(data) { 109 | 110 | return processed_data; 111 | 112 | }; 113 | 114 | //Create draw function 115 | var draw = function(processed_data, div, size) { 116 | 117 | //Calculate values for scales 118 | 119 | //Create scales 120 | 121 | //Create new chart and chain methods 122 | 123 | var glasseye_chart = new XChart(processed_data, div, size); 124 | 125 | glasseye_chart.add_svg(); 126 | 127 | }; 128 | 129 | //Function that builds the chart based on whether the data is inline or from a file 130 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 131 | 132 | } 133 | -------------------------------------------------------------------------------- /glasseye/js/modular/LeafletMap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds an LeafletMap object 3 | * @constructor 4 | * @param {array} processed_data Data that has been given a structure appropriate to the chart 5 | * @param {string} div The div in which the chart will be placed 6 | * @param {string} size The size (one of several preset sizes) 7 | */ 8 | 9 | var LeafletMap = function(processed_data, div, size) { 10 | 11 | var self = this; 12 | 13 | margin = { 14 | top: 10, 15 | bottom: 10, 16 | left: 10, 17 | right: 10 18 | }; 19 | 20 | GlasseyeChart.call(self, div, size, margin, 250); 21 | 22 | self.processed_data = processed_data; 23 | 24 | //Create a new leaflet map 25 | var map = new L.Map("map", { 26 | center: [51.5, 0.12], 27 | zoom: 8 28 | }) 29 | .addLayer(new L.TileLayer("http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png")); 30 | 31 | //Line up svg on leaflet panel 32 | var svg = d3.select(map.getPanes().overlayPane).append("svg"), 33 | g = svg.append("g").attr("class", "leaflet-zoom-hide"); 34 | 35 | 36 | }; 37 | 38 | LeafletMap.prototype = Object.create(GlasseyeChart.prototype); 39 | 40 | /** 41 | * Adds the SVGs corresponding to the LeafletMap object 42 | * 43 | * @method 44 | * @returns {object} The modified LeafletMap object 45 | */ 46 | 47 | LeafletMap.prototype.add_map = function() { 48 | 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /glasseye/js/modular/LinePlot.js: -------------------------------------------------------------------------------- 1 | var LinePlot = function(processed_data, div, size, labels, scales) { 2 | 3 | GridChart.call(this, div, size, labels, scales); 4 | 5 | this.processed_data = processed_data; 6 | 7 | //Some customisations 8 | this.margin.left = 4; 9 | this.y_axis.tickFormat(""); 10 | 11 | this.tip = d3.tip() 12 | .attr('class', 'd3-tip') 13 | .offset([-10, 0]) 14 | .html(function(d) { 15 | return d3.format(".3n")(d.y); 16 | }); 17 | 18 | var x_scale = this.x, 19 | y_scale = this.y; 20 | 21 | this.line = d3.svg.line() 22 | .x(function(d) { 23 | return x_scale(d.x); 24 | }) 25 | .y(function(d) { 26 | return y_scale(d.y); 27 | }); 28 | 29 | }; 30 | 31 | LinePlot.prototype = Object.create(GridChart.prototype); 32 | 33 | LinePlot.prototype.add_line = function() { 34 | 35 | this.chart_area.call(this.tip); 36 | 37 | this.chart_area.append("path") 38 | .datum(this.processed_data) 39 | .attr("class", "line") 40 | .attr("d", this.line); 41 | 42 | var x_scale = this.x, 43 | y_scale = this.y; 44 | 45 | this.chart_area.selectAll("line_points") 46 | .data(this.processed_data) 47 | .enter() 48 | .append("circle") 49 | .attr("class", "line_points") 50 | .attr("cx", function(d) { 51 | return x_scale(d.x); 52 | }) 53 | .attr("cy", function(d) { 54 | return y_scale(d.y); 55 | }) 56 | .attr("r", 10) 57 | .attr("opacity", 0) 58 | .on('mouseover', this.tip.show) 59 | .on('mouseout', this.tip.hide); 60 | 61 | return this; 62 | 63 | }; 64 | 65 | function lineplot(data, div, size, labels) { 66 | 67 | 68 | var inline_parser = function(data) { 69 | 70 | var processed_data = []; 71 | 72 | for (i = 0; i < data.x.length; i++) { 73 | data_item = { 74 | "x": +data.x[i], 75 | "y": +data.y[i] 76 | }; 77 | processed_data.push(data_item); 78 | } 79 | 80 | return processed_data; 81 | }; 82 | 83 | var csv_parser = function(data) { 84 | 85 | var processed_data = data.map(function(d) { 86 | return { 87 | x: +d.x, 88 | y: +d.y 89 | }; 90 | }); 91 | 92 | return processed_data; 93 | 94 | }; 95 | 96 | var draw = function draw_lineplot(processed_data, div, size, labels) { 97 | 98 | var x_values = processed_data.map(function(d) { 99 | return d.x; 100 | }); 101 | var y_values = processed_data.map(function(d) { 102 | return d.y; 103 | }); 104 | var scales = [create_scale(x_values, d3.scale.linear()), create_scale(y_values, d3.scale.linear())]; 105 | var glasseye_chart = new LinePlot(processed_data, div, size, labels, scales); 106 | glasseye_chart.add_svg().add_grid().add_line(); 107 | 108 | }; 109 | 110 | build_chart(data, div, size, labels, csv_parser, inline_parser, draw); 111 | 112 | } 113 | 114 | 115 | function lineplot(data, div, size, labels) { 116 | 117 | 118 | var inline_parser = function(data) { 119 | 120 | var processed_data = []; 121 | 122 | for (i = 0; i < data.x.length; i++) { 123 | data_item = { 124 | "x": +data.x[i], 125 | "y": +data.y[i] 126 | }; 127 | processed_data.push(data_item); 128 | } 129 | 130 | return processed_data; 131 | }; 132 | 133 | var csv_parser = function(data) { 134 | 135 | var processed_data = data.map(function(d) { 136 | return { 137 | x: +d.x, 138 | y: +d.y 139 | }; 140 | }); 141 | 142 | return processed_data; 143 | 144 | }; 145 | 146 | var draw = function draw_lineplot(processed_data, div, size, labels) { 147 | 148 | var x_values = processed_data.map(function(d) { 149 | return d.x; 150 | }); 151 | var y_values = processed_data.map(function(d) { 152 | return d.y; 153 | }); 154 | var scales = [create_scale(x_values, d3.scale.linear()), create_scale(y_values, d3.scale.linear())]; 155 | var glasseye_chart = new LinePlot(processed_data, div, size, labels, scales); 156 | glasseye_chart.add_svg().add_grid().add_line(); 157 | 158 | }; 159 | 160 | build_chart(data, div, size, labels, csv_parser, inline_parser, draw); 161 | 162 | } 163 | -------------------------------------------------------------------------------- /glasseye/js/modular/LogReg.js: -------------------------------------------------------------------------------- 1 | //A template for glasseye charts 2 | 3 | /* 4 | The chart object class. 5 | Contains functions (including closures) and variables that describe the chart in the abstract. 6 | No svg elements are created here. 7 | Even where the chart can be implemented almost entirely by a closure, the closure is constructed 8 | in the object and then executed (and modified) in the methods. This way we have framework that is flexible 9 | enough fpr both techniques. 10 | */ 11 | 12 | var LogisticCurve = function(formula) { 13 | 14 | //Store arguments 15 | this.formula = formula.replace(/ /g, ''); 16 | 17 | //Parse the formula 18 | var right_left = this.formula.split("="); 19 | 20 | var response = right_left[0]; 21 | var explanatory = right_left[1].split("+"); 22 | 23 | var parsed_formula = { 24 | response: response, 25 | explanatory: explanatory.map(function(d) { 26 | var coef_split = d.split("*"); 27 | var coef = parseFloat(coef_split[0]); 28 | var range_split = coef_split[1].split("["); 29 | var variable = range_split[0]; 30 | var variable_range = range_split[1].slice(0, -1).split(",").map(function(d){return parseFloat(d);}); 31 | return { 32 | coef: coef, 33 | variable: variable, 34 | variable_range: variable_range 35 | }; 36 | }) 37 | }; 38 | 39 | console.log(parsed_formula); 40 | 41 | //Logistic Function 42 | function logistic(linear_pred) { 43 | 44 | return linear_pred; 45 | 46 | } 47 | 48 | //Default parameters 49 | 50 | //Inherit any attributes or functions of a parent class 51 | //GlasseyeChart.call(this, x, y); 52 | 53 | //Any overides of parent attributes 54 | 55 | //Derive further attributes 56 | 57 | //Create functions or closures to be used in methods 58 | 59 | //Function to create logistic curve 60 | 61 | var line = d3.svg.line() 62 | .x(function(d) { 63 | return d.x; 64 | }) 65 | .y(function(d) { 66 | return d.y; 67 | }) 68 | .interpolate("basis"); 69 | 70 | }; 71 | 72 | //Methods for the class. This is where svgs are created 73 | 74 | /* 75 | //Inherit methods from parent 76 | XChart.prototype = Object.create(YParent.prototype); 77 | 78 | //Method for adding svgs 79 | XChart.prototype.add_svg = function() { 80 | 81 | //Store this locally so that it can reference in further functions 82 | var self = this; 83 | 84 | //If necessary filter the data 85 | var filtered_data = function(){}; 86 | 87 | //Draw the chart 88 | 89 | //Return the object so that we can use chaining 90 | return self; 91 | 92 | }; 93 | 94 | 95 | //Method for updating svgs 96 | XChart.prototype.update_svg = function() { 97 | 98 | //Store this locally so that it can reference in further functions 99 | var self = this; 100 | 101 | //If necessary filter the data 102 | var filtered_data = function(){}; 103 | 104 | //Update the chart 105 | 106 | //Return the object so that we can use chaining 107 | return self; 108 | 109 | }; 110 | 111 | 112 | //wrapper function to process the data and draw the chart 113 | 114 | // 115 | 116 | function x_chart(data, div, size) { 117 | 118 | //Define data parsers 119 | var inline_parser = function(data) { 120 | 121 | return processed_data; 122 | 123 | }; 124 | 125 | var csv_parser = function(data) { 126 | 127 | return processed_data; 128 | 129 | }; 130 | 131 | //Create draw function 132 | var draw = function(processed_data, div, size) { 133 | 134 | //Calculate values for scales 135 | 136 | //Create scales 137 | 138 | //Create new chart and chain methods 139 | 140 | var glasseye_chart = new XChart(processed_data, div, size); 141 | 142 | glasseye_chart.add_svg(); 143 | 144 | }; 145 | 146 | //Function that builds the chart based on whether the data is inline or from a file 147 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 148 | 149 | } 150 | */ 151 | -------------------------------------------------------------------------------- /glasseye/js/modular/Parsers.js: -------------------------------------------------------------------------------- 1 | var heatmap_parser = function (data) { 2 | 3 | var processed_data; 4 | 5 | processed_data = data.map(function (d) { 6 | return { 7 | category_x: d.category_x, 8 | category_y: d.category_y, 9 | group: d.group, 10 | value: +d.value, 11 | nat_avg: +d.nat_avg, 12 | raw_value: +d.segment 13 | }; 14 | }); 15 | 16 | 17 | //Insert undefined for all combinations that don't appear 18 | 19 | //Get all groups 20 | var group = []; 21 | processed_data.map(function (d) { 22 | if (group.indexOf(d.group) === -1) { 23 | group.push(d.group); 24 | } 25 | }); 26 | 27 | //Get all category_x 28 | var category_x = []; 29 | processed_data.map(function (d) { 30 | if (category_x.indexOf(d.category_x) === -1) { 31 | category_x.push(d.category_x); 32 | } 33 | }); 34 | 35 | //Get all category_y 36 | var category_y = []; 37 | processed_data.map(function (d) { 38 | if (category_y.indexOf(d.category_y) === -1) { 39 | category_y.push(d.category_y); 40 | } 41 | }); 42 | 43 | 44 | var augmented_data = [] 45 | 46 | group.forEach(function (d) { 47 | category_x.forEach(function (e) { 48 | category_y.forEach(function (f) { 49 | var i = processed_data.filter(function (g) { 50 | return g.group === d & g.category_x === e & g.category_y === f; 51 | }); 52 | 53 | if (i.length === 0) { 54 | augmented_data.push( 55 | { 56 | category_x: e, 57 | category_y: f, 58 | group: d, 59 | value: undefined, 60 | nat_avg: undefined, 61 | raw_value: undefined 62 | } 63 | ); 64 | } 65 | else { 66 | 67 | augmented_data.push(i[0]); 68 | } 69 | }) 70 | ; 71 | }) 72 | 73 | } 74 | ) 75 | ; 76 | 77 | return (augmented_data); 78 | 79 | } 80 | ; 81 | 82 | 83 | var polygon_map_parser = function (data) { 84 | 85 | var ireland = topojson.feature(data, data.objects.Ireland).features[0]; 86 | var ulster = topojson.feature(data, data.objects.Ulster).features[0]; 87 | var tv_regions = topojson.feature(data, data.objects.TVRegions).features; 88 | ireland.properties['name'] = "Ireland"; 89 | ulster.properties['name'] = "Ulster"; 90 | tv_regions.push(ireland); 91 | tv_regions.push(ulster); 92 | return (tv_regions); 93 | 94 | }; 95 | 96 | 97 | var time_linked_venn_parser = function (data) { 98 | 99 | //Get all the dates 100 | 101 | var times = []; 102 | data.map(function (d) { 103 | if (times.indexOf(d.time) === -1) { 104 | times.push(d.time); 105 | } 106 | }); 107 | 108 | var parse_date = d3.time.format("%d/%m/%Y").parse; 109 | 110 | //Create the json data from the csv data 111 | var processed_data = times.map(function (g) { 112 | 113 | return { 114 | time: parse_date(g), 115 | venns: data.filter(function (d) { 116 | return d.time === g; 117 | }).map(function (e) { 118 | return { 119 | size: +e.value, 120 | sets: e.group.split("_") 121 | 122 | }; 123 | }) 124 | }; 125 | }); 126 | 127 | return processed_data; 128 | 129 | }; 130 | 131 | var drillable_venn_parser = function (data) { 132 | 133 | 134 | 135 | //Get all the parents 136 | 137 | var parents = []; 138 | data.map(function (d) { 139 | if (parents.indexOf(d.parent) === -1) { 140 | parents.push(d.parent); 141 | } 142 | }); 143 | 144 | //Create the json data from the csv data 145 | var processed_data = parents.map(function (g) { 146 | 147 | return { 148 | parent: g, 149 | venns: data.filter(function (d) { 150 | return d.parent === g; 151 | }).map(function (e) { 152 | return { 153 | size: +e.value, 154 | sets: e.group.split("_") 155 | 156 | }; 157 | }) 158 | }; 159 | }); 160 | 161 | 162 | return processed_data; 163 | 164 | }; 165 | 166 | var timeseries_parser = function (data) { 167 | 168 | var groups = []; 169 | data.map(function (d) { 170 | if (groups.indexOf(d.group) === -1) { 171 | groups.push(d.group); 172 | } 173 | }); 174 | 175 | 176 | var parse_date = d3.time.format("%d/%m/%Y").parse; 177 | 178 | //Create the json data from the csv data 179 | var processed_data = groups.map(function (g) { 180 | 181 | return { 182 | group: g, 183 | values: data.filter(function (d) { 184 | return d.group === g; 185 | }).map(function (e) { 186 | return { 187 | value: +e.value, 188 | time: parse_date(e.time), 189 | variable: e.variable 190 | }; 191 | }).sort(function (a, b) { 192 | return (a.time - b.time); 193 | }) 194 | }; 195 | }); 196 | 197 | return processed_data; 198 | 199 | }; 200 | 201 | var time_linked_parser = function (data) { 202 | 203 | 204 | var categories = []; 205 | data.map(function (d) { 206 | if (categories.indexOf(d.category) === -1) { 207 | categories.push(d.category); 208 | } 209 | }); 210 | 211 | //Try some date formats 212 | var parse_date = d3.time.format("%d/%m/%Y").parse; 213 | 214 | //Create the json data from the csv data 215 | var processed_data = categories.map(function (g) { 216 | 217 | return { 218 | category: g, 219 | values: data.filter(function (d) { 220 | return d.category === g; 221 | }).map(function (e) { 222 | return { 223 | value: +e.value, 224 | time: parse_date(e.time), 225 | variable: e.variable 226 | }; 227 | }).sort(function (a, b) { 228 | return (a.time - b.time); 229 | }) 230 | }; 231 | }); 232 | 233 | return processed_data; 234 | 235 | }; 236 | 237 | var dial_parser = function (data) { 238 | 239 | //Get all the groups 240 | 241 | //Get all the dates 242 | 243 | var groups = []; 244 | data.map(function (d) { 245 | if (groups.indexOf(d.group) === -1) { 246 | groups.push(d.group); 247 | } 248 | }); 249 | 250 | //Create the json data from the csv data 251 | var processed_data = groups.map(function (g) { 252 | 253 | return { 254 | group: g, 255 | values: data.filter(function (d) { 256 | return d.group === g; 257 | }).map(function (e) { 258 | return { 259 | value: +e.value, 260 | variable: e.variable, 261 | label: e.label 262 | }; 263 | }) 264 | }; 265 | }); 266 | 267 | return processed_data; 268 | 269 | }; 270 | 271 | var group_label_value_parser = function (data) { 272 | 273 | var processed_data; 274 | 275 | if (data[0].group === undefined) { 276 | 277 | processed_data = data.map(function (d) { 278 | return {label: d.label, value: +d.value}; 279 | 280 | }); 281 | 282 | } else { 283 | 284 | var groups = []; 285 | data.map(function (d) { 286 | if (groups.indexOf(d.group) === -1) { 287 | groups.push(d.group); 288 | } 289 | }); 290 | 291 | //Create the json data from the csv data 292 | processed_data = groups.map(function (g) { 293 | var y0 = 0; 294 | return { 295 | group: g, 296 | values: data.filter(function (d) { 297 | return d.group === g; 298 | }).map(function (e) { 299 | return { 300 | value: +e.value, 301 | label: e.label, 302 | y0: y0, 303 | y1: y0 += +e.value 304 | }; 305 | }) 306 | }; 307 | }); 308 | 309 | 310 | } 311 | 312 | return processed_data; 313 | 314 | }; 315 | -------------------------------------------------------------------------------- /glasseye/js/modular/PolygonMap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds an PolygonMap object 3 | * @constructor 4 | * @param {array} processed_data Data that has been given a structure appropriate to the chart 5 | * @param {string} div The div in which the chart will be placed 6 | * @param {string} size The size (one of several preset sizes) 7 | */ 8 | 9 | var PolygonMap = function (processed_data, div, size, tooltip_function) { 10 | 11 | var self = this; 12 | 13 | margin = { 14 | top: 5, 15 | bottom: 0, 16 | left: 5, 17 | right: 5 18 | }; 19 | 20 | GlasseyeChart.call(self, div, size, margin, 700); 21 | 22 | self.processed_data = processed_data; 23 | 24 | self.tooltip_function = tooltip_function; 25 | 26 | self.projection = d3.geo.albers() 27 | .center([0, 55.4]) 28 | .rotate([4.4, 0]) 29 | .parallels([50, 60]) 30 | .scale(self.width*300/46) 31 | .translate([self.width / 2, self.height / 2.4]); 32 | 33 | self.path = d3.geo.path() 34 | .projection(self.projection); 35 | 36 | self.tip = d3.select(self.div).append('div') 37 | .attr('class', 'hidden tooltip'); 38 | 39 | 40 | }; 41 | 42 | PolygonMap.prototype = Object.create(GlasseyeChart.prototype); 43 | 44 | /** 45 | * Adds the SVGs corresponding to the PolygonMap object 46 | * 47 | * @method 48 | * @returns {object} The modified PolygonMap object 49 | */ 50 | 51 | PolygonMap.prototype.add_map = function () { 52 | 53 | var self = this; 54 | 55 | self.chart_area.selectAll(".map_region") 56 | .data(self.processed_data) 57 | .enter().append("path") 58 | .attr("class", function (d) { 59 | return "map_region " + d.properties["name"].split(' ').join('_'); 60 | }) 61 | .attr("d", self.path) 62 | .on('mouseenter', function (d) { 63 | self.tooltip_function(d.properties["name"]); 64 | var mouse = d3.mouse(self.chart_area.node()).map(function (d) { 65 | return parseInt(d); 66 | }); 67 | self.tip.classed('hidden', false) 68 | .attr('style', 'left:' + (mouse[0]) + 69 | 'px; top:' + (mouse[1] - 0) + 'px') 70 | .html(d.properties["name"]); 71 | }) 72 | .on('mouseleave', function () { 73 | self.tip.classed('hidden', true) 74 | }); 75 | 76 | return this; 77 | 78 | }; 79 | 80 | 81 | /** 82 | * Redraws the PolygonMap (for example after a resize of the div) 83 | * @method 84 | * @returns {object} The modified PolygonMap object 85 | */ 86 | 87 | PolygonMap.prototype.redraw_map = function (title) { 88 | 89 | var self = this; 90 | 91 | //Delete the existing svg and commentary 92 | d3.select(self.div).selectAll("svg").remove(); 93 | 94 | //Reset the size 95 | self.set_size(); 96 | 97 | self.projection = d3.geo.albers() 98 | .center([0, 55.4]) 99 | .rotate([4.4, 0]) 100 | .parallels([50, 60]) 101 | .scale(self.width*300/46) 102 | .translate([self.width / 2, self.height / 2.4]); 103 | 104 | self.path = d3.geo.path() 105 | .projection(self.projection); 106 | 107 | //Redraw the chart 108 | self = self.add_svg().add_map().add_title(self.title, self.subtitle); 109 | 110 | return self; 111 | 112 | }; 113 | -------------------------------------------------------------------------------- /glasseye/js/modular/RandomNumber.js: -------------------------------------------------------------------------------- 1 | function random_gamma(alpha, beta){ 2 | 3 | //Create d3 random number generator functions 4 | 5 | var random_normal = d3.random.normal() 6 | 7 | var d = alpha - 1/3; 8 | var c = 1/Math.sqrt(9*d); 9 | 10 | var closure = function() { 11 | 12 | while (!(con_1 & con_2)) { 13 | 14 | var z = random_normal(); 15 | var u = Math.random(); 16 | var v = Math.pow(1 + c * z, 3); 17 | 18 | //Conditions 19 | 20 | var con_1 = z > -1 / c; 21 | var con_2 = Math.log(u) < (0.5 * Math.pow(z, 2) + d - d * v + d * Math.log(v)); 22 | } 23 | 24 | return (d * v) / beta; 25 | 26 | } 27 | 28 | return closure; 29 | } 30 | 31 | 32 | function random_dirichlet(alphas){ 33 | 34 | //Create gamma distributions 35 | 36 | var gammas = alphas.map(function(d){ 37 | return random_gamma(d,1); 38 | }) 39 | 40 | var closure = function() { 41 | 42 | var k = gammas.map(function(g) { 43 | return g(); 44 | }); 45 | 46 | var k_sum = d3.sum(k); 47 | 48 | var x = k.map(function(d) {return d/k_sum;}) 49 | 50 | 51 | return x; 52 | 53 | } 54 | 55 | return closure; 56 | 57 | } -------------------------------------------------------------------------------- /glasseye/js/modular/ScatterPlot.js: -------------------------------------------------------------------------------- 1 | var ScatterPlot = function(processed_data, div, size, labels, scales) { 2 | 3 | GridChart.call(this, div, size, labels, scales); 4 | 5 | this.processed_data = processed_data; 6 | 7 | 8 | this.tip = d3.tip() 9 | .attr('class', 'd3-tip') 10 | .offset([-10, 0]) 11 | .html(function(d) { 12 | return d3.format(".3n")(d.y); 13 | }); 14 | 15 | var x_scale = this.x, 16 | y_scale = this.y; 17 | 18 | }; 19 | 20 | ScatterPlot.prototype = Object.create(GridChart.prototype); 21 | 22 | ScatterPlot.prototype.add_points = function() { 23 | 24 | this.chart_area.call(this.tip); 25 | 26 | this.chart_area.append("path") 27 | .datum(this.processed_data) 28 | .attr("class", "line") 29 | .attr("d", this.line); 30 | 31 | var x_scale = this.x, 32 | y_scale = this.y; 33 | 34 | this.chart_area.selectAll("points") 35 | .data(this.processed_data) 36 | .enter() 37 | .append("circle") 38 | .attr("class", "points") 39 | .attr("cx", function(d) { 40 | return x_scale(d.x); 41 | }) 42 | .attr("cy", function(d) { 43 | return y_scale(d.y); 44 | }) 45 | .attr("r", 3) 46 | .on('mouseover', this.tip.show) 47 | .on('mouseout', this.tip.hide); 48 | 49 | return this; 50 | 51 | }; 52 | 53 | function scatterplot(data, div, size, labels) { 54 | 55 | 56 | var inline_parser = function(data) { 57 | 58 | var processed_data = []; 59 | 60 | for (i = 0; i < data.x.length; i++) { 61 | data_item = { 62 | "x": +data.x[i], 63 | "y": +data.y[i] 64 | }; 65 | processed_data.push(data_item); 66 | } 67 | 68 | return processed_data; 69 | }; 70 | 71 | var csv_parser = function(data) { 72 | 73 | var processed_data = data.map(function(d) { 74 | return { 75 | x: +d.x, 76 | y: +d.y, 77 | point_label: d.label 78 | }; 79 | }); 80 | 81 | return processed_data; 82 | 83 | }; 84 | 85 | var draw = function draw_scatterplot(processed_data, div, size, labels) { 86 | 87 | var x_values = processed_data.map(function(d) { 88 | return d.x; 89 | }); 90 | var y_values = processed_data.map(function(d) { 91 | return d.y; 92 | }); 93 | var scales = [create_scale(x_values, d3.scale.linear()), create_scale(y_values, d3.scale.linear())]; 94 | var glasseye_chart = new ScatterPlot(processed_data, div, size, labels, scales); 95 | glasseye_chart.add_svg().add_grid().add_points(); 96 | 97 | }; 98 | 99 | build_chart(data, div, size, labels, csv_parser, inline_parser, draw); 100 | 101 | } 102 | -------------------------------------------------------------------------------- /glasseye/js/modular/Thermometers.js: -------------------------------------------------------------------------------- 1 | var Thermometers = function(processed_data, div, size, labels, scales) { 2 | 3 | var self = this; 4 | 5 | var margin = { 6 | top: 50, 7 | bottom: 80, 8 | right: 50, 9 | left: 50 10 | }; 11 | 12 | BarChart.call(self, processed_data, div, size, labels, scales, margin); 13 | self.bar_width = self.width/7; 14 | self.y_axis.tickFormat(d3.format("0%")).ticks(6).tickSize(6); 15 | 16 | }; 17 | 18 | Thermometers.prototype = Object.create(BarChart.prototype); 19 | 20 | Thermometers.prototype.add_thermometers = function() { 21 | 22 | var self = this; 23 | 24 | self.chart_area.call(self.tip); 25 | 26 | //Customisations 27 | self.svg.attr("class", "glasseye_chart thermometers"); 28 | 29 | 30 | var therm = self.chart_area.selectAll(".thermometer") 31 | .data(self.processed_data) 32 | .enter() 33 | .append("g") 34 | .attr("class", "thermometer") 35 | .attr("transform", function(d) { 36 | return "translate(" + (self.x(d.category) - self.bar_width / 4) + ", " + 0 + ")"; 37 | }); 38 | 39 | 40 | var therm_width = self.bar_width / 2; 41 | var merc_prop = 0.8; 42 | 43 | therm.append("rect") 44 | .attr("class", "glass") 45 | .attr("width", therm_width) 46 | .attr("height", self.height); 47 | 48 | therm.append("rect") 49 | .attr("class", "glass-gap") 50 | .attr("x", therm_width * (1 - merc_prop) / 2) 51 | .attr("width", therm_width * merc_prop) 52 | .attr("height", self.height); 53 | 54 | therm.append("text") 55 | .attr("class", "therm_reading") 56 | .text(d3.format("%")(0)) 57 | .attr("transform", "translate(" + (self.bar_width) + ", " + self.height / 2 + ")"); 58 | 59 | therm.append("rect") 60 | .attr("class", "mercury") 61 | .attr("x", self.bar_width / 8) 62 | .attr("y", self.y(0)) 63 | .attr("width", self.bar_width / 4) 64 | .attr("height", self.height - self.y(0)); 65 | self.svg.append("text").attr("class", "context") 66 | .attr("y", self.height + self.margin.top + 60) 67 | .attr("x", self.margin.left + self.width / 2) 68 | .style("text-anchor", "middle"); 69 | 70 | return this; 71 | 72 | }; 73 | 74 | Thermometers.prototype.update_thermometers = function(time, variable) { 75 | 76 | var self = this; 77 | self.chart_area.selectAll(".mercury") 78 | //.attr("class", function(d,i){return("mercury d_"+ i)}) 79 | .transition() 80 | .duration(500) 81 | .attr("y", function(d) { 82 | var filtered = d.values.filter(function(e) { 83 | return e.time.getTime() === time.getTime() & e.variable === variable; 84 | }); 85 | return self.y(filtered[0].value); 86 | }) 87 | .attr("height", function(d) { 88 | var filtered = d.values.filter(function(e) { 89 | return e.time.getTime() === time.getTime() & e.variable === variable; 90 | }); 91 | 92 | return self.height - self.y(filtered[0].value); 93 | }); 94 | 95 | self.chart_area.selectAll(".therm_reading") 96 | .text(function(d) { 97 | var filtered = d.values.filter(function(e) { 98 | return e.time.getTime() === time.getTime() & e.variable === variable; 99 | }); 100 | return d3.format("%")(filtered[0].value); 101 | }); 102 | 103 | self.svg.selectAll(".context").text("In " + quarter_year(time) + " for " + variable + " households"); 104 | 105 | 106 | 107 | }; 108 | 109 | Thermometers.prototype.add_title = function(title, subtitle) { 110 | 111 | var self = this; 112 | self.title = title; 113 | self.svg.append('text').attr("class", "title") 114 | .text(title) 115 | .attr("y", 20) 116 | .attr("x", self.margin.left + self.width / 2) 117 | .style("text-anchor", "middle"); 118 | 119 | if (subtitle != undefined) { 120 | 121 | self.subtitle = subtitle; 122 | self.svg.append('text').attr("class", "subtitle") 123 | .text(subtitle) 124 | .attr("y", 35) 125 | .attr("x", self.margin.left + self.width / 2) 126 | .style("text-anchor", "middle"); 127 | 128 | } else { 129 | self.subtitle = ""; 130 | } 131 | 132 | return this; 133 | 134 | }; 135 | 136 | 137 | Thermometers.prototype.redraw_thermometer = function(title) { 138 | 139 | //Note no longer uses argument! 140 | 141 | var self = this; 142 | 143 | //Delete the existing svg and commentary 144 | d3.select(self.div).selectAll("svg").remove(); 145 | 146 | //Reset the size 147 | self.set_size(); 148 | self.bar_width = self.width /7; 149 | self.x = self.scales[0].scale_func.rangePoints([0, self.width], 1); 150 | 151 | //Redraw the chart 152 | self.add_svg().add_grid().add_thermometers().add_title(self.title, self.subtitle); 153 | 154 | }; 155 | 156 | function thermometers(data, div, size) { 157 | 158 | var inline_parser = function(data) { 159 | 160 | processed_data = []; 161 | 162 | for (i = 0; i < data.value.length; i++) { 163 | data_item = { 164 | "category": data.category[i], 165 | "value": +data.value[i] 166 | }; 167 | processed_data.push(data_item); 168 | 169 | } 170 | 171 | return processed_data; 172 | 173 | }; 174 | 175 | var csv_parser = function(data) { 176 | 177 | var parse_date = d3.time.format("%d/%m/%Y").parse; 178 | var processed_data = data.map(function(d) { 179 | return { 180 | category: d.category, 181 | filter: d.filter, 182 | value: d.value, 183 | time: parse_date(d.time) 184 | }; 185 | }); 186 | 187 | return processed_data; 188 | 189 | }; 190 | 191 | var draw = function(processed_data, div, size) { 192 | 193 | var x_values = processed_data.map(function(d) { 194 | return d.category; 195 | }); 196 | var y_values = processed_data.map(function(d) { 197 | return d.value; 198 | }); 199 | 200 | var scales = [create_scale(x_values, d3.scale.ordinal()), create_scale(y_values, d3.scale.linear())]; 201 | 202 | var glasseye_chart = new Thermometers(processed_data, div, size, ["category", "value"], scales); 203 | 204 | glasseye_chart.add_svg().add_grid().add_thermometers(); 205 | 206 | }; 207 | 208 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 209 | 210 | 211 | } 212 | -------------------------------------------------------------------------------- /glasseye/js/modular/Tree.js: -------------------------------------------------------------------------------- 1 | var Tree = function(processed_data, div, size) { 2 | 3 | var margin = (size === "full_page") ? { 4 | top: 5, 5 | bottom: 5, 6 | left: 100, 7 | right: 100 8 | } : { 9 | top: 5, 10 | bottom: 5, 11 | left: 50, 12 | right: 50 13 | }; 14 | 15 | GlasseyeChart.call(this, div, size, margin, 300); 16 | 17 | this.processed_data = processed_data; 18 | 19 | var cluster = d3.layout.tree() 20 | .size([this.height, this.width]); 21 | 22 | this.nodes = cluster.nodes(processed_data), 23 | this.links = cluster.links(this.nodes); 24 | 25 | this.diagonal = d3.svg.diagonal() 26 | .projection(function(d) { 27 | return [d.y, d.x]; 28 | }); 29 | 30 | }; 31 | 32 | Tree.prototype = Object.create(GlasseyeChart.prototype); 33 | 34 | Tree.prototype.add_tree = function() { 35 | 36 | var link = this.chart_area.selectAll(".treelink") 37 | .data(this.links) 38 | .enter().append("path") 39 | .attr("class", "treelink") 40 | .attr("d", this.diagonal); 41 | 42 | var node = this.chart_area.selectAll(".treenode") 43 | .data(this.nodes) 44 | .enter().append("g") 45 | .attr("class", "treenode") 46 | .attr("transform", function(d) { 47 | return "translate(" + d.y + "," + d.x + ")"; 48 | }); 49 | 50 | node.append("circle") 51 | .attr("r", 4.5); 52 | 53 | var abbr_len = (this.size === "full_page") ? 20 : 10; 54 | 55 | node.append("text") 56 | .attr("dx", function(d) { 57 | return d.children ? -8 : 8; 58 | }) 59 | .attr("dy", 3) 60 | .style("text-anchor", function(d) { 61 | return d.children ? "end" : "start"; 62 | }) 63 | .text(function(d) { 64 | return abbrev(d.name, abbr_len); 65 | }); 66 | 67 | }; 68 | 69 | 70 | function tree(data, div, size) { 71 | 72 | var inline_parser = function(data) { 73 | return data; 74 | }; 75 | 76 | var csv_parser = function(data) { 77 | return data; 78 | }; 79 | 80 | var draw = function(processed_data, div, size) { 81 | 82 | var glasseye_chart = new Tree(processed_data, div, size); 83 | 84 | glasseye_chart.add_svg().add_tree(); 85 | 86 | }; 87 | 88 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 89 | 90 | 91 | } 92 | -------------------------------------------------------------------------------- /glasseye/js/modular/Venn.js: -------------------------------------------------------------------------------- 1 | var Venn = function(processed_data, div, size) { 2 | 3 | margin = { 4 | top: 5, 5 | bottom: 5, 6 | left: 5, 7 | right: 5 8 | }; 9 | 10 | GlasseyeChart.call(this, div, size, margin); 11 | 12 | this.processed_data = processed_data; 13 | 14 | this.venn_chart = venn.VennDiagram() 15 | .width(this.width) 16 | .height(this.height); 17 | 18 | 19 | 20 | }; 21 | 22 | Venn.prototype = Object.create(GlasseyeChart.prototype); 23 | 24 | Venn.prototype.add_venn = function() { 25 | 26 | this.chart_area.datum(this.processed_data).call(this.venn_chart); 27 | 28 | }; 29 | 30 | 31 | function venn(data, div, size) { 32 | 33 | var inline_parser = function(data) { 34 | return data; 35 | }; 36 | 37 | var csv_parser = function(data) { 38 | return data; 39 | }; 40 | 41 | var draw = function(processed_data, div, size) { 42 | 43 | 44 | var glasseye_chart = new Venn(processed_data, div, size); 45 | 46 | glasseye_chart.add_svg().add_venn(); 47 | 48 | }; 49 | 50 | build_chart(data, div, size, undefined, csv_parser, inline_parser, draw); 51 | 52 | } 53 | -------------------------------------------------------------------------------- /glasseye/js/modular/conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": true 4 | }, 5 | "source": { 6 | "includePattern": ".+\\.js(doc)?$", 7 | "excludePattern": "(^|\\/|\\\\)_" 8 | }, 9 | "plugins": [], 10 | "templates": { 11 | "cleverLinks": false, 12 | "monospaceLinks": false, 13 | "default": { 14 | "outputSourceFiles": true 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /glasseye/js/modular/prepExport.sh: -------------------------------------------------------------------------------- 1 | EXP_DIR='/Users/simon/Documents/CodeRepos/barbarella/WPExport' 2 | GE_DIR='/Users/simon/Documents/CodeRepos/barbarella/javascript/glasseye' 3 | 4 | cp $GE_DIR/js/GlasseyeCharts.min.js $EXP_DIR/js 5 | cp $GE_DIR/js/venn.js $EXP_DIR/js 6 | cp $GE_DIR/js/icheck.min.js $EXP_DIR/js 7 | cp $GE_DIR/css/glasseyeCharts.css $EXP_DIR/css 8 | cp $GE_DIR/css/glasseyeChartsBlack.css $EXP_DIR/css 9 | cp $GE_DIR/js/square/blue.css $EXP_DIR/css 10 | cp $GE_DIR/js/square/blue.png $EXP_DIR/css 11 | cp $GE_DIR/data/*.csv $EXP_DIR/data -------------------------------------------------------------------------------- /glasseye/templates/tufteTemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Glasseye 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /glasseye/testingTemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Glasseye 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 |
22 |
23 | 24 | 28 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /glasseyeLayouts.md: -------------------------------------------------------------------------------- 1 | Glasseye Layouts 2 | ================ 3 | 4 | ### full page 5 | 6 | max (default) width: 500 7 | 8 | Default height: 300 9 | 10 |
11 | 12 | ### margin 13 | 14 | ### double plot wide 15 | 16 | ### double plot narrow 17 | 18 | 19 | 20 | Framed Layouts 21 | ================ -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='glasseye', 4 | version='0.1.17', 5 | description='A python module for converting markdown documents into the glasseye format', 6 | url='https://github.com/coppeliaMLA/glasseye', 7 | author='coppeliamla', 8 | author_email='info@coppelia.io', 9 | license='MIT', 10 | packages=['glasseye'], 11 | zip_safe=False, 12 | entry_points = { 13 | 'console_scripts': ['glasseye=glasseye.__main__:main'], 14 | }, 15 | include_package_data=True, 16 | install_requires=[ 17 | 'pypandoc', 'beautifulsoup4' 18 | ]) 19 | --------------------------------------------------------------------------------