├── app ├── theme │ ├── grid.scss │ ├── app.scss │ ├── colors.scss │ ├── shared.scss │ ├── node.scss │ ├── corner.scss │ ├── cell.scss │ ├── patch.scss │ ├── face.scss │ ├── link.scss │ ├── inputs.scss │ └── legend.scss ├── assets │ ├── favicon.ico │ └── index.html ├── app.scss ├── initialize.jsx ├── components │ ├── inputs.jsx │ ├── legend.jsx │ ├── app.jsx │ └── grid.jsx ├── landlab_raster_grid_example.json └── landlab_hex_grid_example.json ├── .babelrc ├── README.md ├── .eslintrc.yaml ├── .gitignore ├── brunch-config.js ├── package.json └── generate_test_grid_data_for_sketchbook.py /app/theme/grid.scss: -------------------------------------------------------------------------------- 1 | .chart { 2 | text-align: center; 3 | margin: 0 auto; 4 | } 5 | -------------------------------------------------------------------------------- /app/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/landlab/grid-sketchbook/master/app/assets/favicon.ico -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /app/app.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: sans-serif; 3 | font-weight: lighter; 4 | color: #403F25; 5 | } 6 | 7 | html { 8 | background-color: #fafafa; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/theme/app.scss: -------------------------------------------------------------------------------- 1 | *, *::before, *::after { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | background-color: #fafafa; 8 | } 9 | 10 | .chart { 11 | text-align: center; 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # an app for designing landlab grids 2 | 3 | To run locally, install brunch `npm install -g brunch` (you will need node to npm install). 4 | 5 | Use `brunch watch --server` to develop or `brunch build` to build. 6 | 7 | To deploy to gh-pages, run `npm run deploy`. 8 | -------------------------------------------------------------------------------- /app/theme/colors.scss: -------------------------------------------------------------------------------- 1 | $color-cell: #707335; 2 | $color-patch: #A89855; 3 | $color-link: lemonchiffon; 4 | $color-face: lightcyan; 5 | $color-corner: #5992C7; 6 | $color-node: #A9D1F2; 7 | 8 | $text-area: white; 9 | $text-vector: #403F25; 10 | $text-point: #33351F; 11 | 12 | $color-text: #403F25; 13 | -------------------------------------------------------------------------------- /app/initialize.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './components/app'; 4 | 5 | document.addEventListener('DOMContentLoaded', () => { 6 | const root = ( 7 | 8 | ); 9 | ReactDOM.render(root, document.getElementById('app')); 10 | }); 11 | -------------------------------------------------------------------------------- /app/theme/shared.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | 3 | %button { 4 | padding: 10px; 5 | border: none; 6 | font-size: 16px; 7 | background-color: transparent; 8 | &:focus { 9 | outline: none; 10 | } 11 | &:hover { 12 | cursor: pointer; 13 | border: 0.5px solid lightgray; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | parser: "babel-eslint" 2 | 3 | env: 4 | browser: true 5 | commonjs: true 6 | 7 | extends: 8 | - airbnb 9 | 10 | rules: 11 | brace-style: [2, "1tbs", { "allowSingleLine": true }] 12 | no-nested-ternary: 0 13 | newline-per-chained-call: ["error", { "ignoreChainWithDepth": 4 }] 14 | global-require: 0 15 | -------------------------------------------------------------------------------- /app/theme/node.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | 3 | .node { 4 | fill: rgba($color-node, 0.6); 5 | &:hover { 6 | fill: rgba($color-node, 1); 7 | } 8 | } 9 | 10 | .none { 11 | display: none; 12 | } 13 | 14 | .highlight { 15 | fill: rgba($color-node, 1); 16 | } 17 | 18 | .activeLabel { 19 | display: static; 20 | font-size: 1px; 21 | fill: $text-point; 22 | } 23 | -------------------------------------------------------------------------------- /app/theme/corner.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | 3 | .corner { 4 | fill: rgba($color-corner, 0.6); 5 | &:hover { 6 | fill: rgba($color-corner, 1); 7 | } 8 | } 9 | 10 | .none { 11 | display: none; 12 | } 13 | 14 | .highlight { 15 | fill: rgba($color-corner, 1); 16 | } 17 | 18 | .activeLabel { 19 | display: static; 20 | font-size: 1px; 21 | fill: $text-point; 22 | } 23 | -------------------------------------------------------------------------------- /app/theme/cell.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | .cell { 3 | fill: rgba($color-cell, 0.7); 4 | &:hover { 5 | fill: rgba($color-cell, 1); 6 | } 7 | } 8 | 9 | .none{ 10 | display: none; 11 | } 12 | 13 | .highlight { 14 | fill: rgba($color-cell, 1); 15 | stroke: white; 16 | stroke-width: 0.025px; 17 | } 18 | 19 | .activeLabel { 20 | display: static; 21 | fill: $text-area; 22 | font-size: 1px; 23 | } 24 | -------------------------------------------------------------------------------- /app/theme/patch.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | 3 | .patch { 4 | fill: rgba($color-patch, 0.7); 5 | &:hover { 6 | fill: rgba($color-patch, 1); 7 | } 8 | } 9 | 10 | .none { 11 | display: none; 12 | } 13 | 14 | .highlight { 15 | fill: rgba($color-patch, 1); 16 | stroke: white; 17 | stroke-width: 0.025px; 18 | } 19 | 20 | .activeLabel { 21 | display: static; 22 | fill: $text-area; 23 | font-size: 1px; 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Numerous always-ignore extensions 2 | *.diff 3 | *.err 4 | *.orig 5 | *.log 6 | *.rej 7 | *.swo 8 | *.swp 9 | *.vi 10 | *~ 11 | *.sass-cache 12 | 13 | # OS or Editor folders 14 | .DS_Store 15 | .cache 16 | .project 17 | .settings 18 | .tmproj 19 | nbproject 20 | Thumbs.db 21 | 22 | # NPM packages folder. 23 | node_modules/ 24 | 25 | # Brunch folder for temporary files. 26 | tmp/ 27 | 28 | # Brunch output folder. 29 | public/ 30 | -------------------------------------------------------------------------------- /app/theme/face.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | 3 | .face { 4 | stroke: rgba($color-face, 0.6); 5 | fill: none; 6 | &:hover { 7 | stroke: rgba($color-face, 1); 8 | } 9 | } 10 | 11 | .none { 12 | display: none; 13 | } 14 | 15 | .highlight { 16 | stroke: rgba($color-face, 1); 17 | } 18 | 19 | .activeLabel { 20 | display: static; 21 | font-size: 1px; 22 | fill: $text-vector; 23 | } 24 | 25 | .arrow { 26 | stroke: darken($color-face, 5%); 27 | fill: none; 28 | } 29 | 30 | .vertical { 31 | writing-mode: tb; 32 | glyph-orientation-vertical: 0; 33 | } 34 | -------------------------------------------------------------------------------- /app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Landlab Sketchbook 7 | 8 | 9 | 10 |
coming soon…
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/theme/link.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | 3 | .link { 4 | stroke: rgba($color-link, 0.6); 5 | fill: none; 6 | &:hover { 7 | stroke: rgba($color-link, 1); 8 | } 9 | } 10 | 11 | .none { 12 | display: none; 13 | } 14 | 15 | .highlight { 16 | stroke: rgba($color-link, 1); 17 | fill: rgba($color-link, 1); 18 | } 19 | 20 | .activeLabel { 21 | display: static; 22 | font-size: 1px; 23 | fill: $text-vector; 24 | } 25 | 26 | .arrow { 27 | stroke: darken($color-link, 5%); 28 | fill: none; 29 | } 30 | 31 | .vertical { 32 | writing-mode: tb; 33 | glyph-orientation-vertical: 0; 34 | } 35 | -------------------------------------------------------------------------------- /brunch-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | files: { 3 | javascripts: { 4 | joinTo: { 5 | 'vendor.js': /^(?!app\/)/, 6 | 'app.js': /^app\//, 7 | }, 8 | }, 9 | stylesheets: { joinTo: 'app.css' }, 10 | }, 11 | server: { 12 | hostname: '0.0.0.0', 13 | port: 8181, 14 | noPushState: true, 15 | }, 16 | npm: { 17 | globals: { 18 | io: 'socket.io-client', 19 | }, 20 | }, 21 | modules: { 22 | autoRequire: { 23 | 'app.js': ['initialize'], 24 | }, 25 | }, 26 | plugins: { 27 | babel: { 28 | pattern: /\.jsx?$/, 29 | }, 30 | eslint: { 31 | pattern: /^app\/.*\.jsx?$/, 32 | }, 33 | postcss: { 34 | processors: [ 35 | require('autoprefixer')({ browsers: ['> 5%', 'last 2 versions'] }), 36 | ], 37 | }, 38 | sass: { 39 | mode: 'native', 40 | modules: true, 41 | }, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /app/theme/inputs.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'shared'; 3 | 4 | h2 { 5 | font-weight: lighter; 6 | } 7 | 8 | .form { 9 | width: 100%; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | } 14 | 15 | .hexOptions { 16 | text-align: left; 17 | display: flex; 18 | flex-direction: column; 19 | .label { 20 | margin: 0; 21 | font-size: 1rem; 22 | display: flex; 23 | flex-wrap: nowrap; 24 | align-items: center; 25 | justify-content: space-between; 26 | } 27 | .select { 28 | margin: 2px 1rem; 29 | } 30 | } 31 | 32 | .input { 33 | @extend %button; 34 | width: 3rem; 35 | text-align: center; 36 | box-shadow: 0 0 5px -2px rgba($color-cell, 0.3); 37 | margin: 1rem; 38 | background-color: white; 39 | } 40 | 41 | .select { 42 | text-align: center; 43 | box-shadow: 0 0 5px -2px rgba($color-cell, 0.3); 44 | margin: 1rem; 45 | padding: 10px; 46 | height: 2rem; 47 | background-color: transparent; 48 | border: none; 49 | width: 6rem; 50 | background-color: white; 51 | } 52 | 53 | .label { 54 | font-weight: 100; 55 | font-size: 20px; 56 | margin: 1rem; 57 | } 58 | -------------------------------------------------------------------------------- /app/theme/legend.scss: -------------------------------------------------------------------------------- 1 | @import 'colors'; 2 | @import 'shared'; 3 | 4 | .container { 5 | margin: 0 auto; 6 | display: flex; 7 | justify-content: space-around; 8 | width: 65vw; 9 | margin-bottom: 50px; 10 | background-color: white; 11 | border-radius: 4px; 12 | } 13 | 14 | .section { 15 | font-weight: lighter; 16 | margin-left: 10px; 17 | text-align: left; 18 | h4 { 19 | margin-left: 10px; 20 | } 21 | } 22 | 23 | .column { 24 | margin-bottom: 25px; 25 | display: flex; 26 | flex-direction: column; 27 | box-shadow: 0 0 5px -2px rgba($color-cell, 0.3); 28 | } 29 | 30 | h2 { 31 | font-weight: lighter; 32 | } 33 | 34 | .button { 35 | @extend %button; 36 | } 37 | 38 | .cellButtonDown { 39 | @extend %button; 40 | background-color: rgba($color-cell, 0.8); 41 | color: white; 42 | } 43 | 44 | .patchButtonDown { 45 | @extend %button; 46 | background-color: rgba($color-patch, 0.8); 47 | color: white; 48 | } 49 | 50 | .linkButtonDown { 51 | @extend %button; 52 | background-color: rgba($color-link, 0.8); 53 | 54 | } 55 | 56 | .faceButtonDown { 57 | @extend %button; 58 | background-color: rgba($color-face, 0.8); 59 | } 60 | 61 | .nodeButtonDown { 62 | @extend %button; 63 | background-color: rgba($color-node, 0.8); 64 | color: white; 65 | } 66 | 67 | .cornerButtonDown { 68 | @extend %button; 69 | background-color: rgba($color-corner, 0.8); 70 | color: white; 71 | } 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grid-sketchbook", 3 | "version": "1.0.0", 4 | "description": "a sketchbook for designing Landlab grids", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "deploy": "gh-pages -d public" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/jennyknuth/grid-sketchbook.git" 12 | }, 13 | "author": "jenny knuth", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/jennyknuth/grid-sketchbook/issues" 17 | }, 18 | "homepage": "https://github.com/jennyknuth/grid-sketchbook#readme", 19 | "dependencies": { 20 | "autoprefixer": "^9.5.1", 21 | "axios": "^0.19.0", 22 | "classnames": "^2.2.6", 23 | "d3": "^5.9.2", 24 | "react": "^15.6.2", 25 | "react-dom": "^15.6.2", 26 | "socket.io-client": "^2.2.0" 27 | }, 28 | "devDependencies": { 29 | "@babel/preset-env": "^7.4.4", 30 | "@babel/preset-react": "^7.0.0", 31 | "auto-reload-brunch": "^2.7.1", 32 | "babel-brunch": "^7.0.1", 33 | "babel-eslint": "^7.2.3", 34 | "babel-preset-react": "^6.24.1", 35 | "babel-preset-stage-0": "^6.24.1", 36 | "brunch": "^2.10.17", 37 | "clean-css-brunch": "^2.10.0", 38 | "eslint": "^3.19.0", 39 | "eslint-brunch": "^3.12.0", 40 | "eslint-config-airbnb": "^14.1.0", 41 | "eslint-plugin-import": "^2.17.2", 42 | "eslint-plugin-jsx-a11y": "^4.0.0", 43 | "eslint-plugin-react": "^6.10.3", 44 | "gh-pages": "^0.12.0", 45 | "javascript-brunch": "^2.10.0", 46 | "json-brunch": "^1.5.4", 47 | "postcss-brunch": "^2.10.1", 48 | "sass-brunch": "^2.10.8", 49 | "uglify-js-brunch": "^2.10.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/components/inputs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import inputs from '../theme/inputs.scss'; 4 | 5 | const Inputs = (props) => { 6 | const { 7 | grid, 8 | rows, 9 | cols, 10 | layout, 11 | orientation, 12 | onChange, 13 | } = props; 14 | 15 | return ( 16 |
17 |
18 | 27 | { grid === 'radial' ? 28 | : 32 | 36 | } 37 | 41 | {grid === 'hex' && ( 42 |
43 | 51 | 59 |
60 | )} 61 | 62 |
63 |
64 | ); 65 | }; 66 | 67 | Inputs.propTypes = { 68 | grid: React.PropTypes.string.isRequired, 69 | rows: React.PropTypes.number.isRequired, 70 | cols: React.PropTypes.number.isRequired, 71 | layout: React.PropTypes.string.isRequired, 72 | orientation: React.PropTypes.string.isRequired, 73 | onChange: React.PropTypes.func.isRequired, 74 | }; 75 | 76 | export default Inputs; 77 | -------------------------------------------------------------------------------- /app/landlab_raster_grid_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "_type": "graph", 3 | "graph": { 4 | "attrs": {}, 5 | "coords": {}, 6 | "data_vars": { 7 | "nodes_at_link": { 8 | "attrs": {}, 9 | "data": [ 10 | [ 11 | 0, 12 | 1 13 | ], 14 | [ 15 | 1, 16 | 2 17 | ], 18 | [ 19 | 2, 20 | 3 21 | ], 22 | [ 23 | 0, 24 | 4 25 | ], 26 | [ 27 | 1, 28 | 5 29 | ], 30 | [ 31 | 2, 32 | 6 33 | ], 34 | [ 35 | 3, 36 | 7 37 | ], 38 | [ 39 | 4, 40 | 5 41 | ], 42 | [ 43 | 5, 44 | 6 45 | ], 46 | [ 47 | 6, 48 | 7 49 | ], 50 | [ 51 | 4, 52 | 8 53 | ], 54 | [ 55 | 5, 56 | 9 57 | ], 58 | [ 59 | 6, 60 | 10 61 | ], 62 | [ 63 | 7, 64 | 11 65 | ], 66 | [ 67 | 8, 68 | 9 69 | ], 70 | [ 71 | 9, 72 | 10 73 | ], 74 | [ 75 | 10, 76 | 11 77 | ] 78 | ], 79 | "dims": [ 80 | "link", 81 | "nodes_per_link" 82 | ] 83 | }, 84 | "nodes_at_patch": { 85 | "attrs": {}, 86 | "data": [ 87 | [ 88 | 5, 89 | 4, 90 | 0, 91 | 1 92 | ], 93 | [ 94 | 6, 95 | 5, 96 | 1, 97 | 2 98 | ], 99 | [ 100 | 7, 101 | 6, 102 | 2, 103 | 3 104 | ], 105 | [ 106 | 9, 107 | 8, 108 | 4, 109 | 5 110 | ], 111 | [ 112 | 10, 113 | 9, 114 | 5, 115 | 6 116 | ], 117 | [ 118 | 11, 119 | 10, 120 | 6, 121 | 7 122 | ] 123 | ], 124 | "dims": [ 125 | "patch", 126 | "nodes_per_patch" 127 | ] 128 | }, 129 | "x_of_node": { 130 | "attrs": {}, 131 | "data": [ 132 | 0, 133 | 10, 134 | 20, 135 | 30, 136 | 0, 137 | 10, 138 | 20, 139 | 30, 140 | 0, 141 | 10, 142 | 20, 143 | 30 144 | ], 145 | "dims": [ 146 | "node" 147 | ] 148 | }, 149 | "y_of_node": { 150 | "attrs": {}, 151 | "data": [ 152 | 0, 153 | 0, 154 | 0, 155 | 0, 156 | 10, 157 | 10, 158 | 10, 159 | 10, 160 | 20, 161 | 20, 162 | 20, 163 | 20 164 | ], 165 | "dims": [ 166 | "node" 167 | ] 168 | } 169 | }, 170 | "dims": { 171 | "link": 17, 172 | "node": 12, 173 | "nodes_per_link": 2, 174 | "nodes_per_patch": 4, 175 | "patch": 6 176 | } 177 | }, 178 | "href": "/graph/raster?shape=3%2C4&spacing=10.%2C10." 179 | } 180 | -------------------------------------------------------------------------------- /app/components/legend.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import legend from '../theme/legend.scss'; 4 | 5 | 6 | const Legend = props => ( 7 |
8 |
9 |

Areas

10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 |

Vectors

23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 | 31 |
32 |
33 |
34 |
35 |

Points

36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 |
47 |
48 | ); 49 | 50 | export default Legend; 51 | 52 | Legend.propTypes = { 53 | onChange: React.PropTypes.func.isRequired, 54 | active: React.PropTypes.shape({ 55 | cells: React.PropTypes.bool, 56 | cellLabels: React.PropTypes.bool, 57 | patches: React.PropTypes.bool, 58 | patchLabels: React.PropTypes.bool, 59 | links: React.PropTypes.bool, 60 | linkLabels: React.PropTypes.bool, 61 | faces: React.PropTypes.bool, 62 | faceLabels: React.PropTypes.bool, 63 | nodes: React.PropTypes.bool, 64 | nodeLabels: React.PropTypes.bool, 65 | corners: React.PropTypes.bool, 66 | cornerLabels: React.PropTypes.bool, 67 | }).isRequired, 68 | }; 69 | -------------------------------------------------------------------------------- /app/components/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import axios from 'axios'; 3 | import Grid from './grid'; 4 | import Legend from './legend'; 5 | import Inputs from './inputs'; 6 | 7 | import app from '../theme/app.scss'; 8 | 9 | const apiBase = 'https://csdms.io:2083'; 10 | 11 | class App extends React.Component { 12 | constructor() { 13 | super(); 14 | this.state = { 15 | showCells: true, 16 | showCellLabels: false, 17 | showPatches: true, 18 | showPatchLabels: false, 19 | showLinks: true, 20 | showLinkLabels: false, 21 | showFaces: true, 22 | showFaceLabels: false, 23 | showNodes: true, 24 | showNodeLabels: false, 25 | showCorners: true, 26 | showCornerLabels: false, 27 | graph: {}, 28 | grid: 'raster', 29 | rows: 3, 30 | cols: 4, 31 | spacing: 10, 32 | layout: 'hex', 33 | orientation: 'horizontal', 34 | }; 35 | } 36 | 37 | componentWillMount() { 38 | const APIurl = 39 | `${apiBase}/graphs/${this.state.grid}?shape=${this.state.rows},${this.state.cols}&spacing=${this.state.spacing}`; 40 | 41 | axios.get(APIurl) 42 | .then((response) => { 43 | this.setState({ graph: response.data.graph }); 44 | }); 45 | } 46 | 47 | componentDidUpdate(props, state) { 48 | const newGrid = this.state.grid !== state.grid; 49 | const newRows = this.state.rows !== state.rows; 50 | const newCols = this.state.cols !== state.cols; 51 | const newLayout = this.state.layout !== state.layout; 52 | const newOrientation = this.state.orientation !== state.orientation; 53 | const spacing = this.state.grid === 'hex' || 'radial' ? this.state.spacing : `${this.state.spacing},${this.state.spacing}`; 54 | const layoutQuery = this.state.grid === 'hex' ? `&node_layout=${this.state.layout}` : ''; 55 | const orientationQuery = this.state.grid === 'hex' ? `&orientation=${this.state.orientation}` : ''; 56 | const newGraph = newGrid || newRows || newCols || newLayout || newOrientation; 57 | const APIurl = 58 | `${apiBase}/graphs/${this.state.grid}?shape=${this.state.rows},${this.state.cols}&spacing=${spacing}${layoutQuery}${orientationQuery}`; 59 | 60 | if (newGraph) { 61 | axios.get(APIurl) 62 | .then((response) => { 63 | this.setState({ graph: response.data.graph }); 64 | }); 65 | } 66 | } 67 | 68 | updateGridValues(event) { 69 | const isString = isNaN(+event.target.value); 70 | this.setState({ [event.target.name]: isString ? event.target.value : +event.target.value }); 71 | } 72 | 73 | toggleActiveLayers(event) { 74 | this.setState({ [event.target.value]: !this.state[event.target.value] }); 75 | } 76 | 77 | render() { 78 | const activeLayers = { 79 | cells: this.state.showCells, 80 | cellLabels: this.state.showCellLabels, 81 | patches: this.state.showPatches, 82 | patchLabels: this.state.showPatchLabels, 83 | links: this.state.showLinks, 84 | linkLabels: this.state.showLinkLabels, 85 | faces: this.state.showFaces, 86 | faceLabels: this.state.showFaceLabels, 87 | nodes: this.state.showNodes, 88 | nodeLabels: this.state.showNodeLabels, 89 | corners: this.state.showCorners, 90 | cornerLabels: this.state.showCornerLabels, 91 | }; 92 | 93 | return this.state.graph.data_vars ? ( 94 |
95 | this.updateGridValues(e)} 102 | /> 103 | 115 | this.toggleActiveLayers(e)} 118 | /> 119 |
120 | ) : null; 121 | } 122 | } 123 | 124 | export default App; 125 | -------------------------------------------------------------------------------- /generate_test_grid_data_for_sketchbook.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Mar 5 14:14:57 2017 5 | 6 | @author: gtucker 7 | """ 8 | 9 | from landlab import RasterModelGrid, HexModelGrid 10 | from six import print_ 11 | import json 12 | 13 | NUMROWS = 3 14 | NUMCOLS = 4 15 | SPACING = 10.0 16 | 17 | 18 | def display_grid_data(grid): 19 | """Print info about grid.""" 20 | 21 | # Node info 22 | print('Information about nodes:') 23 | for n in range(grid.number_of_nodes): 24 | print_(n, end=' ') # node ID 25 | print_(grid.x_of_node[n], end=' ') # node x coord 26 | print_(grid.y_of_node[n], end=' ') # node y coord 27 | print_(grid.status_at_node[n], end=' ') # boundary code (0=CORE) 28 | if n in grid.core_nodes: 29 | print_('C', end=' ') # core node 30 | else: 31 | print_('B', end=' ') # boundary node 32 | print_(grid.cell_at_node[n], end=' ') # ID of cell (-1 = no cell) 33 | for l in grid.links_at_node[n]: # IDs of links (-1 = none) 34 | print_(l, end=' ') 35 | for d in grid.link_dirs_at_node[n]: # Directions of links (0 = none) 36 | print_(d, end=' ') 37 | for nbr in grid.neighbors_at_node[n]: # ID of neighbor node (-1 = none) 38 | print_(nbr, end=' ') 39 | print('') 40 | 41 | # Link info 42 | print('') 43 | print('Information about links:') 44 | for ln in range(grid.number_of_links): 45 | print_(ln, end=' ') # link ID 46 | print_(grid.node_at_link_tail[ln], end=' ') # ID of tail node 47 | print_(grid.node_at_link_head[ln], end=' ') # ID of head node 48 | print_(grid.face_at_link[ln], end=' ') # ID of face 49 | print_(grid.length_of_link[ln], end=' ') # length 50 | for p in grid.patches_at_link[ln]: # IDs of patches left and right 51 | print_(p, end=' ') 52 | print_(grid.status_at_link[ln], end=' ') # Boundary status 53 | print_(grid.x_of_link[ln], end=' ') # x coord of center 54 | print_(grid.y_of_link[ln], end=' ') # y coord of center 55 | print('') 56 | 57 | # Cell info 58 | print('') 59 | print('Information about cells:') 60 | for c in range(grid.number_of_cells): 61 | print_(c, end=' ') # cell ID 62 | print_(grid.area_of_cell[c], end=' ') # cell surface area 63 | for f in grid.faces_at_cell[c]: # IDs of cell's faces 64 | print_(f, end=' ') 65 | print_(grid.node_at_cell[c], end=' ') # ID of node inside this cell 66 | print_(grid.x_of_cell[c], end=' ') # x coord of center 67 | print_(grid.y_of_cell[c], end=' ') # y coord of center 68 | print('') 69 | 70 | # Corner info 71 | print('') 72 | print('Information about corners: there basically is none at present') 73 | print('') 74 | 75 | # Face info 76 | print('Information about faces:') 77 | for f in range(grid.number_of_faces): 78 | print_(f, end=' ') 79 | print_(grid.width_of_face[f], end=' ') 80 | print_(grid.x_of_face[f], end=' ') 81 | print_(grid.y_of_face[f], end=' ') 82 | print('') 83 | 84 | # Patch info 85 | print('') 86 | print('Information about patches:') 87 | for p in range(grid.number_of_patches): 88 | print_(p, end=' ') 89 | for ln in grid.links_at_patch[p]: # IDs of patch's links 90 | print_(ln, end=' ') 91 | print('') 92 | 93 | 94 | def landlab_grid_to_dict(grid): 95 | """Turn a Landlab grid into a JSON-like dict.""" 96 | 97 | # Create the dictionary 98 | grid_dict = {} 99 | 100 | # Create a list ("array" in JSON-speak) of node information 101 | my_list = [] 102 | for n in range(grid.number_of_nodes): 103 | 104 | # Create a dictionary with data for this particular node 105 | this_node_dict = {} 106 | this_node_dict['id'] = n 107 | this_node_dict['x'] = grid.x_of_node[n] 108 | this_node_dict['y'] = grid.y_of_node[n] 109 | this_node_dict['status'] = int(grid.status_at_node[n]) 110 | links_list = [] 111 | for l in grid.links_at_node[n]: # IDs of links (-1 = none) 112 | links_list.append(l) 113 | this_node_dict['links'] = links_list 114 | dirs_list = [] 115 | for d in grid.link_dirs_at_node[n]: # Directions of links (0 = none) 116 | dirs_list.append(int(d)) 117 | this_node_dict['link_dirs'] = dirs_list 118 | nbrs_list = [] 119 | for nbr in grid.neighbors_at_node[n]: # ID of neighbor node (-1 = none) 120 | nbrs_list.append(nbr) 121 | this_node_dict['neighbor_nodes'] = nbrs_list 122 | 123 | # Append the dict to the list 124 | my_list.append(this_node_dict) 125 | 126 | # ... and add it to the dict 127 | grid_dict['nodes'] = my_list 128 | 129 | # Create a list ("array" in JSON-speak) of LINK information 130 | my_list = [] 131 | for n in range(grid.number_of_links): 132 | 133 | # Create a dictionary with data for this particular link 134 | this_list_dict = {} 135 | this_list_dict['id'] = n 136 | this_list_dict['x'] = grid.x_of_link[n] 137 | this_list_dict['y'] = grid.y_of_link[n] 138 | this_list_dict['tail_node'] = grid.node_at_link_tail[n] 139 | this_list_dict['head_node'] = grid.node_at_link_head[n] 140 | this_list_dict['face_id'] = grid.face_at_link[n] 141 | this_list_dict['length'] = grid.length_of_link[n] 142 | patch_list = [] 143 | for p in grid.patches_at_link[n]: # IDs of patches left and right 144 | patch_list.append(p) 145 | this_list_dict['patches'] = patch_list 146 | this_list_dict['status'] = int(grid.status_at_link[n]) # Boundary status 147 | 148 | # Append the dict to the list 149 | my_list.append(this_list_dict) 150 | 151 | # ... and add it to the dict 152 | grid_dict['links'] = my_list 153 | 154 | # Create a list ("array" in JSON-speak) of CELL information 155 | my_list = [] 156 | for n in range(grid.number_of_cells): 157 | 158 | # Create a dictionary with data for this particular cell 159 | this_cell_dict = {} 160 | this_cell_dict['id'] = n 161 | this_cell_dict['x'] = grid.x_of_cell[n] 162 | this_cell_dict['y'] = grid.y_of_cell[n] 163 | this_cell_dict['area'] = grid.area_of_cell[n] 164 | faces_list = [] 165 | for f in grid.faces_at_cell[n]: # IDs of cell's faces 166 | faces_list.append(f) 167 | this_cell_dict['faces'] = faces_list 168 | this_cell_dict['node'] = grid.node_at_cell[n] 169 | 170 | # Append the dict to the list 171 | my_list.append(this_cell_dict) 172 | 173 | # ... and add it to the dict 174 | grid_dict['cells'] = my_list 175 | 176 | # Create a list ("array" in JSON-speak) of FACE information 177 | my_list = [] 178 | for n in range(grid.number_of_faces): 179 | 180 | # Create a dictionary with data for this particular face 181 | this_face_dict = {} 182 | this_face_dict['id'] = n 183 | this_face_dict['x'] = grid.x_of_face[n] 184 | this_face_dict['y'] = grid.y_of_face[n] 185 | this_face_dict['area'] = grid.width_of_face[n] 186 | 187 | # Append the dict to the list 188 | my_list.append(this_face_dict) 189 | 190 | # ... and add it to the dict 191 | grid_dict['faces'] = my_list 192 | 193 | # Create a list ("array" in JSON-speak) of PATCH information 194 | my_list = [] 195 | for n in range(grid.number_of_patches): 196 | 197 | # Create a dictionary with data for this particular patch 198 | this_patch_dict = {} 199 | this_patch_dict['id'] = n 200 | links_list = [] 201 | for ln in grid.links_at_patch[n]: 202 | links_list.append(ln) 203 | this_patch_dict['links'] = links_list 204 | 205 | # Append the dict to the list 206 | my_list.append(this_patch_dict) 207 | 208 | # ... and add it to the dict 209 | grid_dict['patches'] = my_list 210 | 211 | return grid_dict 212 | 213 | 214 | def write_grid_dict_to_json_file(grid_dict, filename): 215 | """Writes dictionary grid_dict to a JSON file of name .""" 216 | fp = open(filename, 'w') 217 | json.dump(grid_dict, fp, sort_keys=True, indent=4, separators=(',', ': ')) 218 | 219 | 220 | # Make a raster grid for testing 221 | rg = RasterModelGrid((NUMROWS, NUMCOLS), SPACING) 222 | 223 | print('3x4 RASTER GRID:') 224 | print('') 225 | display_grid_data(rg) 226 | 227 | grid_dict = landlab_grid_to_dict(rg) 228 | 229 | write_grid_dict_to_json_file(grid_dict, 'landlab_raster_grid_example.json') 230 | 231 | 232 | # Make a hex grid for testing 233 | hg = HexModelGrid(NUMROWS, NUMCOLS, SPACING) 234 | 235 | print('HEX GRID:') 236 | print('') 237 | display_grid_data(hg) 238 | 239 | grid_dict = landlab_grid_to_dict(hg) 240 | 241 | write_grid_dict_to_json_file(grid_dict, 'landlab_hex_grid_example.json') 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /app/components/grid.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as d3 from 'd3'; 3 | import classNames from 'classnames'; 4 | 5 | import grid from '../theme/grid.scss'; 6 | import node from '../theme/node.scss'; 7 | import cell from '../theme/cell.scss'; 8 | import link from '../theme/link.scss'; 9 | import patch from '../theme/patch.scss'; 10 | import corner from '../theme/corner.scss'; 11 | import face from '../theme/face.scss'; 12 | 13 | class Grid extends React.Component { 14 | constructor() { 15 | super(); 16 | this.state = { 17 | node: false, 18 | activeNode: null, 19 | cell: false, 20 | activeCell: null, 21 | face: false, 22 | activeFace: null, 23 | link: false, 24 | activeLink: null, 25 | patch: false, 26 | activePatch: null, 27 | corner: false, 28 | activeCorner: null, 29 | }; 30 | } 31 | 32 | render() { 33 | const { 34 | nodeX, 35 | nodeY, 36 | patchLinks, 37 | cornerX, 38 | cornerY, 39 | cellFaces, 40 | linkLine, 41 | faceLine, 42 | spacing, 43 | show, 44 | } = this.props; 45 | 46 | const xExtent = d3.extent(nodeX); 47 | const yExtent = d3.extent(nodeY); 48 | 49 | const margin = { top: spacing / 4, right: spacing / 4, bottom: spacing / 4, left: spacing / 4 }; 50 | const innerWidth = xExtent[1] - xExtent[0]; 51 | const innerHeight = yExtent[1] - yExtent[0]; 52 | const marginLeftOffset = margin.left - xExtent[0]; 53 | const marginTopOffset = margin.top + yExtent[0]; 54 | const chartHeight = innerHeight + margin.top + margin.bottom; 55 | const chartWidth = innerWidth + margin.left + margin.right; 56 | const half = spacing / 2; 57 | 58 | const yScale = d3.scaleLinear() 59 | .domain([0, innerHeight]) 60 | .range([innerHeight, 0]); 61 | 62 | const nodes = nodeX.map((d, i) => ( 63 | 64 | this.setState({ node: true, activeNode: i })} 70 | onMouseLeave={() => this.setState({ node: false, activeNode: null })} 71 | /> 72 | 81 | node {i} 82 | 83 | 84 | ), 85 | ); 86 | 87 | const corners = cornerX.map((d, i) => ( 88 | 89 | this.setState({ corner: true, activeCorner: i })} 97 | onMouseLeave={() => this.setState({ corner: false, activeCorner: null })} 98 | /> 99 | 108 | corner {i} 109 | 110 | 111 | ), 112 | ); 113 | 114 | const getPath = (verticies, element) => { 115 | const coordinates = verticies.map((c) => { 116 | if (element === 'node') { 117 | return `${nodeX[c]} ${yScale(nodeY[c])}`; 118 | } else if (element === 'corner') { 119 | return (`${cornerX[c]} ${yScale(cornerY[c])}`); 120 | } 121 | return null; 122 | }); 123 | const d = `M ${coordinates} Z`; 124 | return d; 125 | }; 126 | 127 | const getVerticies = (vector, element) => { 128 | let verticieSet; 129 | if (element === 'node') { 130 | verticieSet = new Set((vector.map(v => linkLine[v])).flat()); 131 | } 132 | if (element === 'corner') { 133 | verticieSet = new Set((vector.map(v => faceLine[v])).flat()); 134 | } 135 | return [...verticieSet]; 136 | }; 137 | 138 | const cellCorners = cellFaces.map(cellFace => getVerticies(cellFace, 'corner')); 139 | const patchNodes = patchLinks.map(patchLink => getVerticies(patchLink, 'node')); 140 | 141 | const cellTextPosition = cellCorners.map((d) => { 142 | const position = 143 | { 144 | x: cornerX[d[1]] - half, 145 | y: yScale(cornerY[d[1]] - (half / 2)), 146 | }; 147 | return position; 148 | }); 149 | 150 | const patchTextPosition = patchNodes.map((d) => { 151 | const position = d.length % 3 === 0 ? 152 | { 153 | x: (nodeX[d[0]] + nodeX[d[1]] + nodeX[d[2]]) / 3, 154 | y: yScale((nodeY[d[0]] + nodeY[d[1]] + nodeY[d[2]]) / 3), 155 | } : 156 | { 157 | x: nodeX[d[1]] - half, 158 | y: yScale(nodeY[d[1]] - (half / 2)), 159 | }; 160 | return position; 161 | }); 162 | 163 | const cells = cellCorners.map((d, i) => ( 164 | this.setState({ cell: true, activeCell: i })} 168 | onMouseLeave={() => this.setState({ cell: false, activeCell: null })} 169 | > 170 | 173 | 181 | cell {i} 182 | 183 | 184 | )); 185 | 186 | const patches = patchNodes.map((d, i) => ( 187 | this.setState({ patch: true, activePatch: i })} 191 | onMouseLeave={() => this.setState({ patch: false, activePatch: null })} 192 | > 193 | 196 | 204 | patch {i} 205 | 206 | 207 | )); 208 | 209 | const faces = faceLine.map((d, i) => { 210 | const vertical = cornerX[d[0]] === cornerX[d[1]]; 211 | const textClassnames = classNames( 212 | (this.state.activeFace === i) || show.faceLabels ? face.activeLabel : face.none, 213 | vertical && face.vertical, 214 | ); 215 | return ( 216 | this.setState({ face: true, activeFace: i })} 219 | onMouseLeave={() => this.setState({ face: false, activeFace: null })} 220 | > 221 | 222 | 231 | 232 | 233 | 234 | 242 | 250 | face {i} 251 | 252 | 253 | ); 254 | }, 255 | ); 256 | 257 | const links = linkLine.map((d, i) => { 258 | const vertical = nodeX[d[0]] === nodeX[d[1]]; 259 | const textClassnames = classNames( 260 | (this.state.activeLink === i) || show.linkLabels ? link.activeLabel : link.none, 261 | vertical && link.vertical, 262 | ); 263 | 264 | return ( 265 | this.setState({ link: true, activeLink: i })} 268 | onMouseLeave={() => this.setState({ link: false, activeLink: null })} 269 | > 270 | 271 | 280 | 281 | 282 | 283 | 284 | 292 | 300 | link {i} 301 | 302 | 303 | ); 304 | }); 305 | 306 | return ( 307 | 308 | 309 | {patches} 310 | {cells} 311 | {links} 312 | {faces} 313 | {nodes} 314 | {corners} 315 | 316 | 317 | ); 318 | } 319 | } 320 | 321 | Grid.propTypes = { 322 | nodeX: React.PropTypes.arrayOf(React.PropTypes.number).isRequired, 323 | nodeY: React.PropTypes.arrayOf(React.PropTypes.number).isRequired, 324 | patchLinks: React.PropTypes.arrayOf(React.PropTypes.array).isRequired, 325 | cornerX: React.PropTypes.arrayOf(React.PropTypes.number).isRequired, 326 | cornerY: React.PropTypes.arrayOf(React.PropTypes.number).isRequired, 327 | cellFaces: React.PropTypes.arrayOf(React.PropTypes.array).isRequired, 328 | linkLine: React.PropTypes.arrayOf(React.PropTypes.array).isRequired, 329 | faceLine: React.PropTypes.arrayOf(React.PropTypes.array).isRequired, 330 | spacing: React.PropTypes.number.isRequired, 331 | show: React.PropTypes.shape({ 332 | cells: React.PropTypes.bool, 333 | cellLabels: React.PropTypes.bool, 334 | patches: React.PropTypes.bool, 335 | patchLabels: React.PropTypes.bool, 336 | links: React.PropTypes.bool, 337 | linkLabels: React.PropTypes.bool, 338 | faces: React.PropTypes.bool, 339 | faceLabels: React.PropTypes.bool, 340 | nodes: React.PropTypes.bool, 341 | nodeLabels: React.PropTypes.bool, 342 | corners: React.PropTypes.bool, 343 | cornerLabels: React.PropTypes.bool, 344 | }).isRequired, 345 | }; 346 | 347 | export default Grid; 348 | -------------------------------------------------------------------------------- /app/landlab_hex_grid_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "area": 86.60254037844386, 5 | "corners": [ 6 | 8, 7 | 11, 8 | 7, 9 | 3, 10 | 0, 11 | 4 12 | ], 13 | "faces": [ 14 | 7, 15 | 11, 16 | 10, 17 | 6, 18 | 0, 19 | 1 20 | ], 21 | "id": 0, 22 | "node": 5, 23 | "x": 5.0, 24 | "y": 8.660254037844386 25 | }, 26 | { 27 | "area": 86.60254037844386, 28 | "corners": [ 29 | 9, 30 | 12, 31 | 8, 32 | 4, 33 | 1, 34 | 5 35 | ], 36 | "faces": [ 37 | 8, 38 | 13, 39 | 12, 40 | 7, 41 | 2, 42 | 3 43 | ], 44 | "id": 1, 45 | "node": 6, 46 | "x": 15.0, 47 | "y": 8.660254037844386 48 | }, 49 | { 50 | "area": 86.60254037844386, 51 | "corners": [ 52 | 10, 53 | 13, 54 | 9, 55 | 5, 56 | 2, 57 | 6 58 | ], 59 | "faces": [ 60 | 9, 61 | 15, 62 | 14, 63 | 8, 64 | 4, 65 | 5 66 | ], 67 | "id": 2, 68 | "node": 7, 69 | "x": 25.0, 70 | "y": 8.660254037844386 71 | } 72 | ], 73 | "corners": [ 74 | { 75 | "cells": [ 76 | 0, 77 | -1 78 | ], 79 | "face_dirs": [ 80 | -1, 81 | 1, 82 | 0 83 | ], 84 | "faces": [ 85 | 1, 86 | 0, 87 | -1 88 | ], 89 | "id": 0, 90 | "x": 5.0, 91 | "y": 2.886751 92 | }, 93 | { 94 | "cells": [ 95 | 1, 96 | -1 97 | ], 98 | "face_dirs": [ 99 | -1, 100 | 1, 101 | 0 102 | ], 103 | "faces": [ 104 | 3, 105 | 2, 106 | -1 107 | ], 108 | "id": 1, 109 | "x": 15.0, 110 | "y": 2.886751 111 | }, 112 | { 113 | "cells": [ 114 | 2, 115 | -1 116 | ], 117 | "face_dirs": [ 118 | -1, 119 | 1, 120 | 0 121 | ], 122 | "faces": [ 123 | 5, 124 | 4, 125 | -1 126 | ], 127 | "id": 2, 128 | "x": 25.0, 129 | "y": 2.886751 130 | }, 131 | { 132 | "cells": [ 133 | 0, 134 | -1 135 | ], 136 | "face_dirs": [ 137 | -1, 138 | -1, 139 | 0 140 | ], 141 | "faces": [ 142 | 6, 143 | 0, 144 | -1 145 | ], 146 | "id": 3, 147 | "x": -8.881784197001252e-16, 148 | "y": 5.773503 149 | }, 150 | { 151 | "cells": [ 152 | 0, 153 | 1 154 | ], 155 | "face_dirs": [ 156 | -1, 157 | 1, 158 | -1 159 | ], 160 | "faces": [ 161 | 7, 162 | 1, 163 | 2 164 | ], 165 | "id": 4, 166 | "x": 10.0, 167 | "y": 5.773503 168 | }, 169 | { 170 | "cells": [ 171 | 1, 172 | 2 173 | ], 174 | "face_dirs": [ 175 | -1, 176 | 1, 177 | -1 178 | ], 179 | "faces": [ 180 | 8, 181 | 3, 182 | 4 183 | ], 184 | "id": 5, 185 | "x": 20.0, 186 | "y": 5.773503 187 | }, 188 | { 189 | "cells": [ 190 | 2, 191 | -1 192 | ], 193 | "face_dirs": [ 194 | -1, 195 | 1, 196 | 0 197 | ], 198 | "faces": [ 199 | 9, 200 | 5, 201 | -1 202 | ], 203 | "id": 6, 204 | "x": 30.0, 205 | "y": 5.773503 206 | }, 207 | { 208 | "cells": [ 209 | 0, 210 | -1 211 | ], 212 | "face_dirs": [ 213 | -1, 214 | 1, 215 | 0 216 | ], 217 | "faces": [ 218 | 10, 219 | 6, 220 | -1 221 | ], 222 | "id": 7, 223 | "x": -8.881784197001252e-16, 224 | "y": 11.547005 225 | }, 226 | { 227 | "cells": [ 228 | 0, 229 | 1 230 | ], 231 | "face_dirs": [ 232 | -1, 233 | 1, 234 | 1 235 | ], 236 | "faces": [ 237 | 12, 238 | 11, 239 | 7 240 | ], 241 | "id": 8, 242 | "x": 10.0, 243 | "y": 11.547005 244 | }, 245 | { 246 | "cells": [ 247 | 1, 248 | 2 249 | ], 250 | "face_dirs": [ 251 | -1, 252 | 1, 253 | 1 254 | ], 255 | "faces": [ 256 | 14, 257 | 13, 258 | 8 259 | ], 260 | "id": 9, 261 | "x": 20.0, 262 | "y": 11.547005 263 | }, 264 | { 265 | "cells": [ 266 | 2, 267 | -1 268 | ], 269 | "face_dirs": [ 270 | 1, 271 | 1, 272 | 0 273 | ], 274 | "faces": [ 275 | 15, 276 | 9, 277 | -1 278 | ], 279 | "id": 10, 280 | "x": 30.0, 281 | "y": 11.547005 282 | }, 283 | { 284 | "cells": [ 285 | 0, 286 | -1 287 | ], 288 | "face_dirs": [ 289 | 1, 290 | -1, 291 | 0 292 | ], 293 | "faces": [ 294 | 10, 295 | 11, 296 | -1 297 | ], 298 | "id": 11, 299 | "x": 4.999999999999999, 300 | "y": 14.433757 301 | }, 302 | { 303 | "cells": [ 304 | 1, 305 | -1 306 | ], 307 | "face_dirs": [ 308 | 1, 309 | -1, 310 | 0 311 | ], 312 | "faces": [ 313 | 12, 314 | 13, 315 | -1 316 | ], 317 | "id": 12, 318 | "x": 15.0, 319 | "y": 14.433757 320 | }, 321 | { 322 | "cells": [ 323 | 2, 324 | -1 325 | ], 326 | "face_dirs": [ 327 | 1, 328 | -1, 329 | 0 330 | ], 331 | "faces": [ 332 | 14, 333 | 15, 334 | -1 335 | ], 336 | "id": 13, 337 | "x": 25.0, 338 | "y": 14.433757 339 | } 340 | ], 341 | "faces": [ 342 | { 343 | "cells": [ 344 | 0, 345 | -1 346 | ], 347 | "corners": [ 348 | 3, 349 | 0 350 | ], 351 | "head_corner": 0, 352 | "id": 0, 353 | "length": 5.773503018922223, 354 | "link": 4, 355 | "tail_corner": 3, 356 | "x": 2.5, 357 | "y": 4.330127018922193 358 | }, 359 | { 360 | "cells": [ 361 | 0, 362 | -1 363 | ], 364 | "corners": [ 365 | 0, 366 | 4 367 | ], 368 | "head_corner": 4, 369 | "id": 1, 370 | "length": 5.773503018922221, 371 | "link": 6, 372 | "tail_corner": 0, 373 | "x": 7.5, 374 | "y": 4.330127018922193 375 | }, 376 | { 377 | "cells": [ 378 | 1, 379 | -1 380 | ], 381 | "corners": [ 382 | 4, 383 | 1 384 | ], 385 | "head_corner": 1, 386 | "id": 2, 387 | "length": 5.773503018922221, 388 | "link": 5, 389 | "tail_corner": 4, 390 | "x": 12.5, 391 | "y": 4.330127018922193 392 | }, 393 | { 394 | "cells": [ 395 | 1, 396 | -1 397 | ], 398 | "corners": [ 399 | 1, 400 | 5 401 | ], 402 | "head_corner": 5, 403 | "id": 3, 404 | "length": 5.773503018922221, 405 | "link": 11, 406 | "tail_corner": 1, 407 | "x": 17.5, 408 | "y": 4.330127018922193 409 | }, 410 | { 411 | "cells": [ 412 | 2, 413 | -1 414 | ], 415 | "corners": [ 416 | 5, 417 | 2 418 | ], 419 | "head_corner": 2, 420 | "id": 4, 421 | "length": 5.773503018922221, 422 | "link": 9, 423 | "tail_corner": 5, 424 | "x": 22.5, 425 | "y": 4.330127018922193 426 | }, 427 | { 428 | "cells": [ 429 | 2, 430 | -1 431 | ], 432 | "corners": [ 433 | 2, 434 | 6 435 | ], 436 | "head_corner": 6, 437 | "id": 5, 438 | "length": 5.773503018922221, 439 | "link": 14, 440 | "tail_corner": 2, 441 | "x": 27.5, 442 | "y": 4.330127018922193 443 | }, 444 | { 445 | "cells": [ 446 | 0, 447 | -1 448 | ], 449 | "corners": [ 450 | 3, 451 | 7 452 | ], 453 | "head_corner": 7, 454 | "id": 6, 455 | "length": 5.773502000000001, 456 | "link": 21, 457 | "tail_corner": 3, 458 | "x": 0.0, 459 | "y": 8.660254037844386 460 | }, 461 | { 462 | "cells": [ 463 | 0, 464 | 1 465 | ], 466 | "corners": [ 467 | 4, 468 | 8 469 | ], 470 | "head_corner": 8, 471 | "id": 7, 472 | "length": 5.773502000000001, 473 | "link": 20, 474 | "tail_corner": 4, 475 | "x": 10.0, 476 | "y": 8.660254037844386 477 | }, 478 | { 479 | "cells": [ 480 | 1, 481 | 2 482 | ], 483 | "corners": [ 484 | 5, 485 | 9 486 | ], 487 | "head_corner": 9, 488 | "id": 8, 489 | "length": 5.773502000000001, 490 | "link": 13, 491 | "tail_corner": 5, 492 | "x": 20.0, 493 | "y": 8.660254037844386 494 | }, 495 | { 496 | "cells": [ 497 | 2, 498 | -1 499 | ], 500 | "corners": [ 501 | 6, 502 | 10 503 | ], 504 | "head_corner": 10, 505 | "id": 9, 506 | "length": 5.773502000000001, 507 | "link": 8, 508 | "tail_corner": 6, 509 | "x": 30.0, 510 | "y": 8.660254037844386 511 | }, 512 | { 513 | "cells": [ 514 | 0, 515 | -1 516 | ], 517 | "corners": [ 518 | 7, 519 | 11 520 | ], 521 | "head_corner": 11, 522 | "id": 10, 523 | "length": 5.773503018922221, 524 | "link": 7, 525 | "tail_corner": 7, 526 | "x": 2.5, 527 | "y": 12.990381056766578 528 | }, 529 | { 530 | "cells": [ 531 | 0, 532 | -1 533 | ], 534 | "corners": [ 535 | 11, 536 | 8 537 | ], 538 | "head_corner": 8, 539 | "id": 11, 540 | "length": 5.773503018922222, 541 | "link": 18, 542 | "tail_corner": 11, 543 | "x": 7.5, 544 | "y": 12.990381056766578 545 | }, 546 | { 547 | "cells": [ 548 | 1, 549 | -1 550 | ], 551 | "corners": [ 552 | 8, 553 | 12 554 | ], 555 | "head_corner": 12, 556 | "id": 12, 557 | "length": 5.773503018922221, 558 | "link": 17, 559 | "tail_corner": 8, 560 | "x": 12.5, 561 | "y": 12.990381056766578 562 | }, 563 | { 564 | "cells": [ 565 | 1, 566 | -1 567 | ], 568 | "corners": [ 569 | 12, 570 | 9 571 | ], 572 | "head_corner": 9, 573 | "id": 13, 574 | "length": 5.773503018922221, 575 | "link": 19, 576 | "tail_corner": 12, 577 | "x": 17.5, 578 | "y": 12.990381056766578 579 | }, 580 | { 581 | "cells": [ 582 | 2, 583 | -1 584 | ], 585 | "corners": [ 586 | 9, 587 | 13 588 | ], 589 | "head_corner": 13, 590 | "id": 14, 591 | "length": 5.773503018922221, 592 | "link": 12, 593 | "tail_corner": 9, 594 | "x": 22.5, 595 | "y": 12.990381056766578 596 | }, 597 | { 598 | "cells": [ 599 | 2, 600 | -1 601 | ], 602 | "corners": [ 603 | 13, 604 | 10 605 | ], 606 | "head_corner": 10, 607 | "id": 15, 608 | "length": 5.773503018922221, 609 | "link": 16, 610 | "tail_corner": 13, 611 | "x": 27.5, 612 | "y": 12.990381056766578 613 | } 614 | ], 615 | "links": [ 616 | { 617 | "face_id": -1, 618 | "head_node": 1, 619 | "id": 0, 620 | "length": 10.0, 621 | "nodes": [ 622 | 0, 623 | 1 624 | ], 625 | "patches": [ 626 | 0, 627 | -1 628 | ], 629 | "status": 4, 630 | "tail_node": 0, 631 | "x": 5.0, 632 | "y": 0.0 633 | }, 634 | { 635 | "face_id": -1, 636 | "head_node": 2, 637 | "id": 1, 638 | "length": 10.0, 639 | "nodes": [ 640 | 1, 641 | 2 642 | ], 643 | "patches": [ 644 | 1, 645 | -1 646 | ], 647 | "status": 4, 648 | "tail_node": 1, 649 | "x": 15.0, 650 | "y": 0.0 651 | }, 652 | { 653 | "face_id": -1, 654 | "head_node": 3, 655 | "id": 2, 656 | "length": 10.0, 657 | "nodes": [ 658 | 2, 659 | 3 660 | ], 661 | "patches": [ 662 | 2, 663 | -1 664 | ], 665 | "status": 4, 666 | "tail_node": 2, 667 | "x": 25.0, 668 | "y": 0.0 669 | }, 670 | { 671 | "face_id": -1, 672 | "head_node": 4, 673 | "id": 3, 674 | "length": 10.0, 675 | "nodes": [ 676 | 0, 677 | 4 678 | ], 679 | "patches": [ 680 | 3, 681 | -1 682 | ], 683 | "status": 4, 684 | "tail_node": 0, 685 | "x": -2.5, 686 | "y": 4.330127018922193 687 | }, 688 | { 689 | "face_id": 0, 690 | "head_node": 5, 691 | "id": 4, 692 | "length": 10.0, 693 | "nodes": [ 694 | 0, 695 | 5 696 | ], 697 | "patches": [ 698 | 0, 699 | 3 700 | ], 701 | "status": 0, 702 | "tail_node": 0, 703 | "x": 2.5, 704 | "y": 4.330127018922193 705 | }, 706 | { 707 | "face_id": 1, 708 | "head_node": 5, 709 | "id": 5, 710 | "length": 10.0, 711 | "nodes": [ 712 | 1, 713 | 5 714 | ], 715 | "patches": [ 716 | 0, 717 | 4 718 | ], 719 | "status": 0, 720 | "tail_node": 1, 721 | "x": 7.5, 722 | "y": 4.330127018922193 723 | }, 724 | { 725 | "face_id": 2, 726 | "head_node": 6, 727 | "id": 6, 728 | "length": 10.0, 729 | "nodes": [ 730 | 1, 731 | 6 732 | ], 733 | "patches": [ 734 | 1, 735 | 4 736 | ], 737 | "status": 0, 738 | "tail_node": 1, 739 | "x": 12.5, 740 | "y": 4.330127018922193 741 | }, 742 | { 743 | "face_id": 3, 744 | "head_node": 6, 745 | "id": 7, 746 | "length": 10.0, 747 | "nodes": [ 748 | 2, 749 | 6 750 | ], 751 | "patches": [ 752 | 1, 753 | 5 754 | ], 755 | "status": 0, 756 | "tail_node": 2, 757 | "x": 17.5, 758 | "y": 4.330127018922193 759 | }, 760 | { 761 | "face_id": 4, 762 | "head_node": 7, 763 | "id": 8, 764 | "length": 10.0, 765 | "nodes": [ 766 | 2, 767 | 7 768 | ], 769 | "patches": [ 770 | 2, 771 | 5 772 | ], 773 | "status": 0, 774 | "tail_node": 2, 775 | "x": 22.5, 776 | "y": 4.330127018922193 777 | }, 778 | { 779 | "face_id": 5, 780 | "head_node": 7, 781 | "id": 9, 782 | "length": 10.0, 783 | "nodes": [ 784 | 3, 785 | 7 786 | ], 787 | "patches": [ 788 | 2, 789 | 6 790 | ], 791 | "status": 0, 792 | "tail_node": 3, 793 | "x": 27.5, 794 | "y": 4.330127018922193 795 | }, 796 | { 797 | "face_id": -1, 798 | "head_node": 8, 799 | "id": 10, 800 | "length": 10.0, 801 | "nodes": [ 802 | 3, 803 | 8 804 | ], 805 | "patches": [ 806 | 6, 807 | -1 808 | ], 809 | "status": 4, 810 | "tail_node": 3, 811 | "x": 32.5, 812 | "y": 4.330127018922193 813 | }, 814 | { 815 | "face_id": 6, 816 | "head_node": 5, 817 | "id": 11, 818 | "length": 10.0, 819 | "nodes": [ 820 | 4, 821 | 5 822 | ], 823 | "patches": [ 824 | 3, 825 | 7 826 | ], 827 | "status": 0, 828 | "tail_node": 4, 829 | "x": 0.0, 830 | "y": 8.660254037844386 831 | }, 832 | { 833 | "face_id": 7, 834 | "head_node": 6, 835 | "id": 12, 836 | "length": 10.0, 837 | "nodes": [ 838 | 5, 839 | 6 840 | ], 841 | "patches": [ 842 | 4, 843 | 8 844 | ], 845 | "status": 0, 846 | "tail_node": 5, 847 | "x": 10.0, 848 | "y": 8.660254037844386 849 | }, 850 | { 851 | "face_id": 8, 852 | "head_node": 7, 853 | "id": 13, 854 | "length": 10.0, 855 | "nodes": [ 856 | 6, 857 | 7 858 | ], 859 | "patches": [ 860 | 5, 861 | 9 862 | ], 863 | "status": 0, 864 | "tail_node": 6, 865 | "x": 20.0, 866 | "y": 8.660254037844386 867 | }, 868 | { 869 | "face_id": 9, 870 | "head_node": 8, 871 | "id": 14, 872 | "length": 10.0, 873 | "nodes": [ 874 | 7, 875 | 8 876 | ], 877 | "patches": [ 878 | 6, 879 | 10 880 | ], 881 | "status": 0, 882 | "tail_node": 7, 883 | "x": 30.0, 884 | "y": 8.660254037844386 885 | }, 886 | { 887 | "face_id": -1, 888 | "head_node": 9, 889 | "id": 15, 890 | "length": 10.0, 891 | "nodes": [ 892 | 4, 893 | 9 894 | ], 895 | "patches": [ 896 | 7, 897 | -1 898 | ], 899 | "status": 4, 900 | "tail_node": 4, 901 | "x": -2.5, 902 | "y": 12.990381056766578 903 | }, 904 | { 905 | "face_id": 10, 906 | "head_node": 9, 907 | "id": 16, 908 | "length": 10.0, 909 | "nodes": [ 910 | 5, 911 | 9 912 | ], 913 | "patches": [ 914 | 7, 915 | 11 916 | ], 917 | "status": 0, 918 | "tail_node": 5, 919 | "x": 2.5, 920 | "y": 12.990381056766578 921 | }, 922 | { 923 | "face_id": 11, 924 | "head_node": 10, 925 | "id": 17, 926 | "length": 10.0, 927 | "nodes": [ 928 | 5, 929 | 10 930 | ], 931 | "patches": [ 932 | 8, 933 | 11 934 | ], 935 | "status": 0, 936 | "tail_node": 5, 937 | "x": 7.5, 938 | "y": 12.990381056766578 939 | }, 940 | { 941 | "face_id": 12, 942 | "head_node": 10, 943 | "id": 18, 944 | "length": 10.0, 945 | "nodes": [ 946 | 6, 947 | 10 948 | ], 949 | "patches": [ 950 | 8, 951 | 12 952 | ], 953 | "status": 0, 954 | "tail_node": 6, 955 | "x": 12.5, 956 | "y": 12.990381056766578 957 | }, 958 | { 959 | "face_id": 13, 960 | "head_node": 11, 961 | "id": 19, 962 | "length": 10.0, 963 | "nodes": [ 964 | 6, 965 | 11 966 | ], 967 | "patches": [ 968 | 9, 969 | 12 970 | ], 971 | "status": 0, 972 | "tail_node": 6, 973 | "x": 17.5, 974 | "y": 12.990381056766578 975 | }, 976 | { 977 | "face_id": 14, 978 | "head_node": 11, 979 | "id": 20, 980 | "length": 10.0, 981 | "nodes": [ 982 | 7, 983 | 11 984 | ], 985 | "patches": [ 986 | 9, 987 | 13 988 | ], 989 | "status": 0, 990 | "tail_node": 7, 991 | "x": 22.5, 992 | "y": 12.990381056766578 993 | }, 994 | { 995 | "face_id": 15, 996 | "head_node": 12, 997 | "id": 21, 998 | "length": 10.0, 999 | "nodes": [ 1000 | 7, 1001 | 12 1002 | ], 1003 | "patches": [ 1004 | 10, 1005 | 13 1006 | ], 1007 | "status": 0, 1008 | "tail_node": 7, 1009 | "x": 27.5, 1010 | "y": 12.990381056766578 1011 | }, 1012 | { 1013 | "face_id": -1, 1014 | "head_node": 12, 1015 | "id": 22, 1016 | "length": 10.0, 1017 | "nodes": [ 1018 | 8, 1019 | 12 1020 | ], 1021 | "patches": [ 1022 | 10, 1023 | -1 1024 | ], 1025 | "status": 4, 1026 | "tail_node": 8, 1027 | "x": 32.5, 1028 | "y": 12.990381056766578 1029 | }, 1030 | { 1031 | "face_id": -1, 1032 | "head_node": 10, 1033 | "id": 23, 1034 | "length": 10.0, 1035 | "nodes": [ 1036 | 9, 1037 | 10 1038 | ], 1039 | "patches": [ 1040 | 11, 1041 | -1 1042 | ], 1043 | "status": 4, 1044 | "tail_node": 9, 1045 | "x": 5.0, 1046 | "y": 17.32050807568877 1047 | }, 1048 | { 1049 | "face_id": -1, 1050 | "head_node": 11, 1051 | "id": 24, 1052 | "length": 10.0, 1053 | "nodes": [ 1054 | 10, 1055 | 11 1056 | ], 1057 | "patches": [ 1058 | 12, 1059 | -1 1060 | ], 1061 | "status": 4, 1062 | "tail_node": 10, 1063 | "x": 15.0, 1064 | "y": 17.32050807568877 1065 | }, 1066 | { 1067 | "face_id": -1, 1068 | "head_node": 12, 1069 | "id": 25, 1070 | "length": 10.0, 1071 | "nodes": [ 1072 | 11, 1073 | 12 1074 | ], 1075 | "patches": [ 1076 | 13, 1077 | -1 1078 | ], 1079 | "status": 4, 1080 | "tail_node": 11, 1081 | "x": 25.0, 1082 | "y": 17.32050807568877 1083 | } 1084 | ], 1085 | "nodes": [ 1086 | { 1087 | "cell": -1, 1088 | "id": 0, 1089 | "link_dirs": [ 1090 | -1, 1091 | -1, 1092 | -1, 1093 | 0, 1094 | 0, 1095 | 0 1096 | ], 1097 | "links": [ 1098 | 0, 1099 | 4, 1100 | 3, 1101 | -1, 1102 | -1, 1103 | -1 1104 | ], 1105 | "neighbor_nodes": [ 1106 | 1, 1107 | 5, 1108 | 4, 1109 | -1, 1110 | -1, 1111 | -1 1112 | ], 1113 | "patches": [ 1114 | 0, 1115 | 3, 1116 | -1, 1117 | -1, 1118 | -1, 1119 | -1 1120 | ], 1121 | "status": 1, 1122 | "x": 0.0, 1123 | "y": 0.0 1124 | }, 1125 | { 1126 | "cell": -1, 1127 | "id": 1, 1128 | "link_dirs": [ 1129 | -1, 1130 | -1, 1131 | -1, 1132 | 1, 1133 | 0, 1134 | 0 1135 | ], 1136 | "links": [ 1137 | 1, 1138 | 6, 1139 | 5, 1140 | 0, 1141 | -1, 1142 | -1 1143 | ], 1144 | "neighbor_nodes": [ 1145 | 2, 1146 | 6, 1147 | 5, 1148 | 0, 1149 | -1, 1150 | -1 1151 | ], 1152 | "patches": [ 1153 | 0, 1154 | 1, 1155 | 4, 1156 | -1, 1157 | -1, 1158 | -1 1159 | ], 1160 | "status": 1, 1161 | "x": 10.0, 1162 | "y": 0.0 1163 | }, 1164 | { 1165 | "cell": -1, 1166 | "id": 2, 1167 | "link_dirs": [ 1168 | -1, 1169 | -1, 1170 | -1, 1171 | 1, 1172 | 0, 1173 | 0 1174 | ], 1175 | "links": [ 1176 | 2, 1177 | 8, 1178 | 7, 1179 | 1, 1180 | -1, 1181 | -1 1182 | ], 1183 | "neighbor_nodes": [ 1184 | 3, 1185 | 7, 1186 | 6, 1187 | 1, 1188 | -1, 1189 | -1 1190 | ], 1191 | "patches": [ 1192 | 1, 1193 | 2, 1194 | 5, 1195 | -1, 1196 | -1, 1197 | -1 1198 | ], 1199 | "status": 1, 1200 | "x": 20.0, 1201 | "y": 0.0 1202 | }, 1203 | { 1204 | "cell": -1, 1205 | "id": 3, 1206 | "link_dirs": [ 1207 | -1, 1208 | -1, 1209 | 1, 1210 | 0, 1211 | 0, 1212 | 0 1213 | ], 1214 | "links": [ 1215 | 10, 1216 | 9, 1217 | 2, 1218 | -1, 1219 | -1, 1220 | -1 1221 | ], 1222 | "neighbor_nodes": [ 1223 | 8, 1224 | 7, 1225 | 2, 1226 | -1, 1227 | -1, 1228 | -1 1229 | ], 1230 | "patches": [ 1231 | 2, 1232 | 6, 1233 | -1, 1234 | -1, 1235 | -1, 1236 | -1 1237 | ], 1238 | "status": 1, 1239 | "x": 30.0, 1240 | "y": 0.0 1241 | }, 1242 | { 1243 | "cell": -1, 1244 | "id": 4, 1245 | "link_dirs": [ 1246 | -1, 1247 | -1, 1248 | 1, 1249 | 0, 1250 | 0, 1251 | 0 1252 | ], 1253 | "links": [ 1254 | 11, 1255 | 15, 1256 | 3, 1257 | -1, 1258 | -1, 1259 | -1 1260 | ], 1261 | "neighbor_nodes": [ 1262 | 5, 1263 | 9, 1264 | 0, 1265 | -1, 1266 | -1, 1267 | -1 1268 | ], 1269 | "patches": [ 1270 | 3, 1271 | 7, 1272 | -1, 1273 | -1, 1274 | -1, 1275 | -1 1276 | ], 1277 | "status": 1, 1278 | "x": -5.0, 1279 | "y": 8.660254037844386 1280 | }, 1281 | { 1282 | "cell": 0, 1283 | "id": 5, 1284 | "link_dirs": [ 1285 | -1, 1286 | -1, 1287 | -1, 1288 | 1, 1289 | 1, 1290 | 1 1291 | ], 1292 | "links": [ 1293 | 12, 1294 | 17, 1295 | 16, 1296 | 11, 1297 | 4, 1298 | 5 1299 | ], 1300 | "neighbor_nodes": [ 1301 | 6, 1302 | 10, 1303 | 9, 1304 | 4, 1305 | 0, 1306 | 1 1307 | ], 1308 | "patches": [ 1309 | 0, 1310 | 3, 1311 | 4, 1312 | 7, 1313 | 8, 1314 | 13 1315 | ], 1316 | "status": 0, 1317 | "x": 5.0, 1318 | "y": 8.660254037844386 1319 | }, 1320 | { 1321 | "cell": 1, 1322 | "id": 6, 1323 | "link_dirs": [ 1324 | -1, 1325 | -1, 1326 | -1, 1327 | 1, 1328 | 1, 1329 | 1 1330 | ], 1331 | "links": [ 1332 | 13, 1333 | 19, 1334 | 18, 1335 | 12, 1336 | 6, 1337 | 7 1338 | ], 1339 | "neighbor_nodes": [ 1340 | 7, 1341 | 11, 1342 | 10, 1343 | 5, 1344 | 1, 1345 | 2 1346 | ], 1347 | "patches": [ 1348 | 1, 1349 | 4, 1350 | 5, 1351 | 8, 1352 | 9, 1353 | 11 1354 | ], 1355 | "status": 0, 1356 | "x": 15.0, 1357 | "y": 8.660254037844386 1358 | }, 1359 | { 1360 | "cell": 2, 1361 | "id": 7, 1362 | "link_dirs": [ 1363 | -1, 1364 | -1, 1365 | -1, 1366 | 1, 1367 | 1, 1368 | 1 1369 | ], 1370 | "links": [ 1371 | 14, 1372 | 21, 1373 | 20, 1374 | 13, 1375 | 8, 1376 | 9 1377 | ], 1378 | "neighbor_nodes": [ 1379 | 8, 1380 | 12, 1381 | 11, 1382 | 6, 1383 | 2, 1384 | 3 1385 | ], 1386 | "patches": [ 1387 | 2, 1388 | 5, 1389 | 6, 1390 | 9, 1391 | 10, 1392 | 12 1393 | ], 1394 | "status": 0, 1395 | "x": 25.0, 1396 | "y": 8.660254037844386 1397 | }, 1398 | { 1399 | "cell": -1, 1400 | "id": 8, 1401 | "link_dirs": [ 1402 | -1, 1403 | 1, 1404 | 1, 1405 | 0, 1406 | 0, 1407 | 0 1408 | ], 1409 | "links": [ 1410 | 22, 1411 | 14, 1412 | 10, 1413 | -1, 1414 | -1, 1415 | -1 1416 | ], 1417 | "neighbor_nodes": [ 1418 | 12, 1419 | 7, 1420 | 3, 1421 | -1, 1422 | -1, 1423 | -1 1424 | ], 1425 | "patches": [ 1426 | 6, 1427 | 10, 1428 | -1, 1429 | -1, 1430 | -1, 1431 | -1 1432 | ], 1433 | "status": 1, 1434 | "x": 35.0, 1435 | "y": 8.660254037844386 1436 | }, 1437 | { 1438 | "cell": -1, 1439 | "id": 9, 1440 | "link_dirs": [ 1441 | -1, 1442 | 1, 1443 | 1, 1444 | 0, 1445 | 0, 1446 | 0 1447 | ], 1448 | "links": [ 1449 | 23, 1450 | 15, 1451 | 16, 1452 | -1, 1453 | -1, 1454 | -1 1455 | ], 1456 | "neighbor_nodes": [ 1457 | 10, 1458 | 4, 1459 | 5, 1460 | -1, 1461 | -1, 1462 | -1 1463 | ], 1464 | "patches": [ 1465 | 7, 1466 | 13, 1467 | -1, 1468 | -1, 1469 | -1, 1470 | -1 1471 | ], 1472 | "status": 1, 1473 | "x": 0.0, 1474 | "y": 17.32050807568877 1475 | }, 1476 | { 1477 | "cell": -1, 1478 | "id": 10, 1479 | "link_dirs": [ 1480 | -1, 1481 | 1, 1482 | 1, 1483 | 1, 1484 | 0, 1485 | 0 1486 | ], 1487 | "links": [ 1488 | 24, 1489 | 23, 1490 | 17, 1491 | 18, 1492 | -1, 1493 | -1 1494 | ], 1495 | "neighbor_nodes": [ 1496 | 11, 1497 | 9, 1498 | 5, 1499 | 6, 1500 | -1, 1501 | -1 1502 | ], 1503 | "patches": [ 1504 | 8, 1505 | 11, 1506 | 13, 1507 | -1, 1508 | -1, 1509 | -1 1510 | ], 1511 | "status": 1, 1512 | "x": 10.0, 1513 | "y": 17.32050807568877 1514 | }, 1515 | { 1516 | "cell": -1, 1517 | "id": 11, 1518 | "link_dirs": [ 1519 | -1, 1520 | 1, 1521 | 1, 1522 | 1, 1523 | 0, 1524 | 0 1525 | ], 1526 | "links": [ 1527 | 25, 1528 | 24, 1529 | 19, 1530 | 20, 1531 | -1, 1532 | -1 1533 | ], 1534 | "neighbor_nodes": [ 1535 | 12, 1536 | 10, 1537 | 6, 1538 | 7, 1539 | -1, 1540 | -1 1541 | ], 1542 | "patches": [ 1543 | 9, 1544 | 11, 1545 | 12, 1546 | -1, 1547 | -1, 1548 | -1 1549 | ], 1550 | "status": 1, 1551 | "x": 20.0, 1552 | "y": 17.32050807568877 1553 | }, 1554 | { 1555 | "cell": -1, 1556 | "id": 12, 1557 | "link_dirs": [ 1558 | 1, 1559 | 1, 1560 | 1, 1561 | 0, 1562 | 0, 1563 | 0 1564 | ], 1565 | "links": [ 1566 | 25, 1567 | 21, 1568 | 22, 1569 | -1, 1570 | -1, 1571 | -1 1572 | ], 1573 | "neighbor_nodes": [ 1574 | 11, 1575 | 7, 1576 | 8, 1577 | -1, 1578 | -1, 1579 | -1 1580 | ], 1581 | "patches": [ 1582 | 10, 1583 | 12, 1584 | -1, 1585 | -1, 1586 | -1, 1587 | -1 1588 | ], 1589 | "status": 1, 1590 | "x": 30.0, 1591 | "y": 17.32050807568877 1592 | } 1593 | ], 1594 | "patches": [ 1595 | { 1596 | "area": 43.30126953125, 1597 | "id": 0, 1598 | "links": [ 1599 | 5, 1600 | 4, 1601 | 0 1602 | ], 1603 | "nodes": [ 1604 | 5, 1605 | 0, 1606 | 1 1607 | ] 1608 | }, 1609 | { 1610 | "area": 43.30126953125, 1611 | "id": 1, 1612 | "links": [ 1613 | 7, 1614 | 6, 1615 | 1 1616 | ], 1617 | "nodes": [ 1618 | 6, 1619 | 1, 1620 | 2 1621 | ] 1622 | }, 1623 | { 1624 | "area": 43.30126953125, 1625 | "id": 2, 1626 | "links": [ 1627 | 9, 1628 | 8, 1629 | 2 1630 | ], 1631 | "nodes": [ 1632 | 7, 1633 | 2, 1634 | 3 1635 | ] 1636 | }, 1637 | { 1638 | "area": 43.30126953125, 1639 | "id": 3, 1640 | "links": [ 1641 | 11, 1642 | 3, 1643 | 4 1644 | ], 1645 | "nodes": [ 1646 | 5, 1647 | 4, 1648 | 0 1649 | ] 1650 | }, 1651 | { 1652 | "area": 43.30126953125, 1653 | "id": 4, 1654 | "links": [ 1655 | 12, 1656 | 5, 1657 | 6 1658 | ], 1659 | "nodes": [ 1660 | 6, 1661 | 5, 1662 | 1 1663 | ] 1664 | }, 1665 | { 1666 | "area": 43.30126190185547, 1667 | "id": 5, 1668 | "links": [ 1669 | 13, 1670 | 7, 1671 | 8 1672 | ], 1673 | "nodes": [ 1674 | 7, 1675 | 6, 1676 | 2 1677 | ] 1678 | }, 1679 | { 1680 | "area": 43.30127716064453, 1681 | "id": 6, 1682 | "links": [ 1683 | 14, 1684 | 9, 1685 | 10 1686 | ], 1687 | "nodes": [ 1688 | 8, 1689 | 7, 1690 | 3 1691 | ] 1692 | }, 1693 | { 1694 | "area": 43.30126953125, 1695 | "id": 7, 1696 | "links": [ 1697 | 16, 1698 | 15, 1699 | 11 1700 | ], 1701 | "nodes": [ 1702 | 9, 1703 | 4, 1704 | 5 1705 | ] 1706 | }, 1707 | { 1708 | "area": 43.30126953125, 1709 | "id": 8, 1710 | "links": [ 1711 | 18, 1712 | 17, 1713 | 12 1714 | ], 1715 | "nodes": [ 1716 | 10, 1717 | 5, 1718 | 6 1719 | ] 1720 | }, 1721 | { 1722 | "area": 43.30126953125, 1723 | "id": 9, 1724 | "links": [ 1725 | 20, 1726 | 19, 1727 | 13 1728 | ], 1729 | "nodes": [ 1730 | 11, 1731 | 6, 1732 | 7 1733 | ] 1734 | }, 1735 | { 1736 | "area": 43.30126953125, 1737 | "id": 10, 1738 | "links": [ 1739 | 22, 1740 | 21, 1741 | 14 1742 | ], 1743 | "nodes": [ 1744 | 12, 1745 | 7, 1746 | 8 1747 | ] 1748 | }, 1749 | { 1750 | "area": 43.30126953125, 1751 | "id": 11, 1752 | "links": [ 1753 | 23, 1754 | 16, 1755 | 17 1756 | ], 1757 | "nodes": [ 1758 | 10, 1759 | 9, 1760 | 5 1761 | ] 1762 | }, 1763 | { 1764 | "area": 43.30126953125, 1765 | "id": 12, 1766 | "links": [ 1767 | 24, 1768 | 18, 1769 | 19 1770 | ], 1771 | "nodes": [ 1772 | 11, 1773 | 6, 1774 | 10 1775 | ] 1776 | }, 1777 | { 1778 | "area": 43.30126953125, 1779 | "id": 13, 1780 | "links": [ 1781 | 25, 1782 | 20, 1783 | 21 1784 | ], 1785 | "nodes": [ 1786 | 12, 1787 | 7, 1788 | 11 1789 | ] 1790 | } 1791 | ] 1792 | } 1793 | --------------------------------------------------------------------------------