=0)k=a[s]=0){if(w=Math.sqrt(Math.pow(a[s]-p[e+7],2)+Math.pow(a[s+1]-p[e+8],2)),2*p[e+3]/w0?(v=l*a[s+6]*p[e+6]/w/w,a[s+2]+=c*v,a[s+3]+=f*v):w<0&&(v=-l*a[s+6]*p[e+6]/w,a[s+2]+=c*v,a[s+3]+=f*v):w>0&&(v=l*a[s+6]*p[e+6]/w/w,a[s+2]+=c*v,a[s+3]+=f*v),p[e+4]<0)break;e=p[e+4];continue}e=p[e+5]}else{if(p[e]>=0&&p[e]!==s&&(c=a[s]-a[p[e]],f=a[s+1]-a[p[e]+1],w=Math.sqrt(c*c+f*f),u.settings.adjustSizes?w>0?(v=l*a[s+6]*a[p[e]+6]/w/w,a[s+2]+=c*v,a[s+3]+=f*v):w<0&&(v=-l*a[s+6]*a[p[e]+6]/w,a[s+2]+=c*v,a[s+3]+=f*v):w>0&&(v=l*a[s+6]*a[p[e]+6]/w/w,a[s+2]+=c*v,a[s+3]+=f*v)),p[e+4]<0)break;e=p[e+4]}else for(l=u.settings.scalingRatio,r=0;r0?(v=l*a[r+6]*a[i+6]/w/w,a[r+2]+=c*v,a[r+3]+=f*v,a[i+2]+=c*v,a[i+3]+=f*v):w<0&&(v=100*l*a[r+6]*a[i+6],a[r+2]+=c*v,a[r+3]+=f*v,a[i+2]-=c*v,a[i+3]-=f*v)):(w=Math.sqrt(c*c+f*f),w>0&&(v=l*a[r+6]*a[i+6]/w/w,a[r+2]+=c*v,a[r+3]+=f*v,a[i+2]-=c*v,a[i+3]-=f*v));for(g=u.settings.gravity/u.settings.scalingRatio,l=u.settings.scalingRatio,s=0;s0&&(v=l*a[s+6]*g):w>0&&(v=l*a[s+6]*g/w),a[s+2]-=c*v,a[s+3]-=f*v;for(l=1*(u.settings.outboundAttractionDistribution?d:1),o=0;o0&&(v=-l*y*Math.log(1+w)/w/a[r+6]):w>0&&(v=-l*y*Math.log(1+w)/w):u.settings.outboundAttractionDistribution?w>0&&(v=-l*y/a[r+6]):w>0&&(v=-l*y)):(w=Math.sqrt(Math.pow(c,2)+Math.pow(f,2)),u.settings.linLogMode?u.settings.outboundAttractionDistribution?w>0&&(v=-l*y*Math.log(1+w)/w/a[r+6]):w>0&&(v=-l*y*Math.log(1+w)/w):u.settings.outboundAttractionDistribution?(w=1,v=-l*y/a[r+6]):(w=1,v=-l*y)),w>0&&(a[r+2]+=c*v,a[r+3]+=f*v,a[i+2]-=c*v,a[i+3]-=f*v);var W,L,F,_;if(u.settings.adjustSizes)for(s=0;su.maxForce&&(a[s+2]=a[s+2]*u.maxForce/W,a[s+3]=a[s+3]*u.maxForce/W),L=a[s+6]*Math.sqrt((a[s+4]-a[s+2])*(a[s+4]-a[s+2])+(a[s+5]-a[s+3])*(a[s+5]-a[s+3])),F=Math.sqrt((a[s+4]+a[s+2])*(a[s+4]+a[s+2])+(a[s+5]+a[s+3])*(a[s+5]+a[s+3]))/2,_=.1*Math.log(1+F)/(1+Math.sqrt(L)),a[s]=a[s]+a[s+2]*(_/u.settings.slowDown),a[s+1]=a[s+1]+a[s+3]*(_/u.settings.slowDown));else for(s=0;s {
20 | console.log('express listening on localhost:' + HTTP_PORT)
21 | });
22 |
23 | app.use(express.static(__dirname + '/browser'));
24 |
25 | app.get('/', function (req, res) {
26 | res.sendFile(__dirname + '/browser/index.html');
27 | })
28 |
29 | //FIXME: proper error handling needed throughout the node server!
30 |
31 | let main_room = io.of('/main');
32 | let py_room = io.of('/py');
33 | let browser_room = io.of('/browser');
34 |
35 | let connections = {};
36 | //let current_browser_id = null; // temp variable that stores a
37 | let py_object_names = [];
38 |
39 | main_room.on('connection', (socket)=>{
40 |
41 |
42 | services.Kernel.listRunning().then((kernelModels)=>{ //token passed in
43 |
44 | let options = {name:kernelModels[0].name }
45 |
46 | services.Kernel.connectTo(kernelModels[0].id,options).then((kernel)=>{
47 | console.log('user ' + socket.id + ' connected to kernel: '+ kernelModels[0].id)
48 |
49 | // main sockets to communicate with Python IPySig here:
50 | socket.on('get-graph', (msg) => {
51 |
52 | // msg schema {'title': 'title_name', 'graph_name': graph_name }
53 |
54 | //TODO:: error check msg here
55 |
56 | let future = kernel.requestExecute({ code:'',
57 | user_expressions:{[`${msg.title}`]:`IPySig.export_graph_instance('${msg.graph_name}')`}
58 | });
59 |
60 | future.onReply = function(reply){
61 |
62 | main_room.to(socket.id).emit('message-reply', {'data': reply.content, 'title': msg.title}); // all replies get sent back through a single channel
63 | }
64 | });
65 |
66 | socket.on('disconnect', ()=>{
67 | console.log('user '+ socket.id + ' diconnected from kernel: '+ kernelModels[0].id)
68 | // can maybe emit here to fetch the browser data store and save it to disk
69 |
70 |
71 | });
72 |
73 | });
74 | //need to close connection to kernel via services.Kernel somehow
75 |
76 | }).catch((err)=>{
77 | console.log(err);
78 | });
79 |
80 | });
81 |
82 |
83 | browser_room.on('connection', (socket)=>{
84 |
85 | // this maintains a private namespace for for browser-server communication
86 |
87 | connections[socket.id] = py_object_names.pop()
88 | console.log('browser_connected: '+ socket.id)
89 |
90 | browser_room.to(socket.id).emit('pyobj-ref-to-browser', {'py_obj_name': connections[socket.id]})
91 | });
92 |
93 |
94 |
95 | py_room.on('connection', (socket)=>{
96 | // this room connects with an IPySig object on the python kernel
97 |
98 | socket.on('py-object-name', (py_obj_name)=>{
99 |
100 | let msg = { 'echo_name': py_obj_name ,'http_port': HTTP_PORT.toString()};
101 |
102 | py_object_names.push(py_obj_name);
103 | // hits python client-side callback and send the HTTP_PORT number
104 | socket.emit('pyconnect_response', msg);
105 | });
106 |
107 | });
108 |
109 |
110 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ipysig_prototype",
3 | "version": "0.0.1",
4 | "description": "A prototype app for IPySigma project that focuses on intercommunication between the notebook server and express",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "jd",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@jupyterlab/services": "^0.40.2",
13 | "express": "^4.15.2",
14 | "socket.io": "^1.7.3",
15 | "ws": "^2.2.1",
16 | "xmlhttprequest": "^1.8.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/assets/css/1-tools/_fonts.scss:
--------------------------------------------------------------------------------
1 | // https://fonts.googleapis.com/css?family=Raleway
2 |
3 | @import 'https://fonts.googleapis.com/css?family=Raleway:100,400,700';
4 |
5 | // https://www.fontsquirrel.com/fonts/Latin-Modern-Roman
6 |
7 | @font-face {
8 | font-family: 'LMRomanSlant10';
9 | src: url('fonts/lmromanslant10-regular-webfont.eot');
10 | src: url('fonts/lmromanslant10-regular-webfont.eot?#iefix') format('embedded-opentype'),
11 | url('fonts/lmromanslant10-regular-webfont.woff') format('woff'),
12 | url('fonts/lmromanslant10-regular-webfont.ttf') format('truetype'),
13 | url('fonts/lmromanslant10-regular-webfont.svg#latin_modern_roman_slante10Rg') format('svg');
14 | font-weight: normal;
15 | font-style: normal;
16 | }
17 |
--------------------------------------------------------------------------------
/assets/css/1-tools/_mixins.sass:
--------------------------------------------------------------------------------
1 | @mixin tile
2 | position: relative
3 | width: 1440px
4 | max-width: 95%
5 | height: 600px
6 | margin: 30px auto
7 | background-color: $tile-bg
8 | overflow: hidden
9 |
10 | @media (max-width: 768px)
11 | margin: 15px auto
12 |
13 | @mixin wrapper
14 | position: relative
15 | display: flex
16 | width: 1180px
17 | max-width: 95%
18 | height: 100%
19 | margin: 0 auto
20 | align-items: center
21 |
22 | @mixin heading
23 | margin-top: 0
24 | margin-bottom: 30px
25 | font-size: 42px
26 | font-family: $raleway
27 | font-weight: 100
28 |
29 | @media (max-width: 640px)
30 | margin-bottom: 20px
31 | font-size: 30px
32 |
--------------------------------------------------------------------------------
/assets/css/1-tools/_normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | html {
4 | font-family: sans-serif;
5 | -ms-text-size-adjust: 100%;
6 | -webkit-text-size-adjust: 100%;
7 | }
8 |
9 | body {
10 | margin: 0;
11 | }
12 |
13 | article,
14 | aside,
15 | details,
16 | figcaption,
17 | figure,
18 | footer,
19 | header,
20 | hgroup,
21 | main,
22 | menu,
23 | nav,
24 | section,
25 | summary {
26 | display: block;
27 | }
28 |
29 | audio,
30 | canvas,
31 | progress,
32 | video {
33 | display: inline-block;
34 | vertical-align: baseline;
35 | }
36 |
37 | audio:not([controls]) {
38 | display: none;
39 | height: 0;
40 | }
41 |
42 | [hidden],
43 | template {
44 | display: none;
45 | }
46 |
47 | a {
48 | background-color: transparent;
49 | }
50 |
51 | a:active,
52 | a:hover {
53 | outline: 0;
54 | }
55 |
56 | abbr[title] {
57 | border-bottom: 1px dotted;
58 | }
59 |
60 | b,
61 | strong {
62 | font-weight: bold;
63 | }
64 |
65 | dfn {
66 | font-style: italic;
67 | }
68 |
69 | h1 {
70 | font-size: 2em;
71 | margin: 0.67em 0;
72 | }
73 |
74 | mark {
75 | background: #ff0;
76 | color: #000;
77 | }
78 |
79 | small {
80 | font-size: 80%;
81 | }
82 |
83 | sub,
84 | sup {
85 | font-size: 75%;
86 | line-height: 0;
87 | position: relative;
88 | vertical-align: baseline;
89 | }
90 |
91 | sup {
92 | top: -0.5em;
93 | }
94 |
95 | sub {
96 | bottom: -0.25em;
97 | }
98 |
99 | img {
100 | border: 0;
101 | }
102 |
103 | svg:not(:root) {
104 | overflow: hidden;
105 | }
106 |
107 | figure {
108 | margin: 1em 40px;
109 | }
110 |
111 | hr {
112 | -moz-box-sizing: content-box;
113 | box-sizing: content-box;
114 | height: 0;
115 | }
116 |
117 | pre {
118 | overflow: auto;
119 | }
120 |
121 | code,
122 | kbd,
123 | pre,
124 | samp {
125 | font-family: monospace, monospace;
126 | font-size: 1em;
127 | }
128 |
129 | button,
130 | input,
131 | optgroup,
132 | select,
133 | textarea {
134 | color: inherit;
135 | font: inherit;
136 | margin: 0;
137 | }
138 |
139 | button {
140 | overflow: visible;
141 | }
142 |
143 | button,
144 | select {
145 | text-transform: none;
146 | }
147 |
148 | button,
149 | html input[type="button"],
150 | input[type="reset"],
151 | input[type="submit"] {
152 | -webkit-appearance: button;
153 | cursor: pointer;
154 | }
155 |
156 | button[disabled],
157 | html input[disabled] {
158 | cursor: default;
159 | }
160 |
161 | button::-moz-focus-inner,
162 | input::-moz-focus-inner {
163 | border: 0;
164 | padding: 0;
165 | }
166 |
167 | input {
168 | line-height: normal;
169 | }
170 |
171 | input[type="checkbox"],
172 | input[type="radio"] {
173 | box-sizing: border-box;
174 | padding: 0;
175 | }
176 |
177 | input[type="number"]::-webkit-inner-spin-button,
178 | input[type="number"]::-webkit-outer-spin-button {
179 | height: auto;
180 | }
181 |
182 | input[type="search"] {
183 | -webkit-appearance: textfield;
184 | -moz-box-sizing: content-box;
185 | -webkit-box-sizing: content-box;
186 | box-sizing: content-box;
187 | }
188 |
189 | input[type="search"]::-webkit-search-cancel-button,
190 | input[type="search"]::-webkit-search-decoration {
191 | -webkit-appearance: none;
192 | }
193 |
194 | fieldset {
195 | border: 1px solid #c0c0c0;
196 | margin: 0 2px;
197 | padding: 0.35em 0.625em 0.75em;
198 | }
199 |
200 | legend {
201 | border: 0;
202 | padding: 0;
203 | }
204 |
205 | textarea {
206 | overflow: auto;
207 | }
208 |
209 | optgroup {
210 | font-weight: bold;
211 | }
212 |
213 | table {
214 | border-collapse: collapse;
215 | border-spacing: 0;
216 | }
217 |
218 | td,
219 | th {
220 | padding: 0;
221 | }
222 |
--------------------------------------------------------------------------------
/assets/css/1-tools/_vars.sass:
--------------------------------------------------------------------------------
1 | // Colors
2 | $white: #fff
3 | $black: #111
4 | $tile-bg: #efefef
5 | $letter-muted: #e3e3e3
6 |
7 | // Fonts
8 | $raleway: 'Raleway', Helvetica, sans-serif
9 | $roman: 'LMRomanSlant10', serif
10 |
--------------------------------------------------------------------------------
/assets/css/2-basics/_body-element.sass:
--------------------------------------------------------------------------------
1 | body
2 | background-color: white
3 | font-size: 16px
4 | line-height: 1.6
5 | font-family: $roman
6 | color: $black
7 | -webkit-font-smoothing: antialiased
8 | -webkit-text-size-adjust: 100%
9 |
10 | @media (max-width: 415px)
11 | font-size: 14px
12 |
--------------------------------------------------------------------------------
/assets/css/2-basics/_selection-colors.sass:
--------------------------------------------------------------------------------
1 | ::selection
2 | background: #FFF498
3 |
4 | ::-moz-selection
5 | background: #FFF498
6 |
7 | img::selection
8 | background: transparent
9 |
10 | img::-moz-selection
11 | background: transparent
12 |
13 | body
14 | -webkit-tap-highlight-color: #FFF498
15 |
--------------------------------------------------------------------------------
/assets/css/2-basics/_typography.sass:
--------------------------------------------------------------------------------
1 | // Headings
2 |
3 | .gigantic, .huge, .large, .bigger, .big,
4 | h1, h2, h3, h4, h5, h6
5 | color: #222
6 | font-weight: bold
7 |
8 | .gigantic
9 | font-size: 110px
10 | line-height: 1.09
11 | letter-spacing: -2px
12 |
13 | .huge, h1
14 | font-size: 68px
15 | line-height: 1.05
16 | letter-spacing: -1px
17 |
18 | .large, h2
19 | font-size: 42px
20 | line-height: 1.14
21 |
22 | .bigger, h3
23 | font-size: 26px
24 | line-height: 1.38
25 |
26 | .big, h4
27 | font-size: 22px
28 | line-height: 1.38
29 |
30 | .small, small
31 | font-size: 10px
32 | line-height: 1.2
33 |
34 |
35 | // Basic Text Style
36 |
37 | p
38 | margin: 0 0 20px 0
39 |
40 | em
41 | font-style: italic
42 |
43 | strong
44 | font-weight: bold
45 |
46 | hr
47 | border: solid #ddd
48 | border-width: 1px 0 0
49 | clear: both
50 | margin: 10px 0 30px
51 | height: 0
52 |
--------------------------------------------------------------------------------
/assets/css/3-modules/_nav.sass:
--------------------------------------------------------------------------------
1 | .nav
2 | visibility: hidden
3 | position: absolute
4 | top: 75px
5 | right: 0
6 | display: flex
7 | width: 100px
8 | flex-direction: column
9 | justify-content: center
10 | margin: 0
11 | padding: 0
12 | list-style: none
13 | background-color: $letter-muted
14 | border: 1px solid $black
15 | border: 1px solid rgba(17,17,17,.15)
16 | opacity: 0
17 | z-index: 10
18 | transition: opacity .3s ease-in-out, visibility 0s .3s
19 |
20 | &.open
21 | visibility: visible
22 | opacity: 1
23 | transition: opacity .3s ease-in-out
24 |
25 | &:before
26 | content: ""
27 | position: absolute
28 | top: -12px
29 | left: 50%
30 | transform: translateX(-50%) rotate(45deg)
31 | width: 25px
32 | height: 25px
33 | border: 1px solid $black
34 | border: 1px solid rgba(17,17,17,.15)
35 | background-color: $letter-muted
36 |
37 | li
38 | position: relative
39 | width: 100%
40 | text-align: center
41 | border-bottom: 1px solid $black
42 | border-bottom: 1px solid rgba(17,17,17,.15)
43 | background-color: $letter-muted
44 | overflow: hidden
45 | z-index: 10
46 |
47 | &:last-child
48 | border-bottom: none
49 |
50 | a
51 | display: block
52 | width: 100%
53 | height: 100%
54 | padding: 5px 0
55 | color: $black
56 | font-size: 12px
57 | font-family: $raleway
58 | text-transform: uppercase
59 | text-decoration: none
60 | transition: background-color .2s ease-in-out
61 |
62 | &:hover
63 | background-color: darken($letter-muted, 3%)
64 |
--------------------------------------------------------------------------------
/assets/css/4-sections/_about.sass:
--------------------------------------------------------------------------------
1 | #about
2 | @include tile
3 |
4 | .wrapper
5 | @include wrapper
6 |
7 | .camera
8 | width: 45%
9 |
10 | img
11 | width: 85%
12 |
13 | .blurb
14 | width: 55%
15 |
16 | h2
17 | @include heading
18 |
19 | &:before
20 | content: ""
21 | position: relative
22 | bottom: 15px
23 | display: inline-block
24 | width: 70px
25 | height: 1px
26 | margin-right: 30px
27 | background-color: $black
28 |
29 | p
30 | max-width: 600px
31 | margin-bottom: 45px
32 |
33 | .social
34 | display: flex
35 | width: 180px
36 | justify-content: space-between
37 |
38 | a
39 | color: $white
40 | text-decoration: none
41 |
42 | i
43 | display: block
44 | width: 30px
45 | height: 30px
46 | font-size: 16px
47 | text-align: center
48 | line-height: 30px
49 | background-color: #909090
50 | transition: background-color .2s ease-in-out
51 |
52 | &:hover
53 | background-color: #020202
54 |
55 | @media (max-width: 1023px)
56 | .camera
57 | display: none
58 |
59 | .blurb
60 | display: flex
61 | width: 100%
62 | flex-direction: column
63 | align-items: flex-end
64 | text-align: right
65 |
66 | h2:before
67 | display: none
68 |
69 | @media (max-width: 640px)
70 | .blurb
71 | align-items: center
72 | text-align: center
73 |
--------------------------------------------------------------------------------
/assets/css/4-sections/_featured.sass:
--------------------------------------------------------------------------------
1 | #featured
2 | @include tile
3 |
4 | .wrapper
5 | @include wrapper
6 |
7 | .blurb
8 | width: 45%
9 |
10 | &:after
11 | content: ""
12 | display: inline-block
13 | width: 1px
14 | height: 70px
15 | background-color: $black
16 |
17 | h2
18 | @include heading
19 |
20 | p
21 | max-width: 400px
22 | margin-bottom: 30px
23 |
24 | .featured
25 | width: 55%
26 |
27 | img
28 | width: 100%
29 |
30 | @media (max-width: 1023px)
31 | .blurb
32 | display: flex
33 | width: 100%
34 | flex-direction: column
35 | align-items: flex-end
36 | text-align: right
37 |
38 | &:after
39 | display: none
40 |
41 | p
42 | margin-bottom: 0
43 |
44 | .featured
45 | display: none
46 |
47 | @media (max-width: 640px)
48 | .blurb
49 | align-items: center
50 | text-align: center
51 |
--------------------------------------------------------------------------------
/assets/css/4-sections/_full-slide.sass:
--------------------------------------------------------------------------------
1 | #full-slide
2 | @include tile
3 |
4 | .banner
5 | position: relative
6 | width: 100%
7 | height: 100%
8 | margin: 0
9 | padding: 0
10 | list-style: none
11 |
12 | li
13 | position: absolute
14 | top: 0
15 | left: 0
16 | right: 0
17 | bottom: 0
18 | background:
19 | position: center
20 | size: cover
21 | repeat: no-repeat
22 | opacity: 0
23 | transition: opacity .6s ease-in-out
24 |
25 | &.active
26 | opacity: 1
27 | transition: opacity .6s ease-in-out
28 |
29 | li:nth-child(1)
30 | background-image: url('../img/full-slide/thumb-1.jpg')
31 |
32 | li:nth-child(2)
33 | background-image: url('../img/full-slide/thumb-2.jpg')
34 |
35 | li:nth-child(3)
36 | background-image: url('../img/full-slide/thumb-3.jpg')
37 |
38 | i
39 | position: absolute
40 | top: 50%
41 | transform: translateY(-50%) scale(1)
42 | width: 60px
43 | height: 60px
44 | font-size: 28px
45 | text-align: center
46 | line-height: 60px
47 | background-color: $white
48 | cursor: pointer
49 | z-index: 10
50 | transition: all .2s ease-in-out
51 |
52 | &:hover
53 | transform: translateY(-50%) scale(1.1)
54 |
55 | .prev
56 | left: 30px
57 |
58 | .next
59 | right: 30px
60 |
61 | @media (max-width: 767px)
62 | i
63 | transform: translateY(-50%) scale(.7)
64 |
65 | &:hover
66 | transform: translateY(-50%) scale(.8)
67 |
68 | .prev
69 | left: 0
70 |
71 | .next
72 | right: 0
73 |
--------------------------------------------------------------------------------
/assets/css/4-sections/_hero.sass:
--------------------------------------------------------------------------------
1 | #hero
2 | @include tile
3 |
4 | header
5 | position: absolute
6 | left: 50%
7 | transform: translateX(-50%)
8 | display: flex
9 | width: 95%
10 | height: 80px
11 | align-items: center
12 | justify-content: flex-end
13 | z-index: 10
14 |
15 | .nav-toggle
16 | position: relative
17 | display: flex
18 | width: 80px
19 | align-items: center
20 | justify-content: space-between
21 | cursor: pointer
22 |
23 | p
24 | margin-bottom: 0
25 | font-size: 14px
26 | font-family: $raleway
27 | text-transform: uppercase
28 |
29 | span,
30 | span:before,
31 | span:after
32 | content: ""
33 | position: relative
34 | display: block
35 | width: 23px
36 | height: 2px
37 | background-color: $black
38 |
39 | span:before
40 | bottom: 7px
41 |
42 | span:after
43 | top: 5px
44 |
45 | .wrapper
46 | @include wrapper
47 |
48 | .welcome
49 | width: 45%
50 |
51 | &:after
52 | content: ""
53 | display: block
54 | width: 1px
55 | height: 70px
56 | background-color: $black
57 |
58 | h1
59 | @include heading
60 |
61 | p
62 | max-width: 450px
63 | margin-bottom: 30px
64 |
65 | @media (max-width: 1180px)
66 | &:after
67 | display: none
68 |
69 | p
70 | margin-bottom: 0
71 |
72 | .photographer
73 | width: 55%
74 | align-self: flex-end
75 | line-height: 0
76 |
77 | img
78 | width: 100%
79 | opacity: .95
80 |
81 | @media (max-width: 1023px)
82 | .welcome
83 | display: flex
84 | width: 100%
85 | flex-direction: column
86 | align-items: flex-end
87 | text-align: right
88 |
89 | .photographer
90 | display: none
91 |
92 | @media (max-width: 640px)
93 | .welcome
94 | align-items: center
95 | text-align: center
96 |
--------------------------------------------------------------------------------
/assets/css/fonts/icomoon.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/css/fonts/icomoon.eot
--------------------------------------------------------------------------------
/assets/css/fonts/icomoon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Generated by IcoMoon
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/assets/css/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/css/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/assets/css/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/css/fonts/icomoon.woff
--------------------------------------------------------------------------------
/assets/css/fonts/lmromanslant10-regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/css/fonts/lmromanslant10-regular-webfont.eot
--------------------------------------------------------------------------------
/assets/css/fonts/lmromanslant10-regular-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/css/fonts/lmromanslant10-regular-webfont.ttf
--------------------------------------------------------------------------------
/assets/css/fonts/lmromanslant10-regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/css/fonts/lmromanslant10-regular-webfont.woff
--------------------------------------------------------------------------------
/assets/css/main.css:
--------------------------------------------------------------------------------
1 | @import "https://fonts.googleapis.com/css?family=Raleway:100,400,700";@font-face{font-family:'LMRomanSlant10';src:url("fonts/lmromanslant10-regular-webfont.eot");src:url("fonts/lmromanslant10-regular-webfont.eot?#iefix") format("embedded-opentype"),url("fonts/lmromanslant10-regular-webfont.woff") format("woff"),url("fonts/lmromanslant10-regular-webfont.ttf") format("truetype"),url("fonts/lmromanslant10-regular-webfont.svg#latin_modern_roman_slante10Rg") format("svg");font-weight:normal;font-style:normal}/*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@font-face{font-family:'icomoon';src:url("fonts/icomoon.eot?3hi6ws");src:url("fonts/icomoon.eot?3hi6ws#iefix") format("embedded-opentype"),url("fonts/icomoon.ttf?3hi6ws") format("truetype"),url("fonts/icomoon.woff?3hi6ws") format("woff"),url("fonts/icomoon.svg?3hi6ws#icomoon") format("svg");font-weight:normal;font-style:normal}[class^="icon-"],[class*=" icon-"]{font-family:'icomoon' !important;speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-chevron-thin-left:before{content:"\e904"}.icon-chevron-thin-right:before{content:"\e905"}.icon-pinterest-p:before{content:"\e900"}.icon-twitter:before{content:"\e901"}.icon-instagram:before{content:"\e902"}.icon-facebook:before{content:"\e903"}body{background-color:#fff;font-size:16px;line-height:1.6;font-family:"LMRomanSlant10",serif;color:#111;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%}@media (max-width: 415px){body{font-size:14px}}::-moz-selection{background:#FFF498}::selection{background:#FFF498}::-moz-selection{background:#FFF498}img::-moz-selection{background:transparent}img::selection{background:transparent}img::-moz-selection{background:transparent}body{-webkit-tap-highlight-color:#FFF498}.gigantic,.huge,.large,.bigger,.big,h1,h2,h3,h4,h5,h6{color:#222;font-weight:bold}.gigantic{font-size:110px;line-height:1.09;letter-spacing:-2px}.huge,h1{font-size:68px;line-height:1.05;letter-spacing:-1px}.large,h2{font-size:42px;line-height:1.14}.bigger,h3{font-size:26px;line-height:1.38}.big,h4{font-size:22px;line-height:1.38}.small,small{font-size:10px;line-height:1.2}p{margin:0 0 20px 0}em{font-style:italic}strong{font-weight:bold}hr{border:solid #ddd;border-width:1px 0 0;clear:both;margin:10px 0 30px;height:0}svg{position:absolute;height:100%}svg path{fill:#e3e3e3}#hero svg{left:10px}#about svg,#featured svg,#three-slide svg{left:90px}#full-slide svg{left:0;opacity:.1;z-index:9}#contact svg{left:0}@media (max-width: 640px){#hero svg,#about svg,#featured svg,#three-slide svg,#full-slide svg,#contact svg{left:0}}.nav{visibility:hidden;position:absolute;top:75px;right:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100px;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;margin:0;padding:0;list-style:none;background-color:#e3e3e3;border:1px solid #111;border:1px solid rgba(17,17,17,0.15);opacity:0;z-index:10;-webkit-transition:opacity .3s ease-in-out,visibility 0s .3s;transition:opacity .3s ease-in-out,visibility 0s .3s}.nav.open{visibility:visible;opacity:1;-webkit-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}.nav:before{content:"";position:absolute;top:-12px;left:50%;-webkit-transform:translateX(-50%) rotate(45deg);transform:translateX(-50%) rotate(45deg);width:25px;height:25px;border:1px solid #111;border:1px solid rgba(17,17,17,0.15);background-color:#e3e3e3}.nav li{position:relative;width:100%;text-align:center;border-bottom:1px solid #111;border-bottom:1px solid rgba(17,17,17,0.15);background-color:#e3e3e3;overflow:hidden;z-index:10}.nav li:last-child{border-bottom:none}.nav li a{display:block;width:100%;height:100%;padding:5px 0;color:#111;font-size:12px;font-family:"Raleway",Helvetica,sans-serif;text-transform:uppercase;text-decoration:none;-webkit-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out}.nav li a:hover{background-color:#dbdbdb}#hero{position:relative;width:1440px;max-width:95%;height:600px;margin:30px auto;background-color:#efefef;overflow:hidden}@media (max-width: 768px){#hero{margin:15px auto}}#hero header{position:absolute;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:95%;height:80px;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end;z-index:10}#hero header .nav-toggle{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:80px;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;cursor:pointer}#hero header .nav-toggle p{margin-bottom:0;font-size:14px;font-family:"Raleway",Helvetica,sans-serif;text-transform:uppercase}#hero header .nav-toggle span,#hero header .nav-toggle span:before,#hero header .nav-toggle span:after{content:"";position:relative;display:block;width:23px;height:2px;background-color:#111}#hero header .nav-toggle span:before{bottom:7px}#hero header .nav-toggle span:after{top:5px}#hero .wrapper{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:1180px;max-width:95%;height:100%;margin:0 auto;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}#hero .wrapper .welcome{width:45%}#hero .wrapper .welcome:after{content:"";display:block;width:1px;height:70px;background-color:#111}#hero .wrapper .welcome h1{margin-top:0;margin-bottom:30px;font-size:42px;font-family:"Raleway",Helvetica,sans-serif;font-weight:100}@media (max-width: 640px){#hero .wrapper .welcome h1{margin-bottom:20px;font-size:30px}}#hero .wrapper .welcome p{max-width:450px;margin-bottom:30px}@media (max-width: 1180px){#hero .wrapper .welcome:after{display:none}#hero .wrapper .welcome p{margin-bottom:0}}#hero .wrapper .photographer{width:55%;-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end;line-height:0}#hero .wrapper .photographer img{width:100%;opacity:.95}@media (max-width: 1023px){#hero .wrapper .welcome{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:end;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;text-align:right}#hero .wrapper .photographer{display:none}}@media (max-width: 640px){#hero .wrapper .welcome{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;text-align:center}}#about{position:relative;width:1440px;max-width:95%;height:600px;margin:30px auto;background-color:#efefef;overflow:hidden}@media (max-width: 768px){#about{margin:15px auto}}#about .wrapper{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:1180px;max-width:95%;height:100%;margin:0 auto;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}#about .wrapper .camera{width:45%}#about .wrapper .camera img{width:85%}#about .wrapper .blurb{width:55%}#about .wrapper .blurb h2{margin-top:0;margin-bottom:30px;font-size:42px;font-family:"Raleway",Helvetica,sans-serif;font-weight:100}@media (max-width: 640px){#about .wrapper .blurb h2{margin-bottom:20px;font-size:30px}}#about .wrapper .blurb h2:before{content:"";position:relative;bottom:15px;display:inline-block;width:70px;height:1px;margin-right:30px;background-color:#111}#about .wrapper .blurb p{max-width:600px;margin-bottom:45px}#about .wrapper .blurb .social{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:180px;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}#about .wrapper .blurb .social a{color:#fff;text-decoration:none}#about .wrapper .blurb .social i{display:block;width:30px;height:30px;font-size:16px;text-align:center;line-height:30px;background-color:#909090;-webkit-transition:background-color .2s ease-in-out;transition:background-color .2s ease-in-out}#about .wrapper .blurb .social i:hover{background-color:#020202}@media (max-width: 1023px){#about .wrapper .camera{display:none}#about .wrapper .blurb{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:end;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;text-align:right}#about .wrapper .blurb h2:before{display:none}}@media (max-width: 640px){#about .wrapper .blurb{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;text-align:center}}#full-slide{position:relative;width:1440px;max-width:95%;height:600px;margin:30px auto;background-color:#efefef;overflow:hidden}@media (max-width: 768px){#full-slide{margin:15px auto}}#full-slide .banner{position:relative;width:100%;height:100%;margin:0;padding:0;list-style:none}#full-slide .banner li{position:absolute;top:0;left:0;right:0;bottom:0;background-position:center;background-size:cover;background-repeat:no-repeat;opacity:0;-webkit-transition:opacity .6s ease-in-out;transition:opacity .6s ease-in-out}#full-slide .banner li.active{opacity:1;-webkit-transition:opacity .6s ease-in-out;transition:opacity .6s ease-in-out}#full-slide .banner li:nth-child(1){background-image:url("../img/full-slide/thumb-1.jpg")}#full-slide .banner li:nth-child(2){background-image:url("../img/full-slide/thumb-2.jpg")}#full-slide .banner li:nth-child(3){background-image:url("../img/full-slide/thumb-3.jpg")}#full-slide i{position:absolute;top:50%;-webkit-transform:translateY(-50%) scale(1);transform:translateY(-50%) scale(1);width:60px;height:60px;font-size:28px;text-align:center;line-height:60px;background-color:#fff;cursor:pointer;z-index:10;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}#full-slide i:hover{-webkit-transform:translateY(-50%) scale(1.1);transform:translateY(-50%) scale(1.1)}#full-slide .prev{left:30px}#full-slide .next{right:30px}@media (max-width: 767px){#full-slide i{-webkit-transform:translateY(-50%) scale(.7);transform:translateY(-50%) scale(.7)}#full-slide i:hover{-webkit-transform:translateY(-50%) scale(.8);transform:translateY(-50%) scale(.8)}#full-slide .prev{left:0}#full-slide .next{right:0}}#featured{position:relative;width:1440px;max-width:95%;height:600px;margin:30px auto;background-color:#efefef;overflow:hidden}@media (max-width: 768px){#featured{margin:15px auto}}#featured .wrapper{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:1180px;max-width:95%;height:100%;margin:0 auto;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}#featured .wrapper .blurb{width:45%}#featured .wrapper .blurb:after{content:"";display:inline-block;width:1px;height:70px;background-color:#111}#featured .wrapper .blurb h2{margin-top:0;margin-bottom:30px;font-size:42px;font-family:"Raleway",Helvetica,sans-serif;font-weight:100}@media (max-width: 640px){#featured .wrapper .blurb h2{margin-bottom:20px;font-size:30px}}#featured .wrapper .blurb p{max-width:400px;margin-bottom:30px}#featured .wrapper .featured{width:55%}#featured .wrapper .featured img{width:100%}@media (max-width: 1023px){#featured .wrapper .blurb{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:end;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end;text-align:right}#featured .wrapper .blurb:after{display:none}#featured .wrapper .blurb p{margin-bottom:0}#featured .wrapper .featured{display:none}}@media (max-width: 640px){#featured .wrapper .blurb{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center;text-align:center}}#three-slide{position:relative;width:1440px;max-width:95%;height:600px;margin:30px auto;background-color:#efefef;overflow:hidden}@media (max-width: 768px){#three-slide{margin:15px auto}}#three-slide .wrapper{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:1180px;max-width:95%;height:100%;margin:0 auto;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}#three-slide .wrapper .slider{position:relative;width:calc(100% - 60px);height:100%;margin:0 auto;padding:0;list-style:none;opacity:1;-webkit-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}#three-slide .wrapper .slider.swap{opacity:0;-webkit-transition:opacity .3s ease-in-out;transition:opacity .3s ease-in-out}#three-slide .wrapper .slider li{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);width:40%;opacity:0}@media (max-width: 900px){#three-slide .wrapper .slider li{width:50%}}#three-slide .wrapper .slider li img{width:100%}#three-slide .wrapper .slider .back{left:0;opacity:1}#three-slide .wrapper .slider .current{left:50%;-webkit-transform:translate(-50%, -50%) scale(1.3);transform:translate(-50%, -50%) scale(1.3);opacity:1;z-index:10}#three-slide .wrapper .slider .front{right:0;opacity:1}@media (max-width: 767px){#three-slide .wrapper .slider{width:calc(100% - 30px)}#three-slide .wrapper .slider li{width:75%}#three-slide .wrapper .slider .back,#three-slide .wrapper .slider .front{display:none}}#three-slide i{position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);width:60px;height:60px;font-size:28px;text-align:center;line-height:60px;background-color:#fff;cursor:pointer;z-index:10;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}#three-slide i:hover{-webkit-transform:translateY(-50%) scale(1.1);transform:translateY(-50%) scale(1.1)}#three-slide .prev{left:30px}#three-slide .next{right:30px}@media (max-width: 767px){#three-slide i{-webkit-transform:translateY(-50%) scale(.7);transform:translateY(-50%) scale(.7)}#three-slide i:hover{-webkit-transform:translateY(-50%) scale(.8);transform:translateY(-50%) scale(.8)}#three-slide .prev{left:0}#three-slide .next{right:0}}#contact{position:relative;width:1440px;max-width:95%;height:600px;margin:30px auto;background-color:#efefef;overflow:hidden}@media (max-width: 768px){#contact{margin:15px auto}}#contact .wrapper{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:1180px;max-width:95%;height:100%;margin:0 auto;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}#contact .wrapper .blurb{max-width:950px;margin-top:-45px;text-align:center}#contact .wrapper .blurb h2{margin-top:0;margin-bottom:30px;font-size:42px;font-family:"Raleway",Helvetica,sans-serif;font-weight:100}@media (max-width: 640px){#contact .wrapper .blurb h2{margin-bottom:20px;font-size:30px}}#contact .wrapper .blurb p{margin-bottom:45px}#contact .wrapper form{position:relative;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;max-width:600px;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;color:#111;font-family:"Raleway",Helvetica,sans-serif}#contact .wrapper form input[type="email"],#contact .wrapper form input[type="text"]{width:42%;height:40px;padding:0 15px;border:1px solid #111;border-radius:0;background:transparent}#contact .wrapper form input[type="email"]::-webkit-input-placeholder,#contact .wrapper form input[type="text"]::-webkit-input-placeholder{color:#111}#contact .wrapper form input[type="email"]::-moz-placeholder,#contact .wrapper form input[type="text"]::-moz-placeholder{color:#111}#contact .wrapper form input[type="email"]:-ms-input-placeholder,#contact .wrapper form input[type="text"]:-ms-input-placeholder{color:#111}#contact .wrapper form input[type="email"]::placeholder,#contact .wrapper form input[type="text"]::placeholder{color:#111}#contact .wrapper form input[type="email"]:focus,#contact .wrapper form input[type="text"]:focus{outline:none}#contact .wrapper form textarea{width:100%;height:90px;margin:30px 0;padding:10px 15px;border:1px solid #111;border-radius:0;background:transparent;resize:none}#contact .wrapper form textarea::-webkit-input-placeholder{color:#111}#contact .wrapper form textarea::-moz-placeholder{color:#111}#contact .wrapper form textarea:-ms-input-placeholder{color:#111}#contact .wrapper form textarea::placeholder{color:#111}#contact .wrapper form textarea:focus{outline:none}#contact .wrapper form input[type="submit"]{width:100%;height:40px;color:#fff;font-weight:700;text-transform:uppercase;border:none;border-radius:0;background-color:#111}@media (max-width: 640px){#contact .wrapper form input[type="email"],#contact .wrapper form input[type="text"]{width:100%;margin-bottom:10px}#contact .wrapper form textarea{margin:0 0 10px 0}}#contact .copyright{position:absolute;bottom:10px;left:50%;-webkit-transform:translate(-50%);transform:translate(-50%);margin-bottom:0;font-family:"Raleway",Helvetica,sans-serif;font-size:12px}
2 |
--------------------------------------------------------------------------------
/assets/css/main.sass:
--------------------------------------------------------------------------------
1 | // This is the single file output by sass. It is intended to ONLY @import other files.
2 |
3 |
4 | // 1 - TOOLS
5 | @import '1-tools/fonts'
6 | @import '1-tools/normalize'
7 | @import '1-tools/icon-font'
8 | @import '1-tools/vars'
9 | @import '1-tools/mixins'
10 |
11 |
12 | // 2 - BASICS
13 | @import '2-basics/body-element'
14 | @import '2-basics/selection-colors'
15 | @import '2-basics/typography'
16 |
17 |
18 | // 3 - Modules
19 | @import '3-modules/letters'
20 | @import '3-modules/nav'
21 |
22 |
23 | // 4 - Sections
24 | @import '4-sections/hero'
25 | @import '4-sections/about'
26 | @import '4-sections/full-slide'
27 | @import '4-sections/featured'
28 | @import '4-sections/three-slide'
29 | @import '4-sections/contact'
30 |
--------------------------------------------------------------------------------
/assets/img/featured.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/img/featured.png
--------------------------------------------------------------------------------
/assets/img/flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/img/flow.png
--------------------------------------------------------------------------------
/assets/img/full-slide/thumb-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/img/full-slide/thumb-1.jpg
--------------------------------------------------------------------------------
/assets/img/full-slide/thumb-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/img/full-slide/thumb-2.jpg
--------------------------------------------------------------------------------
/assets/img/full-slide/thumb-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/img/full-slide/thumb-3.jpg
--------------------------------------------------------------------------------
/assets/img/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/img/hero.png
--------------------------------------------------------------------------------
/assets/img/logos.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/assets/img/logos.png
--------------------------------------------------------------------------------
/assets/js/functions-min.js:
--------------------------------------------------------------------------------
1 | $(document).ready(function(){function e(){$(".nav-toggle").click(function(){$(".nav").toggleClass("open")})}function s(){$('a[href^="#"]').click(function(e){var s=$($(this).attr("href"));s.length&&(e.preventDefault(),$("html, body").animate({scrollTop:s.offset().top-15},300)),$(".nav").toggleClass("open")})}function r(){$("#full-slide .prev, #full-slide .next").click(function(){var e=$(this),s=$(".banner").find(".active"),r=$(".banner").children().index(s),a=$(".banner").children().length;e.hasClass("next")?a-1>r?$(".active").removeClass("active").next().addClass("active"):$(".banner li").removeClass("active").first().addClass("active"):0===r?$(".banner li").removeClass("active").last().addClass("active"):$(".active").removeClass("active").prev().addClass("active")})}function a(){$("#three-slide .prev, #three-slide .next").click(function(){var e=$(this),s=$(".slider").find(".back"),r=$(".slider").children().index(s),a=$(".slider").find(".current"),l=$(".slider").children().index(a),n=$(".slider").find(".front"),t=$(".slider").children().index(n),d=$(".slider").children().length;$(".slider").addClass("swap"),setTimeout(function(){e.hasClass("next")?d-1>t&&d-1>l&&d-1>r?($(".back").removeClass("back").next().addClass("back"),$(".current").removeClass("current").next().addClass("current"),$(".front").removeClass("front").next().addClass("front")):t===d-1?($(".back").removeClass("back").next().addClass("back"),$(".current").removeClass("current").next().addClass("current"),$(".slider li").removeClass("front").first().addClass("front")):l===d-1?($(".back").removeClass("back").next().addClass("back"),$(".slider li").removeClass("current").first().addClass("current"),$(".front").removeClass("front").next().addClass("front")):($(".slider li").removeClass("back").first().addClass("back"),$(".current").removeClass("current").next().addClass("current"),$(".front").removeClass("front").next().addClass("front")):0!==r&&0!==l&&0!==t?($(".back").removeClass("back").prev().addClass("back"),$(".current").removeClass("current").prev().addClass("current"),$(".front").removeClass("front").prev().addClass("front")):0===r?($(".slider li").removeClass("back").last().addClass("back"),$(".current").removeClass("current").prev().addClass("current"),$(".front").removeClass("front").prev().addClass("front")):0===l?($(".back").removeClass("back").prev().addClass("back"),$(".slider li").removeClass("current").last().addClass("current"),$(".front").removeClass("front").prev().addClass("front")):($(".back").removeClass("back").prev().addClass("back"),$(".current").removeClass("current").prev().addClass("current"),$(".slider li").removeClass("front").last().addClass("front")),$(".slider").removeClass("swap")},300)})}e(),s(),r(),a()});
--------------------------------------------------------------------------------
/assets/js/functions.js:
--------------------------------------------------------------------------------
1 | $( document ).ready(function() {
2 |
3 | function nav(){
4 |
5 | $('.nav-toggle').click(function(){
6 |
7 | $('.nav').toggleClass('open');
8 |
9 | });
10 |
11 | }
12 |
13 | function smoothScroll(){
14 |
15 | $('a[href^="#"]').click(function(event){
16 |
17 | var target = $($(this).attr('href'));
18 |
19 | if (target.length){
20 | event.preventDefault();
21 | $('html, body').animate({
22 | scrollTop: target.offset().top - 15
23 | }, 300);
24 | }
25 |
26 | $('.nav').toggleClass('open');
27 |
28 | });
29 |
30 | }
31 |
32 | function fullSlider(){
33 |
34 | $('#full-slide .prev, #full-slide .next').click(function(){
35 |
36 | var $this = $(this),
37 | current = $('.banner').find('.active'),
38 | position = $('.banner').children().index(current),
39 | totalPics = $('.banner').children().length;
40 |
41 | if ($this.hasClass('next')){
42 |
43 | if (position < totalPics - 1){
44 | $('.active').removeClass('active').next().addClass('active');
45 | }
46 |
47 | else {
48 | $('.banner li').removeClass('active').first().addClass('active');
49 | }
50 |
51 | }
52 |
53 | else {
54 |
55 | if (position === 0){
56 | $('.banner li').removeClass('active').last().addClass('active');
57 | }
58 |
59 | else {
60 | $('.active').removeClass('active').prev().addClass('active');
61 | }
62 |
63 | }
64 |
65 | });
66 |
67 | }
68 |
69 | function threeSlider(){
70 |
71 | $('#three-slide .prev, #three-slide .next').click(function(){
72 |
73 | var $this = $(this),
74 | curBack = $('.slider').find('.back'),
75 | posBack = $('.slider').children().index(curBack),
76 | curCurr = $('.slider').find('.current'),
77 | posCurr = $('.slider').children().index(curCurr),
78 | curFront = $('.slider').find('.front'),
79 | posFront = $('.slider').children().index(curFront),
80 | totalPics = $('.slider').children().length;
81 |
82 | $('.slider').addClass('swap');
83 |
84 | setTimeout(function(){
85 |
86 | if ($this.hasClass('next')){
87 |
88 | if (posFront < totalPics - 1 && posCurr < totalPics - 1 && posBack < totalPics - 1){
89 | $('.back').removeClass('back').next().addClass('back');
90 | $('.current').removeClass('current').next().addClass('current');
91 | $('.front').removeClass('front').next().addClass('front');
92 | }
93 |
94 | else {
95 |
96 | if (posFront === totalPics - 1){
97 | $('.back').removeClass('back').next().addClass('back');
98 | $('.current').removeClass('current').next().addClass('current');
99 | $('.slider li').removeClass('front').first().addClass('front');
100 | }
101 |
102 | else if (posCurr === totalPics - 1){
103 | $('.back').removeClass('back').next().addClass('back');
104 | $('.slider li').removeClass('current').first().addClass('current');
105 | $('.front').removeClass('front').next().addClass('front');
106 | }
107 |
108 | else {
109 | $('.slider li').removeClass('back').first().addClass('back');
110 | $('.current').removeClass('current').next().addClass('current');
111 | $('.front').removeClass('front').next().addClass('front');
112 | }
113 |
114 | }
115 |
116 | }
117 |
118 | else {
119 |
120 | if (posBack !== 0 && posCurr !== 0 && posFront !== 0){
121 | $('.back').removeClass('back').prev().addClass('back');
122 | $('.current').removeClass('current').prev().addClass('current');
123 | $('.front').removeClass('front').prev().addClass('front');
124 | }
125 |
126 | else {
127 |
128 | if (posBack === 0){
129 | $('.slider li').removeClass('back').last().addClass('back');
130 | $('.current').removeClass('current').prev().addClass('current');
131 | $('.front').removeClass('front').prev().addClass('front');
132 | }
133 |
134 | else if (posCurr === 0){
135 | $('.back').removeClass('back').prev().addClass('back');
136 | $('.slider li').removeClass('current').last().addClass('current');
137 | $('.front').removeClass('front').prev().addClass('front');
138 | }
139 |
140 | else {
141 | $('.back').removeClass('back').prev().addClass('back');
142 | $('.current').removeClass('current').prev().addClass('current');
143 | $('.slider li').removeClass('front').last().addClass('front');
144 | }
145 |
146 | }
147 |
148 | }
149 |
150 | $('.slider').removeClass('swap');
151 |
152 | }, 300);
153 |
154 | });
155 |
156 | }
157 |
158 | nav();
159 |
160 | smoothScroll();
161 |
162 | fullSlider();
163 |
164 | threeSlider();
165 |
166 | });
167 |
--------------------------------------------------------------------------------
/images/ipysig_diagram1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/images/ipysig_diagram1.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Sigma-NetworkX-Jupyter
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
23 |
24 |
25 |
IPySigma: a network visualization frontend for Jupyter notebooks
26 |
Create beautiful networks
27 |
An experiment from John DeBlase and Daina Bouquin
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
About IPySigma
37 |
Network analytics using tools like NetworkX and Jupyter often leave programmers with difficult to examine hairballs rather than useful visualizations. Meanwhile, more flexible tools like SigmaJS have high learning curves for people new to Javascript. This extensible framework can help you create beautiful SigmaJS graphs, without ditching your Jupyter notebook. IPySigma makes use of the following: Jupyter , Loki , SigmaJS , NetworkX . Check out the code on GitHub
Meet John and Daina
43 |
44 |
45 |
46 |
53 |
54 |
55 |
56 |
How It Works
57 |
[instert description of all components with links]
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/ipysig/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = '0.1.4'
2 |
3 | import logging
4 | import logging.config
5 | import sys
6 |
7 |
8 | logging.basicConfig(stream=sys.stdout, level=logging.INFO, disable_existing_loggers=False)
9 |
10 | from .core import IPySig
11 |
--------------------------------------------------------------------------------
/ipysig/core.py:
--------------------------------------------------------------------------------
1 | # prototype API for IPySig networkx-sigmajs app
2 |
3 | from __future__ import print_function
4 | from __future__ import division
5 |
6 | from socketIO_client import SocketIO, BaseNamespace
7 |
8 | import os
9 | import sys
10 | import re
11 | import webbrowser as web
12 | import subprocess as sbp
13 | import time
14 | import json
15 |
16 | import networkx as nx
17 |
18 | # package imports
19 | from ipysig.sigma_addon_methods import *
20 | from ipysig.exceptions import *
21 |
22 |
23 | class Singleton(type):
24 | '''
25 | A singleton metaclass for the IpySig controller
26 |
27 | '''
28 | _inst = {}
29 |
30 | def __call__(cls, *args, **kwargs):
31 |
32 | if cls not in cls._inst:
33 | cls._inst[cls] = super(Singleton, cls).__call__(*args,**kwargs)
34 |
35 | return cls._inst[cls]
36 |
37 |
38 |
39 | class PyNamespace(BaseNamespace):
40 | '''
41 | SocketIO.client namespace. callback initializes a browser
42 | '''
43 | # callback established here
44 | def on_pyconnect_response(self, package):
45 |
46 | print('Node received: ' + package['echo_name']) # callback from
47 | web.open_new_tab('http://localhost:'+ package['http_port']) #TODO: check package for bad url
48 | time.sleep(1) # possibly not needed
49 |
50 |
51 | class IPySig(object):
52 | '''
53 | Main graph controller object -- keeper of the express server process
54 |
55 | This is a Singleton instance. References to graphs are kept in the _store dict and called by node via a class method
56 |
57 | Example:
58 | g = nx.Graph()
59 | h = nx.Graph()
60 |
61 | ctrl = IPySig('path/to/nodeapp') # load express, init datastore and inject nx.Graph object
62 | ctrl.connect(g, 'key_name') # sets key name and graph instance in datastore, spins up the browser and passes this data to sigma app
63 | ctrl.connect(h, 'key_name')
64 | '''
65 |
66 | __metaclass__ = Singleton # makes IPySig a singleton object
67 |
68 | _store = {} # store all instances keyed by name -- possibly used ordered dict so that node
69 | url = None # url and token of currently running notebook server
70 | token = None
71 | exp_process = None # the express process
72 | activated = False # sanity check for _sigma_api_injection call
73 |
74 |
75 | def __init__(self, node_path):
76 | # one time initialization
77 | self.node_path = node_path # TODO: error check path for validity and possibly to see if the app is listed in the proper directory
78 |
79 | if not self.__class__.activated:
80 | self._sigma_api_injector() # this injects our api into the networkx Graph class
81 | self.__class__.activated = True # Sigma networkx API is already activated - bypasses the injector
82 |
83 | if self.__class__.url is None:
84 | self._get_url_oneserver() # gets the url ## TODO Catch attribute errors if no Notebook server is running
85 |
86 | if self.__class__.exp_process is None:
87 | self.init_express() # threading and subprocess calls run_node if subprocess not running
88 |
89 | #Anything node interacts with for IPySig objects must be a class method
90 | #class methods to help interact with globals: store{}, url, and token
91 |
92 | @classmethod
93 | def export_graph_instance(cls, name): # TODO will need to check for key errors
94 | cls._store[name].sigma_make_graph() # TODO error checking
95 | export = cls._store[name].sigma_export_json() # TODO error checking
96 |
97 | return export
98 |
99 |
100 | def connect(self, graph, key_name):
101 | '''
102 | PUBLIC - stores a graph/key_name and emits those to a running node express server
103 | :CALLS: _emit_instance_name()
104 |
105 | '''
106 | self.__class__._store[key_name] = graph # TODO:: error check
107 | self._emit_instance_name(key_name) # send instance name and bind with browser socket id
108 |
109 |
110 | def disconnect(self, key_name):
111 | '''
112 | PUBLIC
113 |
114 | :TODO
115 | :FIXME
116 | public method that disconnects the graph from the app
117 | -> this would require another emit using the py-room namespace method call as well as clean up code for the _store dict
118 | -> a callback could also be emitted here
119 |
120 | '''
121 | pass
122 |
123 | def _get_url_oneserver(self):
124 | '''
125 | gets url and token if any and sets them... run once - need to error check here
126 | current implementation hard coded to work with only one running server
127 | '''
128 | # TODO: error checking target string
129 | out = sbp.check_output(['jupyter','notebook','list']).split('\n')[1].split(' ')[0] # pulls out single url
130 | self.__class__.url = re.match(r'http://.+/',out).group()
131 | token = re.search(r'(?<=token=).+',out)
132 |
133 | if token:
134 | self.__class__.token = token.group()
135 |
136 | def init_express(self):
137 | '''
138 | PUBLIC
139 | calls the express app using _run_node().. not sure if this should be on a new thread..
140 |
141 | '''
142 | if self.__class__.exp_process is None:
143 |
144 | self.__class__.exp_process = self._run_node() # TODO:: error check
145 | print('loading express... ')
146 | time.sleep(1)
147 | print('IPySig express has started... PID: '+ str(self.__class__.exp_process.pid))
148 |
149 | else:
150 | print('IPySIg is already running... PID: ' + str(self.__class__.exp_process))
151 |
152 | def _emit_instance_name(self, key_name):
153 | '''
154 | opens a socket and emits the user given instance name to express app
155 | CALLED BY: connect()
156 |
157 | '''
158 | with SocketIO('localhost', 3000) as socket:
159 | py_namespace = socket.define(PyNamespace, '/py')
160 | py_namespace.emit('py-object-name', key_name)
161 | socket.wait(seconds=1) # blocks for one second to wait for callbacks
162 |
163 |
164 | def _run_node(self):
165 | '''
166 | Runs the node express script with command arguments for baseUrl and token
167 |
168 | :CALLED BY: _init_express()
169 | :RETURNS: a subprocess with the node command running or None if fail
170 |
171 | '''
172 | express_app = None
173 |
174 | node_command = ['node', self.node_path +'/index.js', '--baseUrl', self.__class__.url]
175 | if self.__class__.token:
176 | node_command.append('--token={}'.format(self.__class__.token)) # attach if running notebook has token (4.2.3+)
177 |
178 | print(' '.join(node_command))
179 | express_app = sbp.Popen(node_command, stdout=sbp.PIPE, bufsize=1, universal_newlines=True) # TODO:: error check
180 |
181 | return express_app
182 |
183 |
184 | def kill_express_process(self):
185 |
186 | #if IPySig.exp_process is not None or IPySig.exp_process.poll() is None:
187 |
188 | if self.__class__.exp_process is not None:
189 | self.__class__.exp_process.kill()
190 | print(str(self.__class__.exp_process.pid) + ' has been killed')
191 |
192 | self.__class__.exp_process = None
193 | else:
194 | print('There is currently no IPySig.exp_process running')
195 |
196 |
197 | def _sigma_api_injector(self):
198 | '''
199 | injects sigma API methods into the nx.Graph class
200 | CALLED BY: __init__()
201 | '''
202 | nx.Graph.sigma_make_graph = sigma_make_graph
203 | nx.Graph.sigma_export_json = sigma_export_json
204 |
205 | nx.Graph.sigma_add_degree_centrality = sigma_add_degree_centrality
206 | nx.Graph.sigma_add_betweenness_centrality = sigma_add_betweenness_centrality
207 | nx.Graph.sigma_add_pagerank = sigma_add_pagerank
208 |
209 | nx.Graph.sigma_node_add_extra = sigma_node_add_extra
210 | nx.Graph.sigma_choose_edge_color = sigma_choose_edge_color
211 | nx.Graph.sigma_build_pandas_dfs = sigma_build_pandas_dfs
212 | nx.Graph.sigma_edge_weights = sigma_edge_weights
213 |
214 | nx.Graph.sigma_color_picker = sigma_color_picker
215 | nx.Graph.sigma_assign_node_colors = sigma_assign_node_colors
216 |
217 | nx.Graph.sigma_node_add_color_node_type = sigma_node_add_color_node_type
218 | nx.Graph.sigma_node_add_label = sigma_node_add_label
219 |
220 |
--------------------------------------------------------------------------------
/ipysig/exceptions.py:
--------------------------------------------------------------------------------
1 | '''
2 | custom exceptions for IPySigma application
3 |
4 | '''
5 |
6 | class IPySigmaBaseException(Exception):
7 | '''
8 | exception base class for IPySigmaPackage
9 | '''
10 | pass
11 |
12 |
13 | class IPySigmaAddOnException(IPySigmaBaseException):
14 | '''
15 | exception base class for sigma_add_on module
16 | '''
17 | pass
18 |
19 |
20 | class IPySigmaCoreException(IPySigmaBaseException):
21 | '''
22 | exception base class for SigmaCore module
23 | '''
24 | pass
25 |
26 |
27 | class IPySigmaGraphNodeIndexError(IPySigmaAddOnException, IndexError):
28 | '''
29 | exception thrown when graphs are empty
30 | '''
31 | pass
32 |
33 | class IPySigmaGraphEdgeIndexError(IPySigmaAddOnException, IndexError):
34 | '''
35 | exception thrown when graphs are empty
36 | '''
37 | pass
38 |
39 |
40 | class IPySigmaGraphDataFrameValueError(IPySigmaAddOnException, ValueError):
41 | '''
42 | exception thrown when build_pandas_df method returns empty dfs
43 | '''
44 | pass
45 |
46 | class IPySigmaGraphJSONValueError(IPySigmaAddOnException, ValueError):
47 | '''
48 | exception thrown if JSON dict is empty
49 | '''
50 | pass
51 |
52 |
53 | class IPySigmaNodeTypeError(IPySigmaAddOnException):
54 | '''
55 | exception thrown if 'node_type' field has not been assigned by user to node attributes
56 | '''
57 |
58 | class IPySigmaLabelError(IPySigmaAddOnException):
59 | '''
60 | exception thrown if 'label' field has not been assigned by the user to node attributes
61 | '''
--------------------------------------------------------------------------------
/ipysig/sigma_addon_methods.py:
--------------------------------------------------------------------------------
1 | '''
2 | this is a module of add-on methods that get injected into the nx.Graph class within IPySig
3 | Sigma calls these on graph object instances stored in the IPySig data store
4 |
5 | '''
6 | import pandas as pd
7 | import networkx as nx
8 | from numpy import cos, sin, pi
9 | from numpy.random import randint
10 |
11 | import json
12 |
13 | import logging
14 | logger = logging.getLogger(__name__)
15 |
16 | from exceptions import IPySigmaGraphNodeIndexError, \
17 | IPySigmaGraphEdgeIndexError, IPySigmaGraphDataFrameValueError, \
18 | IPySigmaGraphJSONValueError, IPySigmaNodeTypeError, IPySigmaLabelError
19 |
20 |
21 | #PUBLIC API
22 |
23 | def sigma_make_graph(self):
24 | '''
25 | A method that serves as a rbuild script for the sigma add on methods
26 | adds centrality measures , and extra attributes that sigma needs for node and edgelists
27 | '''
28 | try:
29 | self.sigma_add_degree_centrality()
30 | self.sigma_add_pagerank()
31 | self.sigma_add_betweenness_centrality()
32 |
33 | self.sigma_nodes, self.sigma_edges = self.sigma_build_pandas_dfs() # makes df and assigns sigma_nodes and sigma_edges instance variables
34 |
35 |
36 | if len(self.sigma_nodes.index) == 0 or len(self.sigma_nodes.columns) == 0:
37 | raise IPySigmaGraphDataFrameValueError('Sigma Node and Edge dataframes are empty')
38 |
39 | except IPySigmaGraphDataFrameValueError as err:
40 | logger.exception("Sigma Node and Edge dataframes are empty ")
41 | raise
42 |
43 | #NOTE these are handled as warnings
44 | try:
45 | self.sigma_node_add_color_node_type()
46 |
47 | except IPySigmaNodeTypeError as err:
48 | logger.warn('No node_type field detected for node attributes: using default color for all nodes')
49 |
50 | try:
51 | self.sigma_node_add_label()
52 |
53 | except IPySigmaLabelError as err:
54 | logger.warn('No label field detected for node attributes: using node id for node label')
55 |
56 |
57 | self.sigma_node_add_extra()
58 | self.sigma_edge_weights()
59 |
60 |
61 | def sigma_export_json(self):
62 | '''
63 | sets and returns obj dictionary with node and edge list arrays(sigma compatible)
64 | assuming all goes well above, this method returns the sigma_json graph object
65 | '''
66 |
67 |
68 | try:
69 |
70 | sigma_obj = { 'error': 'false', 'graph': {} }
71 | sigma_obj['graph']['nodes'] = self.sigma_nodes.to_dict('records')
72 | sigma_obj['graph']['edges'] = self.sigma_edges.to_dict('records')
73 |
74 | if len(sigma_obj['graph']['nodes']) == 0:
75 | raise IPySigmaGraphJSONValueError(' Sigma JSON Export Error ')
76 |
77 |
78 | except AttributeError as err:
79 | logger.warning('Sigma Node and Edges have not been set yet: make sure G.sigma_make_graph() has been called first')
80 | sigma_obj['error'] = 'true'
81 |
82 |
83 | except IPySigmaGraphJSONValueError as err:
84 | logger.warning('Sigma JSON object node and edgelists are empty')
85 | sigma_obj['error'] = 'true'
86 |
87 |
88 | return json.dumps(sigma_obj)
89 |
90 |
91 | def sigma_add_degree_centrality(self):
92 | dc = nx.degree_centrality(self)
93 | nx.set_node_attributes(self, 'sigma_deg_central',dc)
94 |
95 |
96 | def sigma_add_betweenness_centrality(self):
97 | bc = nx.betweenness_centrality(self)
98 | nx.set_node_attributes(self,'sigma_between_central',bc)
99 |
100 |
101 | def sigma_add_pagerank(self):
102 | pr = nx.pagerank(self)
103 | nx.set_node_attributes(self, 'sigma_pagerank',pr)
104 |
105 |
106 | def sigma_build_pandas_dfs(self):
107 | '''
108 | dumps graph data into pandas dfs... returns a node and edge dataframe
109 | for nodes just need to dump attribute dictionary
110 |
111 | Throws index errors if node or edgelists are blank
112 |
113 | :returns: self.sigma_nodes, self.sigma_edges
114 | '''
115 |
116 | try:
117 |
118 | nodelist = self.nodes(data=True)
119 |
120 | if len(nodelist) == 0:
121 | raise IPySigmaGraphNodeIndexError('nx.Graph.nodes list is empty')
122 |
123 | nodes = {}
124 |
125 | nodes['id'] = [i[0] for i in nodelist]
126 | for k in nodelist[0][1].keys(): # the api always injects at least centrality measuers
127 | nodes[k] = [i[1][k] for i in nodelist]
128 |
129 | e = self.edges(data=True)
130 |
131 | if len(e) == 0:
132 | raise IPySigmaGraphEdgeIndexError('nx.Graph.edges list is empty')
133 |
134 | edgelist = [0 for i in xrange(len(self.edges()))] # init for optimization (large edgelists)
135 |
136 | for i in range(len(edgelist)):
137 | edgelist[i] = {'id': i, 'source':e[i][0], 'target':e[i][1]}
138 | if (len(e[i][2]) != 0): # if edge weight exists
139 | edgelist[i]['weight'] = e[i][2]['weight']
140 |
141 |
142 | except (IPySigmaGraphNodeIndexError, IPySigmaGraphEdgeIndexError) as err:
143 | logger.error("Node and Edge lists of nx.Graph object have been left blank ")
144 | raise
145 |
146 |
147 | sigma_edges = pd.DataFrame(edgelist) # these are the pandas dfs that will get exported
148 | sigma_nodes = pd.DataFrame(nodes)
149 |
150 | return sigma_nodes, sigma_edges
151 |
152 |
153 |
154 |
155 | def sigma_node_add_extra(self):
156 |
157 | # adds extra stuff to the nodes list needed for sigma
158 | node_len = len(self.sigma_nodes)
159 |
160 | # spreads nodes in a circle for forceAtlas2
161 | x = 10 * cos(2 * randint(0,node_len,node_len) * pi/node_len)
162 | y = 10 * sin(2 * randint(0,node_len,node_len) * pi/node_len)
163 |
164 | # stuff for sigma nodes
165 | self.sigma_nodes['x'] = x
166 | self.sigma_nodes['y'] = y
167 | self.sigma_nodes['size'] = 0.25 + 15 * self.sigma_nodes['sigma_deg_central'] # set to degree central
168 |
169 |
170 |
171 | def sigma_node_add_color_node_type(self):
172 | '''
173 | adds a color field based on a node_type column:
174 | if node_type is not present a default color is chosen, node_type field is set to None and an error is raised.
175 | '''
176 |
177 | default_node_color = '#79a55c'
178 |
179 | try:
180 | if 'node_type' not in self.sigma_nodes:
181 | raise IPySigmaNodeTypeError
182 |
183 | # clean up
184 | self.sigma_nodes['node_type'] = self.sigma_nodes['node_type'].str.replace(r"'", "") # a single quote throws off json parser
185 | self.sigma_nodes['node_type'].fillna(self.sigma_nodes['node_type'],inplace=True)
186 |
187 | colors = self.sigma_color_picker()
188 | self.sigma_nodes['color'] = self.sigma_nodes.apply(self.sigma_assign_node_colors, args=(colors,), axis=1)
189 |
190 | except IPySigmaNodeTypeError as err:
191 | self.sigma_nodes['node_type'] = 'undefined node_type'
192 | self.sigma_nodes['color'] = default_node_color
193 | raise
194 |
195 |
196 |
197 | def sigma_node_add_label(self):
198 | '''
199 | adds a label field to sigma_nodes:
200 | if one is not present an error is thrown and caught as warning in build and the id field is used
201 | '''
202 | try:
203 | if 'label' not in self.sigma_nodes: # assigns the label field to id if label is not provided
204 | raise IPySigmaLabelError
205 |
206 | #clean up labels
207 | self.sigma_nodes['label'] = self.sigma_nodes['label'].str.replace(r"'", "")
208 | self.sigma_nodes['label'].fillna(self.sigma_nodes['id'],inplace=True)
209 |
210 | except IPySigmaLabelError as err:
211 | self.sigma_nodes['label'] = self.sigma_nodes['id']
212 | raise
213 |
214 |
215 | def sigma_color_picker(self):
216 | '''
217 | helper method that picks colors based on node_type column
218 | :RETURNS: a dict of assigned node colors
219 | '''
220 | if 'node_type' not in self.sigma_nodes:
221 | return
222 |
223 | # assign colors based for each unique value in node_type
224 | node_type_vals_list = self.sigma_nodes['node_type'].unique().tolist()
225 | node_type_color_dict = {}
226 |
227 | for node_type in node_type_vals_list:
228 | r = lambda: randint(0,255)
229 | color = '#%02X%02X%02X' % (r(),r(),r())
230 | node_type_color_dict[node_type] = color
231 |
232 | return node_type_color_dict
233 |
234 |
235 | def sigma_assign_node_colors(self, row, color_dict):
236 | '''
237 | callback for sigma color picker apply method
238 | '''
239 | return color_dict[row['node_type']]
240 |
241 |
242 |
243 | def sigma_edge_weights(self):
244 | '''
245 | if edges have weight attribute change column name to size
246 | '''
247 |
248 | if 'weight' in self.sigma_edges.columns:
249 | self.sigma_edges['size'] = self.sigma_edges['weight'] * 2
250 | # color code edges
251 | self.sigma_edges['color'] = self.sigma_edges.apply(self.sigma_choose_edge_color, axis=1)
252 | else:
253 | self.sigma_edges['color'] = '#d3d3d3' # grey if not weighted
254 |
255 |
256 | def sigma_choose_edge_color(self,row):
257 | '''
258 | callback for sigma_edge_weights
259 | bins the color weight and returns a darker hex value as it increases
260 | '''
261 |
262 | if row['weight'] == 1:
263 | return 'rgba(211, 211, 211, 0.1)'
264 | if row['weight'] >= 2 and row['weight'] < 5:
265 | return 'rgba(214, 50, 50, 0.7)'
266 | if row['weight'] >= 5:
267 | return 'rgba(112, 0, 0, 0.95)'
268 |
269 |
270 |
271 |
--------------------------------------------------------------------------------
/ipysig/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bsnacks000/IPySigma-Demo/8638682347442d08e80111e1bb68ac9babf0db99/ipysig/tests/__init__.py
--------------------------------------------------------------------------------
/ipysig/tests/mocks.py:
--------------------------------------------------------------------------------
1 | '''
2 | A module with some mock classes that can or have been used for testing/debugging
3 | '''
4 | import json
5 | import networkx as nx
6 | from ipysig.sigma_addon_methods import *
7 |
8 |
9 | class DummyGraph(object):
10 | ''' simulates storage of a JSON graph.. method gets called and sends to node server'''
11 | _store = {}
12 |
13 | def __init__(self):
14 | self.graph = {
15 | "graph": {
16 | "nodes": [{"id": "n0","label": "A node","x": 0,"y": 0,"size": 3},{"id": "n1","label": "Another node","x": 3,"y": 1,"size": 2},{"id": "n2","label": "And a last one","x": 1,"y": 3,"size": 1}],
17 | "edges": [{"id": "e0","source": "n0","target": "n1"},{"id": "e1","source": "n1","target": "n2"},{"id": "e2","source": "n2","target": "n0"}]
18 | }
19 | }
20 | self.name = 'dumb_graph'
21 | DummyGraph._store[self.name] = self
22 |
23 | @classmethod
24 | def get_graph(cls, name):
25 | return json.dumps(cls._store[name].graph)
26 |
27 |
28 | class InjectedGraph(object):
29 | '''
30 | returns an injected graph for development testing if not already done
31 |
32 | '''
33 |
34 | def __new__(cls, g=None):
35 |
36 | cls.g = g or nx.Graph()
37 |
38 | if not hasattr(cls.g,'sigma_make_graph'):
39 |
40 | nx.Graph.sigma_make_graph = sigma_make_graph
41 | nx.Graph.sigma_export_json = sigma_export_json
42 |
43 | nx.Graph.sigma_add_degree_centrality = sigma_add_degree_centrality
44 | nx.Graph.sigma_add_betweenness_centrality = sigma_add_betweenness_centrality
45 | nx.Graph.sigma_add_pagerank = sigma_add_pagerank
46 |
47 | nx.Graph.sigma_node_add_extra = sigma_node_add_extra
48 | nx.Graph.sigma_choose_edge_color = sigma_choose_edge_color
49 | nx.Graph.sigma_build_pandas_dfs = sigma_build_pandas_dfs
50 | nx.Graph.sigma_edge_weights = sigma_edge_weights
51 |
52 | return cls.g
--------------------------------------------------------------------------------
/ipysig/tests/test_core.py:
--------------------------------------------------------------------------------
1 | '''
2 | nose tests for ipysig.core.py
3 |
4 | '''
5 |
6 | import unittest
7 | import os
8 | import networkx as nx
9 |
10 | from ..core import IPySig
11 |
12 |
13 | #TODO mock out system calls and express process for tests
14 |
15 | import unittest
16 | from mock import patch
17 |
18 | from ..core import IPySig
19 |
20 |
21 | test_path = os.path.abspath(os.path.join('..','..','app'))
22 |
23 | class TestIPySig(unittest.TestCase):
24 |
25 |
26 | @patch('ipysig.IPySig._get_url_oneserver', return_value='bypassed')
27 | @patch('ipysig.IPySig.init_express',return_value='bypassed')
28 | def test_IPySig_is_singleton(self,mock_get_url_oneserver,mock_init_express):
29 |
30 |
31 | ctrl = IPySig(test_path)
32 | ctrl2 = IPySig(test_path)
33 |
34 | self.assertTrue(ctrl is ctrl2)
35 |
36 |
37 | @patch('ipysig.IPySig._get_url_oneserver', return_value='bypassed')
38 | @patch('ipysig.IPySig.init_express',return_value='bypassed')
39 | def test_IPySig_set_to_true(self, mock_get_url_oneserver,mock_init_express):
40 |
41 | ctrl = IPySig(test_path)
42 | self.assertTrue(IPySig.activated)
43 |
44 |
45 | @patch('ipysig.IPySig._get_url_oneserver', return_value='bypassed')
46 | @patch('ipysig.IPySig.init_express',return_value='bypassed')
47 | def test_sigma_api_injector(self, mock_get_url_oneserver,mock_init_express):
48 |
49 | ctrl = IPySig(test_path)
50 | g = nx.Graph()
51 |
52 | self.assertTrue(hasattr(g, 'sigma_make_graph'))
53 | self.assertTrue(hasattr(g, 'sigma_export_json' ))
54 | self.assertTrue(hasattr(g, 'sigma_add_degree_centrality' ))
55 | self.assertTrue(hasattr(g, 'sigma_add_betweenness_centrality' ))
56 | self.assertTrue(hasattr(g, 'sigma_add_pagerank' ))
57 | self.assertTrue(hasattr(g, 'sigma_node_add_extra' ))
58 | self.assertTrue(hasattr(g, 'sigma_choose_edge_color'))
59 | self.assertTrue(hasattr(g, 'sigma_build_pandas_dfs'))
60 | self.assertTrue(hasattr(g, 'sigma_edge_weights'))
61 |
--------------------------------------------------------------------------------
/ipysig/tests/test_sigma_addon_methods.py:
--------------------------------------------------------------------------------
1 | '''
2 | nose tests for ipysig.sigma_addon_methods.py
3 | '''
4 |
5 | import unittest
6 | import networkx as nx
7 | import pandas as pd
8 | import json
9 |
10 | from ..sigma_addon_methods import *
11 | from ..exceptions import IPySigmaGraphDataFrameValueError, \
12 | IPySigmaGraphEdgeIndexError, IPySigmaGraphNodeIndexError, \
13 | IPySigmaNodeTypeError, IPySigmaLabelError
14 | from .mocks import InjectedGraph
15 |
16 | import logging
17 | logger = logging.getLogger(__name__)
18 |
19 |
20 | class TestSigmaAddOns(unittest.TestCase):
21 |
22 | @classmethod
23 | def setUpClass(cls):
24 | nx.Graph.sigma_make_graph = sigma_make_graph
25 | nx.Graph.sigma_export_json = sigma_export_json
26 |
27 | nx.Graph.sigma_add_degree_centrality = sigma_add_degree_centrality
28 | nx.Graph.sigma_add_betweenness_centrality = sigma_add_betweenness_centrality
29 | nx.Graph.sigma_add_pagerank = sigma_add_pagerank
30 |
31 | nx.Graph.sigma_node_add_extra = sigma_node_add_extra
32 | nx.Graph.sigma_choose_edge_color = sigma_choose_edge_color
33 | nx.Graph.sigma_build_pandas_dfs = sigma_build_pandas_dfs
34 | nx.Graph.sigma_edge_weights = sigma_edge_weights
35 |
36 | nx.Graph.sigma_node_add_color_node_type = sigma_node_add_color_node_type
37 | nx.Graph.sigma_node_add_label = sigma_node_add_label
38 |
39 | nx.Graph.sigma_color_picker = sigma_color_picker
40 | nx.Graph.sigma_assign_node_colors = sigma_assign_node_colors
41 |
42 | def setUp(self):
43 |
44 | self.graph = nx.complete_graph(10)
45 | self.weighted_graph =nx.Graph()
46 |
47 | self.weighted_graph.add_edge('a','b',weight=0.6)
48 | self.weighted_graph.add_edge('a','c',weight=0.2)
49 | self.weighted_graph.add_edge('c','d',weight=0.1)
50 | self.weighted_graph.add_edge('c','e',weight=0.7)
51 |
52 | self.graph_attr = nx.Graph()
53 | self.graph_attr.add_edge(0,1)
54 | self.graph_attr.add_edge(1,2)
55 | self.graph_attr.add_edge(3,0)
56 | self.graph_attr.add_node(0,{'node_type':'A', 'label':'node0'})
57 | self.graph_attr.add_node(1,{'node_type':'A', 'label':'node1'})
58 | self.graph_attr.add_node(2,{'node_type': 'B', 'label':'node2'})
59 | self.graph_attr.add_node(3,{'node_type': 'C', 'label':'node2'})
60 |
61 |
62 |
63 | def tearDown(self):
64 | self.graph = None
65 | self.weighted_graph = None
66 | self.graph_attr = None
67 |
68 | def test_make_graph(self):
69 |
70 | self.graph.sigma_make_graph()
71 | self.assertTrue(hasattr(self.graph, 'sigma_edges'))
72 | self.assertTrue(hasattr(self.graph, 'sigma_nodes'))
73 |
74 |
75 | def test_degree_centrality_is_added_to_graph_obj(self):
76 |
77 | self.graph.sigma_add_degree_centrality()
78 | node_data = self.graph.nodes(data=True)
79 |
80 | for node in node_data:
81 | self.assertIn('sigma_deg_central',node[1])
82 |
83 |
84 | def test_betweenness_centrality_is_added_to_graph_obj(self):
85 |
86 | self.graph.sigma_add_betweenness_centrality()
87 | node_data = self.graph.nodes(data=True)
88 |
89 | for node in node_data:
90 | self.assertIn('sigma_between_central',node[1])
91 |
92 |
93 | def test_pagerank_is_added_to_graph_obj(self):
94 |
95 | self.graph.sigma_add_pagerank()
96 | node_data = self.graph.nodes(data=True)
97 |
98 | for node in node_data:
99 | self.assertIn('sigma_pagerank',node[1])
100 |
101 |
102 | def test_sigma_build_pandas_dfs_edges_nodes_exist(self):
103 |
104 | self.graph.sigma_nodes, self.graph.sigma_edges = self.graph.sigma_build_pandas_dfs()
105 |
106 | self.assertTrue(hasattr(self.graph, 'sigma_edges'))
107 | self.assertTrue(hasattr(self.graph, 'sigma_nodes'))
108 |
109 | self.weighted_graph.sigma_nodes, self.weighted_graph.sigma_edges = self.weighted_graph.sigma_build_pandas_dfs()
110 |
111 | self.assertTrue(hasattr(self.weighted_graph, 'sigma_edges'))
112 | self.assertTrue(hasattr(self.weighted_graph, 'sigma_nodes'))
113 |
114 |
115 | def test_sigma_build_pandas_dfs_edges_nodes_not_none(self):
116 |
117 | self.graph.sigma_nodes, self.graph.sigma_edges = self.graph.sigma_build_pandas_dfs()
118 |
119 | self.assertIsNotNone(self.graph.sigma_nodes)
120 | self.assertIsNotNone(self.graph.sigma_edges)
121 |
122 | self.weighted_graph.sigma_nodes, self.weighted_graph.sigma_edges = self.weighted_graph.sigma_build_pandas_dfs()
123 |
124 | self.assertIsNotNone(self.weighted_graph.sigma_nodes)
125 | self.assertIsNotNone(self.weighted_graph.sigma_edges)
126 |
127 |
128 | def test_extras_are_added_to_graph_obj(self):
129 |
130 | self.graph.sigma_make_graph()
131 |
132 | self.assertIn('y',self.graph.sigma_nodes)
133 | self.assertIn('color',self.graph.sigma_nodes)
134 | self.assertIn('size',self.graph.sigma_nodes)
135 | self.assertIn('x', self.graph.sigma_nodes)
136 |
137 |
138 | def test_edge_weight_color_size_are_set_if_exist(self):
139 |
140 | self.weighted_graph.sigma_nodes, self.weighted_graph.sigma_edges = self.weighted_graph.sigma_build_pandas_dfs()
141 | self.weighted_graph.sigma_edge_weights()
142 |
143 | self.assertIn('size', self.weighted_graph.sigma_edges)
144 | self.assertIn('color', self.weighted_graph.sigma_edges)
145 |
146 |
147 | def test_sigma_node_df_has_id(self):
148 |
149 | self.graph.sigma_nodes, self.graph.sigma_edges = self.graph.sigma_build_pandas_dfs()
150 | self.assertIn('id',self.graph.sigma_nodes)
151 |
152 |
153 | def test_sigma_make_graph_produces_neccesary_columns(self):
154 |
155 | correct_nodes = ['id','x','y','label','node_type','color','size', 'sigma_deg_central', 'sigma_pagerank', 'sigma_between_central']
156 | correct_edges = ['id','source', 'target', 'color']
157 | correct_edges_weighted = ['id', 'source','target','color','weight','size']
158 |
159 | self.graph.sigma_make_graph()
160 | self.weighted_graph.sigma_make_graph()
161 |
162 | node_col_list = self.graph.sigma_nodes.columns.tolist()
163 | edge_col_list = self.graph.sigma_edges.columns.tolist()
164 | weight_edge_col_list = self.weighted_graph.sigma_edges.columns.tolist()
165 |
166 | for n in node_col_list:
167 | self.assertIn(n, correct_nodes)
168 |
169 | for e in edge_col_list:
170 | self.assertIn(e, correct_edges)
171 |
172 | for w in weight_edge_col_list:
173 | self.assertIn(w, correct_edges_weighted)
174 |
175 |
176 | def test_sigma_node_df_contains_additional_columns_if_available(self):
177 |
178 | nx.set_node_attributes(self.graph, 'some_attr', 'xxx')
179 |
180 | self.graph.sigma_make_graph()
181 |
182 | self.assertIn('some_attr',self.graph.sigma_nodes)
183 |
184 |
185 | def test_sigma_build_pandas_dfs_raise_node_index_error_if_blank(self):
186 |
187 | blank_graph = InjectedGraph()
188 | self.assertRaises(IPySigmaGraphNodeIndexError, blank_graph.sigma_make_graph)
189 |
190 |
191 | def test_sigma_build_pandas_dfs_raise_edge_index_error_if_blank(self):
192 |
193 | blank_graph = InjectedGraph()
194 | blank_graph.add_nodes_from(['a','b','c'])
195 | self.assertRaises(IPySigmaGraphEdgeIndexError, blank_graph.sigma_make_graph)
196 |
197 |
198 | def test_sigma_build_pandas_dfs_raise_error_if_none(self):
199 |
200 | def sigma_build_pandas_dfs_none():
201 | '''
202 | this simulates a bogus override of the sigma_build_pandas_df method
203 | '''
204 | return pd.DataFrame(None), pd.DataFrame(None)
205 |
206 | self.graph.sigma_build_pandas_dfs = sigma_build_pandas_dfs_none
207 |
208 | self.assertRaises(IPySigmaGraphDataFrameValueError, self.graph.sigma_make_graph)
209 |
210 |
211 |
212 | def test_export_json_flag_if_empty(self):
213 |
214 | def sigma_build_pandas_dfs_none():
215 | '''
216 | this simulates a bogus override of the sigma_build_pandas_df method
217 | '''
218 | return pd.DataFrame(None), pd.DataFrame(None)
219 |
220 | self.graph.sigma_build_pandas_dfs = sigma_build_pandas_dfs_none
221 | self.graph.sigma_nodes, self.graph.sigma_edges = self.graph.sigma_build_pandas_dfs()
222 |
223 | j = self.graph.sigma_export_json()
224 |
225 | self.assertTrue(json.loads(j)['error'] == 'true')
226 |
227 | def test_export_json_flag_if_dfs_do_not_exist(self):
228 |
229 |
230 | j = self.graph.sigma_export_json()
231 |
232 | self.assertTrue(json.loads(j)['error'] == 'true')
233 |
234 |
235 | def test_sigma_color_picker_correctly_assigns_random_colors(self):
236 |
237 | self.graph_attr.sigma_make_graph()
238 |
239 |
240 | n1_value = self.graph_attr.sigma_nodes['color'][0]
241 | n2_value = self.graph_attr.sigma_nodes['color'][1]
242 | n3_value = self.graph_attr.sigma_nodes['color'][2]
243 | n4_value = self.graph_attr.sigma_nodes['color'][3]
244 |
245 | self.assertEqual(n1_value,n2_value)
246 | self.assertNotEqual(n3_value,n4_value)
247 |
248 |
249 |
250 | def test_sigma_node_add_color_raises_node_type_error(self):
251 |
252 | for node in self.graph_attr.nodes(data=True):
253 | del node[1]['node_type']
254 |
255 | self.graph_attr.sigma_nodes, self.graph_attr.sigma_edges = self.graph_attr.sigma_build_pandas_dfs()
256 |
257 | with self.assertRaises(IPySigmaNodeTypeError):
258 | self.graph_attr.sigma_node_add_color_node_type()
259 |
260 |
261 |
262 | def test_sigma_node_add_label_raises_label_error(self):
263 |
264 | for node in self.graph_attr.nodes(data=True):
265 | del node[1]['label']
266 |
267 | self.graph_attr.sigma_nodes, self.graph_attr.sigma_edges = self.graph_attr.sigma_build_pandas_dfs()
268 |
269 | with self.assertRaises(IPySigmaLabelError):
270 | self.graph_attr.sigma_node_add_label()
271 |
272 |
273 | def test_sigma_node_add_color_default_assigns_default_to_node_type(self):
274 |
275 | for node in self.graph_attr.nodes(data=True):
276 | del node[1]['node_type']
277 |
278 | self.graph_attr.sigma_make_graph()
279 |
280 | nt = self.graph_attr.sigma_nodes['node_type'].tolist()
281 | correct = ['undefined node_type' for i in range(4)]
282 |
283 | self.assertEqual(nt, correct)
284 |
285 |
286 |
287 | def test_sigma_node_add_label_default_assigns_id_to_label(self):
288 |
289 | for node in self.graph_attr.nodes(data=True):
290 | del node[1]['label']
291 |
292 | self.graph_attr.sigma_make_graph()
293 |
294 | id_col = self.graph_attr.sigma_nodes['id'].tolist()
295 | label_col = self.graph_attr.sigma_nodes['label'].tolist()
296 |
297 | self.assertEqual(id_col, label_col)
298 |
299 |
--------------------------------------------------------------------------------
/ipysig_test.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "### Welcome to the Demo - v0.1.4\n",
8 | "This notebook provides a simulated workflow for using IPySigma with networkx.\n",
9 | "\n",
10 | "We only need to import one class, IPySig, the main controller object along with networkx.\n"
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": 1,
16 | "metadata": {},
17 | "outputs": [
18 | {
19 | "name": "stdout",
20 | "output_type": "stream",
21 | "text": [
22 | "INFO:root:Generating grammar tables from /usr/lib/python2.7/lib2to3/Grammar.txt\n",
23 | "INFO:root:Generating grammar tables from /usr/lib/python2.7/lib2to3/PatternGrammar.txt\n"
24 | ]
25 | }
26 | ],
27 | "source": [
28 | "# notebook to simulate a jupyter working environment\n",
29 | "# the python module gets imported here...\n",
30 | "from ipysig.core import IPySig\n",
31 | "import networkx as nx\n"
32 | ]
33 | },
34 | {
35 | "cell_type": "markdown",
36 | "metadata": {},
37 | "source": [
38 | "To start, we only need to import the IPySig object from the ipysig.core package. This is a singleton controller class that manages our sigma.js visualization connections. The root directory of the node.js app is passed in on instantiation. \n",
39 | "\n",
40 | "If the demo notebook is being run inside the repo, this directory is simply './app', but in production this would change to wherever the user installed the node app on their system. \n",
41 | "\n",
42 | "If all goes well, IPySig will initialize the express server and print the PID number for the running process. \n",
43 | "\n",
44 | "On instantiation, the API also injects a set of custom methods onto the nx.Graph class which allows the controller to interface with the graph object and reshape the networkx object into a format suitable both for web socket transfer across the node server and readable by Sigma.js on the frontend.\n"
45 | ]
46 | },
47 | {
48 | "cell_type": "code",
49 | "execution_count": 2,
50 | "metadata": {},
51 | "outputs": [
52 | {
53 | "name": "stdout",
54 | "output_type": "stream",
55 | "text": [
56 | "node ./app/index.js --baseUrl http://localhost:8888/ --token=6e968baaafc0c7e8116622cbe24d49fc90b3ca9502808dc5\n",
57 | "loading express... \n",
58 | "IPySig express has started... PID: 3613\n"
59 | ]
60 | }
61 | ],
62 | "source": [
63 | "ctrl = IPySig('./app') # note this should point to the root folder of the express app"
64 | ]
65 | },
66 | {
67 | "cell_type": "markdown",
68 | "metadata": {},
69 | "source": [
70 | "Let's make a network graph from the stars dataset. This was a custom search from the NASA ADS api and contains relationships between authors and journals for the keyword search \"stars\".\n",
71 | "\n",
72 | "To bring up the visualization tabs we pass in the object instance and a unique identifier to the controllers `connect` method. This key name is important because it binds the browser tab's socket id to a reference of the object.\n",
73 | "\n",
74 | "After executing each cell the visualization tab should open and by entering a `title name` for the graph pressing `Load Graph`, the graph visualization object should appear. This object reference is stored both in the IPySig controller instance and in the browser via a Loki.js datastore.\n",
75 | "\n",
76 | "In our current demo, v0.1.0, we can only store one graph at a time per session.\n",
77 | "\n",
78 | "### Loading the Stars Dataset\n"
79 | ]
80 | },
81 | {
82 | "cell_type": "code",
83 | "execution_count": 3,
84 | "metadata": {
85 | "collapsed": true
86 | },
87 | "outputs": [],
88 | "source": [
89 | "import pickle\n",
90 | "import os\n",
91 | "\n",
92 | "pkl_g = os.path.abspath(os.path.join('.','IPySig_demo_data', 'stars_test_graph.pkl'))\n",
93 | "stars_graph = pickle.load(open(pkl_g, \"rb\" )) "
94 | ]
95 | },
96 | {
97 | "cell_type": "markdown",
98 | "metadata": {},
99 | "source": [
100 | "The stars_graph was saved earlier and loaded via pickle for convenience, but nx graphs can obviously be created on the fly, loaded from a database or csv or come into the notebook from any other data source.\n",
101 | "\n",
102 | "In order to get the most out of IPySigma, certain node and edge list attributes need to be added to the graph object. The sigma graph loader looks for a few key headers that the user needs to provide. The keyword \"label\" provides the visible label for each node and \"node_type\" is used to cataegorize each node and set color attributes.\n",
103 | "\n",
104 | "Note that the system will still work without these labels, however, the user will lose some of the nicest features of Sigma.js.\n"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": null,
110 | "metadata": {},
111 | "outputs": [],
112 | "source": [
113 | "stars_graph.nodes(data=True)[:5]"
114 | ]
115 | },
116 | {
117 | "cell_type": "code",
118 | "execution_count": null,
119 | "metadata": {},
120 | "outputs": [],
121 | "source": [
122 | "stars_graph.edges(data=True)[:10]"
123 | ]
124 | },
125 | {
126 | "cell_type": "markdown",
127 | "metadata": {},
128 | "source": [
129 | "### Connecting the graph\n",
130 | "\n",
131 | "We connect the graph to a new browser session by calling the \"connect\" method on the controller and passing in the object and a key-name for the graph object. \n",
132 | "\n",
133 | "This method call spins up a browser tab and binds the web socket to this graph instance.\n",
134 | "\n",
135 | "From this point, we can provide a graph name in the browser tab and hit the load_graph button. This sends a signal to IPySig to call the `export_graph_instance()` method and jsonify the result.\n",
136 | "\n",
137 | "This method call essentially packages our networkx object and adds attributes to the graph instance that is needed by Sigma.js. It also adds some centrality measures for free, along with any other user given attributes.\n",
138 | "\n",
139 | "The user can now explore the nodes of the graph freely in the browser tab and get node information by toggling a simple jquery based inspector. \n"
140 | ]
141 | },
142 | {
143 | "cell_type": "code",
144 | "execution_count": 4,
145 | "metadata": {},
146 | "outputs": [
147 | {
148 | "name": "stdout",
149 | "output_type": "stream",
150 | "text": [
151 | "Node received: stars\n"
152 | ]
153 | }
154 | ],
155 | "source": [
156 | "ctrl.connect(stars_graph,'stars')"
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "metadata": {},
162 | "source": [
163 | "Quite a bit of functionality for our IPySig controller and node application is still under development.\n",
164 | "\n",
165 | "Our current demo does not yet support disconnect/reconnect methods and auto-kill of the server process when the kernel is accidentally or unexpectedly shuts down. Also some care needs to be taken in the current version so that the controller has a chance to boot the express server before a browser tab and web socket connection is open. \n",
166 | "\n",
167 | "Future implementation will also include the ability to store multiple graphs in a single browser session tab, by storing names in an in memory datastore.\n",
168 | "\n",
169 | "To cleanly exit the application for now we need to kill the express process manually. "
170 | ]
171 | },
172 | {
173 | "cell_type": "code",
174 | "execution_count": null,
175 | "metadata": {
176 | "collapsed": true
177 | },
178 | "outputs": [],
179 | "source": []
180 | },
181 | {
182 | "cell_type": "markdown",
183 | "metadata": {
184 | "collapsed": true
185 | },
186 | "source": [
187 | "### Just for fun a random graph...\n",
188 | "\n",
189 | "Connecting a new graph will spin it up in a new tab. Without adding labels and node_types to the graph, default values are used."
190 | ]
191 | },
192 | {
193 | "cell_type": "code",
194 | "execution_count": 5,
195 | "metadata": {},
196 | "outputs": [
197 | {
198 | "name": "stdout",
199 | "output_type": "stream",
200 | "text": [
201 | "Node received: newman_watts\n"
202 | ]
203 | }
204 | ],
205 | "source": [
206 | "\n",
207 | "G = nx.random_graphs.newman_watts_strogatz_graph(80,15,0.2)\n",
208 | "ctrl.connect(G, 'newman_watts')"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": null,
214 | "metadata": {},
215 | "outputs": [],
216 | "source": [
217 | "ctrl.kill_express_process()"
218 | ]
219 | },
220 | {
221 | "cell_type": "code",
222 | "execution_count": null,
223 | "metadata": {
224 | "collapsed": true
225 | },
226 | "outputs": [],
227 | "source": []
228 | },
229 | {
230 | "cell_type": "code",
231 | "execution_count": null,
232 | "metadata": {
233 | "collapsed": true
234 | },
235 | "outputs": [],
236 | "source": []
237 | },
238 | {
239 | "cell_type": "code",
240 | "execution_count": null,
241 | "metadata": {
242 | "collapsed": true
243 | },
244 | "outputs": [],
245 | "source": []
246 | },
247 | {
248 | "cell_type": "code",
249 | "execution_count": null,
250 | "metadata": {
251 | "collapsed": true
252 | },
253 | "outputs": [],
254 | "source": []
255 | },
256 | {
257 | "cell_type": "code",
258 | "execution_count": null,
259 | "metadata": {
260 | "collapsed": true
261 | },
262 | "outputs": [],
263 | "source": []
264 | }
265 | ],
266 | "metadata": {
267 | "kernelspec": {
268 | "display_name": "Python 2",
269 | "language": "python",
270 | "name": "python2"
271 | },
272 | "language_info": {
273 | "codemirror_mode": {
274 | "name": "ipython",
275 | "version": 2
276 | },
277 | "file_extension": ".py",
278 | "mimetype": "text/x-python",
279 | "name": "python",
280 | "nbconvert_exporter": "python",
281 | "pygments_lexer": "ipython2",
282 | "version": "2.7.10"
283 | }
284 | },
285 | "nbformat": 4,
286 | "nbformat_minor": 2
287 | }
288 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | ads==0.12.3
2 | appdirs==1.4.3
3 | backports-abc==0.5
4 | backports.shutil-get-terminal-size==1.0.0
5 | bleach==2.0.0
6 | certifi==2017.1.23
7 | configparser==3.5.0
8 | cycler==0.10.0
9 | decorator==4.0.11
10 | entrypoints==0.2.2
11 | enum34==1.1.6
12 | funcsigs==1.0.2
13 | functools32==3.2.3.post2
14 | html5lib==0.999999999
15 | httpretty==0.8.10
16 | ipykernel==4.6.0
17 | ipython==5.3.0
18 | ipython-genutils==0.2.0
19 | ipywidgets==6.0.0
20 | Jinja2==2.9.6
21 | jsonschema==2.6.0
22 | jupyter==1.0.0
23 | jupyter-client==5.0.1
24 | jupyter-console==5.1.0
25 | jupyter-core==4.3.0
26 | MarkupSafe==1.0
27 | matplotlib==2.0.2
28 | mistune==0.7.4
29 | mock==2.0.0
30 | nbconvert==5.1.1
31 | nbformat==4.3.0
32 | networkx==1.11
33 | nose==1.3.7
34 | notebook==5.0.0
35 | numpy==1.12.1
36 | packaging==16.8
37 | pandas==0.19.2
38 | pandocfilters==1.4.1
39 | pathlib2==2.2.1
40 | pbr==3.1.1
41 | pexpect==4.2.1
42 | pickleshare==0.7.4
43 | prompt-toolkit==1.0.14
44 | ptyprocess==0.5.1
45 | Pygments==2.2.0
46 | pyparsing==2.2.0
47 | python-dateutil==2.6.0
48 | pytz==2017.2
49 | pyzmq==16.0.2
50 | qtconsole==4.3.0
51 | requests==2.13.0
52 | scandir==1.5
53 | simplegeneric==0.8.1
54 | singledispatch==3.4.0.3
55 | six==1.10.0
56 | socketIO-client==0.7.2
57 | subprocess32==3.2.7
58 | terminado==0.6
59 | testpath==0.3
60 | tornado==4.4.3
61 | traitlets==4.3.2
62 | wcwidth==0.1.7
63 | webencodings==0.5.1
64 | websocket-client==0.40.0
65 | Werkzeug==0.12.2
66 | widgetsnbextension==2.0.0
67 |
--------------------------------------------------------------------------------
/scripts/install_hooks.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | GIT_DIR=$(git rev-parse --git-dir)
4 |
5 | echo "Installing pre-commit hooks..."
6 | # this command creates symlink to our pre-push script
7 | ln -s ../../scripts/pre_commit.sh $GIT_DIR/hooks/pre-commit
8 | echo "Done"
--------------------------------------------------------------------------------
/scripts/pre_commit.sh:
--------------------------------------------------------------------------------
1 | echo "Running pre-commit hook"
2 | ./scripts/run_tests.sh
3 |
4 | # $? stores exit value of the last command
5 | if [ $? -ne 0 ]; then
6 | echo "Tests must pass before commit... exiting"
7 | exit 1
8 | fi
--------------------------------------------------------------------------------
/scripts/run_tests.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # if any command inside script returns error, exit and return that error
4 | set -e
5 |
6 | # magic line to ensure that we're always inside the root of our application,
7 | # no matter from which directory we'll run script
8 | # thanks to it we can just enter `./scripts/run-tests.bash`
9 | cd "${0%/*}/.."
10 |
11 | nosetests # python nose
--------------------------------------------------------------------------------