Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make coordinates be attracted/repulsed to/from certain other coordinates

I am generating a couple of svg images like this one:

<svg version="1.1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
  <style type="text/css">
    text {
      font-family: Helvetica;
      font-size: 60px;
      paint-order: stroke;
      stroke: #000000;
      stroke-width: 6px;
      stroke-linecap: butt;
      stroke-linejoin: miter;
      font-weight: 800;
    }
  </style>
  <g stroke="lightgray" stroke-dasharray="1,1" stroke-width="1" transform="scale(4, 4)">
    <line x1="0" y1="0" x2="256" y2="256"/>
    <line x1="256" y1="0" x2="0" y2="256"/>
    <line x1="128" y1="0" x2="128" y2="256"/>
    <line x1="0" y1="128" x2="256" y2="128"/>
  </g>
  <g transform="scale(1, -1) translate(0, -900)">
    <path d="M 128 445 Q 222 525 301 653 Q 319 685 334 709 L 343 725 Q 351 746 367 763 Q 376 772 374 782 Q 374 791 361 801 Q 327 827 298 819 Q 290 819 293 806 Q 310 732 219 604 L 171 539 Q 133 492 33 396 Q 26 392 35 390 Q 43 390 110 431 L 128 445 Z" fill="#cc2f00"/>
    <path d="M 334 709 Q 386 675 447 629 Q 461 617 472 615 Q 478 615 482 624 Q 488 634 474 663 Q 459 700 343 725 C 314 731 309 725 334 709 Z" fill="#cc5e00"/>
    <path d="M 253 553 Q 224 546 246 534 Q 276 517 325 531 Q 423 558 435 563 Q 439 567 439 572 Q 437 586 406 590 Q 388 593 316 566 L 253 553 Z" fill="#cc8d00"/>
    <path d="M 147 441 Q 143 445 128 445 C 101 446 101 446 110 431 Q 125 406 140 365 Q 153 331 162 323 Q 174 311 176 321 Q 178 329 175 343 L 171 363 Q 161 394 153 422 C 148 441 148 441 147 441 Z" fill="#ccbc00"/>
    <path d="M 255 464 Q 233 477 223 476 Q 213 475 180 456 Q 172 453 147 441 C 120 428 124 416 153 422 Q 161 423 181 431 Q 212 442 216 437 Q 221 433 216 383 C 213 353 241 353 245 383 Q 251 431 265 445 C 272 454 272 454 255 464 Z" fill="#adcc00"/>
    <path d="M 175 343 Q 180 343 186 345 Q 216 357 254 367 Q 261 368 259 373 Q 259 377 245 383 L 216 383 Q 213 383 171 363 C 144 350 145 341 175 343 Z" fill="#7ecc00"/>
    <path d="M 285 468 Q 261 473 255 470 Q 255 468 255 464 C 255 459 255 469 265 445 Q 272 429 278 405 Q 288 371 295 363 Q 305 352 310 361 Q 310 367 310 371 L 308 392 Q 298 434 298 450 C 297 465 297 465 285 468 Z" fill="#4ecc00"/>
    <path d="M 374 410 Q 386 441 396 454 Q 404 463 406 466 C 413 475 413 475 398 484 L 374 498 Q 360 506 347 499 Q 327 482 285 468 C 257 458 270 438 298 450 Q 300 451 310 455 Q 351 468 355 462 Q 357 461 357 459 Q 359 445 342 406 C 330 378 363 382 374 410 Z" fill="#1fcc00"/>
    <path d="M 310 371 Q 323 378 385 390 Q 392 391 392 398 Q 390 402 374 410 C 368 413 368 413 342 406 Q 333 404 308 392 C 281 379 283 358 310 371 Z" fill="#00cc10"/>
    <path d="M 406 466 Q 424 400 427 396 Q 437 382 441 392 L 443 406 L 441 425 Q 434 470 433 482 C 432 494 432 494 420 500 Q 419 501 416 502 Q 398 508 394 504 Q 390 500 398 484 L 406 466 Z" fill="#00cc3f"/>
    <path d="M 519 439 Q 529 478 541 490 Q 555 506 540 514 Q 514 526 506 528 Q 496 531 484 525 Q 454 510 420 500 C 391 491 403 477 433 482 Q 445 484 454 487 Q 490 496 495 490 Q 496 490 496 487 Q 500 472 490 439 C 482 410 511 410 519 439 Z" fill="#00cc6e"/>
    <path d="M 443 406 Q 449 406 455 407 Q 490 416 527 419 Q 534 420 534 425 Q 534 429 519 439 C 519 439 519 439 490 439 Q 488 441 441 425 C 413 415 413 405 443 406 Z" fill="#00cc9d"/>
    <path d="M 169 267 Q 151 272 139 272 Q 134 270 133 267 Q 132 264 139 246 Q 167 188 145 104 Q 129 91 142 62 Q 151 43 160 37 Q 166 27 173 32 Q 192 50 192 163 Q 192 202 192 242 C 192 261 192 261 169 267 Z" fill="#00cccc"/>
    <path d="M 347 278 Q 402 290 419 284 Q 433 278 435 258 Q 439 207 429 114 Q 431 98 416 98 Q 396 98 386 100 Q 380 100 377 98 Q 369 94 398 69 Q 419 47 431 22 Q 441 15 451 20 Q 455 22 459 25 Q 480 58 482 158 Q 476 267 486 283 Q 494 292 488 302 Q 484 307 472 314 Q 437 333 419 323 Q 409 319 388 316 Q 271 292 169 267 C 140 260 164 232 192 242 Q 208 248 231 254 L 248 257 Q 272 265 323 274 L 347 278 Z" fill="#009dcc"/>
    <path d="M 244 188 Q 235 186 214 178 Q 201 174 217 164 Q 226 158 246 163 L 274 170 L 332 184 L 367 190 Q 376 194 396 198 Q 402 199 406 202 Q 415 209 397 218 Q 379 227 368 223 L 332 216 Q 327 216 274 196 L 244 188 Z" fill="#006ecc"/>
    <path d="M 231 254 Q 231 251 233 246 Q 241 216 244 188 L 246 163 Q 252 102 256 93 Q 265 78 270 87 Q 274 97 274 170 L 274 196 Q 274 208 275 218 Q 276 239 274 243 Q 271 250 248 257 C 229 263 229 263 231 254 Z" fill="#003fcc"/>
    <path d="M 332 184 Q 332 80 342 71 Q 343 71 347 71 Q 351 73 354 81 Q 360 98 367 190 L 368 223 Q 368 229 372 243 Q 376 259 368 267 Q 355 276 347 278 C 319 288 317 289 323 274 Q 331 257 332 216 L 332 184 Z" fill="#0010cc"/>
    <path d="M 698 651 L 862 688 Q 908 700 914 708 Q 923 714 918 723 Q 913 729 903 734 Q 864 751 825 733 Q 808 727 794 723 Q 696 689 582 674 Q 548 668 572 653 Q 606 634 659 641 L 698 651 Z" fill="#1f00cc"/>
    <path d="M 657 540 Q 668 553 678 566 Q 699 594 712 608 Q 734 624 698 651 C 674 669 660 671 659 641 Q 659 617 641 563 Q 631 549 630 535 C 626 505 638 517 657 540 Z" fill="#4e00cc"/>
    <path d="M 596 531 Q 594 533 591 535 Q 571 549 557 543 Q 555 541 555 536 Q 555 525 561 515 Q 582 472 572 295 Q 566 269 566 245 Q 566 198 586 180 Q 595 171 604 180 Q 611 192 612 201 L 613 229 Q 615 239 615 265 Q 613 294 613 308 L 613 331 L 613 406 L 613 431 Q 613 455 615 506 C 615 517 615 517 596 531 Z" fill="#7e00cc"/>
    <path d="M 809 223 Q 809 216 817 201 Q 831 173 841 174 Q 855 176 869 209 Q 874 219 876 225 Q 882 242 878 272 Q 868 363 866 451 Q 866 463 868 472 Q 871 489 885 517 Q 892 533 886 539 Q 861 561 817 579 Q 802 583 780 576 Q 774 574 657 540 L 630 535 Q 608 533 596 531 C 566 527 585 503 615 506 Q 627 506 642 510 Q 710 527 768 538 Q 789 542 795 531 Q 809 514 811 453 Q 817 290 811 249 L 809 223 Z" fill="#ad00cc"/>
    <path d="M 613 406 L 618 406 Q 686 417 747 423 Q 768 428 759 440 Q 749 452 727 456 Q 710 459 613 431 C 584 423 583 406 613 406 Z" fill="#cc00bc"/>
    <path d="M 613 308 L 623 308 Q 708 317 761 319 Q 783 323 774 334 Q 762 357 718 354 Q 690 352 613 331 C 584 323 583 308 613 308 Z" fill="#cc008d"/>
    <path d="M 612 201 L 621 201 L 809 223 C 839 226 835 232 811 249 Q 800 259 777 258 Q 753 257 613 229 C 584 223 582 201 612 201 Z" fill="#cc005e"/>
    <path d="M 623 161 Q 608 95 455 -29 Q 447 -35 447 -37 Q 446 -41 460 -39 Q 480 -37 504 -27 Q 562 -4 640 76 Q 686 123 690 126 Q 695 131 697 137 Q 704 157 671 167 Q 652 174 641 174 Q 627 174 623 161 Z" fill="#cc002f"/>
    <path d="M 757 157 Q 757 153 757 151 Q 758 140 774 118 Q 829 47 864 -7 Q 877 -29 892 -36 Q 898 -37 903 -33 Q 923 -23 917 25 Q 914 77 768 163 Q 761 169 757 157 Z" fill="#cc0000"/>
  </g>
  <g>
    <text fill="#FFFFFF" x="306" y="131">1</text>
    <text fill="#FFFFFF" x="343" y="231">2</text>
    <text fill="#FFFFFF" x="247" y="396">3</text>
    <text fill="#FFFFFF" x="119" y="511">4</text>
    <text fill="#FFFFFF" x="157" y="511">5</text>
    <text fill="#FFFFFF" x="180" y="591">6</text>
    <text fill="#FFFFFF" x="261" y="473">7</text>
    <text fill="#FFFFFF" x="294" y="472">8</text>
    <text fill="#FFFFFF" x="312" y="564">9</text>
    <text fill="#FFFFFF" x="399" y="420">10</text>
    <text fill="#FFFFFF" x="428" y="421">11</text>
    <text fill="#FFFFFF" x="449" y="509">12</text>
    <text fill="#FFFFFF" x="140" y="655">13</text>
    <text fill="#FFFFFF" x="176" y="654">14</text>
    <text fill="#FFFFFF" x="226" y="747">15</text>
    <text fill="#FFFFFF" x="236" y="669">16</text>
    <text fill="#FFFFFF" x="330" y="649">17</text>
    <text fill="#FFFFFF" x="574" y="255">18</text>
    <text fill="#FFFFFF" x="667" y="284">19</text>
    <text fill="#FFFFFF" x="563" y="383">20</text>
    <text fill="#FFFFFF" x="601" y="395">21</text>
    <text fill="#FFFFFF" x="616" y="508">22</text>
    <text fill="#FFFFFF" x="618" y="606">23</text>
    <text fill="#FFFFFF" x="617" y="712">24</text>
    <text fill="#FFFFFF" x="680" y="777">25</text>
    <text fill="#FFFFFF" x="766" y="766">26</text>
  </g>
</svg>

These svgs are stroke-order diagrams for Chinese characters. So the image shows each stroke in a different color and at the start point of each stroke a number is shown to visualize in which order they have to be drawn.

The problem with my approach is that numbers can overlap or be too close to each other making it hard to read.

I'd like to implement an algorithm that calculates better positions for each coordinate. I was thinking about something that would allow you to specify an attraction to the starting point and a repulsion to all the coordinates of all the other numbers.

Is this algorithm something that already exists and does it have a name? How could it be done in JavaScript?

Here is a piece of code with the coordinates for the stroke start positions at which the numbers are currently drawn:

let strokeStartPositions = [
    [306, 131],
    [343, 231],
    [247, 396],
    [119, 511],
    [157, 511],
    [180, 591],
    [261, 473],
    [294, 472],
    [312, 564],
    [399, 420],
    [428, 421],
    [449, 509],
    [140, 655],
    [176, 654],
    [226, 747],
    [236, 669],
    [330, 649],
    [574, 255],
    [667, 284],
    [563, 383],
    [601, 395],
    [616, 508],
    [618, 606],
    [617, 712],
    [680, 777],
    [766, 766]
]

function repositionAlgorithm(position, attractedTo, repulsedFrom) {
    // ?
    return position
}
let numberPositions = []
for (let [i,currentPosition] of strokeStartPositions.entries()) {
    let oldX = currentPosition[0]
    let oldY = currentPosition[1]
    let repulsedFrom = strokeStartPositions.slice()
    repulsedFrom.splice(i,1) // all positions but the current one
    let attractedTo = [currentPosition]
    let newPosition = repositionAlgorithm(currentPosition, attractedTo, repulsedFrom)
    numberPositions[i] = newPosition
    let newX = newPosition[0]
    let newY = newPosition[1]
    console.log(`Moved from (${oldX}|${oldY}) to (${newX}|${newY}).`)
}

Reducing the text size of double digit numbers somewhat helped, but overlaps can still occur and I can't really reduce the text size any further because it will become unreadable..

Here's a fiddle if you want to play around with it: https://jsfiddle.net/t2o7p1ea/31/ The relevant part starts at line 62 and ends at line 78.

Edit: I found a solution that works okay. But it's not perfect. If you can come up with something better. I'll gladly award the bounty to you.

like image 941
Forivin Avatar asked Sep 09 '18 20:09

Forivin


4 Answers

After days of fiddling around, I finally came up with an idea that somewhat works.

The idea was: Take all stroke starting points and pretend they are circles with a diameter roughly as big as fontSize*digitAmount.

while any circles intercept each other
    iterate over all circles
        iterate over all circles
            if the circle of this iteration is the same as the circle of the outer iteration
                skip this iteration (continue)
             if circle of this and the outer iteration intercept
                 calculate a point that from the perspective of circle1
                 is about 1 pixel behind circle 2
                 and move circle2 to that new position
                 calculate a point that from the perspective of circle2
                 is about 1 pixel behind circle 1
                 and move circle1 to that new position

(I decided to move them pixel by pixel so that there will be an iteration for each circle to react after every change in case new circles are being intercepted now.)

After adjusting and fine tuning the parameters, I got a result that looks pretty good. It's still not perfect because I'm treating the numbers as if they were circles and if I make the circles too big, the numbers get pushed around forever and the loop doesn't end.

Also, I went back to using colored numbers and I changed the borders to be white instead of black to improve readability.

//#!/usr/bin/env node
//const fs = require('fs-extra')
//const readline = require('readline')

const strokeData = {"character":"龥","strokes":["M 128 445 Q 222 525 301 653 Q 319 685 334 709 L 343 725 Q 351 746 367 763 Q 376 772 374 782 Q 374 791 361 801 Q 327 827 298 819 Q 290 819 293 806 Q 310 732 219 604 L 171 539 Q 133 492 33 396 Q 26 392 35 390 Q 43 390 110 431 L 128 445 Z","M 334 709 Q 386 675 447 629 Q 461 617 472 615 Q 478 615 482 624 Q 488 634 474 663 Q 459 700 343 725 C 314 731 309 725 334 709 Z","M 253 553 Q 224 546 246 534 Q 276 517 325 531 Q 423 558 435 563 Q 439 567 439 572 Q 437 586 406 590 Q 388 593 316 566 L 253 553 Z","M 147 441 Q 143 445 128 445 C 101 446 101 446 110 431 Q 125 406 140 365 Q 153 331 162 323 Q 174 311 176 321 Q 178 329 175 343 L 171 363 Q 161 394 153 422 C 148 441 148 441 147 441 Z","M 255 464 Q 233 477 223 476 Q 213 475 180 456 Q 172 453 147 441 C 120 428 124 416 153 422 Q 161 423 181 431 Q 212 442 216 437 Q 221 433 216 383 C 213 353 241 353 245 383 Q 251 431 265 445 C 272 454 272 454 255 464 Z","M 175 343 Q 180 343 186 345 Q 216 357 254 367 Q 261 368 259 373 Q 259 377 245 383 L 216 383 Q 213 383 171 363 C 144 350 145 341 175 343 Z","M 285 468 Q 261 473 255 470 Q 255 468 255 464 C 255 459 255 469 265 445 Q 272 429 278 405 Q 288 371 295 363 Q 305 352 310 361 Q 310 367 310 371 L 308 392 Q 298 434 298 450 C 297 465 297 465 285 468 Z","M 374 410 Q 386 441 396 454 Q 404 463 406 466 C 413 475 413 475 398 484 L 374 498 Q 360 506 347 499 Q 327 482 285 468 C 257 458 270 438 298 450 Q 300 451 310 455 Q 351 468 355 462 Q 357 461 357 459 Q 359 445 342 406 C 330 378 363 382 374 410 Z","M 310 371 Q 323 378 385 390 Q 392 391 392 398 Q 390 402 374 410 C 368 413 368 413 342 406 Q 333 404 308 392 C 281 379 283 358 310 371 Z","M 406 466 Q 424 400 427 396 Q 437 382 441 392 L 443 406 L 441 425 Q 434 470 433 482 C 432 494 432 494 420 500 Q 419 501 416 502 Q 398 508 394 504 Q 390 500 398 484 L 406 466 Z","M 519 439 Q 529 478 541 490 Q 555 506 540 514 Q 514 526 506 528 Q 496 531 484 525 Q 454 510 420 500 C 391 491 403 477 433 482 Q 445 484 454 487 Q 490 496 495 490 Q 496 490 496 487 Q 500 472 490 439 C 482 410 511 410 519 439 Z","M 443 406 Q 449 406 455 407 Q 490 416 527 419 Q 534 420 534 425 Q 534 429 519 439 C 519 439 519 439 490 439 Q 488 441 441 425 C 413 415 413 405 443 406 Z","M 169 267 Q 151 272 139 272 Q 134 270 133 267 Q 132 264 139 246 Q 167 188 145 104 Q 129 91 142 62 Q 151 43 160 37 Q 166 27 173 32 Q 192 50 192 163 Q 192 202 192 242 C 192 261 192 261 169 267 Z","M 347 278 Q 402 290 419 284 Q 433 278 435 258 Q 439 207 429 114 Q 431 98 416 98 Q 396 98 386 100 Q 380 100 377 98 Q 369 94 398 69 Q 419 47 431 22 Q 441 15 451 20 Q 455 22 459 25 Q 480 58 482 158 Q 476 267 486 283 Q 494 292 488 302 Q 484 307 472 314 Q 437 333 419 323 Q 409 319 388 316 Q 271 292 169 267 C 140 260 164 232 192 242 Q 208 248 231 254 L 248 257 Q 272 265 323 274 L 347 278 Z","M 244 188 Q 235 186 214 178 Q 201 174 217 164 Q 226 158 246 163 L 274 170 L 332 184 L 367 190 Q 376 194 396 198 Q 402 199 406 202 Q 415 209 397 218 Q 379 227 368 223 L 332 216 Q 327 216 274 196 L 244 188 Z","M 231 254 Q 231 251 233 246 Q 241 216 244 188 L 246 163 Q 252 102 256 93 Q 265 78 270 87 Q 274 97 274 170 L 274 196 Q 274 208 275 218 Q 276 239 274 243 Q 271 250 248 257 C 229 263 229 263 231 254 Z","M 332 184 Q 332 80 342 71 Q 343 71 347 71 Q 351 73 354 81 Q 360 98 367 190 L 368 223 Q 368 229 372 243 Q 376 259 368 267 Q 355 276 347 278 C 319 288 317 289 323 274 Q 331 257 332 216 L 332 184 Z","M 698 651 L 862 688 Q 908 700 914 708 Q 923 714 918 723 Q 913 729 903 734 Q 864 751 825 733 Q 808 727 794 723 Q 696 689 582 674 Q 548 668 572 653 Q 606 634 659 641 L 698 651 Z","M 657 540 Q 668 553 678 566 Q 699 594 712 608 Q 734 624 698 651 C 674 669 660 671 659 641 Q 659 617 641 563 Q 631 549 630 535 C 626 505 638 517 657 540 Z","M 596 531 Q 594 533 591 535 Q 571 549 557 543 Q 555 541 555 536 Q 555 525 561 515 Q 582 472 572 295 Q 566 269 566 245 Q 566 198 586 180 Q 595 171 604 180 Q 611 192 612 201 L 613 229 Q 615 239 615 265 Q 613 294 613 308 L 613 331 L 613 406 L 613 431 Q 613 455 615 506 C 615 517 615 517 596 531 Z","M 809 223 Q 809 216 817 201 Q 831 173 841 174 Q 855 176 869 209 Q 874 219 876 225 Q 882 242 878 272 Q 868 363 866 451 Q 866 463 868 472 Q 871 489 885 517 Q 892 533 886 539 Q 861 561 817 579 Q 802 583 780 576 Q 774 574 657 540 L 630 535 Q 608 533 596 531 C 566 527 585 503 615 506 Q 627 506 642 510 Q 710 527 768 538 Q 789 542 795 531 Q 809 514 811 453 Q 817 290 811 249 L 809 223 Z","M 613 406 L 618 406 Q 686 417 747 423 Q 768 428 759 440 Q 749 452 727 456 Q 710 459 613 431 C 584 423 583 406 613 406 Z","M 613 308 L 623 308 Q 708 317 761 319 Q 783 323 774 334 Q 762 357 718 354 Q 690 352 613 331 C 584 323 583 308 613 308 Z","M 612 201 L 621 201 L 809 223 C 839 226 835 232 811 249 Q 800 259 777 258 Q 753 257 613 229 C 584 223 582 201 612 201 Z","M 623 161 Q 608 95 455 -29 Q 447 -35 447 -37 Q 446 -41 460 -39 Q 480 -37 504 -27 Q 562 -4 640 76 Q 686 123 690 126 Q 695 131 697 137 Q 704 157 671 167 Q 652 174 641 174 Q 627 174 623 161 Z","M 757 157 Q 757 153 757 151 Q 758 140 774 118 Q 829 47 864 -7 Q 877 -29 892 -36 Q 898 -37 903 -33 Q 923 -23 917 25 Q 914 77 768 163 Q 761 169 757 157 Z"],"medians":[[[306,809],[331,777],[299,694],[261,628],[204,549],[134,471],[36,394]],[[343,709],[352,714],[418,680],[449,658],[470,627]],[[247,544],[296,542],[396,571],[430,571]],[[119,429],[133,427],[170,326]],[[157,429],[195,452],[229,454],[238,443],[232,399],[223,394]],[[180,349],[185,359],[223,372],[254,372]],[[261,467],[274,459],[282,444],[303,366]],[[294,468],[302,463],[319,466],[353,482],[378,469],[365,424],[346,415]],[[312,376],[342,394],[386,397]],[[399,500],[418,476],[436,400]],[[428,499],[438,494],[495,510],[511,504],[517,496],[513,469],[509,453],[497,445]],[[449,411],[455,420],[472,424],[512,429],[528,425]],[[140,265],[165,240],[171,216],[174,151],[162,84],[168,40]],[[176,266],[196,259],[400,303],[434,303],[452,293],[458,266],[459,170],[452,94],[438,71],[383,94]],[[226,173],[325,199],[399,208]],[[236,251],[256,233],[264,91]],[[330,271],[351,252],[345,75]],[[574,665],[607,659],[648,661],[853,714],[907,718]],[[667,636],[685,620],[651,553],[636,541]],[[563,537],[592,497],[590,223],[595,187]],[[601,525],[632,521],[792,558],[815,551],[843,521],[837,466],[846,276],[841,185]],[[616,412],[628,424],[686,434],[729,439],[750,433]],[[618,314],[628,322],[706,335],[741,336],[766,328]],[[617,208],[629,219],[775,239],[792,239],[800,230]],[[680,143],[651,136],[615,87],[554,27],[503,-10],[454,-36]],[[766,154],[877,36],[893,-2],[894,-22]]]}


const colorSaturation = 100 // 0-255
const colorLightness = 40 // 0-255
const fontSize = 80
const fontBorderWidth = 8




// helper function
function hslToRgb(h, s, l) { // To generate rainbow colors
    h /= 360
    s /= 100
    l /= 100
    let r, g, b
    if (s === 0) {
        r = g = b = l // achromatic
    } else {
        const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1
            if (t > 1) t -= 1
            if (t < 1 / 6) return p + (q - p) * 6 * t
            if (t < 1 / 2) return q
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
            return p
        }
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s
        const p = 2 * l - q
        r = hue2rgb(p, q, h + 1 / 3)
        g = hue2rgb(p, q, h)
        b = hue2rgb(p, q, h - 1 / 3)
    }
    const toHex = x => {
        const hex = Math.round(x * 255).toString(16)
        return hex.length === 1 ? '0' + hex : hex
    }
    return `#${toHex(r)}${toHex(g)}${toHex(b)}`
}

// helper function
function circlesIntersect(circle1, circle2) {
    const deltaX = circle1.x - circle2.x
    const deltaY = circle1.y - circle2.y
    const rSum = circle1.r + circle2.r
    return deltaX*deltaX + deltaY*deltaY <= rSum * rSum
}
// helper function
function anyCircleIntersects(circleArr) {

    for (const circle1 of circleArr) {
        for (const circle2 of circleArr) {
            if (circle1 !== circle2) {
                if (circlesIntersect(circle1,circle2))
                    return true
            }
        }
    }
}
// helper function
function calculate_point_on_other_side_of_p2(p1, p2, distance_p2_to_p3) {
    const deltaX = p1[0]-p2[0]
    const deltaY = p1[1]-p2[1]
    const distance_p1_to_p2 = Math.sqrt(deltaX*deltaX + deltaY*deltaY)
    const scale = distance_p2_to_p3 / distance_p1_to_p2
    let p3 = []
    p3[0] = p2[0] - deltaX * scale
    p3[1] = p2[1] - deltaY * scale
    return p3
}

//const lineReader = readline.createInterface({
//    input: fs.createReadStream('../graphics.txt')
//})

//lineReader.on('line', line => {
    //const item = JSON.parse(line)
    const item = strokeData
    const charCode = item.character.charCodeAt()
    const startingPoints = item.medians.map(i=>({x:i[0][0],y:i[0][1]}))

    let pathes = ``
    let strokeStartPositions = []
    for (const [i,stroke] of item.strokes.entries()) {
        const strokeColor = hslToRgb(360/item.strokes.length*(i+1), colorSaturation, colorLightness)
        pathes += `    <path d="${stroke}" fill="${strokeColor}"/>\n`
        let x = startingPoints[i].x
        x = i<9 ? x-fontSize/4 : x-fontSize/2
        //x = x-fontSize/2
        let y = 900+fontSize/2-startingPoints[i].y
        //y = y-fontSize/2

        strokeStartPositions[i] = [x, y]
    }

    const t0 = Date.now()
    // while any circles intercept each other
    while (anyCircleIntersects(strokeStartPositions.map((pos,i)=>{return {r:i<9 ? fontSize/2.5 : fontSize/1.75, x:pos[0], y:pos[1]}}))) {
        if (Date.now() > t0+5000) {
            alert("Timeout!")
            break
        }
        // iterate over all circles
        for (const [i,p1] of strokeStartPositions.entries()) {
            const p1X = p1[0]
            const p1Y = p1[1]
            const p1Radius = i<9 ? fontSize/2.5 : fontSize/1.75
            // iterate over all circles
            for (const [j,p2] of strokeStartPositions.entries()) {
                if (i === j) // if the circle of this iteration is the same as the circle of the outer iteration
                    continue // skip this iteration (continue)
                const p2X = p2[0]
                const p2Y = p2[1]
                // If for some reason 2 circles are on the exact same position, move one of them a little
                if (p1X === p2X && p1Y === p2Y) 
                    p2[0] = p2[0]+1 // p2[1] = p2[1]+1
                const p2Radius = j<9 ? fontSize/2.5 : fontSize/1.75
                // if the circle of this and the outer iteration intercept
                if (circlesIntersect({r:p1Radius, x:p1X, y:p1Y}, {r:p2Radius, x:p2X, y:p2Y})) {
                    // calculate a point that from the perspective of circle1
                    // is about 1 pixel behind circle 2
                    let newP2 = calculate_point_on_other_side_of_p2(p1, p2, 1)
                    // calculate a point that from the perspective of circle2
                    // is about 1 pixel behind circle 1
                    let newP1 = calculate_point_on_other_side_of_p2(p2, p1, 1)
                    // and move circle2 to the newP2 position
                    strokeStartPositions[i][0] = Math.round(newP1[0])
                    strokeStartPositions[i][1] = Math.round(newP1[1])
                    // and move circle1 to the newP1 position
                    strokeStartPositions[j][0] = Math.round(newP2[0])
                    strokeStartPositions[j][1] = Math.round(newP2[1])
                }
            }
        }
    }
    let texts = ``
    for (const [i,pos] of strokeStartPositions.entries()) {
        const textColor = hslToRgb(360/strokeStartPositions.length*(i+1), colorSaturation, colorLightness)
        //const textColor = '#FFFFFF'
        const x = pos[0]
        const y = pos[1]
        texts += `    <text fill="${textColor}" x="${x}" y="${y}">${i+1}</text>\n`
    }

    const newSvgContent = `<svg version="1.1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
  <style type="text/css">
    text {
      font-family: Helvetica;
      font-size: ${fontSize}px;
      paint-order: stroke;
      stroke: #FFFFFF;
      stroke-width: ${fontBorderWidth}px;
      stroke-linecap: butt;
      stroke-linejoin: miter;
      font-weight: bold;
    }
  </style>
  <g stroke="lightgray" stroke-dasharray="1,1" stroke-width="1" transform="scale(4, 4)">
    <line x1="0" y1="0" x2="256" y2="256"/>
    <line x1="256" y1="0" x2="0" y2="256"/>
    <line x1="128" y1="0" x2="128" y2="256"/>
    <line x1="0" y1="128" x2="256" y2="128"/>
  </g>
  <g transform="scale(1, -1) translate(0, -900)">
${pathes}  </g>
  <g>
${texts}  </g>
</svg>`

    const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    svgEl.innerHTML = newSvgContent
    document.body.appendChild(svgEl)
    //fs.outputFile(`../svgs-still/${charCode}-still.svg`, newSvgContent).then(() => {
    //    //console.log(`../svgs-still/${charCode}-still.svg written`) // logging severely reduces performance
    //}).catch(e=>console.error(new Error(e)))
//})
html, body {
  width: 100%;
  height: 100%;
}
svg {
  width: 100%;
  height: 100%;
}
like image 181
Forivin Avatar answered Oct 22 '22 04:10

Forivin


Changes made. I'm using the midpoint of the stroke instead of the beginning, so the points are farther apart. Even though it doesn't show the beginning of the stroke, it's now easier to see which color/stroke the number is associated with.

  • const fontSize = 40
  • const startingPoints = item.medians.map(i=>({x:i[i.length % 2 == 0 ? Math.floor(i.length/2)-1 : Math.floor(i.length/2)][0],y:i[i.length % 2 == 0 ? Math.floor(i.length/2)-1 : Math.floor(i.length/2)][1]})) // use midpoint of stroke
  • const x = -20+startingPoints[i].x //change positioning
  • const y = 915+fontSize/2-startingPoints[i].y //change positioning

//#!/usr/bin/env node
//const fs = require('fs-extra')
//const readline = require('readline')

const strokeData = {"character":"龥","strokes":["M 128 445 Q 222 525 301 653 Q 319 685 334 709 L 343 725 Q 351 746 367 763 Q 376 772 374 782 Q 374 791 361 801 Q 327 827 298 819 Q 290 819 293 806 Q 310 732 219 604 L 171 539 Q 133 492 33 396 Q 26 392 35 390 Q 43 390 110 431 L 128 445 Z","M 334 709 Q 386 675 447 629 Q 461 617 472 615 Q 478 615 482 624 Q 488 634 474 663 Q 459 700 343 725 C 314 731 309 725 334 709 Z","M 253 553 Q 224 546 246 534 Q 276 517 325 531 Q 423 558 435 563 Q 439 567 439 572 Q 437 586 406 590 Q 388 593 316 566 L 253 553 Z","M 147 441 Q 143 445 128 445 C 101 446 101 446 110 431 Q 125 406 140 365 Q 153 331 162 323 Q 174 311 176 321 Q 178 329 175 343 L 171 363 Q 161 394 153 422 C 148 441 148 441 147 441 Z","M 255 464 Q 233 477 223 476 Q 213 475 180 456 Q 172 453 147 441 C 120 428 124 416 153 422 Q 161 423 181 431 Q 212 442 216 437 Q 221 433 216 383 C 213 353 241 353 245 383 Q 251 431 265 445 C 272 454 272 454 255 464 Z","M 175 343 Q 180 343 186 345 Q 216 357 254 367 Q 261 368 259 373 Q 259 377 245 383 L 216 383 Q 213 383 171 363 C 144 350 145 341 175 343 Z","M 285 468 Q 261 473 255 470 Q 255 468 255 464 C 255 459 255 469 265 445 Q 272 429 278 405 Q 288 371 295 363 Q 305 352 310 361 Q 310 367 310 371 L 308 392 Q 298 434 298 450 C 297 465 297 465 285 468 Z","M 374 410 Q 386 441 396 454 Q 404 463 406 466 C 413 475 413 475 398 484 L 374 498 Q 360 506 347 499 Q 327 482 285 468 C 257 458 270 438 298 450 Q 300 451 310 455 Q 351 468 355 462 Q 357 461 357 459 Q 359 445 342 406 C 330 378 363 382 374 410 Z","M 310 371 Q 323 378 385 390 Q 392 391 392 398 Q 390 402 374 410 C 368 413 368 413 342 406 Q 333 404 308 392 C 281 379 283 358 310 371 Z","M 406 466 Q 424 400 427 396 Q 437 382 441 392 L 443 406 L 441 425 Q 434 470 433 482 C 432 494 432 494 420 500 Q 419 501 416 502 Q 398 508 394 504 Q 390 500 398 484 L 406 466 Z","M 519 439 Q 529 478 541 490 Q 555 506 540 514 Q 514 526 506 528 Q 496 531 484 525 Q 454 510 420 500 C 391 491 403 477 433 482 Q 445 484 454 487 Q 490 496 495 490 Q 496 490 496 487 Q 500 472 490 439 C 482 410 511 410 519 439 Z","M 443 406 Q 449 406 455 407 Q 490 416 527 419 Q 534 420 534 425 Q 534 429 519 439 C 519 439 519 439 490 439 Q 488 441 441 425 C 413 415 413 405 443 406 Z","M 169 267 Q 151 272 139 272 Q 134 270 133 267 Q 132 264 139 246 Q 167 188 145 104 Q 129 91 142 62 Q 151 43 160 37 Q 166 27 173 32 Q 192 50 192 163 Q 192 202 192 242 C 192 261 192 261 169 267 Z","M 347 278 Q 402 290 419 284 Q 433 278 435 258 Q 439 207 429 114 Q 431 98 416 98 Q 396 98 386 100 Q 380 100 377 98 Q 369 94 398 69 Q 419 47 431 22 Q 441 15 451 20 Q 455 22 459 25 Q 480 58 482 158 Q 476 267 486 283 Q 494 292 488 302 Q 484 307 472 314 Q 437 333 419 323 Q 409 319 388 316 Q 271 292 169 267 C 140 260 164 232 192 242 Q 208 248 231 254 L 248 257 Q 272 265 323 274 L 347 278 Z","M 244 188 Q 235 186 214 178 Q 201 174 217 164 Q 226 158 246 163 L 274 170 L 332 184 L 367 190 Q 376 194 396 198 Q 402 199 406 202 Q 415 209 397 218 Q 379 227 368 223 L 332 216 Q 327 216 274 196 L 244 188 Z","M 231 254 Q 231 251 233 246 Q 241 216 244 188 L 246 163 Q 252 102 256 93 Q 265 78 270 87 Q 274 97 274 170 L 274 196 Q 274 208 275 218 Q 276 239 274 243 Q 271 250 248 257 C 229 263 229 263 231 254 Z","M 332 184 Q 332 80 342 71 Q 343 71 347 71 Q 351 73 354 81 Q 360 98 367 190 L 368 223 Q 368 229 372 243 Q 376 259 368 267 Q 355 276 347 278 C 319 288 317 289 323 274 Q 331 257 332 216 L 332 184 Z","M 698 651 L 862 688 Q 908 700 914 708 Q 923 714 918 723 Q 913 729 903 734 Q 864 751 825 733 Q 808 727 794 723 Q 696 689 582 674 Q 548 668 572 653 Q 606 634 659 641 L 698 651 Z","M 657 540 Q 668 553 678 566 Q 699 594 712 608 Q 734 624 698 651 C 674 669 660 671 659 641 Q 659 617 641 563 Q 631 549 630 535 C 626 505 638 517 657 540 Z","M 596 531 Q 594 533 591 535 Q 571 549 557 543 Q 555 541 555 536 Q 555 525 561 515 Q 582 472 572 295 Q 566 269 566 245 Q 566 198 586 180 Q 595 171 604 180 Q 611 192 612 201 L 613 229 Q 615 239 615 265 Q 613 294 613 308 L 613 331 L 613 406 L 613 431 Q 613 455 615 506 C 615 517 615 517 596 531 Z","M 809 223 Q 809 216 817 201 Q 831 173 841 174 Q 855 176 869 209 Q 874 219 876 225 Q 882 242 878 272 Q 868 363 866 451 Q 866 463 868 472 Q 871 489 885 517 Q 892 533 886 539 Q 861 561 817 579 Q 802 583 780 576 Q 774 574 657 540 L 630 535 Q 608 533 596 531 C 566 527 585 503 615 506 Q 627 506 642 510 Q 710 527 768 538 Q 789 542 795 531 Q 809 514 811 453 Q 817 290 811 249 L 809 223 Z","M 613 406 L 618 406 Q 686 417 747 423 Q 768 428 759 440 Q 749 452 727 456 Q 710 459 613 431 C 584 423 583 406 613 406 Z","M 613 308 L 623 308 Q 708 317 761 319 Q 783 323 774 334 Q 762 357 718 354 Q 690 352 613 331 C 584 323 583 308 613 308 Z","M 612 201 L 621 201 L 809 223 C 839 226 835 232 811 249 Q 800 259 777 258 Q 753 257 613 229 C 584 223 582 201 612 201 Z","M 623 161 Q 608 95 455 -29 Q 447 -35 447 -37 Q 446 -41 460 -39 Q 480 -37 504 -27 Q 562 -4 640 76 Q 686 123 690 126 Q 695 131 697 137 Q 704 157 671 167 Q 652 174 641 174 Q 627 174 623 161 Z","M 757 157 Q 757 153 757 151 Q 758 140 774 118 Q 829 47 864 -7 Q 877 -29 892 -36 Q 898 -37 903 -33 Q 923 -23 917 25 Q 914 77 768 163 Q 761 169 757 157 Z"],"medians":[[[306,809],[331,777],[299,694],[261,628],[204,549],[134,471],[36,394]],[[343,709],[352,714],[418,680],[449,658],[470,627]],[[247,544],[296,542],[396,571],[430,571]],[[119,429],[133,427],[170,326]],[[157,429],[195,452],[229,454],[238,443],[232,399],[223,394]],[[180,349],[185,359],[223,372],[254,372]],[[261,467],[274,459],[282,444],[303,366]],[[294,468],[302,463],[319,466],[353,482],[378,469],[365,424],[346,415]],[[312,376],[342,394],[386,397]],[[399,500],[418,476],[436,400]],[[428,499],[438,494],[495,510],[511,504],[517,496],[513,469],[509,453],[497,445]],[[449,411],[455,420],[472,424],[512,429],[528,425]],[[140,265],[165,240],[171,216],[174,151],[162,84],[168,40]],[[176,266],[196,259],[400,303],[434,303],[452,293],[458,266],[459,170],[452,94],[438,71],[383,94]],[[226,173],[325,199],[399,208]],[[236,251],[256,233],[264,91]],[[330,271],[351,252],[345,75]],[[574,665],[607,659],[648,661],[853,714],[907,718]],[[667,636],[685,620],[651,553],[636,541]],[[563,537],[592,497],[590,223],[595,187]],[[601,525],[632,521],[792,558],[815,551],[843,521],[837,466],[846,276],[841,185]],[[616,412],[628,424],[686,434],[729,439],[750,433]],[[618,314],[628,322],[706,335],[741,336],[766,328]],[[617,208],[629,219],[775,239],[792,239],[800,230]],[[680,143],[651,136],[615,87],[554,27],[503,-10],[454,-36]],[[766,154],[877,36],[893,-2],[894,-22]]]}


const colorSaturation = 100 // 0-255
const colorLightness = 40 // 0-255
const fontSize = 40
const fontBorderWidth = 6

function hslToRgb(h, s, l) { // To generate rainbow colors
    h /= 360
    s /= 100
    l /= 100
    let r, g, b
    if (s === 0) {
        r = g = b = l // achromatic
    } else {
        const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1
            if (t > 1) t -= 1
            if (t < 1 / 6) return p + (q - p) * 6 * t
            if (t < 1 / 2) return q
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
            return p
        }
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s
        const p = 2 * l - q
        r = hue2rgb(p, q, h + 1 / 3)
        g = hue2rgb(p, q, h)
        b = hue2rgb(p, q, h - 1 / 3)
    }
    const toHex = x => {
        const hex = Math.round(x * 255).toString(16)
        return hex.length === 1 ? '0' + hex : hex
    }
    return `#${toHex(r)}${toHex(g)}${toHex(b)}`
}

//const lineReader = readline.createInterface({
//    input: fs.createReadStream('../graphics.txt')
//})

//lineReader.on('line', line => {
    //const item = JSON.parse(line)
    const item = strokeData
    const charCode = item.character.charCodeAt()
    const startingPoints = item.medians.map(i=>({x:i[i.length % 2 == 0 ? Math.floor(i.length/2)-1 : Math.floor(i.length/2)][0],y:i[i.length % 2 == 0 ? Math.floor(i.length/2)-1 : Math.floor(i.length/2)][1]}))

    let pathes = ``
    let strokeStartPositions = []
    for (const [i,stroke] of item.strokes.entries()) {
        const strokeColor = hslToRgb(360/item.strokes.length*(i+1), colorSaturation, colorLightness)
        pathes += `    <path d="${stroke}" fill="${strokeColor}"/>\n`        
        const x = -20+startingPoints[i].x
        const y = 915+fontSize/2-startingPoints[i].y
        strokeStartPositions[i] = [x, y]
    }
    
    function repositionAlgorithm(position, attractedTo, repulsedFrom) {
        // ?
        return position
    }
    
    let numberPositions = []
    for (const [i,currentPosition] of strokeStartPositions.entries()) {
        const oldX = currentPosition[0]
        const oldY = currentPosition[1]
        const repulsedFrom = strokeStartPositions.slice().splice(i,1) // all positions but the current one
        const attractedTo = [currentPosition]
        const newPosition = repositionAlgorithm(currentPosition, attractedTo, repulsedFrom)
        numberPositions[i] = newPosition
        const newX = newPosition[0]
        const newY = newPosition[1]
        //console.log(`Moved from (${oldX}|${oldY}) to (${newX}|${newY}).`)
    }
    
    let texts = ``
    for (const [i,pos] of numberPositions.entries()) {
        const textColor = "#FFFFFF" //const textColor = hslToRgb(360/pathEls.length*(i+1), colorSaturation, colorLightness)
        const x = pos[0]
        const y = pos[1]
        texts += `    <text fill="${textColor}" x="${x}" y="${y}">${i+1}</text>\n`
    }

    const newSvgContent = `<svg version="1.1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
  <style type="text/css">
    text {
      font-family: Helvetica;
      font-size: ${fontSize}px;
      paint-order: stroke;
      stroke: #000000;
      stroke-width: 6px;
      stroke-linecap: butt;
      stroke-linejoin: miter;
      font-weight: 800;
    }
  </style>
  <g stroke="lightgray" stroke-dasharray="1,1" stroke-width="1" transform="scale(4, 4)">
    <line x1="0" y1="0" x2="256" y2="256"/>
    <line x1="256" y1="0" x2="0" y2="256"/>
    <line x1="128" y1="0" x2="128" y2="256"/>
    <line x1="0" y1="128" x2="256" y2="128"/>
  </g>
  <g transform="scale(1, -1) translate(0, -900)">
${pathes}  </g>
  <g>
${texts}  </g>
</svg>`
    
    const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    svgEl.innerHTML = newSvgContent
    document.body.appendChild(svgEl)
    //fs.outputFile(`../svgs-still/${charCode}-still.svg`, newSvgContent).then(() => {
    //    //console.log(`../svgs-still/${charCode}-still.svg written`) // logging severely reduces performance
    //}).catch(e=>console.error(new Error(e)))
//})
html, body {
  width: 100%;
  height: 100%;
}
svg {
  width: 100%;
  height: 100%;
}
like image 21
Emil Avatar answered Oct 22 '22 03:10

Emil


Maybe would be useful color each number with the same color of the respective stroke:

const strokeData = {"character":"龥","strokes":["M 128 445 Q 222 525 301 653 Q 319 685 334 709 L 343 725 Q 351 746 367 763 Q 376 772 374 782 Q 374 791 361 801 Q 327 827 298 819 Q 290 819 293 806 Q 310 732 219 604 L 171 539 Q 133 492 33 396 Q 26 392 35 390 Q 43 390 110 431 L 128 445 Z","M 334 709 Q 386 675 447 629 Q 461 617 472 615 Q 478 615 482 624 Q 488 634 474 663 Q 459 700 343 725 C 314 731 309 725 334 709 Z","M 253 553 Q 224 546 246 534 Q 276 517 325 531 Q 423 558 435 563 Q 439 567 439 572 Q 437 586 406 590 Q 388 593 316 566 L 253 553 Z","M 147 441 Q 143 445 128 445 C 101 446 101 446 110 431 Q 125 406 140 365 Q 153 331 162 323 Q 174 311 176 321 Q 178 329 175 343 L 171 363 Q 161 394 153 422 C 148 441 148 441 147 441 Z","M 255 464 Q 233 477 223 476 Q 213 475 180 456 Q 172 453 147 441 C 120 428 124 416 153 422 Q 161 423 181 431 Q 212 442 216 437 Q 221 433 216 383 C 213 353 241 353 245 383 Q 251 431 265 445 C 272 454 272 454 255 464 Z","M 175 343 Q 180 343 186 345 Q 216 357 254 367 Q 261 368 259 373 Q 259 377 245 383 L 216 383 Q 213 383 171 363 C 144 350 145 341 175 343 Z","M 285 468 Q 261 473 255 470 Q 255 468 255 464 C 255 459 255 469 265 445 Q 272 429 278 405 Q 288 371 295 363 Q 305 352 310 361 Q 310 367 310 371 L 308 392 Q 298 434 298 450 C 297 465 297 465 285 468 Z","M 374 410 Q 386 441 396 454 Q 404 463 406 466 C 413 475 413 475 398 484 L 374 498 Q 360 506 347 499 Q 327 482 285 468 C 257 458 270 438 298 450 Q 300 451 310 455 Q 351 468 355 462 Q 357 461 357 459 Q 359 445 342 406 C 330 378 363 382 374 410 Z","M 310 371 Q 323 378 385 390 Q 392 391 392 398 Q 390 402 374 410 C 368 413 368 413 342 406 Q 333 404 308 392 C 281 379 283 358 310 371 Z","M 406 466 Q 424 400 427 396 Q 437 382 441 392 L 443 406 L 441 425 Q 434 470 433 482 C 432 494 432 494 420 500 Q 419 501 416 502 Q 398 508 394 504 Q 390 500 398 484 L 406 466 Z","M 519 439 Q 529 478 541 490 Q 555 506 540 514 Q 514 526 506 528 Q 496 531 484 525 Q 454 510 420 500 C 391 491 403 477 433 482 Q 445 484 454 487 Q 490 496 495 490 Q 496 490 496 487 Q 500 472 490 439 C 482 410 511 410 519 439 Z","M 443 406 Q 449 406 455 407 Q 490 416 527 419 Q 534 420 534 425 Q 534 429 519 439 C 519 439 519 439 490 439 Q 488 441 441 425 C 413 415 413 405 443 406 Z","M 169 267 Q 151 272 139 272 Q 134 270 133 267 Q 132 264 139 246 Q 167 188 145 104 Q 129 91 142 62 Q 151 43 160 37 Q 166 27 173 32 Q 192 50 192 163 Q 192 202 192 242 C 192 261 192 261 169 267 Z","M 347 278 Q 402 290 419 284 Q 433 278 435 258 Q 439 207 429 114 Q 431 98 416 98 Q 396 98 386 100 Q 380 100 377 98 Q 369 94 398 69 Q 419 47 431 22 Q 441 15 451 20 Q 455 22 459 25 Q 480 58 482 158 Q 476 267 486 283 Q 494 292 488 302 Q 484 307 472 314 Q 437 333 419 323 Q 409 319 388 316 Q 271 292 169 267 C 140 260 164 232 192 242 Q 208 248 231 254 L 248 257 Q 272 265 323 274 L 347 278 Z","M 244 188 Q 235 186 214 178 Q 201 174 217 164 Q 226 158 246 163 L 274 170 L 332 184 L 367 190 Q 376 194 396 198 Q 402 199 406 202 Q 415 209 397 218 Q 379 227 368 223 L 332 216 Q 327 216 274 196 L 244 188 Z","M 231 254 Q 231 251 233 246 Q 241 216 244 188 L 246 163 Q 252 102 256 93 Q 265 78 270 87 Q 274 97 274 170 L 274 196 Q 274 208 275 218 Q 276 239 274 243 Q 271 250 248 257 C 229 263 229 263 231 254 Z","M 332 184 Q 332 80 342 71 Q 343 71 347 71 Q 351 73 354 81 Q 360 98 367 190 L 368 223 Q 368 229 372 243 Q 376 259 368 267 Q 355 276 347 278 C 319 288 317 289 323 274 Q 331 257 332 216 L 332 184 Z","M 698 651 L 862 688 Q 908 700 914 708 Q 923 714 918 723 Q 913 729 903 734 Q 864 751 825 733 Q 808 727 794 723 Q 696 689 582 674 Q 548 668 572 653 Q 606 634 659 641 L 698 651 Z","M 657 540 Q 668 553 678 566 Q 699 594 712 608 Q 734 624 698 651 C 674 669 660 671 659 641 Q 659 617 641 563 Q 631 549 630 535 C 626 505 638 517 657 540 Z","M 596 531 Q 594 533 591 535 Q 571 549 557 543 Q 555 541 555 536 Q 555 525 561 515 Q 582 472 572 295 Q 566 269 566 245 Q 566 198 586 180 Q 595 171 604 180 Q 611 192 612 201 L 613 229 Q 615 239 615 265 Q 613 294 613 308 L 613 331 L 613 406 L 613 431 Q 613 455 615 506 C 615 517 615 517 596 531 Z","M 809 223 Q 809 216 817 201 Q 831 173 841 174 Q 855 176 869 209 Q 874 219 876 225 Q 882 242 878 272 Q 868 363 866 451 Q 866 463 868 472 Q 871 489 885 517 Q 892 533 886 539 Q 861 561 817 579 Q 802 583 780 576 Q 774 574 657 540 L 630 535 Q 608 533 596 531 C 566 527 585 503 615 506 Q 627 506 642 510 Q 710 527 768 538 Q 789 542 795 531 Q 809 514 811 453 Q 817 290 811 249 L 809 223 Z","M 613 406 L 618 406 Q 686 417 747 423 Q 768 428 759 440 Q 749 452 727 456 Q 710 459 613 431 C 584 423 583 406 613 406 Z","M 613 308 L 623 308 Q 708 317 761 319 Q 783 323 774 334 Q 762 357 718 354 Q 690 352 613 331 C 584 323 583 308 613 308 Z","M 612 201 L 621 201 L 809 223 C 839 226 835 232 811 249 Q 800 259 777 258 Q 753 257 613 229 C 584 223 582 201 612 201 Z","M 623 161 Q 608 95 455 -29 Q 447 -35 447 -37 Q 446 -41 460 -39 Q 480 -37 504 -27 Q 562 -4 640 76 Q 686 123 690 126 Q 695 131 697 137 Q 704 157 671 167 Q 652 174 641 174 Q 627 174 623 161 Z","M 757 157 Q 757 153 757 151 Q 758 140 774 118 Q 829 47 864 -7 Q 877 -29 892 -36 Q 898 -37 903 -33 Q 923 -23 917 25 Q 914 77 768 163 Q 761 169 757 157 Z"],"medians":[[[306,809],[331,777],[299,694],[261,628],[204,549],[134,471],[36,394]],[[343,709],[352,714],[418,680],[449,658],[470,627]],[[247,544],[296,542],[396,571],[430,571]],[[119,429],[133,427],[170,326]],[[157,429],[195,452],[229,454],[238,443],[232,399],[223,394]],[[180,349],[185,359],[223,372],[254,372]],[[261,467],[274,459],[282,444],[303,366]],[[294,468],[302,463],[319,466],[353,482],[378,469],[365,424],[346,415]],[[312,376],[342,394],[386,397]],[[399,500],[418,476],[436,400]],[[428,499],[438,494],[495,510],[511,504],[517,496],[513,469],[509,453],[497,445]],[[449,411],[455,420],[472,424],[512,429],[528,425]],[[140,265],[165,240],[171,216],[174,151],[162,84],[168,40]],[[176,266],[196,259],[400,303],[434,303],[452,293],[458,266],[459,170],[452,94],[438,71],[383,94]],[[226,173],[325,199],[399,208]],[[236,251],[256,233],[264,91]],[[330,271],[351,252],[345,75]],[[574,665],[607,659],[648,661],[853,714],[907,718]],[[667,636],[685,620],[651,553],[636,541]],[[563,537],[592,497],[590,223],[595,187]],[[601,525],[632,521],[792,558],[815,551],[843,521],[837,466],[846,276],[841,185]],[[616,412],[628,424],[686,434],[729,439],[750,433]],[[618,314],[628,322],[706,335],[741,336],[766,328]],[[617,208],[629,219],[775,239],[792,239],[800,230]],[[680,143],[651,136],[615,87],[554,27],[503,-10],[454,-36]],[[766,154],[877,36],[893,-2],[894,-22]]]}

const colorSaturation = 100 // 0-255
const colorLightness = 40 // 0-255
const fontSize = 45
const fontBorderWidth = 6

function hslToRgb(h, s, l) { // To generate rainbow colors
  h /= 360
  s /= 100
  l /= 100
  let r, g, b
  if (s === 0) {
    r = g = b = l // achromatic
  } else {
    const hue2rgb = (p, q, t) => {
      if (t < 0) t += 1
      if (t > 1) t -= 1
      if (t < 1 / 6) return p + (q - p) * 6 * t
      if (t < 1 / 2) return q
      if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
      return p
    }
    const q = l < 0.5 ? l * (1 + s) : l + s - l * s
    const p = 2 * l - q
    r = hue2rgb(p, q, h + 1 / 3)
    g = hue2rgb(p, q, h)
    b = hue2rgb(p, q, h - 1 / 3)
  }
  const toHex = x => {
    const hex = Math.round(x * 255).toString(16)
    return hex.length === 1 ? '0' + hex : hex
  }
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`
}

const item = strokeData
const charCode = item.character.charCodeAt()
const startingPoints = item.medians.map(i=>({x:i[0][0],y:i[0][1]}))

let pathes = ``
let strokeStartPositions = []
for (const [i,stroke] of item.strokes.entries()) {
  const strokeColor = hslToRgb(360/item.strokes.length*(i+1), colorSaturation, colorLightness)
  pathes += `    <path d="${stroke}" fill="${strokeColor}"/>\n`        
  const x = startingPoints[i].x
  const y = 900+fontSize/2-startingPoints[i].y
  strokeStartPositions[i] = [x, y]
}

let texts = ``
for (const [i,currentPosition] of strokeStartPositions.entries()) {
  const textColor = hslToRgb(360/strokeStartPositions.length*(i+1), colorSaturation, colorLightness)
  const posX = currentPosition[0]
  const posY = currentPosition[1]
  texts += `    <text fill="${textColor}" x="${posX-20}" y="${posY-20}">${i+1}</text>\n`
}

const newSvgContent = `<svg version="1.1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<style type="text/css">
text {
font-family: Helvetica;
font-size: ${fontSize}px;
paint-order: stroke;
stroke: #000000;
stroke-width: 6px;
stroke-linecap: butt;
stroke-linejoin: miter;
font-weight: 800;
}
</style>
<g stroke="lightgray" stroke-dasharray="1,1" stroke-width="1" transform="scale(4, 4)">
<line x1="0" y1="0" x2="256" y2="256"/>
<line x1="256" y1="0" x2="0" y2="256"/>
<line x1="128" y1="0" x2="128" y2="256"/>
<line x1="0" y1="128" x2="256" y2="128"/>
</g>
<g transform="scale(1, -1) translate(0, -900)">
${pathes}  </g>
<g>
${texts}  </g>
</svg>`

const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg")
svgEl.innerHTML = newSvgContent
document.body.appendChild(svgEl)
html, body {
  width: 100%;
  height: 100%;
}
svg {
  width: 100%;
  height: 100%;
}

An ideal picture, where you show how the final result should be, would be good as starting point to reproduce it with a code

Edit #1

To generate random colors, test this repository:

//#!/usr/bin/env node
//const fs = require('fs-extra')
//const readline = require('readline')

const strokeData = {"character":"龥","strokes":["M 128 445 Q 222 525 301 653 Q 319 685 334 709 L 343 725 Q 351 746 367 763 Q 376 772 374 782 Q 374 791 361 801 Q 327 827 298 819 Q 290 819 293 806 Q 310 732 219 604 L 171 539 Q 133 492 33 396 Q 26 392 35 390 Q 43 390 110 431 L 128 445 Z","M 334 709 Q 386 675 447 629 Q 461 617 472 615 Q 478 615 482 624 Q 488 634 474 663 Q 459 700 343 725 C 314 731 309 725 334 709 Z","M 253 553 Q 224 546 246 534 Q 276 517 325 531 Q 423 558 435 563 Q 439 567 439 572 Q 437 586 406 590 Q 388 593 316 566 L 253 553 Z","M 147 441 Q 143 445 128 445 C 101 446 101 446 110 431 Q 125 406 140 365 Q 153 331 162 323 Q 174 311 176 321 Q 178 329 175 343 L 171 363 Q 161 394 153 422 C 148 441 148 441 147 441 Z","M 255 464 Q 233 477 223 476 Q 213 475 180 456 Q 172 453 147 441 C 120 428 124 416 153 422 Q 161 423 181 431 Q 212 442 216 437 Q 221 433 216 383 C 213 353 241 353 245 383 Q 251 431 265 445 C 272 454 272 454 255 464 Z","M 175 343 Q 180 343 186 345 Q 216 357 254 367 Q 261 368 259 373 Q 259 377 245 383 L 216 383 Q 213 383 171 363 C 144 350 145 341 175 343 Z","M 285 468 Q 261 473 255 470 Q 255 468 255 464 C 255 459 255 469 265 445 Q 272 429 278 405 Q 288 371 295 363 Q 305 352 310 361 Q 310 367 310 371 L 308 392 Q 298 434 298 450 C 297 465 297 465 285 468 Z","M 374 410 Q 386 441 396 454 Q 404 463 406 466 C 413 475 413 475 398 484 L 374 498 Q 360 506 347 499 Q 327 482 285 468 C 257 458 270 438 298 450 Q 300 451 310 455 Q 351 468 355 462 Q 357 461 357 459 Q 359 445 342 406 C 330 378 363 382 374 410 Z","M 310 371 Q 323 378 385 390 Q 392 391 392 398 Q 390 402 374 410 C 368 413 368 413 342 406 Q 333 404 308 392 C 281 379 283 358 310 371 Z","M 406 466 Q 424 400 427 396 Q 437 382 441 392 L 443 406 L 441 425 Q 434 470 433 482 C 432 494 432 494 420 500 Q 419 501 416 502 Q 398 508 394 504 Q 390 500 398 484 L 406 466 Z","M 519 439 Q 529 478 541 490 Q 555 506 540 514 Q 514 526 506 528 Q 496 531 484 525 Q 454 510 420 500 C 391 491 403 477 433 482 Q 445 484 454 487 Q 490 496 495 490 Q 496 490 496 487 Q 500 472 490 439 C 482 410 511 410 519 439 Z","M 443 406 Q 449 406 455 407 Q 490 416 527 419 Q 534 420 534 425 Q 534 429 519 439 C 519 439 519 439 490 439 Q 488 441 441 425 C 413 415 413 405 443 406 Z","M 169 267 Q 151 272 139 272 Q 134 270 133 267 Q 132 264 139 246 Q 167 188 145 104 Q 129 91 142 62 Q 151 43 160 37 Q 166 27 173 32 Q 192 50 192 163 Q 192 202 192 242 C 192 261 192 261 169 267 Z","M 347 278 Q 402 290 419 284 Q 433 278 435 258 Q 439 207 429 114 Q 431 98 416 98 Q 396 98 386 100 Q 380 100 377 98 Q 369 94 398 69 Q 419 47 431 22 Q 441 15 451 20 Q 455 22 459 25 Q 480 58 482 158 Q 476 267 486 283 Q 494 292 488 302 Q 484 307 472 314 Q 437 333 419 323 Q 409 319 388 316 Q 271 292 169 267 C 140 260 164 232 192 242 Q 208 248 231 254 L 248 257 Q 272 265 323 274 L 347 278 Z","M 244 188 Q 235 186 214 178 Q 201 174 217 164 Q 226 158 246 163 L 274 170 L 332 184 L 367 190 Q 376 194 396 198 Q 402 199 406 202 Q 415 209 397 218 Q 379 227 368 223 L 332 216 Q 327 216 274 196 L 244 188 Z","M 231 254 Q 231 251 233 246 Q 241 216 244 188 L 246 163 Q 252 102 256 93 Q 265 78 270 87 Q 274 97 274 170 L 274 196 Q 274 208 275 218 Q 276 239 274 243 Q 271 250 248 257 C 229 263 229 263 231 254 Z","M 332 184 Q 332 80 342 71 Q 343 71 347 71 Q 351 73 354 81 Q 360 98 367 190 L 368 223 Q 368 229 372 243 Q 376 259 368 267 Q 355 276 347 278 C 319 288 317 289 323 274 Q 331 257 332 216 L 332 184 Z","M 698 651 L 862 688 Q 908 700 914 708 Q 923 714 918 723 Q 913 729 903 734 Q 864 751 825 733 Q 808 727 794 723 Q 696 689 582 674 Q 548 668 572 653 Q 606 634 659 641 L 698 651 Z","M 657 540 Q 668 553 678 566 Q 699 594 712 608 Q 734 624 698 651 C 674 669 660 671 659 641 Q 659 617 641 563 Q 631 549 630 535 C 626 505 638 517 657 540 Z","M 596 531 Q 594 533 591 535 Q 571 549 557 543 Q 555 541 555 536 Q 555 525 561 515 Q 582 472 572 295 Q 566 269 566 245 Q 566 198 586 180 Q 595 171 604 180 Q 611 192 612 201 L 613 229 Q 615 239 615 265 Q 613 294 613 308 L 613 331 L 613 406 L 613 431 Q 613 455 615 506 C 615 517 615 517 596 531 Z","M 809 223 Q 809 216 817 201 Q 831 173 841 174 Q 855 176 869 209 Q 874 219 876 225 Q 882 242 878 272 Q 868 363 866 451 Q 866 463 868 472 Q 871 489 885 517 Q 892 533 886 539 Q 861 561 817 579 Q 802 583 780 576 Q 774 574 657 540 L 630 535 Q 608 533 596 531 C 566 527 585 503 615 506 Q 627 506 642 510 Q 710 527 768 538 Q 789 542 795 531 Q 809 514 811 453 Q 817 290 811 249 L 809 223 Z","M 613 406 L 618 406 Q 686 417 747 423 Q 768 428 759 440 Q 749 452 727 456 Q 710 459 613 431 C 584 423 583 406 613 406 Z","M 613 308 L 623 308 Q 708 317 761 319 Q 783 323 774 334 Q 762 357 718 354 Q 690 352 613 331 C 584 323 583 308 613 308 Z","M 612 201 L 621 201 L 809 223 C 839 226 835 232 811 249 Q 800 259 777 258 Q 753 257 613 229 C 584 223 582 201 612 201 Z","M 623 161 Q 608 95 455 -29 Q 447 -35 447 -37 Q 446 -41 460 -39 Q 480 -37 504 -27 Q 562 -4 640 76 Q 686 123 690 126 Q 695 131 697 137 Q 704 157 671 167 Q 652 174 641 174 Q 627 174 623 161 Z","M 757 157 Q 757 153 757 151 Q 758 140 774 118 Q 829 47 864 -7 Q 877 -29 892 -36 Q 898 -37 903 -33 Q 923 -23 917 25 Q 914 77 768 163 Q 761 169 757 157 Z"],"medians":[[[306,809],[331,777],[299,694],[261,628],[204,549],[134,471],[36,394]],[[343,709],[352,714],[418,680],[449,658],[470,627]],[[247,544],[296,542],[396,571],[430,571]],[[119,429],[133,427],[170,326]],[[157,429],[195,452],[229,454],[238,443],[232,399],[223,394]],[[180,349],[185,359],[223,372],[254,372]],[[261,467],[274,459],[282,444],[303,366]],[[294,468],[302,463],[319,466],[353,482],[378,469],[365,424],[346,415]],[[312,376],[342,394],[386,397]],[[399,500],[418,476],[436,400]],[[428,499],[438,494],[495,510],[511,504],[517,496],[513,469],[509,453],[497,445]],[[449,411],[455,420],[472,424],[512,429],[528,425]],[[140,265],[165,240],[171,216],[174,151],[162,84],[168,40]],[[176,266],[196,259],[400,303],[434,303],[452,293],[458,266],[459,170],[452,94],[438,71],[383,94]],[[226,173],[325,199],[399,208]],[[236,251],[256,233],[264,91]],[[330,271],[351,252],[345,75]],[[574,665],[607,659],[648,661],[853,714],[907,718]],[[667,636],[685,620],[651,553],[636,541]],[[563,537],[592,497],[590,223],[595,187]],[[601,525],[632,521],[792,558],[815,551],[843,521],[837,466],[846,276],[841,185]],[[616,412],[628,424],[686,434],[729,439],[750,433]],[[618,314],[628,322],[706,335],[741,336],[766,328]],[[617,208],[629,219],[775,239],[792,239],[800,230]],[[680,143],[651,136],[615,87],[554,27],[503,-10],[454,-36]],[[766,154],[877,36],[893,-2],[894,-22]]]}

const fontSize = 45
const fontBorderWidth = 6

//const lineReader = readline.createInterface({
//    input: fs.createReadStream('../graphics.txt')
//})

//lineReader.on('line', line => {
  //const item = JSON.parse(line)
  const item = strokeData
  const charCode = item.character.charCodeAt()
  const startingPoints = item.medians.map(i=>({x:i[0][0],y:i[0][1]}))

  let pathes = ``
  let strokeStartPositions = []
  let randomColors = []
  for (const [i,stroke] of item.strokes.entries()) {
    randomColors.push(randomColor())
    const strokeColor = randomColors[i]
    pathes += `    <path d="${stroke}" fill="${strokeColor}"/>\n`        
    let x = startingPoints[i].x
    x = i<9 ? x-fontSize/4 : x-fontSize/2
    //x = x-fontSize/2
    let y = 900+fontSize/2-startingPoints[i].y
    //y = y-fontSize/2

    strokeStartPositions[i] = [x, y]
  }

  function circlesIntersect(circle1, circle2) {
    const deltaX = circle1.x - circle2.x
    const deltaY = circle1.y - circle2.y
    const rSum = circle1.r + circle2.r
    return deltaX*deltaX + deltaY*deltaY <= rSum * rSum
  }

  function anyCircleIntersects(circleArr) {
    for (const circle1 of circleArr) {
      for (const circle2 of circleArr) {
        if (circle1 !== circle2) {
          if (circlesIntersect(circle1,circle2))
            return true
        }
      }
    }
  }

  function calculate_point_on_other_side_of_p2(p1, p2, distance_p2_to_p3) {
    const deltaX = p1[0]-p2[0]
    const deltaY = p1[1]-p2[1]
    const distance_p1_to_p2 = Math.sqrt(deltaX*deltaX + deltaY*deltaY)
    const scale = distance_p2_to_p3 / distance_p1_to_p2
    let p3 = []
    p3[0] = p2[0] - deltaX * scale
    p3[1] = p2[1] - deltaY * scale
    return p3
  }

  const t0 = Date.now()
  while (anyCircleIntersects(strokeStartPositions.map(pos=>{return {r:fontSize/2, x:pos[0], y:pos[1]}}))) {
    if (Date.now() > t0+1000) {
      ;
      break
    }
    for (const [i,p1] of strokeStartPositions.entries()) {
      const p1X = p1[0]
      const p1Y = p1[1]
      for (const [j,p2] of strokeStartPositions.entries()) {
        if (i === j)
          continue
        const p2X = p2[0]
        const p2Y = p2[1]
        if (p1X === p2X)
          p2[0] = p2[0]+1
        if (p1Y === p2Y)
          p2[1] = p2[1]+1
        const p1Radius = i<9 ? fontSize/2.5 : fontSize/1.75
        const p2Radius = j<9 ? fontSize/2.5 : fontSize/1.75
        if (circlesIntersect({r:p1Radius, x:p1X, y:p1Y}, {r:p2Radius, x:p2X, y:p2Y})) {
          let newP2 = calculate_point_on_other_side_of_p2(p1, p2, 1)
          let newP1 = calculate_point_on_other_side_of_p2(p2, p1, 1)
          strokeStartPositions[i][0] = Math.round(newP1[0])
          strokeStartPositions[i][1] = Math.round(newP1[1])
          strokeStartPositions[j][0] = Math.round(newP2[0])
          strokeStartPositions[j][1] = Math.round(newP2[1])
        }
      }
    }
  }
  let texts = ``
  for (const [i,pos] of strokeStartPositions.entries()) {
    //const textColor = "#FFFFFF"
    const textColor = randomColors[i];
    const x = pos[0]
    const y = pos[1]
    texts += `    <text fill="${textColor}" x="${x}" y="${y}">${i+1}</text>\n`
  }

  const newSvgContent = `<svg version="1.1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<style type="text/css">
  @import url('https://fonts.googleapis.com/css?family=Roboto');
  text {
    font-family: 'Roboto', sans-serif;
    font-size: ${fontSize}px;
    paint-order: stroke;
    stroke: #000000;
    stroke-width: 6px;
    stroke-linecap: butt;
    stroke-linejoin: miter;
    font-weight: 800;
  }
</style>
<g stroke="lightgray" stroke-dasharray="1,1" stroke-width="1" transform="scale(4, 4)">
  <line x1="0" y1="0" x2="256" y2="256"/>
  <line x1="256" y1="0" x2="0" y2="256"/>
  <line x1="128" y1="0" x2="128" y2="256"/>
  <line x1="0" y1="128" x2="256" y2="128"/>
</g>
<g transform="scale(1, -1) translate(0, -900)">
  ${pathes} 
</g>
<g>
  ${texts}
</g>
</svg>`

  const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg")
  svgEl.innerHTML = newSvgContent
  document.body.appendChild(svgEl)
  //fs.outputFile(`../svgs-still/${charCode}-still.svg`, newSvgContent).then(() => {
  //    //console.log(`../svgs-still/${charCode}-still.svg written`) // logging severely reduces performance
  //}).catch(e=>console.error(new Error(e)))
//})
html, body {
    width: 100%;
    height: 100%;
}
svg {
    width: 100%;
    height: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/randomcolor/0.6.1/randomColor.min.js"></script>

Edit #2

Take a look at this pen!

Cheers

like image 4
Riccardo Volpe Avatar answered Oct 22 '22 02:10

Riccardo Volpe


I've done similar stuff in the past and it's quite tricky because this is basically like building your own UI framework.

Here's my take on it. Unfortunately I couldn't complete it but the basic idea is this:

  1. Get beginning point and ending point of each stroke
  2. Calculate stroke direction (north, north-east, west, etc...)
  3. Assume each text item displaying the index is a rectangle
  4. Check if the index you are about to add overlaps with any of the previous indexes (Ex: index 5 since it overlaps with 4)
  5. Move the new stroke index in the direction of the stroke until it no longer overlaps with the previous index rectangle.

The reason the example below doesn't work is because you need the accurate start and end point of each of the strokes. Initially I was using the first median value of each stroke as the starting point (what you provided) and the last median value as the ending point (not accurate it seems)

If you were to plug in the actual starting and ending points of each stroke you should get a much better result.

const strokeData = {"character":"?","strokes":["M 128 445 Q 222 525 301 653 Q 319 685 334 709 L 343 725 Q 351 746 367 763 Q 376 772 374 782 Q 374 791 361 801 Q 327 827 298 819 Q 290 819 293 806 Q 310 732 219 604 L 171 539 Q 133 492 33 396 Q 26 392 35 390 Q 43 390 110 431 L 128 445 Z","M 334 709 Q 386 675 447 629 Q 461 617 472 615 Q 478 615 482 624 Q 488 634 474 663 Q 459 700 343 725 C 314 731 309 725 334 709 Z","M 253 553 Q 224 546 246 534 Q 276 517 325 531 Q 423 558 435 563 Q 439 567 439 572 Q 437 586 406 590 Q 388 593 316 566 L 253 553 Z","M 147 441 Q 143 445 128 445 C 101 446 101 446 110 431 Q 125 406 140 365 Q 153 331 162 323 Q 174 311 176 321 Q 178 329 175 343 L 171 363 Q 161 394 153 422 C 148 441 148 441 147 441 Z","M 255 464 Q 233 477 223 476 Q 213 475 180 456 Q 172 453 147 441 C 120 428 124 416 153 422 Q 161 423 181 431 Q 212 442 216 437 Q 221 433 216 383 C 213 353 241 353 245 383 Q 251 431 265 445 C 272 454 272 454 255 464 Z","M 175 343 Q 180 343 186 345 Q 216 357 254 367 Q 261 368 259 373 Q 259 377 245 383 L 216 383 Q 213 383 171 363 C 144 350 145 341 175 343 Z","M 285 468 Q 261 473 255 470 Q 255 468 255 464 C 255 459 255 469 265 445 Q 272 429 278 405 Q 288 371 295 363 Q 305 352 310 361 Q 310 367 310 371 L 308 392 Q 298 434 298 450 C 297 465 297 465 285 468 Z","M 374 410 Q 386 441 396 454 Q 404 463 406 466 C 413 475 413 475 398 484 L 374 498 Q 360 506 347 499 Q 327 482 285 468 C 257 458 270 438 298 450 Q 300 451 310 455 Q 351 468 355 462 Q 357 461 357 459 Q 359 445 342 406 C 330 378 363 382 374 410 Z","M 310 371 Q 323 378 385 390 Q 392 391 392 398 Q 390 402 374 410 C 368 413 368 413 342 406 Q 333 404 308 392 C 281 379 283 358 310 371 Z","M 406 466 Q 424 400 427 396 Q 437 382 441 392 L 443 406 L 441 425 Q 434 470 433 482 C 432 494 432 494 420 500 Q 419 501 416 502 Q 398 508 394 504 Q 390 500 398 484 L 406 466 Z","M 519 439 Q 529 478 541 490 Q 555 506 540 514 Q 514 526 506 528 Q 496 531 484 525 Q 454 510 420 500 C 391 491 403 477 433 482 Q 445 484 454 487 Q 490 496 495 490 Q 496 490 496 487 Q 500 472 490 439 C 482 410 511 410 519 439 Z","M 443 406 Q 449 406 455 407 Q 490 416 527 419 Q 534 420 534 425 Q 534 429 519 439 C 519 439 519 439 490 439 Q 488 441 441 425 C 413 415 413 405 443 406 Z","M 169 267 Q 151 272 139 272 Q 134 270 133 267 Q 132 264 139 246 Q 167 188 145 104 Q 129 91 142 62 Q 151 43 160 37 Q 166 27 173 32 Q 192 50 192 163 Q 192 202 192 242 C 192 261 192 261 169 267 Z","M 347 278 Q 402 290 419 284 Q 433 278 435 258 Q 439 207 429 114 Q 431 98 416 98 Q 396 98 386 100 Q 380 100 377 98 Q 369 94 398 69 Q 419 47 431 22 Q 441 15 451 20 Q 455 22 459 25 Q 480 58 482 158 Q 476 267 486 283 Q 494 292 488 302 Q 484 307 472 314 Q 437 333 419 323 Q 409 319 388 316 Q 271 292 169 267 C 140 260 164 232 192 242 Q 208 248 231 254 L 248 257 Q 272 265 323 274 L 347 278 Z","M 244 188 Q 235 186 214 178 Q 201 174 217 164 Q 226 158 246 163 L 274 170 L 332 184 L 367 190 Q 376 194 396 198 Q 402 199 406 202 Q 415 209 397 218 Q 379 227 368 223 L 332 216 Q 327 216 274 196 L 244 188 Z","M 231 254 Q 231 251 233 246 Q 241 216 244 188 L 246 163 Q 252 102 256 93 Q 265 78 270 87 Q 274 97 274 170 L 274 196 Q 274 208 275 218 Q 276 239 274 243 Q 271 250 248 257 C 229 263 229 263 231 254 Z","M 332 184 Q 332 80 342 71 Q 343 71 347 71 Q 351 73 354 81 Q 360 98 367 190 L 368 223 Q 368 229 372 243 Q 376 259 368 267 Q 355 276 347 278 C 319 288 317 289 323 274 Q 331 257 332 216 L 332 184 Z","M 698 651 L 862 688 Q 908 700 914 708 Q 923 714 918 723 Q 913 729 903 734 Q 864 751 825 733 Q 808 727 794 723 Q 696 689 582 674 Q 548 668 572 653 Q 606 634 659 641 L 698 651 Z","M 657 540 Q 668 553 678 566 Q 699 594 712 608 Q 734 624 698 651 C 674 669 660 671 659 641 Q 659 617 641 563 Q 631 549 630 535 C 626 505 638 517 657 540 Z","M 596 531 Q 594 533 591 535 Q 571 549 557 543 Q 555 541 555 536 Q 555 525 561 515 Q 582 472 572 295 Q 566 269 566 245 Q 566 198 586 180 Q 595 171 604 180 Q 611 192 612 201 L 613 229 Q 615 239 615 265 Q 613 294 613 308 L 613 331 L 613 406 L 613 431 Q 613 455 615 506 C 615 517 615 517 596 531 Z","M 809 223 Q 809 216 817 201 Q 831 173 841 174 Q 855 176 869 209 Q 874 219 876 225 Q 882 242 878 272 Q 868 363 866 451 Q 866 463 868 472 Q 871 489 885 517 Q 892 533 886 539 Q 861 561 817 579 Q 802 583 780 576 Q 774 574 657 540 L 630 535 Q 608 533 596 531 C 566 527 585 503 615 506 Q 627 506 642 510 Q 710 527 768 538 Q 789 542 795 531 Q 809 514 811 453 Q 817 290 811 249 L 809 223 Z","M 613 406 L 618 406 Q 686 417 747 423 Q 768 428 759 440 Q 749 452 727 456 Q 710 459 613 431 C 584 423 583 406 613 406 Z","M 613 308 L 623 308 Q 708 317 761 319 Q 783 323 774 334 Q 762 357 718 354 Q 690 352 613 331 C 584 323 583 308 613 308 Z","M 612 201 L 621 201 L 809 223 C 839 226 835 232 811 249 Q 800 259 777 258 Q 753 257 613 229 C 584 223 582 201 612 201 Z","M 623 161 Q 608 95 455 -29 Q 447 -35 447 -37 Q 446 -41 460 -39 Q 480 -37 504 -27 Q 562 -4 640 76 Q 686 123 690 126 Q 695 131 697 137 Q 704 157 671 167 Q 652 174 641 174 Q 627 174 623 161 Z","M 757 157 Q 757 153 757 151 Q 758 140 774 118 Q 829 47 864 -7 Q 877 -29 892 -36 Q 898 -37 903 -33 Q 923 -23 917 25 Q 914 77 768 163 Q 761 169 757 157 Z"],"medians":[[[300,809],[331,777],[299,694],[261,628],[204,549],[134,471],[36,394]],[[343,709],[352,714],[418,680],[449,658],[470,627]],[[247,544],[296,542],[396,571],[430,571]],[[119,429],[133,427],[170,326]],[[157,429],[195,452],[229,454],[238,443],[232,399],[223,394]],[[180,349],[185,359],[223,372],[254,372]],[[261,467],[274,459],[282,444],[303,366]],[[294,468],[302,463],[319,466],[353,482],[378,469],[365,424],[346,415]],[[312,376],[342,394],[386,397]],[[399,500],[418,476],[436,400]],[[428,499],[438,494],[495,510],[511,504],[517,496],[513,469],[509,453],[497,445]],[[449,411],[455,420],[472,424],[512,429],[528,425]],[[140,265],[165,240],[171,216],[174,151],[162,84],[168,40]],[[176,266],[196,259],[400,303],[434,303],[452,293],[458,266],[459,170],[452,94],[438,71],[383,94]],[[226,173],[325,199],[399,208]],[[236,251],[256,233],[264,91]],[[330,271],[351,252],[345,75]],[[574,665],[607,659],[648,661],[853,714],[907,718]],[[667,636],[685,620],[651,553],[636,541]],[[563,537],[592,497],[590,223],[595,187]],[[601,525],[632,521],[792,558],[815,551],[843,521],[837,466],[846,276],[841,185]],[[616,412],[628,424],[686,434],[729,439],[750,433]],[[618,314],[628,322],[706,335],[741,336],[766,328]],[[617,208],[629,219],[775,239],[792,239],[800,230]],[[680,143],[651,136],[615,87],[554,27],[503,-10],[454,-36]],[[766,154],[877,36],[893,-2],[894,-22]]]}
const colorSaturation = 100 // 0-255
const colorLightness = 40 // 0-255
const fontSize = 60
const fontBorderWidth = 6

function hslToRgb(h, s, l) { // To generate rainbow colors
    h /= 360
    s /= 100
    l /= 100
    let r, g, b
    if (s === 0) {
        r = g = b = l // achromatic
    } else {
        const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1
            if (t > 1) t -= 1
            if (t < 1 / 6) return p + (q - p) * 6 * t
            if (t < 1 / 2) return q
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6
            return p
        }
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s
        const p = 2 * l - q
        r = hue2rgb(p, q, h + 1 / 3)
        g = hue2rgb(p, q, h)
        b = hue2rgb(p, q, h - 1 / 3)
    }
    const toHex = x => {
        const hex = Math.round(x * 255).toString(16)
        return hex.length === 1 ? '0' + hex : hex
    }
    return `#${toHex(r)}${toHex(g)}${toHex(b)}`
}

    const item = strokeData
    const charCode = item.character.charCodeAt()
    const startingPoints = item.medians.map(i=>({x:i[0][0],y:i[0][1]}))
    const endingPoints = item.medians.map(i=>({x:i[i.length - 1][0],y:i[i.length - 1][1]}))

    let pathes = ``
    let strokeStartPositions = []
    for (const [i,stroke] of item.strokes.entries()) {
        const strokeColor = hslToRgb(360/item.strokes.length*(i+1), colorSaturation, colorLightness)
        pathes += `    <path d="${stroke}" fill="${strokeColor}"/>\n`        
        const x = startingPoints[i].x
        const y = 900+fontSize/2-startingPoints[i].y
        strokeStartPositions[i] = [x, y]
    }
    
    function repositionAlgorithm(position, finalPosition, strokeStartPositions, currentPositionIndex) {
        var strokeDirection = getStrokeDirection({x: position[0], y: position[1]}, finalPosition); 
	
		var boxWidth = fontSize;
		if (currentPositionIndex.toString().length == 2)
			boxWidth = boxWidth * 1.5; //if number has 2 digits
		else if (currentPositionIndex.toString().length == 3)
			boxWidth = boxWidth * 2; //if number has 3 digits
		
        var currentPositionRectangle = { 
          	x1: position[0],
          	x2: position[0] + boxWidth,
            	y1: position[1],
          	y2: position[1] + fontSize
          };
        //make sure position doesn't overlap with bounding box of other PAST positions
        for(var i = 0; i < currentPositionIndex; i++) {
        	var otherPositionRectangle = { 
          	x1: strokeStartPositions[i][0],
          	x2: strokeStartPositions[i][0] + boxWidth,
            	y1: strokeStartPositions[i][1],
          	y2: strokeStartPositions[i][1] + fontSize
          };
          
          var maxLoop = 5000; // prevent endless loop
          while (currentPositionRectangle.x1 < otherPositionRectangle.x2
          	&& currentPositionRectangle.x2 > otherPositionRectangle.x1
            && currentPositionRectangle.y2 > otherPositionRectangle.y1 
            && currentPositionRectangle.y1 < otherPositionRectangle.y2
            && maxLoop-- > 0) { //Thanks to: https://stackoverflow.com/questions/306316/determine-if-two-rectangles-overlap-each-other
			  //while overlapping, move the text position according to the stroke direction
              switch(strokeDirection) {
              	case "ne":
                	currentPositionRectangle.x1 += 2;
                  	currentPositionRectangle.x2 += 2;
					currentPositionRectangle.y1--;
                  	currentPositionRectangle.y2--;
					break;
				case "nw":
                	currentPositionRectangle.x1 -= 2;
                  	currentPositionRectangle.x2 -= 2;
					currentPositionRectangle.y1--;
                  	currentPositionRectangle.y2--;
					break;
				case "se":
                	currentPositionRectangle.x1 += 2;
                  	currentPositionRectangle.x2 += 2;
					currentPositionRectangle.y1++;
                  	currentPositionRectangle.y2++;
					break;
				case "sw":
                	currentPositionRectangle.x1 -= 2;
                  	currentPositionRectangle.x2 -= 2;
					currentPositionRectangle.y1++;
                  	currentPositionRectangle.y2++;
					break;
				case "n":
					currentPositionRectangle.y1--;
                  	currentPositionRectangle.y2--;
					break;
				case "s":
					currentPositionRectangle.y1++;
                  	currentPositionRectangle.y2++;
					break;
				case "w":
                	currentPositionRectangle.x1--;
                  	currentPositionRectangle.x2--;
					break;
				case "e":
                	currentPositionRectangle.x1++;
                  	currentPositionRectangle.x2++;
					break;
                default:
                	throw new Error("Unknown strokeDirection");
              }
            }          
        }
        
		position[0] = currentPositionRectangle.x1;
		position[1] = currentPositionRectangle.y1;
        return position
    }
	
	function getStrokeDirection(p1, p2) {
		//thanks to: https://stackoverflow.com/questions/35104991/relative-cardinal-direction-of-two-coordinates
		//Y increases towards the bottom in SVG so we replace p2.Y with p1.Y
		var angle = Math.atan2(p1.y - p2.y, p2.x - p1.x);
		angle += Math.PI;
		angle /= Math.PI / 4;
		var halfQuarter = parseInt(angle);
		halfQuarter %= 8;
		//we also need to switch North with South because Y increases towards the south
		switch (halfQuarter) {
			case 0:
				return "s";
				break;
			case 4:
				return "n";
				break;
			case 6:
				return "e";
				break;
			case 2:
				return "w";
				break;
			case 7:
				return "se";
				break;
			case 1:
				return "sw";
				break;
			case 5:
				return "ne";
				break;
			case 3:
				return "nw";
				break;
			default:
				return "unknown";
				break;
		}
	}
    
    let numberPositions = []
    for (const [i,currentPosition] of strokeStartPositions.entries()) {
        const oldX = currentPosition[0]
        const oldY = currentPosition[1]
	var finalPosition = endingPoints[i];
        const repulsedFrom = strokeStartPositions.slice().splice(i,1) // all positions but the current one
        const attractedTo = [currentPosition]
        const newPosition = repositionAlgorithm(currentPosition, finalPosition, strokeStartPositions, i)
        numberPositions[i] = newPosition
        const newX = newPosition[0]
        const newY = newPosition[1]
        console.log(`Moved from (${oldX}|${oldY}) to (${newX}|${newY}).`)
    }
    
    let texts = ``
    for (const [i,pos] of numberPositions.entries()) {
        const textColor = "#FFFFFF" //const textColor = hslToRgb(360/pathEls.length*(i+1), colorSaturation, colorLightness)
        const x = pos[0]
        const y = pos[1]
        texts += `    <text fill="${textColor}" x="${x}" y="${y}">${i+1}</text>\n`
    }

    const newSvgContent = `<svg version="1.1" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
  <style type="text/css">
    text {
      font-family: Helvetica;
      font-size: ${fontSize}px;
      paint-order: stroke;
      stroke: #000000;
      stroke-width: 6px;
      stroke-linecap: butt;
      stroke-linejoin: miter;
      font-weight: 800;
    }
  </style>
  <g stroke="lightgray" stroke-dasharray="1,1" stroke-width="1" transform="scale(4, 4)">
    <line x1="0" y1="0" x2="256" y2="256"/>
    <line x1="256" y1="0" x2="0" y2="256"/>
    <line x1="128" y1="0" x2="128" y2="256"/>
    <line x1="0" y1="128" x2="256" y2="128"/>
  </g>
  <g transform="scale(1, -1) translate(0, -900)">
${pathes}  </g>
  <g>
${texts}  </g>
</svg>`
    
    const svgEl = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    svgEl.innerHTML = newSvgContent
    document.body.appendChild(svgEl)
    //fs.outputFile(`../svgs-still/${charCode}-still.svg`, newSvgContent).then(() => {
    //    //console.log(`../svgs-still/${charCode}-still.svg written`) // logging severely reduces performance
    //}).catch(e=>console.error(new Error(e)))
//})
html, body {
  width: 100%;
  height: 100%;
}
svg {
  width: 100%;
  height: 100%;
}
like image 3
Sal Avatar answered Oct 22 '22 04:10

Sal