Kennedy and Reingold-Tillford algorithms for tree node positioning
23

Configure Feed

Select the types of activity you want to include in your feed.

trees / src / render.ml
18 kB 408 lines
1type color = { r : int; g : int; b : int } 2 3type image = { 4 width : int; 5 height : int; 6 pixels : color array array; 7} 8 9let color r g b = { r; g; b } 10let bg_color = color 248 250 252 11let shadow_color = color 148 163 184 12let edge_color = color 203 213 225 13let node_fill = color 255 255 255 14let node_border = color 59 130 246 15let text_color = color 30 41 59 16 17let create_image width height = 18 { width; height; pixels = Array.make_matrix height width bg_color } 19 20let set_pixel img x y c = 21 if x >= 0 && x < img.width && y >= 0 && y < img.height then 22 img.pixels.(y).(x) <- c 23 24let blend c1 c2 t = 25 let t = max 0.0 (min 1.0 t) in 26 let inv = 1.0 -. t in 27 { r = int_of_float (float_of_int c1.r *. inv +. float_of_int c2.r *. t); 28 g = int_of_float (float_of_int c1.g *. inv +. float_of_int c2.g *. t); 29 b = int_of_float (float_of_int c1.b *. inv +. float_of_int c2.b *. t) } 30 31let set_pixel_alpha img x y c alpha = 32 if x >= 0 && x < img.width && y >= 0 && y < img.height then 33 img.pixels.(y).(x) <- blend img.pixels.(y).(x) c alpha 34 35let draw_shadow img cx cy radius = 36 let offset = 4 in 37 for dy = -radius - 8 to radius + 8 do 38 for dx = -radius - 8 to radius + 8 do 39 let dist = sqrt (float_of_int (dx * dx + dy * dy)) in 40 if dist <= float_of_int (radius + 6) then begin 41 let alpha = (1.0 -. (dist /. float_of_int (radius + 6))) *. 0.3 in 42 set_pixel_alpha img (cx + dx + offset) (cy + dy + offset) shadow_color alpha 43 end 44 done 45 done 46 47let node_gradient_top = color 255 255 255 48let node_gradient_bot = color 241 245 249 49 50let draw_node img cx cy radius = 51 for dy = -radius - 2 to radius + 2 do 52 for dx = -radius - 2 to radius + 2 do 53 let dist = sqrt (float_of_int (dx * dx + dy * dy)) in 54 let x, y = cx + dx, cy + dy in 55 let r = float_of_int radius in 56 if dist <= r -. 2.5 then begin 57 let grad_t = (float_of_int dy +. r) /. (2.0 *. r) in 58 let fill = blend node_gradient_top node_gradient_bot grad_t in 59 set_pixel img x y fill 60 end 61 else if dist <= r then 62 set_pixel img x y node_border 63 else if dist <= r +. 1.0 then 64 let alpha = 1.0 -. (dist -. r) in 65 set_pixel_alpha img x y node_border alpha 66 done 67 done 68 69let draw_line_smooth img x0 y0 x1 y1 thickness c = 70 let fx0, fy0 = float_of_int x0, float_of_int y0 in 71 let fx1, fy1 = float_of_int x1, float_of_int y1 in 72 let dist = sqrt ((fx1 -. fx0) ** 2.0 +. (fy1 -. fy0) ** 2.0) in 73 let steps = int_of_float (dist *. 2.0) + 1 in 74 for i = 0 to steps do 75 let t = float_of_int i /. float_of_int steps in 76 let x = fx0 +. t *. (fx1 -. fx0) in 77 let y = fy0 +. t *. (fy1 -. fy0) in 78 let r = float_of_int thickness in 79 for dy = int_of_float (-.r -. 1.0) to int_of_float (r +. 1.0) do 80 for dx = int_of_float (-.r -. 1.0) to int_of_float (r +. 1.0) do 81 let d = sqrt (float_of_int (dx * dx + dy * dy)) in 82 if d <= r +. 0.5 then begin 83 let alpha = if d <= r -. 0.5 then 1.0 else (r +. 0.5 -. d) in 84 set_pixel_alpha img (int_of_float x + dx) (int_of_float y + dy) c alpha 85 end 86 done 87 done 88 done 89 90let draw_arc img cx cy radius start_angle end_angle thickness c = 91 let steps = int_of_float (abs_float (end_angle -. start_angle) *. float_of_int radius *. 2.0) + 10 in 92 for i = 0 to steps do 93 let t = float_of_int i /. float_of_int steps in 94 let angle = start_angle +. t *. (end_angle -. start_angle) in 95 let x = float_of_int cx +. float_of_int radius *. cos angle in 96 let y = float_of_int cy +. float_of_int radius *. sin angle in 97 let r = float_of_int thickness in 98 for dy = int_of_float (-.r -. 1.0) to int_of_float (r +. 1.0) do 99 for dx = int_of_float (-.r -. 1.0) to int_of_float (r +. 1.0) do 100 let d = sqrt (float_of_int (dx * dx + dy * dy)) in 101 if d <= r +. 0.5 then begin 102 let alpha = if d <= r -. 0.5 then 1.0 else (r +. 0.5 -. d) in 103 set_pixel_alpha img (int_of_float x + dx) (int_of_float y + dy) c alpha 104 end 105 done 106 done 107 done 108 109let pi = 3.14159265359 110 111let draw_glyph img cx cy size ch = 112 let t = max 1 (size / 8) in 113 let w = size * 6 / 10 in 114 let h = size in 115 let x0 = cx - w / 2 in 116 let x1 = cx + w / 2 in 117 let y0 = cy - h / 2 in 118 let y1 = cy + h / 2 in 119 let ymid = cy in 120 let xmid = cx in 121 let r = w / 2 in 122 match ch with 123 | 'A' -> draw_line_smooth img x0 y1 xmid y0 t text_color; 124 draw_line_smooth img xmid y0 x1 y1 t text_color; 125 draw_line_smooth img (x0 + w/4) ymid (x1 - w/4) ymid t text_color 126 | 'B' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 127 draw_line_smooth img x0 y0 (xmid - t) y0 t text_color; 128 draw_line_smooth img x0 ymid (xmid - t) ymid t text_color; 129 draw_line_smooth img x0 y1 (xmid - t) y1 t text_color; 130 draw_arc img (xmid - t) (y0 + (ymid - y0) / 2) ((ymid - y0) / 2) (-.pi /. 2.0) (pi /. 2.0) t text_color; 131 draw_arc img (xmid - t) (ymid + (y1 - ymid) / 2) ((y1 - ymid) / 2) (-.pi /. 2.0) (pi /. 2.0) t text_color 132 | 'C' -> draw_arc img xmid cy r (pi *. 0.3) (pi *. 1.7) t text_color 133 | 'D' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 134 draw_line_smooth img x0 y0 (xmid - r/2) y0 t text_color; 135 draw_line_smooth img x0 y1 (xmid - r/2) y1 t text_color; 136 draw_arc img (xmid - r/2) cy (h / 2) (-.pi /. 2.0) (pi /. 2.0) t text_color 137 | 'E' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 138 draw_line_smooth img x0 y0 x1 y0 t text_color; 139 draw_line_smooth img x0 ymid (x1 - w/4) ymid t text_color; 140 draw_line_smooth img x0 y1 x1 y1 t text_color 141 | 'F' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 142 draw_line_smooth img x0 y0 x1 y0 t text_color; 143 draw_line_smooth img x0 ymid (x1 - w/4) ymid t text_color 144 | 'G' -> draw_arc img xmid cy r (pi *. 0.3) (pi *. 1.7) t text_color; 145 draw_line_smooth img xmid ymid x1 ymid t text_color; 146 draw_line_smooth img x1 ymid x1 (ymid + r/2) t text_color 147 | 'H' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 148 draw_line_smooth img x1 y0 x1 y1 t text_color; 149 draw_line_smooth img x0 ymid x1 ymid t text_color 150 | 'I' -> draw_line_smooth img xmid y0 xmid y1 t text_color; 151 draw_line_smooth img (xmid - w/3) y0 (xmid + w/3) y0 t text_color; 152 draw_line_smooth img (xmid - w/3) y1 (xmid + w/3) y1 t text_color 153 | 'J' -> draw_line_smooth img x1 y0 x1 (y1 - r) t text_color; 154 draw_arc img (x1 - r) (y1 - r) r 0.0 (pi *. 0.6) t text_color 155 | 'K' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 156 draw_line_smooth img x0 ymid x1 y0 t text_color; 157 draw_line_smooth img x0 ymid x1 y1 t text_color 158 | 'L' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 159 draw_line_smooth img x0 y1 x1 y1 t text_color 160 | 'M' -> draw_line_smooth img x0 y1 x0 y0 t text_color; 161 draw_line_smooth img x0 y0 xmid ymid t text_color; 162 draw_line_smooth img xmid ymid x1 y0 t text_color; 163 draw_line_smooth img x1 y0 x1 y1 t text_color 164 | 'N' -> draw_line_smooth img x0 y1 x0 y0 t text_color; 165 draw_line_smooth img x0 y0 x1 y1 t text_color; 166 draw_line_smooth img x1 y1 x1 y0 t text_color 167 | 'O' -> draw_arc img xmid cy r 0.0 (2.0 *. pi) t text_color 168 | 'P' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 169 draw_line_smooth img x0 y0 (xmid - t) y0 t text_color; 170 draw_line_smooth img x0 ymid (xmid - t) ymid t text_color; 171 draw_arc img (xmid - t) (y0 + (ymid - y0) / 2) ((ymid - y0) / 2) (-.pi /. 2.0) (pi /. 2.0) t text_color 172 | 'Q' -> draw_arc img xmid cy r 0.0 (2.0 *. pi) t text_color; 173 draw_line_smooth img xmid ymid (x1 + t) (y1 + t) t text_color 174 | 'R' -> draw_line_smooth img x0 y0 x0 y1 t text_color; 175 draw_line_smooth img x0 y0 (xmid - t) y0 t text_color; 176 draw_line_smooth img x0 ymid (xmid - t) ymid t text_color; 177 draw_arc img (xmid - t) (y0 + (ymid - y0) / 2) ((ymid - y0) / 2) (-.pi /. 2.0) (pi /. 2.0) t text_color; 178 draw_line_smooth img xmid ymid x1 y1 t text_color 179 | 'S' -> draw_arc img xmid (y0 + h/4) (h/4) (pi *. 0.9) (pi *. 2.3) t text_color; 180 draw_arc img xmid (y1 - h/4) (h/4) (pi *. (-0.1)) (pi *. 1.3) t text_color 181 | 'T' -> draw_line_smooth img x0 y0 x1 y0 t text_color; 182 draw_line_smooth img xmid y0 xmid y1 t text_color 183 | 'U' -> draw_line_smooth img x0 y0 x0 (y1 - r) t text_color; 184 draw_line_smooth img x1 y0 x1 (y1 - r) t text_color; 185 draw_arc img xmid (y1 - r) r pi (2.0 *. pi) t text_color 186 | 'V' -> draw_line_smooth img x0 y0 xmid y1 t text_color; 187 draw_line_smooth img xmid y1 x1 y0 t text_color 188 | 'W' -> draw_line_smooth img x0 y0 (x0 + w/4) y1 t text_color; 189 draw_line_smooth img (x0 + w/4) y1 xmid (ymid + h/6) t text_color; 190 draw_line_smooth img xmid (ymid + h/6) (x1 - w/4) y1 t text_color; 191 draw_line_smooth img (x1 - w/4) y1 x1 y0 t text_color 192 | 'X' -> draw_line_smooth img x0 y0 x1 y1 t text_color; 193 draw_line_smooth img x0 y1 x1 y0 t text_color 194 | 'Y' -> draw_line_smooth img x0 y0 xmid ymid t text_color; 195 draw_line_smooth img x1 y0 xmid ymid t text_color; 196 draw_line_smooth img xmid ymid xmid y1 t text_color 197 | 'Z' -> draw_line_smooth img x0 y0 x1 y0 t text_color; 198 draw_line_smooth img x1 y0 x0 y1 t text_color; 199 draw_line_smooth img x0 y1 x1 y1 t text_color 200 | '0' -> draw_arc img xmid cy r 0.0 (2.0 *. pi) t text_color; 201 draw_line_smooth img (x0 + w/4) (y1 - h/4) (x1 - w/4) (y0 + h/4) t text_color 202 | '1' -> draw_line_smooth img xmid y0 xmid y1 t text_color; 203 draw_line_smooth img (xmid - w/3) (y0 + h/4) xmid y0 t text_color; 204 draw_line_smooth img (xmid - w/3) y1 (xmid + w/3) y1 t text_color 205 | '2' -> draw_arc img xmid (y0 + h/3) (h/3) (pi *. 1.2) (pi *. 2.1) t text_color; 206 draw_line_smooth img (x1 - t/2) (y0 + h/3) x0 y1 t text_color; 207 draw_line_smooth img x0 y1 x1 y1 t text_color 208 | '3' -> draw_arc img xmid (y0 + h/4) (h/4) (pi *. 1.1) (pi *. 2.0) t text_color; 209 draw_arc img xmid (y1 - h/4) (h/4) 0.0 (pi *. 0.9) t text_color 210 | '4' -> draw_line_smooth img (x1 - w/4) y0 (x1 - w/4) y1 t text_color; 211 draw_line_smooth img (x1 - w/4) y0 x0 (ymid + h/8) t text_color; 212 draw_line_smooth img x0 (ymid + h/8) x1 (ymid + h/8) t text_color 213 | '5' -> draw_line_smooth img x1 y0 x0 y0 t text_color; 214 draw_line_smooth img x0 y0 x0 ymid t text_color; 215 draw_line_smooth img x0 ymid (xmid - t) ymid t text_color; 216 draw_arc img (xmid - t) (ymid + (y1 - ymid) / 2) ((y1 - ymid) / 2) (-.pi /. 2.0) (pi *. 0.7) t text_color 217 | '6' -> draw_arc img xmid (y1 - h/3) (h/3) 0.0 (2.0 *. pi) t text_color; 218 draw_arc img (x1 + w/5) (y0 + h/3) (h/2) (pi *. 0.7) (pi *. 1.0) t text_color 219 | '7' -> draw_line_smooth img x0 y0 x1 y0 t text_color; 220 draw_line_smooth img x1 y0 (x0 + w/4) y1 t text_color 221 | '8' -> draw_arc img xmid (y0 + h/4) (h/4 - t/2) 0.0 (2.0 *. pi) t text_color; 222 draw_arc img xmid (y1 - h/4) (h/4) 0.0 (2.0 *. pi) t text_color 223 | '9' -> draw_arc img xmid (y0 + h/3) (h/3) 0.0 (2.0 *. pi) t text_color; 224 draw_arc img (x0 - w/5) (y1 - h/3) (h/2) (-.pi *. 0.3) 0.0 t text_color 225 | '-' -> draw_line_smooth img (x0 + w/4) ymid (x1 - w/4) ymid t text_color 226 | _ -> () 227 228let draw_text img cx cy radius text = 229 let len = String.length text in 230 let max_w = float_of_int radius *. 1.3 in 231 let max_h = float_of_int radius *. 0.7 in 232 let char_size_w = int_of_float (max_w /. (float_of_int len *. 0.7)) in 233 let char_size_h = int_of_float max_h in 234 let char_size = min char_size_w char_size_h in 235 let char_w = char_size * 7 / 10 in 236 let total_w = len * char_w in 237 let start_x = cx - total_w / 2 + char_w / 2 in 238 String.iteri (fun i ch -> 239 let upper = if ch >= 'a' && ch <= 'z' then Char.chr (Char.code ch - 32) else ch in 240 draw_glyph img (start_x + i * char_w) cy char_size upper 241 ) text 242 243let bezier_point p0 p1 p2 p3 t = 244 let inv = 1.0 -. t in 245 let inv2 = inv *. inv in 246 let inv3 = inv2 *. inv in 247 let t2 = t *. t in 248 let t3 = t2 *. t in 249 inv3 *. p0 +. 3.0 *. inv2 *. t *. p1 +. 3.0 *. inv *. t2 *. p2 +. t3 *. p3 250 251let draw_bezier_aa img x0 y0 x1 y1 x2 y2 x3 y3 thickness c = 252 let steps = int_of_float (sqrt ((x3-.x0)**2.0 +. (y3-.y0)**2.0) *. 3.0) in 253 let steps = max steps 80 in 254 for i = 0 to steps do 255 let t = float_of_int i /. float_of_int steps in 256 let x = bezier_point x0 x1 x2 x3 t in 257 let y = bezier_point y0 y1 y2 y3 t in 258 for dx = -thickness - 1 to thickness + 1 do 259 for dy = -thickness - 1 to thickness + 1 do 260 let dist = sqrt (float_of_int (dx * dx + dy * dy)) in 261 if dist <= float_of_int thickness +. 0.8 then begin 262 let alpha = if dist <= float_of_int thickness then 1.0 263 else 1.0 -. (dist -. float_of_int thickness) /. 0.8 in 264 set_pixel_alpha img (int_of_float x + dx) (int_of_float y + dy) c alpha 265 end 266 done 267 done 268 done 269 270let draw_curved_edge img x0 y0 x1 y1 = 271 let fx0, fy0 = float_of_int x0, float_of_int y0 in 272 let fx1, fy1 = float_of_int x1, float_of_int y1 in 273 let ctrl_y = fy0 +. (fy1 -. fy0) *. 0.4 in 274 draw_bezier_aa img fx0 fy0 fx0 ctrl_y fx1 (fy1 -. (fy1 -. fy0) *. 0.4) fx1 fy1 2 edge_color 275 276let write_ppm filename img = 277 let oc = open_out filename in 278 Printf.fprintf oc "P3\n%d %d\n255\n" img.width img.height; 279 for y = 0 to img.height - 1 do 280 for x = 0 to img.width - 1 do 281 let p = img.pixels.(y).(x) in 282 Printf.fprintf oc "%d %d %d " p.r p.g p.b 283 done; 284 output_char oc '\n' 285 done; 286 close_out oc 287 288let crc32_table = 289 Array.init 256 (fun n -> 290 let c = ref (Int32.of_int n) in 291 for _ = 0 to 7 do 292 if Int32.(logand !c 1l = 1l) then 293 c := Int32.(logxor (shift_right_logical !c 1) 0xEDB88320l) 294 else 295 c := Int32.shift_right_logical !c 1 296 done; 297 !c) 298 299let crc32 data = 300 let crc = ref 0xFFFFFFFFl in 301 Bytes.iter (fun b -> 302 let idx = Int32.(to_int (logand (logxor !crc (of_int (Char.code b))) 0xFFl)) in 303 crc := Int32.(logxor (shift_right_logical !crc 8) crc32_table.(idx)) 304 ) data; 305 Int32.logxor !crc 0xFFFFFFFFl 306 307let adler32 data = 308 let a = ref 1 and b = ref 0 in 309 Bytes.iter (fun c -> 310 a := (!a + Char.code c) mod 65521; 311 b := (!b + !a) mod 65521 312 ) data; 313 Int32.(logor (shift_left (of_int !b) 16) (of_int !a)) 314 315let write_png filename img = 316 let oc = open_out_bin filename in 317 let write_byte b = output_byte oc b in 318 let write_int32_be n = 319 write_byte (Int32.to_int (Int32.shift_right_logical n 24) land 0xFF); 320 write_byte (Int32.to_int (Int32.shift_right_logical n 16) land 0xFF); 321 write_byte (Int32.to_int (Int32.shift_right_logical n 8) land 0xFF); 322 write_byte (Int32.to_int n land 0xFF) 323 in 324 let write_chunk chunk_type data = 325 write_int32_be (Int32.of_int (Bytes.length data)); 326 let type_and_data = Bytes.cat (Bytes.of_string chunk_type) data in 327 output_bytes oc type_and_data; 328 write_int32_be (crc32 type_and_data) 329 in 330 output_string oc "\137PNG\r\n\026\n"; 331 let ihdr = Bytes.create 13 in 332 Bytes.set_int32_be ihdr 0 (Int32.of_int img.width); 333 Bytes.set_int32_be ihdr 4 (Int32.of_int img.height); 334 Bytes.set ihdr 8 '\008'; Bytes.set ihdr 9 '\002'; 335 Bytes.set ihdr 10 '\000'; Bytes.set ihdr 11 '\000'; Bytes.set ihdr 12 '\000'; 336 write_chunk "IHDR" ihdr; 337 let raw_size = img.height * (1 + img.width * 3) in 338 let raw = Bytes.create raw_size in 339 let pos = ref 0 in 340 for y = 0 to img.height - 1 do 341 Bytes.set raw !pos '\000'; incr pos; 342 for x = 0 to img.width - 1 do 343 let p = img.pixels.(y).(x) in 344 Bytes.set raw !pos (Char.chr p.r); incr pos; 345 Bytes.set raw !pos (Char.chr p.g); incr pos; 346 Bytes.set raw !pos (Char.chr p.b); incr pos 347 done 348 done; 349 let max_block = 65535 in 350 let num_blocks = (raw_size + max_block - 1) / max_block in 351 let zlib_size = 2 + raw_size + num_blocks * 5 + 4 in 352 let zlib = Bytes.create zlib_size in 353 Bytes.set zlib 0 '\x78'; Bytes.set zlib 1 '\x01'; 354 let zpos = ref 2 in 355 let rpos = ref 0 in 356 for i = 0 to num_blocks - 1 do 357 let remaining = raw_size - !rpos in 358 let block_len = min max_block remaining in 359 let is_last = i = num_blocks - 1 in 360 Bytes.set zlib !zpos (if is_last then '\x01' else '\x00'); incr zpos; 361 Bytes.set zlib !zpos (Char.chr (block_len land 0xFF)); incr zpos; 362 Bytes.set zlib !zpos (Char.chr ((block_len lsr 8) land 0xFF)); incr zpos; 363 let nlen = block_len lxor 0xFFFF in 364 Bytes.set zlib !zpos (Char.chr (nlen land 0xFF)); incr zpos; 365 Bytes.set zlib !zpos (Char.chr ((nlen lsr 8) land 0xFF)); incr zpos; 366 Bytes.blit raw !rpos zlib !zpos block_len; 367 zpos := !zpos + block_len; 368 rpos := !rpos + block_len 369 done; 370 let adler = adler32 raw in 371 Bytes.set_int32_be zlib !zpos adler; 372 write_chunk "IDAT" zlib; 373 write_chunk "IEND" Bytes.empty; 374 close_out oc 375 376let render_tree ~filename ~node_radius ~h_scale ~v_scale ~padding nodes edges = 377 let min_x = List.fold_left (fun acc (_, x, _) -> min acc x) 0.0 nodes in 378 let max_x = List.fold_left (fun acc (_, x, _) -> max acc x) 0.0 nodes in 379 let max_depth = List.fold_left (fun acc (_, _, d) -> max acc d) 0 nodes in 380 381 let width = int_of_float ((max_x -. min_x) *. h_scale) + 2 * padding + 2 * node_radius in 382 let height = (max_depth + 1) * v_scale + 2 * padding in 383 384 let img = create_image width height in 385 386 let to_pixel_x x = int_of_float ((x -. min_x) *. h_scale) + padding + node_radius in 387 let to_pixel_y depth = depth * v_scale + padding + node_radius in 388 389 List.iter (fun (px, py, cx, cy) -> 390 let px', py' = to_pixel_x px, to_pixel_y py in 391 let cx', cy' = to_pixel_x cx, to_pixel_y cy in 392 draw_curved_edge img px' py' cx' cy' 393 ) edges; 394 395 List.iter (fun (_, x, depth) -> 396 let px, py = to_pixel_x x, to_pixel_y depth in 397 draw_shadow img px py node_radius 398 ) nodes; 399 400 List.iter (fun (_, x, depth) -> 401 let px, py = to_pixel_x x, to_pixel_y depth in 402 draw_node img px py node_radius 403 ) nodes; 404 405 let is_png = String.length filename >= 4 && 406 String.sub filename (String.length filename - 4) 4 = ".png" in 407 if is_png then write_png filename img else write_ppm filename img; 408 Printf.printf "Wrote %s (%dx%d)\n" filename width height