├── .gitignore ├── others ├── occ_aba.png └── palindromes.png ├── .npmignore ├── _config.yml ├── src ├── strlib.test.ts ├── strlib.ts ├── vis_str.ts └── vis_str_demo.ts ├── jest.config.js ├── tsconfig.json ├── .github └── workflows │ ├── build.yml │ └── gh-page.yml ├── LICENSE ├── package.json ├── dist ├── vis_str_demo_occ.html └── vis_str_demo.html ├── webpack.config.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | docs 4 | dist/*.js -------------------------------------------------------------------------------- /others/occ_aba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kg86/visstr/HEAD/others/occ_aba.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | **/tsconfig.json 3 | **/webpack.config.js 4 | node_modules 5 | src 6 | -------------------------------------------------------------------------------- /others/palindromes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kg86/visstr/HEAD/others/palindromes.png -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | plugins: 3 | - jekyll-analytics 4 | jekyll_analytics: 5 | GoogleAnalytics: # Add, if you want to track with Google Analytics 6 | id: UA-1745272-7 # Required - replace with your tracking id -------------------------------------------------------------------------------- /src/strlib.test.ts: -------------------------------------------------------------------------------- 1 | import * as strlib from "./strlib"; 2 | 3 | test("enum palindromes", () => { 4 | expect(strlib.enumPalindromes("aba")).toStrictEqual([ 5 | [0, 0], 6 | [1, 1], 7 | [2, 2], 8 | [0, 2], 9 | ]); 10 | }); 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ["/src"], 3 | testMatch: [ 4 | "**/__tests__/**/*.+(ts|tsx|js)", 5 | "**/?(*.)+(spec|test).+(ts|tsx|js)", 6 | ], 7 | transform: { 8 | "^.+\\.(ts|tsx)$": "ts-jest", 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strict": true, 5 | "skipLibCheck": true, 6 | "declaration": true, 7 | "target": "es6", 8 | "module": "CommonJS", 9 | "lib": ["es6", "dom"], 10 | "outDir": "lib" 11 | }, 12 | "files": ["src/vis_str.ts", "src/vis_str_demo.ts"] 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | push: 4 | branches: 5 | - '**' 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | 11 | - uses: actions/checkout@v1 12 | 13 | - name: npm install, build, and test 14 | run: | 15 | npm install 16 | npm run build --if-present 17 | npm run test 18 | npx typedoc src/*.ts 19 | 20 | -------------------------------------------------------------------------------- /src/strlib.ts: -------------------------------------------------------------------------------- 1 | import { Range, RangeSimple, RangeLine, VisStr } from "./vis_str"; 2 | export const isPalindrome = (str: string): boolean => { 3 | for (let i = 0; i < str.length / 2; i++) { 4 | if (str[i] != str[str.length - i - 1]) return false; 5 | } 6 | return true; 7 | }; 8 | 9 | export const enumPalindromes = (str: string): RangeLine[] => { 10 | // export const enumPalindromes = (str: string): RangeSimple[] => { 11 | const n = str.length; 12 | // let res: RangeSimple[] = []; 13 | let res: RangeLine[] = []; 14 | for (let len = 1; len <= n; len++) { 15 | for (let beg = 0; beg + len <= n; beg++) { 16 | if (isPalindrome(str.substring(beg, beg + len))) 17 | res.push([beg, beg + len - 1]); 18 | } 19 | } 20 | return res; 21 | }; 22 | -------------------------------------------------------------------------------- /.github/workflows/gh-page.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | permissions: 8 | contents: write 9 | jobs: 10 | build-and-deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | 14 | - uses: actions/checkout@v4 15 | 16 | - name: npm install, build, and test 17 | run: | 18 | npm install 19 | npm run build --if-present 20 | npm run test 21 | npx typedoc src/vis_str.ts 22 | 23 | - name: move files 24 | run: | 25 | mkdir public 26 | mv dist public/ 27 | mv docs public/ 28 | mv lib public/ 29 | mv others public/ 30 | mv _config.yml public/ 31 | mv README.md public/ 32 | 33 | - name: Deploy 34 | uses: peaceiris/actions-gh-pages@v4 35 | with: 36 | github_token: ${{ secrets.GITHUB_TOKEN }} 37 | publish_branch: gh-pages 38 | publish_dir: ./public 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 kg86 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "visstr", 3 | "version": "0.0.4", 4 | "description": "String Visualizer", 5 | "repository": "github:kg86/visstr", 6 | "homepage": "https://github.com/kg86/visstr", 7 | "license": "MIT", 8 | "author": "kg86", 9 | "main": "lib/vis_str.js", 10 | "keywords": [ 11 | "visualization", 12 | "string", 13 | "stringology" 14 | ], 15 | "files": [ 16 | "LICENSE", 17 | "dist", 18 | "lib" 19 | ], 20 | "dependencies": { 21 | "color-convert": "^2.0.1", 22 | "webpack": "^5.45.1", 23 | "webpack-cli": "^4.7.2" 24 | }, 25 | "devDependencies": { 26 | "@types/color-convert": "^2.0.0", 27 | "@types/jest": "^29.2.6", 28 | "jest": "^29.3.1", 29 | "prettier": "2.3.2", 30 | "ts-jest": "^29.0.5", 31 | "ts-loader": "^9.2.3", 32 | "typedoc": "^0.21.4", 33 | "typescript": "^4.3.5", 34 | "webpack-dev-server": "^4.11.0" 35 | }, 36 | "scripts": { 37 | "build": "tsc && webpack", 38 | "prepublishOnly": "npm run build && webpack", 39 | "test": "jest", 40 | "watch": "tsc && webpack --watch", 41 | "start": "npx webpack-dev-server" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /dist/vis_str_demo_occ.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Occurrences of "aba" 6 | 7 | 8 |

Occurrences of "aba".

9 | 15 | 16 | 17 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const configDemo = { 4 | mode: 'development', 5 | // devtool: 'source-map', 6 | // devtool: 'cheap-eval-source-map', 7 | // devtool: 'cheap-module-eval-source-map', 8 | devtool: 'inline-source-map', 9 | entry: { 10 | vis_str_demo: './src/vis_str_demo.ts', 11 | }, 12 | output: { 13 | filename: '[name].js', 14 | path: path.resolve(__dirname, 'dist'), 15 | }, 16 | resolve: { 17 | modules: ['node_modules', path.resolve(__dirname, 'src')], 18 | extensions: ['.ts', '.js'], 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.ts$/, 24 | use: 'ts-loader', 25 | }, 26 | ], 27 | }, 28 | devServer: { 29 | 30 | static: { 31 | directory: path.join(__dirname, 'dist'), 32 | }, 33 | compress: true, 34 | port: 9000, 35 | } 36 | } 37 | const configLibrary = { 38 | mode: 'development', 39 | // devtool: 'source-map', 40 | // devtool: 'cheap-eval-source-map', 41 | // devtool: 'cheap-module-eval-source-map', 42 | devtool: 'inline-source-map', 43 | entry: { 44 | vis_str: './src/vis_str.ts', 45 | }, 46 | output: { 47 | filename: '[name].umd.js', 48 | path: path.resolve(__dirname, 'lib'), 49 | library: 'visstr', 50 | libraryTarget: 'umd', 51 | umdNamedDefine: true, 52 | }, 53 | resolve: { 54 | modules: ['node_modules', 'color-convert', path.resolve(__dirname, 'src')], 55 | extensions: ['.ts', '.js'], 56 | }, 57 | module: { 58 | rules: [ 59 | { 60 | test: /\.ts$/, 61 | use: 'ts-loader', 62 | }, 63 | ], 64 | }, 65 | } 66 | 67 | module.exports = [configDemo, configLibrary] 68 | // module.exports = [configLibrary] 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/visstr.svg?sanitize=true)](https://www.npmjs.com/package/visstr) 2 | 3 | # VisStr 4 | 5 | VisStr is a library to visualize a string and its properties such as repetitions, occurrences, and redundancies. 6 | 7 | The following image visualizes all palindromes that occurs as substrings in "mississippi$". 8 | 9 | ![](others/palindromes.png) 10 | 11 | See [demos](https://kg86.github.io/visstr/dist/vis_str_demo.html) and [API documents](https://kg86.github.io/visstr/docs/index.html). 12 | 13 | ## Compile 14 | 15 | Run the following commands. 16 | 17 | ```bash 18 | $ git clone https://github.com/kg86/visstr.git 19 | $ cd visstr 20 | $ npm install 21 | $ npm run build 22 | ``` 23 | 24 | Libraries are output in `./lib` (UMD/CommonJS) and demo assets in `./dist`. 25 | 26 | ## Examples: Visualizing the substring occurrences 27 | 28 | This is an example to visualize all the occurrences of `aba` in `abaababaabaab` using VisStr. 29 | 30 | 1. Add `` element to the HTML which is a core of the visualizer. 31 | 32 | ```html 33 | 34 | 35 | ``` 36 | 37 | 2. Load the VisStr library `../lib/vis_str.umd.js`. 38 | Note that we have to use umd version for browser. 39 | 40 | ```html 41 | 42 | ``` 43 | 44 | 3. Create a string and a range list representing the occurrences of `aba`, and specify the style of the range list (e.g., arrow, line, curve) and the color. 45 | 46 | ```javascript 47 | const { VisStr } = window.visstr; 48 | const canvas = document.querySelector("#canvas"); 49 | // create visstr object. 50 | const vstr = new VisStr(canvas); 51 | // input string 52 | const s = "abaababaabaab"; 53 | 54 | // create occurrences of aba. 55 | const occAba = [ 56 | [0, 2], 57 | [3, 5], 58 | [5, 7], 59 | [8, 10], 60 | ]; 61 | // add line style and color. 62 | const ranges = vstr.makeRanges(occAba, "arrow", "#ff0000"); 63 | // make group so that they are not overlap with each others. 64 | const grouped = vstr.nonOverlapRanges(ranges); 65 | vstr.draw(s, grouped); 66 | ``` 67 | 68 | The result is visualized as follows: 69 | 70 | ![](others/occ_aba.png) 71 | 72 | You can find the [source code](dist/vis_str_demo_occ.html), and the [demo page](https://kg86.github.io/visstr/dist/vis_str_demo_occ.html). 73 | -------------------------------------------------------------------------------- /dist/vis_str_demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 | 16 | String Visualizer 17 | 18 | 19 | 20 |

String Visualizer

21 | visualize several characteristics of strings. 22 | 23 | 24 | 25 | 26 | 34 | 35 | 36 | 37 | 47 | 48 | 49 | 50 | 60 | 61 | 62 | 63 | 74 | 75 | 76 | 77 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 |
Input String: 27 | 33 |
Font Size: 38 | 16px 39 | 32px 40 | 48px 64px 46 |
Line Style Left (or Both): 51 | line 52 | curve 53 | arrow 59 |
Line Style Right: 64 | line 65 | curve 66 | arrow 67 | none 73 |
Visuzlize: 78 | palindromes runs 84 |
85 | squares 86 | rmostsquares 91 | lmostsquares 96 |
97 | left_maximal 102 | right_maximal 107 | maximal_repeat 112 |
113 | lpf 114 | lz77 115 | lz78 116 |
117 | lyndon_factorization 122 | lyndon_array 127 | lyndon words 133 |
134 | prev smaller suffixex 139 | next smaller suffixex 144 |
Effective Alphabet
Rank Array
155 | 156 | 157 | 158 |
159 | See also the visstr repository. 160 |
161 | We want new visualizations of strings. Call for PRs! 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/vis_str.ts: -------------------------------------------------------------------------------- 1 | import * as convert from 'color-convert' 2 | 3 | /** The simple range representation for strings */ 4 | export type RangeStr = [number, number, string[]] 5 | /** The simple range representation for line */ 6 | export type RangeLine = [number, number, number?] 7 | /** The simple range representation */ 8 | export type RangeSimple = RangeStr | RangeLine 9 | 10 | export interface Range { 11 | /** The style to draw range. It is either of ["line", "curve", "arrow", "str"]. If "str" is chosen, the optinal parameter `str` must be given. For other styles, you can set left style and right style lie "line,arrow". */ 12 | style: string 13 | /** The color to draw range, e.g. "#000000" for black. */ 14 | color: string 15 | /** The beginning index of the range. */ 16 | beg: number 17 | /** The ending index of the range. Note that indexes are inclusive. */ 18 | end: number 19 | /** The step of the range [`beg`, `end`]. For example, a range [`beg`, `end`, `step`] = [1, 8, 3] represents continuous ranges [[`beg`, `end`]]=[[1, 3], [4, 6], [7, 8]] */ 20 | step?: number 21 | /** The strings of the range. Its length must be equal to the length of the range `end` - `beg` + 1 */ 22 | str?: string[] 23 | } 24 | 25 | export interface RangePx { 26 | /** The style to draw range. It is either of ["line", "curve", "arrow", "str"]. If "str" is chosen, the optinal parameter `str` must be given. For other styles, you can set left style and right style lie "line,arrow". */ 27 | style: string 28 | /** The color to draw range, e.g. "#000000" for black. */ 29 | color: string 30 | /** The x-coordinate which begins the range. */ 31 | x_beg: number 32 | /** The x-coordinate which ends the range. */ 33 | x_end: number 34 | /** The y-coordinate of the range. */ 35 | y: number 36 | /** The strings of the range. Its length must be equal to the length of the range `end` - `beg` + 1 */ 37 | str?: string[] 38 | } 39 | 40 | export class VisStr { 41 | private canvas: HTMLCanvasElement 42 | private ctx: CanvasRenderingContext2D 43 | private str_x: number 44 | private str_y: number 45 | private font_size: number 46 | private font_size_half: number 47 | private font_type: string 48 | /** The offset to start drawing a range from a center position of an index. */ 49 | private range_beg_offset: number 50 | private range_end_offset: number 51 | 52 | /** 53 | * 54 | * @param canvas HTMLCanvasElement 55 | * @param font_size font size 56 | * @param font_type font name 57 | */ 58 | constructor( 59 | canvas: HTMLCanvasElement, 60 | font_size = 32, 61 | font_type = 'Courier', 62 | ) { 63 | this.canvas = canvas 64 | this.font_size = font_size 65 | this.font_size_half = this.font_size / 2 66 | this.font_type = font_type 67 | this.ctx = canvas.getContext('2d') as CanvasRenderingContext2D 68 | this.str_x = this.font_size 69 | this.str_y = this.font_size * 2 + this.font_size_half 70 | this.range_beg_offset = -this.font_size / 4 71 | this.range_end_offset = this.font_size / 4 72 | } 73 | 74 | /** Clear the canvas. */ 75 | clear() { 76 | this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height) 77 | } 78 | 79 | /** 80 | * Returns the x-coordinate which is a beginning of a range. 81 | * 82 | * @param idx index of a range 83 | * @return The x-coordinate of a range beginning at `idx` 84 | */ 85 | rangeBeg(idx: number): number { 86 | return this.str_x + this.font_size * idx + this.range_beg_offset 87 | } 88 | 89 | /** 90 | * Returns the x-coordinate which is a ending of a range. 91 | * 92 | * @param idx index of a range 93 | * @return The x-coordinate of a range ending at `idx` 94 | */ 95 | rangeEnd(idx: number): number { 96 | return this.str_x + this.font_size * idx + this.range_end_offset 97 | } 98 | 99 | /** 100 | * Return the height of a given range. 101 | * @param r A range. 102 | */ 103 | rangeHeight(r: Range): number { 104 | return r.style === 'str' ? this.font_size : Math.round(this.font_size * 0.5) 105 | } 106 | 107 | /** 108 | * For a range not to draw strings, split it to three parts left, center, and right. 109 | * @param rpx Given range to split. 110 | */ 111 | splitRangePx(rpx: RangePx): RangePx[] { 112 | const styles = rpx.style.split(',') 113 | 114 | let rl = Object.assign({}, rpx) 115 | let rc = Object.assign({}, rpx) 116 | let rr = Object.assign({}, rpx) 117 | rl.x_end = rpx.x_beg + this.curve_d() 118 | rl.style = styles[0] 119 | 120 | rr.x_beg = rpx.x_end 121 | rr.x_end = rpx.x_end - this.curve_d() 122 | rr.style = styles.length > 1 ? styles[1] : styles[0] 123 | 124 | rc.x_beg = rl.x_end 125 | rc.x_end = rr.x_end 126 | rc.style = 'line' 127 | return [rl, rc, rr] 128 | } 129 | 130 | /** 131 | * Draw curve as a part of a range. 132 | * @param rpx A part of a range. 133 | */ 134 | drawCurvePart(rpx: RangePx) { 135 | this.ctx.beginPath() 136 | this.ctx.moveTo(rpx.x_beg, rpx.y - this.curve_d()) 137 | this.ctx.quadraticCurveTo(rpx.x_beg, rpx.y, rpx.x_end, rpx.y) 138 | this.ctx.stroke() 139 | } 140 | 141 | /** 142 | * Return the length of a beginning (or ending) part of a range. 143 | */ 144 | curve_d(): number { 145 | return this.font_size_half / 2 146 | } 147 | 148 | /** 149 | * Draw line as a part of a range. 150 | * @param rpx A part of a range. 151 | */ 152 | drawLinePxPart(rpx: RangePx) { 153 | this.ctx.beginPath() 154 | this.ctx.moveTo(rpx.x_beg, rpx.y) 155 | this.ctx.lineTo(rpx.x_end, rpx.y) 156 | this.ctx.stroke() 157 | } 158 | 159 | /** 160 | * Draw arrow as a part of a range. 161 | * @param rpx A part of a range. 162 | */ 163 | drawArrowPxPart(rpx: RangePx) { 164 | const dx = this.curve_d() * (rpx.x_beg < rpx.x_end ? -1 : +1) 165 | this.drawLinePxPart(rpx) 166 | this.ctx.beginPath() 167 | this.ctx.moveTo(rpx.x_end + dx / 2, rpx.y + dx / 2) 168 | this.ctx.lineTo(rpx.x_end + dx, rpx.y) 169 | this.ctx.lineTo(rpx.x_end + dx / 2, rpx.y - dx / 2) 170 | this.ctx.stroke() 171 | } 172 | 173 | /** 174 | * Draw range as a part of a range. 175 | * @param rpx A part of a range. 176 | */ 177 | drawRangePxPart(rpx: RangePx) { 178 | if (rpx.style == 'line') { 179 | this.drawLinePxPart(rpx) 180 | } else if (rpx.style == 'curve') { 181 | this.drawCurvePart(rpx) 182 | } else if (rpx.style == 'arrow') { 183 | this.drawArrowPxPart(rpx) 184 | } 185 | } 186 | 187 | /** 188 | * Draw range. 189 | * @param rpx A range to draw. 190 | */ 191 | drawRangePx(rpx: RangePx) { 192 | if (rpx.style == 'line') { 193 | this.drawLinePxPart(rpx) 194 | } else { 195 | const [rl, rc, rr] = this.splitRangePx(rpx) 196 | this.drawRangePxPart(rl) 197 | this.drawRangePxPart(rc) 198 | this.drawRangePxPart(rr) 199 | } 200 | } 201 | 202 | /** 203 | * Draw strings. 204 | * @param r A range to draw strings. 205 | * @param y The y-coorinate to draw range `r`. 206 | */ 207 | drawStr(r: Range, y: number) { 208 | const rstr = r.str as string[] 209 | for (let i = 0; i < rstr.length; i++) { 210 | const c = rstr[i] 211 | const cx = this.str_x + (r.beg + i) * this.font_size 212 | this.ctx.fillText(c, cx, y + this.font_size * 0.3, this.font_size) 213 | this.ctx.beginPath() 214 | this.ctx.rect( 215 | cx - this.font_size_half, 216 | y - this.font_size_half, 217 | this.font_size, 218 | this.font_size, 219 | ) 220 | this.ctx.stroke() 221 | } 222 | } 223 | 224 | /** 225 | * Draw range. 226 | * @param r A range to draw. 227 | * @param y A y-coordinate to draw `r`. 228 | */ 229 | drawRange(r: Range, y: number) { 230 | this.ctx.strokeStyle = r.color 231 | let rpx = { 232 | x_beg: this.rangeBeg(r.beg), 233 | x_end: this.rangeEnd(r.end), 234 | y: y, 235 | style: r.style, 236 | color: r.color, 237 | str: r.str, 238 | } 239 | if (r.style == 'str') { 240 | this.drawStr(r, y) 241 | } else if (r.step === undefined) { 242 | this.drawRangePx(rpx) 243 | } else { 244 | let x_beg = this.rangeBeg(r.beg) 245 | for (let cur = r.beg + r.step - 1; cur < r.end; cur += r.step) { 246 | rpx.x_end = this.str_x + this.font_size * cur + this.font_size_half 247 | this.drawRangePx(rpx) 248 | rpx.x_beg = rpx.x_end 249 | } 250 | if ((r.end - r.beg + 1) % r.step === 0) { 251 | rpx.x_end = this.rangeEnd(r.end) 252 | this.drawRangePx(rpx) 253 | } else { 254 | // There is an uncomplete range. 255 | rpx.x_end = this.str_x + this.font_size * r.end + this.font_size_half 256 | rpx.style = r.style.split(',')[0] + ',line' 257 | this.drawRangePx(rpx) 258 | } 259 | } 260 | } 261 | 262 | /** 263 | * Draw ranges. 264 | * @param range_rows Ranges to draw. 265 | */ 266 | drawRanges(range_rows: Range[][]) { 267 | let ypx = this.str_y 268 | for (const ranges of range_rows) { 269 | const height = Math.max(...ranges.map(r => this.rangeHeight(r))) 270 | for (const range of ranges) { 271 | this.drawRange(range, ypx + height / 2) 272 | } 273 | ypx += height 274 | } 275 | } 276 | 277 | /** 278 | * Draw an input string. 279 | */ 280 | drawInputStr(input_str: string) { 281 | let index = ['i'] 282 | for (let i = 0; i < input_str.length; i++) index.push('' + i) 283 | let r = { 284 | style: 'str', 285 | color: '#000000', 286 | beg: -1, 287 | end: input_str.length - 1, 288 | str: index, 289 | } 290 | this.drawRange(r, this.str_y - this.font_size - this.font_size_half) 291 | const chars = ['Str'] 292 | for (let i = 0; i < input_str.length; i++) 293 | chars.push(input_str.substring(i, i + 1)) 294 | r.str = chars 295 | this.drawRange(r, this.str_y - this.font_size_half) 296 | } 297 | 298 | /** 299 | * Draw a given string and ranges. 300 | * @param input_str Input string to draw. 301 | * @param rss The ranges to draw which are related to a given string `input_str` 302 | */ 303 | draw(input_str: string, rss: Range[][]) { 304 | let range_bound = [-1, input_str.length - 1] 305 | rss.forEach(rs => 306 | rs.forEach( 307 | r => 308 | (range_bound = [ 309 | Math.min(range_bound[0], r.beg), 310 | Math.max(range_bound[1], r.end), 311 | ]), 312 | ), 313 | ) 314 | this.str_x = this.font_size + Math.abs(range_bound[0]) * this.font_size 315 | this.canvas.width = (range_bound[1] - range_bound[0] + 2) * this.font_size 316 | this.canvas.height = 317 | this.str_y + 318 | this.font_size_half + 319 | rss.reduce( 320 | (acm, rs) => acm + Math.max(...rs.map(r => this.rangeHeight(r))), 321 | 0, 322 | ) 323 | 324 | // DPI settings 325 | const dpr = window.devicePixelRatio || 1 326 | const rect = this.canvas.getBoundingClientRect() 327 | // console.log('dpr', dpr, ' rect', rect) 328 | this.canvas.width *= dpr 329 | this.canvas.height *= dpr 330 | this.ctx.scale(dpr, dpr) 331 | this.canvas.style.width = this.canvas.width / dpr + 'px' 332 | 333 | this.canvas.style.height = this.canvas.height / dpr + 'px' 334 | this.ctx.textAlign = 'center' 335 | this.ctx.lineWidth = 3 336 | this.ctx.font = this.font_size + 'px ' + this.font_type 337 | this.drawInputStr(input_str) 338 | this.drawRanges(rss) 339 | } 340 | 341 | /** 342 | * Make group that each contains a single range. 343 | * @param ranges The range list. 344 | */ 345 | makeSingleGroups(ranges: Range[]): Range[][] { 346 | return ranges.map(range => [range]) 347 | } 348 | 349 | /** 350 | * Return the grouped ranges that each contains non overlapping ranges. 351 | * @param Ts The range list. 352 | * @param rangef The function to return the tuple beginning index and ending index of a given range `Ts[i]`. 353 | */ 354 | nonOverlapObjs(Ts: T[], rangef: (arg0: T) => number[]): T[][] { 355 | if (Ts.length <= 0) return [] 356 | const ends = Ts.map(t => rangef(t)[1]) 357 | const n = Math.max(...ends) + 1 358 | let used = new Array(n) 359 | used.fill(false) 360 | let res = [] 361 | let rows: T[] = [] 362 | for (const t of Ts) { 363 | // check whether or not a range can be inserted to the current row. 364 | let used_any = false 365 | for (let i = rangef(t)[0]; i <= rangef(t)[1]; i++) { 366 | used_any = used_any || used[i] 367 | } 368 | if (used_any) { 369 | res.push(rows) 370 | rows = [t] 371 | used.fill(false) 372 | } else { 373 | rows.push(t) 374 | } 375 | for (let i = rangef(t)[0]; i <= rangef(t)[1]; i++) { 376 | used[i] = true 377 | } 378 | } 379 | if (rows.length > 0) res.push(rows) 380 | 381 | return res 382 | } 383 | 384 | /** 385 | * Return the grouped ranges that each contains non overlapping ranges. 386 | * @param rs The range list. 387 | */ 388 | nonOverlapRanges(rs: Range[]): Range[][] { 389 | return this.nonOverlapObjs(rs, r => [r.beg, r.end]) 390 | } 391 | 392 | /** 393 | * Return the grouped ranges that each contains non overlapping ranges. 394 | * @param rs The range list. 395 | */ 396 | nonOverlapRangesSimple(rs: RangeSimple[]): RangeSimple[][] { 397 | return this.nonOverlapObjs(rs, x => [x[0], x[1]]) 398 | } 399 | 400 | /** 401 | * Return the range list `rs` specified with the style `style`. 402 | * @param rs The range list. 403 | * @param style The style of the ranges `rs` to draw. 404 | */ 405 | makeGroupRangesAutoColor(rs: RangeSimple[][], style: string): Range[][] { 406 | let res = [] 407 | for (let i = 0; i < rs.length; i++) { 408 | const color = '#' + convert.hsv.hex([(i * 360) / rs.length, 80, 80]) 409 | res.push(this.makeRanges(rs[i], style, color)) 410 | } 411 | return res 412 | } 413 | 414 | /** 415 | * Return the range list `rs` specified with style `style` and `color`. 416 | * @param ranges The range list. 417 | * @param style The style of the ranges `rs` to draw. 418 | * @param color The color of the ranges `rs` to draw. 419 | */ 420 | makeRanges(ranges: RangeSimple[], style: string, color: string): Range[] { 421 | return ranges.map(range => { 422 | const is_str = 423 | typeof range[2] !== 'undefined' && typeof range[2] !== 'number' 424 | const step = typeof range[2] === 'number' ? range[2] : undefined 425 | const str = typeof range[2] !== 'number' ? range[2] : undefined 426 | return { 427 | style: is_str ? 'str' : style, 428 | color, 429 | beg: range[0], 430 | end: range[1], 431 | step, 432 | str, 433 | } 434 | }) 435 | } 436 | 437 | /** 438 | * Return the range list `rs` specified with the style `style`. 439 | * @param rs The range list. 440 | * @param style The style of the ranges `rs` to draw. 441 | */ 442 | makeRangesAutoColor(rs: RangeSimple[], style: string): Range[] { 443 | return rs.map((range, i) => ({ 444 | style, 445 | color: '#' + convert.hsv.hex([(i * 360) / rs.length, 80, 80]), 446 | beg: range[0], 447 | end: range[1], 448 | })) 449 | } 450 | } 451 | -------------------------------------------------------------------------------- /src/vis_str_demo.ts: -------------------------------------------------------------------------------- 1 | import { Range, RangeSimple, VisStr } from "./vis_str"; 2 | import * as strlib from "./strlib"; 3 | 4 | const substrings = (str: string): string[] => { 5 | const n = str.length; 6 | let res = new Set(); 7 | for (let i = 0; i < n; i++) { 8 | for (let j = i + 1; j <= n; j++) res.add(str.substring(i, j)); 9 | } 10 | return [...res.keys()]; 11 | }; 12 | 13 | const findAll = (str: string, pat: string): RangeSimple[] => { 14 | const m = pat.length; 15 | let res: RangeSimple[] = []; 16 | let pos = str.indexOf(pat); 17 | while (pos !== -1) { 18 | res.push([pos, pos + m - 1]); 19 | pos = str.indexOf(pat, pos + 1); 20 | } 21 | return res; 22 | }; 23 | 24 | // const isPalindrome = (str: string): boolean => { 25 | // for (let i = 0; i < str.length / 2; i++) { 26 | // if (str[i] != str[str.length - i - 1]) return false; 27 | // } 28 | // return true; 29 | // }; 30 | 31 | // export const enumPalindromes = (str: string): RangeSimple[] => { 32 | // const n = str.length; 33 | // let res: RangeSimple[] = []; 34 | // for (let len = 1; len < n; len++) { 35 | // for (let beg = 0; beg + len <= n; beg++) { 36 | // if (isPalindrome(str.substring(beg, beg + len))) 37 | // res.push([beg, beg + len - 1]); 38 | // } 39 | // } 40 | // return res; 41 | // }; 42 | const isPalindrome = strlib.isPalindrome; 43 | const enumPalindromes = strlib.enumPalindromes; 44 | 45 | const lcp = (str: string, i: number, j: number): number => { 46 | let n = str.length; 47 | let match_len = 0; 48 | while (i + match_len < n && j + match_len < n) { 49 | if (str[i + match_len] == str[j + match_len]) match_len++; 50 | else break; 51 | } 52 | return match_len; 53 | }; 54 | 55 | const prevOccLPF = (str: string): [number[], number[]] => { 56 | let prevOcc = []; 57 | let lpf = []; 58 | const n = str.length; 59 | for (let i = 0; i < n; i++) { 60 | let poccx = -1; 61 | let lpfx = 0; 62 | for (let j = 0; j < i; j++) { 63 | const l = lcp(str, i, j); 64 | if (lpfx < l) { 65 | lpfx = l; 66 | poccx = j; 67 | } 68 | } 69 | prevOcc.push(poccx); 70 | lpf.push(lpfx); 71 | } 72 | return [prevOcc, lpf]; 73 | }; 74 | 75 | const enumPrevOccLPF = (str: string): RangeSimple[][] => { 76 | const n = str.length; 77 | const [prevOcc, lpf] = prevOccLPF(str); 78 | let res: RangeSimple[][] = [ 79 | [[-1, n - 1, ["occ"].concat(prevOcc.map((x) => x.toString()))]], 80 | [[-1, n - 1, ["len"].concat(lpf.map((x) => x.toString()))]], 81 | ]; 82 | for (let i = 0; i < prevOcc.length; i++) { 83 | if (lpf[i] > 0) { 84 | res.push([ 85 | [i, i + lpf[i] - 1], 86 | [prevOcc[i], prevOcc[i] + lpf[i] - 1], 87 | ]); 88 | } 89 | } 90 | return res; 91 | }; 92 | 93 | const isSquare = (s: string, beg: number, p: number): boolean => { 94 | for (let i = 0; i < p; i++) { 95 | if (s[beg + i] != s[beg + p + i]) return false; 96 | } 97 | return true; 98 | }; 99 | 100 | const enumSquares = (s: string): RangeSimple[] => { 101 | const n = s.length; 102 | let res: RangeSimple[] = []; 103 | for (let p = 1; p < n; p++) { 104 | for (let offset = 0; offset < 2 * p; offset++) { 105 | for (let beg = offset; beg < n - 2 * p + 1; beg += 2 * p) { 106 | if (isSquare(s, beg, p)) { 107 | res.push([beg, beg + 2 * p - 1, p]); 108 | } 109 | } 110 | } 111 | } 112 | return res; 113 | }; 114 | 115 | const isRightmostSquare = (s: string, beg: number, p: number): boolean => { 116 | if (!isSquare(s, beg, p)) return false; 117 | return !s.includes(s.substr(beg, 2 * p), beg + 1); 118 | }; 119 | 120 | const isLeftmostSquare = (s: string, beg: number, p: number): boolean => { 121 | if (!isSquare(s, beg, p)) return false; 122 | return !s.substr(0, beg + 2 * p - 1).includes(s.substr(beg, 2 * p)); 123 | }; 124 | 125 | const enumRightmostSquares = (s: string): RangeSimple[] => { 126 | const n = s.length; 127 | let res: RangeSimple[] = []; 128 | for (let p = 1; p < n; p++) { 129 | for (let offset = 0; offset < 2 * p; offset++) { 130 | for (let beg = offset; beg < n - 2 * p + 1; beg += 2 * p) { 131 | if (isRightmostSquare(s, beg, p)) { 132 | res.push([beg, beg + 2 * p - 1, p]); 133 | } 134 | } 135 | } 136 | } 137 | return res; 138 | }; 139 | 140 | const enumLeftmostSquares = (s: string): RangeSimple[] => { 141 | const n = s.length; 142 | let res: RangeSimple[] = []; 143 | for (let p = 1; p < n; p++) { 144 | for (let offset = 0; offset < 2 * p; offset++) { 145 | for (let beg = offset; beg < n - 2 * p + 1; beg += 2 * p) { 146 | if (isLeftmostSquare(s, beg, p)) { 147 | res.push([beg, beg + 2 * p - 1, p]); 148 | } 149 | } 150 | } 151 | } 152 | return res; 153 | }; 154 | 155 | const isRun = (s: string, beg: number, p: number): boolean => { 156 | if (beg > 0 && s[beg - 1] == s[beg + p - 1]) return false; 157 | for (let i = 0; i < p; i++) { 158 | if (s[beg + i] != s[beg + p + i]) return false; 159 | } 160 | return true; 161 | }; 162 | 163 | const enumRuns = (s: string): RangeSimple[] => { 164 | const n = s.length; 165 | let res: RangeSimple[] = []; 166 | let rmap = new Set(); 167 | for (let p = 1; p < n; p++) { 168 | for (let beg = 0; beg + 2 * p <= n; beg++) { 169 | if (isRun(s, beg, p)) { 170 | let match = 2 * p; 171 | while (match < n && s[beg + (match % p)] == s[beg + match]) { 172 | match++; 173 | } 174 | const key = beg + "," + (beg + match - 1); 175 | if (!rmap.has(key)) { 176 | res.push([beg, beg + match - 1, p]); 177 | rmap.add(key); 178 | } 179 | } 180 | } 181 | } 182 | return res; 183 | }; 184 | 185 | const leftExtensions = (str: string, pat: string): string[] => { 186 | let res = new Set(); 187 | let fromIdx = 1; 188 | let pos = str.indexOf(pat, fromIdx); 189 | while (pos !== -1) { 190 | res.add(str[pos - 1]); 191 | pos = str.indexOf(pat, pos + 1); 192 | } 193 | return [...res.keys()]; 194 | }; 195 | 196 | const reverse = (str: string): string => { 197 | return str.split("").reverse().join(""); 198 | }; 199 | 200 | const rightExtensions = (str: string, pat: string): string[] => { 201 | const rstr = reverse(str); 202 | const rpat = reverse(pat); 203 | return leftExtensions(rstr, rpat); 204 | }; 205 | 206 | const isLeftMaximal = (str: string, pat: string): boolean => { 207 | return leftExtensions(str, pat).length > 1; 208 | }; 209 | 210 | const isRightMaximal = (str: string, pat: string): boolean => { 211 | return rightExtensions(str, pat).length > 1; 212 | }; 213 | 214 | const isMaxRepeat = (str: string, pat: string): boolean => { 215 | return isLeftMaximal(str, pat) && isRightMaximal(str, pat); 216 | }; 217 | 218 | const lz77 = (str: string, show_factorid: number = 1): RangeSimple[][] => { 219 | const n = str.length; 220 | const [occs, lens] = prevOccLPF(str); 221 | const res: RangeSimple[][] = []; 222 | 223 | for (let i = 0; i < n; ) { 224 | let ranges: RangeSimple[] = []; 225 | if (occs[i] === -1) { 226 | ranges = [[i, i, [str[i]]]]; 227 | i += 1; 228 | } else { 229 | ranges = [ 230 | [occs[i], occs[i] + lens[i] - 1], 231 | [i, i + lens[i] - 1], 232 | ]; 233 | i += lens[i]; 234 | } 235 | if (show_factorid >= 0) { 236 | const last_end = ranges[ranges.length - 1][1]; 237 | ranges.push([last_end + 1, last_end + 1, ["f" + show_factorid]]); 238 | show_factorid++; 239 | } 240 | res.push(ranges); 241 | } 242 | return res; 243 | }; 244 | 245 | const lz78 = (str: string, show_factorid = 1): RangeSimple[][] => { 246 | let d = new Map(); 247 | let res: RangeSimple[][] = []; 248 | for (var i = 0; i < str.length; ) { 249 | let j = i + 1; 250 | while (j <= str.length && d.has(str.substring(i, j))) { 251 | j++; 252 | } 253 | let row: RangeSimple[] = []; 254 | if (j - i > 1) { 255 | const prev = d.get(str.substring(i, j - 1)) as number; 256 | row.push([prev, prev + (j - i - 2)]); 257 | row.push([i, j - 2]); 258 | } 259 | if (j < str.length) { 260 | row.push([j - 1, j, [str[j - 1], "f" + show_factorid]]); 261 | } else { 262 | row.push([j - 1, j - 1, ["f" + show_factorid]]); 263 | } 264 | show_factorid++; 265 | res.push(row); 266 | d.set(str.substring(i, j), i); 267 | i = j; 268 | } 269 | return res; 270 | }; 271 | 272 | const isLyndon = (str: string): boolean => { 273 | for (let i = 1; i < str.length; i++) { 274 | let lessthan = false; 275 | for (let j = 0; j < str.length; j++) { 276 | const j2 = (i + j) % str.length; 277 | if (str[j] > str[j2]) return false; 278 | else if (str[j] < str[j2]) { 279 | lessthan = true; 280 | break; 281 | } 282 | } 283 | if (!lessthan) return false; 284 | } 285 | return true; 286 | }; 287 | 288 | // const enumLyndon = (str: string): RangeSimple[][] => { 289 | // const check = (str: string, pat: string) => isLyndon(pat) 290 | // return enumIfGroup(str, check) 291 | // } 292 | const enumLyndon = (str: string): RangeSimple[][] => { 293 | const res: RangeSimple[][] = []; 294 | for (let len = 1; len <= str.length; len++) { 295 | const group: RangeSimple[] = []; 296 | for (let i = 0; i + len <= str.length; i++) { 297 | const sub = str.substr(i, len); 298 | if (isLyndon(sub)) group.push([i, i + len - 1]); 299 | } 300 | if (group.length > 0) res.push(group); 301 | } 302 | return res; 303 | }; 304 | 305 | // Duval's algorithm 306 | // find longest lyndon factor which starts at beg in str. 307 | // return [len, repeat], where 308 | // len is the length of the factor, 309 | // repeat is the maximum repeat of the factor. 310 | const findLongestLyndonFactor = ( 311 | str: string, 312 | beg: number 313 | ): [number, number] => { 314 | let i = beg; 315 | let end = beg + 1; 316 | while (end < str.length && str[i] <= str[end]) { 317 | if (str[i] === str[end]) { 318 | i++; 319 | end++; 320 | } else if (str[i] < str[end]) { 321 | // str[beg...end] is Lyndon string 322 | i = beg; 323 | end++; 324 | } 325 | } 326 | // str[beg...end-1] is the longest Lyndon prefix of str[beg...]. 327 | const len = end - i; 328 | const repeat = Math.floor((end - beg) / (end - i)); 329 | return [len, repeat]; 330 | }; 331 | 332 | const lyndonFactorization = (str: string): RangeSimple[][] => { 333 | let res: RangeSimple[][] = []; 334 | let beg = 0; 335 | 336 | while (beg < str.length) { 337 | const factor = findLongestLyndonFactor(str, beg); 338 | const len_factor = factor[0] * factor[1]; 339 | res.push([[beg, beg + len_factor - 1, factor[0]]] as RangeSimple[]); 340 | beg += len_factor; 341 | } 342 | return res; 343 | }; 344 | 345 | const lyndonArray = (str: string): RangeSimple[][] => { 346 | const res: RangeSimple[][] = []; 347 | for (let i = 0; i < str.length; i++) { 348 | const factor = findLongestLyndonFactor(str, i); 349 | res.push([[i, i + factor[0] - 1]] as RangeSimple[]); 350 | } 351 | return res; 352 | }; 353 | 354 | // replace the characters to effective alphabet [0, sigma-1] 355 | // sigma is the number of distinct characters of given string 356 | // sigma must be less than 10 357 | const replaceEffectiveAlphabet = (str: string): string[] => { 358 | const chars = new Set(); 359 | for (let i = 0; i < str.length; i++) chars.add(str[i]); 360 | const arr = Array.from(chars.values()); 361 | arr.sort(); 362 | const rep = new Map(); 363 | arr.map((c, i) => rep.set(c, i.toString())); 364 | const reps: string[] = []; 365 | 366 | for (let i = 0; i < str.length; i++) reps.push(rep.get(str[i]) as string); 367 | return reps; 368 | // return reps.join('') 369 | }; 370 | 371 | const suffixArray = (str: string): number[] => { 372 | const suffixes = [...Array(str.length).keys()].map((i) => str.substr(i)); 373 | suffixes.sort(); 374 | return suffixes.map((s) => str.length - s.length); 375 | }; 376 | 377 | const rankArray = (str: string, sa?: number[]) => { 378 | if (sa === undefined) sa = suffixArray(str); 379 | const rank = Array(str.length); 380 | sa.forEach((pos, r) => (rank[pos] = r)); 381 | return rank; 382 | }; 383 | 384 | // next smaller suffixes 385 | const nssArray = (str: string, rank?: number[]) => { 386 | if (rank === undefined) rank = rankArray(str); 387 | const n = rank.length; 388 | const nssa = new Array(n); 389 | for (let i = 0; i < n; i++) { 390 | let nss = n; 391 | for (let j = i + 1; j < n; j++) { 392 | if (rank[i] > rank[j]) { 393 | nss = j; 394 | break; 395 | } 396 | } 397 | nssa[i] = nss; 398 | } 399 | return nssa; 400 | }; 401 | 402 | // next smaller suffixes 403 | const prevArray = (str: string, rank?: number[]) => { 404 | if (rank === undefined) rank = rankArray(str); 405 | const n = rank.length; 406 | const pssa = new Array(n); 407 | for (let i = 0; i < n; i++) { 408 | let pss = -1; 409 | for (let j = i - 1; j >= 0; j--) { 410 | if (rank[i] > rank[j]) { 411 | pss = j; 412 | break; 413 | } 414 | } 415 | pssa[i] = pss; 416 | } 417 | return pssa; 418 | }; 419 | 420 | const nextSmallerSuffixes = (str: string): RangeSimple[][] => { 421 | const nssa = nssArray(str); 422 | const res: RangeSimple[][] = []; 423 | for (let i = 0; i < str.length; i++) { 424 | const group: RangeSimple[] = [[i, nssa[i]]]; 425 | if (group.length > 0) res.push(group); 426 | } 427 | return res; 428 | }; 429 | 430 | const prevSmallerSuffixes = (str: string): RangeSimple[][] => { 431 | const pssa = prevArray(str); 432 | const res: RangeSimple[][] = []; 433 | for (let i = 0; i < str.length; i++) { 434 | const group: RangeSimple[] = [[pssa[i], i]]; 435 | if (group.length > 0) res.push(group); 436 | } 437 | return res; 438 | }; 439 | 440 | const enumIf = ( 441 | str: string, 442 | check: (s: string, p: string) => boolean 443 | ): RangeSimple[] => { 444 | return flat(enumIfGroup(str, check)); 445 | }; 446 | 447 | const enumIfGroup = ( 448 | str: string, 449 | check: (s: string, p: string) => boolean 450 | ): RangeSimple[][] => { 451 | return substrings(str) 452 | .filter((p) => check(str, p)) 453 | .map((p) => findAll(str, p)); 454 | }; 455 | 456 | const radioValue = (selector: string): string => { 457 | let res = ""; 458 | const elms = document.querySelectorAll(selector); 459 | for (let i = 0; i < elms.length; i++) { 460 | if (elms[i].checked) res = elms[i].value; 461 | } 462 | return res; 463 | }; 464 | 465 | const flat = (arr: T[][]): T[] => { 466 | return arr.reduce((acm, x) => acm.concat(x), [] as T[]); 467 | }; 468 | 469 | const draw = (e: Event) => { 470 | // get font size 471 | let font_size = parseInt(radioValue("[name=font_size]")); 472 | // get line style 473 | let range_style = radioValue("[name=line_style]"); 474 | const line_style_right = radioValue("[name=line_style_right]"); 475 | 476 | range_style += line_style_right.length === 0 ? "" : "," + line_style_right; 477 | let visualize = radioValue("[name=visualize]"); 478 | console.log( 479 | `font_size=${font_size}, line_style=${range_style}, visualize=${visualize}` 480 | ); 481 | 482 | // get input string 483 | const elm = document.querySelector("#input_str") as HTMLInputElement; 484 | let input_str = elm.value; 485 | 486 | // get canvas 487 | const canvas = document.querySelector("#canvas") as HTMLCanvasElement; 488 | // canvas.width = window.innerWidth - 50 489 | const visStr = new VisStr(canvas, (font_size = font_size)); 490 | 491 | // compute ranges 492 | let rangesp: RangeSimple[] = []; 493 | let ranges_group: RangeSimple[][] = []; 494 | let ranges: Range[][] = []; 495 | 496 | const show_effective_alphabet = ( 497 | document.getElementById("effective_alphabet") as HTMLInputElement 498 | ).checked; 499 | const show_rank_array = ( 500 | document.getElementById("rank_array") as HTMLInputElement 501 | ).checked; 502 | 503 | if (show_effective_alphabet) { 504 | ranges_group.push([ 505 | [ 506 | -1, 507 | input_str.length - 1, 508 | ["eStr", ...replaceEffectiveAlphabet(input_str)], 509 | ], 510 | ] as RangeSimple[]); 511 | } 512 | if (show_rank_array) { 513 | ranges_group.push([ 514 | [-1, input_str.length - 1, ["rank", ...rankArray(input_str)]], 515 | ] as RangeSimple[]); 516 | } 517 | 518 | if ( 519 | visualize === "runs" || 520 | visualize === "palindromes" || 521 | visualize === "squares" || 522 | visualize === "rmostsquares" || 523 | visualize === "lmostsquares" 524 | ) { 525 | if (visualize === "runs") { 526 | rangesp = enumRuns(input_str) as RangeSimple[]; 527 | } else if (visualize === "palindromes") { 528 | rangesp = enumPalindromes(input_str) as RangeSimple[]; 529 | } else if (visualize === "squares") { 530 | rangesp = enumSquares(input_str) as RangeSimple[]; 531 | } else if (visualize === "rmostsquares") { 532 | rangesp = enumRightmostSquares(input_str) as RangeSimple[]; 533 | } else if (visualize === "lmostsquares") { 534 | rangesp = enumLeftmostSquares(input_str) as RangeSimple[]; 535 | } 536 | console.log("rangesp", rangesp); 537 | ranges_group = ranges_group.concat( 538 | visStr.nonOverlapRangesSimple(rangesp) 539 | ); 540 | console.log("range_group", ranges_group); 541 | ranges = visStr.makeGroupRangesAutoColor(ranges_group, range_style); 542 | console.log("rangesp", ranges); 543 | } else { 544 | if (visualize === "lpf") 545 | ranges_group = ranges_group.concat(enumPrevOccLPF(input_str)); 546 | else if (visualize === "left_maximal") 547 | ranges_group = ranges_group.concat( 548 | enumIfGroup(input_str, isLeftMaximal) 549 | ); 550 | else if (visualize === "right_maximal") 551 | ranges_group = ranges_group.concat( 552 | enumIfGroup(input_str, isRightMaximal) 553 | ); 554 | else if (visualize === "max_repeat") 555 | ranges_group = ranges_group.concat( 556 | enumIfGroup(input_str, isMaxRepeat) 557 | ); 558 | else if (visualize === "lz77") 559 | ranges_group = ranges_group.concat(lz77(input_str)); 560 | else if (visualize === "lz78") 561 | ranges_group = ranges_group.concat(lz78(input_str)); 562 | else if (visualize === "lyndon_factorization") 563 | ranges_group = ranges_group.concat(lyndonFactorization(input_str)); 564 | else if (visualize === "lyndon_array") 565 | ranges_group = ranges_group.concat(lyndonArray(input_str)); 566 | else if (visualize === "enum_lyndon") 567 | ranges_group = ranges_group.concat(enumLyndon(input_str)); 568 | else if (visualize === "prev_smaller_suffix") 569 | ranges_group = ranges_group.concat(prevSmallerSuffixes(input_str)); 570 | else if (visualize === "next_smaller_suffix") 571 | ranges_group = ranges_group.concat(nextSmallerSuffixes(input_str)); 572 | ranges = visStr.makeGroupRangesAutoColor(ranges_group, range_style); 573 | ranges = flat(ranges.map((x) => visStr.nonOverlapRanges(x))); 574 | } 575 | 576 | visStr.draw(input_str, ranges); 577 | }; 578 | 579 | const selectorAddEvent = (selector: string, event: string, func: any) => { 580 | const elms = document.querySelectorAll(selector); 581 | for (let i = 0; i < elms.length; i++) { 582 | elms[i].addEventListener(event, func); 583 | } 584 | }; 585 | 586 | const main = () => { 587 | const input_str = document.getElementById("input_str") as HTMLElement; 588 | input_str.addEventListener("input", draw); 589 | input_str.addEventListener("propertychange", draw); 590 | 591 | // add event for radio buttons 592 | selectorAddEvent("[name=font_size]", "click", draw); 593 | selectorAddEvent("[name=line_style]", "click", draw); 594 | selectorAddEvent("[name=line_style_right]", "click", draw); 595 | selectorAddEvent("[name=visualize]", "click", draw); 596 | // selectorAddEvent('#effective_alphabet', 'click', draw) 597 | selectorAddEvent("[type=checkbox]", "click", draw); 598 | 599 | // draw initially. 600 | input_str.dispatchEvent( 601 | new CustomEvent("propertychange", { detail: "init event" }) 602 | ); 603 | }; 604 | 605 | main(); 606 | --------------------------------------------------------------------------------