Rebol [ Title: "Spider" File: %spider.r Author: "Ladislav Mecir" Date: 19-Apr-2007/21:19:18+2:00 History: [ 17/Nov/2004/9:03 "First working version" 23/Nov/2004/14:10 {Multiple data series allowed, dialect enhanced} 25/Nov/2004/10:12 {Autoscale bug (order-related) corrected} 28/Nov/2004/11:20 {Compatibility 1.2.10 thru 1.2.56} 30-Aug-2006/17:24:02+2:00 {Starting "at the top", continuing clockwise} 8-Sep-2006/16:40:58+2:00 {Autoscale rounding, scale correction} 19-Apr-2007/21:19:18+2:00 {to pair! reduce [] replaced by as-pair} ] Description: { Keywords: size - takes a pair! argument - the size of the chart offset - takes a pair! - the offset of the chart font - takes a font - defines the current font (should be transparent) directions - no argument - draws the data directions scale - block! or integer! - draws the scale categories - block of strings - describes the data categories data - block of numbers - can be used multiple times to define data series Keywords are functions evaluating their arguments. Draw commands are allowed in the Spider chart description block. } ] #include-check %lfunc.r #include-check %build.r comment [ ; usage: view layout build/with [ box 600x600 effect [ draw [ spider [ size 600x600 ; offset 100x100 pen black scale 4 ; scale [0 150 300 450 600] categories [ "Category 1" "Category 2" "Category 3" "Category 4" "Category 5" "Category 6" "Category 7" "Category 8" ] directions pen red data [100 200 300 400 500 600 700 800] pen blue data [100 100 100 100 100 100 100 100] ] ] ] ] [spider: :spider*] ] spider*: lfunc [ [catch] description [block!] {a block containing a description of a chart} ] [] [ ; initialization current-font: default-font: make face/font [] data-max: 0.0 ; maximal data value size-data: 200x200 ; the size of the chart offset-data: 0x0 ; the position of the upper left edge of the chart scale-data: none ; scale scale-font: current-font scale-max: none categories-data: none ; list of strings describing data category-font: current-font data-data: make block! 0 ; charted data ; collect data description: build/with description [ ; set the chart size size: func [size [pair!]] [size-data: size []] ; set the chart offset offset: func [offset [pair!]] [offset-data: offset []] ; set the current font font: func [fnt [object!]] [ current-font: fnt reduce ['font fnt] ] ; set the scale scale: func [scale [block! integer!]] [ scale-data: scale if block? scale [ scale-max: first maximum-of scale data-max: max data-max scale-max ] scale-font: current-font 'scale ] ; set the data data: func [data [block!]] [ data-max: max data-max first maximum-of data insert/only tail data-data data reduce ['data data] ] ; describe categories categories: func [cat [block!]] [ categories-data: cat category-font: current-font 'categories ] ] ; set the default font insert description reduce ['font default-font] ; check data if empty? data-data [throw make error! "No data"] ; compute the data count data-count: length? data-data/1 ; check data count consistence if all [categories-data data-count <> length? categories-data] [ throw make error! "Categories don't correspond to data" ] foreach series data-data [ if data-count <> length? series [ throw make error! "Data series inconsistence" ] ] ; compute the chart centre chart-centre: as-pair round size-data/x / 2 + offset-data/x round size-data/y / 2 + offset-data/y spider-radius: round/floor min size-data/x / 2 size-data/y / 2 ; default category space is zero category-horiz: 0 category-vert: 0 ; auxiliary variables f: none angle: none direction: none position-x: none position-y: none ; initialize direction block direction-block: make block! data-count repeat i data-count [ angle: 360 / data-count * (i - 1) direction: reduce [sine angle cosine angle] insert/only tail direction-block direction ] if categories-data [ repeat i data-count [ direction: direction-block/:i ; compute size of the category text layout [f: text categories-data/:i with [font: category-font]] f: size-text f insert tail direction f ; compute category space case [ direction/1 > 0.0 [ position-x: direction/1 * spider-radius - spider-radius + f/x position-y: 0 ] direction/1 < 0.0 [ position-x: - direction/1 * spider-radius - spider-radius + f/x position-y: 0 ] true [ position-x: f/x / 2 position-y: f/y ] ] category-horiz: max category-horiz position-x category-vert: max category-vert position-y ] category-horiz: category-font/size + category-horiz category-vert: category-font/size + category-vert spider-radius: round/floor min size-data/x / 2 - category-horiz size-data/y / 2 - category-vert ] ; auto scale if needed auto-scale-ticks: none auto-scale-factor: none if integer? scale-data [ auto-scale-ticks: scale-data scale-data: make block! auto-scale-ticks + 1 insert scale-data 0 auto-scale-factor: power 10.0 round/floor (log-10 data-max) - 2 scale-max: round/floor/to data-max auto-scale-factor repeat i auto-scale-ticks [ append scale-data round/to scale-max / auto-scale-ticks * i auto-scale-factor ] ] spider-ratio: spider-radius / data-max ; draw the graph build/with description [ scale: lfunc [] [] [ chart: make block! 1 + (3 + data-count * length? scale-data) ; scale net foreach tick next scale-data [ insert tail chart 'polygon repeat i data-count [ position-x: direction-block/:i/1 * spider-ratio * tick position-y: direction-block/:i/2 * spider-ratio * tick insert tail chart as-pair round chart-centre/x + position-x round chart-centre/y - position-y ] ] ; scale texts position-x: chart-centre/x - scale-font/size string: none foreach tick scale-data [ string: form tick layout [f: text string with [font: scale-font]] f: size-text f position-y: as-pair round position-x - f/x round chart-centre/y - (spider-ratio * tick) - (scale-font/size / 2) insert tail chart reduce ['text position-y string] ] chart ] data: lfunc [ {Draw the data} data ] [] [ chart: make block! 1 + data-count insert tail chart 'polygon repeat i data-count [ position-x: direction-block/:i/1 * spider-ratio * data/:i position-y: direction-block/:i/2 * spider-ratio * data/:i insert tail chart as-pair round chart-centre/x + position-x round chart-centre/y - position-y ] chart ] directions: lfunc [] [] [ chart: make block! 3 * data-count repeat i data-count [ direction: direction-block/:i position-x: direction/1 * spider-radius position-y: direction/2 * spider-radius position-x: as-pair round chart-centre/x + position-x round chart-centre/y - position-y insert tail chart reduce ['line chart-centre position-x] ] chart ] categories: lfunc [] [] [ chart: make block! 3 * data-count repeat i data-count [ direction: direction-block/:i f: direction/3 position-x: direction/1 * spider-radius position-y: direction/2 * spider-radius case [ direction/1 > 0.0 [ position-x: position-x + (category-font/size / 2) position-y: position-y + (f/y / 2) ] all [direction/1 = 0.0 direction/2 > 0.0] [ position-x: position-x - (f/x / 2) position-y: position-y + (category-font/size / 2) + f/y ] direction/1 < 0.0 [ position-x: position-x - f/x - (category-font/size / 2) position-y: position-y + (f/y / 2) ] true [ position-x: position-x - (f/x / 2) position-y: position-y - (category-font/size / 2) ] ] position-x: as-pair round chart-centre/x + position-x round chart-centre/y - position-y insert tail chart reduce ['text position-x categories-data/:i] ] chart ] ] ]