├── .gitignore ├── LICENSE ├── README.md ├── docs ├── dochack.js ├── nimdoc.out.css ├── src │ ├── graph.html │ ├── graph.idx │ └── graph │ │ ├── backendpng.html │ │ ├── backendpng.idx │ │ ├── backendsvg.html │ │ ├── backendsvg.idx │ │ ├── color.html │ │ ├── color.idx │ │ ├── plot.html │ │ ├── plot.idx │ │ ├── surface.html │ │ └── surface.idx └── theindex.html ├── examples ├── example1.nim ├── example1.png ├── example1.svg ├── example2.nim ├── example2.png ├── example2.svg ├── examples.nim └── nim.cfg ├── graph.nimble ├── notes ├── current.nim ├── currentpng.png ├── currentsvg.svg ├── matplotlib-colors.png ├── target.md ├── targetpng.png └── targetsvg.svg └── src ├── graph.nim └── graph ├── backendpng.nim ├── backendsvg.nim ├── color.nim ├── plot.nim └── surface.nim /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore files with no extention: 2 | * 3 | !*/ 4 | !*.* 5 | 6 | # normal ignores: 7 | *.exe 8 | nimcache 9 | *.pdb 10 | *.ilk 11 | .* 12 | *.pdf 13 | references 14 | tdraw.png 15 | tdraw.svg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Stisa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in the 7 | Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, subject 10 | to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 19 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Graph 2 | ===== 3 | 4 | This is a basic plotting library, written in [nim](http://nim-lang.org). 5 | The end goal is to have a tiny plotting lib to use with [jupyternim](https://github.com/stisa/jupyternim) 6 | Outputs `.png` or `.svg` files, or a string that contains the `png` as binary data or the `svg` as string. 7 | 8 | For what I want to achieve and where I'm at, see [target](notes/target.md) 9 | 10 | Some examples are in [examples](examples): 11 | 12 | **Note: text labels are WIP, only for svg atm** 13 | 14 | ### Example 15 | ![current](notes/currentsvg.svg) 16 | ```nim 17 | import graph, math, arraymancer 18 | let 19 | x = arange(0.0'f64, 10,0.1) 20 | let 21 | y = sin(x) 22 | y2 = cos(x) 23 | var srf = plot(x.data,y.data) 24 | srf.plot(x.data, y2.data) 25 | srf.grid 26 | # Save to file 27 | srf.saveTo("currentpng.png") 28 | srf.saveTo("currentsvg.svg") 29 | ``` 30 | 31 | ## Current structure 32 | - **graph**: exposes everything ( basic functionality ) 33 | 34 | Inside `graph` there are specific apis: 35 | - color: exposes various colours and the proc `color(r,g,b,a)` 36 | - plot: initializing the plot, adding plots 37 | - surface: the implementation of `Surface` and `Axis` 38 | - `backend`: handles rendering the plot 39 | 40 | ## TODO: 41 | 42 | * matplotlib defaults 43 | - figure size is 6.4x4.8" 44 | - dpi is 100 45 | * [target style](notes/target.md) 46 | * plotProc should lazily evaluate the proc? 47 | * better integration with Arraymancer (a Concept that matches if .data and [] ?) 48 | * integrate chroma? (need to contribute blend?) 49 | * separate drawing layers for the plot and the background/names/etc so that lines aren't overwritten 50 | * can I use Arraymancer's tensor without blas? Would they work in js? 51 | * documentation 52 | * looks like matplotlib does some spline/approximation stuff to get that smooth 53 | * nope, checked the svg and it's plain lines => I need a better line algo 54 | * distinguish margin and padding 55 | * calculate max/min y value str len and adjust eg left margin to fit labels 56 | * svg backend: less strings, more nodes -------------------------------------------------------------------------------- /docs/nimdoc.out.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stylesheet for use with Docutils/rst2html. 3 | 4 | See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to 5 | customize this style sheet. 6 | 7 | Modified from Chad Skeeters' rst2html-style 8 | https://bitbucket.org/cskeeters/rst2html-style/ 9 | 10 | Modified by Boyd Greenfield and narimiran 11 | */ 12 | 13 | :root { 14 | --primary-background: #fff; 15 | --secondary-background: ghostwhite; 16 | --third-background: #e8e8e8; 17 | --border: #dde; 18 | --text: #222; 19 | --anchor: #07b; 20 | --anchor-focus: #607c9f; 21 | --input-focus: #1fa0eb; 22 | --strong: #3c3c3c; 23 | --hint: #9A9A9A; 24 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1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"); 25 | 26 | --keyword: #5e8f60; 27 | --identifier: #222; 28 | --comment: #484a86; 29 | --operator: #155da4; 30 | --punctuation: black; 31 | --other: black; 32 | --escapeSequence: #c4891b; 33 | --number: #252dbe; 34 | --literal: #a4255b; 35 | --raw-data: #a4255b; 36 | } 37 | 38 | [data-theme="dark"] { 39 | --primary-background: #171921; 40 | --secondary-background: #1e202a; 41 | --third-background: #2b2e3b; 42 | --border: #0e1014; 43 | --text: #fff; 44 | --anchor: #8be9fd; 45 | --anchor-focus: #8be9fd; 46 | --input-focus: #8be9fd; 47 | --strong: #bd93f9; 48 | --hint: #7A7C85; 49 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); 50 | 51 | --keyword: #ff79c6; 52 | --identifier: #f8f8f2; 53 | --comment: #6272a4; 54 | --operator: #ff79c6; 55 | --punctuation: #f8f8f2; 56 | --other: #f8f8f2; 57 | --escapeSequence: #bd93f9; 58 | --number: #bd93f9; 59 | --literal: #f1fa8c; 60 | --raw-data: #8be9fd; 61 | } 62 | 63 | .theme-switch-wrapper { 64 | display: flex; 65 | align-items: center; 66 | } 67 | 68 | .theme-switch-wrapper em { 69 | margin-left: 10px; 70 | font-size: 1rem; 71 | } 72 | 73 | .theme-switch { 74 | display: inline-block; 75 | height: 22px; 76 | position: relative; 77 | width: 50px; 78 | } 79 | 80 | .theme-switch input { 81 | display: none; 82 | } 83 | 84 | .slider { 85 | background-color: #ccc; 86 | bottom: 0; 87 | cursor: pointer; 88 | left: 0; 89 | position: absolute; 90 | right: 0; 91 | top: 0; 92 | transition: .4s; 93 | } 94 | 95 | .slider:before { 96 | background-color: #fff; 97 | bottom: 4px; 98 | content: ""; 99 | height: 13px; 100 | left: 4px; 101 | position: absolute; 102 | transition: .4s; 103 | width: 13px; 104 | } 105 | 106 | input:checked + .slider { 107 | background-color: #66bb6a; 108 | } 109 | 110 | input:checked + .slider:before { 111 | transform: translateX(26px); 112 | } 113 | 114 | .slider.round { 115 | border-radius: 17px; 116 | } 117 | 118 | .slider.round:before { 119 | border-radius: 50%; 120 | } 121 | 122 | html { 123 | font-size: 100%; 124 | -webkit-text-size-adjust: 100%; 125 | -ms-text-size-adjust: 100%; } 126 | 127 | body { 128 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 129 | font-weight: 400; 130 | font-size: 1.125em; 131 | line-height: 1.5; 132 | color: var(--text); 133 | background-color: var(--primary-background); } 134 | 135 | /* Skeleton grid */ 136 | .container { 137 | position: relative; 138 | width: 100%; 139 | max-width: 1050px; 140 | margin: 0 auto; 141 | padding: 0; 142 | box-sizing: border-box; } 143 | 144 | .column, 145 | .columns { 146 | width: 100%; 147 | float: left; 148 | box-sizing: border-box; 149 | margin-left: 1%; 150 | } 151 | 152 | .column:first-child, 153 | .columns:first-child { 154 | margin-left: 0; } 155 | 156 | .three.columns { 157 | width: 19%; } 158 | 159 | .nine.columns { 160 | width: 80.0%; } 161 | 162 | .twelve.columns { 163 | width: 100%; 164 | margin-left: 0; } 165 | 166 | @media screen and (max-width: 860px) { 167 | .three.columns { 168 | display: none; 169 | } 170 | .nine.columns { 171 | width: 98.0%; 172 | } 173 | body { 174 | font-size: 1em; 175 | line-height: 1.35; 176 | } 177 | } 178 | 179 | cite { 180 | font-style: italic !important; } 181 | 182 | 183 | /* Nim search input */ 184 | div#searchInputDiv { 185 | margin-bottom: 1em; 186 | } 187 | input#searchInput { 188 | width: 80%; 189 | } 190 | 191 | /* 192 | * Some custom formatting for input forms. 193 | * This also fixes input form colors on Firefox with a dark system theme on Linux. 194 | */ 195 | input { 196 | -moz-appearance: none; 197 | background-color: var(--secondary-background); 198 | color: var(--text); 199 | border: 1px solid var(--border); 200 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 201 | font-size: 0.9em; 202 | padding: 6px; 203 | } 204 | 205 | input:focus { 206 | border: 1px solid var(--input-focus); 207 | box-shadow: 0 0 3px var(--input-focus); 208 | } 209 | 210 | select { 211 | -moz-appearance: none; 212 | background-color: var(--secondary-background); 213 | color: var(--text); 214 | border: 1px solid var(--border); 215 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 216 | font-size: 0.9em; 217 | padding: 6px; 218 | } 219 | 220 | select:focus { 221 | border: 1px solid var(--input-focus); 222 | box-shadow: 0 0 3px var(--input-focus); 223 | } 224 | 225 | /* Docgen styles */ 226 | /* Links */ 227 | a { 228 | color: var(--anchor); 229 | text-decoration: none; 230 | } 231 | 232 | a span.Identifier { 233 | text-decoration: underline; 234 | text-decoration-color: #aab; 235 | } 236 | 237 | a.reference-toplevel { 238 | font-weight: bold; 239 | } 240 | 241 | a.toc-backref { 242 | text-decoration: none; 243 | color: var(--text); } 244 | 245 | a.link-seesrc { 246 | color: #607c9f; 247 | font-size: 0.9em; 248 | font-style: italic; } 249 | 250 | a:hover, 251 | a:focus { 252 | color: var(--anchor-focus); 253 | text-decoration: underline; } 254 | 255 | a:hover span.Identifier { 256 | color: var(--anchor); 257 | } 258 | 259 | 260 | sub, 261 | sup { 262 | position: relative; 263 | font-size: 75%; 264 | line-height: 0; 265 | vertical-align: baseline; } 266 | 267 | sup { 268 | top: -0.5em; } 269 | 270 | sub { 271 | bottom: -0.25em; } 272 | 273 | img { 274 | width: auto; 275 | height: auto; 276 | max-width: 100%; 277 | vertical-align: middle; 278 | border: 0; 279 | -ms-interpolation-mode: bicubic; } 280 | 281 | @media print { 282 | * { 283 | color: black !important; 284 | text-shadow: none !important; 285 | background: transparent !important; 286 | box-shadow: none !important; } 287 | 288 | a, 289 | a:visited { 290 | text-decoration: underline; } 291 | 292 | a[href]:after { 293 | content: " (" attr(href) ")"; } 294 | 295 | abbr[title]:after { 296 | content: " (" attr(title) ")"; } 297 | 298 | .ir a:after, 299 | a[href^="javascript:"]:after, 300 | a[href^="#"]:after { 301 | content: ""; } 302 | 303 | pre, 304 | blockquote { 305 | border: 1px solid #999; 306 | page-break-inside: avoid; } 307 | 308 | thead { 309 | display: table-header-group; } 310 | 311 | tr, 312 | img { 313 | page-break-inside: avoid; } 314 | 315 | img { 316 | max-width: 100% !important; } 317 | 318 | @page { 319 | margin: 0.5cm; } 320 | 321 | h1 { 322 | page-break-before: always; } 323 | 324 | h1.title { 325 | page-break-before: avoid; } 326 | 327 | p, 328 | h2, 329 | h3 { 330 | orphans: 3; 331 | widows: 3; } 332 | 333 | h2, 334 | h3 { 335 | page-break-after: avoid; } 336 | } 337 | 338 | 339 | p { 340 | margin-top: 0.5em; 341 | margin-bottom: 0.5em; 342 | } 343 | 344 | small { 345 | font-size: 85%; } 346 | 347 | strong { 348 | font-weight: 600; 349 | font-size: 0.95em; 350 | color: var(--strong); 351 | } 352 | 353 | em { 354 | font-style: italic; } 355 | 356 | h1 { 357 | font-size: 1.8em; 358 | font-weight: 400; 359 | padding-bottom: .25em; 360 | border-bottom: 6px solid var(--third-background); 361 | margin-top: 2.5em; 362 | margin-bottom: 1em; 363 | line-height: 1.2em; } 364 | 365 | h1.title { 366 | padding-bottom: 1em; 367 | border-bottom: 0px; 368 | font-size: 2.5em; 369 | text-align: center; 370 | font-weight: 900; 371 | margin-top: 0.75em; 372 | margin-bottom: 0em; 373 | } 374 | 375 | h2 { 376 | font-size: 1.3em; 377 | margin-top: 2em; } 378 | 379 | h2.subtitle { 380 | text-align: center; } 381 | 382 | h3 { 383 | font-size: 1.125em; 384 | font-style: italic; 385 | margin-top: 1.5em; } 386 | 387 | h4 { 388 | font-size: 1.125em; 389 | margin-top: 1em; } 390 | 391 | h5 { 392 | font-size: 1.125em; 393 | margin-top: 0.75em; } 394 | 395 | h6 { 396 | font-size: 1.1em; } 397 | 398 | 399 | ul, 400 | ol { 401 | padding: 0; 402 | margin-top: 0.5em; 403 | margin-left: 0.75em; } 404 | 405 | ul ul, 406 | ul ol, 407 | ol ol, 408 | ol ul { 409 | margin-bottom: 0; 410 | margin-left: 1.25em; } 411 | 412 | li { 413 | list-style-type: circle; 414 | } 415 | 416 | ul.simple-boot li { 417 | list-style-type: none; 418 | margin-left: 0em; 419 | margin-bottom: 0.5em; 420 | } 421 | 422 | ol.simple > li, ul.simple > li { 423 | margin-bottom: 0.25em; 424 | margin-left: 0.4em } 425 | 426 | ul.simple.simple-toc > li { 427 | margin-top: 1em; 428 | } 429 | 430 | ul.simple-toc { 431 | list-style: none; 432 | font-size: 0.9em; 433 | margin-left: -0.3em; 434 | margin-top: 1em; } 435 | 436 | ul.simple-toc > li { 437 | list-style-type: none; 438 | } 439 | 440 | ul.simple-toc-section { 441 | list-style-type: circle; 442 | margin-left: 1em; 443 | color: #6c9aae; } 444 | 445 | 446 | ol.arabic { 447 | list-style: decimal; } 448 | 449 | ol.loweralpha { 450 | list-style: lower-alpha; } 451 | 452 | ol.upperalpha { 453 | list-style: upper-alpha; } 454 | 455 | ol.lowerroman { 456 | list-style: lower-roman; } 457 | 458 | ol.upperroman { 459 | list-style: upper-roman; } 460 | 461 | ul.auto-toc { 462 | list-style-type: none; } 463 | 464 | 465 | dl { 466 | margin-bottom: 1.5em; } 467 | 468 | dt { 469 | margin-bottom: -0.5em; 470 | margin-left: 0.0em; } 471 | 472 | dd { 473 | margin-left: 2.0em; 474 | margin-bottom: 3.0em; 475 | margin-top: 0.5em; } 476 | 477 | 478 | hr { 479 | margin: 2em 0; 480 | border: 0; 481 | border-top: 1px solid #aaa; } 482 | 483 | blockquote { 484 | font-size: 0.9em; 485 | font-style: italic; 486 | padding-left: 0.5em; 487 | margin-left: 0; 488 | border-left: 5px solid #bbc; 489 | } 490 | 491 | .pre { 492 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 493 | font-weight: 500; 494 | font-size: 0.85em; 495 | color: var(--text); 496 | background-color: var(--third-background); 497 | padding-left: 3px; 498 | padding-right: 3px; 499 | border-radius: 4px; 500 | } 501 | 502 | pre { 503 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 504 | color: var(--text); 505 | font-weight: 500; 506 | display: inline-block; 507 | box-sizing: border-box; 508 | min-width: 100%; 509 | padding: 0.5em; 510 | margin-top: 0.5em; 511 | margin-bottom: 0.5em; 512 | font-size: 0.85em; 513 | white-space: pre !important; 514 | overflow-y: hidden; 515 | overflow-x: visible; 516 | background-color: var(--secondary-background); 517 | border: 1px solid var(--border); 518 | -webkit-border-radius: 6px; 519 | -moz-border-radius: 6px; 520 | border-radius: 6px; } 521 | 522 | .pre-scrollable { 523 | max-height: 340px; 524 | overflow-y: scroll; } 525 | 526 | 527 | /* Nim line-numbered tables */ 528 | .line-nums-table { 529 | width: 100%; 530 | table-layout: fixed; } 531 | 532 | table.line-nums-table { 533 | border-radius: 4px; 534 | border: 1px solid #cccccc; 535 | background-color: ghostwhite; 536 | border-collapse: separate; 537 | margin-top: 15px; 538 | margin-bottom: 25px; } 539 | 540 | .line-nums-table tbody { 541 | border: none; } 542 | 543 | .line-nums-table td pre { 544 | border: none; 545 | background-color: transparent; } 546 | 547 | .line-nums-table td.blob-line-nums { 548 | width: 28px; } 549 | 550 | .line-nums-table td.blob-line-nums pre { 551 | color: #b0b0b0; 552 | -webkit-filter: opacity(75%); 553 | text-align: right; 554 | border-color: transparent; 555 | background-color: transparent; 556 | padding-left: 0px; 557 | margin-left: 0px; 558 | padding-right: 0px; 559 | margin-right: 0px; } 560 | 561 | 562 | table { 563 | max-width: 100%; 564 | background-color: transparent; 565 | margin-top: 0.5em; 566 | margin-bottom: 1.5em; 567 | border-collapse: collapse; 568 | border-color: var(--third-background); 569 | border-spacing: 0; 570 | font-size: 0.9em; 571 | } 572 | 573 | table th, table td { 574 | padding: 0px 0.5em 0px; 575 | border-color: var(--third-background); 576 | } 577 | 578 | table th { 579 | background-color: var(--third-background); 580 | border-color: var(--third-background); 581 | font-weight: bold; } 582 | 583 | table th.docinfo-name { 584 | background-color: transparent; 585 | } 586 | 587 | table tr:hover { 588 | background-color: var(--third-background); } 589 | 590 | 591 | /* rst2html default used to remove borders from tables and images */ 592 | .borderless, table.borderless td, table.borderless th { 593 | border: 0; } 594 | 595 | table.borderless td, table.borderless th { 596 | /* Override padding for "table.docutils td" with "! important". 597 | The right padding separates the table cells. */ 598 | padding: 0 0.5em 0 0 !important; } 599 | 600 | .first { 601 | /* Override more specific margin styles with "! important". */ 602 | margin-top: 0 !important; } 603 | 604 | .last, .with-subtitle { 605 | margin-bottom: 0 !important; } 606 | 607 | .hidden { 608 | display: none; } 609 | 610 | blockquote.epigraph { 611 | margin: 2em 5em; } 612 | 613 | dl.docutils dd { 614 | margin-bottom: 0.5em; } 615 | 616 | object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { 617 | overflow: hidden; } 618 | 619 | 620 | div.figure { 621 | margin-left: 2em; 622 | margin-right: 2em; } 623 | 624 | div.footer, div.header { 625 | clear: both; 626 | text-align: center; 627 | color: #666; 628 | font-size: smaller; } 629 | 630 | div.footer { 631 | padding-top: 5em; 632 | } 633 | 634 | div.line-block { 635 | display: block; 636 | margin-top: 1em; 637 | margin-bottom: 1em; } 638 | 639 | div.line-block div.line-block { 640 | margin-top: 0; 641 | margin-bottom: 0; 642 | margin-left: 1.5em; } 643 | 644 | div.topic { 645 | margin: 2em; } 646 | 647 | div.search_results { 648 | background-color: var(--third-background); 649 | margin: 3em; 650 | padding: 1em; 651 | border: 1px solid #4d4d4d; 652 | } 653 | 654 | div#global-links ul { 655 | margin-left: 0; 656 | list-style-type: none; 657 | } 658 | 659 | div#global-links > simple-boot { 660 | margin-left: 3em; 661 | } 662 | 663 | hr.docutils { 664 | width: 75%; } 665 | 666 | img.align-left, .figure.align-left, object.align-left { 667 | clear: left; 668 | float: left; 669 | margin-right: 1em; } 670 | 671 | img.align-right, .figure.align-right, object.align-right { 672 | clear: right; 673 | float: right; 674 | margin-left: 1em; } 675 | 676 | img.align-center, .figure.align-center, object.align-center { 677 | display: block; 678 | margin-left: auto; 679 | margin-right: auto; } 680 | 681 | .align-left { 682 | text-align: left; } 683 | 684 | .align-center { 685 | clear: both; 686 | text-align: center; } 687 | 688 | .align-right { 689 | text-align: right; } 690 | 691 | /* reset inner alignment in figures */ 692 | div.align-right { 693 | text-align: inherit; } 694 | 695 | p.attribution { 696 | text-align: right; 697 | margin-left: 50%; } 698 | 699 | p.caption { 700 | font-style: italic; } 701 | 702 | p.credits { 703 | font-style: italic; 704 | font-size: smaller; } 705 | 706 | p.label { 707 | white-space: nowrap; } 708 | 709 | p.rubric { 710 | font-weight: bold; 711 | font-size: larger; 712 | color: maroon; 713 | text-align: center; } 714 | 715 | p.topic-title { 716 | font-weight: bold; } 717 | 718 | pre.address { 719 | margin-bottom: 0; 720 | margin-top: 0; 721 | font: inherit; } 722 | 723 | pre.literal-block, pre.doctest-block, pre.math, pre.code { 724 | margin-left: 2em; 725 | margin-right: 2em; } 726 | 727 | pre.code .ln { 728 | color: grey; } 729 | 730 | /* line numbers */ 731 | pre.code, code { 732 | background-color: #eeeeee; } 733 | 734 | pre.code .comment, code .comment { 735 | color: #5c6576; } 736 | 737 | pre.code .keyword, code .keyword { 738 | color: #3B0D06; 739 | font-weight: bold; } 740 | 741 | pre.code .literal.string, code .literal.string { 742 | color: #0c5404; } 743 | 744 | pre.code .name.builtin, code .name.builtin { 745 | color: #352b84; } 746 | 747 | pre.code .deleted, code .deleted { 748 | background-color: #DEB0A1; } 749 | 750 | pre.code .inserted, code .inserted { 751 | background-color: #A3D289; } 752 | 753 | span.classifier { 754 | font-style: oblique; } 755 | 756 | span.classifier-delimiter { 757 | font-weight: bold; } 758 | 759 | span.option { 760 | white-space: nowrap; } 761 | 762 | span.problematic { 763 | color: #b30000; } 764 | 765 | span.section-subtitle { 766 | /* font-size relative to parent (h1..h6 element) */ 767 | font-size: 80%; } 768 | 769 | span.DecNumber { 770 | color: var(--number); } 771 | 772 | span.BinNumber { 773 | color: var(--number); } 774 | 775 | span.HexNumber { 776 | color: var(--number); } 777 | 778 | span.OctNumber { 779 | color: var(--number); } 780 | 781 | span.FloatNumber { 782 | color: var(--number); } 783 | 784 | span.Identifier { 785 | color: var(--identifier); } 786 | 787 | span.Keyword { 788 | font-weight: 600; 789 | color: var(--keyword); } 790 | 791 | span.StringLit { 792 | color: var(--literal); } 793 | 794 | span.LongStringLit { 795 | color: var(--literal); } 796 | 797 | span.CharLit { 798 | color: var(--literal); } 799 | 800 | span.EscapeSequence { 801 | color: var(--escapeSequence); } 802 | 803 | span.Operator { 804 | color: var(--operator); } 805 | 806 | span.Punctuation { 807 | color: var(--punctuation); } 808 | 809 | span.Comment, span.LongComment { 810 | font-style: italic; 811 | font-weight: 400; 812 | color: var(--comment); } 813 | 814 | span.RegularExpression { 815 | color: darkviolet; } 816 | 817 | span.TagStart { 818 | color: darkviolet; } 819 | 820 | span.TagEnd { 821 | color: darkviolet; } 822 | 823 | span.Key { 824 | color: #252dbe; } 825 | 826 | span.Value { 827 | color: #252dbe; } 828 | 829 | span.RawData { 830 | color: var(--raw-data); } 831 | 832 | span.Assembler { 833 | color: #252dbe; } 834 | 835 | span.Preprocessor { 836 | color: #252dbe; } 837 | 838 | span.Directive { 839 | color: #252dbe; } 840 | 841 | span.Command, span.Rule, span.Hyperlink, span.Label, span.Reference, 842 | span.Other { 843 | color: var(--other); } 844 | 845 | /* Pop type, const, proc, and iterator defs in nim def blocks */ 846 | dt pre > span.Identifier, dt pre > span.Operator { 847 | color: var(--identifier); 848 | font-weight: 700; } 849 | 850 | dt pre > span.Keyword ~ span.Identifier, dt pre > span.Identifier ~ span.Identifier, 851 | dt pre > span.Operator ~ span.Identifier, dt pre > span.Other ~ span.Identifier { 852 | color: var(--identifier); 853 | font-weight: inherit; } 854 | 855 | /* Nim sprite for the footer (taken from main page favicon) */ 856 | .nim-sprite { 857 | display: inline-block; 858 | width: 51px; 859 | height: 14px; 860 | background-position: 0 0; 861 | background-size: 51px 14px; 862 | -webkit-filter: opacity(50%); 863 | background-repeat: no-repeat; 864 | background-image: var(--nim-sprite-base64); 865 | margin-bottom: 5px; } 866 | 867 | span.pragmadots { 868 | /* Position: relative frees us up to make the dots 869 | look really nice without fucking up the layout and 870 | causing bulging in the parent container */ 871 | position: relative; 872 | /* 1px down looks slightly nicer */ 873 | top: 1px; 874 | padding: 2px; 875 | background-color: var(--third-background); 876 | border-radius: 4px; 877 | margin: 0 2px; 878 | cursor: pointer; 879 | font-size: 0.8em; 880 | } 881 | 882 | span.pragmadots:hover { 883 | background-color: var(--hint); 884 | } 885 | span.pragmawrap { 886 | display: none; 887 | } 888 | 889 | span.attachedType { 890 | display: none; 891 | visibility: hidden; 892 | } 893 | -------------------------------------------------------------------------------- /docs/src/graph.idx: -------------------------------------------------------------------------------- 1 | saveTo src/graph.html#saveTo,Surface,string graph: saveTo(sur: var Surface; filename: string) 2 | saveTo src/graph.html#saveTo,Surface,string_2 graph: saveTo(sur: Surface; filename: string) 3 | -------------------------------------------------------------------------------- /docs/src/graph/backendpng.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | src/graph/backendpng 18 | 1171 | 1172 | 1197 | 1207 | 1208 | 1209 |
1210 |
1211 |

src/graph/backendpng

1212 |
1213 |
1214 |
    1215 |
  • 1216 | Imports 1217 |
      1218 | 1219 |
    1220 |
  • 1221 |
  • 1222 | Procs 1223 | 1230 |
  • 1231 | 1232 |
1233 | 1234 |
1235 |
1236 |
1237 |

1238 |
1239 |

Imports

1240 |
1241 | surface, color 1242 |
1243 |
1244 |

Procs

1245 |
1246 |
proc saveToPng(sur: var Surface; filename: string) {...}{.raises: [Exception, OSError],
1247 |     tags: [WriteIOEffect].}
1248 |
1249 | Convience function. Saves img into filename 1250 | 1251 |
1252 |
proc png(sur: var Surface): PNG[string] {...}{.raises: [PNGError, NZError], tags: [].}
1253 |
1254 | 1255 | 1256 |
1257 | 1258 |
1259 | 1260 |
1261 |
1262 | 1263 |
1264 | 1269 |
1270 |
1271 |
1272 | 1273 | 1274 | 1275 | -------------------------------------------------------------------------------- /docs/src/graph/backendpng.idx: -------------------------------------------------------------------------------- 1 | saveToPng src/graph/backendpng.html#saveToPng,Surface,string backendpng: saveToPng(sur: var Surface; filename: string) 2 | png src/graph/backendpng.html#png,Surface backendpng: png(sur: var Surface): PNG[string] 3 | -------------------------------------------------------------------------------- /docs/src/graph/backendsvg.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | src/graph/backendsvg 18 | 1171 | 1172 | 1197 | 1207 | 1208 | 1209 |
1210 |
1211 |

src/graph/backendsvg

1212 |
1213 |
1214 |
    1215 |
  • 1216 | Imports 1217 |
      1218 | 1219 |
    1220 |
  • 1221 |
  • 1222 | Procs 1223 | 1230 |
  • 1231 | 1232 |
1233 | 1234 |
1235 |
1236 |
1237 |

1238 |
1239 |

Imports

1240 |
1241 | surface, color 1242 |
1243 |
1244 |

Procs

1245 |
1246 |
proc svg(s: Surface): string {...}{.raises: [ValueError], tags: [].}
1247 |
1248 | 1249 | 1250 |
1251 |
proc saveToSvg(s: Surface; f: string) {...}{.raises: [IOError, ValueError],
1252 |                                    tags: [WriteIOEffect].}
1253 |
1254 | 1255 | 1256 |
1257 | 1258 |
1259 | 1260 |
1261 |
1262 | 1263 |
1264 | 1269 |
1270 |
1271 |
1272 | 1273 | 1274 | 1275 | -------------------------------------------------------------------------------- /docs/src/graph/backendsvg.idx: -------------------------------------------------------------------------------- 1 | svg src/graph/backendsvg.html#svg,Surface backendsvg: svg(s: Surface): string 2 | saveToSvg src/graph/backendsvg.html#saveToSvg,Surface,string backendsvg: saveToSvg(s: Surface; f: string) 3 | -------------------------------------------------------------------------------- /docs/src/graph/color.idx: -------------------------------------------------------------------------------- 1 | Color src/graph/color.html#Color color: Color 2 | Transparent src/graph/color.html#Transparent color: Transparent 3 | Black src/graph/color.html#Black color: Black 4 | Blue src/graph/color.html#Blue color: Blue 5 | Green src/graph/color.html#Green color: Green 6 | Red src/graph/color.html#Red color: Red 7 | Purple src/graph/color.html#Purple color: Purple 8 | Yellow src/graph/color.html#Yellow color: Yellow 9 | White src/graph/color.html#White color: White 10 | HalfTBlack src/graph/color.html#HalfTBlack color: HalfTBlack 11 | HalfTBlue src/graph/color.html#HalfTBlue color: HalfTBlue 12 | HalfTGreen src/graph/color.html#HalfTGreen color: HalfTGreen 13 | HalfTRed src/graph/color.html#HalfTRed color: HalfTRed 14 | HalftWhite src/graph/color.html#HalftWhite color: HalftWhite 15 | Viridis src/graph/color.html#Viridis color: Viridis 16 | toColor src/graph/color.html#toColor.c,Viridis color: toColor(v: Viridis): Color 17 | color src/graph/color.html#color,int,int,int,int color: color(r, g, b, a: int = 0): Color 18 | withAlpha src/graph/color.html#withAlpha,Color,int color: withAlpha(c: Color; a: int = 0): Color 19 | blend src/graph/color.html#blend,Color,Color color: blend(orig: var Color; newc: Color) 20 | blend src/graph/color.html#blend,Color,Color_2 color: blend(orig: Color; newc: Color): Color 21 | `$` src/graph/color.html#$,Color color: `$`(c: Color): string 22 | -------------------------------------------------------------------------------- /docs/src/graph/plot.idx: -------------------------------------------------------------------------------- 1 | New way ideas: src/graph/plot.html#new-way-ideascolon New way ideas: 2 | plot src/graph/plot.html#plot,Surface,openArray[T],openArray[T],Color plot: plot[T: SomeFloat](sur: var Surface; x, y: openArray[T]; col: Color = Viridis.orange) 3 | plot src/graph/plot.html#plot,Surface,openArray[T],proc(T),Color plot: plot[T: SomeFloat](sur: var Surface; x: openArray[T]; y: proc (x: T): T;\n col: Color = Viridis.orange) 4 | plot src/graph/plot.html#plot,openArray[float],openArray[float],Color,Color,tuple[float,float],int,bool,bool plot: plot(x, y: openArray[float]; lncolor: Color = Viridis.blue; bgColor: Color = White;\n origin: tuple[x0, y0: float] = (0.0, 0.0); padding = 20; grid: bool = false;\n box: bool = true; ticks = true; size = [432, 288]): Surface 5 | -------------------------------------------------------------------------------- /docs/src/graph/surface.idx: -------------------------------------------------------------------------------- 1 | Axis src/graph/surface.html#Axis surface: Axis 2 | Surface src/graph/surface.html#Surface surface: Surface 3 | pixelFromVal src/graph/surface.html#pixelFromVal,Axis,float surface: pixelFromVal(a: Axis; val: float): int 4 | pixelFromVal2 src/graph/surface.html#pixelFromVal2,Axis,float,bool surface: pixelFromVal2(a: Axis; val: float; invert: bool = false): float 5 | `[]` src/graph/surface.html#[],Surface,int,int surface: `[]`(sur: Surface; i, j: int): Color 6 | `[]=` src/graph/surface.html#[]=,Surface,int,int,Color surface: `[]=`(sur: var Surface; i, j: int; color: Color) 7 | `[]` src/graph/surface.html#[],Surface,float,float surface: `[]`(sur: Surface; x, y: float): Color 8 | `[]=` src/graph/surface.html#[]=,Surface,float,float,Color surface: `[]=`(sur: var Surface; x, y: float; color: Color) 9 | fillWith src/graph/surface.html#fillWith,Surface,Color surface: fillWith(sur: var Surface; color: Color = White) 10 | initAxis src/graph/surface.html#initAxis,float,float,float,int,int,int surface: initAxis(v0, v1: float; origin: float = 0.0; p0 = 0; p1: int = 288; padding = 10): Axis 11 | initSurface src/graph/surface.html#initSurface,Axis,Axis surface: initSurface(x, y: Axis): Surface 12 | initSurface src/graph/surface.html#initSurface,int,int,int,int surface: initSurface(x0, w, y0, h: int): Surface 13 | grid src/graph/surface.html#grid,Surface surface: grid(s: var Surface) 14 | box src/graph/surface.html#box,Surface surface: box(s: var Surface; ticks = false) 15 | -------------------------------------------------------------------------------- /examples/example1.nim: -------------------------------------------------------------------------------- 1 | import graph, examples 2 | let xx = linspace(0.0,10,0.1) 3 | var srf = plot(xx,exp(xx),Red,White) 4 | srf.saveTo("example1.png") 5 | srf.saveTo("example1.svg") 6 | -------------------------------------------------------------------------------- /examples/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stisa/graph/6b140beb907aa7a7b2555f654894cd94f8b842ca/examples/example1.png -------------------------------------------------------------------------------- /examples/example1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 10 | 11 | 12 | 2 13 | 14 | 15 | 4 16 | 17 | 18 | 6 19 | 20 | 21 | 8 22 | 23 | 24 | 10 25 | 26 | 27 | +1.0 28 | 29 | 30 | +2.2e+03 31 | 32 | 33 | +4.4e+03 34 | 35 | 36 | +6.6e+03 37 | 38 | 39 | +8.8e+03 40 | 41 | 42 | +1.1e+04 43 | 44 | 45 | +1.3e+04 46 | 47 | 48 | +1.5e+04 49 | 50 | 51 | +1.8e+04 52 | 53 | 54 | +2.0e+04 55 | 56 | 57 | +2.2e+04 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/example2.nim: -------------------------------------------------------------------------------- 1 | import 2 | graph, examples 3 | import math 4 | 5 | let xx = linspace(0.0, 2*Pi, 0.1) 6 | 7 | # Create the surface with a plot in blue 8 | var srf = plot(xx,sin(xx),Viridis.blue) 9 | 10 | # Draw a cos over the surface 11 | srf.plot(xx,cos(xx), Purple) 12 | 13 | # Pass the proc (eg ``sinh``) to be mapped to xx so that yy=sin(xx) 14 | srf.plot(xx, sinh, Viridis.green) 15 | 16 | # Save to file 17 | srf.saveTo("example2.png") 18 | srf.saveTo("example2.svg") 19 | -------------------------------------------------------------------------------- /examples/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stisa/graph/6b140beb907aa7a7b2555f654894cd94f8b842ca/examples/example2.png -------------------------------------------------------------------------------- /examples/example2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 0 12 | 13 | 14 | 1.24 15 | 16 | 17 | 2.48 18 | 19 | 20 | 3.72 21 | 22 | 23 | 4.96 24 | 25 | 26 | 6.2 27 | 28 | 29 | -1.0 30 | 31 | 32 | -0.80 33 | 34 | 35 | -0.60 36 | 37 | 38 | -0.40 39 | 40 | 41 | -0.20 42 | 43 | 44 | -0.00017 45 | 46 | 47 | +0.20 48 | 49 | 50 | +0.40 51 | 52 | 53 | +0.60 54 | 55 | 56 | +0.80 57 | 58 | 59 | +1.0 60 | 61 | 62 | -------------------------------------------------------------------------------- /examples/examples.nim: -------------------------------------------------------------------------------- 1 | import sequtils,math 2 | 3 | iterator linsp*[T](fm,to,step:T):T = 4 | if fmto: 10 | var res: T = T(fm) 11 | while res>=to: 12 | yield res 13 | res-=step 14 | else: 15 | yield fm 16 | 17 | 18 | proc linspace* [T](fm,to,step:T):seq[T] = toSeq(linsp(fm, to, step)) # Result and step should be same type, not all 4 19 | 20 | proc sin* (x:openarray[float]):seq[float] = 21 | result = map(x) do (x:float)->float : 22 | sin(x) 23 | 24 | proc exp*(a:openarray[float]):seq[float] = 25 | result = newSeq[float](a.len) 26 | for i,r in result.mpairs : r = exp(a[i]) 27 | 28 | proc cos* (x:openarray[float]):seq[float] = 29 | result = map(x) do (x:float)->float : 30 | cos(x) 31 | -------------------------------------------------------------------------------- /examples/nim.cfg: -------------------------------------------------------------------------------- 1 | --path="../src/" -------------------------------------------------------------------------------- /graph.nimble: -------------------------------------------------------------------------------- 1 | # Package 2 | 3 | version = "0.4.0" 4 | author = "stisa" 5 | description = "Beginning of a 2D plotting library for Nim" 6 | license = "MIT" 7 | 8 | srcDir = "src" 9 | 10 | # Dependencies 11 | requires "nimPNG" 12 | requires "nimsvg" 13 | requires "nim >= 0.15.2" 14 | 15 | task examples, "Build examples": 16 | withDir "examples": 17 | exec("nim c -r example1.nim") 18 | exec("nim c -r example2.nim") 19 | #exec("nim c -r example3.nim") 20 | 21 | task current, "Build current.png": 22 | withDir "notes": 23 | exec("nim c -r current.nim") 24 | 25 | task docs, "Build docs": 26 | exec(r"nim doc --docRoot:@pkg --project --outdir:docs .\src\graph.nim") -------------------------------------------------------------------------------- /notes/current.nim: -------------------------------------------------------------------------------- 1 | import graph, math, arraymancer 2 | let 3 | x = arange(0.0'f64, 10,0.1) 4 | let 5 | y = sin(x) 6 | y2 = cos(x) 7 | var srf = plot(x.data,y.data) 8 | srf.plot(x.data, y2.data) 9 | srf.grid 10 | # Save to file 11 | srf.saveTo("currentpng.png") 12 | srf.saveTo("currentsvg.svg") -------------------------------------------------------------------------------- /notes/currentpng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stisa/graph/6b140beb907aa7a7b2555f654894cd94f8b842ca/notes/currentpng.png -------------------------------------------------------------------------------- /notes/currentsvg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 0 13 | 14 | 15 | 1.98 16 | 17 | 18 | 3.96 19 | 20 | 21 | 5.94 22 | 23 | 24 | 7.92 25 | 26 | 27 | 9.9 28 | 29 | 30 | -1.0 31 | 32 | 33 | -0.80 34 | 35 | 36 | -0.60 37 | 38 | 39 | -0.40 40 | 41 | 42 | -0.20 43 | 44 | 45 | -0.00017 46 | 47 | 48 | +0.20 49 | 50 | 51 | +0.40 52 | 53 | 54 | +0.60 55 | 56 | 57 | +0.80 58 | 59 | 60 | +1.0 61 | 62 | 63 | -------------------------------------------------------------------------------- /notes/matplotlib-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stisa/graph/6b140beb907aa7a7b2555f654894cd94f8b842ca/notes/matplotlib-colors.png -------------------------------------------------------------------------------- /notes/target.md: -------------------------------------------------------------------------------- 1 | Goals 2 | ------ 3 | The goal is to have a very simple plotting library for 2d line plots, with an api modeled 4 | after matlab and matplotlib. 5 | 6 | 7 | ## Target 8 | 9 | The reference style will be matplotlib-like, a goal is to be able to produce something matching: 10 | 11 | PNG: 12 | 13 | ![targetpng.png](targetpng.png) 14 | 15 | SVG: 16 | 17 | ![targetsvg.svg](targetsvg.svg) 18 | 19 | Those are produced by 20 | ```python 21 | import matplotlib.pyplot as pp 22 | import numpy as np 23 | x = np.arange(0.0,10,0.1) 24 | y1 = np.sin(x) 25 | y2 = np.cos(x) 26 | fig, ax = pp.subplots() 27 | ax.plot(x,y1,x,y2) 28 | ax.grid() 29 | fig.savefig("targetpng.png") 30 | fig.savefig("targetsvg.svg") 31 | ``` 32 | 33 | ## Current 34 | Currently we have: 35 | 36 | PNG: 37 | 38 | ![currentpng.png](currentpng.png) 39 | 40 | SVG: 41 | 42 | ![currentsvg.svg](currentsvg.svg) 43 | 44 | with: 45 | 46 | ```nim 47 | import graph, math, arraymancer 48 | let 49 | x = arange(0.0'f64, 10,0.1) 50 | let 51 | y = sin(x) 52 | y2 = cos(x) 53 | var srf = plot(x.data,y.data) 54 | srf.plot(x.data, y2.data) 55 | srf.grid 56 | # Save to file 57 | srf.saveTo("currentpng.png") 58 | srf.saveTo("currentsvg.svg") 59 | ``` -------------------------------------------------------------------------------- /notes/targetpng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stisa/graph/6b140beb907aa7a7b2555f654894cd94f8b842ca/notes/targetpng.png -------------------------------------------------------------------------------- /notes/targetsvg.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 307 | 308 | 309 | 310 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 327 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 366 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 652 | 653 | 654 | 755 | 756 | 757 | 760 | 761 | 762 | 765 | 766 | 767 | 770 | 771 | 772 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | -------------------------------------------------------------------------------- /src/graph.nim: -------------------------------------------------------------------------------- 1 | import strutils 2 | import 3 | ./graph/surface, ./graph/plot, ./graph/color 4 | 5 | export color, plot, surface 6 | 7 | import ./graph/backendpng, ./graph/backendsvg 8 | 9 | export backendpng, backendsvg 10 | 11 | proc saveTo*(sur: var Surface,filename:string) = 12 | ## Convience function. Saves `img` into `filename` 13 | if filename.endsWith("svg"): 14 | sur.saveToSvg(filename) 15 | elif filename.endsWith("png"): 16 | sur.saveToPng(filename) 17 | else: 18 | raise newException(ValueError, "Only svg and png outputs are supported") 19 | 20 | proc saveTo*(sur:Surface,filename:string) = 21 | ## Convience function. Saves `img` into `filename` 22 | if filename.endsWith("svg"): 23 | sur.saveToSvg(filename) 24 | else: 25 | raise newException(ValueError, "Only svg and png outputs are supported") 26 | 27 | when isMainModule: 28 | runnableExamples: 29 | import math, arraymancer 30 | let x = arange(0.0,10,0.1) 31 | let y = sin(x) 32 | let y2 = cos(x) 33 | var rt = plot(x.data,y.data) 34 | rt.plot(x.data,y2.data, col=Viridis.orange) 35 | rt.drawgrid = true 36 | rt.saveTo("tdraw.png") 37 | rt.saveTo("tdraw.svg") -------------------------------------------------------------------------------- /src/graph/backendpng.nim: -------------------------------------------------------------------------------- 1 | import ./surface, nimPNG, ./color 2 | 3 | import math 4 | 5 | proc aaline(srf:var Surface, x0,y0,x1,y1:int, w: float = 1.0, color : Color = Red) = 6 | # Anti aliased thick line with a modified bresenham line algorithm 7 | # see http://members.chello.at/easyfilter/bresenham.html 8 | var 9 | dx = abs(x1-x0) 10 | dy = abs(y1-y0) 11 | sx = if x0= -dx): 28 | e2 += dy 29 | y2 = yi 30 | while e2.float < ed*w and (y1 != y2 or dx > dy): 31 | y2 += sy 32 | ap = 255-(255*(abs(e2).float/ed - hfw + 1)).int 33 | #echo "second ", ap 34 | srf[y2,xi] = srf[y2,xi].blend(color.withAlpha(min(max(0, ap),255))) 35 | e2 += dx 36 | 37 | if(xi==x1): break 38 | e2 = err 39 | err -= dy 40 | xi += sx 41 | 42 | if(2*e2 <= dy): 43 | e2 = dx-e2 44 | while e2.float < ed*w and (x1 != x2 or dx < dy): 45 | x2 += sx 46 | ap = 255-(255*(abs(e2).float/ed - hfw + 1)).int 47 | #echo "third ", ap 48 | srf[yi,x2] = srf[yi,x2].blend(color.withAlpha(min(max(0, ap),255))) 49 | e2 += dy 50 | 51 | if(yi==y1): break 52 | err += dx 53 | yi += sy 54 | 55 | proc renderLine(srf:var Surface, x1,y1,x2,y2:float, w:float=1.0, color : Color = Red) {.inline.} = 56 | srf.aaline(srf.x.pixelFromVal(srf.origin.x0+x1), srf.y.pixelFromVal(srf.origin.y0+y1), 57 | srf.x.pixelFromVal(srf.origin.x0+x2),srf.y.pixelFromVal(srf.origin.y0+y2), w, color) 58 | 59 | proc xticks(sur: var Surface, every: float = 0.20, color:Color=Viridis.gray) = 60 | ## Plot ticks with `Color`, the distance between ticks is `every` as a percentage. 61 | let incr = (sur.x.unpadded.max-sur.x.unpadded.min)*every 62 | var point = sur.x.unpadded.min 63 | let ticksize = (sur.y.max.val-sur.y.min.val)*every/15 64 | while point <= sur.x.max.val: 65 | sur.renderLine(point, sur.y.min.val-ticksize , point, sur.y.min.val, 1.0, color) 66 | point += incr 67 | 68 | proc yticks(sur: var Surface, every: float = 0.10, color:Color=Viridis.gray) = 69 | ## Plot ticks with `Color`, the distance between ticks is `every` as a percentage. 70 | let incr = (sur.y.unpadded.max-sur.y.unpadded.min)*every 71 | var point = sur.y.unpadded.min 72 | let ticksize = (sur.x.max.val-sur.x.min.val)*every/10 73 | while point <= sur.y.max.val: 74 | sur.renderLine(sur.x.min.val-ticksize, point, sur.x.min.val, point, 1.0, color) 75 | point += incr 76 | 77 | 78 | proc renderBox(sur: var Surface, color:Color=Black, ticks: bool = false) = 79 | ## Plot a box around the drawable part of the plot 80 | ## ## If `ticks` is true, ticks are also plotted 81 | sur.renderLine(sur.x.min.val, sur.y.min.val , sur.x.min.val, sur.y.max.val, 1.0, color) 82 | sur.renderLine(sur.x.min.val, sur.y.min.val , sur.x.max.val, sur.y.min.val, 1.0, color) 83 | sur.renderLine(sur.x.max.val, sur.y.min.val , sur.x.max.val, sur.y.max.val, 1.0, color) 84 | sur.renderLine(sur.x.min.val, sur.y.max.val , sur.x.max.val, sur.y.max.val, 1.0, color) 85 | if ticks: 86 | sur.xticks(color=color) 87 | sur.yticks(color=color) 88 | 89 | proc gridX(sur: var Surface, every: float = 0.10, color:Color=Viridis.gray, ticks:bool=false) = 90 | ## Plot a grid with `Color`, the distance between lines is `every` as a percentage. 91 | let incr = (sur.x.unpadded.max-sur.x.unpadded.min)*every 92 | var point = sur.x.unpadded.min 93 | let ticksize = if not ticks: 0.0 else:(sur.y.max.val-sur.y.min.val)*every/15 94 | while point <= sur.x.max.val: 95 | sur.renderLine(point, sur.y.min.val-ticksize , point, sur.y.max.val, 1.0, color) 96 | point += incr 97 | 98 | proc gridY(sur: var Surface, every: float = 0.10, color:Color=Viridis.gray, ticks:bool=false) = 99 | ## Plot a grid with `Color`, the distance between lines is `every` as a percentage. 100 | let incr = (sur.y.unpadded.max-sur.y.unpadded.min)*every 101 | var point = sur.y.unpadded.min 102 | let ticksize = if not ticks: 0.0 else: (sur.x.max.val-sur.x.min.val)*every/10 103 | while point <= sur.y.max.val: 104 | sur.renderLine(sur.x.min.val-ticksize, point, sur.x.max.val, point, 1.0, color) 105 | point += incr 106 | 107 | proc renderGrid(sur: var Surface, everyX: float = 0.2, everyY: float = 0.10, color:Color=Viridis.gray, ticks:bool=false) = 108 | ## Plot a grid with `Color`, the distance between lines is `every` as a percentage. 109 | ## If `ticks` is true, the lines extend slightly outside 110 | sur.gridX(everyX, color, ticks) 111 | sur.gridY(everyY, color, ticks) 112 | 113 | 114 | proc render(srf: var Surface) = 115 | srf.fillWith(srf.bg) 116 | 117 | if srf.drawgrid: 118 | srf.renderGrid 119 | if srf.drawbox: 120 | srf.renderBox(ticks=srf.drawticks) 121 | 122 | # color up the lines, FIFO 123 | for plot in srf.plots.mitems: 124 | # if done, we don't need to redraw 125 | if plot.done: continue 126 | for i in 0.. colors are at half alpha 15 | HalfTBlue* = Color(0x0000FF88) 16 | HalfTGreen* = Color(0x00FF0088) 17 | HalfTRed* = Color(0xFF000088) 18 | HalftWhite* = Color(0xFFFFFF88) 19 | 20 | type Viridis* {.pure.} = enum 21 | transp = Color(0x00000000) 22 | blue = Color(0x1f77b4ff) 23 | green = Color(0x2ca02cff) 24 | gray = Color(0xbfbfbfff) 25 | red = Color(0xd62728ff) 26 | orange = Color(0xff7f0eff) 27 | 28 | converter toColor*(v: Viridis): Color {.inline.} = Color(v) 29 | 30 | proc color* (r,g,b,a:int=0) :Color= 31 | assert(abs(r)<256, $r) 32 | assert(abs(g)<256, $g) 33 | assert(abs(b)<256, $b) 34 | assert(abs(a)<256, $a) 35 | result = (r shl 24+g shl 16+b shl 8+a).uint32 36 | 37 | proc withAlpha* (c:Color, a:int=0): Color= 38 | assert(abs(a)<256, $a) 39 | # -cast[uint8](c).uint32 <- this part reset alpha to 0 40 | result = (c-cast[uint8](c).uint32+a.uint32).uint32 41 | 42 | proc alpha(c: Color): uint32 {.inline.} = cast[uint8](c) 43 | 44 | proc blend*( orig: var Color, newc: Color) = 45 | let 46 | alpha = newc.alpha 47 | inv_alpha = 255 - newc.alpha 48 | orig = ( 49 | ((alpha * cast[uint8](newc shr 24) + inv_alpha * cast[uint8](orig shr 24)) shr 8) shl 24 + 50 | ((alpha * cast[uint8](newc shr 16) + inv_alpha * cast[uint8](orig shr 16)) shr 8) shl 16 + 51 | ((alpha * cast[uint8](newc shr 8) + inv_alpha * cast[uint8](orig shr 8)) shr 8) shl 8 + 52 | 0xff).uint32 53 | 54 | proc blend*( orig: Color, newc: Color): Color = 55 | let 56 | alpha = newc.alpha 57 | inv_alpha = 255 - newc.alpha 58 | result = ( 59 | ((alpha * cast[uint8](newc shr 24) + inv_alpha * cast[uint8](orig shr 24)) shr 8) shl 24 + 60 | ((alpha * cast[uint8](newc shr 16) + inv_alpha * cast[uint8](orig shr 16)) shr 8) shl 16 + 61 | ((alpha * cast[uint8](newc shr 8) + inv_alpha * cast[uint8](orig shr 8)) shr 8) shl 8 + 62 | 0xff).uint32 63 | 64 | 65 | proc `$`*(c:Color):string = 66 | result = "" 67 | result.setLen(4) 68 | 69 | result[0] = cast[uint8](c shr 24).char 70 | result[1] = cast[uint8](c shr 16).char 71 | result[2] = cast[uint8](c shr 8).char 72 | result[3] = cast[uint8](c shr 0).char 73 | 74 | when isMainModule: 75 | echo repr color(255,000,000,255) 76 | echo repr color(255,000,000,0) 77 | echo repr Red 78 | echo repr Red.withAlpha(0) 79 | echo repr Red.withAlpha(88) 80 | echo repr cast[uint8](HalfTGreen) 81 | -------------------------------------------------------------------------------- /src/graph/plot.nim: -------------------------------------------------------------------------------- 1 | import ./surface, ./color 2 | from sequtils import map, zip 3 | 4 | #[ 5 | proc bresline*(srf:var Surface, x1,y1,x2,y2:int, color : Color = Red) = 6 | ## Draws a line between x1,y1 and x2,y2. Uses Bresenham's line algorithm. 7 | var dx = x2-x1 8 | var dy = y2-y1 9 | 10 | let ix = if dx > 0 : 1 elif dx<0 : -1 else: 0 11 | let iy = if dy > 0 : 1 elif dy<0 : -1 else: 0 12 | 13 | dx = abs(dx) shl 1 14 | dy = abs(dy) shl 1 15 | 16 | var xi = x1 17 | var yi = y1 18 | srf[yi,xi] = color 19 | 20 | if dx>=dy: 21 | var err = dy-(dx shr 1) 22 | while xi != x2: 23 | if (err >= 0): 24 | err -= dx 25 | yi+=iy 26 | 27 | err += dy 28 | xi += ix 29 | srf[yi,xi] = color 30 | else: 31 | var err = dx - (dy shr 1) 32 | while yi!=y2: 33 | if err >= 0: 34 | err -= dy 35 | xi += ix 36 | err += dx 37 | yi += iy 38 | srf[yi,xi] = color 39 | 40 | proc drawTicks(sur:var Surface,color:Color=Black,every:float=10.0,yevery:float=10.0) = 41 | # every is a percentage of the width/height 42 | var ticks = int(float(sur.x.max.pixel-sur.x.min.pixel)/every) 43 | var last_at = 0.0 44 | var ticksize = abs((sur.y.max.val-sur.y.min.val)/float(sur.y.min.pixel-sur.y.max.pixel))*3 45 | var span = sur.x.max.val-sur.x.min.val 46 | 47 | for i in 1..T: 79 | y(x) 80 | sur.plots.add((@x, @yy, col, false)) 81 | 82 | proc plot*(x,y: openarray[float], lncolor: Color = Viridis.blue, bgColor: Color = White, 83 | origin: tuple[x0,y0: float] = (0.0,0.0), padding= 20, 84 | grid:bool=false, box:bool=true, ticks=true, size = [432,288]): Surface = 85 | ## Inits a surface and append points (x,y) to it. Returns the surface. 86 | let xa = initAxis(min(x),max(x),origin.x0, 0, size[0], padding) 87 | let ya = initAxis(min(y),max(y),origin.y0, 0, size[1], padding) 88 | 89 | result = initSurface(xa,ya) 90 | result.bg = bgColor 91 | if grid: 92 | result.drawgrid = true 93 | #result.drawAxis() 94 | if box: 95 | result.box(ticks=ticks) 96 | result.plot(x,y,lncolor) 97 | -------------------------------------------------------------------------------- /src/graph/surface.nim: -------------------------------------------------------------------------------- 1 | import ./color 2 | import math 3 | type 4 | Axis* = object 5 | max*: tuple[val:float,pixel:int] 6 | min*: tuple[val:float,pixel:int] 7 | unpadded*: tuple[min:float, max: float] 8 | origin: float#tuple[val,pixel:int] 9 | padding: int # % of padding 10 | #TODO: idea: tickevery: float # every % make a tick, also defines grids 11 | 12 | Surface* = object 13 | width*: int 14 | height*: int 15 | x*: Axis 16 | y*: Axis 17 | ratio: float 18 | origin*: tuple[x0,y0:float] 19 | pixels*: seq[Color] 20 | plots*: seq[tuple[x,y:seq[float],c:Color, done:bool]] 21 | bg*: Color 22 | drawgrid*: bool 23 | drawbox*: bool 24 | drawticks*: bool 25 | drawlabels*: bool 26 | #CHECK: other alternatives: 27 | #bg: seq[Color] # background pixels (background, grids etc) 28 | #fg: seq[Color] # foreground pixels (plot lines) 29 | # and then blend on save? 30 | #pixelPairs*: seq[tuple[x:int,y:int]] # a seq of pair of pixels values to be connected by a line 31 | #pixelsize: int 32 | 33 | proc pixelFromVal*(a:Axis,val:float):int = 34 | let 35 | paddedmax = a.max.pixel - (((a.max.pixel-a.min.pixel) * a.padding) div 100).int 36 | paddedmin = a.min.pixel + (((a.max.pixel-a.min.pixel) * a.padding) div 200).int 37 | #result = ((( (val) - a.min.val)/(a.max.val-a.min.val) * (a.max.pixel-a.min.pixel).float)+(a.min.pixel).float).int 38 | result = ( 39 | ( # We lose too much precision converting to int here :/ 40 | (((val) - a.min.val)/(a.max.val-a.min.val)) * 41 | paddedmax.float 42 | ) + paddedmin.float 43 | ).round.int 44 | proc pixelFromVal2*(a:Axis,val:float, invert:bool=false):float = 45 | let 46 | paddedmax = a.max.pixel - (((a.max.pixel-a.min.pixel) * a.padding) div 100) 47 | paddedmin = a.min.pixel + (((a.max.pixel-a.min.pixel) * a.padding) div 200) 48 | #result = ((( (val) - a.min.val)/(a.max.val-a.min.val) * (a.max.pixel-a.min.pixel).float)+(a.min.pixel).float).int 49 | if not invert: 50 | result = ( 51 | ( 52 | ((val) - a.min.val)/(a.max.val-a.min.val) * 53 | paddedmax.float 54 | ) + paddedmin.float 55 | ) 56 | else: 57 | result = a.max.pixel.float - ( 58 | ( 59 | ((val) - a.min.val)/(a.max.val-a.min.val) * 60 | paddedmax.float 61 | ) + paddedmin.float 62 | ) 63 | proc `[]`*(sur:Surface, i,j:int):Color = 64 | ## j: position along the horizontal axis 65 | ## i: position along the vertical axis 66 | ## IN PIXEL 67 | if i >= sur.height or i < 0: return 68 | if j >= sur.width or j < 0: return 69 | result = sur.pixels[(sur.height-1-i)*(sur.width)+j] 70 | 71 | proc `[]=`*(sur: var Surface, i,j:int, color:Color) = 72 | if i >= sur.height or i < 0: return 73 | if j >= sur.width or j < 0: return 74 | sur.pixels[(sur.height-1-i)*(sur.width)+j] = color 75 | 76 | proc `[]`*(sur:Surface, x,y:float):Color = 77 | ## x: position along the horizontal axis 78 | ## y: position along the vertical axis 79 | ## VALUES NOT IN PIXEL 80 | result = sur[sur.x.pixelFromVal(x),sur.y.pixelFromVal(y)] 81 | 82 | proc `[]=`*(sur: var Surface, x,y:float, color:Color) = 83 | #echo sur.y.pixelFromVal(y),sur.x.pixelFromVal(x) 84 | sur[sur.x.pixelFromVal(x), sur.y.pixelFromVal(y)] = color 85 | 86 | proc fillWith*(sur: var Surface,color:Color=White) = 87 | ## Loop over every pixel in `img` and sets its color to `color` 88 | #sur.pixels.fill(color) 89 | for pix in sur.pixels.mitems: pix = Color(color) 90 | 91 | proc initAxis*(v0,v1:float,origin:float=0.0,p0=0,p1:int=288, padding=10): Axis = 92 | ## padding is a percentage? 93 | let padfloat = (v1-v0) * (padding / 200) 94 | result.max = (v1+padfloat,p1) 95 | result.min = (v0-padfloat,p0) 96 | result.padding = padding 97 | result.origin = origin 98 | result.unpadded = (v0,v1) 99 | 100 | proc initSurface*(x,y:Axis) : Surface = 101 | result.x = x 102 | result.y = y 103 | result.width = abs(x.max.pixel-x.min.pixel)+1 #wtf 104 | result.height = abs(y.max.pixel-y.min.pixel)+1 105 | result.pixels = newSeq[Color](result.height*result.width) 106 | result.origin = (x.origin,y.origin) 107 | result.bg = White 108 | result.plots = @[] 109 | 110 | proc initSurface*(x0,w,y0,h:int) : Surface = 111 | result.x = initAxis(x0.float,w.float, 0, 0, w) 112 | result.y = initAxis(y0.float,h.float, 0, 0, h) 113 | result.width = w#abs(x.max.pixel-x.min.pixel)+1 #wtf 114 | result.height = h#abs(y.max.pixel-y.min.pixel)+1 115 | result.pixels = newSeq[Color](result.height*result.width) 116 | result.origin = (result.x.origin,result.y.origin) 117 | 118 | proc grid*(s: var Surface) = s.drawgrid = true 119 | proc box*(s: var Surface, ticks=false) = 120 | s.drawbox = true 121 | if ticks: 122 | s.drawticks = true 123 | s.drawlabels = true --------------------------------------------------------------------------------