├── .gitignore ├── main.wasm ├── assets ├── app.js └── wasm_exec.js ├── README.md ├── LICENSE ├── image ├── visualization.go └── draw.go ├── digits ├── parser_test.go └── parser.go ├── main.go ├── index.html └── resources ├── e.txt ├── phi.txt └── pi.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | goexec 3 | -------------------------------------------------------------------------------- /main.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hugolgst/digart/HEAD/main.wasm -------------------------------------------------------------------------------- /assets/app.js: -------------------------------------------------------------------------------- 1 | // Delete the notification 2 | document.getElementById("notification-delete").onclick = function() { 3 | document.getElementById("notification-modal").className = "modal" 4 | } 5 | 6 | // Read the content of a file and put it into the number field 7 | document.getElementById("read").onclick = function() { 8 | const input = document.querySelector("input[type=file]") 9 | if (input.files.length === 0) { 10 | console.log('No file selected.') 11 | return 12 | } 13 | 14 | const reader = new FileReader(); 15 | reader.onload = function fileReadCompleted() { 16 | let content = reader.result.substring( 17 | 0, 18 | parseInt(document.getElementById("digits").value) 19 | ) 20 | 21 | document.getElementById("number").value = content 22 | }; 23 | reader.readAsText(input.files[0]); 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
4 | 15 | Introduction — 16 | License 17 |
18 | 19 | ## Introduction 20 | In the image below, each dot represents a decimal which is in a segment that represents the previous decimal, its position is its position in π. 21 | 22 | The website is using JS and WebAssembly 23 | 24 | ## License 25 | [MIT](https://github.com/hugolgst/digart/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-Present Hugo Lageneste 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /image/visualization.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "math" 8 | ) 9 | 10 | // DrawData draws in the given image all the points for the parsed decimals 11 | func DrawData(img image.RGBA, parsedDigits [][][]int, radius, image int) { 12 | fmt.Println("Drawing the data") 13 | 14 | // Arbitrary number of iterations 15 | iterations := 80 16 | 17 | // Iterate through segments 18 | for segmentIndex, segment := range parsedDigits { 19 | // Browse all the lines 20 | for line := 0; line < len(segment); line++ { 21 | // Calculate the radius for the actual line 22 | r := float64(radius*5/4 + line * radius/15) 23 | 24 | t := math.Pi/float64(iterations) 25 | 26 | // Draw the points 27 | for i := 0; i < int(iterations*2); i++ { 28 | // Calculate the coordinates 29 | x1 := r*math.Cos(t)+float64(image/2) 30 | y1 := r*math.Sin(t)+float64(image/2) 31 | 32 | t += math.Pi/float64(iterations) 33 | size := iterations/5 34 | 35 | // Only iterate through the wanted part of the circle 36 | if i < segmentIndex*size || i >= size*(segmentIndex+1) + len(segment[line]) - size { 37 | continue 38 | } 39 | 40 | // Choose the correct color 41 | colorIndex := segment[line][i%size] 42 | var c color.RGBA 43 | if colorIndex == -1 { 44 | c = color.RGBA{A: 255} 45 | } else { 46 | c = colors[colorIndex] 47 | } 48 | 49 | // Then draw the point 50 | DrawPoint(img, int(x1), int(y1), 6, c) 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /digits/parser_test.go: -------------------------------------------------------------------------------- 1 | package digits 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestParseDigits(t *testing.T) { 9 | number := "3141592653589" 10 | digits := ParseDigits(number) 11 | excepted := [][][]int{ 12 | nil, 13 | {{-1, 4, -1, 5, -1, -1, -1, -1, -1, -1}}, 14 | {{-1, -1, -1, -1, -1, -1, 6, -1, -1, -1}}, 15 | {{1, -1, -1, -1, -1, -1, -1, -1, -1, 5}}, 16 | {{-1, -1, 1, -1, -1, -1, -1, -1, -1, -1}}, 17 | {{8, -1, -1, -1, 9, -1, -1, -1, 3, -1}}, 18 | {{-1, -1, -1, -1, -1, -1, -1, 5, -1, -1}}, 19 | nil, 20 | {{-1, 9, -1, -1, -1, -1, -1, -1, -1, -1}}, 21 | {{-1, -1, -1, -1, -1, 2, -1, -1, -1, -1}}, 22 | } 23 | 24 | if !reflect.DeepEqual(digits, excepted) { 25 | t.Errorf("ParseDigits failed, excepted %d got %d.", excepted, digits) 26 | } 27 | } 28 | 29 | func TestGenerateSegment(t *testing.T) { 30 | var segments [][]int 31 | position, value := 2, 5 32 | 33 | segments = GenerateSegment(segments, position, value) 34 | excepted := [][]int{{-1, -1, 5, -1, -1, -1, -1, -1, -1, -1}} 35 | 36 | if !reflect.DeepEqual(segments, excepted) { 37 | t.Errorf("GenerateSegment failed, excepted %d got %d.", excepted, segments) 38 | } 39 | 40 | position, value = 2, 8 41 | segments = GenerateSegment(segments, position, value) 42 | excepted = append(excepted, []int{-1, -1, 8, -1, -1, -1, -1, -1, -1, -1}) 43 | 44 | if !reflect.DeepEqual(segments, excepted) { 45 | t.Errorf("GenerateSegment failed, excepted %d got %d.", excepted, segments) 46 | } 47 | } 48 | 49 | func TestMakeIntArray(t *testing.T) { 50 | array := MakeIntArray(10) 51 | excepted := []int{-1, -1, -1, -1, -1, -1, -1, -1, -1, -1} 52 | 53 | if !reflect.DeepEqual(array, excepted) { 54 | t.Errorf("MakeIntArray failed, excepted %d got %d.", excepted, array) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /image/draw.go: -------------------------------------------------------------------------------- 1 | package image 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "math" 8 | ) 9 | 10 | var colors = []color.RGBA{ 11 | {18, 67, 89, 255}, 12 | {58, 165, 229, 255}, 13 | {78, 118, 33, 255}, 14 | {77, 206, 105, 255}, 15 | {104, 6, 9, 255}, 16 | {229, 22, 37, 255}, 17 | {147, 82, 17, 255}, 18 | {225, 205, 31, 255}, 19 | {63, 32, 77, 255}, 20 | {139, 85, 193, 255}, 21 | } 22 | 23 | // DrawPoint draws a point at (`x`; `y`) in the given image with the given color 24 | func DrawPoint(img image.RGBA, x, y, width int, color color.RGBA) { 25 | // Draw multiples layers for the width 26 | for w := 0; w < width; w++ { 27 | r := float64(1+w) 28 | iterations := 2000 29 | t := math.Pi/float64(iterations) 30 | 31 | // Draw the point 32 | for i := 0; i < int(iterations*2); i++ { 33 | // Calculate the coordinates 34 | x1 := r*math.Cos(t)+float64(x) 35 | y1 := r*math.Sin(t)+float64(y) 36 | 37 | // Draw the point 38 | img.Set(int(x1), int(y1), color) 39 | 40 | t += math.Pi/float64(iterations) 41 | } 42 | } 43 | } 44 | 45 | // DrawCircle draws in the given image the middle circle of the visualization with 46 | // the 10 segments in color. 47 | func DrawCircle(img image.RGBA, radius, width, image int) { 48 | fmt.Println("Drawing main circle") 49 | 50 | // Draw multiples layers for the width 51 | for w := 0; w < width; w++ { 52 | iterations := 2000 53 | 54 | // Draw a circle with a parametric equation 55 | t := math.Pi / float64(iterations) 56 | for i := 0; i < iterations*2; i++ { 57 | // Choose a color for each part 58 | var color color.RGBA 59 | for n := range colors { 60 | // Calculate the part of the circle `t` is in 61 | part := t/(math.Pi/5) 62 | color = colors[int(part)%(n+1)] 63 | } 64 | 65 | // Calculate the coordinates for `t` in the center of the image 66 | x := float64(radius + w) * math.Cos(t) + float64(image/2) 67 | y := float64(radius + w) * math.Sin(t) + float64(image/2) 68 | // Draw the point 69 | img.Set(int(x), int(y), color) 70 | 71 | t += math.Pi/float64(iterations) 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /digits/parser.go: -------------------------------------------------------------------------------- 1 | package digits 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | const bubblesBySegment = 15 10 | 11 | // ParseDigits returns a 3D array of integers which represents the segments of the first circle 12 | // then the different lines of a segment and finally the points which represents the decimals. 13 | func ParseDigits(number string) [][][]int { 14 | fmt.Printf("Parsing the digits\n") 15 | 16 | number = strings.Replace(number, ".", "", -1) 17 | 18 | // Generate the 3D array 19 | var digits = make([][][]int, 10) 20 | 21 | // Iterate all the digits of the given number 22 | for digitIndex := 0; digitIndex < len(number)-1; digitIndex++ { 23 | previous, _ := strconv.Atoi(string(number[digitIndex])) 24 | current, _ := strconv.Atoi(string(number[digitIndex+1])) 25 | segments := digits[previous] 26 | 27 | // Create a new segment if no one has been found 28 | if len(segments) == 0 { 29 | segments = append(segments, MakeIntArray(bubblesBySegment)) 30 | } 31 | 32 | // Set the position of the digit in the given number for an array of 10 numbers 33 | position := digitIndex % bubblesBySegment 34 | 35 | // Then generate the segment with the current value and its position 36 | digits[previous] = GenerateSegment(segments, position, current) 37 | } 38 | 39 | return digits 40 | } 41 | 42 | // GenerateSegment returns a segment where the value has been filled 43 | func GenerateSegment(segments [][]int, position, value int) [][]int { 44 | filled := false 45 | // Iterate the segments to find an available position 46 | for i := range segments { 47 | // If the position is free then fill it with the current digit 48 | if segments[i][position] == -1 { 49 | segments[i][position] = value 50 | filled = true 51 | break 52 | } 53 | } 54 | 55 | // If a position has not been found in the existent segments' lines then create one 56 | // and fill it with the current digit 57 | if !filled { 58 | segments = append(segments, MakeIntArray(bubblesBySegment)) 59 | segments[len(segments)-1][position] = value 60 | } 61 | 62 | return segments 63 | } 64 | 65 | // MakeIntArray returns a `size`-elements-array which elements are -1 66 | func MakeIntArray(size int) (array []int) { 67 | array = make([]int, size) 68 | for i := range array { 69 | array[i] = -1 70 | } 71 | 72 | return 73 | } -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "./digits" 5 | digartImage "./image" 6 | "bytes" 7 | "encoding/base64" 8 | "fmt" 9 | "image" 10 | "image/color" 11 | "image/draw" 12 | "image/png" 13 | "syscall/js" 14 | ) 15 | 16 | // CreateImage returns the created image of `width` and `height` dimensions. 17 | func CreateImage(width int, height int, background color.RGBA) *image.RGBA { 18 | rect := image.Rect(0, 0, width, height) 19 | img := image.NewRGBA(rect) 20 | draw.Draw(img, img.Bounds(), &image.Uniform{C: background}, image.ZP, draw.Src) 21 | 22 | return img 23 | } 24 | 25 | func BuildImage(number string) string { 26 | // Read and parse the number 27 | parsedDigits := digits.ParseDigits(number) 28 | 29 | out := bytes.NewBuffer(nil) 30 | 31 | dim := int(float64(len(number))/1.5) 32 | background := color.RGBA{A: 255} 33 | img := CreateImage(dim, dim, background) 34 | 35 | // Draw the elements 36 | digartImage.DrawCircle(*img, 350, 40, dim) 37 | digartImage.DrawData(*img, parsedDigits, 350, dim) 38 | 39 | if err := png.Encode(out, img); err != nil { 40 | panic(err) 41 | } 42 | 43 | b64 := base64.StdEncoding.EncodeToString(out.Bytes()) 44 | return fmt.Sprint(`data:image/png;base64,`, b64) 45 | } 46 | 47 | func main() { 48 | var number string 49 | 50 | c := make(chan struct{}, 0) 51 | 52 | runAction := js.FuncOf(func(this js.Value, args []js.Value) interface{} { 53 | numberInput := js.Global().Get("document"). 54 | Call("getElementById", "number"). 55 | Get("value") 56 | 57 | number = numberInput.String() 58 | 59 | if len(number) < 1500 { 60 | js.Global().Get("document"). 61 | Call("getElementById", "notification-message"). 62 | Set("innerHTML", "The number must contain at least 1500 digits, please.") 63 | 64 | js.Global().Get("document"). 65 | Call("getElementById", "notification-modal"). 66 | Set("className", "modal is-active") 67 | return nil 68 | } 69 | 70 | imageSrc := BuildImage(number) 71 | parameters := map[string]string{ 72 | "src": imageSrc, 73 | "width": "500", 74 | "height": "500", 75 | } 76 | 77 | for id, value := range parameters { 78 | js.Global().Get("document"). 79 | Call("getElementById", "image"). 80 | Set(id, value) 81 | } 82 | 83 | return nil 84 | }) 85 | 86 | js.Global().Get("document"). 87 | Call("getElementById", "submit"). 88 | Call("addEventListener", "click", runAction) 89 | 90 | <-c 91 | } 92 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |or
67 |103 | 104 | Read 105 | 106 |
107 | 108 |109 | 110 | Generate 111 | 112 |
113 |The image might take a little time to build up.
121 |